# HG changeset patch # User Bram Moolenaar # Date 1578682804 -3600 # Node ID 91bb1299503470a40d2c71c3acf0f91ffff7f79f # Parent 1a951a4beee3f9c17995dfd240f61060cd2df9c8 patch 8.2.0110: prop_find() is not implemented Commit: https://github.com/vim/vim/commit/e05a89ac6399a8c7d164c99fdab6841d999a9128 Author: Bram Moolenaar Date: Fri Jan 10 19:56:46 2020 +0100 patch 8.2.0110: prop_find() is not implemented Problem: prop_find() is not implemented. Solution: Implement prop_find(). (Ryan Hackett, closes https://github.com/vim/vim/issues/5421, closes https://github.com/vim/vim/issues/4970) diff --git a/runtime/doc/textprop.txt b/runtime/doc/textprop.txt --- a/runtime/doc/textprop.txt +++ b/runtime/doc/textprop.txt @@ -154,8 +154,6 @@ prop_add({lnum}, {col}, {props}) added to. When not found, the global property types are used. If not found an error is given. - See |text-properties| for information about text properties. - Can also be used as a |method|: > GetLnum()->prop_add(col, props) @@ -168,14 +166,11 @@ prop_clear({lnum} [, {lnum-end} [, {prop When {props} contains a "bufnr" item use this buffer, otherwise use the current buffer. - See |text-properties| for information about text properties. - Can also be used as a |method|: > GetLnum()->prop_clear() < *prop_find()* prop_find({props} [, {direction}]) - {not implemented yet} Search for a text property as specified with {props}: id property with this ID type property with this type name @@ -198,8 +193,6 @@ prop_find({props} [, {direction}]) as with prop_list(), and additionally an "lnum" entry. If no match is found then an empty Dict is returned. - See |text-properties| for information about text properties. - prop_list({lnum} [, {props}]) *prop_list()* Return a List with all text properties in line {lnum}. @@ -223,8 +216,6 @@ prop_list({lnum} [, {props}]) *prop_l When "end" is zero the property continues in the next line. The line break after this line is included. - See |text-properties| for information about text properties. - Can also be used as a |method|: > GetLnum()->prop_list() < @@ -248,8 +239,6 @@ prop_remove({props} [, {lnum} [, {lnum-e Returns the number of properties that were removed. - See |text-properties| for information about text properties. - Can also be used as a |method|: > GetProps()->prop_remove() @@ -275,8 +264,6 @@ prop_type_add({name}, {props}) *prop_ty end_incl when TRUE inserts at the end position will be included in the text property - See |text-properties| for information about text properties. - Can also be used as a |method|: > GetPropName()->prop_type_add(props) @@ -285,8 +272,6 @@ prop_type_change({name}, {props}) *pro property with this name does not exist an error is given. The {props} argument is just like |prop_type_add()|. - See |text-properties| for information about text properties. - Can also be used as a |method|: > GetPropName()->prop_type_change(props) @@ -301,8 +286,6 @@ prop_type_delete({name} [, {props}]) * When text property type {name} is not found there is no error. - See |text-properties| for information about text properties. - Can also be used as a |method|: > GetPropName()->prop_type_delete() @@ -316,8 +299,6 @@ prop_type_get([{name} [, {props}]) *pr {props} can contain a "bufnr" item. When it is given, use this buffer instead of the global property types. - See |text-properties| for information about text properties. - Can also be used as a |method|: > GetPropName()->prop_type_get() @@ -327,8 +308,6 @@ prop_type_list([{props}]) *prop_type_ {props} can contain a "bufnr" item. When it is given, use this buffer instead of the global property types. - See |text-properties| for information about text properties. - ============================================================================== 3. When text changes *text-prop-changes* diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -620,6 +620,7 @@ static funcentry_T global_functions[] = #ifdef FEAT_PROP_POPUP {"prop_add", 3, 3, FEARG_1, f_prop_add}, {"prop_clear", 1, 3, FEARG_1, f_prop_clear}, + {"prop_find", 1, 2, FEARG_1, f_prop_find}, {"prop_list", 1, 2, FEARG_1, f_prop_list}, {"prop_remove", 1, 3, FEARG_1, f_prop_remove}, {"prop_type_add", 2, 2, FEARG_1, f_prop_type_add}, diff --git a/src/proto/textprop.pro b/src/proto/textprop.pro --- a/src/proto/textprop.pro +++ b/src/proto/textprop.pro @@ -6,6 +6,7 @@ int get_text_props(buf_T *buf, linenr_T int find_visible_prop(win_T *wp, int type_id, int id, textprop_T *prop, linenr_T *found_lnum); proptype_T *text_prop_type_by_id(buf_T *buf, int id); void f_prop_clear(typval_T *argvars, typval_T *rettv); +void f_prop_find(typval_T *argvars, typval_T *rettv); void f_prop_list(typval_T *argvars, typval_T *rettv); void f_prop_remove(typval_T *argvars, typval_T *rettv); void f_prop_type_add(typval_T *argvars, typval_T *rettv); diff --git a/src/testdir/test_textprop.vim b/src/testdir/test_textprop.vim --- a/src/testdir/test_textprop.vim +++ b/src/testdir/test_textprop.vim @@ -103,6 +103,118 @@ func Get_expected_props() \ ] endfunc +func Test_prop_find() + new + call setline(1, ['one one one', 'twotwo', 'three', 'fourfour', 'five', 'sixsix']) + + " Add two text props on lines 1 and 5, and one spanning lines 2 to 4. + call prop_type_add('prop_name', {'highlight': 'Directory'}) + call prop_add(1, 5, {'type': 'prop_name', 'id': 10, 'length': 3}) + call prop_add(2, 4, {'type': 'prop_name', 'id': 11, 'end_lnum': 4, 'end_col': 9}) + call prop_add(5, 4, {'type': 'prop_name', 'id': 12, 'length': 1}) + + let expected = [ + \ {'lnum': 1, 'col': 5, 'length': 3, 'id': 10, 'type': 'prop_name', 'start': 1, 'end': 1}, + \ {'lnum': 2, 'col': 4, 'id': 11, 'type': 'prop_name', 'start': 1, 'end': 0}, + \ {'lnum': 5, 'col': 4, 'length': 1, 'id': 12, 'type': 'prop_name', 'start': 1, 'end': 1} + \ ] + + " Starting at line 5 col 1 this should find the prop at line 5 col 4. + call cursor(5,1) + let result = prop_find({'type': 'prop_name'}, 'f') + call assert_equal(expected[2], result) + + " With skipstart left at false (default), this should find the prop at line + " 5 col 4. + let result = prop_find({'type': 'prop_name', 'lnum': 5, 'col': 4}, 'b') + call assert_equal(expected[2], result) + + " With skipstart set to true, this should skip the prop at line 5 col 4. + let result = prop_find({'type': 'prop_name', 'lnum': 5, 'col': 4, 'skipstart': 1}, 'b') + unlet result.length + call assert_equal(expected[1], result) + + " Search backwards from line 1 col 10 to find the prop on the same line. + let result = prop_find({'type': 'prop_name', 'lnum': 1, 'col': 10}, 'b') + call assert_equal(expected[0], result) + + " with skipstart set to false, if the start position is anywhere between the + " start and end lines of a text prop (searching forward or backward), the + " result should be the prop on the first line (the line with 'start' set to 1). + call cursor(3,1) + let result = prop_find({'type': 'prop_name'}, 'f') + unlet result.length + call assert_equal(expected[1], result) + let result = prop_find({'type': 'prop_name'}, 'b') + unlet result.length + call assert_equal(expected[1], result) + + " with skipstart set to true, if the start position is anywhere between the + " start and end lines of a text prop (searching forward or backward), all lines + " of the prop will be skipped. + let result = prop_find({'type': 'prop_name', 'skipstart': 1}, 'b') + call assert_equal(expected[0], result) + let result = prop_find({'type': 'prop_name', 'skipstart': 1}, 'f') + call assert_equal(expected[2], result) + + " Use skipstart to search through all props with type name 'prop_name'. + " First forward... + let lnum = 1 + let col = 1 + let i = 0 + for exp in expected + let result = prop_find({'type': 'prop_name', 'lnum': lnum, 'col': col, 'skipstart': 1}, 'f') + if !has_key(exp, "length") + unlet result.length + endif + call assert_equal(exp, result) + let lnum = result.lnum + let col = result.col + let i = i + 1 + endfor + + " ...then backwards. + let lnum = 6 + let col = 4 + let i = 2 + while i >= 0 + let result = prop_find({'type': 'prop_name', 'lnum': lnum, 'col': col, 'skipstart': 1}, 'b') + if !has_key(expected[i], "length") + unlet result.length + endif + call assert_equal(expected[i], result) + let lnum = result.lnum + let col = result.col + let i = i - 1 + endwhile + + " Starting from line 6 col 1 search backwards for prop with id 10. + call cursor(6,1) + let result = prop_find({'id': 10, 'skipstart': 1}, 'b') + call assert_equal(expected[0], result) + + " Starting from line 1 col 1 search forwards for prop with id 12. + call cursor(1,1) + let result = prop_find({'id': 12}, 'f') + call assert_equal(expected[2], result) + + " Search for a prop with an unknown id. + let result = prop_find({'id': 999}, 'f') + call assert_equal({}, result) + + " Search backwards from the proceeding position of the prop with id 11 + " (at line num 2 col 4). This should return an empty dict. + let result = prop_find({'id': 11, 'lnum': 2, 'col': 3}, 'b') + call assert_equal({}, result) + + " When lnum is given and col is omitted, use column 1. + let result = prop_find({'type': 'prop_name', 'lnum': 1}, 'f') + call assert_equal(expected[0], result) + + call prop_clear(1,6) + call prop_type_delete('prop_name') +endfunc + func Test_prop_add() new call AddPropTypes() diff --git a/src/textprop.c b/src/textprop.c --- a/src/textprop.c +++ b/src/textprop.c @@ -469,6 +469,24 @@ find_type_by_id(hashtab_T *ht, int id) } /* + * Fill 'dict' with text properties in 'prop'. + */ + static void +prop_fill_dict(dict_T *dict, textprop_T *prop, buf_T *buf) +{ + proptype_T *pt; + + dict_add_number(dict, "col", prop->tp_col); + dict_add_number(dict, "length", prop->tp_len); + dict_add_number(dict, "id", prop->tp_id); + dict_add_number(dict, "start", !(prop->tp_flags & TP_FLAG_CONT_PREV)); + dict_add_number(dict, "end", !(prop->tp_flags & TP_FLAG_CONT_NEXT)); + pt = text_prop_type_by_id(buf, prop->tp_type); + if (pt != NULL) + dict_add_string(dict, "type", pt->pt_name); +} + +/* * Find a property type by ID in "buf" or globally. * Returns NULL if not found. */ @@ -537,6 +555,190 @@ f_prop_clear(typval_T *argvars, typval_T } /* + * prop_find({props} [, {direction}]) + */ + void +f_prop_find(typval_T *argvars, typval_T *rettv) +{ + pos_T *cursor = &curwin->w_cursor; + dict_T *dict; + buf_T *buf = curbuf; + dictitem_T *di; + int lnum_start; + int start_pos_has_prop = 0; + int seen_end = 0; + int id = -1; + int type_id = -1; + int skipstart = 0; + int lnum = -1; + int col = -1; + int dir = 1; // 1 = forward, -1 = backward + + if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL) + { + emsg(_(e_invarg)); + return; + } + dict = argvars[0].vval.v_dict; + + if (get_bufnr_from_arg(&argvars[0], &buf) == FAIL) + return; + if (buf->b_ml.ml_mfp == NULL) + return; + + if (argvars[1].v_type != VAR_UNKNOWN) + { + char_u *dir_s = tv_get_string(&argvars[1]); + + if (*dir_s == 'b') + dir = -1; + else if (*dir_s != 'f') + { + emsg(_(e_invarg)); + return; + } + } + + di = dict_find(dict, (char_u *)"lnum", -1); + if (di != NULL) + lnum = tv_get_number(&di->di_tv); + + di = dict_find(dict, (char_u *)"col", -1); + if (di != NULL) + col = tv_get_number(&di->di_tv); + + if (lnum == -1) + { + lnum = cursor->lnum; + col = cursor->col + 1; + } + else if (col == -1) + col = 1; + + if (lnum < 1 || lnum > buf->b_ml.ml_line_count) + { + emsg(_(e_invrange)); + return; + } + + di = dict_find(dict, (char_u *)"skipstart", -1); + if (di != NULL) + skipstart = tv_get_number(&di->di_tv); + + if (dict_find(dict, (char_u *)"id", -1) != NULL) + id = dict_get_number(dict, (char_u *)"id"); + if (dict_find(dict, (char_u *)"type", -1)) + { + char_u *name = dict_get_string(dict, (char_u *)"type", FALSE); + proptype_T *type = lookup_prop_type(name, buf); + + if (type == NULL) + return; + type_id = type->pt_id; + } + if (id == -1 && type_id == -1) + { + emsg(_("E968: Need at least one of 'id' or 'type'")); + return; + } + + lnum_start = lnum; + + if (rettv_dict_alloc(rettv) == FAIL) + return; + + while (1) + { + char_u *text = ml_get_buf(buf, lnum, FALSE); + size_t textlen = STRLEN(text) + 1; + int count = (int)((buf->b_ml.ml_line_len - textlen) + / sizeof(textprop_T)); + int i; + textprop_T prop; + int prop_start; + int prop_end; + + for (i = 0; i < count; ++i) + { + mch_memmove(&prop, text + textlen + i * sizeof(textprop_T), + sizeof(textprop_T)); + + if (prop.tp_id == id || prop.tp_type == type_id) + { + // Check if the starting position has text props. + if (lnum_start == lnum) + { + if (col >= prop.tp_col + && (col <= prop.tp_col + prop.tp_len-1)) + start_pos_has_prop = 1; + } + else + { + // Not at the first line of the search so adjust col to + // indicate that we're continuing from prev/next line. + if (dir < 0) + col = buf->b_ml.ml_line_len; + else + col = 1; + } + + prop_start = !(prop.tp_flags & TP_FLAG_CONT_PREV); + prop_end = !(prop.tp_flags & TP_FLAG_CONT_NEXT); + if (!prop_start && prop_end && dir > 0) + seen_end = 1; + + // Skip lines without the start flag. + if (!prop_start) + { + // Always search backwards for start when search started + // on a prop and we're not skipping. + if (start_pos_has_prop && !skipstart) + dir = -1; + break; + } + + // If skipstart is true, skip the prop at start pos (even if + // continued from another line). + if (start_pos_has_prop && skipstart && !seen_end) + { + start_pos_has_prop = 0; + break; + } + + if (dir < 0) + { + if (col < prop.tp_col) + break; + } + else + { + if (col > prop.tp_col + prop.tp_len-1) + break; + } + + prop_fill_dict(rettv->vval.v_dict, &prop, buf); + dict_add_number(rettv->vval.v_dict, "lnum", lnum); + + return; + } + } + + if (dir > 0) + { + if (lnum >= buf->b_ml.ml_line_count) + break; + lnum++; + } + else + { + if (lnum <= 1) + break; + lnum--; + } + } +} + +/* * prop_list({lnum} [, {bufnr}]) */ void @@ -564,7 +766,6 @@ f_prop_list(typval_T *argvars, typval_T / sizeof(textprop_T)); int i; textprop_T prop; - proptype_T *pt; for (i = 0; i < count; ++i) { @@ -574,15 +775,7 @@ f_prop_list(typval_T *argvars, typval_T break; mch_memmove(&prop, text + textlen + i * sizeof(textprop_T), sizeof(textprop_T)); - dict_add_number(d, "col", prop.tp_col); - dict_add_number(d, "length", prop.tp_len); - dict_add_number(d, "id", prop.tp_id); - dict_add_number(d, "start", !(prop.tp_flags & TP_FLAG_CONT_PREV)); - dict_add_number(d, "end", !(prop.tp_flags & TP_FLAG_CONT_NEXT)); - pt = text_prop_type_by_id(buf, prop.tp_type); - if (pt != NULL) - dict_add_string(d, "type", pt->pt_name); - + prop_fill_dict(d, &prop, buf); list_append_dict(rettv->vval.v_list, d); } } diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -743,6 +743,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 110, +/**/ 109, /**/ 108,