aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/api/private/helpers.c
diff options
context:
space:
mode:
authorBjörn Linse <bjorn.linse@gmail.com>2021-03-22 23:27:59 +0100
committerGitHub <noreply@github.com>2021-03-22 23:27:59 +0100
commit1df1098b0c54258a41c4f97c9d3131d061a5b206 (patch)
tree315d116677afd7dfbfbc6858949e66e574c81aee /src/nvim/api/private/helpers.c
parent6fef28da58a217a45d2d6e5b68ecfddd1eb84c29 (diff)
parent243820ebd0d9df7664311c8bf79d879bf23eb742 (diff)
downloadrneovim-1df1098b0c54258a41c4f97c9d3131d061a5b206.tar.gz
rneovim-1df1098b0c54258a41c4f97c9d3131d061a5b206.tar.bz2
rneovim-1df1098b0c54258a41c4f97c9d3131d061a5b206.zip
Merge pull request #13998 from bfredl/border
floats: add MS-DOS mode (borders)
Diffstat (limited to 'src/nvim/api/private/helpers.c')
-rw-r--r--src/nvim/api/private/helpers.c352
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;
+}