aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/api')
-rw-r--r--src/nvim/api/buffer.c273
-rw-r--r--src/nvim/api/private/defs.h1
-rw-r--r--src/nvim/api/private/helpers.c36
-rw-r--r--src/nvim/api/tabpage.c29
-rw-r--r--src/nvim/api/ui.c343
-rw-r--r--src/nvim/api/ui.h11
-rw-r--r--src/nvim/api/vim.c38
-rw-r--r--src/nvim/api/window.c33
8 files changed, 714 insertions, 50 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index b7a86af134..55b535c78c 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -19,6 +19,7 @@
#include "nvim/mark.h"
#include "nvim/fileio.h"
#include "nvim/move.h"
+#include "nvim/syntax.h"
#include "nvim/window.h"
#include "nvim/undo.h"
@@ -44,14 +45,22 @@ Integer buffer_line_count(Buffer buffer, Error *err)
/// Gets a buffer line
///
+/// @deprecated use buffer_get_lines instead.
+/// for positive indices (including 0) use
+/// "buffer_get_lines(buffer, index, index+1, true)"
+/// for negative indices use
+/// "buffer_get_lines(buffer, index-1, index, true)"
+///
/// @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, Integer index, Error *err)
{
- String rv = {.size = 0};
- Array slice = buffer_get_line_slice(buffer, index, index, true, true, err);
+ String rv = { .size = 0 };
+
+ index = convert_index(index);
+ Array slice = buffer_get_lines(buffer, index, index+1, true, err);
if (!err->set && slice.size) {
rv = slice.items[0].data.string;
@@ -64,6 +73,12 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err)
/// Sets a buffer line
///
+/// @deprecated use buffer_set_lines instead.
+/// for positive indices use
+/// "buffer_set_lines(buffer, index, index+1, true, [line])"
+/// for negative indices use
+/// "buffer_set_lines(buffer, index-1, index, true, [line])"
+///
/// @param buffer The buffer handle
/// @param index The line index
/// @param line The new line.
@@ -71,23 +86,34 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err)
void buffer_set_line(Buffer buffer, Integer index, String line, Error *err)
{
Object l = STRING_OBJ(line);
- Array array = {.items = &l, .size = 1};
- buffer_set_line_slice(buffer, index, index, true, true, array, err);
+ Array array = { .items = &l, .size = 1 };
+ index = convert_index(index);
+ buffer_set_lines(buffer, index, index+1, true, array, err);
}
/// Deletes a buffer line
///
+/// @deprecated use buffer_set_lines instead.
+/// for positive indices use
+/// "buffer_set_lines(buffer, index, index+1, true, [])"
+/// for negative indices use
+/// "buffer_set_lines(buffer, index-1, index, true, [])"
/// @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, Integer index, Error *err)
{
Array array = ARRAY_DICT_INIT;
- buffer_set_line_slice(buffer, index, index, true, true, array, err);
+ index = convert_index(index);
+ buffer_set_lines(buffer, index, index+1, true, array, err);
}
/// Retrieves a line range from the buffer
///
+/// @deprecated use buffer_get_lines(buffer, newstart, newend, false)
+/// where newstart = start + int(not include_start) - int(start < 0)
+/// newend = end + int(include_end) - int(end < 0)
+/// int(bool) = 1 if bool is true else 0
/// @param buffer The buffer handle
/// @param start The first line index
/// @param end The last line index
@@ -102,16 +128,48 @@ ArrayOf(String) buffer_get_line_slice(Buffer buffer,
Boolean include_end,
Error *err)
{
+ start = convert_index(start) + !include_start;
+ end = convert_index(end) + include_end;
+ return buffer_get_lines(buffer, start , end, false, err);
+}
+
+
+/// Retrieves a line range from the buffer
+///
+/// Indexing is zero-based, end-exclusive. Negative indices are interpreted
+/// as length+1+index, i e -1 refers to the index past the end. So to get the
+/// last element set start=-2 and end=-1.
+///
+/// Out-of-bounds indices are clamped to the nearest valid value, unless
+/// `strict_indexing` is set.
+///
+/// @param buffer The buffer handle
+/// @param start The first line index
+/// @param end The last line index (exclusive)
+/// @param strict_indexing whether out-of-bounds should be an error.
+/// @param[out] err Details of an error that may have occurred
+/// @return An array of lines
+ArrayOf(String) buffer_get_lines(Buffer buffer,
+ Integer start,
+ Integer end,
+ Boolean strict_indexing,
+ Error *err)
+{
Array rv = ARRAY_DICT_INIT;
buf_T *buf = find_buffer_by_handle(buffer, err);
- if (!buf || !inbounds(buf, start)) {
+ if (!buf) {
return rv;
}
- start = normalize_index(buf, start) + (include_start ? 0 : 1);
- include_end = include_end || (end >= buf->b_ml.ml_line_count);
- end = normalize_index(buf, end) + (include_end ? 1 : 0);
+ bool oob = false;
+ start = normalize_index(buf, start, &oob);
+ end = normalize_index(buf, end, &oob);
+
+ if (strict_indexing && oob) {
+ api_set_error(err, Validation, _("Index out of bounds"));
+ return rv;
+ }
if (start >= end) {
// Return 0-length array
@@ -151,8 +209,14 @@ end:
return rv;
}
+
/// Replaces a line range on the buffer
///
+/// @deprecated use buffer_set_lines(buffer, newstart, newend, false, lines)
+/// where newstart = start + int(not include_start) + int(start < 0)
+/// newend = end + int(include_end) + int(end < 0)
+/// int(bool) = 1 if bool is true else 0
+///
/// @param buffer The buffer handle
/// @param start The first line index
/// @param end The last line index
@@ -169,20 +233,52 @@ void buffer_set_line_slice(Buffer buffer,
ArrayOf(String) replacement,
Error *err)
{
+ start = convert_index(start) + !include_start;
+ end = convert_index(end) + include_end;
+ buffer_set_lines(buffer, start, end, false, replacement, err);
+}
+
+
+/// Replaces line range on the buffer
+///
+/// Indexing is zero-based, end-exclusive. Negative indices are interpreted
+/// as length+1+index, i e -1 refers to the index past the end. So to change
+/// or delete the last element set start=-2 and end=-1.
+///
+/// To insert lines at a given index, set both start and end to the same index.
+/// To delete a range of lines, set replacement to an empty array.
+///
+/// Out-of-bounds indices are clamped to the nearest valid value, unless
+/// `strict_indexing` is set.
+///
+/// @param buffer The buffer handle
+/// @param start The first line index
+/// @param end The last line index (exclusive)
+/// @param strict_indexing whether out-of-bounds should be an error.
+/// @param replacement An array of lines to use as replacement
+/// @param[out] err Details of an error that may have occurred
+void buffer_set_lines(Buffer buffer,
+ Integer start,
+ Integer end,
+ Boolean strict_indexing,
+ ArrayOf(String) replacement,
+ Error *err)
+{
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
return;
}
- if (!inbounds(buf, start)) {
+ bool oob = false;
+ start = normalize_index(buf, start, &oob);
+ end = normalize_index(buf, end, &oob);
+
+ if (strict_indexing && oob) {
api_set_error(err, Validation, _("Index out of bounds"));
return;
}
- start = normalize_index(buf, start) + (include_start ? 0 : 1);
- include_end = include_end || (end >= buf->b_ml.ml_line_count);
- end = normalize_index(buf, end) + (include_end ? 1 : 0);
if (start > end) {
api_set_error(err,
@@ -327,13 +423,16 @@ Object buffer_get_var(Buffer buffer, String name, Error *err)
return dict_get_value(buf->b_vars, name, err);
}
-/// Sets a buffer-scoped (b:) variable. 'nil' value deletes the variable.
+/// Sets a buffer-scoped (b:) 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
+/// @return The old value or nil if there was no previous value.
+///
+/// @warning It may return nil if there was no previous value
+/// or if previous value was `v:null`.
Object buffer_set_var(Buffer buffer, String name, Object value, Error *err)
{
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -342,7 +441,27 @@ Object buffer_set_var(Buffer buffer, String name, Object value, Error *err)
return (Object) OBJECT_INIT;
}
- return dict_set_value(buf->b_vars, name, value, err);
+ return dict_set_value(buf->b_vars, name, value, false, err);
+}
+
+/// Removes a buffer-scoped (b:) variable
+///
+/// @param buffer The buffer handle
+/// @param name The variable name
+/// @param[out] err Details of an error that may have occurred
+/// @return The old value or nil if there was no previous value.
+///
+/// @warning It may return nil if there was no previous value
+/// or if previous value was `v:null`.
+Object buffer_del_var(Buffer buffer, String name, Error *err)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return (Object) OBJECT_INIT;
+ }
+
+ return dict_set_value(buf->b_vars, name, NIL, true, err);
}
/// Gets a buffer option value
@@ -456,6 +575,8 @@ Boolean buffer_is_valid(Buffer buffer)
/// Inserts a sequence of lines to a buffer at a certain index
///
+/// @deprecated use buffer_set_lines(buffer, lnum, lnum, true, lines)
+///
/// @param buffer The buffer handle
/// @param lnum Insert the lines after `lnum`. If negative, it will append
/// to the end of the buffer.
@@ -466,8 +587,9 @@ void buffer_insert(Buffer buffer,
ArrayOf(String) lines,
Error *err)
{
- bool end_start = lnum < 0;
- buffer_set_line_slice(buffer, lnum, lnum, !end_start, end_start, lines, err);
+ // "lnum" will be the index of the line after inserting,
+ // no matter if it is negative or not
+ buffer_set_lines(buffer, lnum, lnum, true, lines, err);
}
/// Return a tuple (row,col) representing the position of the named mark
@@ -514,6 +636,99 @@ ArrayOf(Integer, 2) buffer_get_mark(Buffer buffer, String name, Error *err)
return rv;
}
+/// Adds a highlight to buffer.
+///
+/// This can be used for plugins which dynamically generate highlights to a
+/// buffer (like a semantic highlighter or linter). The function adds a single
+/// highlight to a buffer. Unlike matchaddpos() highlights follow changes to
+/// line numbering (as lines are inserted/removed above the highlighted line),
+/// like signs and marks do.
+///
+/// "src_id" is useful for batch deletion/updating of a set of highlights. When
+/// called with src_id = 0, an unique source id is generated and returned.
+/// Succesive calls can pass in it as "src_id" to add new highlights to the same
+/// source group. All highlights in the same group can then be cleared with
+/// buffer_clear_highlight. If the highlight never will be manually deleted
+/// pass in -1 for "src_id".
+///
+/// If "hl_group" is the empty string no highlight is added, but a new src_id
+/// is still returned. This is useful for an external plugin to synchrounously
+/// request an unique src_id at initialization, and later asynchronously add and
+/// clear highlights in response to buffer changes.
+///
+/// @param buffer The buffer handle
+/// @param src_id Source group to use or 0 to use a new group,
+/// or -1 for ungrouped highlight
+/// @param hl_group Name of the highlight group to use
+/// @param line The line to highlight
+/// @param col_start Start of range of columns to highlight
+/// @param col_end End of range of columns to highlight,
+/// or -1 to highlight to end of line
+/// @param[out] err Details of an error that may have occurred
+/// @return The src_id that was used
+Integer buffer_add_highlight(Buffer buffer,
+ Integer src_id,
+ String hl_group,
+ Integer line,
+ Integer col_start,
+ Integer col_end,
+ Error *err)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return 0;
+ }
+
+ if (line < 0 || line >= MAXLNUM) {
+ api_set_error(err, Validation, _("Line number outside range"));
+ return 0;
+ }
+ if (col_start < 0 || col_start > MAXCOL) {
+ api_set_error(err, Validation, _("Column value outside range"));
+ return 0;
+ }
+ if (col_end < 0 || col_end > MAXCOL) {
+ col_end = MAXCOL;
+ }
+
+ int hlg_id = syn_name2id((char_u*)hl_group.data);
+ src_id = bufhl_add_hl(buf, (int)src_id, hlg_id, (linenr_T)line+1,
+ (colnr_T)col_start+1, (colnr_T)col_end);
+ return src_id;
+}
+
+/// Clears highlights from a given source group and a range of lines
+///
+/// To clear a source group in the entire buffer, pass in 1 and -1 to
+/// line_start and line_end respectively.
+///
+/// @param buffer The buffer handle
+/// @param src_id Highlight source group to clear, or -1 to clear all groups.
+/// @param line_start Start of range of lines to clear
+/// @param line_end End of range of lines to clear (exclusive)
+/// or -1 to clear to end of file.
+/// @param[out] err Details of an error that may have occurred
+void buffer_clear_highlight(Buffer buffer,
+ Integer src_id,
+ Integer line_start,
+ Integer line_end,
+ Error *err)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return;
+ }
+
+ if (line_start < 0 || line_start >= MAXLNUM) {
+ api_set_error(err, Validation, _("Line number outside range"));
+ return;
+ }
+ if (line_end < 0 || line_end > MAXLNUM) {
+ line_end = MAXLNUM;
+ }
+
+ bufhl_clear_line_range(buf, (int)src_id, (int)line_start+1, (int)line_end);
+}
// Check if deleting lines made the cursor position invalid.
// Changed the lines from "lo" to "hi" and added "extra" lines (negative if
@@ -538,20 +753,26 @@ 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)
+static int64_t normalize_index(buf_T *buf, int64_t index, bool *oob)
{
+ int64_t line_count = buf->b_ml.ml_line_count;
// Fix if < 0
- index = index < 0 ? buf->b_ml.ml_line_count + index : index;
+ index = index < 0 ? line_count + index +1 : index;
+
+ // Check for oob
+ if (index > line_count) {
+ *oob = true;
+ index = line_count;
+ } else if (index < 0) {
+ *oob = true;
+ index = 0;
+ }
// 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;
}
-// Returns true if the 0-indexed `index` is within the 1-indexed buffer bounds.
-static bool inbounds(buf_T *buf, int64_t index)
+static int64_t convert_index(int64_t index)
{
- linenr_T nlines = buf->b_ml.ml_line_count;
- return index >= -nlines && index < nlines;
+ return index < 0 ? index - 1 : index;
}
diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h
index 6c8e324649..fbfa87d5ae 100644
--- a/src/nvim/api/private/defs.h
+++ b/src/nvim/api/private/defs.h
@@ -99,4 +99,3 @@ struct key_value_pair {
#endif // NVIM_API_PRIVATE_DEFS_H
-
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 7a0b5191d7..db3e499427 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -90,14 +90,17 @@ 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.
+/// vimscript equivalents.
///
/// @param dict The vimscript dict
/// @param key The key
/// @param value The new value
+/// @param del Delete key in place of setting it. Argument `value` is ignored in
+/// this case.
/// @param[out] err Details of an error that may have occurred
/// @return the old value, if any
-Object dict_set_value(dict_T *dict, String key, Object value, Error *err)
+Object dict_set_value(dict_T *dict, String key, Object value, bool del,
+ Error *err)
{
Object rv = OBJECT_INIT;
@@ -118,7 +121,7 @@ Object dict_set_value(dict_T *dict, String key, Object value, Error *err)
dictitem_T *di = dict_find(dict, (uint8_t *)key.data, (int)key.size);
- if (value.type == kObjectTypeNil) {
+ if (del) {
// Delete the key
if (di == NULL) {
// Doesn't exist, fail
@@ -397,13 +400,13 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err)
switch (obj.type) {
case kObjectTypeNil:
- tv->v_type = VAR_NUMBER;
- tv->vval.v_number = 0;
+ tv->v_type = VAR_SPECIAL;
+ tv->vval.v_special = kSpecialVarNull;
break;
case kObjectTypeBoolean:
- tv->v_type = VAR_NUMBER;
- tv->vval.v_number = obj.data.boolean;
+ tv->v_type = VAR_SPECIAL;
+ tv->vval.v_special = obj.data.boolean? kSpecialVarTrue: kSpecialVarFalse;
break;
case kObjectTypeBuffer:
@@ -651,6 +654,21 @@ static Object vim_to_object_rec(typval_T *obj, PMap(ptr_t) *lookup)
}
switch (obj->v_type) {
+ case VAR_SPECIAL:
+ switch (obj->vval.v_special) {
+ case kSpecialVarTrue:
+ case kSpecialVarFalse: {
+ rv.type = kObjectTypeBoolean;
+ rv.data.boolean = (obj->vval.v_special == kSpecialVarTrue);
+ break;
+ }
+ case kSpecialVarNull: {
+ rv.type = kObjectTypeNil;
+ break;
+ }
+ }
+ break;
+
case VAR_STRING:
rv.type = kObjectTypeString;
rv.data.string = cstr_to_string((char *) obj->vval.v_string);
@@ -730,6 +748,10 @@ static Object vim_to_object_rec(typval_T *obj, PMap(ptr_t) *lookup)
}
}
break;
+
+ case VAR_UNKNOWN:
+ case VAR_FUNC:
+ break;
}
return rv;
diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c
index 126ee4072d..c8311b0aa0 100644
--- a/src/nvim/api/tabpage.c
+++ b/src/nvim/api/tabpage.c
@@ -54,13 +54,16 @@ Object tabpage_get_var(Tabpage tabpage, String name, Error *err)
return dict_get_value(tab->tp_vars, name, err);
}
-/// Sets a tab-scoped (t:) variable. 'nil' value deletes the variable.
+/// Sets a tab-scoped (t:) 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
+/// @return The old value or nil if there was no previous value.
+///
+/// @warning It may return nil if there was no previous value
+/// or if previous value was `v:null`.
Object tabpage_set_var(Tabpage tabpage, String name, Object value, Error *err)
{
tabpage_T *tab = find_tab_by_handle(tabpage, err);
@@ -69,7 +72,27 @@ Object tabpage_set_var(Tabpage tabpage, String name, Object value, Error *err)
return (Object) OBJECT_INIT;
}
- return dict_set_value(tab->tp_vars, name, value, err);
+ return dict_set_value(tab->tp_vars, name, value, false, err);
+}
+
+/// Removes a tab-scoped (t:) variable
+///
+/// @param tabpage handle
+/// @param name The variable name
+/// @param[out] err Details of an error that may have occurred
+/// @return The old value or nil if there was no previous value.
+///
+/// @warning It may return nil if there was no previous value
+/// or if previous value was `v:null`.
+Object tabpage_del_var(Tabpage tabpage, String name, Error *err)
+{
+ tabpage_T *tab = find_tab_by_handle(tabpage, err);
+
+ if (!tab) {
+ return (Object) OBJECT_INIT;
+ }
+
+ return dict_set_value(tab->tp_vars, name, NIL, true, err);
}
/// Gets the current window in a tab page
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
new file mode 100644
index 0000000000..1703d49296
--- /dev/null
+++ b/src/nvim/api/ui.c
@@ -0,0 +1,343 @@
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "nvim/vim.h"
+#include "nvim/ui.h"
+#include "nvim/memory.h"
+#include "nvim/map.h"
+#include "nvim/msgpack_rpc/channel.h"
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/ui.c.generated.h"
+#endif
+
+typedef struct {
+ uint64_t channel_id;
+ Array buffer;
+} UIData;
+
+static PMap(uint64_t) *connected_uis = NULL;
+
+void remote_ui_init(void)
+ FUNC_API_NOEXPORT
+{
+ connected_uis = pmap_new(uint64_t)();
+}
+
+void remote_ui_disconnect(uint64_t channel_id)
+ FUNC_API_NOEXPORT
+{
+ UI *ui = pmap_get(uint64_t)(connected_uis, channel_id);
+ if (!ui) {
+ return;
+ }
+ UIData *data = ui->data;
+ // destroy pending screen updates
+ api_free_array(data->buffer);
+ pmap_del(uint64_t)(connected_uis, channel_id);
+ xfree(ui->data);
+ ui_detach_impl(ui);
+ xfree(ui);
+}
+
+void ui_attach(uint64_t channel_id, Integer width, Integer height,
+ Boolean enable_rgb, Error *err)
+{
+ if (pmap_has(uint64_t)(connected_uis, channel_id)) {
+ api_set_error(err, Exception, _("UI already attached for channel"));
+ return;
+ }
+
+ if (width <= 0 || height <= 0) {
+ api_set_error(err, Validation,
+ _("Expected width > 0 and height > 0"));
+ return;
+ }
+ UIData *data = xmalloc(sizeof(UIData));
+ data->channel_id = channel_id;
+ data->buffer = (Array)ARRAY_DICT_INIT;
+ UI *ui = xcalloc(1, sizeof(UI));
+ ui->width = (int)width;
+ ui->height = (int)height;
+ ui->rgb = enable_rgb;
+ ui->data = data;
+ ui->resize = remote_ui_resize;
+ ui->clear = remote_ui_clear;
+ ui->eol_clear = remote_ui_eol_clear;
+ ui->cursor_goto = remote_ui_cursor_goto;
+ ui->update_menu = remote_ui_update_menu;
+ ui->busy_start = remote_ui_busy_start;
+ ui->busy_stop = remote_ui_busy_stop;
+ ui->mouse_on = remote_ui_mouse_on;
+ ui->mouse_off = remote_ui_mouse_off;
+ ui->mode_change = remote_ui_mode_change;
+ ui->set_scroll_region = remote_ui_set_scroll_region;
+ ui->scroll = remote_ui_scroll;
+ ui->highlight_set = remote_ui_highlight_set;
+ ui->put = remote_ui_put;
+ ui->bell = remote_ui_bell;
+ ui->visual_bell = remote_ui_visual_bell;
+ ui->update_fg = remote_ui_update_fg;
+ ui->update_bg = remote_ui_update_bg;
+ ui->update_sp = remote_ui_update_sp;
+ ui->flush = remote_ui_flush;
+ ui->suspend = remote_ui_suspend;
+ ui->set_title = remote_ui_set_title;
+ ui->set_icon = remote_ui_set_icon;
+ pmap_put(uint64_t)(connected_uis, channel_id, ui);
+ ui_attach_impl(ui);
+ return;
+}
+
+void ui_detach(uint64_t channel_id, Error *err)
+{
+ if (!pmap_has(uint64_t)(connected_uis, channel_id)) {
+ api_set_error(err, Exception, _("UI is not attached for channel"));
+ }
+ remote_ui_disconnect(channel_id);
+}
+
+Object ui_try_resize(uint64_t channel_id, Integer width,
+ Integer height, Error *err)
+{
+ if (!pmap_has(uint64_t)(connected_uis, channel_id)) {
+ api_set_error(err, Exception, _("UI is not attached for channel"));
+ }
+
+ if (width <= 0 || height <= 0) {
+ api_set_error(err, Validation,
+ _("Expected width > 0 and height > 0"));
+ return NIL;
+ }
+
+ UI *ui = pmap_get(uint64_t)(connected_uis, channel_id);
+ ui->width = (int)width;
+ ui->height = (int)height;
+ ui_refresh();
+ return NIL;
+}
+
+static void push_call(UI *ui, char *name, Array args)
+{
+ Array call = ARRAY_DICT_INIT;
+ UIData *data = ui->data;
+
+ // To optimize data transfer(especially for "put"), we bundle adjacent
+ // calls to same method together, so only add a new call entry if the last
+ // method call is different from "name"
+ if (kv_size(data->buffer)) {
+ call = kv_A(data->buffer, kv_size(data->buffer) - 1).data.array;
+ }
+
+ if (!kv_size(call) || strcmp(kv_A(call, 0).data.string.data, name)) {
+ call = (Array)ARRAY_DICT_INIT;
+ ADD(data->buffer, ARRAY_OBJ(call));
+ ADD(call, STRING_OBJ(cstr_to_string(name)));
+ }
+
+ ADD(call, ARRAY_OBJ(args));
+ kv_A(data->buffer, kv_size(data->buffer) - 1).data.array = call;
+}
+
+static void remote_ui_resize(UI *ui, int width, int height)
+{
+ Array args = ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(width));
+ ADD(args, INTEGER_OBJ(height));
+ push_call(ui, "resize", args);
+}
+
+static void remote_ui_clear(UI *ui)
+{
+ Array args = ARRAY_DICT_INIT;
+ push_call(ui, "clear", args);
+}
+
+static void remote_ui_eol_clear(UI *ui)
+{
+ Array args = ARRAY_DICT_INIT;
+ push_call(ui, "eol_clear", args);
+}
+
+static void remote_ui_cursor_goto(UI *ui, int row, int col)
+{
+ Array args = ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(row));
+ ADD(args, INTEGER_OBJ(col));
+ push_call(ui, "cursor_goto", args);
+}
+
+static void remote_ui_update_menu(UI *ui)
+{
+ Array args = ARRAY_DICT_INIT;
+ push_call(ui, "update_menu", args);
+}
+
+static void remote_ui_busy_start(UI *ui)
+{
+ Array args = ARRAY_DICT_INIT;
+ push_call(ui, "busy_start", args);
+}
+
+static void remote_ui_busy_stop(UI *ui)
+{
+ Array args = ARRAY_DICT_INIT;
+ push_call(ui, "busy_stop", args);
+}
+
+static void remote_ui_mouse_on(UI *ui)
+{
+ Array args = ARRAY_DICT_INIT;
+ push_call(ui, "mouse_on", args);
+}
+
+static void remote_ui_mouse_off(UI *ui)
+{
+ Array args = ARRAY_DICT_INIT;
+ push_call(ui, "mouse_off", args);
+}
+
+static void remote_ui_mode_change(UI *ui, int mode)
+{
+ Array args = ARRAY_DICT_INIT;
+ if (mode == INSERT) {
+ ADD(args, STRING_OBJ(cstr_to_string("insert")));
+ } else if (mode == REPLACE) {
+ ADD(args, STRING_OBJ(cstr_to_string("replace")));
+ } else {
+ assert(mode == NORMAL);
+ ADD(args, STRING_OBJ(cstr_to_string("normal")));
+ }
+ push_call(ui, "mode_change", args);
+}
+
+static void remote_ui_set_scroll_region(UI *ui, int top, int bot, int left,
+ int right)
+{
+ Array args = ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(top));
+ ADD(args, INTEGER_OBJ(bot));
+ ADD(args, INTEGER_OBJ(left));
+ ADD(args, INTEGER_OBJ(right));
+ push_call(ui, "set_scroll_region", args);
+}
+
+static void remote_ui_scroll(UI *ui, int count)
+{
+ Array args = ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(count));
+ push_call(ui, "scroll", args);
+}
+
+static void remote_ui_highlight_set(UI *ui, HlAttrs attrs)
+{
+ Array args = ARRAY_DICT_INIT;
+ Dictionary hl = ARRAY_DICT_INIT;
+
+ if (attrs.bold) {
+ PUT(hl, "bold", BOOLEAN_OBJ(true));
+ }
+
+ if (attrs.underline) {
+ PUT(hl, "underline", BOOLEAN_OBJ(true));
+ }
+
+ if (attrs.undercurl) {
+ PUT(hl, "undercurl", BOOLEAN_OBJ(true));
+ }
+
+ if (attrs.italic) {
+ PUT(hl, "italic", BOOLEAN_OBJ(true));
+ }
+
+ if (attrs.reverse) {
+ PUT(hl, "reverse", BOOLEAN_OBJ(true));
+ }
+
+ if (attrs.foreground != -1) {
+ PUT(hl, "foreground", INTEGER_OBJ(attrs.foreground));
+ }
+
+ if (attrs.background != -1) {
+ PUT(hl, "background", INTEGER_OBJ(attrs.background));
+ }
+
+ if (attrs.special != -1) {
+ PUT(hl, "special", INTEGER_OBJ(attrs.special));
+ }
+
+ ADD(args, DICTIONARY_OBJ(hl));
+ push_call(ui, "highlight_set", args);
+}
+
+static void remote_ui_put(UI *ui, uint8_t *data, size_t size)
+{
+ Array args = ARRAY_DICT_INIT;
+ String str = { .data = xmemdupz(data, size), .size = size };
+ ADD(args, STRING_OBJ(str));
+ push_call(ui, "put", args);
+}
+
+static void remote_ui_bell(UI *ui)
+{
+ Array args = ARRAY_DICT_INIT;
+ push_call(ui, "bell", args);
+}
+
+static void remote_ui_visual_bell(UI *ui)
+{
+ Array args = ARRAY_DICT_INIT;
+ push_call(ui, "visual_bell", args);
+}
+
+static void remote_ui_update_fg(UI *ui, int fg)
+{
+ Array args = ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(fg));
+ push_call(ui, "update_fg", args);
+}
+
+static void remote_ui_update_bg(UI *ui, int bg)
+{
+ Array args = ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(bg));
+ push_call(ui, "update_bg", args);
+}
+
+static void remote_ui_update_sp(UI *ui, int sp)
+{
+ Array args = ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(sp));
+ push_call(ui, "update_sp", args);
+}
+
+static void remote_ui_flush(UI *ui)
+{
+ UIData *data = ui->data;
+ channel_send_event(data->channel_id, "redraw", data->buffer);
+ data->buffer = (Array)ARRAY_DICT_INIT;
+}
+
+static void remote_ui_suspend(UI *ui)
+{
+ Array args = ARRAY_DICT_INIT;
+ push_call(ui, "suspend", args);
+}
+
+static void remote_ui_set_title(UI *ui, char *title)
+{
+ Array args = ARRAY_DICT_INIT;
+ ADD(args, STRING_OBJ(cstr_to_string(title)));
+ push_call(ui, "set_title", args);
+}
+
+static void remote_ui_set_icon(UI *ui, char *icon)
+{
+ Array args = ARRAY_DICT_INIT;
+ ADD(args, STRING_OBJ(cstr_to_string(icon)));
+ push_call(ui, "set_icon", args);
+}
diff --git a/src/nvim/api/ui.h b/src/nvim/api/ui.h
new file mode 100644
index 0000000000..b3af14f8a8
--- /dev/null
+++ b/src/nvim/api/ui.h
@@ -0,0 +1,11 @@
+#ifndef NVIM_API_UI_H
+#define NVIM_API_UI_H
+
+#include <stdint.h>
+
+#include "nvim/api/private/defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/ui.h.generated.h"
+#endif
+#endif // NVIM_API_UI_H
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 9279f6b469..46d72b847d 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -98,7 +98,7 @@ void vim_feedkeys(String keys, String mode, Boolean escape_csi)
/// @return The number of bytes actually written, which can be lower than
/// requested if the buffer becomes full.
Integer vim_input(String keys)
- FUNC_ATTR_ASYNC
+ FUNC_API_ASYNC
{
return (Integer)input_enqueue(keys);
}
@@ -116,8 +116,14 @@ String vim_replace_termcodes(String str, Boolean from_part, Boolean do_lt,
}
char *ptr = NULL;
- replace_termcodes((char_u *)str.data, (char_u **)&ptr,
- from_part, do_lt, special);
+ // Set 'cpoptions' the way we want it.
+ // FLAG_CPO_BSLASH set - backslashes are *not* treated specially
+ // FLAG_CPO_KEYCODE set - keycodes are *not* reverse-engineered
+ // FLAG_CPO_SPECI unset - <Key> sequences *are* interpreted
+ // The third from end parameter of replace_termcodes() is true so that the
+ // <lt> sequence is recognised - needed for a real backslash.
+ replace_termcodes((char_u *)str.data, str.size, (char_u **)&ptr,
+ from_part, do_lt, special, CPO_TO_CPO_FLAGS);
return cstr_as_string(ptr);
}
@@ -291,7 +297,7 @@ void vim_change_directory(String dir, Error *err)
return;
}
- post_chdir(false);
+ post_chdir(kCdScopeGlobal);
try_end(err);
}
@@ -331,15 +337,31 @@ Object vim_get_var(String name, Error *err)
return dict_get_value(&globvardict, name, err);
}
-/// Sets a global variable. Passing 'nil' as value deletes the variable.
+/// Sets a global 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
+/// @return The old value or nil if there was no previous value.
+///
+/// @warning It may return nil if there was no previous value
+/// or if previous value was `v:null`.
Object vim_set_var(String name, Object value, Error *err)
{
- return dict_set_value(&globvardict, name, value, err);
+ return dict_set_value(&globvardict, name, value, false, err);
+}
+
+/// Removes a global variable
+///
+/// @param name The variable name
+/// @param[out] err Details of an error that may have occurred
+/// @return The old value or nil if there was no previous value.
+///
+/// @warning It may return nil if there was no previous value
+/// or if previous value was `v:null`.
+Object vim_del_var(String name, Error *err)
+{
+ return dict_set_value(&globvardict, name, NIL, true, err);
}
/// Gets a vim variable
@@ -596,7 +618,7 @@ Dictionary vim_get_color_map(void)
Array vim_get_api_info(uint64_t channel_id)
- FUNC_ATTR_ASYNC
+ FUNC_API_ASYNC
{
Array rv = ARRAY_DICT_INIT;
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index aad616c7bf..f644453358 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -57,8 +57,8 @@ void window_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err)
{
win_T *win = find_window_by_handle(window, err);
- if (pos.size != 2 || pos.items[0].type != kObjectTypeInteger ||
- pos.items[1].type != kObjectTypeInteger) {
+ if (pos.size != 2 || pos.items[0].type != kObjectTypeInteger
+ || pos.items[1].type != kObjectTypeInteger) {
api_set_error(err,
Validation,
_("Argument \"pos\" must be a [row, col] array"));
@@ -197,13 +197,16 @@ Object window_get_var(Window window, String name, Error *err)
return dict_get_value(win->w_vars, name, err);
}
-/// Sets a window-scoped (w:) variable. 'nil' value deletes the variable.
+/// Sets a window-scoped (w:) 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
+/// @return The old value or nil if there was no previous value.
+///
+/// @warning It may return nil if there was no previous value
+/// or if previous value was `v:null`.
Object window_set_var(Window window, String name, Object value, Error *err)
{
win_T *win = find_window_by_handle(window, err);
@@ -212,7 +215,27 @@ Object window_set_var(Window window, String name, Object value, Error *err)
return (Object) OBJECT_INIT;
}
- return dict_set_value(win->w_vars, name, value, err);
+ return dict_set_value(win->w_vars, name, value, false, err);
+}
+
+/// Removes a window-scoped (w:) variable
+///
+/// @param window The window handle
+/// @param name The variable name
+/// @param[out] err Details of an error that may have occurred
+/// @return The old value or nil if there was no previous value.
+///
+/// @warning It may return nil if there was no previous value
+/// or if previous value was `v:null`.
+Object window_del_var(Window window, String name, Error *err)
+{
+ win_T *win = find_window_by_handle(window, err);
+
+ if (!win) {
+ return (Object) OBJECT_INIT;
+ }
+
+ return dict_set_value(win->w_vars, name, NIL, true, err);
}
/// Gets a window option value