diff options
Diffstat (limited to 'src/nvim/api/private/helpers.c')
-rw-r--r-- | src/nvim/api/private/helpers.c | 352 |
1 files changed, 352 insertions, 0 deletions
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 7cee569989..d2b787a6f5 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1645,6 +1645,20 @@ bool api_object_to_bool(Object obj, const char *what, } } +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; + } else if (obj.type == kObjectTypeInteger) { + return (int)obj.data.integer; + } else { + api_set_error(err, kErrorTypeValidation, + "%s is not a valid highlight", what); + return 0; + } +} + HlMessage parse_hl_msg(Array chunks, Error *err) { HlMessage hl_msg = KV_INITIAL_VALUE; @@ -1720,3 +1734,341 @@ DecorProvider *get_provider(NS ns_id, bool force) return item; } + +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]; + } defaults[] = { + { "double", { "╔", "═", "╗", "║", "╝", "═", "╚", "║" } }, + { "single", { "┌", "─", "┐", "│", "┘", "─", "└", "│" } }, + { NULL, { { NUL } } }, + }; + + 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 = NULL_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"); + } + size_t len = MIN(string.size, sizeof(*chars)-1); + 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; + } + } 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)); + return; + } + } + api_set_error(err, kErrorTypeValidation, + "invalid border style \"%s\"", str.data); + } +} + +bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, + 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")) { + if (val.type == kObjectTypeInteger) { + fconfig->external = val.data.integer; + } else if (val.type == kObjectTypeBoolean) { + fconfig->external = val.data.boolean; + } else { + api_set_error(err, kErrorTypeValidation, + "'external' key must be Boolean"); + return false; + } + has_external = fconfig->external; + } else if (!strcmp(key, "focusable")) { + if (val.type == kObjectTypeInteger) { + fconfig->focusable = val.data.integer; + } else if (val.type == kObjectTypeBoolean) { + fconfig->focusable = val.data.boolean; + } else { + api_set_error(err, kErrorTypeValidation, + "'focusable' key must be Boolean"); + 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 { + 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; + } + } + + 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; + } + return true; +} |