From 22d161a5dd1c519f998916f45d61be92662fbb44 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Oct 2017 20:11:44 +0300 Subject: api/vim: Add nvim_parse_expression function --- src/nvim/api/vim.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) (limited to 'src/nvim/api/vim.c') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 98f4410347..9daa2fb398 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -886,6 +886,72 @@ theend: return rv; } +/// Parse a VimL expression +/// +/// @param[in] expr Expression to parse. Is always treated as a single line. +/// @param[in] flags Flags: "m" if multiple expressions in a row are allowed +/// (only the first one will be parsed), "E" if EOC tokens +/// are not allowed (determines whether they will stop +/// parsing process or be recognized as an operator/space, +/// though also yielding an error). +/// +/// Use only "m" to parse like for "=", only "E" to +/// parse like for ":echo", empty string for ":let". +/// +/// @return AST: top-level dectionary holds keys +/// +/// "error": Dictionary with error, present only if parser saw some +/// error. Contains the following keys: +/// +/// "message": String, error message in printf format, translated. +/// Must contain exactly one "%.*s". +/// "arg": String, error message argument. +/// +/// "ast": actual AST, a dictionary with the following keys: +/// +/// "type": node type, one of the value names from ExprASTNodeType +/// stringified without "kExprNode" prefix. +/// "start": a pair [line, column] describing where node is “started” +/// where "line" is always 0 (will not be 0 if you will be +/// using nvim_parse_viml() on e.g. ":let", but that is not +/// present yet). Both elements are Integers. +/// "len": “length” of the node. This and "start" are there for +/// debugging purposes primary (debugging parser and providing +/// debug information). +/// "children": a list of nodes described in top/"ast". There always +/// is zero, one or two children, key will contain an +/// empty array if node can have children, but has no and +/// will not be present at all if node can’t have any +/// children. Maximum number of children may be found in +/// node_maxchildren array. +/// +/// Local values (present only for certain nodes): +/// +/// "scope": a single Integer, specifies scope for "Option" and +/// "PlainIdentifier" nodes. For "Option" it is one of +/// ExprOptScope values, for "PlainIdentifier" it is one of +/// ExprVarScope values. +/// "ident": identifier (without scope, if any), present for "Option", +/// "PlainIdentifier", "PlainKey" and "Environment" nodes. +/// "name": Integer, register name (one character) or -1. Only present +/// for "Register" nodes. +/// "cmp_type": String, comparison type, one of the value names from +/// ExprComparisonType, stringified without "kExprCmp" +/// prefix. Only present for "Comparison" nodes. +/// "ccs_strategy": String, case comparison strategy, one of the +/// value names from ExprCaseCompareStrategy, +/// stringified without "kCCStrategy" prefix. Only +/// present for "Comparison" nodes. +/// "ivalue": Integer, integer value for "Integer" nodes. +/// "fvalue": Float, floating-point value for "Float" nodes. +/// "svalue": String, value for "SingleQuotedString" and +/// "DoubleQuotedString" nodes. +Dictionary nvim_parse_expression(String expr, String flags, Error *err) + FUNC_API_SINCE(4) +{ + return (Dictionary)ARRAY_DICT_INIT; +} + /// Writes a message to vim output or error buffer. The string is split /// and flushed after each newline. Incomplete lines are kept for writing -- cgit From b9d5aea073521f3278bea257be306b7ac2b8d83c Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 3 Nov 2017 11:38:59 +0300 Subject: api/vim: Create part of nvim_parse_expression function --- src/nvim/api/vim.c | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) (limited to 'src/nvim/api/vim.c') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 9daa2fb398..446a049897 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -33,6 +33,8 @@ #include "nvim/syntax.h" #include "nvim/getchar.h" #include "nvim/os/input.h" +#include "nvim/viml/parser/expressions.h" +#include "nvim/viml/parser/parser.h" #define LINE_BUFFER_SIZE 4096 @@ -897,6 +899,12 @@ theend: /// /// Use only "m" to parse like for "=", only "E" to /// parse like for ":echo", empty string for ":let". +/// @param[in] highlight If true, return value will also include "highlight" +/// key containing array of 4-tuples (arrays) (Integer, +/// Integer, Integer, String), where first three numbers +/// define the highlighted region and represent line, +/// starting column and ending column (latter exclusive: +/// one should highlight region [start_col, end_col)). /// /// @return AST: top-level dectionary holds keys /// @@ -946,9 +954,105 @@ theend: /// "fvalue": Float, floating-point value for "Float" nodes. /// "svalue": String, value for "SingleQuotedString" and /// "DoubleQuotedString" nodes. -Dictionary nvim_parse_expression(String expr, String flags, Error *err) +Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, + Error *err) FUNC_API_SINCE(4) { + int pflags = 0; + for (size_t i = 0 ; i < flags.size ; i++) { + switch (flags.data[i]) { + case 'm': { pflags |= kExprFlagsMulti; break; } + case 'E': { pflags |= kExprFlagsDisallowEOC; break; } + default: { + api_set_error(err, kErrorTypeValidation, "Invalid flag: '%c' (%u)", + flags.data[i], (unsigned)flags.data[i]); + return (Dictionary)ARRAY_DICT_INIT; + } + } + } + ParserLine plines[] = { + { + .data = expr.data, + .size = expr.size, + .allocated = false, + }, + { NULL, 0, false }, + }; + ParserLine *plines_p = plines; + ParserHighlight colors; + kvi_init(colors); + ParserHighlight *const colors_p = (highlight ? &colors : NULL); + ParserState pstate; + viml_parser_init( + &pstate, parser_simple_get_line, &plines_p, colors_p); + ExprAST east = viml_pexpr_parse(&pstate, pflags); + + const size_t dict_size = ( + 1 // "ast" + + (size_t)(east.err.arg != NULL) // "error" + + (size_t)highlight // "highlight" + ); + Dictionary ret = { + .items = xmalloc(dict_size * sizeof(ret.items[0])), + .size = 0, + .capacity = dict_size, + }; + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_AS_STRING("ast"), + .value = NIL, + }; + if (east.err.arg != NULL) { + Dictionary err_dict = { + .items = xmalloc(2 * sizeof(err_dict.items[0])), + .size = 2, + .capacity = 2, + }; + err_dict.items[0] = (KeyValuePair) { + .key = STATIC_CSTR_AS_STRING("message"), + .value = STRING_OBJ(cstr_to_string(east.err.arg)), + }; + err_dict.items[1] = (KeyValuePair) { + .key = STATIC_CSTR_AS_STRING("arg"), + .value = STRING_OBJ(((String) { + .data = xmemdup(east.err.arg, (size_t)east.err.arg_len), + .size = (size_t)east.err.arg_len, + })), + }; + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_AS_STRING("error"), + .value = DICTIONARY_OBJ(err_dict), + }; + } + if (highlight) { + Array hl = (Array) { + .items = xmalloc(kv_size(colors) * sizeof(hl.items[0])), + .capacity = kv_size(colors), + .size = kv_size(colors), + }; + for (size_t i = 0 ; i < kv_size(colors) ; i++) { + const ParserHighlightChunk chunk = kv_A(colors, i); + Array chunk_arr = (Array) { + .items = xmalloc(4), + .capacity = 4, + .size = 4, + }; + chunk_arr.items[0] = INTEGER_OBJ((Integer)chunk.start.line); + chunk_arr.items[1] = INTEGER_OBJ((Integer)chunk.start.col); + chunk_arr.items[2] = INTEGER_OBJ((Integer)chunk.end_col); + chunk_arr.items[3] = STRING_OBJ(cstr_to_string(chunk.group)); + hl.items[i] = ARRAY_OBJ(chunk_arr); + } + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_AS_STRING("highlight"), + .value = ARRAY_OBJ(hl), + }; + } + + // FIXME: populate AST + + assert(ret.size == ret.capacity); + viml_pexpr_free_ast(east); + viml_parser_destroy(&pstate); return (Dictionary)ARRAY_DICT_INIT; } -- cgit From 07ec709141886c6db4f944665e07a36ef7302eb4 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 5 Nov 2017 01:33:44 +0300 Subject: vim/api: Actually dump AST, fix some bugs in nvim_parse_expression --- src/nvim/api/vim.c | 264 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 249 insertions(+), 15 deletions(-) (limited to 'src/nvim/api/vim.c') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 446a049897..1aab87010e 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -888,6 +888,13 @@ theend: return rv; } +typedef struct { + ExprASTNode **node_p; + Object *ret_node_p; +} ExprASTConvStackItem; + +typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; + /// Parse a VimL expression /// /// @param[in] expr Expression to parse. Is always treated as a single line. @@ -915,7 +922,8 @@ theend: /// Must contain exactly one "%.*s". /// "arg": String, error message argument. /// -/// "ast": actual AST, a dictionary with the following keys: +/// "ast": actual AST, either nil or a dictionary with the following +/// keys: /// /// "type": node type, one of the value names from ExprASTNodeType /// stringified without "kExprNode" prefix. @@ -927,11 +935,9 @@ theend: /// debugging purposes primary (debugging parser and providing /// debug information). /// "children": a list of nodes described in top/"ast". There always -/// is zero, one or two children, key will contain an -/// empty array if node can have children, but has no and -/// will not be present at all if node can’t have any -/// children. Maximum number of children may be found in -/// node_maxchildren array. +/// is zero, one or two children, key will not be present +/// if node has no children. Maximum number of children +/// may be found in node_maxchildren array. /// /// Local values (present only for certain nodes): /// @@ -950,6 +956,8 @@ theend: /// value names from ExprCaseCompareStrategy, /// stringified without "kCCStrategy" prefix. Only /// present for "Comparison" nodes. +/// "invert": Boolean, true if result of comparison needs to be +/// inverted. Only present for "Comparison" nodes. /// "ivalue": Integer, integer value for "Integer" nodes. /// "fvalue": Float, floating-point value for "Float" nodes. /// "svalue": String, value for "SingleQuotedString" and @@ -998,7 +1006,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, .capacity = dict_size, }; ret.items[ret.size++] = (KeyValuePair) { - .key = STATIC_CSTR_AS_STRING("ast"), + .key = STATIC_CSTR_TO_STRING("ast"), .value = NIL, }; if (east.err.arg != NULL) { @@ -1008,18 +1016,18 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, .capacity = 2, }; err_dict.items[0] = (KeyValuePair) { - .key = STATIC_CSTR_AS_STRING("message"), + .key = STATIC_CSTR_TO_STRING("message"), .value = STRING_OBJ(cstr_to_string(east.err.arg)), }; err_dict.items[1] = (KeyValuePair) { - .key = STATIC_CSTR_AS_STRING("arg"), + .key = STATIC_CSTR_TO_STRING("arg"), .value = STRING_OBJ(((String) { - .data = xmemdup(east.err.arg, (size_t)east.err.arg_len), + .data = xmemdupz(east.err.arg, (size_t)east.err.arg_len), .size = (size_t)east.err.arg_len, })), }; ret.items[ret.size++] = (KeyValuePair) { - .key = STATIC_CSTR_AS_STRING("error"), + .key = STATIC_CSTR_TO_STRING("error"), .value = DICTIONARY_OBJ(err_dict), }; } @@ -1032,7 +1040,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, for (size_t i = 0 ; i < kv_size(colors) ; i++) { const ParserHighlightChunk chunk = kv_A(colors, i); Array chunk_arr = (Array) { - .items = xmalloc(4), + .items = xmalloc(4 * sizeof(chunk_arr.items[0])), .capacity = 4, .size = 4, }; @@ -1043,17 +1051,243 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, hl.items[i] = ARRAY_OBJ(chunk_arr); } ret.items[ret.size++] = (KeyValuePair) { - .key = STATIC_CSTR_AS_STRING("highlight"), + .key = STATIC_CSTR_TO_STRING("highlight"), .value = ARRAY_OBJ(hl), }; } - // FIXME: populate AST + // Walk over the AST, freeing nodes in process. + ExprASTConvStack ast_conv_stack; + kvi_init(ast_conv_stack); + kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { + .node_p = &east.root, + .ret_node_p = &ret.items[0].value, + })); + while (kv_size(ast_conv_stack)) { + ExprASTConvStackItem cur_item = kv_last(ast_conv_stack); + if (*cur_item.node_p == NULL) { + assert(kv_size(ast_conv_stack) == 1); + kv_drop(ast_conv_stack, 1); + } else { + ExprASTNode *const node = *cur_item.node_p; + if (cur_item.ret_node_p->type == kObjectTypeNil) { + const size_t ret_node_items_size = (size_t)( + 3 // "type", "start" and "len" + + (node->children != NULL) // "children" + + (node->type == kExprNodeOption + || node->type == kExprNodePlainIdentifier) // "scope" + + (node->type == kExprNodeOption + || node->type == kExprNodePlainIdentifier + || node->type == kExprNodePlainKey + || node->type == kExprNodeEnvironment) // "ident" + + (node->type == kExprNodeRegister) // "name" + + (3 // "cmp_type", "ccs_strategy", "invert" + * (node->type == kExprNodeComparison)) + + (node->type == kExprNodeInteger) // "ivalue" + + (node->type == kExprNodeFloat) // "fvalue" + + (node->type == kExprNodeDoubleQuotedString + || node->type == kExprNodeSingleQuotedString) // "svalue" + + 0); + Dictionary ret_node = { + .items = xmalloc(ret_node_items_size * sizeof(ret_node.items[0])), + .capacity = ret_node_items_size, + .size = 0, + }; + *cur_item.ret_node_p = DICTIONARY_OBJ(ret_node); + } + Dictionary *ret_node = &cur_item.ret_node_p->data.dictionary; + if (node->children != NULL) { + const size_t num_children = 1 + (node->children->next != NULL); + Array children_array = { + .items = xmalloc(num_children * sizeof(children_array.items[0])), + .capacity = num_children, + .size = num_children, + }; + for (size_t i = 0; i < num_children; i++) { + children_array.items[i] = NIL; + } + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("children"), + .value = ARRAY_OBJ(children_array), + }; + kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { + .node_p = &node->children, + .ret_node_p = &children_array.items[0], + })); + } else if (node->next != NULL) { + kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { + .node_p = &node->children, + .ret_node_p = cur_item.ret_node_p + 1, + })); + } else if (node != NULL) { + kv_drop(ast_conv_stack, 1); + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("type"), + .value = STRING_OBJ(cstr_to_string(east_node_type_tab[node->type])), + }; + Array start_array = { + .items = xmalloc(2 * sizeof(start_array.items[0])), + .capacity = 2, + .size = 2, + }; + start_array.items[0] = INTEGER_OBJ((Integer)node->start.line); + start_array.items[1] = INTEGER_OBJ((Integer)node->start.col); + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("start"), + .value = ARRAY_OBJ(start_array), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("len"), + .value = INTEGER_OBJ((Integer)node->len), + }; + switch (node->type) { + case kExprNodeDoubleQuotedString: + case kExprNodeSingleQuotedString: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("svalue"), + .value = STRING_OBJ(cstr_as_string(node->data.str.value)), + }; + break; + } + case kExprNodeOption: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("scope"), + .value = INTEGER_OBJ(node->data.opt.scope), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.opt.ident, + node->data.opt.ident_len), + .size = node->data.opt.ident_len, + })), + }; + break; + } + case kExprNodePlainIdentifier: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("scope"), + .value = INTEGER_OBJ(node->data.var.scope), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.var.ident, + node->data.var.ident_len), + .size = node->data.var.ident_len, + })), + }; + break; + } + case kExprNodePlainKey: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.var.ident, + node->data.var.ident_len), + .size = node->data.var.ident_len, + })), + }; + break; + } + case kExprNodeEnvironment: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.env.ident, + node->data.env.ident_len), + .size = node->data.env.ident_len, + })), + }; + break; + } + case kExprNodeRegister: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("name"), + .value = INTEGER_OBJ(node->data.reg.name), + }; + break; + } + case kExprNodeComparison: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("cmp_type"), + .value = STRING_OBJ(cstr_to_string( + eltkn_cmp_type_tab[node->data.cmp.type])), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ccs_strategy"), + .value = STRING_OBJ(cstr_to_string( + eltkn_cmp_type_tab[node->data.cmp.ccs])), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("invert"), + .value = BOOLEAN_OBJ(node->data.cmp.inv), + }; + break; + } + case kExprNodeFloat: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("fvalue"), + .value = FLOAT_OBJ(node->data.flt.value), + }; + break; + } + case kExprNodeInteger: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ivalue"), + .value = INTEGER_OBJ((Integer)( + node->data.num.value > API_INTEGER_MAX + ? API_INTEGER_MAX + : (Integer)node->data.num.value)), + }; + break; + } + case kExprNodeMissing: + case kExprNodeOpMissing: + case kExprNodeTernary: + case kExprNodeTernaryValue: + case kExprNodeSubscript: + case kExprNodeListLiteral: + case kExprNodeUnaryPlus: + case kExprNodeBinaryPlus: + case kExprNodeNested: + case kExprNodeCall: + case kExprNodeComplexIdentifier: + case kExprNodeUnknownFigure: + case kExprNodeLambda: + case kExprNodeDictLiteral: + case kExprNodeCurlyBracesIdentifier: + case kExprNodeComma: + case kExprNodeColon: + case kExprNodeArrow: + case kExprNodeConcat: + case kExprNodeConcatOrSubscript: + case kExprNodeOr: + case kExprNodeAnd: + case kExprNodeUnaryMinus: + case kExprNodeBinaryMinus: + case kExprNodeNot: + case kExprNodeMultiplication: + case kExprNodeDivision: + case kExprNodeMod: { + break; + } + } + assert(cur_item.ret_node_p->data.dictionary.size + == cur_item.ret_node_p->data.dictionary.capacity); + xfree(*cur_item.node_p); + *cur_item.node_p = NULL; + } + } + } + kvi_destroy(ast_conv_stack); assert(ret.size == ret.capacity); + // Should be a no-op actually, leaving it in case non-nodes will need to be + // freed later. viml_pexpr_free_ast(east); viml_parser_destroy(&pstate); - return (Dictionary)ARRAY_DICT_INIT; + return ret; } -- cgit From 7bc6de75263f58c6c4f999bc86a6454ae9f28b80 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 5 Nov 2017 02:41:44 +0300 Subject: api/vim,functests: Add tests for nvim_parse_expression, fix found bugs --- src/nvim/api/vim.c | 47 +++++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 18 deletions(-) (limited to 'src/nvim/api/vim.c') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 1aab87010e..5f725acaf7 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -995,21 +995,21 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, &pstate, parser_simple_get_line, &plines_p, colors_p); ExprAST east = viml_pexpr_parse(&pstate, pflags); - const size_t dict_size = ( + const size_t ret_size = ( 1 // "ast" - + (size_t)(east.err.arg != NULL) // "error" + + (size_t)(east.err.msg != NULL) // "error" + (size_t)highlight // "highlight" ); Dictionary ret = { - .items = xmalloc(dict_size * sizeof(ret.items[0])), + .items = xmalloc(ret_size * sizeof(ret.items[0])), .size = 0, - .capacity = dict_size, + .capacity = ret_size, }; ret.items[ret.size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("ast"), .value = NIL, }; - if (east.err.arg != NULL) { + if (east.err.msg != NULL) { Dictionary err_dict = { .items = xmalloc(2 * sizeof(err_dict.items[0])), .size = 2, @@ -1017,15 +1017,22 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, }; err_dict.items[0] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("message"), - .value = STRING_OBJ(cstr_to_string(east.err.arg)), - }; - err_dict.items[1] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("arg"), - .value = STRING_OBJ(((String) { - .data = xmemdupz(east.err.arg, (size_t)east.err.arg_len), - .size = (size_t)east.err.arg_len, - })), + .value = STRING_OBJ(cstr_to_string(east.err.msg)), }; + if (east.err.arg == NULL) { + err_dict.items[1] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("arg"), + .value = STRING_OBJ(STRING_INIT), + }; + } else { + err_dict.items[1] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("arg"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(east.err.arg, (size_t)east.err.arg_len), + .size = (size_t)east.err.arg_len, + })), + }; + } ret.items[ret.size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("error"), .value = DICTIONARY_OBJ(err_dict), @@ -1055,6 +1062,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, .value = ARRAY_OBJ(hl), }; } + kvi_destroy(colors); // Walk over the AST, freeing nodes in process. ExprASTConvStack ast_conv_stack; @@ -1065,11 +1073,11 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, })); while (kv_size(ast_conv_stack)) { ExprASTConvStackItem cur_item = kv_last(ast_conv_stack); - if (*cur_item.node_p == NULL) { + ExprASTNode *const node = *cur_item.node_p; + if (node == NULL) { assert(kv_size(ast_conv_stack) == 1); kv_drop(ast_conv_stack, 1); } else { - ExprASTNode *const node = *cur_item.node_p; if (cur_item.ret_node_p->type == kObjectTypeNil) { const size_t ret_node_items_size = (size_t)( 3 // "type", "start" and "len" @@ -1116,7 +1124,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, })); } else if (node->next != NULL) { kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { - .node_p = &node->children, + .node_p = &node->next, .ret_node_p = cur_item.ret_node_p + 1, })); } else if (node != NULL) { @@ -1145,7 +1153,10 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, case kExprNodeSingleQuotedString: { ret_node->items[ret_node->size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("svalue"), - .value = STRING_OBJ(cstr_as_string(node->data.str.value)), + .value = STRING_OBJ(((String) { + .data = node->data.str.value, + .size = node->data.str.size, + })), }; break; } @@ -1217,7 +1228,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, ret_node->items[ret_node->size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("ccs_strategy"), .value = STRING_OBJ(cstr_to_string( - eltkn_cmp_type_tab[node->data.cmp.ccs])), + ccs_tab[node->data.cmp.ccs])), }; ret_node->items[ret_node->size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("invert"), -- cgit From 7849070f998902bb6aae5d6f9147d4daf5421b36 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 5 Nov 2017 21:06:12 +0300 Subject: tests: Add missing test cases --- src/nvim/api/vim.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/nvim/api/vim.c') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 5f725acaf7..b12c595cb5 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -971,6 +971,11 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, switch (flags.data[i]) { case 'm': { pflags |= kExprFlagsMulti; break; } case 'E': { pflags |= kExprFlagsDisallowEOC; break; } + case NUL: { + api_set_error(err, kErrorTypeValidation, "Invalid flag: '\\0' (%u)", + (unsigned)flags.data[i]); + return (Dictionary)ARRAY_DICT_INIT; + } default: { api_set_error(err, kErrorTypeValidation, "Invalid flag: '%c' (%u)", flags.data[i], (unsigned)flags.data[i]); @@ -995,6 +1000,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, &pstate, parser_simple_get_line, &plines_p, colors_p); ExprAST east = viml_pexpr_parse(&pstate, pflags); + // FIXME add parse_length key const size_t ret_size = ( 1 // "ast" + (size_t)(east.err.msg != NULL) // "error" -- cgit From ebb59778371784ba28d37fc615ae02a5338f365d Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 6 Nov 2017 01:15:18 +0300 Subject: api/vim: Add “len” dictionary key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows determining where parsing ended which may be needed for e.g. parsing `:echo` with that API function. --- src/nvim/api/vim.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'src/nvim/api/vim.c') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index b12c595cb5..8de37e2cf3 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -922,6 +922,12 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; /// Must contain exactly one "%.*s". /// "arg": String, error message argument. /// +/// "len": Amount of bytes successfully parsed. With flags equal to "" +/// that should be equal to the length of expr string. +/// +/// @note: “Sucessfully parsed” here means “participated in AST +/// creation”, not “till the first error”. +/// /// "ast": actual AST, either nil or a dictionary with the following /// keys: /// @@ -1000,9 +1006,8 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, &pstate, parser_simple_get_line, &plines_p, colors_p); ExprAST east = viml_pexpr_parse(&pstate, pflags); - // FIXME add parse_length key const size_t ret_size = ( - 1 // "ast" + 2 // "ast", "len" + (size_t)(east.err.msg != NULL) // "error" + (size_t)highlight // "highlight" ); @@ -1015,6 +1020,12 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, .key = STATIC_CSTR_TO_STRING("ast"), .value = NIL, }; + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("len"), + .value = INTEGER_OBJ((Integer)(pstate.pos.line == 1 + ? plines[0].size + : pstate.pos.col)), + }; if (east.err.msg != NULL) { Dictionary err_dict = { .items = xmalloc(2 * sizeof(err_dict.items[0])), -- cgit From 4aebd00a9eeeb2f56ff53dd4e383825e997ee7be Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 6 Nov 2017 20:28:37 +0300 Subject: *: Fix linter errors --- src/nvim/api/vim.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'src/nvim/api/vim.c') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 0596c3ebd8..bac19ef363 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -930,7 +930,7 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; /// @note: “Sucessfully parsed” here means “participated in AST /// creation”, not “till the first error”. /// -/// "ast": actual AST, either nil or a dictionary with the following +/// "ast": actual AST, either nil or a dictionary with the following /// keys: /// /// "type": node type, one of the value names from ExprASTNodeType @@ -1009,10 +1009,10 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, ExprAST east = viml_pexpr_parse(&pstate, pflags); const size_t ret_size = ( - 2 // "ast", "len" - + (size_t)(east.err.msg != NULL) // "error" - + (size_t)highlight // "highlight" - ); + 2 // "ast", "len" + + (size_t)(east.err.msg != NULL) // "error" + + (size_t)highlight // "highlight" + + 0); Dictionary ret = { .items = xmalloc(ret_size * sizeof(ret.items[0])), .size = 0, @@ -1242,12 +1242,12 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, ret_node->items[ret_node->size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("cmp_type"), .value = STRING_OBJ(cstr_to_string( - eltkn_cmp_type_tab[node->data.cmp.type])), + eltkn_cmp_type_tab[node->data.cmp.type])), }; ret_node->items[ret_node->size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("ccs_strategy"), .value = STRING_OBJ(cstr_to_string( - ccs_tab[node->data.cmp.ccs])), + ccs_tab[node->data.cmp.ccs])), }; ret_node->items[ret_node->size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("invert"), @@ -1266,9 +1266,9 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, ret_node->items[ret_node->size++] = (KeyValuePair) { .key = STATIC_CSTR_TO_STRING("ivalue"), .value = INTEGER_OBJ((Integer)( - node->data.num.value > API_INTEGER_MAX - ? API_INTEGER_MAX - : (Integer)node->data.num.value)), + node->data.num.value > API_INTEGER_MAX + ? API_INTEGER_MAX + : (Integer)node->data.num.value)), }; break; } -- cgit From c7495ebcc0918ffd682083408895451318e41d1f Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 12 Nov 2017 02:18:43 +0300 Subject: viml/parser/expressions: Add support for parsing assignments --- src/nvim/api/vim.c | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) (limited to 'src/nvim/api/vim.c') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index bac19ef363..ce554d351c 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -900,14 +900,21 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; /// Parse a VimL expression /// /// @param[in] expr Expression to parse. Is always treated as a single line. -/// @param[in] flags Flags: "m" if multiple expressions in a row are allowed -/// (only the first one will be parsed), "E" if EOC tokens -/// are not allowed (determines whether they will stop -/// parsing process or be recognized as an operator/space, -/// though also yielding an error). -/// -/// Use only "m" to parse like for "=", only "E" to -/// parse like for ":echo", empty string for ":let". +/// @param[in] flags Flags: +/// +/// - "m" if multiple expressions in a row are allowed (only +/// the first one will be parsed), +/// - "E" if EOC tokens are not allowed (determines whether +/// they will stop parsing process or be recognized as an +/// operator/space, though also yielding an error). +/// - "l" when needing to start parsing with lvalues for +/// ":let" or ":for". +/// +/// Common flag sets: +/// - "m" to parse like for ":echo". +/// - "E" to parse like for "=". +/// - empty string for ":call". +/// - "lm" to parse for ":let". /// @param[in] highlight If true, return value will also include "highlight" /// key containing array of 4-tuples (arrays) (Integer, /// Integer, Integer, String), where first three numbers @@ -964,6 +971,9 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; /// value names from ExprCaseCompareStrategy, /// stringified without "kCCStrategy" prefix. Only /// present for "Comparison" nodes. +/// "augmentation": String, augmentation type for "Assignment" nodes. +/// Is either an empty string, "Add", "Subtract" or +/// "Concat" for "=", "+=", "-=" or ".=" respectively. /// "invert": Boolean, true if result of comparison needs to be /// inverted. Only present for "Comparison" nodes. /// "ivalue": Integer, integer value for "Integer" nodes. @@ -979,6 +989,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, switch (flags.data[i]) { case 'm': { pflags |= kExprFlagsMulti; break; } case 'E': { pflags |= kExprFlagsDisallowEOC; break; } + case 'l': { pflags |= kExprFlagsParseLet; break; } case NUL: { api_set_error(err, kErrorTypeValidation, "Invalid flag: '\\0' (%u)", (unsigned)flags.data[i]); @@ -1114,6 +1125,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, + (node->type == kExprNodeFloat) // "fvalue" + (node->type == kExprNodeDoubleQuotedString || node->type == kExprNodeSingleQuotedString) // "svalue" + + (node->type == kExprNodeAssignment) // "augmentation" + 0); Dictionary ret_node = { .items = xmalloc(ret_node_items_size * sizeof(ret_node.items[0])), @@ -1272,6 +1284,17 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, }; break; } + case kExprNodeAssignment: { + const ExprAssignmentType asgn_type = node->data.ass.type; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("augmentation"), + .value = STRING_OBJ( + asgn_type == kExprAsgnPlain + ? (String)STRING_INIT + : cstr_to_string(expr_asgn_type_tab[asgn_type])), + }; + break; + } case kExprNodeMissing: case kExprNodeOpMissing: case kExprNodeTernary: -- cgit From 17077b68133a62d0dc1b84cb48779464c117e028 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 26 Nov 2017 16:08:53 +0300 Subject: viml/parser/expressions: Make $ENV not depend on &isident --- src/nvim/api/vim.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api/vim.c') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 7838f9faa3..a0816dbc8e 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -983,7 +983,7 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; /// "DoubleQuotedString" nodes. Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, Error *err) - FUNC_API_SINCE(4) + FUNC_API_SINCE(4) FUNC_API_ASYNC { int pflags = 0; for (size_t i = 0 ; i < flags.size ; i++) { -- cgit From dc232b74fb1a7927d6c13bd8c80bb42fc18a859f Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 10 Dec 2017 01:24:54 +0100 Subject: doc: hack to avoid doxygen bug Use `@cond ` to obscure a section from doxygen. doxygen thinks kvec_withinit_t() is a function. That adds noise to the generated API documentation, and also prevents the following function from being noticed. --- src/nvim/api/vim.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/nvim/api/vim.c') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 416e7d22d2..f0db391abe 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -789,6 +789,10 @@ ArrayOf(Dictionary) nvim_get_keymap(String mode) return keymap_array(mode, NULL); } +/// Returns a 2-tuple (Array), where item 0 is the current channel id and item +/// 1 is the |api-metadata| map (Dictionary). +/// +/// @returns 2-tuple [{channel-id}, {api-metadata}] Array nvim_get_api_info(uint64_t channel_id) FUNC_API_SINCE(1) FUNC_API_ASYNC FUNC_API_REMOTE_ONLY { @@ -896,7 +900,9 @@ typedef struct { Object *ret_node_p; } ExprASTConvStackItem; +///@cond DOXYGEN_NOT_A_FUNCTION typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; +///@endcond /// Parse a VimL expression /// -- cgit From 45998deb5d325c7a44ce38b0c7d954919458f105 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 12 Dec 2017 00:52:14 +0300 Subject: *: Fix linter errors --- src/nvim/api/vim.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/nvim/api/vim.c') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index f0db391abe..c0daac8085 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -900,9 +900,9 @@ typedef struct { Object *ret_node_p; } ExprASTConvStackItem; -///@cond DOXYGEN_NOT_A_FUNCTION +/// @cond DOXYGEN_NOT_A_FUNCTION typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; -///@endcond +/// @endcond /// Parse a VimL expression /// -- cgit From fe60fa9faafd90cfe756c80119abae6f8906fc4b Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 16 Dec 2017 21:33:59 +0100 Subject: doc vim-patch:8.0.1206: no autocmd for entering or leaving the command line (commit a4f6cec7a31ff8dbfa089b9e22227afbeb951e9b) NA patches: vim-patch:8.0.0320: warning for unused variable with small build --- src/nvim/api/vim.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api/vim.c') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index c0daac8085..172f2ce18e 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -929,7 +929,7 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; /// starting column and ending column (latter exclusive: /// one should highlight region [start_col, end_col)). /// -/// @return AST: top-level dictionary holds keys +/// @return AST: top-level dictionary with these keys: /// /// "error": Dictionary with error, present only if parser saw some /// error. Contains the following keys: -- cgit From c095f83116eb8ef87983ca5fea61053755fbc4e5 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 5 Jan 2018 11:17:21 +0100 Subject: api: change nvim_command_output behavior Implement nvim_command_output with `execute({cmd},"silent")`. Behavior changes: - does not provoke any hit-enter prompt - no longer prepends a newline char - does not capture some noise (like the "[New File]" message, see the change to tabnewentered_spec.lua) Technically ("bug-for-bug") this a breaking change. But the previous behavior of nvim_command_output meant that it probably wasn't used for anything outside of tests. Also remove the undocumented `v:command_output` variable which was a hack introduced only for the purposes of nvim_command_output. closes #7726 --- src/nvim/api/vim.c | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) (limited to 'src/nvim/api/vim.c') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 172f2ce18e..8929bc5b9b 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -43,14 +43,15 @@ #endif /// Executes an ex-command. -/// On VimL error: Returns the VimL error; v:errmsg is not updated. +/// +/// On parse error: forwards the Vim error; does not update v:errmsg. +/// On runtime error: forwards the Vim error; does not update v:errmsg. /// /// @param command Ex-command string -/// @param[out] err Error details (including actual VimL error), if any +/// @param[out] err Error details (Vim error), if any void nvim_command(String command, Error *err) FUNC_API_SINCE(1) { - // Run the command try_start(); do_cmdline_cmd(command.data); update_screen(VALID); @@ -207,18 +208,39 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, return cstr_as_string(ptr); } -String nvim_command_output(String str, Error *err) +/// Executes an ex-command and returns its (non-error) output. +/// Shell |:!| output is not captured. +/// +/// On parse error: forwards the Vim error; does not update v:errmsg. +/// On runtime error: forwards the Vim error; does not update v:errmsg. +/// +/// @param command Ex-command string +/// @param[out] err Error details (Vim error), if any +String nvim_command_output(String command, Error *err) FUNC_API_SINCE(1) { - do_cmdline_cmd("redir => v:command_output"); - nvim_command(str, err); - do_cmdline_cmd("redir END"); - + Array args = ARRAY_DICT_INIT; + ADD(args, STRING_OBJ(copy_string(command))); + ADD(args, STRING_OBJ(cstr_to_string("silent"))); + String fn = cstr_to_string("execute"); + Object rv = nvim_call_function(fn, args, err); + api_free_string(fn); + api_free_array(args); if (ERROR_SET(err)) { + assert(rv.type == kObjectTypeNil); return (String)STRING_INIT; } - return cstr_to_string((char *)get_vim_var_str(VV_COMMAND_OUTPUT)); + assert(rv.type == kObjectTypeString); + // execute() always(?) prepends a newline; remove it. + if (rv.data.string.size > 1) { + assert(rv.data.string.data[0] == '\n'); + String *s = &rv.data.string; + s->size--; + memmove(s->data, s->data + 1, s->size); + s->data[s->size] = '\0'; + } + return rv.data.string; } /// Evaluates a VimL expression (:help expression). -- cgit From 5055d4a755d3c100cddf51bded77abca04beb971 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 9 Jan 2018 10:36:25 +0100 Subject: api: nvim_command_output: direct impl --- src/nvim/api/vim.c | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-) (limited to 'src/nvim/api/vim.c') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 8929bc5b9b..0e7cc428d4 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -219,28 +219,38 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, String nvim_command_output(String command, Error *err) FUNC_API_SINCE(1) { - Array args = ARRAY_DICT_INIT; - ADD(args, STRING_OBJ(copy_string(command))); - ADD(args, STRING_OBJ(cstr_to_string("silent"))); - String fn = cstr_to_string("execute"); - Object rv = nvim_call_function(fn, args, err); - api_free_string(fn); - api_free_array(args); + const int save_msg_silent = msg_silent; + garray_T *const save_capture_ga = capture_ga; + garray_T capture_local; + ga_init(&capture_local, 1, 80); + + try_start(); + msg_silent++; + capture_ga = &capture_local; + do_cmdline_cmd(command.data); + capture_ga = save_capture_ga; + msg_silent = save_msg_silent; + try_end(err); + if (ERROR_SET(err)) { - assert(rv.type == kObjectTypeNil); - return (String)STRING_INIT; + goto theend; } - assert(rv.type == kObjectTypeString); - // execute() always(?) prepends a newline; remove it. - if (rv.data.string.size > 1) { - assert(rv.data.string.data[0] == '\n'); - String *s = &rv.data.string; - s->size--; - memmove(s->data, s->data + 1, s->size); - s->data[s->size] = '\0'; + if (capture_local.ga_len > 1) { + // redir always(?) prepends a newline; remove it. + char *s = capture_local.ga_data; + assert(s[0] == '\n'); + memmove(s, s + 1, (size_t)capture_local.ga_len); + s[capture_local.ga_len - 1] = '\0'; + return (String) { // Caller will free the memory. + .data = s, + .size = (size_t)(capture_local.ga_len - 1), + }; } - return rv.data.string; + +theend: + ga_clear(&capture_local); + return (String)STRING_INIT; } /// Evaluates a VimL expression (:help expression). -- cgit From 9f994bb69925ed943b17d03f2cad7d5e2c21e992 Mon Sep 17 00:00:00 2001 From: geekodour Date: Fri, 16 Feb 2018 14:10:40 +0530 Subject: api: nvim_list_uis #8004 ref #7438 closes #4842 --- src/nvim/api/vim.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'src/nvim/api/vim.c') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 0e7cc428d4..dad67c5e4b 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -35,6 +35,7 @@ #include "nvim/os/input.h" #include "nvim/viml/parser/expressions.h" #include "nvim/viml/parser/parser.h" +#include "nvim/ui.h" #define LINE_BUFFER_SIZE 4096 @@ -1468,3 +1469,12 @@ Float nvim__id_float(Float flt) { return flt; } + +/// Gets a list of dictionaries representing attached UIs. +/// +/// @return Array of UI dictionaries +Array nvim_list_uis(void) + FUNC_API_SINCE(4) +{ + return ui_array(); +} -- cgit From dbad797edd4636f830abd7ade1138a1a27ac30d2 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 11 Mar 2018 21:47:12 +0100 Subject: API: nvim_get_proc_children() ref https://github.com/libuv/libuv/pull/836 --- src/nvim/api/vim.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'src/nvim/api/vim.c') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index dad67c5e4b..850f892c18 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -33,6 +33,7 @@ #include "nvim/syntax.h" #include "nvim/getchar.h" #include "nvim/os/input.h" +#include "nvim/os/process.h" #include "nvim/viml/parser/expressions.h" #include "nvim/viml/parser/parser.h" #include "nvim/ui.h" @@ -1478,3 +1479,36 @@ Array nvim_list_uis(void) { return ui_array(); } + +/// Gets the immediate children of process `pid`. +/// +/// @return Array of child process ids, or empty array if process not found. +Array nvim_get_proc_children(Integer pid, Error *err) + FUNC_API_SINCE(4) +{ + Array proc_array = ARRAY_DICT_INIT; + int *proc_list = NULL; + + if (pid <= 0 || pid > INT_MAX) { + api_set_error(err, kErrorTypeException, "Invalid pid: %d", pid); + goto end; + } + + size_t proc_count; + int rv = os_proc_children((int)pid, &proc_list, &proc_count); + if (rv == 1) { + goto end; // Process not found; return empty list. + } else if (rv != 0) { + api_set_error(err, kErrorTypeException, + "Failed to get process children. pid=%d error=%d", pid, rv); + goto end; + } + + for (size_t i = 0; i < proc_count; i++) { + ADD(proc_array, INTEGER_OBJ(proc_list[i])); + } + +end: + xfree(proc_list); + return proc_array; +} -- cgit From 12af7016e23e7b7f507dc99a1b73e64d0bb5ccf3 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 14 Mar 2018 23:26:37 +0100 Subject: nvim_get_proc_children: fallback to shell MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /proc/…/children may be unavailable because of an unset kernel option. Fallback to `pgrep` invoked in a shell. --- src/nvim/api/vim.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) (limited to 'src/nvim/api/vim.c') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 850f892c18..9f1f395c28 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1482,7 +1482,7 @@ Array nvim_list_uis(void) /// Gets the immediate children of process `pid`. /// -/// @return Array of child process ids, or empty array if process not found. +/// @return Array of child process ids. Empty array if process not found. Array nvim_get_proc_children(Integer pid, Error *err) FUNC_API_SINCE(4) { @@ -1490,17 +1490,27 @@ Array nvim_get_proc_children(Integer pid, Error *err) int *proc_list = NULL; if (pid <= 0 || pid > INT_MAX) { - api_set_error(err, kErrorTypeException, "Invalid pid: %d", pid); + api_set_error(err, kErrorTypeException, "Invalid pid: %" PRId64, pid); goto end; } size_t proc_count; int rv = os_proc_children((int)pid, &proc_list, &proc_count); - if (rv == 1) { - goto end; // Process not found; return empty list. - } else if (rv != 0) { - api_set_error(err, kErrorTypeException, - "Failed to get process children. pid=%d error=%d", pid, rv); + if (rv != 0) { + // syscall failed (possibly because of kernel options), try shelling out. + Array a = ARRAY_DICT_INIT; + ADD(a, INTEGER_OBJ(pid)); + String s = cstr_to_string("return vim._os_proc_children(select(1, ...))"); + Object o = nvim_execute_lua(s, a, err); + api_free_string(s); + api_free_array(a); + if (o.type == kObjectTypeArray) { + proc_array = o.data.array; + } else if (!ERROR_SET(err)) { + api_set_error(err, kErrorTypeException, + "Failed to get process children. pid=%" PRId64 " error=%d", + pid, rv); + } goto end; } -- cgit From a034d4b69d6032b3431c10b8a11c998551700fc2 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 16 Mar 2018 05:13:38 +0100 Subject: API: nvim_get_proc() TODO: "exepath" field (win32: QueryFullProcessImageName()) On unix-likes `ps` is used because the platform-specific APIs are a nightmare. For reference, below is a (incomplete) attempt: diff --git a/src/nvim/os/process.c b/src/nvim/os/process.c index 09769925aca5..99afbbf290c1 100644 --- a/src/nvim/os/process.c +++ b/src/nvim/os/process.c @@ -208,3 +210,60 @@ int os_proc_children(int ppid, int **proc_list, size_t *proc_count) return 0; } +/// Gets various properties of the process identified by `pid`. +/// +/// @param pid Process to inspect. +/// @return Map of process properties, empty on error. +Dictionary os_proc_info(int pid) +{ + Dictionary pinfo = ARRAY_DICT_INIT; +#ifdef WIN32 + +#elif defined(__APPLE__) + char buf[PROC_PIDPATHINFO_MAXSIZE]; + if (proc_pidpath(pid, buf, sizeof(buf))) { + name = getName(buf); + PUT(pinfo, "exepath", STRING_OBJ(cstr_to_string(buf))); + return name; + } else { + ILOG("proc_pidpath() failed for pid: %d", pid); + } +#elif defined(BSD) +# if defined(__FreeBSD__) +# define KP_COMM(o) o.ki_comm +# else +# define KP_COMM(o) o.p_comm +# endif + struct kinfo_proc *proc = kinfo_getproc(pid); + if (proc) { + PUT(pinfo, "name", cstr_to_string(KP_COMM(proc))); + xfree(proc); + } else { + ILOG("kinfo_getproc() failed for pid: %d", pid); + } + +#elif defined(__linux__) + char fname[256] = { 0 }; + char buf[MAXPATHL]; + snprintf(fname, sizeof(fname), "/proc/%d/comm", pid); + FILE *fp = fopen(fname, "r"); + // FileDescriptor *f = file_open_new(&error, fname, kFileReadOnly, 0); + // ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf, + // const size_t size) + if (fp == NULL) { + ILOG("fopen() of /proc/%d/comm failed", pid); + } else { + size_t n = fread(buf, sizeof(char), sizeof(buf) - 1, fp); + if (n == 0) { + WLOG("fread() of /proc/%d/comm failed", pid); + } else { + size_t end = MIN(sizeof(buf) - 1, n); + end = (end > 0 && buf[end - 1] == '\n') ? end - 1 : end; + buf[end] = '\0'; + PUT(pinfo, "name", STRING_OBJ(cstr_to_string(buf))); + } + } + fclose(fp); +#endif + return pinfo; +} --- src/nvim/api/vim.c | 50 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 5 deletions(-) (limited to 'src/nvim/api/vim.c') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 9f1f395c28..962081cc23 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1482,11 +1482,11 @@ Array nvim_list_uis(void) /// Gets the immediate children of process `pid`. /// -/// @return Array of child process ids. Empty array if process not found. +/// @return Array of child process ids, empty if process not found. Array nvim_get_proc_children(Integer pid, Error *err) FUNC_API_SINCE(4) { - Array proc_array = ARRAY_DICT_INIT; + Array rvobj = ARRAY_DICT_INIT; int *proc_list = NULL; if (pid <= 0 || pid > INT_MAX) { @@ -1498,6 +1498,7 @@ Array nvim_get_proc_children(Integer pid, Error *err) int rv = os_proc_children((int)pid, &proc_list, &proc_count); if (rv != 0) { // syscall failed (possibly because of kernel options), try shelling out. + DLOG("fallback to vim._os_proc_children()"); Array a = ARRAY_DICT_INIT; ADD(a, INTEGER_OBJ(pid)); String s = cstr_to_string("return vim._os_proc_children(select(1, ...))"); @@ -1505,7 +1506,7 @@ Array nvim_get_proc_children(Integer pid, Error *err) api_free_string(s); api_free_array(a); if (o.type == kObjectTypeArray) { - proc_array = o.data.array; + rvobj = o.data.array; } else if (!ERROR_SET(err)) { api_set_error(err, kErrorTypeException, "Failed to get process children. pid=%" PRId64 " error=%d", @@ -1515,10 +1516,49 @@ Array nvim_get_proc_children(Integer pid, Error *err) } for (size_t i = 0; i < proc_count; i++) { - ADD(proc_array, INTEGER_OBJ(proc_list[i])); + ADD(rvobj, INTEGER_OBJ(proc_list[i])); } end: xfree(proc_list); - return proc_array; + return rvobj; +} + +/// Gets info describing process `pid`. +/// +/// @return Map of process properties, or NIL if process not found. +Object nvim_get_proc(Integer pid, Error *err) + FUNC_API_SINCE(4) +{ + Object rvobj = OBJECT_INIT; + rvobj.data.dictionary = (Dictionary)ARRAY_DICT_INIT; + rvobj.type = kObjectTypeDictionary; + + if (pid <= 0 || pid > INT_MAX) { + api_set_error(err, kErrorTypeException, "Invalid pid: %" PRId64, pid); + return NIL; + } +#ifdef WIN32 + rvobj.data.dictionary = os_proc_info((int)pid); + if (rvobj.data.dictionary.size == 0) { // Process not found. + return NIL; + } +#else + // Cross-platform process info APIs are miserable, so use `ps` instead. + Array a = ARRAY_DICT_INIT; + ADD(a, INTEGER_OBJ(pid)); + String s = cstr_to_string("return vim._os_proc_info(select(1, ...))"); + Object o = nvim_execute_lua(s, a, err); + api_free_string(s); + api_free_array(a); + if (o.type == kObjectTypeArray && o.data.array.size == 0) { + return NIL; // Process not found. + } else if (o.type == kObjectTypeDictionary) { + rvobj.data.dictionary = o.data.dictionary; + } else if (!ERROR_SET(err)) { + api_set_error(err, kErrorTypeException, + "Failed to get process info. pid=%" PRId64, pid); + } +#endif + return rvobj; } -- cgit From 1c3a849881f45e2acb08ce99e5b33dd753f61d5d Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 13 Apr 2018 00:49:37 +0200 Subject: API/nvim_command_output: handle :echon capture (#8265) ref https://github.com/neovim/python-client/pull/290 --- src/nvim/api/vim.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) (limited to 'src/nvim/api/vim.c') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 962081cc23..07ec6e8c27 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -239,15 +239,17 @@ String nvim_command_output(String command, Error *err) } if (capture_local.ga_len > 1) { - // redir always(?) prepends a newline; remove it. - char *s = capture_local.ga_data; - assert(s[0] == '\n'); - memmove(s, s + 1, (size_t)capture_local.ga_len); - s[capture_local.ga_len - 1] = '\0'; - return (String) { // Caller will free the memory. - .data = s, - .size = (size_t)(capture_local.ga_len - 1), + String s = (String){ + .data = capture_local.ga_data, + .size = (size_t)capture_local.ga_len, }; + // redir usually (except :echon) prepends a newline. + if (s.data[0] == '\n') { + memmove(s.data, s.data + 1, s.size); + s.data[s.size - 1] = '\0'; + s.size = s.size - 1; + } + return s; // Caller will free the memory. } theend: -- cgit