To: vim_dev@googlegroups.com Subject: Patch 8.2.3652 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.3652 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) Files: runtime/doc/textprop.txt, src/textprop.c, src/testdir/test_textprop.vim *** ../vim-8.2.3651/runtime/doc/textprop.txt 2021-09-17 20:06:53.120560956 +0100 --- runtime/doc/textprop.txt 2021-11-23 11:38:46.587158933 +0000 *************** *** 230,242 **** prop_list({lnum} [, {props}]) *prop_list()* ! Return a List with all text properties in line {lnum}. ! When {props} contains a "bufnr" item, use this buffer instead ! of the current buffer. The properties are ordered by starting column and priority. Each property is a Dict with these entries: col starting column length length in bytes, one more if line break is included --- 230,254 ---- prop_list({lnum} [, {props}]) *prop_list()* ! Returns a List with all the text properties in line {lnum}. ! 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,258 **** --- 265,294 ---- 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() < *************** *** 262,268 **** {lnum-end} is given, remove matching text properties from line {lnum} to {lnum-end} (inclusive). When {lnum} is omitted remove matching text properties from ! all lines. {props} is a dictionary with these fields: id remove text properties with this ID --- 298,305 ---- {lnum-end} is given, remove matching text properties from line {lnum} to {lnum-end} (inclusive). When {lnum} is omitted remove matching text properties from ! all lines (this requires going over all lines, thus will be a ! bit slow for a buffer with many lines). {props} is a dictionary with these fields: id remove text properties with this ID *************** *** 294,301 **** properties the one with the highest priority will be used; negative values can be used, the default priority is zero ! combine when TRUE combine the highlight with any ! syntax highlight; when omitted or FALSE syntax highlight will not be used start_incl when TRUE inserts at the start position will be included in the text property --- 331,338 ---- properties the one with the highest priority will be used; negative values can be used, the default priority is zero ! combine when omitted or TRUE combine the highlight ! with any syntax highlight; when FALSE syntax highlight will not be used start_incl when TRUE inserts at the start position will be included in the text property *** ../vim-8.2.3651/src/textprop.c 2021-08-16 20:38:38.131122584 +0100 --- src/textprop.c 2021-11-23 11:41:05.338750937 +0000 *************** *** 897,948 **** } /* * prop_list({lnum} [, {bufnr}]) */ void f_prop_list(typval_T *argvars, typval_T *rettv) { ! linenr_T lnum; ! buf_T *buf = curbuf; 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 (argvars[1].v_type != VAR_UNKNOWN) { 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; ! for (i = 0; i < count; ++i) { ! dict_T *d = dict_alloc(); ! 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); } } } /* --- 897,1155 ---- } /* + * 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; ! 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; ! 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 (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; ! } ! 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) { ! if (di->di_tv.v_type != VAR_LIST) ! { ! emsg(_(e_listreq)); ! goto errret; ! } ! 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); } /* *** ../vim-8.2.3651/src/testdir/test_textprop.vim 2021-08-25 16:01:57.132832684 +0100 --- src/testdir/test_textprop.vim 2021-11-23 11:34:52.611847493 +0000 *************** *** 5,10 **** --- 5,11 ---- 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,1650 **** --- 1646,1799 ---- 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 *** ../vim-8.2.3651/src/version.c 2021-11-22 21:58:37.918668436 +0000 --- src/version.c 2021-11-23 11:36:52.087495811 +0000 *************** *** 759,760 **** --- 759,762 ---- { /* Add new patch number below this line */ + /**/ + 3652, /**/ -- Every person is responsible for the choices he makes. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///