0
0
mirror of https://github.com/vim/vim.git synced 2025-09-05 21:43:39 -04:00

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)
This commit is contained in:
Yegappan Lakshmanan 2021-11-23 11:46:32 +00:00 committed by Bram Moolenaar
parent 04b568b38f
commit e021662f39
4 changed files with 424 additions and 30 deletions

View File

@ -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_list()*
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()
<

View File

@ -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

View File

@ -896,53 +896,260 @@ f_prop_find(typval_T *argvars, typval_T *rettv)
}
}
/*
* 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;
for (i = 0; i < count; ++i)
if (d != NULL && (di = dict_find(d, (char_u *)"end_lnum", -1)) != NULL)
{
dict_T *d = dict_alloc();
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;
}
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_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);
}
/*

View File

@ -757,6 +757,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
3652,
/**/
3651,
/**/