# HG changeset patch # User Bram Moolenaar # Date 1637668805 -3600 # Node ID 685206b54ecfb21a2f7f7544a8744464abda27a7 # Parent ae5abfa1efc84f4c686aecea8ca39aa73a873ce5 patch 8.2.3652: can only get text properties one line at a time Commit: https://github.com/vim/vim/commit/e021662f39b38ef7cf27e13850d0ce6890e48376 Author: Yegappan Lakshmanan Date: Tue Nov 23 11:46:32 2021 +0000 patch 8.2.3652: can only get text properties one line at a time Problem: Can only get text properties one line at a time. Solution: Add options to prop_list() to use a range of lines and filter by types. (Yegappan Lakshmanan, closes #9138) diff --git a/runtime/doc/textprop.txt b/runtime/doc/textprop.txt --- a/runtime/doc/textprop.txt +++ b/runtime/doc/textprop.txt @@ -1,4 +1,4 @@ -*textprop.txt* For Vim version 8.2. Last change: 2021 Aug 16 +*textprop.txt* For Vim version 8.2. Last change: 2021 Nov 23 VIM REFERENCE MANUAL by Bram Moolenaar @@ -230,13 +230,25 @@ prop_find({props} [, {direction}]) prop_list({lnum} [, {props}]) *prop_list()* - Return a List with all text properties in line {lnum}. + Returns a List with all the text properties in line {lnum}. - When {props} contains a "bufnr" item, use this buffer instead - of the current buffer. + The following optional items are supported in {props}: + bufnr use this buffer instead of the current buffer + end_lnum return text properties in all the lines + between {lnum} and {end_lnum} (inclusive). + A negative value is used as an offset from the + last buffer line; -1 refers to the last buffer + line. + types List of property type names. Return only text + properties that match one of the type names. + ids List of property identifiers. Return only text + properties with one of these identifiers. The properties are ordered by starting column and priority. Each property is a Dict with these entries: + lnum starting line number. Present only when + returning text properties between {lnum} and + {end_lnum}. col starting column length length in bytes, one more if line break is included @@ -253,6 +265,30 @@ 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. + Returns an empty list on error. + + Examples: + " get text properties placed in line 5 + echo prop_list(5) + " get text properties placed in line 20 in buffer 4 + echo prop_list(20, {'bufnr': 4}) + " get all the text properties between line 1 and 20 + echo prop_list(1, {'end_lnum': 20}) + " get all the text properties of type 'myprop' + echo prop_list(1, {'types': ['myprop'], + \ 'end_lnum': -1}) + " get all the text properties of type 'prop1' or 'prop2' + echo prop_list(1, {'types': ['prop1', 'prop2'], + \ 'end_lnum': -1}) + " get all the text properties with ID 8 + echo prop_list(1, {'ids': [8], 'end_lnum': line('$')}) + " get all the text properties with ID 10 and 20 + echo prop_list(1, {'ids': [10, 20], 'end_lnum': -1}) + " get text properties with type 'myprop' and ID 100 + " in buffer 4. + echo prop_list(1, {'bufnr': 4, 'types': ['myprop'], + \ 'ids': [100], 'end_lnum': -1}) + Can also be used as a |method|: > GetLnum()->prop_list() < 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 @@ -5,6 +5,7 @@ source check.vim CheckFeature textprop source screendump.vim +source vim9.vim func Test_proptype_global() call prop_type_add('comment', {'highlight': 'Directory', 'priority': 123, 'start_incl': 1, 'end_incl': 1}) @@ -1645,6 +1646,154 @@ def Test_prop_bufnr_zero() endtry enddef +" Tests for the prop_list() function +func Test_prop_list() + let lines =<< trim END + new + call AddPropTypes() + call setline(1, repeat([repeat('a', 60)], 10)) + call prop_add(1, 4, {'type': 'one', 'id': 5, 'end_col': 6}) + call prop_add(1, 5, {'type': 'two', 'id': 10, 'end_col': 7}) + call prop_add(3, 12, {'type': 'one', 'id': 20, 'end_col': 14}) + call prop_add(3, 13, {'type': 'two', 'id': 10, 'end_col': 15}) + call prop_add(5, 20, {'type': 'one', 'id': 10, 'end_col': 22}) + call prop_add(5, 21, {'type': 'two', 'id': 20, 'end_col': 23}) + call assert_equal([ + \ {'id': 5, 'col': 4, 'type_bufnr': 0, 'end': 1, + \ 'type': 'one', 'length': 2, 'start': 1}, + \ {'id': 10, 'col': 5, 'type_bufnr': 0, 'end': 1, + \ 'type': 'two', 'length': 2, 'start': 1}], prop_list(1)) + #" text properties between a few lines + call assert_equal([ + \ {'lnum': 3, 'id': 20, 'col': 12, 'type_bufnr': 0, 'end': 1, + \ 'type': 'one', 'length': 2, 'start': 1}, + \ {'lnum': 3, 'id': 10, 'col': 13, 'type_bufnr': 0, 'end': 1, + \ 'type': 'two', 'length': 2, 'start': 1}, + \ {'lnum': 5, 'id': 10, 'col': 20, 'type_bufnr': 0, 'end': 1, + \ 'type': 'one', 'length': 2, 'start': 1}, + \ {'lnum': 5, 'id': 20, 'col': 21, 'type_bufnr': 0, 'end': 1, + \ 'type': 'two', 'length': 2, 'start': 1}], + \ prop_list(2, {'end_lnum': 5})) + #" text properties across all the lines + call assert_equal([ + \ {'lnum': 1, 'id': 5, 'col': 4, 'type_bufnr': 0, 'end': 1, + \ 'type': 'one', 'length': 2, 'start': 1}, + \ {'lnum': 3, 'id': 20, 'col': 12, 'type_bufnr': 0, 'end': 1, + \ 'type': 'one', 'length': 2, 'start': 1}, + \ {'lnum': 5, 'id': 10, 'col': 20, 'type_bufnr': 0, 'end': 1, + \ 'type': 'one', 'length': 2, 'start': 1}], + \ prop_list(1, {'types': ['one'], 'end_lnum': -1})) + #" text properties with the specified identifier + call assert_equal([ + \ {'lnum': 3, 'id': 20, 'col': 12, 'type_bufnr': 0, 'end': 1, + \ 'type': 'one', 'length': 2, 'start': 1}, + \ {'lnum': 5, 'id': 20, 'col': 21, 'type_bufnr': 0, 'end': 1, + \ 'type': 'two', 'length': 2, 'start': 1}], + \ prop_list(1, {'ids': [20], 'end_lnum': 10})) + #" text properties of the specified type and id + call assert_equal([ + \ {'lnum': 1, 'id': 10, 'col': 5, 'type_bufnr': 0, 'end': 1, + \ 'type': 'two', 'length': 2, 'start': 1}, + \ {'lnum': 3, 'id': 10, 'col': 13, 'type_bufnr': 0, 'end': 1, + \ 'type': 'two', 'length': 2, 'start': 1}], + \ prop_list(1, {'types': ['two'], 'ids': [10], 'end_lnum': 20})) + call assert_equal([], prop_list(1, {'ids': [40, 50], 'end_lnum': 10})) + call assert_equal([], prop_list(6, {'end_lnum': 10})) + call assert_equal([], prop_list(2, {'end_lnum': 2})) + #" error cases + call assert_fails("echo prop_list(1, {'end_lnum': -20})", 'E16:') + call assert_fails("echo prop_list(4, {'end_lnum': 2})", 'E16:') + call assert_fails("echo prop_list(1, {'end_lnum': '$'})", 'E889:') + call assert_fails("echo prop_list(1, {'types': ['blue'], 'end_lnum': 10})", + \ 'E971:') + call assert_fails("echo prop_list(1, {'types': ['one', 'blue'], + \ 'end_lnum': 10})", 'E971:') + call assert_fails("echo prop_list(1, {'types': ['one', 10], + \ 'end_lnum': 10})", 'E928:') + call assert_fails("echo prop_list(1, {'types': ['']})", 'E971:') + call assert_equal([], prop_list(2, {'types': []})) + call assert_equal([], prop_list(2, {'types': test_null_list()})) + call assert_fails("call prop_list(1, {'types': {}})", 'E714:') + call assert_fails("call prop_list(1, {'types': 'one'})", 'E714:') + call assert_equal([], prop_list(2, {'types': ['one'], + \ 'ids': test_null_list()})) + call assert_equal([], prop_list(2, {'types': ['one'], 'ids': []})) + call assert_fails("call prop_list(1, {'types': ['one'], 'ids': {}})", + \ 'E714:') + call assert_fails("call prop_list(1, {'types': ['one'], 'ids': 10})", + \ 'E714:') + call assert_fails("call prop_list(1, {'types': ['one'], 'ids': [[]]})", + \ 'E745:') + call assert_fails("call prop_list(1, {'types': ['one'], 'ids': [10, []]})", + \ 'E745:') + #" get text properties from a non-current buffer + wincmd w + call assert_equal([ + \ {'lnum': 1, 'id': 5, 'col': 4, 'type_bufnr': 0, 'end': 1, + \ 'type': 'one', 'length': 2, 'start': 1}, + \ {'lnum': 1, 'id': 10, 'col': 5, 'type_bufnr': 0, 'end': 1, + \ 'type': 'two', 'length': 2, 'start': 1}, + \ {'lnum': 3, 'id': 20, 'col': 12, 'type_bufnr': 0, 'end': 1, + \ 'type': 'one', 'length': 2, 'start': 1}, + \ {'lnum': 3, 'id': 10, 'col': 13, 'type_bufnr': 0, 'end': 1, + \ 'type': 'two', 'length': 2, 'start': 1}], + \ prop_list(1, {'bufnr': winbufnr(1), 'end_lnum': 4})) + wincmd w + + #" get text properties after clearing all the properties + call prop_clear(1, line('$')) + call assert_equal([], prop_list(1, {'end_lnum': 10})) + + call prop_add(2, 4, {'type': 'one', 'id': 5, 'end_col': 6}) + call prop_add(2, 4, {'type': 'two', 'id': 10, 'end_col': 6}) + call prop_add(2, 4, {'type': 'three', 'id': 15, 'end_col': 6}) + #" get text properties with a list of types + call assert_equal([ + \ {'id': 10, 'col': 4, 'type_bufnr': 0, 'end': 1, + \ 'type': 'two', 'length': 2, 'start': 1}, + \ {'id': 5, 'col': 4, 'type_bufnr': 0, 'end': 1, + \ 'type': 'one', 'length': 2, 'start': 1}], + \ prop_list(2, {'types': ['one', 'two']})) + call assert_equal([ + \ {'id': 15, 'col': 4, 'type_bufnr': 0, 'end': 1, + \ 'type': 'three', 'length': 2, 'start': 1}, + \ {'id': 5, 'col': 4, 'type_bufnr': 0, 'end': 1, + \ 'type': 'one', 'length': 2, 'start': 1}], + \ prop_list(2, {'types': ['one', 'three']})) + #" get text properties with a list of identifiers + call assert_equal([ + \ {'id': 10, 'col': 4, 'type_bufnr': 0, 'end': 1, + \ 'type': 'two', 'length': 2, 'start': 1}, + \ {'id': 5, 'col': 4, 'type_bufnr': 0, 'end': 1, + \ 'type': 'one', 'length': 2, 'start': 1}], + \ prop_list(2, {'ids': [5, 10, 20]})) + call prop_clear(1, line('$')) + call assert_equal([], prop_list(2, {'types': ['one', 'two']})) + call assert_equal([], prop_list(2, {'ids': [5, 10, 20]})) + + #" get text properties from a hidden buffer + edit! Xaaa + call setline(1, repeat([repeat('b', 60)], 10)) + call prop_add(1, 4, {'type': 'one', 'id': 5, 'end_col': 6}) + call prop_add(4, 8, {'type': 'two', 'id': 10, 'end_col': 10}) + VAR bnr = bufnr() + hide edit Xbbb + call assert_equal([ + \ {'lnum': 1, 'id': 5, 'col': 4, 'type_bufnr': 0, 'end': 1, + \ 'type': 'one', 'length': 2, 'start': 1}, + \ {'lnum': 4, 'id': 10, 'col': 8, 'type_bufnr': 0, 'end': 1, + \ 'type': 'two', 'length': 2, 'start': 1}], + \ prop_list(1, {'bufnr': bnr, + \ 'types': ['one', 'two'], 'ids': [5, 10], 'end_lnum': -1})) + #" get text properties from an unloaded buffer + bunload! Xaaa + call assert_equal([], prop_list(1, {'bufnr': bnr, 'end_lnum': -1})) + + call DeletePropTypes() + :%bw! + END + call CheckLegacyAndVim9Success(lines) +endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/textprop.c b/src/textprop.c --- a/src/textprop.c +++ b/src/textprop.c @@ -897,52 +897,259 @@ f_prop_find(typval_T *argvars, typval_T } /* + * Returns TRUE if 'type_or_id' is in the 'types_or_ids' list. + */ + static int +prop_type_or_id_in_list(int *types_or_ids, int len, int type_or_id) +{ + int i; + + for (i = 0; i < len; i++) + if (types_or_ids[i] == type_or_id) + return TRUE; + + return FALSE; +} + +/* + * Return all the text properties in line 'lnum' in buffer 'buf' in 'retlist'. + * If 'prop_types' is not NULL, then return only the text properties with + * matching property type in the 'prop_types' array. + * If 'prop_ids' is not NULL, then return only the text properties with + * an identifier in the 'props_ids' array. + * If 'add_lnum' is TRUE, then add the line number also to the text property + * dictionary. + */ + static void +get_props_in_line( + buf_T *buf, + linenr_T lnum, + int *prop_types, + int prop_types_len, + int *prop_ids, + int prop_ids_len, + list_T *retlist, + int add_lnum) +{ + char_u *text = ml_get_buf(buf, lnum, FALSE); + size_t textlen = STRLEN(text) + 1; + int count; + int i; + textprop_T prop; + + count = (int)((buf->b_ml.ml_line_len - textlen) / sizeof(textprop_T)); + for (i = 0; i < count; ++i) + { + mch_memmove(&prop, text + textlen + i * sizeof(textprop_T), + sizeof(textprop_T)); + if ((prop_types == NULL + || prop_type_or_id_in_list(prop_types, prop_types_len, + prop.tp_type)) + && (prop_ids == NULL || + prop_type_or_id_in_list(prop_ids, prop_ids_len, + prop.tp_id))) + { + dict_T *d = dict_alloc(); + + if (d == NULL) + break; + prop_fill_dict(d, &prop, buf); + if (add_lnum) + dict_add_number(d, "lnum", lnum); + list_append_dict(retlist, d); + } + } +} + +/* + * Convert a List of property type names into an array of property type + * identifiers. Returns a pointer to the allocated array. Returns NULL on + * error. 'num_types' is set to the number of returned property types. + */ + static int * +get_prop_types_from_names(list_T *l, buf_T *buf, int *num_types) +{ + int *prop_types; + listitem_T *li; + int i; + char_u *name; + proptype_T *type; + + *num_types = 0; + + prop_types = ALLOC_MULT(int, list_len(l)); + if (prop_types == NULL) + return NULL; + + i = 0; + FOR_ALL_LIST_ITEMS(l, li) + { + if (li->li_tv.v_type != VAR_STRING) + { + emsg(_(e_stringreq)); + goto errret; + } + name = li->li_tv.vval.v_string; + if (name == NULL) + goto errret; + + type = lookup_prop_type(name, buf); + if (type == NULL) + goto errret; + prop_types[i++] = type->pt_id; + } + + *num_types = i; + return prop_types; + +errret: + VIM_CLEAR(prop_types); + return NULL; +} + +/* + * Convert a List of property identifiers into an array of property + * identifiers. Returns a pointer to the allocated array. Returns NULL on + * error. 'num_ids' is set to the number of returned property identifiers. + */ + static int * +get_prop_ids_from_list(list_T *l, int *num_ids) +{ + int *prop_ids; + listitem_T *li; + int i; + int id; + int error; + + *num_ids = 0; + + prop_ids = ALLOC_MULT(int, list_len(l)); + if (prop_ids == NULL) + return NULL; + + i = 0; + FOR_ALL_LIST_ITEMS(l, li) + { + error = FALSE; + id = tv_get_number_chk(&li->li_tv, &error); + if (error) + goto errret; + + prop_ids[i++] = id; + } + + *num_ids = i; + return prop_ids; + +errret: + VIM_CLEAR(prop_ids); + return NULL; +} + +/* * prop_list({lnum} [, {bufnr}]) */ void f_prop_list(typval_T *argvars, typval_T *rettv) { - linenr_T lnum; - buf_T *buf = curbuf; + linenr_T lnum; + linenr_T start_lnum; + linenr_T end_lnum; + buf_T *buf = curbuf; + int add_lnum = FALSE; + int *prop_types = NULL; + int prop_types_len = 0; + int *prop_ids = NULL; + int prop_ids_len = 0; + list_T *l; + dictitem_T *di; if (in_vim9script() && (check_for_number_arg(argvars, 0) == FAIL || check_for_opt_dict_arg(argvars, 1) == FAIL)) return; - lnum = tv_get_number(&argvars[0]); + if (rettv_list_alloc(rettv) != OK) + return; + + // default: get text properties on current line + start_lnum = tv_get_number(&argvars[0]); + end_lnum = start_lnum; if (argvars[1].v_type != VAR_UNKNOWN) { + dict_T *d; + + if (argvars[1].v_type != VAR_DICT) + { + emsg(_(e_dictreq)); + return; + } + d = argvars[1].vval.v_dict; + if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL) return; - } - if (lnum < 1 || lnum > buf->b_ml.ml_line_count) - { - emsg(_(e_invalid_range)); - return; - } - if (rettv_list_alloc(rettv) == OK) - { - 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; + if (d != NULL && (di = dict_find(d, (char_u *)"end_lnum", -1)) != NULL) + { + if (di->di_tv.v_type != VAR_NUMBER) + { + emsg(_(e_numberreq)); + return; + } + end_lnum = tv_get_number(&di->di_tv); + if (end_lnum < 0) + // negative end_lnum is used as an offset from the last buffer + // line + end_lnum = buf->b_ml.ml_line_count + end_lnum + 1; + else if (end_lnum > buf->b_ml.ml_line_count) + end_lnum = buf->b_ml.ml_line_count; + add_lnum = TRUE; + } + if (d != NULL && (di = dict_find(d, (char_u *)"types", -1)) != NULL) + { + if (di->di_tv.v_type != VAR_LIST) + { + emsg(_(e_listreq)); + return; + } - for (i = 0; i < count; ++i) + l = di->di_tv.vval.v_list; + if (l != NULL && list_len(l) > 0) + { + prop_types = get_prop_types_from_names(l, buf, &prop_types_len); + if (prop_types == NULL) + return; + } + } + if (d != NULL && (di = dict_find(d, (char_u *)"ids", -1)) != NULL) { - dict_T *d = dict_alloc(); + if (di->di_tv.v_type != VAR_LIST) + { + emsg(_(e_listreq)); + goto errret; + } - if (d == NULL) - 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); + l = di->di_tv.vval.v_list; + if (l != NULL && list_len(l) > 0) + { + prop_ids = get_prop_ids_from_list(l, &prop_ids_len); + if (prop_ids == NULL) + goto errret; + } } } + if (start_lnum < 1 || start_lnum > buf->b_ml.ml_line_count + || end_lnum < 1 || end_lnum < start_lnum) + emsg(_(e_invalid_range)); + else + for (lnum = start_lnum; lnum <= end_lnum; lnum++) + get_props_in_line(buf, lnum, prop_types, prop_types_len, + prop_ids, prop_ids_len, + rettv->vval.v_list, add_lnum); + +errret: + VIM_CLEAR(prop_types); + VIM_CLEAR(prop_ids); } /* diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -758,6 +758,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 3652, +/**/ 3651, /**/ 3650,