diff options
Diffstat (limited to 'src/nvim/api')
-rw-r--r-- | src/nvim/api/buffer.c | 501 | ||||
-rw-r--r-- | src/nvim/api/deprecated.c | 16 | ||||
-rw-r--r-- | src/nvim/api/keysets.lua | 62 | ||||
-rw-r--r-- | src/nvim/api/private/defs.h | 16 | ||||
-rw-r--r-- | src/nvim/api/private/dispatch.c | 3 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.c | 582 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.h | 16 | ||||
-rw-r--r-- | src/nvim/api/ui.c | 3 | ||||
-rw-r--r-- | src/nvim/api/vim.c | 515 | ||||
-rw-r--r-- | src/nvim/api/win_config.c | 639 | ||||
-rw-r--r-- | src/nvim/api/win_config.h | 11 | ||||
-rw-r--r-- | src/nvim/api/window.c | 111 |
12 files changed, 1480 insertions, 995 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 8973f8fef6..31d44c68bf 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -418,7 +418,7 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ try_start(); aco_save_T aco; - aucmd_prepbuf(&aco, (buf_T *)buf); + aucmd_prepbuf(&aco, buf); if (!MODIFIABLE(buf)) { api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'"); @@ -624,7 +624,8 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In if (replacement.size == 1) { firstlen += last_part_len; } - char *first = xmallocz(firstlen), *last = NULL; + char *first = xmallocz(firstlen); + char *last = NULL; memcpy(first, str_at_start, (size_t)start_col); memcpy(first+start_col, first_item.data, first_item.size); memchrsub(first+start_col, NUL, NL, first_item.size); @@ -637,7 +638,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In memcpy(last+last_item.size, str_at_end+end_col, last_part_len); } - char **lines = (new_len != 0) ? xcalloc(new_len, sizeof(char *)) : NULL; + char **lines = xcalloc(new_len, sizeof(char *)); lines[0] = first; new_byte += (bcount_t)(first_item.size); for (size_t i = 1; i < new_len-1; i++) { @@ -656,7 +657,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In try_start(); aco_save_T aco; - aucmd_prepbuf(&aco, (buf_T *)buf); + aucmd_prepbuf(&aco, buf); if (!MODIFIABLE(buf)) { api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'"); @@ -858,7 +859,7 @@ ArrayOf(Dictionary) nvim_buf_get_keymap(Buffer buffer, String mode, Error *err) /// @see |nvim_set_keymap()| /// /// @param buffer Buffer handle, or 0 for current buffer -void nvim_buf_set_keymap(Buffer buffer, String mode, String lhs, String rhs, Dictionary opts, +void nvim_buf_set_keymap(Buffer buffer, String mode, String lhs, String rhs, Dict(keymap) *opts, Error *err) FUNC_API_SINCE(6) { @@ -874,8 +875,7 @@ void nvim_buf_del_keymap(Buffer buffer, String mode, String lhs, Error *err) FUNC_API_SINCE(6) { String rhs = { .data = "", .size = 0 }; - Dictionary opts = ARRAY_DICT_INIT; - modify_keymap(buffer, true, mode, lhs, rhs, opts, err); + modify_keymap(buffer, true, mode, lhs, rhs, NULL, err); } /// Gets a map of buffer-local |user-commands|. @@ -885,22 +885,13 @@ void nvim_buf_del_keymap(Buffer buffer, String mode, String lhs, Error *err) /// @param[out] err Error details, if any. /// /// @returns Map of maps describing commands. -Dictionary nvim_buf_get_commands(Buffer buffer, Dictionary opts, Error *err) +Dictionary nvim_buf_get_commands(Buffer buffer, Dict(get_commands) *opts, Error *err) FUNC_API_SINCE(4) { bool global = (buffer == -1); - bool builtin = false; - - for (size_t i = 0; i < opts.size; i++) { - String k = opts.items[i].key; - Object v = opts.items[i].value; - if (!strequal("builtin", k.data)) { - api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); - return (Dictionary)ARRAY_DICT_INIT; - } - if (strequal("builtin", k.data)) { - builtin = v.data.boolean; - } + bool builtin = api_object_to_bool(opts->builtin, "builtin", false, err); + if (ERROR_SET(err)) { + return (Dictionary)ARRAY_DICT_INIT; } if (global) { @@ -1118,14 +1109,96 @@ Boolean nvim_buf_is_valid(Buffer buffer) return ret; } -/// Return a tuple (row,col) representing the position of the named mark. +/// Deletes a named mark in the buffer. See |mark-motions|. +/// +/// @note only deletes marks set in the buffer, if the mark is not set +/// in the buffer it will return false. +/// @param buffer Buffer to set the mark on +/// @param name Mark name +/// @return true if the mark was deleted, else false. +/// @see |nvim_buf_set_mark()| +/// @see |nvim_del_mark()| +Boolean nvim_buf_del_mark(Buffer buffer, String name, Error *err) + FUNC_API_SINCE(8) +{ + bool res = false; + buf_T *buf = find_buffer_by_handle(buffer, err); + + if (!buf) { + return res; + } + + if (name.size != 1) { + api_set_error(err, kErrorTypeValidation, + "Mark name must be a single character"); + return res; + } + + pos_T *pos = getmark_buf(buf, *name.data, false); + + // pos point to NULL when there's no mark with name + if (pos == NULL) { + api_set_error(err, kErrorTypeValidation, "Invalid mark name: '%c'", + *name.data); + return res; + } + + // pos->lnum is 0 when the mark is not valid in the buffer, or is not set. + if (pos->lnum != 0) { + // since the mark belongs to the buffer delete it. + res = set_mark(buf, name, 0, 0, err); + } + + return res; +} + +/// Sets a named mark in the given buffer, all marks are allowed +/// file/uppercase, visual, last change, etc. See |mark-motions|. +/// +/// Marks are (1,0)-indexed. |api-indexing| +/// +/// @note Passing 0 as line deletes the mark +/// +/// @param buffer Buffer to set the mark on +/// @param name Mark name +/// @param line Line number +/// @param col Column/row number +/// @return true if the mark was set, else false. +/// @see |nvim_buf_del_mark()| +/// @see |nvim_buf_get_mark()| +Boolean nvim_buf_set_mark(Buffer buffer, String name, Integer line, Integer col, Error *err) + FUNC_API_SINCE(8) +{ + bool res = false; + buf_T *buf = find_buffer_by_handle(buffer, err); + + if (!buf) { + return res; + } + + if (name.size != 1) { + api_set_error(err, kErrorTypeValidation, + "Mark name must be a single character"); + return res; + } + + res = set_mark(buf, name, line, col, err); + + return res; +} + +/// Returns a tuple (row,col) representing the position of the named mark. See +/// |mark-motions|. /// /// Marks are (1,0)-indexed. |api-indexing| /// /// @param buffer Buffer handle, or 0 for current buffer /// @param name Mark name /// @param[out] err Error details, if any -/// @return (row, col) tuple +/// @return (row, col) tuple, (0, 0) if the mark is not set, or is an +/// uppercase/file mark set in another buffer. +/// @see |nvim_buf_set_mark()| +/// @see |nvim_buf_del_mark()| ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) FUNC_API_SINCE(1) { @@ -1267,7 +1340,7 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, if (extmark.row < 0) { return rv; } - return extmark_to_array(extmark, false, (bool)details); + return extmark_to_array(extmark, false, details); } /// Gets extmarks in "traversal order" from a |charwise| region defined by @@ -1415,6 +1488,10 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// - end_col : ending col of the mark, 0-based exclusive. /// - hl_group : name of the highlight group used to highlight /// this mark. +/// - hl_eol : when true, for a multiline highlight covering the +/// EOL of a line, continue the highlight for the rest +/// of the screen line (just like for diff and +/// cursorline highlight). /// - virt_text : virtual text to link to this mark. /// A list of [text, highlight] tuples, each representing a /// text chunk with specified highlight. `highlight` element @@ -1442,10 +1519,28 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// default /// - "combine": combine with background text color /// - "blend": blend with background text color. -/// - hl_eol : when true, for a multiline highlight covering the -/// EOL of a line, continue the highlight for the rest -/// of the screen line (just like for diff and -/// cursorline highlight). +/// +/// - virt_lines : virtual lines to add next to this mark +/// This should be an array over lines, where each line in +/// turn is an array over [text, highlight] tuples. In +/// general, buffer and window options do not affect the +/// display of the text. In particular 'wrap' +/// and 'linebreak' options do not take effect, so +/// the number of extra screen lines will always match +/// the size of the array. However the 'tabstop' buffer +/// option is still used for hard tabs. By default lines are +/// placed below the buffer line containing the mark. +/// +/// Note: currently virtual lines are limited to one block +/// per buffer. Thus setting a new mark disables any previous +/// `virt_lines` decoration. However plugins should not rely +/// on this behaviour, as this limitation is planned to be +/// removed. +/// +/// - virt_lines_above: place virtual lines above instead. +/// - virt_lines_leftcol: Place extmarks in the leftmost +/// column of the window, bypassing +/// sign and number columns. /// /// - ephemeral : for use with |nvim_set_decoration_provider| /// callbacks. The mark will only be used for the current @@ -1463,193 +1558,189 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// @param[out] err Error details, if any /// @return Id of the created/updated extmark Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer col, - Dictionary opts, Error *err) + Dict(set_extmark) *opts, Error *err) FUNC_API_SINCE(7) { + Decoration decor = DECORATION_INIT; + buf_T *buf = find_buffer_by_handle(buffer, err); if (!buf) { - return 0; + goto error; } if (!ns_initialized((uint64_t)ns_id)) { api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); - return 0; + goto error; } - bool ephemeral = false; - uint64_t id = 0; + if (opts->id.type == kObjectTypeInteger && opts->id.data.integer > 0) { + id = (uint64_t)opts->id.data.integer; + } else if (HAS_KEY(opts->id)) { + api_set_error(err, kErrorTypeValidation, "id is not a positive integer"); + goto error; + } + int line2 = -1; - Decoration decor = DECORATION_INIT; + if (opts->end_line.type == kObjectTypeInteger) { + Integer val = opts->end_line.data.integer; + if (val < 0 || val > buf->b_ml.ml_line_count) { + api_set_error(err, kErrorTypeValidation, "end_line value outside range"); + goto error; + } else { + line2 = (int)val; + } + } else if (HAS_KEY(opts->end_line)) { + api_set_error(err, kErrorTypeValidation, "end_line is not an integer"); + goto error; + } + colnr_T col2 = -1; + if (opts->end_col.type == kObjectTypeInteger) { + Integer val = opts->end_col.data.integer; + if (val < 0 || val > MAXCOL) { + api_set_error(err, kErrorTypeValidation, "end_col value outside range"); + goto error; + } else { + col2 = (int)val; + } + } else if (HAS_KEY(opts->end_col)) { + api_set_error(err, kErrorTypeValidation, "end_col is not an integer"); + goto error; + } - bool right_gravity = true; - bool end_right_gravity = false; - bool end_gravity_set = false; + if (HAS_KEY(opts->hl_group)) { + decor.hl_id = object_to_hl_id(opts->hl_group, "hl_group", err); + if (ERROR_SET(err)) { + goto error; + } + } - for (size_t i = 0; i < opts.size; i++) { - String k = opts.items[i].key; - Object *v = &opts.items[i].value; - if (strequal("id", k.data)) { - if (v->type != kObjectTypeInteger || v->data.integer <= 0) { - api_set_error(err, kErrorTypeValidation, - "id is not a positive integer"); - goto error; - } + if (opts->virt_text.type == kObjectTypeArray) { + decor.virt_text = parse_virt_text(opts->virt_text.data.array, err, + &decor.virt_text_width); + if (ERROR_SET(err)) { + goto error; + } + } else if (HAS_KEY(opts->virt_text)) { + api_set_error(err, kErrorTypeValidation, "virt_text is not an Array"); + goto error; + } + + if (opts->virt_text_pos.type == kObjectTypeString) { + String str = opts->virt_text_pos.data.string; + if (strequal("eol", str.data)) { + decor.virt_text_pos = kVTEndOfLine; + } else if (strequal("overlay", str.data)) { + decor.virt_text_pos = kVTOverlay; + } else if (strequal("right_align", str.data)) { + decor.virt_text_pos = kVTRightAlign; + } else { + api_set_error(err, kErrorTypeValidation, "virt_text_pos: invalid value"); + goto error; + } + } else if (HAS_KEY(opts->virt_text_pos)) { + api_set_error(err, kErrorTypeValidation, "virt_text_pos is not a String"); + goto error; + } - id = (uint64_t)v->data.integer; - } else if (strequal("end_line", k.data)) { - if (v->type != kObjectTypeInteger) { - api_set_error(err, kErrorTypeValidation, - "end_line is not an integer"); - goto error; - } - if (v->data.integer < 0 || v->data.integer > buf->b_ml.ml_line_count) { - api_set_error(err, kErrorTypeValidation, - "end_line value outside range"); - goto error; - } + if (opts->virt_text_win_col.type == kObjectTypeInteger) { + decor.col = (int)opts->virt_text_win_col.data.integer; + decor.virt_text_pos = kVTWinCol; + } else if (HAS_KEY(opts->virt_text_win_col)) { + api_set_error(err, kErrorTypeValidation, + "virt_text_win_col is not a Number of the correct size"); + goto error; + } - line2 = (int)v->data.integer; - } else if (strequal("end_col", k.data)) { - if (v->type != kObjectTypeInteger) { - api_set_error(err, kErrorTypeValidation, - "end_col is not an integer"); - goto error; - } - if (v->data.integer < 0 || v->data.integer > MAXCOL) { - api_set_error(err, kErrorTypeValidation, - "end_col value outside range"); - goto error; - } +#define OPTION_TO_BOOL(target, name, val) \ + target = api_object_to_bool(opts-> name, #name, val, err); \ + if (ERROR_SET(err)) { \ + goto error; \ + } - col2 = (colnr_T)v->data.integer; - } else if (strequal("hl_group", k.data)) { - String hl_group; - switch (v->type) { - case kObjectTypeString: - hl_group = v->data.string; - decor.hl_id = syn_check_group((char_u *)(hl_group.data), - (int)hl_group.size); - break; - case kObjectTypeInteger: - decor.hl_id = (int)v->data.integer; - break; - default: - api_set_error(err, kErrorTypeValidation, - "hl_group is not valid."); - goto error; - } - } else if (strequal("virt_text", k.data)) { - if (v->type != kObjectTypeArray) { - api_set_error(err, kErrorTypeValidation, - "virt_text is not an Array"); - goto error; - } - decor.virt_text = parse_virt_text(v->data.array, err, - &decor.virt_text_width); - if (ERROR_SET(err)) { - goto error; - } - } else if (strequal("virt_text_pos", k.data)) { - if (v->type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, - "virt_text_pos is not a String"); - goto error; - } - String str = v->data.string; - if (strequal("eol", str.data)) { - decor.virt_text_pos = kVTEndOfLine; - } else if (strequal("overlay", str.data)) { - decor.virt_text_pos = kVTOverlay; - } else if (strequal("right_align", str.data)) { - decor.virt_text_pos = kVTRightAlign; - } else { - api_set_error(err, kErrorTypeValidation, - "virt_text_pos: invalid value"); - goto error; - } - } else if (strequal("virt_text_win_col", k.data)) { - if (v->type != kObjectTypeInteger) { - api_set_error(err, kErrorTypeValidation, - "virt_text_win_col is not a Number of the correct size"); - goto error; - } + OPTION_TO_BOOL(decor.virt_text_hide, virt_text_hide, false); + OPTION_TO_BOOL(decor.hl_eol, hl_eol, false); - decor.col = (int)v->data.integer; - decor.virt_text_pos = kVTWinCol; - } else if (strequal("virt_text_hide", k.data)) { - decor.virt_text_hide = api_object_to_bool(*v, - "virt_text_hide", false, err); - if (ERROR_SET(err)) { - goto error; - } - } else if (strequal("hl_eol", k.data)) { - decor.hl_eol = api_object_to_bool(*v, "hl_eol", false, err); - if (ERROR_SET(err)) { - goto error; - } - } else if (strequal("hl_mode", k.data)) { - if (v->type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, - "hl_mode is not a String"); - goto error; - } - String str = v->data.string; - if (strequal("replace", str.data)) { - decor.hl_mode = kHlModeReplace; - } else if (strequal("combine", str.data)) { - decor.hl_mode = kHlModeCombine; - } else if (strequal("blend", str.data)) { - decor.hl_mode = kHlModeBlend; - } else { - api_set_error(err, kErrorTypeValidation, - "virt_text_pos: invalid value"); + if (opts->hl_mode.type == kObjectTypeString) { + String str = opts->hl_mode.data.string; + if (strequal("replace", str.data)) { + decor.hl_mode = kHlModeReplace; + } else if (strequal("combine", str.data)) { + decor.hl_mode = kHlModeCombine; + } else if (strequal("blend", str.data)) { + decor.hl_mode = kHlModeBlend; + } else { + api_set_error(err, kErrorTypeValidation, + "virt_text_pos: invalid value"); + goto error; + } + } else if (HAS_KEY(opts->hl_mode)) { + api_set_error(err, kErrorTypeValidation, "hl_mode is not a String"); + goto error; + } + + VirtLines virt_lines = KV_INITIAL_VALUE; + bool virt_lines_above = false; + bool virt_lines_leftcol = false; + + if (opts->virt_lines.type == kObjectTypeArray) { + Array a = opts->virt_lines.data.array; + for (size_t j = 0; j < a.size; j++) { + if (a.items[j].type != kObjectTypeArray) { + api_set_error(err, kErrorTypeValidation, "virt_text_line item is not an Array"); goto error; } - } else if (strequal("ephemeral", k.data)) { - ephemeral = api_object_to_bool(*v, "ephemeral", false, err); + int dummig; + VirtText jtem = parse_virt_text(a.items[j].data.array, err, &dummig); + kv_push(virt_lines, jtem); if (ERROR_SET(err)) { goto error; } - } else if (strequal("priority", k.data)) { - if (v->type != kObjectTypeInteger) { - api_set_error(err, kErrorTypeValidation, - "priority is not a Number of the correct size"); - goto error; - } + } + } else if (HAS_KEY(opts->virt_lines)) { + api_set_error(err, kErrorTypeValidation, "virt_lines is not an Array"); + goto error; + } - if (v->data.integer < 0 || v->data.integer > UINT16_MAX) { - api_set_error(err, kErrorTypeValidation, - "priority is not a valid value"); - goto error; - } - decor.priority = (DecorPriority)v->data.integer; - } else if (strequal("right_gravity", k.data)) { - if (v->type != kObjectTypeBoolean) { - api_set_error(err, kErrorTypeValidation, - "right_gravity must be a boolean"); - goto error; - } - right_gravity = v->data.boolean; - } else if (strequal("end_right_gravity", k.data)) { - if (v->type != kObjectTypeBoolean) { - api_set_error(err, kErrorTypeValidation, - "end_right_gravity must be a boolean"); - goto error; - } - end_right_gravity = v->data.boolean; - end_gravity_set = true; - } else { - api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); + OPTION_TO_BOOL(virt_lines_above, virt_lines_above, false); + OPTION_TO_BOOL(virt_lines_leftcol, virt_lines_leftcol, false); + + if (opts->priority.type == kObjectTypeInteger) { + Integer val = opts->priority.data.integer; + + if (val < 0 || val > UINT16_MAX) { + api_set_error(err, kErrorTypeValidation, "priority is not a valid value"); goto error; } + decor.priority = (DecorPriority)val; + } else if (HAS_KEY(opts->priority)) { + api_set_error(err, kErrorTypeValidation, "priority is not a Number of the correct size"); + goto error; + } + + bool right_gravity = true; + OPTION_TO_BOOL(right_gravity, right_gravity, true); + + // Only error out if they try to set end_right_gravity without + // setting end_col or end_line + if (line2 == -1 && col2 == -1 && HAS_KEY(opts->end_right_gravity)) { + api_set_error(err, kErrorTypeValidation, + "cannot set end_right_gravity without setting end_line or end_col"); + goto error; } + bool end_right_gravity = false; + OPTION_TO_BOOL(end_right_gravity, end_right_gravity, false); + size_t len = 0; + + bool ephemeral = false; + OPTION_TO_BOOL(ephemeral, ephemeral, false); + if (line < 0 || line > buf->b_ml.ml_line_count) { api_set_error(err, kErrorTypeValidation, "line value outside range"); - return 0; + goto error; } else if (line < buf->b_ml.ml_line_count) { len = ephemeral ? MAXCOL : STRLEN(ml_get_buf(buf, (linenr_T)line+1, false)); } @@ -1658,16 +1749,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer col = (Integer)len; } else if (col < -1 || col > (Integer)len) { api_set_error(err, kErrorTypeValidation, "col value outside range"); - return 0; - } - - - // Only error out if they try to set end_right_gravity without - // setting end_col or end_line - if (line2 == -1 && col2 == -1 && end_gravity_set) { - api_set_error(err, kErrorTypeValidation, - "cannot set end_right_gravity " - "without setting end_line or end_col"); + goto error; } if (col2 >= 0) { @@ -1688,15 +1770,6 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer col2 = 0; } - if (decor.virt_text_pos == kVTRightAlign) { - decor.col = 0; - for (size_t i = 0; i < kv_size(decor.virt_text); i++) { - decor.col - += (int)mb_string2cells((char_u *)kv_A(decor.virt_text, i).text); - } - } - - Decoration *d = NULL; if (ephemeral) { @@ -1721,9 +1794,23 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer goto error; } - id = extmark_set(buf, (uint64_t)ns_id, id, (int)line, (colnr_T)col, - line2, col2, d, right_gravity, - end_right_gravity, kExtmarkNoUndo); + if (kv_size(virt_lines) && buf->b_virt_line_mark) { + mtpos_t pos = marktree_lookup(buf->b_marktree, buf->b_virt_line_mark, NULL); + clear_virt_lines(buf, pos.row); // handles pos.row == -1 + } + + uint64_t mark = extmark_set(buf, (uint64_t)ns_id, &id, (int)line, (colnr_T)col, + line2, col2, d, right_gravity, + end_right_gravity, kExtmarkNoUndo); + + if (kv_size(virt_lines)) { + buf->b_virt_lines = virt_lines; + buf->b_virt_line_mark = mark; + buf->b_virt_line_pos = -1; + buf->b_virt_line_above = virt_lines_above; + buf->b_virt_line_leftcol = virt_lines_leftcol; + redraw_buf_line_later(buf, MIN(buf->b_ml.ml_line_count, line+1+(virt_lines_above?0:1))); + } } return (Integer)id; @@ -1816,7 +1903,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In int hl_id = 0; if (hl_group.size > 0) { - hl_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size); + hl_id = syn_check_group(hl_group.data, (int)hl_group.size); } else { return ns_id; } @@ -1827,7 +1914,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In end_line++; } - extmark_set(buf, ns, 0, + extmark_set(buf, ns, NULL, (int)line, (colnr_T)col_start, end_line, (colnr_T)col_end, decor_hl(hl_id), true, false, kExtmarkNoUndo); @@ -1895,7 +1982,7 @@ Object nvim_buf_call(Buffer buffer, LuaRef fun, Error *err) } try_start(); aco_save_T aco; - aucmd_prepbuf(&aco, (buf_T *)buf); + aucmd_prepbuf(&aco, buf); Array args = ARRAY_DICT_INIT; Object res = nlua_call_ref(fun, NULL, args, true, err); diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index 21b9db85c0..907d09e5b7 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -150,7 +150,7 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, Integer src_id, Integer line, A decor->virt_text = virt_text; decor->virt_text_width = width; - extmark_set(buf, ns_id, 0, (int)line, 0, -1, -1, decor, true, + extmark_set(buf, ns_id, NULL, (int)line, 0, -1, -1, decor, true, false, kExtmarkNoUndo); return src_id; } @@ -165,6 +165,7 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, Integer src_id, Integer line, A /// @param lines Array of lines /// @param[out] err Error details, if any void buffer_insert(Buffer buffer, Integer lnum, ArrayOf(String) lines, Error *err) + FUNC_API_DEPRECATED_SINCE(1) { // "lnum" will be the index of the line after inserting, // no matter if it is negative or not @@ -184,6 +185,7 @@ void buffer_insert(Buffer buffer, Integer lnum, ArrayOf(String) lines, Error *er /// @param[out] err Error details, if any /// @return Line string String buffer_get_line(Buffer buffer, Integer index, Error *err) + FUNC_API_DEPRECATED_SINCE(1) { String rv = { .size = 0 }; @@ -212,6 +214,7 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err) /// @param line Contents of the new line /// @param[out] err Error details, if any void buffer_set_line(Buffer buffer, Integer index, String line, Error *err) + FUNC_API_DEPRECATED_SINCE(1) { Object l = STRING_OBJ(line); Array array = { .items = &l, .size = 1 }; @@ -230,6 +233,7 @@ void buffer_set_line(Buffer buffer, Integer index, String line, Error *err) /// @param index line index /// @param[out] err Error details, if any void buffer_del_line(Buffer buffer, Integer index, Error *err) + FUNC_API_DEPRECATED_SINCE(1) { Array array = ARRAY_DICT_INIT; index = convert_index(index); @@ -255,6 +259,7 @@ ArrayOf(String) buffer_get_line_slice(Buffer buffer, Boolean include_start, Boolean include_end, Error *err) + FUNC_API_DEPRECATED_SINCE(1) { start = convert_index(start) + !include_start; end = convert_index(end) + include_end; @@ -278,6 +283,7 @@ ArrayOf(String) buffer_get_line_slice(Buffer buffer, /// @param[out] err Error details, if any void buffer_set_line_slice(Buffer buffer, Integer start, Integer end, Boolean include_start, Boolean include_end, ArrayOf(String) replacement, Error *err) + FUNC_API_DEPRECATED_SINCE(1) { start = convert_index(start) + !include_start; end = convert_index(end) + include_end; @@ -298,6 +304,7 @@ void buffer_set_line_slice(Buffer buffer, Integer start, Integer end, Boolean in /// @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) + FUNC_API_DEPRECATED_SINCE(1) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -317,6 +324,7 @@ Object buffer_set_var(Buffer buffer, String name, Object value, Error *err) /// @param[out] err Error details, if any /// @return Old value Object buffer_del_var(Buffer buffer, String name, Error *err) + FUNC_API_DEPRECATED_SINCE(1) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -340,6 +348,7 @@ Object buffer_del_var(Buffer buffer, String name, Error *err) /// @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) + FUNC_API_DEPRECATED_SINCE(1) { win_T *win = find_window_by_handle(window, err); @@ -359,6 +368,7 @@ Object window_set_var(Window window, String name, Object value, Error *err) /// @param[out] err Error details, if any /// @return Old value Object window_del_var(Window window, String name, Error *err) + FUNC_API_DEPRECATED_SINCE(1) { win_T *win = find_window_by_handle(window, err); @@ -382,6 +392,7 @@ Object window_del_var(Window window, String name, Error *err) /// @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) + FUNC_API_DEPRECATED_SINCE(1) { tabpage_T *tab = find_tab_by_handle(tabpage, err); @@ -401,6 +412,7 @@ Object tabpage_set_var(Tabpage tabpage, String name, Object value, Error *err) /// @param[out] err Error details, if any /// @return Old value Object tabpage_del_var(Tabpage tabpage, String name, Error *err) + FUNC_API_DEPRECATED_SINCE(1) { tabpage_T *tab = find_tab_by_handle(tabpage, err); @@ -417,6 +429,7 @@ Object tabpage_del_var(Tabpage tabpage, String name, Error *err) /// OR if previous value was `v:null`. /// @return Old value or nil if there was no previous value. Object vim_set_var(String name, Object value, Error *err) + FUNC_API_DEPRECATED_SINCE(1) { return dict_set_var(&globvardict, name, value, false, true, err); } @@ -424,6 +437,7 @@ Object vim_set_var(String name, Object value, Error *err) /// @deprecated /// @see nvim_del_var Object vim_del_var(String name, Error *err) + FUNC_API_DEPRECATED_SINCE(1) { return dict_set_var(&globvardict, name, NIL, true, true, err); } diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua new file mode 100644 index 0000000000..144c252687 --- /dev/null +++ b/src/nvim/api/keysets.lua @@ -0,0 +1,62 @@ +return { + context = { + "types"; + }; + set_extmark = { + "id"; + "end_line"; + "end_col"; + "hl_group"; + "virt_text"; + "virt_text_pos"; + "virt_text_win_col"; + "virt_text_hide"; + "hl_eol"; + "hl_mode"; + "ephemeral"; + "priority"; + "right_gravity"; + "end_right_gravity"; + "virt_lines"; + "virt_lines_above"; + "virt_lines_leftcol"; + }; + keymap = { + "noremap"; + "nowait"; + "silent"; + "script"; + "expr"; + "unique"; + }; + get_commands = { + "builtin"; + }; + float_config = { + "row"; + "col"; + "width"; + "height"; + "anchor"; + "relative"; + "win"; + "bufpos"; + "external"; + "focusable"; + "zindex"; + "border"; + "style"; + "noautocmd"; + }; + runtime = { + "is_lua"; + }; + eval_statusline = { + "winid"; + "maxwidth"; + "fillchar"; + "highlights"; + "use_tabline"; + }; +} + diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h index f0d48bf145..663d1e16b5 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -1,15 +1,15 @@ #ifndef NVIM_API_PRIVATE_DEFS_H #define NVIM_API_PRIVATE_DEFS_H -#include <stdint.h> #include <stdbool.h> +#include <stdint.h> #include <string.h> #include "nvim/func_attr.h" #include "nvim/types.h" -#define ARRAY_DICT_INIT {.size = 0, .capacity = 0, .items = NULL} -#define STRING_INIT {.data = NULL, .size = 0} +#define ARRAY_DICT_INIT { .size = 0, .capacity = 0, .items = NULL } +#define STRING_INIT { .data = NULL, .size = 0 } #define OBJECT_INIT { .type = kObjectTypeNil } #define ERROR_INIT { .type = kErrorTypeNone, .msg = NULL } #define REMOTE_TYPE(type) typedef handle_T type @@ -19,6 +19,7 @@ #ifdef INCLUDE_GENERATED_DECLARATIONS # define ArrayOf(...) Array # define DictionaryOf(...) Dictionary +# define Dict(name) KeyDict_##name #endif // Basic types @@ -129,5 +130,14 @@ struct key_value_pair { Object value; }; +typedef Object *(*field_hash)(void *retval, const char *str, size_t len); +typedef struct { + char *str; + size_t ptr_off; +} KeySetLink; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "keysets_defs.generated.h" +#endif #endif // NVIM_API_PRIVATE_DEFS_H diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c index 38ce7ca78c..519bf8ff14 100644 --- a/src/nvim/api/private/dispatch.c +++ b/src/nvim/api/private/dispatch.c @@ -14,6 +14,7 @@ #include "nvim/api/tabpage.h" #include "nvim/api/ui.h" #include "nvim/api/vim.h" +#include "nvim/api/win_config.h" #include "nvim/api/window.h" #include "nvim/log.h" #include "nvim/map.h" @@ -45,5 +46,5 @@ MsgpackRpcRequestHandler msgpack_rpc_get_handler_for(const char *name, size_t na } #ifdef INCLUDE_GENERATED_DECLARATIONS -#include "api/private/dispatch_wrappers.generated.h" +# include "api/private/dispatch_wrappers.generated.h" #endif diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 541793e528..f1259c8a18 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -4,6 +4,7 @@ #include <assert.h> #include <inttypes.h> #include <stdbool.h> +#include <stddef.h> #include <stdlib.h> #include <string.h> @@ -24,6 +25,7 @@ #include "nvim/lua/executor.h" #include "nvim/map.h" #include "nvim/map_defs.h" +#include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/msgpack_rpc/helpers.h" @@ -395,7 +397,7 @@ void set_option_to(uint64_t channel_id, void *to, int type, String name, Object return; } - stringval = (char *)value.data.string.data; + stringval = value.data.string.data; } const sctx_T save_current_sctx = current_sctx; @@ -814,15 +816,8 @@ Array string_to_array(const String input, bool crlf) /// buffer, or -1 to signify global behavior ("all buffers") /// @param is_unmap When true, removes the mapping that matches {lhs}. void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String rhs, - Dictionary opts, Error *err) + Dict(keymap) *opts, Error *err) { - char *err_msg = NULL; // the error message to report, if any - char *err_arg = NULL; // argument for the error message format string - ErrorType err_type = kErrorTypeNone; - - char_u *lhs_buf = NULL; - char_u *rhs_buf = NULL; - bool global = (buffer == -1); if (global) { buffer = 0; @@ -833,10 +828,21 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String return; } - MapArguments parsed_args; - memset(&parsed_args, 0, sizeof(parsed_args)); - if (parse_keymap_opts(opts, &parsed_args, err)) { - goto fail_and_free; + MapArguments parsed_args = MAP_ARGUMENTS_INIT; + if (opts) { +#define KEY_TO_BOOL(name) \ + parsed_args. name = api_object_to_bool(opts-> name, #name, false, err); \ + if (ERROR_SET(err)) { \ + goto fail_and_free; \ + } + + KEY_TO_BOOL(nowait); + KEY_TO_BOOL(noremap); + KEY_TO_BOOL(silent); + KEY_TO_BOOL(script); + KEY_TO_BOOL(expr); + KEY_TO_BOOL(unique); +#undef KEY_TO_BOOL } parsed_args.buffer = !global; @@ -845,17 +851,13 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String CPO_TO_CPO_FLAGS, &parsed_args); if (parsed_args.lhs_len > MAXMAPLEN) { - err_msg = "LHS exceeds maximum map length: %s"; - err_arg = lhs.data; - err_type = kErrorTypeValidation; - goto fail_with_message; + api_set_error(err, kErrorTypeValidation, "LHS exceeds maximum map length: %s", lhs.data); + goto fail_and_free; } if (mode.size > 1) { - err_msg = "Shortname is too long: %s"; - err_arg = mode.data; - err_type = kErrorTypeValidation; - goto fail_with_message; + api_set_error(err, kErrorTypeValidation, "Shortname is too long: %s", mode.data); + goto fail_and_free; } int mode_val; // integer value of the mapping mode, to be passed to do_map() char_u *p = (char_u *)((mode.size) ? mode.data : "m"); @@ -867,18 +869,14 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String && mode.size > 0) { // get_map_mode() treats unrecognized mode shortnames as ":map". // This is an error unless the given shortname was empty string "". - err_msg = "Invalid mode shortname: \"%s\""; - err_arg = (char *)p; - err_type = kErrorTypeValidation; - goto fail_with_message; + api_set_error(err, kErrorTypeValidation, "Invalid mode shortname: \"%s\"", (char *)p); + goto fail_and_free; } } if (parsed_args.lhs_len == 0) { - err_msg = "Invalid (empty) LHS"; - err_arg = ""; - err_type = kErrorTypeValidation; - goto fail_with_message; + api_set_error(err, kErrorTypeValidation, "Invalid (empty) LHS"); + goto fail_and_free; } bool is_noremap = parsed_args.noremap; @@ -891,16 +889,13 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String // the given RHS was nonempty and not a <Nop>, but was parsed as if it // were empty? assert(false && "Failed to parse nonempty RHS!"); - err_msg = "Parsing of nonempty RHS failed: %s"; - err_arg = rhs.data; - err_type = kErrorTypeException; - goto fail_with_message; + api_set_error(err, kErrorTypeValidation, "Parsing of nonempty RHS failed: %s", rhs.data); + goto fail_and_free; } } else if (is_unmap && parsed_args.rhs_len) { - err_msg = "Gave nonempty RHS in unmap command: %s"; - err_arg = (char *)parsed_args.rhs; - err_type = kErrorTypeValidation; - goto fail_with_message; + api_set_error(err, kErrorTypeValidation, + "Gave nonempty RHS in unmap command: %s", parsed_args.rhs); + goto fail_and_free; } // buf_do_map() reads noremap/unmap as its own argument. @@ -929,113 +924,12 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String goto fail_and_free; } // switch - xfree(lhs_buf); - xfree(rhs_buf); - xfree(parsed_args.rhs); - xfree(parsed_args.orig_rhs); - - return; - -fail_with_message: - api_set_error(err, err_type, err_msg, err_arg); - fail_and_free: - xfree(lhs_buf); - xfree(rhs_buf); xfree(parsed_args.rhs); xfree(parsed_args.orig_rhs); return; } -/// Read in the given opts, setting corresponding flags in `out`. -/// -/// @param opts A dictionary passed to @ref nvim_set_keymap or -/// @ref nvim_buf_set_keymap. -/// @param[out] out MapArguments object in which to set parsed -/// |:map-arguments| flags. -/// @param[out] err Error details, if any. -/// -/// @returns Zero on success, nonzero on failure. -Integer parse_keymap_opts(Dictionary opts, MapArguments *out, Error *err) -{ - char *err_msg = NULL; // the error message to report, if any - char *err_arg = NULL; // argument for the error message format string - ErrorType err_type = kErrorTypeNone; - - out->buffer = false; - out->nowait = false; - out->silent = false; - out->script = false; - out->expr = false; - out->unique = false; - - for (size_t i = 0; i < opts.size; i++) { - KeyValuePair *key_and_val = &opts.items[i]; - char *optname = key_and_val->key.data; - - if (key_and_val->value.type != kObjectTypeBoolean) { - err_msg = "Gave non-boolean value for an opt: %s"; - err_arg = optname; - err_type = kErrorTypeValidation; - goto fail_with_message; - } - - bool was_valid_opt = false; - switch (optname[0]) { - // note: strncmp up to and including the null terminator, so that - // "nowaitFoobar" won't match against "nowait" - - // don't recognize 'buffer' as a key; user shouldn't provide <buffer> - // when calling nvim_set_keymap or nvim_buf_set_keymap, since it can be - // inferred from which function they called - case 'n': - if (STRNCMP(optname, "noremap", 8) == 0) { - was_valid_opt = true; - out->noremap = key_and_val->value.data.boolean; - } else if (STRNCMP(optname, "nowait", 7) == 0) { - was_valid_opt = true; - out->nowait = key_and_val->value.data.boolean; - } - break; - case 's': - if (STRNCMP(optname, "silent", 7) == 0) { - was_valid_opt = true; - out->silent = key_and_val->value.data.boolean; - } else if (STRNCMP(optname, "script", 7) == 0) { - was_valid_opt = true; - out->script = key_and_val->value.data.boolean; - } - break; - case 'e': - if (STRNCMP(optname, "expr", 5) == 0) { - was_valid_opt = true; - out->expr = key_and_val->value.data.boolean; - } - break; - case 'u': - if (STRNCMP(optname, "unique", 7) == 0) { - was_valid_opt = true; - out->unique = key_and_val->value.data.boolean; - } - break; - default: - break; - } // switch - if (!was_valid_opt) { - err_msg = "Invalid key: %s"; - err_arg = optname; - err_type = kErrorTypeValidation; - goto fail_with_message; - } - } // for - - return 0; - -fail_with_message: - api_set_error(err, err_type, err_msg, err_arg); - return 1; -} - /// Collects `n` buffer lines into array `l`, optionally replacing newlines /// with NUL. /// @@ -1413,8 +1307,10 @@ static void set_option_value_for(char *key, int numval, char *stringval, int opt switch (opt_type) { case SREQ_WIN: - if (switch_win(&save_curwin, &save_curtab, (win_T *)from, - win_find_tabpage((win_T *)from), false) == FAIL) { + if (switch_win_noblock(&save_curwin, &save_curtab, (win_T *)from, + win_find_tabpage((win_T *)from), true) + == FAIL) { + restore_win_noblock(save_curwin, save_curtab, true); if (try_end(err)) { return; } @@ -1424,7 +1320,7 @@ static void set_option_value_for(char *key, int numval, char *stringval, int opt return; } set_option_value_err(key, numval, stringval, opt_flags, err); - restore_win(save_curwin, save_curtab, true); + restore_win_noblock(save_curwin, save_curtab, true); break; case SREQ_BUF: aucmd_prepbuf(&aco, (buf_T *)from); @@ -1625,7 +1521,7 @@ VirtText parse_virt_text(Array chunks, Error *err, int *width) } } - char *text = transstr(str.size > 0 ? str.data : ""); // allocates + char *text = transstr(str.size > 0 ? str.data : "", false); // allocates w += (int)mb_string2cells((char_u *)text); kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id })); @@ -1663,7 +1559,7 @@ int object_to_hl_id(Object obj, const char *what, Error *err) { if (obj.type == kObjectTypeString) { String str = obj.data.string; - return str.size ? syn_check_group((char_u *)str.data, (int)str.size) : 0; + return str.size ? syn_check_group(str.data, (int)str.size) : 0; } else if (obj.type == kObjectTypeInteger) { return MAX((int)obj.data.integer, 0); } else { @@ -1697,7 +1593,7 @@ HlMessage parse_hl_msg(Array chunks, Error *err) String hl = chunk.items[1].data.string; if (hl.size > 0) { // TODO(bfredl): use object_to_hl_id and allow integer - int hl_id = syn_check_group((char_u *)hl.data, (int)hl.size); + int hl_id = syn_check_group(hl.data, (int)hl.size); attr = hl_id > 0 ? syn_id2attr(hl_id) : 0; } } @@ -1723,367 +1619,77 @@ const char *describe_ns(NS ns_id) return "(UNKNOWN PLUGIN)"; } -static bool parse_float_anchor(String anchor, FloatAnchor *out) +bool api_dict_to_keydict(void *rv, field_hash hashy, Dictionary dict, Error *err) { - if (anchor.size == 0) { - *out = (FloatAnchor)0; - } - char *str = anchor.data; - if (striequal(str, "NW")) { - *out = 0; // NW is the default - } else if (striequal(str, "NE")) { - *out = kFloatAnchorEast; - } else if (striequal(str, "SW")) { - *out = kFloatAnchorSouth; - } else if (striequal(str, "SE")) { - *out = kFloatAnchorSouth | kFloatAnchorEast; - } else { - return false; - } - return true; -} + for (size_t i = 0; i < dict.size; i++) { + String k = dict.items[i].key; + Object *field = hashy(rv, k.data, k.size); + if (!field) { + api_set_error(err, kErrorTypeValidation, "Invalid key: '%.*s'", (int)k.size, k.data); + return false; + } -static bool parse_float_relative(String relative, FloatRelative *out) -{ - char *str = relative.data; - if (striequal(str, "editor")) { - *out = kFloatRelativeEditor; - } else if (striequal(str, "win")) { - *out = kFloatRelativeWindow; - } else if (striequal(str, "cursor")) { - *out = kFloatRelativeCursor; - } else { - return false; + *field = dict.items[i].value; } + return true; } -static bool parse_float_bufpos(Array bufpos, lpos_T *out) +void api_free_keydict(void *dict, KeySetLink *table) { - if (bufpos.size != 2 - || bufpos.items[0].type != kObjectTypeInteger - || bufpos.items[1].type != kObjectTypeInteger) { - return false; + for (size_t i = 0; table[i].str; i++) { + api_free_object(*(Object *)((char *)dict + table[i].ptr_off)); } - out->lnum = bufpos.items[0].data.integer; - out->col = (colnr_T)bufpos.items[1].data.integer; - return true; } -static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) +/// Set a named mark +/// buffer and mark name must be validated already +/// @param buffer Buffer to set the mark on +/// @param name Mark name +/// @param line Line number +/// @param col Column/row number +/// @return true if the mark was set, else false +bool set_mark(buf_T *buf, String name, Integer line, Integer col, Error *err) { - struct { - const char *name; - schar_T chars[8]; - bool shadow_color; - } defaults[] = { - { "double", { "╔", "═", "╗", "║", "╝", "═", "╚", "║" }, false }, - { "single", { "┌", "─", "┐", "│", "┘", "─", "└", "│" }, false }, - { "shadow", { "", "", " ", " ", " ", " ", " ", "" }, true }, - { "rounded", { "╭", "─", "╮", "│", "╯", "─", "╰", "│" }, false }, - { "solid", { " ", " ", " ", " ", " ", " ", " ", " " }, false }, - { NULL, { { NUL } }, false }, - }; - - schar_T *chars = fconfig->border_chars; - int *hl_ids = fconfig->border_hl_ids; - - fconfig->border = true; - - if (style.type == kObjectTypeArray) { - Array arr = style.data.array; - size_t size = arr.size; - if (!size || size > 8 || (size & (size-1))) { - api_set_error(err, kErrorTypeValidation, - "invalid number of border chars"); - return; - } - for (size_t i = 0; i < size; i++) { - Object iytem = arr.items[i]; - String string; - int hl_id = 0; - if (iytem.type == kObjectTypeArray) { - Array iarr = iytem.data.array; - if (!iarr.size || iarr.size > 2) { - api_set_error(err, kErrorTypeValidation, "invalid border char"); - return; - } - if (iarr.items[0].type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, "invalid border char"); - return; - } - string = iarr.items[0].data.string; - if (iarr.size == 2) { - hl_id = object_to_hl_id(iarr.items[1], "border char highlight", err); - if (ERROR_SET(err)) { - return; - } - } - } else if (iytem.type == kObjectTypeString) { - string = iytem.data.string; - } else { - api_set_error(err, kErrorTypeValidation, "invalid border char"); - return; - } - if (string.size - && mb_string2cells_len((char_u *)string.data, string.size) > 1) { - api_set_error(err, kErrorTypeValidation, - "border chars must be one cell"); - return; - } - size_t len = MIN(string.size, sizeof(*chars)-1); - if (len) { - memcpy(chars[i], string.data, len); - } - chars[i][len] = NUL; - hl_ids[i] = hl_id; - } - while (size < 8) { - memcpy(chars+size, chars, sizeof(*chars) * size); - memcpy(hl_ids+size, hl_ids, sizeof(*hl_ids) * size); - size <<= 1; - } - if ((chars[7][0] && chars[1][0] && !chars[0][0]) - || (chars[1][0] && chars[3][0] && !chars[2][0]) - || (chars[3][0] && chars[5][0] && !chars[4][0]) - || (chars[5][0] && chars[7][0] && !chars[6][0])) { - api_set_error(err, kErrorTypeValidation, - "corner between used edges must be specified"); - } - } else if (style.type == kObjectTypeString) { - String str = style.data.string; - if (str.size == 0 || strequal(str.data, "none")) { - fconfig->border = false; - return; + buf = buf == NULL ? curbuf : buf; + // If line == 0 the marks is being deleted + bool res = false; + bool deleting = false; + if (line == 0) { + col = 0; + deleting = true; + } else { + if (col > MAXCOL) { + api_set_error(err, kErrorTypeValidation, "Column value outside range"); + return res; } - for (size_t i = 0; defaults[i].name; i++) { - if (strequal(str.data, defaults[i].name)) { - memcpy(chars, defaults[i].chars, sizeof(defaults[i].chars)); - memset(hl_ids, 0, 8 * sizeof(*hl_ids)); - if (defaults[i].shadow_color) { - int hl_blend = SYN_GROUP_STATIC("FloatShadow"); - int hl_through = SYN_GROUP_STATIC("FloatShadowThrough"); - hl_ids[2] = hl_through; - hl_ids[3] = hl_blend; - hl_ids[4] = hl_blend; - hl_ids[5] = hl_blend; - hl_ids[6] = hl_through; - } - return; - } + if (line < 1 || line > buf->b_ml.ml_line_count) { + api_set_error(err, kErrorTypeValidation, "Line value outside range"); + return res; } - api_set_error(err, kErrorTypeValidation, - "invalid border style \"%s\"", str.data); } -} - -bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, bool new_win, - Error *err) -{ - // TODO(bfredl): use a get/has_key interface instead and get rid of extra - // flags - bool has_row = false, has_col = false, has_relative = false; - bool has_external = false, has_window = false; - bool has_width = false, has_height = false; - bool has_bufpos = false; - - for (size_t i = 0; i < config.size; i++) { - char *key = config.items[i].key.data; - Object val = config.items[i].value; - if (!strcmp(key, "row")) { - has_row = true; - if (val.type == kObjectTypeInteger) { - fconfig->row = (double)val.data.integer; - } else if (val.type == kObjectTypeFloat) { - fconfig->row = val.data.floating; - } else { - api_set_error(err, kErrorTypeValidation, - "'row' key must be Integer or Float"); - return false; - } - } else if (!strcmp(key, "col")) { - has_col = true; - if (val.type == kObjectTypeInteger) { - fconfig->col = (double)val.data.integer; - } else if (val.type == kObjectTypeFloat) { - fconfig->col = val.data.floating; - } else { - api_set_error(err, kErrorTypeValidation, - "'col' key must be Integer or Float"); - return false; - } - } else if (strequal(key, "width")) { - has_width = true; - if (val.type == kObjectTypeInteger && val.data.integer > 0) { - fconfig->width = (int)val.data.integer; - } else { - api_set_error(err, kErrorTypeValidation, - "'width' key must be a positive Integer"); - return false; - } - } else if (strequal(key, "height")) { - has_height = true; - if (val.type == kObjectTypeInteger && val.data.integer > 0) { - fconfig->height = (int)val.data.integer; - } else { - api_set_error(err, kErrorTypeValidation, - "'height' key must be a positive Integer"); - return false; - } - } else if (!strcmp(key, "anchor")) { - if (val.type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, - "'anchor' key must be String"); - return false; - } - if (!parse_float_anchor(val.data.string, &fconfig->anchor)) { - api_set_error(err, kErrorTypeValidation, - "Invalid value of 'anchor' key"); - return false; - } - } else if (!strcmp(key, "relative")) { - if (val.type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, - "'relative' key must be String"); - return false; - } - // ignore empty string, to match nvim_win_get_config - if (val.data.string.size > 0) { - has_relative = true; - if (!parse_float_relative(val.data.string, &fconfig->relative)) { - api_set_error(err, kErrorTypeValidation, - "Invalid value of 'relative' key"); - return false; - } - } - } else if (!strcmp(key, "win")) { - has_window = true; - if (val.type != kObjectTypeInteger - && val.type != kObjectTypeWindow) { - api_set_error(err, kErrorTypeValidation, - "'win' key must be Integer or Window"); - return false; - } - fconfig->window = (Window)val.data.integer; - } else if (!strcmp(key, "bufpos")) { - if (val.type != kObjectTypeArray) { - api_set_error(err, kErrorTypeValidation, - "'bufpos' key must be Array"); - return false; - } - if (!parse_float_bufpos(val.data.array, &fconfig->bufpos)) { - api_set_error(err, kErrorTypeValidation, - "Invalid value of 'bufpos' key"); - return false; - } - has_bufpos = true; - } else if (!strcmp(key, "external")) { - has_external = fconfig->external - = api_object_to_bool(val, "'external' key", false, err); - if (ERROR_SET(err)) { - return false; - } - } else if (!strcmp(key, "focusable")) { - fconfig->focusable - = api_object_to_bool(val, "'focusable' key", true, err); - if (ERROR_SET(err)) { - return false; - } - } else if (strequal(key, "zindex")) { - if (val.type == kObjectTypeInteger && val.data.integer > 0) { - fconfig->zindex = (int)val.data.integer; - } else { - api_set_error(err, kErrorTypeValidation, - "'zindex' key must be a positive Integer"); - return false; - } - } else if (!strcmp(key, "border")) { - parse_border_style(val, fconfig, err); - if (ERROR_SET(err)) { - return false; - } - } else if (!strcmp(key, "style")) { - if (val.type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, - "'style' key must be String"); - return false; - } - if (val.data.string.data[0] == NUL) { - fconfig->style = kWinStyleUnused; - } else if (striequal(val.data.string.data, "minimal")) { - fconfig->style = kWinStyleMinimal; - } else { - api_set_error(err, kErrorTypeValidation, - "Invalid value of 'style' key"); - } - } else if (strequal(key, "noautocmd") && new_win) { - fconfig->noautocmd - = api_object_to_bool(val, "'noautocmd' key", false, err); - if (ERROR_SET(err)) { - return false; - } + pos_T pos = { line, (int)col, (int)col }; + res = setmark_pos(*name.data, &pos, buf->handle); + if (!res) { + if (deleting) { + api_set_error(err, kErrorTypeException, + "Failed to delete named mark: %c", *name.data); } else { - api_set_error(err, kErrorTypeValidation, - "Invalid key '%s'", key); - return false; - } - } - - if (has_window && !(has_relative - && fconfig->relative == kFloatRelativeWindow)) { - api_set_error(err, kErrorTypeValidation, - "'win' key is only valid with relative='win'"); - return false; - } - - if ((has_relative && fconfig->relative == kFloatRelativeWindow) - && (!has_window || fconfig->window == 0)) { - fconfig->window = curwin->handle; - } - - if (has_window && !has_bufpos) { - fconfig->bufpos.lnum = -1; - } - - if (has_bufpos) { - if (!has_row) { - fconfig->row = (fconfig->anchor & kFloatAnchorSouth) ? 0 : 1; - has_row = true; - } - if (!has_col) { - fconfig->col = 0; - has_col = true; + api_set_error(err, kErrorTypeException, + "Failed to set named mark: %c", *name.data); } } + return res; +} - if (has_relative && has_external) { - api_set_error(err, kErrorTypeValidation, - "Only one of 'relative' and 'external' must be used"); - return false; - } else if (!reconf && !has_relative && !has_external) { - api_set_error(err, kErrorTypeValidation, - "One of 'relative' and 'external' must be used"); - return false; - } else if (has_relative) { - fconfig->external = false; - } - - if (!reconf && !(has_height && has_width)) { - api_set_error(err, kErrorTypeValidation, - "Must specify 'width' and 'height'"); - return false; - } - - if (fconfig->external && !ui_has(kUIMultigrid)) { - api_set_error(err, kErrorTypeValidation, - "UI doesn't support external windows"); - return false; - } - - if (has_relative != has_row || has_row != has_col) { - api_set_error(err, kErrorTypeValidation, - "'relative' requires 'row'/'col' or 'bufpos'"); - return false; +/// Get default statusline highlight for window +const char *get_default_stl_hl(win_T *wp) +{ + if (wp == NULL) { + return "TabLineFill"; + } else if (wp == curwin) { + return "StatusLine"; + } else { + return "StatusLineNC"; } - return true; } diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index ecce6afa26..08d2c8d90c 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -4,12 +4,12 @@ #include <stdbool.h> #include "nvim/api/private/defs.h" -#include "nvim/vim.h" -#include "nvim/getchar.h" -#include "nvim/memory.h" #include "nvim/decoration.h" #include "nvim/ex_eval.h" +#include "nvim/getchar.h" #include "nvim/lib/kvec.h" +#include "nvim/memory.h" +#include "nvim/vim.h" #define OBJECT_OBJ(o) o @@ -59,6 +59,9 @@ #define NIL ((Object)OBJECT_INIT) #define NULL_STRING ((String)STRING_INIT) +// currently treat key=vim.NIL as if the key was missing +#define HAS_KEY(o) ((o).type != kObjectTypeNil) + #define PUT(dict, k, v) \ kv_push(dict, ((KeyValuePair) { .key = cstr_to_string(k), .value = v })) @@ -73,7 +76,7 @@ name.size = fixsize; \ name.items = name##__items; \ -#define STATIC_CSTR_AS_STRING(s) ((String) {.data = s, .size = sizeof(s) - 1}) +#define STATIC_CSTR_AS_STRING(s) ((String) { .data = s, .size = sizeof(s) - 1 }) /// Create a new String instance, putting data in allocated memory /// @@ -134,10 +137,13 @@ typedef struct { msg_list = &private_msg_list; \ private_msg_list = NULL; \ code \ - msg_list = saved_msg_list; /* Restore the exception context. */ \ + msg_list = saved_msg_list; /* Restore the exception context. */ \ } while (0) #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/helpers.h.generated.h" +# include "keysets.h.generated.h" #endif + + #endif // NVIM_API_PRIVATE_HELPERS_H diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 9b200dcba2..d86aecc318 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -40,7 +40,6 @@ typedef struct { static PMap(uint64_t) connected_uis = MAP_INIT; void remote_ui_disconnect(uint64_t channel_id) - FUNC_API_NOEXPORT { UI *ui = pmap_get(uint64_t)(&connected_uis, channel_id); if (!ui) { @@ -57,7 +56,6 @@ void remote_ui_disconnect(uint64_t channel_id) /// 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) { @@ -172,6 +170,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona /// @deprecated void ui_attach(uint64_t channel_id, Integer width, Integer height, Boolean enable_rgb, Error *err) + FUNC_API_DEPRECATED_SINCE(1) { Dictionary opts = ARRAY_DICT_INIT; PUT(opts, "rgb", BOOLEAN_OBJ(enable_rgb)); diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 3be45d0cf7..b5cc02e761 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -17,6 +17,7 @@ #include "nvim/api/window.h" #include "nvim/ascii.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/context.h" #include "nvim/decoration.h" #include "nvim/edit.h" @@ -59,7 +60,6 @@ #endif void api_vim_free_all_mem(void) - FUNC_API_NOEXPORT { String name; handle_T id; @@ -196,7 +196,7 @@ Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Error *err) Integer nvim_get_hl_id_by_name(String name) FUNC_API_SINCE(7) { - return syn_check_group((const char_u *)name.data, (int)name.size); + return syn_check_group(name.data, (int)name.size); } Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) @@ -228,7 +228,7 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) void nvim_set_hl(Integer ns_id, String name, Dictionary val, Error *err) FUNC_API_SINCE(7) { - int hl_id = syn_check_group( (char_u *)(name.data), (int)name.size); + int hl_id = syn_check_group(name.data, (int)name.size); int link_id = -1; HlAttrs attrs = dict2hlattrs(val, true, &link_id, err); @@ -264,7 +264,6 @@ void nvim__set_hl_ns(Integer ns_id, Error *err) } static void on_redraw_event(void **argv) - FUNC_API_NOEXPORT { redraw_all_later(NOT_VALID); } @@ -714,7 +713,7 @@ Object nvim_call_dict_function(Object dict, String fn, Array args, Error *err) } fn = (String) { .data = (char *)di->di_tv.vval.v_string, - .size = strlen((char *)di->di_tv.vval.v_string), + .size = STRLEN(di->di_tv.vval.v_string), }; } @@ -758,6 +757,11 @@ ArrayOf(String) nvim_list_runtime_paths(Error *err) return nvim_get_runtime_file(NULL_STRING, true, err); } +Array nvim__runtime_inspect(void) +{ + return runtime_inspect(); +} + /// Find files in runtime directories /// /// 'name' can contain wildcards. For example @@ -796,6 +800,25 @@ String nvim__get_lib_dir(void) return cstr_as_string(get_lib_dir()); } +/// Find files in runtime directories +/// +/// @param pat pattern of files to search for +/// @param all whether to return all matches or only the first +/// @param options +/// is_lua: only search lua subdirs +/// @return list of absolute paths to the found files +ArrayOf(String) nvim__get_runtime(Array pat, Boolean all, Dict(runtime) *opts, Error *err) + FUNC_API_SINCE(8) + FUNC_API_FAST +{ + bool is_lua = api_object_to_bool(opts->is_lua, "is_lua", false, err); + if (ERROR_SET(err)) { + return (Array)ARRAY_DICT_INIT; + } + return runtime_get_named(is_lua, pat, all); +} + + /// Changes the global working directory. /// /// @param dir Directory path @@ -1221,14 +1244,20 @@ fail: /// buffer in a configured window before calling this. For instance, for a /// floating display, first create an empty buffer using |nvim_create_buf()|, /// then display it using |nvim_open_win()|, and then call this function. -/// Then |nvim_chan_send()| cal be called immediately to process sequences +/// Then |nvim_chan_send()| can be called immediately to process sequences /// in a virtual terminal having the intended size. /// /// @param buffer the buffer to use (expected to be empty) -/// @param opts Optional parameters. Reserved for future use. +/// @param opts Optional parameters. +/// - on_input: lua callback for input sent, i e keypresses in terminal +/// mode. Note: keypresses are sent raw as they would be to the pty +/// master end. For instance, a carriage return is sent +/// as a "\r", not as a "\n". |textlock| applies. It is possible +/// to call |nvim_chan_send| directly in the callback however. +/// ["input", term, bufnr, data] /// @param[out] err Error details, if any /// @return Channel id, or 0 on error -Integer nvim_open_term(Buffer buffer, Dictionary opts, Error *err) +Integer nvim_open_term(Buffer buffer, DictionaryOf(LuaRef) opts, Error *err) FUNC_API_SINCE(7) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -1236,13 +1265,27 @@ Integer nvim_open_term(Buffer buffer, Dictionary opts, Error *err) return 0; } - if (opts.size > 0) { - api_set_error(err, kErrorTypeValidation, "opts dict isn't empty"); - return 0; + LuaRef cb = LUA_NOREF; + for (size_t i = 0; i < opts.size; i++) { + String k = opts.items[i].key; + Object *v = &opts.items[i].value; + if (strequal("on_input", k.data)) { + if (v->type != kObjectTypeLuaRef) { + api_set_error(err, kErrorTypeValidation, + "%s is not a function", "on_input"); + return 0; + } + cb = v->data.luaref; + v->data.luaref = LUA_NOREF; + break; + } else { + api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); + } } TerminalOptions topts; Channel *chan = channel_alloc(kChannelStreamInternal); + chan->stream.internal.cb = cb; topts.data = chan; // NB: overridden in terminal_check_size if a window is already // displaying the buffer @@ -1260,7 +1303,18 @@ Integer nvim_open_term(Buffer buffer, Dictionary opts, Error *err) static void term_write(char *buf, size_t size, void *data) { - // TODO(bfredl): lua callback + Channel *chan = data; + LuaRef cb = chan->stream.internal.cb; + if (cb == LUA_NOREF) { + return; + } + FIXED_TEMP_ARRAY(args, 3); + args.items[0] = INTEGER_OBJ((Integer)chan->id); + args.items[1] = BUFFER_OBJ(terminal_buf(chan->term)); + args.items[2] = STRING_OBJ(((String){ .data = buf, .size = size })); + textlock++; + nlua_call_ref(cb, "input", args, false, NULL); + textlock--; } static void term_resize(uint16_t width, uint16_t height, void *data) @@ -1305,153 +1359,6 @@ void nvim_chan_send(Integer chan, String data, Error *err) } } -/// Open a new window. -/// -/// Currently this is used to open floating and external windows. -/// Floats are windows that are drawn above the split layout, at some anchor -/// position in some other window. Floats can be drawn internally or by external -/// GUI with the |ui-multigrid| extension. External windows are only supported -/// with multigrid GUIs, and are displayed as separate top-level windows. -/// -/// For a general overview of floats, see |api-floatwin|. -/// -/// Exactly one of `external` and `relative` must be specified. The `width` and -/// `height` of the new window must be specified. -/// -/// With relative=editor (row=0,col=0) refers to the top-left corner of the -/// screen-grid and (row=Lines-1,col=Columns-1) refers to the bottom-right -/// corner. Fractional values are allowed, but the builtin implementation -/// (used by non-multigrid UIs) will always round down to nearest integer. -/// -/// Out-of-bounds values, and configurations that make the float not fit inside -/// the main editor, are allowed. The builtin implementation truncates values -/// so floats are fully within the main screen grid. External GUIs -/// could let floats hover outside of the main window like a tooltip, but -/// this should not be used to specify arbitrary WM screen positions. -/// -/// Example (Lua): window-relative float -/// <pre> -/// vim.api.nvim_open_win(0, false, -/// {relative='win', row=3, col=3, width=12, height=3}) -/// </pre> -/// -/// Example (Lua): buffer-relative float (travels as buffer is scrolled) -/// <pre> -/// vim.api.nvim_open_win(0, false, -/// {relative='win', width=12, height=3, bufpos={100,10}}) -/// </pre> -/// -/// @param buffer Buffer to display, or 0 for current buffer -/// @param enter Enter the window (make it the current window) -/// @param config Map defining the window configuration. Keys: -/// - `relative`: Sets the window layout to "floating", placed at (row,col) -/// coordinates relative to: -/// - "editor" The global editor grid -/// - "win" Window given by the `win` field, or current window. -/// - "cursor" Cursor position in current window. -/// - `win`: |window-ID| for relative="win". -/// - `anchor`: Decides which corner of the float to place at (row,col): -/// - "NW" northwest (default) -/// - "NE" northeast -/// - "SW" southwest -/// - "SE" southeast -/// - `width`: Window width (in character cells). Minimum of 1. -/// - `height`: Window height (in character cells). Minimum of 1. -/// - `bufpos`: Places float relative to buffer text (only when -/// relative="win"). Takes a tuple of zero-indexed [line, column]. -/// `row` and `col` if given are applied relative to this -/// position, else they default to `row=1` and `col=0` -/// (thus like a tooltip near the buffer text). -/// - `row`: Row position in units of "screen cell height", may be fractional. -/// - `col`: Column position in units of "screen cell width", may be -/// fractional. -/// - `focusable`: Enable focus by user actions (wincmds, mouse events). -/// Defaults to true. Non-focusable windows can be entered by -/// |nvim_set_current_win()|. -/// - `external`: GUI should display the window as an external -/// top-level window. Currently accepts no other positioning -/// configuration together with this. -/// - `zindex`: Stacking order. floats with higher `zindex` go on top on -/// floats with lower indices. Must be larger than zero. The -/// following screen elements have hard-coded z-indices: -/// - 100: insert completion popupmenu -/// - 200: message scrollback -/// - 250: cmdline completion popupmenu (when wildoptions+=pum) -/// The default value for floats are 50. In general, values below 100 are -/// recommended, unless there is a good reason to overshadow builtin -/// elements. -/// - `style`: Configure the appearance of the window. Currently only takes -/// one non-empty value: -/// - "minimal" Nvim will display the window with many UI options -/// disabled. This is useful when displaying a temporary -/// float where the text should not be edited. Disables -/// 'number', 'relativenumber', 'cursorline', 'cursorcolumn', -/// 'foldcolumn', 'spell' and 'list' options. 'signcolumn' -/// is changed to `auto` and 'colorcolumn' is cleared. The -/// end-of-buffer region is hidden by setting `eob` flag of -/// 'fillchars' to a space char, and clearing the -/// |EndOfBuffer| region in 'winhighlight'. -/// - `border`: Style of (optional) window border. This can either be a string -/// or an array. The string values are -/// - "none": No border (default). -/// - "single": A single line box. -/// - "double": A double line box. -/// - "rounded": Like "single", but with rounded corners ("╭" etc.). -/// - "solid": Adds padding by a single whitespace cell. -/// - "shadow": A drop shadow effect by blending with the background. -/// - If it is an array, it should have a length of eight or any divisor of -/// eight. The array will specifify the eight chars building up the border -/// in a clockwise fashion starting with the top-left corner. As an -/// example, the double box style could be specified as -/// [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ]. -/// If the number of chars are less than eight, they will be repeated. Thus -/// an ASCII border could be specified as -/// [ "/", "-", "\\", "|" ], -/// or all chars the same as -/// [ "x" ]. -/// An empty string can be used to turn off a specific border, for instance, -/// [ "", "", "", ">", "", "", "", "<" ] -/// will only make vertical borders but not horizontal ones. -/// By default, `FloatBorder` highlight is used, which links to `VertSplit` -/// when not defined. It could also be specified by character: -/// [ {"+", "MyCorner"}, {"x", "MyBorder"} ]. -/// - `noautocmd`: If true then no buffer-related autocommand events such as -/// |BufEnter|, |BufLeave| or |BufWinEnter| may fire from -/// calling this function. -/// -/// @param[out] err Error details, if any -/// -/// @return Window handle, or 0 on error -Window nvim_open_win(Buffer buffer, Boolean enter, Dictionary config, Error *err) - FUNC_API_SINCE(6) - FUNC_API_CHECK_TEXTLOCK -{ - FloatConfig fconfig = FLOAT_CONFIG_INIT; - if (!parse_float_config(config, &fconfig, false, true, err)) { - return 0; - } - win_T *wp = win_new_float(NULL, fconfig, err); - if (!wp) { - return 0; - } - if (enter) { - win_enter(wp, false); - } - if (!win_valid(wp)) { - api_set_error(err, kErrorTypeException, "Window was closed immediately"); - return 0; - } - if (buffer > 0) { - win_set_buf(wp->handle, buffer, fconfig.noautocmd, err); - } - - if (fconfig.style == kWinStyleMinimal) { - win_set_minimal_style(wp); - didset_window_options(wp); - } - return wp->handle; -} - /// Gets the current list of tabpage handles. /// /// @return List of tabpage handles @@ -1507,7 +1414,7 @@ void nvim_set_current_tabpage(Tabpage tabpage, Error *err) } } -/// Creates a new *namespace*, or gets an existing one. +/// Creates a new \*namespace\* or gets an existing one. /// /// Namespaces are used for buffer highlights and virtual text, see /// |nvim_buf_add_highlight()| and |nvim_buf_set_extmark()|. @@ -1758,24 +1665,15 @@ Dictionary nvim_get_color_map(void) /// @param[out] err Error details, if any /// /// @return map of global |context|. -Dictionary nvim_get_context(Dictionary opts, Error *err) +Dictionary nvim_get_context(Dict(context) *opts, Error *err) FUNC_API_SINCE(6) { Array types = ARRAY_DICT_INIT; - for (size_t i = 0; i < opts.size; i++) { - String k = opts.items[i].key; - Object v = opts.items[i].value; - if (strequal("types", k.data)) { - if (v.type != kObjectTypeArray) { - api_set_error(err, kErrorTypeValidation, "invalid value for key: %s", - k.data); - return (Dictionary)ARRAY_DICT_INIT; - } - types = v.data.array; - } else { - api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); - return (Dictionary)ARRAY_DICT_INIT; - } + if (opts->types.type == kObjectTypeArray) { + types = opts->types.data.array; + } else if (opts->types.type != kObjectTypeNil) { + api_set_error(err, kErrorTypeValidation, "invalid value for key: types"); + return (Dictionary)ARRAY_DICT_INIT; } int int_types = types.size > 0 ? 0 : kCtxAll; @@ -1885,7 +1783,7 @@ ArrayOf(Dictionary) nvim_get_keymap(String mode) /// as keys excluding |<buffer>| but including |noremap|. /// Values are Booleans. Unknown key is an error. /// @param[out] err Error details, if any. -void nvim_set_keymap(String mode, String lhs, String rhs, Dictionary opts, Error *err) +void nvim_set_keymap(String mode, String lhs, String rhs, Dict(keymap) *opts, Error *err) FUNC_API_SINCE(6) { modify_keymap(-1, false, mode, lhs, rhs, opts, err); @@ -1911,7 +1809,7 @@ void nvim_del_keymap(String mode, String lhs, Error *err) /// @param[out] err Error details, if any. /// /// @returns Map of maps describing commands. -Dictionary nvim_get_commands(Dictionary opts, Error *err) +Dictionary nvim_get_commands(Dict(get_commands) *opts, Error *err) FUNC_API_SINCE(4) { return nvim_buf_get_commands(-1, opts, err); @@ -2937,3 +2835,266 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, Erro error: decor_provider_clear(p); } + +/// Deletes a uppercase/file named mark. See |mark-motions|. +/// +/// @note fails with error if a lowercase or buffer local named mark is used. +/// @param name Mark name +/// @return true if the mark was deleted, else false. +/// @see |nvim_buf_del_mark()| +/// @see |nvim_get_mark()| +Boolean nvim_del_mark(String name, Error *err) + FUNC_API_SINCE(8) +{ + bool res = false; + if (name.size != 1) { + api_set_error(err, kErrorTypeValidation, + "Mark name must be a single character"); + return res; + } + // Only allow file/uppercase marks + // TODO(muniter): Refactor this ASCII_ISUPPER macro to a proper function + if (ASCII_ISUPPER(*name.data) || ascii_isdigit(*name.data)) { + res = set_mark(NULL, name, 0, 0, err); + } else { + api_set_error(err, kErrorTypeValidation, + "Only file/uppercase marks allowed, invalid mark name: '%c'", + *name.data); + } + return res; +} + +/// Return a tuple (row, col, buffer, buffername) representing the position of +/// the uppercase/file named mark. See |mark-motions|. +/// +/// Marks are (1,0)-indexed. |api-indexing| +/// +/// @note fails with error if a lowercase or buffer local named mark is used. +/// @param name Mark name +/// @return 4-tuple (row, col, buffer, buffername), (0, 0, 0, '') if the mark is +/// not set. +/// @see |nvim_buf_set_mark()| +/// @see |nvim_del_mark()| +Array nvim_get_mark(String name, Error *err) + FUNC_API_SINCE(8) +{ + Array rv = ARRAY_DICT_INIT; + + if (name.size != 1) { + api_set_error(err, kErrorTypeValidation, + "Mark name must be a single character"); + return rv; + } else if (!(ASCII_ISUPPER(*name.data) || ascii_isdigit(*name.data))) { + api_set_error(err, kErrorTypeValidation, + "Only file/uppercase marks allowed, invalid mark name: '%c'", + *name.data); + return rv; + } + + xfmark_T mark = get_global_mark(*name.data); + pos_T pos = mark.fmark.mark; + bool allocated = false; + int bufnr; + char *filename; + + // Marks are from an open buffer it fnum is non zero + if (mark.fmark.fnum != 0) { + bufnr = mark.fmark.fnum; + filename = (char *)buflist_nr2name(bufnr, true, true); + allocated = true; + // Marks comes from shada + } else { + filename = (char *)mark.fname; + bufnr = 0; + } + + bool exists = filename != NULL; + Integer row; + Integer col; + + if (!exists || pos.lnum <= 0) { + if (allocated) { + xfree(filename); + allocated = false; + } + filename = ""; + bufnr = 0; + row = 0; + col = 0; + } else { + row = pos.lnum; + col = pos.col; + } + + ADD(rv, INTEGER_OBJ(row)); + ADD(rv, INTEGER_OBJ(col)); + ADD(rv, INTEGER_OBJ(bufnr)); + ADD(rv, STRING_OBJ(cstr_to_string(filename))); + + if (allocated) { + xfree(filename); + } + + return rv; +} + +/// Evaluates statusline string. +/// +/// @param str Statusline string (see 'statusline'). +/// @param opts Optional parameters. +/// - winid: (number) |window-ID| of the window to use as context for statusline. +/// - maxwidth: (number) Maximum width of statusline. +/// - fillchar: (string) Character to fill blank spaces in the statusline (see +/// 'fillchars'). +/// - highlights: (boolean) Return highlight information. +/// - use_tabline: (boolean) Evaluate tabline instead of statusline. When |TRUE|, {winid} +/// is ignored. +/// +/// @param[out] err Error details, if any. +/// @return Dictionary containing statusline information, with these keys: +/// - str: (string) Characters that will be displayed on the statusline. +/// - width: (number) Display width of the statusline. +/// - highlights: Array containing highlight information of the statusline. Only included when +/// the "highlights" key in {opts} is |TRUE|. Each element of the array is a +/// |Dictionary| with these keys: +/// - start: (number) Byte index (0-based) of first character that uses the highlight. +/// - group: (string) Name of highlight group. +Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *err) + FUNC_API_SINCE(8) FUNC_API_FAST +{ + Dictionary result = ARRAY_DICT_INIT; + + int maxwidth; + char fillchar = 0; + Window window = 0; + bool use_tabline = false; + bool highlights = false; + + if (HAS_KEY(opts->winid)) { + if (opts->winid.type != kObjectTypeInteger) { + api_set_error(err, kErrorTypeValidation, "winid must be an integer"); + return result; + } + + window = (Window)opts->winid.data.integer; + } + + if (HAS_KEY(opts->fillchar)) { + if (opts->fillchar.type != kObjectTypeString || opts->fillchar.data.string.size > 1) { + api_set_error(err, kErrorTypeValidation, "fillchar must be an ASCII character"); + return result; + } + + fillchar = opts->fillchar.data.string.data[0]; + } + + if (HAS_KEY(opts->highlights)) { + highlights = api_object_to_bool(opts->highlights, "highlights", false, err); + + if (ERROR_SET(err)) { + return result; + } + } + + if (HAS_KEY(opts->use_tabline)) { + use_tabline = api_object_to_bool(opts->use_tabline, "use_tabline", false, err); + + if (ERROR_SET(err)) { + return result; + } + } + + win_T *wp, *ewp; + + if (use_tabline) { + wp = NULL; + ewp = curwin; + fillchar = ' '; + } else { + wp = find_window_by_handle(window, err); + ewp = wp; + + if (fillchar == 0) { + int attr; + fillchar = (char)fillchar_status(&attr, wp); + } + } + + if (HAS_KEY(opts->maxwidth)) { + if (opts->maxwidth.type != kObjectTypeInteger) { + api_set_error(err, kErrorTypeValidation, "maxwidth must be an integer"); + return result; + } + + maxwidth = (int)opts->maxwidth.data.integer; + } else { + maxwidth = use_tabline ? Columns : wp->w_width; + } + + char buf[MAXPATHL]; + stl_hlrec_t *hltab; + stl_hlrec_t **hltab_ptr = highlights ? &hltab : NULL; + + // Temporarily reset 'cursorbind' to prevent side effects from moving the cursor away and back. + int p_crb_save = ewp->w_p_crb; + ewp->w_p_crb = false; + + int width = build_stl_str_hl( + ewp, + (char_u *)buf, + sizeof(buf), + (char_u *)str.data, + false, + (char_u)fillchar, + maxwidth, + hltab_ptr, + NULL); + + PUT(result, "width", INTEGER_OBJ(width)); + + // Restore original value of 'cursorbind' + ewp->w_p_crb = p_crb_save; + + if (highlights) { + Array hl_values = ARRAY_DICT_INIT; + const char *grpname; + char user_group[6]; + + // If first character doesn't have a defined highlight, + // add the default highlight at the beginning of the highlight list + if (hltab->start == NULL || ((char *)hltab->start - buf) != 0) { + Dictionary hl_info = ARRAY_DICT_INIT; + grpname = get_default_stl_hl(wp); + + PUT(hl_info, "start", INTEGER_OBJ(0)); + PUT(hl_info, "group", CSTR_TO_OBJ(grpname)); + + ADD(hl_values, DICTIONARY_OBJ(hl_info)); + } + + for (stl_hlrec_t *sp = hltab; sp->start != NULL; sp++) { + Dictionary hl_info = ARRAY_DICT_INIT; + + PUT(hl_info, "start", INTEGER_OBJ((char *)sp->start - buf)); + + if (sp->userhl == 0) { + grpname = get_default_stl_hl(wp); + } else if (sp->userhl < 0) { + grpname = (char *)syn_id2name(-sp->userhl); + } else { + snprintf(user_group, sizeof(user_group), "User%d", sp->userhl); + grpname = user_group; + } + + PUT(hl_info, "group", CSTR_TO_OBJ(grpname)); + + ADD(hl_values, DICTIONARY_OBJ(hl_info)); + } + + PUT(result, "highlights", ARRAY_OBJ(hl_values)); + } + + PUT(result, "str", CSTR_TO_OBJ((char *)buf)); + + return result; +} diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c new file mode 100644 index 0000000000..ceb7f71423 --- /dev/null +++ b/src/nvim/api/win_config.c @@ -0,0 +1,639 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +#include <assert.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" +#include "nvim/api/win_config.h" +#include "nvim/ascii.h" +#include "nvim/option.h" +#include "nvim/screen.h" +#include "nvim/strings.h" +#include "nvim/syntax.h" +#include "nvim/ui.h" +#include "nvim/window.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "api/win_config.c.generated.h" +#endif + + +/// Open a new window. +/// +/// Currently this is used to open floating and external windows. +/// Floats are windows that are drawn above the split layout, at some anchor +/// position in some other window. Floats can be drawn internally or by external +/// GUI with the |ui-multigrid| extension. External windows are only supported +/// with multigrid GUIs, and are displayed as separate top-level windows. +/// +/// For a general overview of floats, see |api-floatwin|. +/// +/// Exactly one of `external` and `relative` must be specified. The `width` and +/// `height` of the new window must be specified. +/// +/// With relative=editor (row=0,col=0) refers to the top-left corner of the +/// screen-grid and (row=Lines-1,col=Columns-1) refers to the bottom-right +/// corner. Fractional values are allowed, but the builtin implementation +/// (used by non-multigrid UIs) will always round down to nearest integer. +/// +/// Out-of-bounds values, and configurations that make the float not fit inside +/// the main editor, are allowed. The builtin implementation truncates values +/// so floats are fully within the main screen grid. External GUIs +/// could let floats hover outside of the main window like a tooltip, but +/// this should not be used to specify arbitrary WM screen positions. +/// +/// Example (Lua): window-relative float +/// <pre> +/// vim.api.nvim_open_win(0, false, +/// {relative='win', row=3, col=3, width=12, height=3}) +/// </pre> +/// +/// Example (Lua): buffer-relative float (travels as buffer is scrolled) +/// <pre> +/// vim.api.nvim_open_win(0, false, +/// {relative='win', width=12, height=3, bufpos={100,10}}) +/// </pre> +/// +/// @param buffer Buffer to display, or 0 for current buffer +/// @param enter Enter the window (make it the current window) +/// @param config Map defining the window configuration. Keys: +/// - relative: Sets the window layout to "floating", placed at (row,col) +/// coordinates relative to: +/// - "editor" The global editor grid +/// - "win" Window given by the `win` field, or current window. +/// - "cursor" Cursor position in current window. +/// - win: |window-ID| for relative="win". +/// - anchor: Decides which corner of the float to place at (row,col): +/// - "NW" northwest (default) +/// - "NE" northeast +/// - "SW" southwest +/// - "SE" southeast +/// - width: Window width (in character cells). Minimum of 1. +/// - height: Window height (in character cells). Minimum of 1. +/// - bufpos: Places float relative to buffer text (only when +/// relative="win"). Takes a tuple of zero-indexed [line, column]. +/// `row` and `col` if given are applied relative to this +/// position, else they default to: +/// - `row=1` and `col=0` if `anchor` is "NW" or "NE" +/// - `row=0` and `col=0` if `anchor` is "SW" or "SE" +/// (thus like a tooltip near the buffer text). +/// - row: Row position in units of "screen cell height", may be fractional. +/// - col: Column position in units of "screen cell width", may be +/// fractional. +/// - focusable: Enable focus by user actions (wincmds, mouse events). +/// Defaults to true. Non-focusable windows can be entered by +/// |nvim_set_current_win()|. +/// - external: GUI should display the window as an external +/// top-level window. Currently accepts no other positioning +/// configuration together with this. +/// - zindex: Stacking order. floats with higher `zindex` go on top on +/// floats with lower indices. Must be larger than zero. The +/// following screen elements have hard-coded z-indices: +/// - 100: insert completion popupmenu +/// - 200: message scrollback +/// - 250: cmdline completion popupmenu (when wildoptions+=pum) +/// The default value for floats are 50. In general, values below 100 are +/// recommended, unless there is a good reason to overshadow builtin +/// elements. +/// - style: Configure the appearance of the window. Currently only takes +/// one non-empty value: +/// - "minimal" Nvim will display the window with many UI options +/// disabled. This is useful when displaying a temporary +/// float where the text should not be edited. Disables +/// 'number', 'relativenumber', 'cursorline', 'cursorcolumn', +/// 'foldcolumn', 'spell' and 'list' options. 'signcolumn' +/// is changed to `auto` and 'colorcolumn' is cleared. The +/// end-of-buffer region is hidden by setting `eob` flag of +/// 'fillchars' to a space char, and clearing the +/// |EndOfBuffer| region in 'winhighlight'. +/// - border: Style of (optional) window border. This can either be a string +/// or an array. The string values are +/// - "none": No border (default). +/// - "single": A single line box. +/// - "double": A double line box. +/// - "rounded": Like "single", but with rounded corners ("╭" etc.). +/// - "solid": Adds padding by a single whitespace cell. +/// - "shadow": A drop shadow effect by blending with the background. +/// - If it is an array, it should have a length of eight or any divisor of +/// eight. The array will specifify the eight chars building up the border +/// in a clockwise fashion starting with the top-left corner. As an +/// example, the double box style could be specified as +/// [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ]. +/// If the number of chars are less than eight, they will be repeated. Thus +/// an ASCII border could be specified as +/// [ "/", "-", "\\", "|" ], +/// or all chars the same as +/// [ "x" ]. +/// An empty string can be used to turn off a specific border, for instance, +/// [ "", "", "", ">", "", "", "", "<" ] +/// will only make vertical borders but not horizontal ones. +/// By default, `FloatBorder` highlight is used, which links to `VertSplit` +/// when not defined. It could also be specified by character: +/// [ {"+", "MyCorner"}, {"x", "MyBorder"} ]. +/// - noautocmd: If true then no buffer-related autocommand events such as +/// |BufEnter|, |BufLeave| or |BufWinEnter| may fire from +/// calling this function. +/// +/// @param[out] err Error details, if any +/// +/// @return Window handle, or 0 on error +Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, Error *err) + FUNC_API_SINCE(6) + FUNC_API_CHECK_TEXTLOCK +{ + FloatConfig fconfig = FLOAT_CONFIG_INIT; + if (!parse_float_config(config, &fconfig, false, true, err)) { + return 0; + } + win_T *wp = win_new_float(NULL, fconfig, err); + if (!wp) { + return 0; + } + if (enter) { + win_enter(wp, false); + } + // autocmds in win_enter or win_set_buf below may close the window + if (win_valid(wp) && buffer > 0) { + win_set_buf(wp->handle, buffer, fconfig.noautocmd, err); + } + if (!win_valid(wp)) { + api_set_error(err, kErrorTypeException, "Window was closed immediately"); + return 0; + } + + if (fconfig.style == kWinStyleMinimal) { + win_set_minimal_style(wp); + didset_window_options(wp); + } + return wp->handle; +} + +/// Configures window layout. Currently only for floating and external windows +/// (including changing a split window to those layouts). +/// +/// When reconfiguring a floating window, absent option keys will not be +/// changed. `row`/`col` and `relative` must be reconfigured together. +/// +/// @see |nvim_open_win()| +/// +/// @param window Window handle, or 0 for current window +/// @param config Map defining the window configuration, +/// see |nvim_open_win()| +/// @param[out] err Error details, if any +void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err) + FUNC_API_SINCE(6) +{ + win_T *win = find_window_by_handle(window, err); + if (!win) { + return; + } + bool new_float = !win->w_floating; + // reuse old values, if not overridden + FloatConfig fconfig = new_float ? FLOAT_CONFIG_INIT : win->w_float_config; + + if (!parse_float_config(config, &fconfig, !new_float, false, err)) { + return; + } + if (new_float) { + if (!win_new_float(win, fconfig, err)) { + return; + } + redraw_later(win, NOT_VALID); + } else { + win_config_float(win, fconfig); + win->w_pos_changed = true; + } + if (fconfig.style == kWinStyleMinimal) { + win_set_minimal_style(win); + didset_window_options(win); + } +} + +/// Gets window configuration. +/// +/// The returned value may be given to |nvim_open_win()|. +/// +/// `relative` is empty for normal windows. +/// +/// @param window Window handle, or 0 for current window +/// @param[out] err Error details, if any +/// @return Map defining the window configuration, see |nvim_open_win()| +Dictionary nvim_win_get_config(Window window, Error *err) + FUNC_API_SINCE(6) +{ + Dictionary rv = ARRAY_DICT_INIT; + + win_T *wp = find_window_by_handle(window, err); + if (!wp) { + return rv; + } + + FloatConfig *config = &wp->w_float_config; + + PUT(rv, "focusable", BOOLEAN_OBJ(config->focusable)); + PUT(rv, "external", BOOLEAN_OBJ(config->external)); + + if (wp->w_floating) { + PUT(rv, "width", INTEGER_OBJ(config->width)); + PUT(rv, "height", INTEGER_OBJ(config->height)); + if (!config->external) { + if (config->relative == kFloatRelativeWindow) { + PUT(rv, "win", INTEGER_OBJ(config->window)); + if (config->bufpos.lnum >= 0) { + Array pos = ARRAY_DICT_INIT; + ADD(pos, INTEGER_OBJ(config->bufpos.lnum)); + ADD(pos, INTEGER_OBJ(config->bufpos.col)); + PUT(rv, "bufpos", ARRAY_OBJ(pos)); + } + } + PUT(rv, "anchor", STRING_OBJ(cstr_to_string(float_anchor_str[config->anchor]))); + PUT(rv, "row", FLOAT_OBJ(config->row)); + PUT(rv, "col", FLOAT_OBJ(config->col)); + PUT(rv, "zindex", INTEGER_OBJ(config->zindex)); + } + if (config->border) { + Array border = ARRAY_DICT_INIT; + for (size_t i = 0; i < 8; i++) { + Array tuple = ARRAY_DICT_INIT; + + String s = cstrn_to_string((const char *)config->border_chars[i], sizeof(schar_T)); + + int hi_id = config->border_hl_ids[i]; + char_u *hi_name = syn_id2name(hi_id); + if (hi_name[0]) { + ADD(tuple, STRING_OBJ(s)); + ADD(tuple, STRING_OBJ(cstr_to_string((const char *)hi_name))); + ADD(border, ARRAY_OBJ(tuple)); + } else { + ADD(border, STRING_OBJ(s)); + } + } + PUT(rv, "border", ARRAY_OBJ(border)); + } + } + + const char *rel = (wp->w_floating && !config->external + ? float_relative_str[config->relative] : ""); + PUT(rv, "relative", STRING_OBJ(cstr_to_string(rel))); + + return rv; +} + +static bool parse_float_anchor(String anchor, FloatAnchor *out) +{ + if (anchor.size == 0) { + *out = (FloatAnchor)0; + } + char *str = anchor.data; + if (striequal(str, "NW")) { + *out = 0; // NW is the default + } else if (striequal(str, "NE")) { + *out = kFloatAnchorEast; + } else if (striequal(str, "SW")) { + *out = kFloatAnchorSouth; + } else if (striequal(str, "SE")) { + *out = kFloatAnchorSouth | kFloatAnchorEast; + } else { + return false; + } + return true; +} + +static bool parse_float_relative(String relative, FloatRelative *out) +{ + char *str = relative.data; + if (striequal(str, "editor")) { + *out = kFloatRelativeEditor; + } else if (striequal(str, "win")) { + *out = kFloatRelativeWindow; + } else if (striequal(str, "cursor")) { + *out = kFloatRelativeCursor; + } else { + return false; + } + return true; +} + +static bool parse_float_bufpos(Array bufpos, lpos_T *out) +{ + if (bufpos.size != 2 + || bufpos.items[0].type != kObjectTypeInteger + || bufpos.items[1].type != kObjectTypeInteger) { + return false; + } + out->lnum = bufpos.items[0].data.integer; + out->col = (colnr_T)bufpos.items[1].data.integer; + return true; +} + +static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) +{ + struct { + const char *name; + schar_T chars[8]; + bool shadow_color; + } defaults[] = { + { "double", { "╔", "═", "╗", "║", "╝", "═", "╚", "║" }, false }, + { "single", { "┌", "─", "┐", "│", "┘", "─", "└", "│" }, false }, + { "shadow", { "", "", " ", " ", " ", " ", " ", "" }, true }, + { "rounded", { "╭", "─", "╮", "│", "╯", "─", "╰", "│" }, false }, + { "solid", { " ", " ", " ", " ", " ", " ", " ", " " }, false }, + { NULL, { { NUL } }, false }, + }; + + schar_T *chars = fconfig->border_chars; + int *hl_ids = fconfig->border_hl_ids; + + fconfig->border = true; + + if (style.type == kObjectTypeArray) { + Array arr = style.data.array; + size_t size = arr.size; + if (!size || size > 8 || (size & (size-1))) { + api_set_error(err, kErrorTypeValidation, + "invalid number of border chars"); + return; + } + for (size_t i = 0; i < size; i++) { + Object iytem = arr.items[i]; + String string; + int hl_id = 0; + if (iytem.type == kObjectTypeArray) { + Array iarr = iytem.data.array; + if (!iarr.size || iarr.size > 2) { + api_set_error(err, kErrorTypeValidation, "invalid border char"); + return; + } + if (iarr.items[0].type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, "invalid border char"); + return; + } + string = iarr.items[0].data.string; + if (iarr.size == 2) { + hl_id = object_to_hl_id(iarr.items[1], "border char highlight", err); + if (ERROR_SET(err)) { + return; + } + } + } else if (iytem.type == kObjectTypeString) { + string = iytem.data.string; + } else { + api_set_error(err, kErrorTypeValidation, "invalid border char"); + return; + } + if (string.size + && mb_string2cells_len((char_u *)string.data, string.size) > 1) { + api_set_error(err, kErrorTypeValidation, + "border chars must be one cell"); + return; + } + size_t len = MIN(string.size, sizeof(*chars)-1); + if (len) { + memcpy(chars[i], string.data, len); + } + chars[i][len] = NUL; + hl_ids[i] = hl_id; + } + while (size < 8) { + memcpy(chars+size, chars, sizeof(*chars) * size); + memcpy(hl_ids+size, hl_ids, sizeof(*hl_ids) * size); + size <<= 1; + } + if ((chars[7][0] && chars[1][0] && !chars[0][0]) + || (chars[1][0] && chars[3][0] && !chars[2][0]) + || (chars[3][0] && chars[5][0] && !chars[4][0]) + || (chars[5][0] && chars[7][0] && !chars[6][0])) { + api_set_error(err, kErrorTypeValidation, + "corner between used edges must be specified"); + } + } else if (style.type == kObjectTypeString) { + String str = style.data.string; + if (str.size == 0 || strequal(str.data, "none")) { + fconfig->border = false; + return; + } + for (size_t i = 0; defaults[i].name; i++) { + if (strequal(str.data, defaults[i].name)) { + memcpy(chars, defaults[i].chars, sizeof(defaults[i].chars)); + memset(hl_ids, 0, 8 * sizeof(*hl_ids)); + if (defaults[i].shadow_color) { + int hl_blend = SYN_GROUP_STATIC("FloatShadow"); + int hl_through = SYN_GROUP_STATIC("FloatShadowThrough"); + hl_ids[2] = hl_through; + hl_ids[3] = hl_blend; + hl_ids[4] = hl_blend; + hl_ids[5] = hl_blend; + hl_ids[6] = hl_through; + } + return; + } + } + api_set_error(err, kErrorTypeValidation, + "invalid border style \"%s\"", str.data); + } +} + +static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig, bool reconf, + bool new_win, Error *err) +{ + bool has_relative = false, relative_is_win = false; + if (config->relative.type == kObjectTypeString) { + // ignore empty string, to match nvim_win_get_config + if (config->relative.data.string.size > 0) { + if (!parse_float_relative(config->relative.data.string, &fconfig->relative)) { + api_set_error(err, kErrorTypeValidation, "Invalid value of 'relative' key"); + return false; + } + + if (!(HAS_KEY(config->row) && HAS_KEY(config->col)) && !HAS_KEY(config->bufpos)) { + api_set_error(err, kErrorTypeValidation, + "'relative' requires 'row'/'col' or 'bufpos'"); + return false; + } + + has_relative = true; + fconfig->external = false; + if (fconfig->relative == kFloatRelativeWindow) { + relative_is_win = true; + fconfig->bufpos.lnum = -1; + } + } + } else if (HAS_KEY(config->relative)) { + api_set_error(err, kErrorTypeValidation, "'relative' key must be String"); + return false; + } + + if (config->anchor.type == kObjectTypeString) { + if (!parse_float_anchor(config->anchor.data.string, &fconfig->anchor)) { + api_set_error(err, kErrorTypeValidation, "Invalid value of 'anchor' key"); + return false; + } + } else if (HAS_KEY(config->anchor)) { + api_set_error(err, kErrorTypeValidation, "'anchor' key must be String"); + return false; + } + + if (HAS_KEY(config->row)) { + if (!has_relative) { + api_set_error(err, kErrorTypeValidation, "non-float cannot have 'row'"); + return false; + } else if (config->row.type == kObjectTypeInteger) { + fconfig->row = (double)config->row.data.integer; + } else if (config->row.type == kObjectTypeFloat) { + fconfig->row = config->row.data.floating; + } else { + api_set_error(err, kErrorTypeValidation, + "'row' key must be Integer or Float"); + return false; + } + } + + if (HAS_KEY(config->col)) { + if (!has_relative) { + api_set_error(err, kErrorTypeValidation, "non-float cannot have 'col'"); + return false; + } else if (config->col.type == kObjectTypeInteger) { + fconfig->col = (double)config->col.data.integer; + } else if (config->col.type == kObjectTypeFloat) { + fconfig->col = config->col.data.floating; + } else { + api_set_error(err, kErrorTypeValidation, + "'col' key must be Integer or Float"); + return false; + } + } + + if (HAS_KEY(config->bufpos)) { + if (!has_relative) { + api_set_error(err, kErrorTypeValidation, "non-float cannot have 'bufpos'"); + return false; + } else if (config->bufpos.type == kObjectTypeArray) { + if (!parse_float_bufpos(config->bufpos.data.array, &fconfig->bufpos)) { + api_set_error(err, kErrorTypeValidation, "Invalid value of 'bufpos' key"); + return false; + } + + if (!HAS_KEY(config->row)) { + fconfig->row = (fconfig->anchor & kFloatAnchorSouth) ? 0 : 1; + } + if (!HAS_KEY(config->col)) { + fconfig->col = 0; + } + } else { + api_set_error(err, kErrorTypeValidation, "'bufpos' key must be Array"); + return false; + } + } + + if (config->width.type == kObjectTypeInteger && config->width.data.integer > 0) { + fconfig->width = (int)config->width.data.integer; + } else if (HAS_KEY(config->width)) { + api_set_error(err, kErrorTypeValidation, "'width' key must be a positive Integer"); + return false; + } else if (!reconf) { + api_set_error(err, kErrorTypeValidation, "Must specify 'width'"); + return false; + } + + if (config->height.type == kObjectTypeInteger && config->height.data.integer > 0) { + fconfig->height = (int)config->height.data.integer; + } else if (HAS_KEY(config->height)) { + api_set_error(err, kErrorTypeValidation, "'height' key must be a positive Integer"); + return false; + } else if (!reconf) { + api_set_error(err, kErrorTypeValidation, "Must specify 'height'"); + return false; + } + + if (relative_is_win) { + fconfig->window = curwin->handle; + if (config->win.type == kObjectTypeInteger || config->win.type == kObjectTypeWindow) { + if (config->win.data.integer > 0) { + fconfig->window = (Window)config->win.data.integer; + } + } else if (HAS_KEY(config->win)) { + api_set_error(err, kErrorTypeValidation, "'win' key must be Integer or Window"); + return false; + } + } else { + if (HAS_KEY(config->win)) { + api_set_error(err, kErrorTypeValidation, "'win' key is only valid with relative='win'"); + return false; + } + } + + if (HAS_KEY(config->external)) { + fconfig->external = api_object_to_bool(config->external, "'external' key", false, err); + if (ERROR_SET(err)) { + return false; + } + if (has_relative && fconfig->external) { + api_set_error(err, kErrorTypeValidation, + "Only one of 'relative' and 'external' must be used"); + return false; + } + if (fconfig->external && !ui_has(kUIMultigrid)) { + api_set_error(err, kErrorTypeValidation, + "UI doesn't support external windows"); + return false; + } + } + + if (!reconf && (!has_relative && !fconfig->external)) { + api_set_error(err, kErrorTypeValidation, + "One of 'relative' and 'external' must be used"); + return false; + } + + + if (HAS_KEY(config->focusable)) { + fconfig->focusable = api_object_to_bool(config->focusable, "'focusable' key", false, err); + if (ERROR_SET(err)) { + return false; + } + } + + if (config->zindex.type == kObjectTypeInteger && config->zindex.data.integer > 0) { + fconfig->zindex = (int)config->zindex.data.integer; + } else if (HAS_KEY(config->zindex)) { + api_set_error(err, kErrorTypeValidation, "'zindex' key must be a positive Integer"); + return false; + } + + if (HAS_KEY(config->border)) { + parse_border_style(config->border, fconfig, err); + if (ERROR_SET(err)) { + return false; + } + } + + if (config->style.type == kObjectTypeString) { + if (config->style.data.string.data[0] == NUL) { + fconfig->style = kWinStyleUnused; + } else if (striequal(config->style.data.string.data, "minimal")) { + fconfig->style = kWinStyleMinimal; + } else { + api_set_error(err, kErrorTypeValidation, "Invalid value of 'style' key"); + } + } else if (HAS_KEY(config->style)) { + api_set_error(err, kErrorTypeValidation, "'style' key must be String"); + return false; + } + + if (HAS_KEY(config->noautocmd)) { + if (!new_win) { + api_set_error(err, kErrorTypeValidation, "Invalid key: 'noautocmd'"); + return false; + } + fconfig->noautocmd = api_object_to_bool(config->noautocmd, "'noautocmd' key", false, err); + if (ERROR_SET(err)) { + return false; + } + } + + return true; +} diff --git a/src/nvim/api/win_config.h b/src/nvim/api/win_config.h new file mode 100644 index 0000000000..9271c35f23 --- /dev/null +++ b/src/nvim/api/win_config.h @@ -0,0 +1,11 @@ +#ifndef NVIM_API_WIN_CONFIG_H +#define NVIM_API_WIN_CONFIG_H + +#include <stdint.h> + +#include "nvim/api/private/defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "api/win_config.h.generated.h" +#endif +#endif // NVIM_API_WIN_CONFIG_H diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 99ba297111..6e68c057dc 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -373,117 +373,6 @@ Boolean nvim_win_is_valid(Window window) } -/// Configures window layout. Currently only for floating and external windows -/// (including changing a split window to those layouts). -/// -/// When reconfiguring a floating window, absent option keys will not be -/// changed. `row`/`col` and `relative` must be reconfigured together. -/// -/// @see |nvim_open_win()| -/// -/// @param window Window handle, or 0 for current window -/// @param config Map defining the window configuration, -/// see |nvim_open_win()| -/// @param[out] err Error details, if any -void nvim_win_set_config(Window window, Dictionary config, Error *err) - FUNC_API_SINCE(6) -{ - win_T *win = find_window_by_handle(window, err); - if (!win) { - return; - } - bool new_float = !win->w_floating; - // reuse old values, if not overridden - FloatConfig fconfig = new_float ? FLOAT_CONFIG_INIT : win->w_float_config; - - if (!parse_float_config(config, &fconfig, !new_float, false, err)) { - return; - } - if (new_float) { - if (!win_new_float(win, fconfig, err)) { - return; - } - redraw_later(win, NOT_VALID); - } else { - win_config_float(win, fconfig); - win->w_pos_changed = true; - } - if (fconfig.style == kWinStyleMinimal) { - win_set_minimal_style(win); - didset_window_options(win); - } -} - -/// Gets window configuration. -/// -/// The returned value may be given to |nvim_open_win()|. -/// -/// `relative` is empty for normal windows. -/// -/// @param window Window handle, or 0 for current window -/// @param[out] err Error details, if any -/// @return Map defining the window configuration, see |nvim_open_win()| -Dictionary nvim_win_get_config(Window window, Error *err) - FUNC_API_SINCE(6) -{ - Dictionary rv = ARRAY_DICT_INIT; - - win_T *wp = find_window_by_handle(window, err); - if (!wp) { - return rv; - } - - FloatConfig *config = &wp->w_float_config; - - PUT(rv, "focusable", BOOLEAN_OBJ(config->focusable)); - PUT(rv, "external", BOOLEAN_OBJ(config->external)); - - if (wp->w_floating) { - PUT(rv, "width", INTEGER_OBJ(config->width)); - PUT(rv, "height", INTEGER_OBJ(config->height)); - if (!config->external) { - if (config->relative == kFloatRelativeWindow) { - PUT(rv, "win", INTEGER_OBJ(config->window)); - if (config->bufpos.lnum >= 0) { - Array pos = ARRAY_DICT_INIT; - ADD(pos, INTEGER_OBJ(config->bufpos.lnum)); - ADD(pos, INTEGER_OBJ(config->bufpos.col)); - PUT(rv, "bufpos", ARRAY_OBJ(pos)); - } - } - PUT(rv, "anchor", STRING_OBJ(cstr_to_string(float_anchor_str[config->anchor]))); - PUT(rv, "row", FLOAT_OBJ(config->row)); - PUT(rv, "col", FLOAT_OBJ(config->col)); - PUT(rv, "zindex", INTEGER_OBJ(config->zindex)); - } - if (config->border) { - Array border = ARRAY_DICT_INIT; - for (size_t i = 0; i < 8; i++) { - Array tuple = ARRAY_DICT_INIT; - - String s = cstrn_to_string((const char *)config->border_chars[i], sizeof(schar_T)); - - int hi_id = config->border_hl_ids[i]; - char_u *hi_name = syn_id2name(hi_id); - if (hi_name[0]) { - ADD(tuple, STRING_OBJ(s)); - ADD(tuple, STRING_OBJ(cstr_to_string((const char *)hi_name))); - ADD(border, ARRAY_OBJ(tuple)); - } else { - ADD(border, STRING_OBJ(s)); - } - } - PUT(rv, "border", ARRAY_OBJ(border)); - } - } - - const char *rel = (wp->w_floating && !config->external - ? float_relative_str[config->relative] : ""); - PUT(rv, "relative", STRING_OBJ(cstr_to_string(rel))); - - return rv; -} - /// Closes the window and hide the buffer it contains (like |:hide| with a /// |window-ID|). /// |