To: vim_dev@googlegroups.com Subject: Patch 8.2.0110 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.0110 Problem: prop_find() is not implemented. Solution: Implement prop_find(). (Ryan Hackett, closes #5421, closes #4970) Files: src/evalfunc.c, src/proto/textprop.pro, src/testdir/test_textprop.vim, src/textprop.c, runtime/doc/textprop.txt *** ../vim-8.2.0109/src/evalfunc.c 2020-01-08 19:32:14.970527443 +0100 --- src/evalfunc.c 2020-01-10 19:51:35.852314531 +0100 *************** *** 620,625 **** --- 620,626 ---- #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}, *** ../vim-8.2.0109/src/proto/textprop.pro 2019-12-12 12:55:35.000000000 +0100 --- src/proto/textprop.pro 2020-01-10 18:56:51.600928100 +0100 *************** *** 6,11 **** --- 6,12 ---- 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); *** ../vim-8.2.0109/src/testdir/test_textprop.vim 2020-01-09 21:35:44.912474257 +0100 --- src/testdir/test_textprop.vim 2020-01-10 18:56:51.600928100 +0100 *************** *** 103,108 **** --- 103,220 ---- \ ] 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() *** ../vim-8.2.0109/src/textprop.c 2020-01-04 16:13:44.408524644 +0100 --- src/textprop.c 2020-01-10 19:55:37.263273898 +0100 *************** *** 469,474 **** --- 469,492 ---- } /* + * 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,542 **** --- 555,744 ---- } /* + * 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,570 **** / sizeof(textprop_T)); int i; textprop_T prop; - proptype_T *pt; for (i = 0; i < count; ++i) { --- 766,771 ---- *************** *** 574,588 **** 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); ! list_append_dict(rettv->vval.v_list, d); } } --- 775,781 ---- break; mch_memmove(&prop, text + textlen + i * sizeof(textprop_T), sizeof(textprop_T)); ! prop_fill_dict(d, &prop, buf); list_append_dict(rettv->vval.v_list, d); } } *** ../vim-8.2.0109/runtime/doc/textprop.txt 2019-12-12 12:49:06.000000000 +0100 --- runtime/doc/textprop.txt 2020-01-10 19:00:09.236227590 +0100 *************** *** 154,161 **** 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) --- 154,159 ---- *************** *** 168,181 **** 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 --- 166,176 ---- *************** *** 198,205 **** 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}. --- 193,198 ---- *************** *** 223,230 **** 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() < --- 216,221 ---- *************** *** 248,255 **** 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() --- 239,244 ---- *************** *** 275,282 **** 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) --- 264,269 ---- *************** *** 285,292 **** 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) --- 272,277 ---- *************** *** 301,308 **** 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() --- 286,291 ---- *************** *** 316,323 **** {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() --- 299,304 ---- *************** *** 327,334 **** {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* --- 308,313 ---- *** ../vim-8.2.0109/src/version.c 2020-01-09 21:35:44.912474257 +0100 --- src/version.c 2020-01-10 18:58:23.020538339 +0100 *************** *** 744,745 **** --- 744,747 ---- { /* Add new patch number below this line */ + /**/ + 110, /**/ -- "Software is like sex... it's better when it's free." -- Linus Torvalds, initiator of the free Linux OS Makes me wonder what FSF stands for...? /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///