diff --git a/src/clint.py b/src/clint.py index e7d76366b0..4b7bf002e6 100755 --- a/src/clint.py +++ b/src/clint.py @@ -2544,6 +2544,7 @@ def CheckSpacing(filename, clean_lines, linenum, nesting_state, error): r'(?builtin, "builtin", false, err); + if (ERROR_SET(err)) { + return (Dictionary)ARRAY_DICT_INIT; } if (global) { @@ -1485,7 +1475,7 @@ 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) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -1498,211 +1488,174 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer return 0; } - bool ephemeral = false; uint64_t id = 0; - int line2 = -1; - Decoration decor = DECORATION_INIT; - colnr_T col2 = -1; + 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; + } - bool right_gravity = true; - bool end_right_gravity = false; - bool end_gravity_set = false; + int line2 = -1; + 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; + } + + Decoration decor = DECORATION_INIT; + + 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; + } + } + + 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; + } + + 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; + } + +#define OPTION_TO_BOOL(target, name, val) \ + target = api_object_to_bool(opts-> name, #name, val, err); \ + if (ERROR_SET(err)) { \ + goto error; \ + } + + OPTION_TO_BOOL(decor.virt_text_hide, virt_text_hide, false); + OPTION_TO_BOOL(decor.hl_eol, hl_eol, false); + + 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; - 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"); + 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; } - - 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; - } - - 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; - } - - 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); + 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("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; - } - - 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("virt_lines", k.data)) { - if (v->type != kObjectTypeArray) { - api_set_error(err, kErrorTypeValidation, - "virt_lines is not an Array"); - goto error; - } - Array a = v->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; - } - 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("virt_lines_above", k.data)) { - virt_lines_above = api_object_to_bool(*v, "virt_lines_above", false, err); - if (ERROR_SET(err)) { - goto error; - } - } else if (strequal("virt_lines_leftcol", k.data)) { - virt_lines_leftcol = api_object_to_bool(*v, "virt_lines_leftcol", 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"); - goto error; - } - } else if (strequal("ephemeral", k.data)) { - ephemeral = api_object_to_bool(*v, "ephemeral", false, err); - 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; - } - - 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); - goto error; } + } else if (HAS_KEY(opts->virt_lines)) { + api_set_error(err, kErrorTypeValidation, "virt_lines is not an Array"); + goto error; } + 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; @@ -1717,15 +1670,6 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer 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"); - } - if (col2 >= 0) { if (line2 >= 0 && line2 < buf->b_ml.ml_line_count) { len = ephemeral ? MAXCOL : STRLEN(ml_get_buf(buf, (linenr_T)line2 + 1, false)); @@ -1744,15 +1688,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) { diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua new file mode 100644 index 0000000000..76ce9e15ea --- /dev/null +++ b/src/nvim/api/keysets.lua @@ -0,0 +1,52 @@ +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"; + }; +} + diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h index f0d48bf145..8346e01558 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -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/helpers.c b/src/nvim/api/private/helpers.c index 193f1dd572..24fac0a916 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -6,6 +6,7 @@ #include #include #include +#include #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" @@ -814,7 +815,7 @@ 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 @@ -833,10 +834,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; @@ -947,95 +959,6 @@ fail_and_free: 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 - // 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. /// @@ -1879,213 +1802,227 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) } } -bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, bool new_win, +bool parse_float_config(Dict(float_config) *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; + 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; + } - 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 { + if (!(HAS_KEY(config->row) && HAS_KEY(config->col)) && !HAS_KEY(config->bufpos)) { api_set_error(err, kErrorTypeValidation, - "'row' key must be Integer or Float"); + "'relative' requires 'row'/'col' or 'bufpos'"); 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; + + 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, - "Invalid key '%s'", key); + "'row' key must be Integer or Float"); return false; } } - if (has_window && !(has_relative - && fconfig->relative == kFloatRelativeWindow)) { - api_set_error(err, kErrorTypeValidation, - "'win' key is only valid with relative='win'"); + 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 ((has_relative && fconfig->relative == kFloatRelativeWindow) - && (!has_window || fconfig->window == 0)) { + 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 (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 (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; } - if (!has_col) { - fconfig->col = 0; - has_col = true; + } else { + if (HAS_KEY(config->win)) { + api_set_error(err, kErrorTypeValidation, "'win' key is only valid with relative='win'"); + return false; } } - 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) { + 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; - } else if (has_relative) { - fconfig->external = false; } - if (!reconf && !(has_height && has_width)) { - api_set_error(err, kErrorTypeValidation, - "Must specify 'width' and 'height'"); + + 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 (fconfig->external && !ui_has(kUIMultigrid)) { - api_set_error(err, kErrorTypeValidation, - "UI doesn't support external windows"); + 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_relative != has_row || has_row != has_col) { - api_set_error(err, kErrorTypeValidation, - "'relative' requires 'row'/'col' or 'bufpos'"); - 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; } + +bool api_dict_to_keydict(void *rv, field_hash hashy, Dictionary dict, Error *err) +{ + 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; + } + + *field = dict.items[i].value; + } + + return true; +} + +void api_free_keydict(void *dict, KeySetLink *table) +{ + for (size_t i = 0; table[i].str; i++) { + api_free_object(*(Object *)((char *)dict + table[i].ptr_off)); + } +} + diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index ecce6afa26..2cdd80bffe 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -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 })) @@ -138,6 +141,9 @@ typedef struct { } while (0) #ifdef INCLUDE_GENERATED_DECLARATIONS +# include "keysets.h.generated.h" # include "api/private/helpers.h.generated.h" #endif + + #endif // NVIM_API_PRIVATE_HELPERS_H diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index e2d7c03307..f80d605d9a 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1424,7 +1424,7 @@ void nvim_chan_send(Integer chan, String data, Error *err) /// @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) +Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, Error *err) FUNC_API_SINCE(6) FUNC_API_CHECK_TEXTLOCK { @@ -1761,24 +1761,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; @@ -1888,7 +1879,7 @@ ArrayOf(Dictionary) nvim_get_keymap(String mode) /// as keys excluding || 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); @@ -1914,7 +1905,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); diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 99ba297111..95eae1af2d 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -385,7 +385,7 @@ Boolean nvim_win_is_valid(Window 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) +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); diff --git a/src/nvim/generators/c_grammar.lua b/src/nvim/generators/c_grammar.lua index c9ab0cf709..a5f76e1c6a 100644 --- a/src/nvim/generators/c_grammar.lua +++ b/src/nvim/generators/c_grammar.lua @@ -17,7 +17,7 @@ local fill = ws ^ 0 local c_comment = P('//') * (not_nl ^ 0) local c_preproc = P('#') * (not_nl ^ 0) local typed_container = - (P('ArrayOf(') + P('DictionaryOf(')) * ((any - P(')')) ^ 1) * P(')') + (P('ArrayOf(') + P('DictionaryOf(') + P('Dict(')) * ((any - P(')')) ^ 1) * P(')') local c_id = ( typed_container + (letter * (alpha ^ 0)) diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index 99d80cdebc..6ed2e5dab8 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -152,6 +152,8 @@ for _,f in ipairs(functions) do for i,param in ipairs(f.parameters) do if param[1] == "DictionaryOf(LuaRef)" then param = {"Dictionary", param[2]} + elseif startswith(param[1], "Dict(") then + param = {"Dictionary", param[2]} end f_exported.parameters[i] = param end @@ -173,7 +175,10 @@ local output = io.open(dispatch_outputf, 'wb') local function real_type(type) local rv = type - if c_grammar.typed_container:match(rv) then + local rmatch = string.match(type, "Dict%(([_%w]+)%)") + if rmatch then + return "KeyDict_"..rmatch + elseif c_grammar.typed_container:match(rv) then if rv:match('Array') then rv = 'Array' else @@ -209,8 +214,9 @@ for i = 1, #functions do -- Declare/initialize variables that will hold converted arguments for j = 1, #fn.parameters do local param = fn.parameters[j] + local rt = real_type(param[1]) local converted = 'arg_'..j - output:write('\n '..param[1]..' '..converted..';') + output:write('\n '..rt..' '..converted..';') end output:write('\n') output:write('\n if (args.size != '..#fn.parameters..') {') @@ -225,7 +231,24 @@ for i = 1, #functions do param = fn.parameters[j] converted = 'arg_'..j local rt = real_type(param[1]) - if rt ~= 'Object' then + if rt == 'Object' then + output:write('\n '..converted..' = args.items['..(j - 1)..'];\n') + elseif rt:match('^KeyDict_') then + converted = '&' .. converted + output:write('\n if (args.items['..(j - 1)..'].type == kObjectTypeDictionary) {') --luacheck: ignore 631 + output:write('\n memset('..converted..', 0, sizeof(*'..converted..'));') -- TODO: neeeee + output:write('\n if (!api_dict_to_keydict('..converted..', '..rt..'_get_field, args.items['..(j - 1)..'].data.dictionary, error)) {') + output:write('\n goto cleanup;') + output:write('\n }') + output:write('\n } else if (args.items['..(j - 1)..'].type == kObjectTypeArray && args.items['..(j - 1)..'].data.array.size == 0) {') --luacheck: ignore 631 + output:write('\n memset('..converted..', 0, sizeof(*'..converted..'));') + + output:write('\n } else {') + output:write('\n api_set_error(error, kErrorTypeException, \ + "Wrong type for argument '..j..' when calling '..fn.name..', expecting '..param[1]..'");') + output:write('\n goto cleanup;') + output:write('\n }\n') + else if rt:match('^Buffer$') or rt:match('^Window$') or rt:match('^Tabpage$') then -- Buffer, Window, and Tabpage have a specific type, but are stored in integer output:write('\n if (args.items['.. @@ -257,10 +280,7 @@ for i = 1, #functions do "Wrong type for argument '..j..' when calling '..fn.name..', expecting '..param[1]..'");') output:write('\n goto cleanup;') output:write('\n }\n') - else - output:write('\n '..converted..' = args.items['..(j - 1)..'];\n') end - args[#args + 1] = converted end @@ -423,13 +443,24 @@ local function process_function(fn) if param[1] == "DictionaryOf(LuaRef)" then extra = "true, " end + local errshift = 0 + if string.match(param_type, '^KeyDict_') then + write_shifted_output(output, string.format([[ + %s %s = { 0 }; nlua_pop_keydict(lstate, &%s, %s_get_field, %s&err);]], param_type, cparam, cparam, param_type, extra)) + cparam = '&'..cparam + errshift = 1 -- free incomplete dict on error + else + write_shifted_output(output, string.format([[ + const %s %s = nlua_pop_%s(lstate, %s&err);]], param[1], cparam, param_type, extra)) + end + write_shifted_output(output, string.format([[ - const %s %s = nlua_pop_%s(lstate, %s&err); if (ERROR_SET(&err)) { goto exit_%u; } - ]], param[1], cparam, param_type, extra, #fn.parameters - j)) + + ]], #fn.parameters - j + errshift)) free_code[#free_code + 1] = ('api_free_%s(%s);'):format( lc_param_type, cparam) cparams = cparam .. ', ' .. cparams @@ -446,7 +477,7 @@ local function process_function(fn) for i = 1, #free_code do local rev_i = #free_code - i + 1 local code = free_code[rev_i] - if i == 1 then + if i == 1 and not string.match(real_type(fn.parameters[1][1]), '^KeyDict_') then free_at_exit_code = free_at_exit_code .. ('\n %s'):format(code) else free_at_exit_code = free_at_exit_code .. ('\n exit_%u:\n %s'):format( diff --git a/src/nvim/generators/gen_declarations.lua b/src/nvim/generators/gen_declarations.lua index 0782c8115d..97491679a4 100755 --- a/src/nvim/generators/gen_declarations.lua +++ b/src/nvim/generators/gen_declarations.lua @@ -60,7 +60,7 @@ local right_word = concat( ) local word = branch( concat( - branch(lit('ArrayOf('), lit('DictionaryOf(')), -- typed container macro + branch(lit('ArrayOf('), lit('DictionaryOf('), lit('Dict(')), -- typed container macro one_or_more(any_character - lit(')')), lit(')') ), diff --git a/src/nvim/generators/gen_keysets.lua b/src/nvim/generators/gen_keysets.lua new file mode 100644 index 0000000000..63ef202fe1 --- /dev/null +++ b/src/nvim/generators/gen_keysets.lua @@ -0,0 +1,67 @@ + +local nvimsrcdir = arg[1] +local shared_file = arg[2] +local funcs_file = arg[3] +local defs_file = arg[4] + +_G.vim = loadfile(shared_file)() + +if nvimsrcdir == '--help' then + print([[ +Usage: + lua gen_keyset.lua TODOFIXUPDATETHIS + +Will generate build/src/nvim/auto/keyset.generated.h with definition of functions +static const array. +]]) + os.exit(0) +end + + +package.path = nvimsrcdir .. '/?.lua;' .. package.path +local hashy = require'generators.hashy' + +local funcspipe = io.open(funcs_file, 'wb') +local defspipe = io.open(defs_file, 'wb') + +local keysets = require'api.keysets' + +for name, keys in pairs(keysets) do + local neworder, hashfun = hashy.hashy_hash(name, keys, function (idx) + return name.."_table["..idx.."].str" + end) + + defspipe:write("typedef struct {\n") + for _, key in ipairs(neworder) do + defspipe:write(" Object "..key..";\n") + end + defspipe:write("} KeyDict_"..name..";\n\n") + + defspipe:write("extern KeySetLink "..name.."_table[];\n") + + funcspipe:write("KeySetLink "..name.."_table[] = {\n") + for _, key in ipairs(neworder) do + funcspipe:write(' {"'..key..'", offsetof(KeyDict_'..name..", "..key..")},\n") + end + funcspipe:write(' {NULL, 0},\n') + funcspipe:write("};\n\n") + + funcspipe:write(hashfun) + + funcspipe:write([[ +Object *KeyDict_]]..name..[[_get_field(void *retval, const char *str, size_t len) +{ + int hash = ]]..name..[[_hash(str, len); + if (hash == -1) { + return NULL; + } + + return (Object *)((char *)retval + ]]..name..[[_table[hash].ptr_off); +} + +]]) + defspipe:write("#define api_free_keydict_"..name.."(x) api_free_keydict(x, "..name.."_table)\n") +end + +funcspipe:close() +defspipe:close() diff --git a/src/nvim/generators/hashy.lua b/src/nvim/generators/hashy.lua new file mode 100644 index 0000000000..fac24c810a --- /dev/null +++ b/src/nvim/generators/hashy.lua @@ -0,0 +1,122 @@ +-- HASHY McHASHFACE + +local M = {} +_G.d = M + + +local function setdefault(table, key) + local val = table[key] + if val == nil then + val = {} + table[key] = val + end + return val +end + +function M.build_pos_hash(strings) + local len_buckets = {} + local maxlen = 0 + for _,s in ipairs(strings) do + table.insert(setdefault(len_buckets, #s),s) + if #s > maxlen then maxlen = #s end + end + + local len_pos_buckets = {} + local worst_buck_size = 0 + + for len = 1,maxlen do + local strs = len_buckets[len] + if strs then + -- the best position so far generates `best_bucket` + -- with `minsize` worst case collisions + local bestpos, minsize, best_bucket = nil, #strs*2, nil + for pos = 1,len do + local try_bucket = {} + for _,str in ipairs(strs) do + local poschar = string.sub(str, pos, pos) + table.insert(setdefault(try_bucket, poschar), str) + end + local maxsize = 1 + for _,pos_strs in pairs(try_bucket) do + maxsize = math.max(maxsize, #pos_strs) + end + if maxsize < minsize then + bestpos = pos + minsize = maxsize + best_bucket = try_bucket + end + end + len_pos_buckets[len] = {bestpos, best_bucket} + worst_buck_size = math.max(worst_buck_size, minsize) + end + end + return len_pos_buckets, maxlen, worst_buck_size +end + +function M.switcher(put, tab, maxlen, worst_buck_size) + local neworder = {} + put " switch (len) {\n" + local bucky = worst_buck_size > 1 + for len = 1,maxlen do + local vals = tab[len] + if vals then + put(" case "..len..": ") + local pos, posbuck = unpack(vals) + local keys = vim.tbl_keys(posbuck) + if #keys > 1 then + table.sort(keys) + put("switch (str["..(pos-1).."]) {\n") + for _,c in ipairs(keys) do + local buck = posbuck[c] + local startidx = #neworder + vim.list_extend(neworder, buck) + local endidx = #neworder + put(" case '"..c.."': ") + put("low = "..startidx.."; ") + if bucky then put("high = "..endidx.."; ") end + put "break;\n" + end + put " default: break;\n" + put " }\n " + else + local startidx = #neworder + table.insert(neworder, posbuck[keys[1]][1]) + local endidx = #neworder + put("low = "..startidx.."; ") + if bucky then put("high = "..endidx.."; ") end + end + put " break;\n" + end + end + put " default: break;\n" + put " }\n" + return neworder +end + +function M.hashy_hash(name, strings, access) + local stats = {} + local put = function(str) table.insert(stats, str) end + local len_pos_buckets, maxlen, worst_buck_size = M.build_pos_hash(strings) + put("int "..name.."_hash(const char *str, size_t len)\n{\n") + if worst_buck_size > 1 then + put(" int low = 0, high = 0;\n") + else + put(" int low = -1;\n") + end + local neworder = M.switcher(put, len_pos_buckets, maxlen, worst_buck_size) + if worst_buck_size > 1 then + error [[ not implemented yet ]] -- TODO(bfredl) + else + put [[ + if (low < 0) { + return -1; + } + ]] + put("if(memcmp(str, "..access("low")..", len)) {\n return -1;\n }\n") + put " return low;\n" + put "}\n\n" + end + return neworder, table.concat(stats) +end + +return M diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h index f0b52079aa..83fa00977f 100644 --- a/src/nvim/getchar.h +++ b/src/nvim/getchar.h @@ -56,6 +56,8 @@ struct map_arguments { size_t orig_rhs_len; }; typedef struct map_arguments MapArguments; +#define MAP_ARGUMENTS_INIT { false, false, false, false, false, false, false, \ + { 0 }, 0, NULL, 0, false, NULL, 0 } #define KEYLEN_PART_KEY -1 // keylen value for incomplete key-code #define KEYLEN_PART_MAP -2 // keylen value for incomplete mapping diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index fac5bab664..fd4cfc4c31 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -1299,3 +1299,25 @@ void nlua_init_types(lua_State *const lstate) lua_rawset(lstate, -3); } + + +void nlua_pop_keydict(lua_State *L, void *retval, field_hash hashy, Error *err) +{ + lua_pushnil(L); // [dict, nil] + while (lua_next(L, -2)) { + // [dict, key, value] + size_t len; + const char *s = lua_tolstring(L, -2, &len); + Object *field = hashy(retval, s, len); + if (!field) { + api_set_error(err, kErrorTypeValidation, "invalid key: %.*s", (int)len, s); + lua_pop(L, 3); // [] + return; + } + + *field = nlua_pop_Object(L, true, err); + } + // [dict] + lua_pop(L, 1); + // [] +} diff --git a/src/nvim/msgpack_rpc/helpers.c b/src/nvim/msgpack_rpc/helpers.c index b1b9c77953..549016e751 100644 --- a/src/nvim/msgpack_rpc/helpers.c +++ b/src/nvim/msgpack_rpc/helpers.c @@ -15,6 +15,7 @@ #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS +# include "keysets.generated.h" # include "msgpack_rpc/helpers.c.generated.h" #endif diff --git a/test/config/paths.lua.in b/test/config/paths.lua.in index 7fe5d8ad80..e3979981ba 100644 --- a/test/config/paths.lua.in +++ b/test/config/paths.lua.in @@ -19,5 +19,6 @@ if module.test_luajit_prg == '' then end end table.insert(module.include_paths, "${CMAKE_BINARY_DIR}/include") +table.insert(module.include_paths, "${CMAKE_BINARY_DIR}/src/nvim/auto") return module diff --git a/test/functional/api/command_spec.lua b/test/functional/api/command_spec.lua index 37331d11c7..6f929ad1ca 100644 --- a/test/functional/api/command_spec.lua +++ b/test/functional/api/command_spec.lua @@ -21,7 +21,7 @@ describe('nvim_get_commands', function() it('validates input', function() eq('builtin=true not implemented', pcall_err(meths.get_commands, {builtin=true})) - eq('unexpected key: foo', pcall_err(meths.get_commands, + eq("Invalid key: 'foo'", pcall_err(meths.get_commands, {foo='blah'})) end) diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua index 4194945645..dd8eef7ca0 100644 --- a/test/functional/api/keymap_spec.lua +++ b/test/functional/api/keymap_spec.lua @@ -436,16 +436,16 @@ describe('nvim_set_keymap, nvim_del_keymap', function() end) it('error on invalid optnames', function() - eq('Invalid key: silentt', + eq("Invalid key: 'silentt'", pcall_err(meths.set_keymap, 'n', 'lhs', 'rhs', {silentt = true})) - eq('Invalid key: sidd', + eq("Invalid key: 'sidd'", pcall_err(meths.set_keymap, 'n', 'lhs', 'rhs', {sidd = false})) - eq('Invalid key: nowaiT', + eq("Invalid key: 'nowaiT'", pcall_err(meths.set_keymap, 'n', 'lhs', 'rhs', {nowaiT = false})) end) it('error on option key', function() - eq('Invalid key: buffer', + eq("Invalid key: 'buffer'", pcall_err(meths.set_keymap, 'n', 'lhs', 'rhs', {buffer = true})) end) @@ -454,8 +454,8 @@ describe('nvim_set_keymap, nvim_del_keymap', function() -- note: need '%' to escape hyphens, which have special meaning in lua it('throws an error when given non-boolean value for '..opt, function() local opts = {} - opts[opt] = 2 - eq('Gave non-boolean value for an opt: '..opt, + opts[opt] = 'fooo' + eq(opt..' is not a boolean', pcall_err(meths.set_keymap, 'n', 'lhs', 'rhs', opts)) end) end diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index df602ed48c..6bcf8dd91f 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -1119,7 +1119,7 @@ describe('API', function() describe('nvim_get_context', function() it('validates args', function() - eq('unexpected key: blah', + eq("Invalid key: 'blah'", pcall_err(nvim, 'get_context', {blah={}})) eq('invalid value for key: types', pcall_err(nvim, 'get_context', {types=42})) diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 79f308cf26..e57c63bb0f 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -1529,7 +1529,7 @@ describe('float window', function() it('API has proper error messages', function() local buf = meths.create_buf(false,false) - eq("Invalid key 'bork'", + eq("Invalid key: 'bork'", pcall_err(meths.open_win,buf, false, {width=20,height=2,bork=true})) eq("'win' key is only valid with relative='win'", pcall_err(meths.open_win,buf, false, {width=20,height=2,relative='editor',row=0,col=0,win=0})) @@ -1542,13 +1542,15 @@ describe('float window', function() eq("'relative' requires 'row'/'col' or 'bufpos'", pcall_err(meths.open_win,buf, false, {width=20,height=2,relative='editor'})) eq("'width' key must be a positive Integer", - pcall_err(meths.open_win,buf, false, {width=-1,height=2,relative='editor'})) + pcall_err(meths.open_win,buf, false, {width=-1,height=2,relative='editor', row=0, col=0})) eq("'height' key must be a positive Integer", - pcall_err(meths.open_win,buf, false, {width=20,height=-1,relative='editor'})) + pcall_err(meths.open_win,buf, false, {width=20,height=-1,relative='editor', row=0, col=0})) eq("'height' key must be a positive Integer", - pcall_err(meths.open_win,buf, false, {width=20,height=0,relative='editor'})) - eq("Must specify 'width' and 'height'", - pcall_err(meths.open_win,buf, false, {relative='editor'})) + pcall_err(meths.open_win,buf, false, {width=20,height=0,relative='editor', row=0, col=0})) + eq("Must specify 'width'", + pcall_err(meths.open_win,buf, false, {relative='editor', row=0, col=0})) + eq("Must specify 'height'", + pcall_err(meths.open_win,buf, false, {relative='editor', row=0, col=0, width=2})) end) it('can be placed relative window or cursor', function()