diff options
Diffstat (limited to 'src/nvim/api')
-rw-r--r-- | src/nvim/api/buffer.c | 171 | ||||
-rw-r--r-- | src/nvim/api/private/dispatch.c | 3 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.c | 49 | ||||
-rw-r--r-- | src/nvim/api/ui.c | 386 | ||||
-rw-r--r-- | src/nvim/api/ui_events.in.h | 68 | ||||
-rw-r--r-- | src/nvim/api/vim.c | 34 |
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; +} |