aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/api')
-rw-r--r--src/nvim/api/buffer.c171
-rw-r--r--src/nvim/api/private/dispatch.c3
-rw-r--r--src/nvim/api/private/helpers.c49
-rw-r--r--src/nvim/api/ui.c386
-rw-r--r--src/nvim/api/ui_events.in.h68
-rw-r--r--src/nvim/api/vim.c34
6 files changed, 606 insertions, 105 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 8ff24b877e..487a912882 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -13,6 +13,7 @@
#include "nvim/api/private/defs.h"
#include "nvim/vim.h"
#include "nvim/buffer.h"
+#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -31,11 +32,26 @@
# include "api/buffer.c.generated.h"
#endif
+
+/// \defgroup api-buffer
+///
+/// Unloaded Buffers:~
+///
+/// Buffers may be unloaded by the |:bunload| command or the buffer's
+/// |'bufhidden'| option. When a buffer is unloaded its file contents are freed
+/// from memory and vim cannot operate on the buffer lines until it is reloaded
+/// (usually by opening the buffer again in a new window). API methods such as
+/// |nvim_buf_get_lines()| and |nvim_buf_line_count()| will be affected.
+///
+/// You can use |nvim_buf_is_loaded()| or |nvim_buf_line_count()| to check
+/// whether a buffer is loaded.
+
+
/// Gets the buffer line count
///
/// @param buffer Buffer handle
/// @param[out] err Error details, if any
-/// @return Line count
+/// @return Line count, or 0 for unloaded buffer. |api-buffer|
Integer nvim_buf_line_count(Buffer buffer, Error *err)
FUNC_API_SINCE(1)
{
@@ -45,6 +61,11 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err)
return 0;
}
+ // return sentinel value if the buffer isn't loaded
+ if (buf->b_ml.ml_mfp == NULL) {
+ return 0;
+ }
+
return buf->b_ml.ml_line_count;
}
@@ -205,7 +226,7 @@ ArrayOf(String) buffer_get_line_slice(Buffer buffer,
/// @param end Last line index (exclusive)
/// @param strict_indexing Whether out-of-bounds should be an error.
/// @param[out] err Error details, if any
-/// @return Array of lines
+/// @return Array of lines, or empty array for unloaded buffer.
ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id,
Buffer buffer,
Integer start,
@@ -221,6 +242,11 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id,
return rv;
}
+ // return sentinel value if the buffer isn't loaded
+ if (buf->b_ml.ml_mfp == NULL) {
+ return rv;
+ }
+
bool oob = false;
start = normalize_index(buf, start, &oob);
end = normalize_index(buf, end, &oob);
@@ -463,6 +489,41 @@ end:
try_end(err);
}
+/// Returns the byte offset for a line.
+///
+/// Line 1 (index=0) has offset 0. UTF-8 bytes are counted. EOL is one byte.
+/// 'fileformat' and 'fileencoding' are ignored. The line index just after the
+/// last line gives the total byte-count of the buffer. A final EOL byte is
+/// counted if it would be written, see 'eol'.
+///
+/// Unlike |line2byte()|, throws error for out-of-bounds indexing.
+/// Returns -1 for unloaded buffer.
+///
+/// @param buffer Buffer handle
+/// @param index Line index
+/// @param[out] err Error details, if any
+/// @return Integer byte offset, or -1 for unloaded buffer.
+Integer nvim_buf_get_offset(Buffer buffer, Integer index, Error *err)
+ FUNC_API_SINCE(5)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return 0;
+ }
+
+ // return sentinel value if the buffer isn't loaded
+ if (buf->b_ml.ml_mfp == NULL) {
+ return -1;
+ }
+
+ if (index < 0 || index > buf->b_ml.ml_line_count) {
+ api_set_error(err, kErrorTypeValidation, "Index out of bounds");
+ return 0;
+ }
+
+ return ml_find_line_or_offset(buf, (int)index+1, NULL, true);
+}
+
/// Gets a buffer-scoped (b:) variable.
///
/// @param buffer Buffer handle
@@ -745,10 +806,27 @@ void nvim_buf_set_name(Buffer buffer, String name, Error *err)
}
}
-/// Checks if a buffer is valid
+/// Checks if a buffer is valid and loaded. See |api-buffer| for more info
+/// about unloaded buffers.
+///
+/// @param buffer Buffer handle
+/// @return true if the buffer is valid and loaded, false otherwise.
+Boolean nvim_buf_is_loaded(Buffer buffer)
+ FUNC_API_SINCE(5)
+{
+ Error stub = ERROR_INIT;
+ buf_T *buf = find_buffer_by_handle(buffer, &stub);
+ api_clear_error(&stub);
+ return buf && buf->b_ml.ml_mfp != NULL;
+}
+
+/// Checks if a buffer is valid.
+///
+/// @note Even if a buffer is valid it may have been unloaded. See |api-buffer|
+/// for more info about unloaded buffers.
///
/// @param buffer Buffer handle
-/// @return true if the buffer is valid, false otherwise
+/// @return true if the buffer is valid, false otherwise.
Boolean nvim_buf_is_valid(Buffer buffer)
FUNC_API_SINCE(1)
{
@@ -889,7 +967,7 @@ Integer nvim_buf_add_highlight(Buffer buffer,
return src_id;
}
-/// Clears highlights from a given source group and a range of lines
+/// Clears highlights and virtual text from a given source id and range of lines
///
/// To clear a source group in the entire buffer, pass in 0 and -1 to
/// line_start and line_end respectively.
@@ -923,6 +1001,89 @@ void nvim_buf_clear_highlight(Buffer buffer,
bufhl_clear_line_range(buf, (int)src_id, (int)line_start+1, (int)line_end);
}
+
+/// Set the virtual text (annotation) for a buffer line.
+///
+/// By default (and currently the only option) the text will be placed after
+/// the buffer text. Virtual text will never cause reflow, rather virtual
+/// text will be truncated at the end of the screen line. The virtual text will
+/// begin after one cell to the right of the ordinary text, this will contain
+/// the |lcs-eol| char if set, otherwise just be a space.
+///
+/// The same src_id can be used for both virtual text and highlights added by
+/// nvim_buf_add_highlight. Virtual text is cleared using
+/// nvim_buf_clear_highlight.
+///
+/// @param buffer Buffer handle
+/// @param src_id Source group to use or 0 to use a new group,
+/// or -1 for a ungrouped annotation
+/// @param line Line to annotate with virtual text (zero-indexed)
+/// @param chunks A list of [text, hl_group] arrays, each representing a
+/// text chunk with specified highlight. `hl_group` element
+/// can be omitted for no highlight.
+/// @param opts Optional parameters. Currently not used.
+/// @param[out] err Error details, if any
+/// @return The src_id that was used
+Integer nvim_buf_set_virtual_text(Buffer buffer,
+ Integer src_id,
+ Integer line,
+ Array chunks,
+ Dictionary opts,
+ Error *err)
+ FUNC_API_SINCE(5)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return 0;
+ }
+
+ if (line < 0 || line >= MAXLNUM) {
+ api_set_error(err, kErrorTypeValidation, "Line number outside range");
+ return 0;
+ }
+
+ if (opts.size > 0) {
+ api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+ return 0;
+ }
+
+ VirtText virt_text = KV_INITIAL_VALUE;
+ for (size_t i = 0; i < chunks.size; i++) {
+ if (chunks.items[i].type != kObjectTypeArray) {
+ api_set_error(err, kErrorTypeValidation, "Chunk is not an array");
+ goto free_exit;
+ }
+ Array chunk = chunks.items[i].data.array;
+ if (chunk.size == 0 || chunk.size > 2
+ || chunk.items[0].type != kObjectTypeString
+ || (chunk.size == 2 && chunk.items[1].type != kObjectTypeString)) {
+ api_set_error(err, kErrorTypeValidation,
+ "Chunk is not an array with one or two strings");
+ goto free_exit;
+ }
+
+ String str = chunk.items[0].data.string;
+ char *text = transstr(str.size > 0 ? str.data : ""); // allocates
+
+ int hl_id = 0;
+ if (chunk.size == 2) {
+ String hl = chunk.items[1].data.string;
+ if (hl.size > 0) {
+ hl_id = syn_check_group((char_u *)hl.data, (int)hl.size);
+ }
+ }
+ kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id }));
+ }
+
+ src_id = bufhl_add_virt_text(buf, (int)src_id, (linenr_T)line+1,
+ virt_text);
+ return src_id;
+
+free_exit:
+ kv_destroy(virt_text);
+ return 0;
+}
+
// Check if deleting lines made the cursor position invalid.
// Changed the lines from "lo" to "hi" and added "extra" lines (negative if
// deleted).
diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c
index dec2b6c185..8492225a69 100644
--- a/src/nvim/api/private/dispatch.c
+++ b/src/nvim/api/private/dispatch.c
@@ -40,7 +40,8 @@ MsgpackRpcRequestHandler msgpack_rpc_get_handler_for(const char *name,
map_get(String, MsgpackRpcRequestHandler)(methods, m);
if (!rv.fn) {
- api_set_error(error, kErrorTypeException, "Invalid method: %s",
+ api_set_error(error, kErrorTypeException, "Invalid method: %.*s",
+ m.size > 0 ? (int)m.size : (int)sizeof("<empty>"),
m.size > 0 ? m.data : "<empty>");
}
return rv;
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index f3e883de02..cd6060b5d2 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -162,7 +162,7 @@ Object dict_get_value(dict_T *dict, String key, Error *err)
dictitem_T *const di = tv_dict_find(dict, key.data, (ptrdiff_t)key.size);
if (di == NULL) {
- api_set_error(err, kErrorTypeValidation, "Key '%s' not found", key.data);
+ api_set_error(err, kErrorTypeValidation, "Key not found: %s", key.data);
return (Object)OBJECT_INIT;
}
@@ -191,13 +191,12 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del,
}
if (key.size == 0) {
- api_set_error(err, kErrorTypeValidation,
- "Empty variable names aren't allowed");
+ api_set_error(err, kErrorTypeValidation, "Key name is empty");
return rv;
}
if (key.size > INT_MAX) {
- api_set_error(err, kErrorTypeValidation, "Key length is too high");
+ api_set_error(err, kErrorTypeValidation, "Key name is too long");
return rv;
}
@@ -220,7 +219,7 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del,
// Delete the key
if (di == NULL) {
// Doesn't exist, fail
- api_set_error(err, kErrorTypeValidation, "Key does not exist: %s",
+ api_set_error(err, kErrorTypeValidation, "Key not found: %s",
key.data);
} else {
// Return the old value
@@ -284,9 +283,7 @@ Object get_option_from(void *from, int type, String name, Error *err)
type, from);
if (!flags) {
- api_set_error(err,
- kErrorTypeValidation,
- "Invalid option name \"%s\"",
+ api_set_error(err, kErrorTypeValidation, "Invalid option name: '%s'",
name.data);
return rv;
}
@@ -303,15 +300,14 @@ Object get_option_from(void *from, int type, String name, Error *err)
rv.data.string.data = stringval;
rv.data.string.size = strlen(stringval);
} else {
- api_set_error(err,
- kErrorTypeException,
- "Unable to get value for option \"%s\"",
+ api_set_error(err, kErrorTypeException,
+ "Failed to get value for option '%s'",
name.data);
}
} else {
api_set_error(err,
kErrorTypeException,
- "Unknown type for option \"%s\"",
+ "Unknown type for option '%s'",
name.data);
}
@@ -336,24 +332,20 @@ void set_option_to(uint64_t channel_id, void *to, int type,
int flags = get_option_value_strict(name.data, NULL, NULL, type, to);
if (flags == 0) {
- api_set_error(err,
- kErrorTypeValidation,
- "Invalid option name \"%s\"",
+ api_set_error(err, kErrorTypeValidation, "Invalid option name '%s'",
name.data);
return;
}
if (value.type == kObjectTypeNil) {
if (type == SREQ_GLOBAL) {
- api_set_error(err,
- kErrorTypeException,
- "Unable to unset option \"%s\"",
+ api_set_error(err, kErrorTypeException, "Cannot unset option '%s'",
name.data);
return;
} else if (!(flags & SOPT_GLOBAL)) {
api_set_error(err,
kErrorTypeException,
- "Cannot unset option \"%s\" "
+ "Cannot unset option '%s' "
"because it doesn't have a global value",
name.data);
return;
@@ -370,7 +362,7 @@ void set_option_to(uint64_t channel_id, void *to, int type,
if (value.type != kObjectTypeBoolean) {
api_set_error(err,
kErrorTypeValidation,
- "Option \"%s\" requires a boolean value",
+ "Option '%s' requires a Boolean value",
name.data);
return;
}
@@ -378,17 +370,15 @@ void set_option_to(uint64_t channel_id, void *to, int type,
numval = value.data.boolean;
} else if (flags & SOPT_NUM) {
if (value.type != kObjectTypeInteger) {
- api_set_error(err,
- kErrorTypeValidation,
- "Option \"%s\" requires an integer value",
+ api_set_error(err, kErrorTypeValidation,
+ "Option '%s' requires an integer value",
name.data);
return;
}
if (value.data.integer > INT_MAX || value.data.integer < INT_MIN) {
- api_set_error(err,
- kErrorTypeValidation,
- "Value for option \"%s\" is outside range",
+ api_set_error(err, kErrorTypeValidation,
+ "Value for option '%s' is out of range",
name.data);
return;
}
@@ -396,9 +386,8 @@ void set_option_to(uint64_t channel_id, void *to, int type,
numval = (int)value.data.integer;
} else {
if (value.type != kObjectTypeString) {
- api_set_error(err,
- kErrorTypeValidation,
- "Option \"%s\" requires a string value",
+ api_set_error(err, kErrorTypeValidation,
+ "Option '%s' requires a string value",
name.data);
return;
}
@@ -1168,7 +1157,7 @@ static void set_option_value_err(char *key,
}
void api_set_error(Error *err, ErrorType errType, const char *format, ...)
- FUNC_ATTR_NONNULL_ALL
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PRINTF(3, 4)
{
assert(kErrorTypeNone != errType);
va_list args1;
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index b6e0b9a566..01f8c9f71c 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -16,6 +16,7 @@
#include "nvim/api/private/helpers.h"
#include "nvim/popupmnu.h"
#include "nvim/cursor_shape.h"
+#include "nvim/highlight.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/ui.c.generated.h"
@@ -25,6 +26,12 @@
typedef struct {
uint64_t channel_id;
Array buffer;
+
+ int hl_id; // current higlight for legacy put event
+ Integer cursor_row, cursor_col; // Intended visibule cursor position
+
+ // Position of legacy cursor, used both for drawing and visible user cursor.
+ Integer client_row, client_col;
} UIData;
static PMap(uint64_t) *connected_uis = NULL;
@@ -51,6 +58,21 @@ void remote_ui_disconnect(uint64_t channel_id)
xfree(ui);
}
+/// Wait until ui has connected on stdio channel.
+void remote_ui_wait_for_attach(void)
+ FUNC_API_NOEXPORT
+{
+ Channel *channel = find_channel(CHAN_STDIO);
+ if (!channel) {
+ // this function should only be called in --embed mode, stdio channel
+ // can be assumed.
+ abort();
+ }
+
+ LOOP_PROCESS_EVENTS_UNTIL(&main_loop, channel->events, -1,
+ pmap_has(uint64_t)(connected_uis, CHAN_STDIO));
+}
+
void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height,
Dictionary options, Error *err)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
@@ -70,10 +92,9 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height,
ui->width = (int)width;
ui->height = (int)height;
ui->rgb = true;
- 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->grid_resize = remote_ui_grid_resize;
+ ui->grid_clear = remote_ui_grid_clear;
+ ui->grid_cursor_goto = remote_ui_grid_cursor_goto;
ui->mode_info_set = remote_ui_mode_info_set;
ui->update_menu = remote_ui_update_menu;
ui->busy_start = remote_ui_busy_start;
@@ -81,16 +102,12 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height,
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->grid_scroll = remote_ui_grid_scroll;
+ ui->hl_attr_define = remote_ui_hl_attr_define;
+ ui->raw_line = remote_ui_raw_line;
ui->bell = remote_ui_bell;
ui->visual_bell = remote_ui_visual_bell;
ui->default_colors_set = remote_ui_default_colors_set;
- 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;
@@ -102,16 +119,22 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height,
memset(ui->ui_ext, 0, sizeof(ui->ui_ext));
for (size_t i = 0; i < options.size; i++) {
- ui_set_option(ui, options.items[i].key, options.items[i].value, err);
+ ui_set_option(ui, true, options.items[i].key, options.items[i].value, err);
if (ERROR_SET(err)) {
xfree(ui);
return;
}
}
+ if (ui->ui_ext[kUIHlState]) {
+ ui->ui_ext[kUILinegrid] = true;
+ }
+
UIData *data = xmalloc(sizeof(UIData));
data->channel_id = channel_id;
data->buffer = (Array)ARRAY_DICT_INIT;
+ data->hl_id = 0;
+ data->client_col = -1;
ui->data = data;
pmap_put(uint64_t)(connected_uis, channel_id, ui);
@@ -173,13 +196,11 @@ void nvim_ui_set_option(uint64_t channel_id, String name,
}
UI *ui = pmap_get(uint64_t)(connected_uis, channel_id);
- ui_set_option(ui, name, value, error);
- if (!ERROR_SET(error)) {
- ui_refresh();
- }
+ ui_set_option(ui, false, name, value, error);
}
-static void ui_set_option(UI *ui, String name, Object value, Error *error)
+static void ui_set_option(UI *ui, bool init, String name, Object value,
+ Error *error)
{
if (strequal(name.data, "rgb")) {
if (value.type != kObjectTypeBoolean) {
@@ -187,40 +208,45 @@ static void ui_set_option(UI *ui, String name, Object value, Error *error)
return;
}
ui->rgb = value.data.boolean;
+ // A little drastic, but only legacy uis need to use this option
+ if (!init) {
+ ui_refresh();
+ }
return;
}
+ // LEGACY: Deprecated option, use `ext_cmdline` instead.
+ bool is_popupmenu = strequal(name.data, "popupmenu_external");
+
for (UIExtension i = 0; i < kUIExtCount; i++) {
- if (strequal(name.data, ui_ext_names[i])) {
+ if (strequal(name.data, ui_ext_names[i])
+ || (i == kUIPopupmenu && is_popupmenu)) {
if (value.type != kObjectTypeBoolean) {
- snprintf((char *)IObuff, IOSIZE, "%s must be a Boolean",
- ui_ext_names[i]);
- api_set_error(error, kErrorTypeValidation, (char *)IObuff);
+ api_set_error(error, kErrorTypeValidation, "%s must be a Boolean",
+ name.data);
return;
}
- ui->ui_ext[i] = value.data.boolean;
- return;
- }
- }
-
- if (strequal(name.data, "popupmenu_external")) {
- // LEGACY: Deprecated option, use `ext_cmdline` instead.
- if (value.type != kObjectTypeBoolean) {
- api_set_error(error, kErrorTypeValidation,
- "popupmenu_external must be a Boolean");
+ bool boolval = value.data.boolean;
+ if (!init && i == kUILinegrid && boolval != ui->ui_ext[i]) {
+ // There shouldn't be a reason for an UI to do this ever
+ // so explicitly don't support this.
+ api_set_error(error, kErrorTypeValidation,
+ "ext_linegrid option cannot be changed");
+ }
+ ui->ui_ext[i] = boolval;
+ if (!init) {
+ ui_set_ext_option(ui, i, boolval);
+ }
return;
}
- ui->ui_ext[kUIPopupmenu] = value.data.boolean;
- return;
}
api_set_error(error, kErrorTypeValidation, "No such UI option: %s",
name.data);
-#undef UI_EXT_OPTION
}
/// Pushes data into UI.UIData, to be consumed later by remote_ui_flush().
-static void push_call(UI *ui, char *name, Array args)
+static void push_call(UI *ui, const char *name, Array args)
{
Array call = ARRAY_DICT_INIT;
UIData *data = ui->data;
@@ -242,27 +268,313 @@ static void push_call(UI *ui, char *name, Array args)
kv_A(data->buffer, kv_size(data->buffer) - 1).data.array = call;
}
+static void remote_ui_grid_clear(UI *ui, Integer grid)
+{
+ Array args = ARRAY_DICT_INIT;
+ if (ui->ui_ext[kUILinegrid]) {
+ ADD(args, INTEGER_OBJ(grid));
+ }
+ const char *name = ui->ui_ext[kUILinegrid] ? "grid_clear" : "clear";
+ push_call(ui, name, args);
+}
-static void remote_ui_highlight_set(UI *ui, HlAttrs attrs)
+static void remote_ui_grid_resize(UI *ui, Integer grid,
+ Integer width, Integer height)
{
Array args = ARRAY_DICT_INIT;
- Dictionary hl = hlattrs2dict(&attrs, ui->rgb);
+ if (ui->ui_ext[kUILinegrid]) {
+ ADD(args, INTEGER_OBJ(grid));
+ }
+ ADD(args, INTEGER_OBJ(width));
+ ADD(args, INTEGER_OBJ(height));
+ const char *name = ui->ui_ext[kUILinegrid] ? "grid_resize" : "resize";
+ push_call(ui, name, args);
+}
+
+static void remote_ui_grid_scroll(UI *ui, Integer grid, Integer top,
+ Integer bot, Integer left, Integer right,
+ Integer rows, Integer cols)
+{
+ if (ui->ui_ext[kUILinegrid]) {
+ Array args = ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(grid));
+ ADD(args, INTEGER_OBJ(top));
+ ADD(args, INTEGER_OBJ(bot));
+ ADD(args, INTEGER_OBJ(left));
+ ADD(args, INTEGER_OBJ(right));
+ ADD(args, INTEGER_OBJ(rows));
+ ADD(args, INTEGER_OBJ(cols));
+ push_call(ui, "grid_scroll", args);
+ } else {
+ Array args = ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(top));
+ ADD(args, INTEGER_OBJ(bot-1));
+ ADD(args, INTEGER_OBJ(left));
+ ADD(args, INTEGER_OBJ(right-1));
+ push_call(ui, "set_scroll_region", args);
+
+ args = (Array)ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(rows));
+ push_call(ui, "scroll", args);
+
+ // some clients have "clear" being affected by scroll region,
+ // so reset it.
+ args = (Array)ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(0));
+ ADD(args, INTEGER_OBJ(ui->height-1));
+ ADD(args, INTEGER_OBJ(0));
+ ADD(args, INTEGER_OBJ(ui->width-1));
+ push_call(ui, "set_scroll_region", args);
+ }
+}
+
+static void remote_ui_default_colors_set(UI *ui, Integer rgb_fg,
+ Integer rgb_bg, Integer rgb_sp,
+ Integer cterm_fg, Integer cterm_bg)
+{
+ Array args = ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(rgb_fg));
+ ADD(args, INTEGER_OBJ(rgb_bg));
+ ADD(args, INTEGER_OBJ(rgb_sp));
+ ADD(args, INTEGER_OBJ(cterm_fg));
+ ADD(args, INTEGER_OBJ(cterm_bg));
+ push_call(ui, "default_colors_set", args);
+
+ // Deprecated
+ if (!ui->ui_ext[kUILinegrid]) {
+ args = (Array)ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(ui->rgb ? rgb_fg : cterm_fg - 1));
+ push_call(ui, "update_fg", args);
+
+ args = (Array)ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(ui->rgb ? rgb_bg : cterm_bg - 1));
+ push_call(ui, "update_bg", args);
+
+ args = (Array)ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(ui->rgb ? rgb_sp : -1));
+ push_call(ui, "update_sp", args);
+ }
+}
+
+static void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs,
+ HlAttrs cterm_attrs, Array info)
+{
+ if (!ui->ui_ext[kUILinegrid]) {
+ return;
+ }
+ Array args = ARRAY_DICT_INIT;
+
+ ADD(args, INTEGER_OBJ(id));
+ ADD(args, DICTIONARY_OBJ(hlattrs2dict(rgb_attrs, true)));
+ ADD(args, DICTIONARY_OBJ(hlattrs2dict(cterm_attrs, false)));
+
+ if (ui->ui_ext[kUIHlState]) {
+ ADD(args, ARRAY_OBJ(copy_array(info)));
+ } else {
+ ADD(args, ARRAY_OBJ((Array)ARRAY_DICT_INIT));
+ }
+
+ push_call(ui, "hl_attr_define", args);
+}
+
+static void remote_ui_highlight_set(UI *ui, int id)
+{
+ Array args = ARRAY_DICT_INIT;
+ UIData *data = ui->data;
+
+
+ if (data->hl_id == id) {
+ return;
+ }
+ data->hl_id = id;
+ Dictionary hl = hlattrs2dict(syn_attr2entry(id), ui->rgb);
ADD(args, DICTIONARY_OBJ(hl));
push_call(ui, "highlight_set", args);
}
+/// "true" cursor used only for input focus
+static void remote_ui_grid_cursor_goto(UI *ui, Integer grid, Integer row,
+ Integer col)
+{
+ if (ui->ui_ext[kUILinegrid]) {
+ Array args = ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(grid));
+ ADD(args, INTEGER_OBJ(row));
+ ADD(args, INTEGER_OBJ(col));
+ push_call(ui, "grid_cursor_goto", args);
+ } else {
+ UIData *data = ui->data;
+ data->cursor_row = row;
+ data->cursor_col = col;
+ remote_ui_cursor_goto(ui, row, col);
+ }
+}
+
+/// emulated cursor used both for drawing and for input focus
+static void remote_ui_cursor_goto(UI *ui, Integer row, Integer col)
+{
+ UIData *data = ui->data;
+ if (data->client_row == row && data->client_col == col) {
+ return;
+ }
+ data->client_row = row;
+ data->client_col = 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_put(UI *ui, const char *cell)
+{
+ UIData *data = ui->data;
+ data->client_col++;
+ Array args = ARRAY_DICT_INIT;
+ ADD(args, STRING_OBJ(cstr_to_string(cell)));
+ push_call(ui, "put", args);
+}
+
+static void remote_ui_raw_line(UI *ui, Integer grid, Integer row,
+ Integer startcol, Integer endcol,
+ Integer clearcol, Integer clearattr,
+ Boolean wrap, const schar_T *chunk,
+ const sattr_T *attrs)
+{
+ UIData *data = ui->data;
+ if (ui->ui_ext[kUILinegrid]) {
+ Array args = ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(grid));
+ ADD(args, INTEGER_OBJ(row));
+ ADD(args, INTEGER_OBJ(startcol));
+ Array cells = ARRAY_DICT_INIT;
+ int repeat = 0;
+ size_t ncells = (size_t)(endcol-startcol);
+ int last_hl = -1;
+ for (size_t i = 0; i < ncells; i++) {
+ repeat++;
+ if (i == ncells-1 || attrs[i] != attrs[i+1]
+ || STRCMP(chunk[i], chunk[i+1])) {
+ Array cell = ARRAY_DICT_INIT;
+ ADD(cell, STRING_OBJ(cstr_to_string((const char *)chunk[i])));
+ if (attrs[i] != last_hl || repeat > 1) {
+ ADD(cell, INTEGER_OBJ(attrs[i]));
+ last_hl = attrs[i];
+ }
+ if (repeat > 1) {
+ ADD(cell, INTEGER_OBJ(repeat));
+ }
+ ADD(cells, ARRAY_OBJ(cell));
+ repeat = 0;
+ }
+ }
+ if (endcol < clearcol) {
+ Array cell = ARRAY_DICT_INIT;
+ ADD(cell, STRING_OBJ(cstr_to_string(" ")));
+ ADD(cell, INTEGER_OBJ(clearattr));
+ ADD(cell, INTEGER_OBJ(clearcol-endcol));
+ ADD(cells, ARRAY_OBJ(cell));
+ }
+ ADD(args, ARRAY_OBJ(cells));
+
+ push_call(ui, "grid_line", args);
+ } else {
+ for (int i = 0; i < endcol-startcol; i++) {
+ remote_ui_cursor_goto(ui, row, startcol+i);
+ remote_ui_highlight_set(ui, attrs[i]);
+ remote_ui_put(ui, (const char *)chunk[i]);
+ if (utf_ambiguous_width(utf_ptr2char(chunk[i]))) {
+ data->client_col = -1; // force cursor update
+ }
+ }
+ if (endcol < clearcol) {
+ remote_ui_cursor_goto(ui, row, endcol);
+ remote_ui_highlight_set(ui, (int)clearattr);
+ // legacy eol_clear was only ever used with cleared attributes
+ // so be on the safe side
+ if (clearattr == 0 && clearcol == Columns) {
+ Array args = ARRAY_DICT_INIT;
+ push_call(ui, "eol_clear", args);
+ } else {
+ for (Integer c = endcol; c < clearcol; c++) {
+ remote_ui_put(ui, " ");
+ }
+ }
+ }
+ }
+}
+
static void remote_ui_flush(UI *ui)
{
UIData *data = ui->data;
if (data->buffer.size > 0) {
+ if (!ui->ui_ext[kUILinegrid]) {
+ remote_ui_cursor_goto(ui, data->cursor_row, data->cursor_col);
+ }
+ push_call(ui, "flush", (Array)ARRAY_DICT_INIT);
rpc_send_event(data->channel_id, "redraw", data->buffer);
data->buffer = (Array)ARRAY_DICT_INIT;
}
}
+static Array translate_contents(UI *ui, Array contents)
+{
+ Array new_contents = ARRAY_DICT_INIT;
+ for (size_t i = 0; i < contents.size; i++) {
+ Array item = contents.items[i].data.array;
+ Array new_item = ARRAY_DICT_INIT;
+ int attr = (int)item.items[0].data.integer;
+ if (attr) {
+ Dictionary rgb_attrs = hlattrs2dict(syn_attr2entry(attr), ui->rgb);
+ ADD(new_item, DICTIONARY_OBJ(rgb_attrs));
+ } else {
+ ADD(new_item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT));
+ }
+ ADD(new_item, copy_object(item.items[1]));
+ ADD(new_contents, ARRAY_OBJ(new_item));
+ }
+ return new_contents;
+}
+
+static Array translate_firstarg(UI *ui, Array args)
+{
+ Array new_args = ARRAY_DICT_INIT;
+ Array contents = args.items[0].data.array;
+
+ ADD(new_args, ARRAY_OBJ(translate_contents(ui, contents)));
+ for (size_t i = 1; i < args.size; i++) {
+ ADD(new_args, copy_object(args.items[i]));
+ }
+ return new_args;
+}
+
static void remote_ui_event(UI *ui, char *name, Array args, bool *args_consumed)
{
+ if (!ui->ui_ext[kUILinegrid]) {
+ // the representation of highlights in cmdline changed, translate back
+ // never consumes args
+ if (strequal(name, "cmdline_show")) {
+ Array new_args = translate_firstarg(ui, args);
+ push_call(ui, name, new_args);
+ return;
+ } else if (strequal(name, "cmdline_block_show")) {
+ Array new_args = ARRAY_DICT_INIT;
+ Array block = args.items[0].data.array;
+ Array new_block = ARRAY_DICT_INIT;
+ for (size_t i = 0; i < block.size; i++) {
+ ADD(new_block,
+ ARRAY_OBJ(translate_contents(ui, block.items[i].data.array)));
+ }
+ ADD(new_args, ARRAY_OBJ(new_block));
+ push_call(ui, name, new_args);
+ return;
+ } else if (strequal(name, "cmdline_block_append")) {
+ Array new_args = translate_firstarg(ui, args);
+ push_call(ui, name, new_args);
+ return;
+ }
+ }
+
Array my_args = ARRAY_DICT_INIT;
// Objects are currently single-reference
// make a copy, but only if necessary
diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h
index 3ef16a7ac3..10331fd5c2 100644
--- a/src/nvim/api/ui_events.in.h
+++ b/src/nvim/api/ui_events.in.h
@@ -10,14 +10,6 @@
#include "nvim/func_attr.h"
#include "nvim/ui.h"
-void resize(Integer width, Integer height)
- FUNC_API_SINCE(3);
-void clear(void)
- FUNC_API_SINCE(3);
-void eol_clear(void)
- FUNC_API_SINCE(3);
-void cursor_goto(Integer row, Integer col)
- FUNC_API_SINCE(3);
void mode_info_set(Boolean enabled, Array cursor_styles)
FUNC_API_SINCE(3);
void update_menu(void)
@@ -32,29 +24,12 @@ void mouse_off(void)
FUNC_API_SINCE(3);
void mode_change(String mode, Integer mode_idx)
FUNC_API_SINCE(3);
-void set_scroll_region(Integer top, Integer bot, Integer left, Integer right)
- FUNC_API_SINCE(3);
-void scroll(Integer count)
- FUNC_API_SINCE(3);
-void highlight_set(HlAttrs attrs)
- FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL;
-void put(String str)
- FUNC_API_SINCE(3);
void bell(void)
FUNC_API_SINCE(3);
void visual_bell(void)
FUNC_API_SINCE(3);
void flush(void)
FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL;
-void update_fg(Integer fg)
- FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL;
-void update_bg(Integer bg)
- FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL;
-void update_sp(Integer sp)
- FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL;
-void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp,
- Integer cterm_fg, Integer cterm_bg)
- FUNC_API_SINCE(4);
void suspend(void)
FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL;
void set_title(String title)
@@ -64,6 +39,49 @@ void set_icon(String icon)
void option_set(String name, Object value)
FUNC_API_SINCE(4) FUNC_API_BRIDGE_IMPL;
+// First revison of the grid protocol, used by default
+void update_fg(Integer fg)
+ FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
+void update_bg(Integer bg)
+ FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
+void update_sp(Integer sp)
+ FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
+void resize(Integer width, Integer height)
+ FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
+void clear(void)
+ FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
+void eol_clear(void)
+ FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
+void cursor_goto(Integer row, Integer col)
+ FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
+void highlight_set(HlAttrs attrs)
+ FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY FUNC_API_REMOTE_IMPL;
+void put(String str)
+ FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
+void set_scroll_region(Integer top, Integer bot, Integer left, Integer right)
+ FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
+void scroll(Integer count)
+ FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
+
+// Second revison of the grid protocol, used with ext_linegrid ui option
+void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp,
+ Integer cterm_fg, Integer cterm_bg)
+ FUNC_API_SINCE(4) FUNC_API_REMOTE_IMPL;
+void hl_attr_define(Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs,
+ Array info)
+ FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL;
+void grid_resize(Integer grid, Integer width, Integer height)
+ FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL;
+void grid_clear(Integer grid)
+ FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL;
+void grid_cursor_goto(Integer grid, Integer row, Integer col)
+ FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL;
+void grid_line(Integer grid, Integer row, Integer col_start, Array data)
+ FUNC_API_SINCE(5) FUNC_API_REMOTE_ONLY;
+void grid_scroll(Integer grid, Integer top, Integer bot,
+ Integer left, Integer right, Integer rows, Integer cols)
+ FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL;
+
void popupmenu_show(Array items, Integer selected, Integer row, Integer col)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void popupmenu_hide(void)
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 03567ddfd8..7fcccfd988 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -21,6 +21,7 @@
#include "nvim/vim.h"
#include "nvim/buffer.h"
#include "nvim/file_search.h"
+#include "nvim/highlight.h"
#include "nvim/window.h"
#include "nvim/types.h"
#include "nvim/ex_docmd.h"
@@ -501,7 +502,7 @@ Integer nvim_strwidth(String text, Error *err)
FUNC_API_SINCE(1)
{
if (text.size > INT_MAX) {
- api_set_error(err, kErrorTypeValidation, "String length is too high");
+ api_set_error(err, kErrorTypeValidation, "String is too long");
return 0;
}
@@ -558,7 +559,7 @@ void nvim_set_current_dir(String dir, Error *err)
FUNC_API_SINCE(1)
{
if (dir.size >= MAXPATHL) {
- api_set_error(err, kErrorTypeValidation, "Directory string is too long");
+ api_set_error(err, kErrorTypeValidation, "Directory name is too long");
return;
}
@@ -1027,7 +1028,7 @@ Array nvim_get_api_info(uint64_t channel_id)
/// @param attributes Informal attributes describing the client. Clients might
/// define their own keys, but the following are suggested:
/// - "website" Website of client (for instance github repository)
-/// - "license" Informal descripton of the license, such as "Apache 2",
+/// - "license" Informal description of the license, such as "Apache 2",
/// "GPLv3" or "MIT"
/// - "logo" URI or path to image, preferably small logo or icon.
/// .png or .svg format is preferred.
@@ -1082,7 +1083,7 @@ void nvim_set_client_info(uint64_t channel_id, String name,
/// - "buffer" buffer with connected |terminal| instance (optional)
/// - "client" information about the client on the other end of the
/// RPC channel, if it has added it using
-/// |nvim_set_client_info|. (optional)
+/// |nvim_set_client_info()|. (optional)
///
Dictionary nvim_get_chan_info(Integer chan, Error *err)
FUNC_API_SINCE(4)
@@ -1096,7 +1097,7 @@ Dictionary nvim_get_chan_info(Integer chan, Error *err)
/// Get information about all open channels.
///
/// @returns Array of Dictionaries, each describing a channel with
-/// the format specified at |nvim_get_chan_info|.
+/// the format specified at |nvim_get_chan_info()|.
Array nvim_list_chans(void)
FUNC_API_SINCE(4)
{
@@ -1135,14 +1136,14 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err)
if (calls.items[i].type != kObjectTypeArray) {
api_set_error(err,
kErrorTypeValidation,
- "All items in calls array must be arrays");
+ "Items in calls array must be arrays");
goto validation_error;
}
Array call = calls.items[i].data.array;
if (call.size != 2) {
api_set_error(err,
kErrorTypeValidation,
- "All items in calls array must be arrays of size 2");
+ "Items in calls array must be arrays of size 2");
goto validation_error;
}
@@ -1850,3 +1851,22 @@ Object nvim_get_proc(Integer pid, Error *err)
#endif
return rvobj;
}
+
+/// NB: if your UI doesn't use hlstate, this will not return hlstate first time
+Array nvim__inspect_cell(Integer row, Integer col, Error *err)
+{
+ Array ret = ARRAY_DICT_INIT;
+ if (row < 0 || row >= screen_Rows
+ || col < 0 || col >= screen_Columns) {
+ return ret;
+ }
+ size_t off = LineOffset[(size_t)row] + (size_t)col;
+ ADD(ret, STRING_OBJ(cstr_to_string((char *)ScreenLines[off])));
+ int attr = ScreenAttrs[off];
+ ADD(ret, DICTIONARY_OBJ(hl_get_attr_by_id(attr, true, err)));
+ // will not work first time
+ if (!highlight_use_hlstate()) {
+ ADD(ret, ARRAY_OBJ(hl_inspect(attr)));
+ }
+ return ret;
+}