diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 31e901a54d29d..db74f9c10b6e8 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2801,7 +2801,9 @@ prompt_getprompt({buf}) String get prompt text prompt_setcallback({buf}, {expr}) none set prompt callback function prompt_setinterrupt({buf}, {text}) none set prompt interrupt function prompt_setprompt({buf}, {text}) none set prompt text -prop_add({lnum}, {col}, {props}) none add a text property +prop_add({lnum}, {col}, {props}) none add one text property +prop_add_list({props}, [[{lnum}, {col}, {end-lnum}, {end-col}], ...]) + none add multiple text properties prop_clear({lnum} [, {lnum-end} [, {props}]]) none remove all text properties prop_find({props} [, {direction}]) diff --git a/runtime/doc/textprop.txt b/runtime/doc/textprop.txt index 52191ae03a76c..c742293db427a 100644 --- a/runtime/doc/textprop.txt +++ b/runtime/doc/textprop.txt @@ -108,6 +108,9 @@ prop_type_list([{props}]) get list of property types Manipulating text properties: prop_add({lnum}, {col}, {props}) add a text property +prop_add_list({props}, [[{lnum}, {col}, {end-lnum}, {end-col}], ...]) + add a text property at multiple + positions. prop_clear({lnum} [, {lnum-end} [, {bufnr}]]) remove all text properties prop_find({props} [, {direction}]) search for a text property @@ -158,6 +161,35 @@ prop_add({lnum}, {col}, {props}) Can also be used as a |method|: > GetLnum()->prop_add(col, props) + *prop_add_list()* +prop_add_list({props}, [[{lnum}, {col}, {end-lnum}, {end-col}], ...]) + Similar to prop_add(), but attaches a text property at + multiple positions in a buffer. + + {props} is a dictionary with these fields: + bufnr buffer to add the property to; when omitted + the current buffer is used + id user defined ID for the property; must be a + number; when omitted zero is used + type name of the text property type + All fields except "type" are optional. + + The second argument is a List of Lists where each list + specifies the starting and ending position of the text. The + first two items {lnum} and {col} specify the starting position + of the text where the property will be attached and the last + two items {end-lnum} and {end-col} specify the position just + after the text. + + Example: + call prop_add_list(#{type: 'MyProp', id: 2}, + \ [[1, 4, 1, 7], + \ [1, 15, 1, 20], + \ [2, 30, 3, 30]] + + Can also be used as a |method|: > + GetProp()->prop_add_list([[1, 1, 1, 2], [1, 4, 1, 8]]) + prop_clear({lnum} [, {lnum-end} [, {props}]]) *prop_clear()* Remove all text properties from line {lnum}. diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index 3a3a0fca78997..a5dde18c38f05 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -1161,6 +1161,7 @@ Prompt Buffer: *promptbuffer-functions* Text Properties: *text-property-functions* prop_add() attach a property at a position + prop_add_list() attach a property at multiple positions prop_clear() remove all properties from a line or lines prop_find() search for a property prop_list() return a list of all properties in a line diff --git a/src/evalfunc.c b/src/evalfunc.c index 2b50798f4fe93..4d73d40f12441 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -708,6 +708,7 @@ static argcheck_T arg2_buffer_number[] = {arg_buffer, arg_number}; static argcheck_T arg2_buffer_string[] = {arg_buffer, arg_string}; static argcheck_T arg2_chan_or_job_dict[] = {arg_chan_or_job, arg_dict_any}; static argcheck_T arg2_chan_or_job_string[] = {arg_chan_or_job, arg_string}; +static argcheck_T arg2_dict_any_list_any[] = {arg_dict_any, arg_list_any}; static argcheck_T arg2_dict_any_string_or_nr[] = {arg_dict_any, arg_string_or_nr}; static argcheck_T arg2_dict_string[] = {arg_dict_any, arg_string}; static argcheck_T arg2_float_or_nr[] = {arg_float_or_nr, arg_float_or_nr}; @@ -1740,6 +1741,8 @@ static funcentry_T global_functions[] = ret_void, JOB_FUNC(f_prompt_setprompt)}, {"prop_add", 3, 3, FEARG_1, arg3_number_number_dict, ret_void, PROP_FUNC(f_prop_add)}, + {"prop_add_list", 2, 2, FEARG_1, arg2_dict_any_list_any, + ret_void, PROP_FUNC(f_prop_add_list)}, {"prop_clear", 1, 3, FEARG_1, arg3_number_number_dict, ret_void, PROP_FUNC(f_prop_clear)}, {"prop_find", 1, 2, FEARG_1, arg2_dict_string, diff --git a/src/proto/textprop.pro b/src/proto/textprop.pro index 8a4a22fa0bc85..72a900bfa8ec7 100644 --- a/src/proto/textprop.pro +++ b/src/proto/textprop.pro @@ -1,6 +1,7 @@ /* textprop.c */ int find_prop_type_id(char_u *name, buf_T *buf); void f_prop_add(typval_T *argvars, typval_T *rettv); +void f_prop_add_list(typval_T *argvars, typval_T *rettv); void prop_add_common(linenr_T start_lnum, colnr_T start_col, dict_T *dict, buf_T *default_buf, typval_T *dict_arg); int get_text_props(buf_T *buf, linenr_T lnum, char_u **props, int will_change); int count_props(linenr_T lnum, int only_starting); diff --git a/src/testdir/test_textprop.vim b/src/testdir/test_textprop.vim index 6af47e36dcb83..373df6e653043 100644 --- a/src/testdir/test_textprop.vim +++ b/src/testdir/test_textprop.vim @@ -339,6 +339,41 @@ func Test_prop_add() bwipe! endfunc +" Test for the prop_add_list() function +func Test_prop_add_list() + new + call AddPropTypes() + call setline(1, ['one one one', 'two two two', 'six six six', 'ten ten ten']) + call prop_add_list(#{type: 'one', id: 2}, + \ [[1, 1, 1, 3], [2, 5, 2, 7], [3, 6, 4, 6]]) + call assert_equal([#{id: 2, col: 1, type_bufnr: 0, end: 1, type: 'one', + \ length: 2, start: 1}], prop_list(1)) + call assert_equal([#{id: 2, col: 5, type_bufnr: 0, end: 1, type: 'one', + \ length: 2, start: 1}], prop_list(2)) + call assert_equal([#{id: 2, col: 6, type_bufnr: 0, end: 0, type: 'one', + \ length: 7, start: 1}], prop_list(3)) + call assert_equal([#{id: 2, col: 1, type_bufnr: 0, end: 1, type: 'one', + \ length: 5, start: 0}], prop_list(4)) + call assert_fails('call prop_add_list([1, 2], [[1, 1, 3]])', 'E1206:') + call assert_fails('call prop_add_list({}, {})', 'E1211:') + call assert_fails('call prop_add_list({}, [[1, 1, 3]])', 'E965:') + call assert_fails('call prop_add_list(#{type: "abc"}, [[1, 1, 1, 3]])', 'E971:') + call assert_fails('call prop_add_list(#{type: "one"}, [[]])', 'E474:') + call assert_fails('call prop_add_list(#{type: "one"}, [[1, 1, 1, 1], {}])', 'E714:') + call assert_fails('call prop_add_list(#{type: "one"}, [[1, 1, "a"]])', 'E474:') + call assert_fails('call prop_add_list(#{type: "one"}, [[2, 2]])', 'E474:') + call assert_fails('call prop_add_list(#{type: "one"}, [[1, 1, 2], [2, 2]])', 'E474:') + call assert_fails('call prop_add_list(#{type: "one"}, [[1, 1, 1, 2], [4, 1, 5, 2]])', 'E966:') + call assert_fails('call prop_add_list(#{type: "one"}, [[3, 1, 1, 2]])', 'E966:') + call assert_fails('call prop_add_list(#{type: "one"}, [[2, 2, 2, 2], [3, 20, 3, 22]])', 'E964:') + call assert_fails('eval #{type: "one"}->prop_add_list([[2, 2, 2, 2], [3, 20, 3, 22]])', 'E964:') + call assert_fails('call prop_add_list(test_null_dict(), [[2, 2, 2]])', 'E965:') + call assert_fails('call prop_add_list(#{type: "one"}, test_null_list())', 'E714:') + call assert_fails('call prop_add_list(#{type: "one"}, [test_null_list()])', 'E714:') + call DeletePropTypes() + bw! +endfunc + func Test_prop_remove() new call AddPropTypes() diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim index 5e45edbaeefa9..00d021fe194ac 100644 --- a/src/testdir/test_vim9_builtin.vim +++ b/src/testdir/test_vim9_builtin.vim @@ -2364,6 +2364,11 @@ def Test_prop_add() CheckDefAndScriptFailure2(['prop_add(1, 2, [])'], 'E1013: Argument 3: type mismatch, expected dict but got list', 'E1206: Dictionary required for argument 3') enddef +def Test_prop_add_list() + CheckDefAndScriptFailure2(['prop_add_list([], [])'], 'E1013: Argument 1: type mismatch, expected dict but got list', 'E1206: Dictionary required for argument 1') + CheckDefAndScriptFailure2(['prop_add_list({}, {})'], 'E1013: Argument 2: type mismatch, expected list but got dict', 'E1211: List required for argument 2') +enddef + def Test_prop_clear() CheckDefAndScriptFailure2(['prop_clear("a")'], 'E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1') CheckDefAndScriptFailure2(['prop_clear(1, "b")'], 'E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2') diff --git a/src/textprop.c b/src/textprop.c index 6c9d5c8c4f92e..26c7f52084169 100644 --- a/src/textprop.c +++ b/src/textprop.c @@ -183,102 +183,49 @@ f_prop_add(typval_T *argvars, typval_T *rettv UNUSED) } /* - * Shared between prop_add() and popup_create(). - * "dict_arg" is the function argument of a dict containing "bufnr". - * it is NULL for popup_create(). + * Attach a text property 'type_name' to the text starting + * at [start_lnum, start_col] and ending at [end_lnum, end_col] in + * the buffer 'buf' and assign identifier 'id'. */ - void -prop_add_common( - linenr_T start_lnum, - colnr_T start_col, - dict_T *dict, - buf_T *default_buf, - typval_T *dict_arg) + static int +prop_add_one( + buf_T *buf, + char_u *type_name, + int id, + linenr_T start_lnum, + linenr_T end_lnum, + colnr_T start_col, + colnr_T end_col) { - linenr_T lnum; - linenr_T end_lnum; - colnr_T end_col; - char_u *type_name; proptype_T *type; - buf_T *buf = default_buf; - int id = 0; - char_u *newtext; + linenr_T lnum; int proplen; - size_t textlen; char_u *props = NULL; char_u *newprops; - textprop_T tmp_prop; + size_t textlen; + char_u *newtext; int i; - - if (dict == NULL || dict_find(dict, (char_u *)"type", -1) == NULL) - { - emsg(_("E965: missing property type name")); - return; - } - type_name = dict_get_string(dict, (char_u *)"type", FALSE); - - if (dict_find(dict, (char_u *)"end_lnum", -1) != NULL) - { - end_lnum = dict_get_number(dict, (char_u *)"end_lnum"); - if (end_lnum < start_lnum) - { - semsg(_(e_invargval), "end_lnum"); - return; - } - } - else - end_lnum = start_lnum; - - if (dict_find(dict, (char_u *)"length", -1) != NULL) - { - long length = dict_get_number(dict, (char_u *)"length"); - - if (length < 0 || end_lnum > start_lnum) - { - semsg(_(e_invargval), "length"); - return; - } - end_col = start_col + length; - } - else if (dict_find(dict, (char_u *)"end_col", -1) != NULL) - { - end_col = dict_get_number(dict, (char_u *)"end_col"); - if (end_col <= 0) - { - semsg(_(e_invargval), "end_col"); - return; - } - } - else if (start_lnum == end_lnum) - end_col = start_col; - else - end_col = 1; - - if (dict_find(dict, (char_u *)"id", -1) != NULL) - id = dict_get_number(dict, (char_u *)"id"); - - if (dict_arg != NULL && get_bufnr_from_arg(dict_arg, &buf) == FAIL) - return; + textprop_T tmp_prop; type = lookup_prop_type(type_name, buf); if (type == NULL) - return; + return FAIL; if (start_lnum < 1 || start_lnum > buf->b_ml.ml_line_count) { semsg(_(e_invalid_lnum), (long)start_lnum); - return; + return FAIL; } if (end_lnum < start_lnum || end_lnum > buf->b_ml.ml_line_count) { semsg(_(e_invalid_lnum), (long)end_lnum); - return; + return FAIL; } if (buf->b_ml.ml_mfp == NULL) { emsg(_("E275: Cannot add text property to unloaded buffer")); - return; + return FAIL; } for (lnum = start_lnum; lnum <= end_lnum; ++lnum) @@ -297,7 +244,7 @@ prop_add_common( if (col - 1 > (colnr_T)textlen) { semsg(_(e_invalid_col), (long)start_col); - return; + return FAIL; } if (lnum == end_lnum) @@ -312,7 +259,7 @@ prop_add_common( // Allocate the new line with space for the new property. newtext = alloc(buf->b_ml.ml_line_len + sizeof(textprop_T)); if (newtext == NULL) - return; + return FAIL; // Copy the text, including terminating NUL. mch_memmove(newtext, buf->b_ml.ml_line_ptr, textlen); @@ -351,8 +298,156 @@ prop_add_common( buf->b_ml.ml_flags |= ML_LINE_DIRTY; } - buf->b_has_textprop = TRUE; // this is never reset changed_lines_buf(buf, start_lnum, end_lnum + 1, 0); + return OK; +} + +/* + * prop_add_list() + * First argument specifies the text property: + * {'type': , 'id': , 'bufnr': } + * Second argument is a List where each item is a List with the following + * entries: [lnum, start_col, end_col] + */ + void +f_prop_add_list(typval_T *argvars, typval_T *rettv UNUSED) +{ + dict_T *dict; + char_u *type_name; + buf_T *buf = curbuf; + int id = 0; + listitem_T *li; + list_T *pos_list; + linenr_T start_lnum; + colnr_T start_col; + linenr_T end_lnum; + colnr_T end_col; + int error = FALSE; + + if (check_for_dict_arg(argvars, 0) == FAIL + || check_for_list_arg(argvars, 1) == FAIL) + return; + + if (argvars[1].vval.v_list == NULL) + { + emsg(_(e_listreq)); + return; + } + + dict = argvars[0].vval.v_dict; + if (dict == NULL || dict_find(dict, (char_u *)"type", -1) == NULL) + { + emsg(_("E965: missing property type name")); + return; + } + type_name = dict_get_string(dict, (char_u *)"type", FALSE); + + if (dict_find(dict, (char_u *)"id", -1) != NULL) + id = dict_get_number(dict, (char_u *)"id"); + + if (get_bufnr_from_arg(&argvars[0], &buf) == FAIL) + return; + + FOR_ALL_LIST_ITEMS(argvars[1].vval.v_list, li) + { + if (li->li_tv.v_type != VAR_LIST || li->li_tv.vval.v_list == NULL) + { + emsg(_(e_listreq)); + return; + } + + pos_list = li->li_tv.vval.v_list; + start_lnum = list_find_nr(pos_list, 0L, &error); + start_col = list_find_nr(pos_list, 1L, &error); + end_lnum = list_find_nr(pos_list, 2L, &error); + end_col = list_find_nr(pos_list, 3L, &error); + if (error || start_lnum <= 0 || start_col <= 0 + || end_lnum <= 0 || end_col <= 0) + { + emsg(_(e_invarg)); + return; + } + if (prop_add_one(buf, type_name, id, start_lnum, end_lnum, + start_col, end_col) == FAIL) + return; + } + + buf->b_has_textprop = TRUE; // this is never reset + redraw_buf_later(buf, VALID); +} + +/* + * Shared between prop_add() and popup_create(). + * "dict_arg" is the function argument of a dict containing "bufnr". + * it is NULL for popup_create(). + */ + void +prop_add_common( + linenr_T start_lnum, + colnr_T start_col, + dict_T *dict, + buf_T *default_buf, + typval_T *dict_arg) +{ + linenr_T end_lnum; + colnr_T end_col; + char_u *type_name; + buf_T *buf = default_buf; + int id = 0; + + if (dict == NULL || dict_find(dict, (char_u *)"type", -1) == NULL) + { + emsg(_("E965: missing property type name")); + return; + } + type_name = dict_get_string(dict, (char_u *)"type", FALSE); + + if (dict_find(dict, (char_u *)"end_lnum", -1) != NULL) + { + end_lnum = dict_get_number(dict, (char_u *)"end_lnum"); + if (end_lnum < start_lnum) + { + semsg(_(e_invargval), "end_lnum"); + return; + } + } + else + end_lnum = start_lnum; + + if (dict_find(dict, (char_u *)"length", -1) != NULL) + { + long length = dict_get_number(dict, (char_u *)"length"); + + if (length < 0 || end_lnum > start_lnum) + { + semsg(_(e_invargval), "length"); + return; + } + end_col = start_col + length; + } + else if (dict_find(dict, (char_u *)"end_col", -1) != NULL) + { + end_col = dict_get_number(dict, (char_u *)"end_col"); + if (end_col <= 0) + { + semsg(_(e_invargval), "end_col"); + return; + } + } + else if (start_lnum == end_lnum) + end_col = start_col; + else + end_col = 1; + + if (dict_find(dict, (char_u *)"id", -1) != NULL) + id = dict_get_number(dict, (char_u *)"id"); + + if (dict_arg != NULL && get_bufnr_from_arg(dict_arg, &buf) == FAIL) + return; + + prop_add_one(buf, type_name, id, start_lnum, end_lnum, start_col, end_col); + + buf->b_has_textprop = TRUE; // this is never reset redraw_buf_later(buf, VALID); } diff --git a/src/version.c b/src/version.c index 7f1f4961586fb..97a58e3c02192 100644 --- a/src/version.c +++ b/src/version.c @@ -755,6 +755,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 3356, /**/ 3355, /**/