Patch 9.0.0233
Problem: Removing multiple text properties takes many calls.
Solution: Pass a list to prop_remove(). (Ben Jackson, closes #10945)
Files: runtime/doc/textprop.txt, src/textprop.c, src/errors.h,
src/testdir/test_textprop.vim
*** ../vim-9.0.0232/runtime/doc/textprop.txt 2022-08-05 17:04:43.402914763 +0100
--- runtime/doc/textprop.txt 2022-08-20 20:54:00.459011754 +0100
***************
*** 143,154 ****
zero is used
text text to be displayed before {col}, or after the
line if {col} is zero
text_align when "text" is present and {col} is zero
specifies where to display the text:
after after the end of the line
! right right aligned in the window
below in the next screen line
! When omitted "after" is used.
text_wrap when "text" is present and {col} is zero,
specifies what happens if the text doesn't
fit:
--- 143,158 ----
zero is used
text text to be displayed before {col}, or after the
line if {col} is zero
+ *E1294*
text_align when "text" is present and {col} is zero
specifies where to display the text:
after after the end of the line
! right right aligned in the window (unless
! the text wraps to the next screen
! line)
below in the next screen line
! When omitted "after" is used. Only one
! "right" property can fit in earch line.
text_wrap when "text" is present and {col} is zero,
specifies what happens if the text doesn't
fit:
***************
*** 186,194 ****
buffer line, the cursor cannot be placed on it. A mouse click
in the text will move the cursor to the first character after
the text, or the last character of the line.
A negative "id" will be chosen and is returned. Once a
- Any Tab in the text will be changed to a space (Rationale:
- otherwise the size of the text is difficult to compute).
property with "text" has been added for a buffer then using a
negative "id" for any other property will give an error:
*E1293*
--- 190,199 ----
buffer line, the cursor cannot be placed on it. A mouse click
in the text will move the cursor to the first character after
the text, or the last character of the line.
+ Any Tab and other control character in the text will be
+ changed to a space (Rationale: otherwise the size of the text
+ is difficult to compute).
A negative "id" will be chosen and is returned. Once a
property with "text" has been added for a buffer then using a
negative "id" for any other property will give an error:
*E1293*
***************
*** 347,357 ****
{props} is a dictionary with these fields:
id remove text properties with this ID
type remove text properties with this type name
! both "id" and "type" must both match
bufnr use this buffer instead of the current one
all when TRUE remove all matching text properties,
not just the first one
! A property matches when either "id" or "type" matches.
If buffer "bufnr" does not exist you get an error message.
If buffer "bufnr" is not loaded then nothing happens.
--- 352,367 ----
{props} is a dictionary with these fields:
id remove text properties with this ID
type remove text properties with this type name
! types remove text properties with type names in this
! List
! both "id" and "type"/"types" must both match
bufnr use this buffer instead of the current one
all when TRUE remove all matching text properties,
not just the first one
! Only one of "type" and "types" may be supplied. *E1295*
!
! A property matches when either "id" or one of the supplied
! types matches.
If buffer "bufnr" does not exist you get an error message.
If buffer "bufnr" is not loaded then nothing happens.
*** ../vim-9.0.0232/src/textprop.c 2022-08-15 15:54:45.710375583 +0100
--- src/textprop.c 2022-08-20 20:47:49.903395746 +0100
***************
*** 1010,1016 ****
}
if (both && (!id_found || type_id == -1))
{
! emsg(_(e_need_id_and_type_with_both));
return;
}
--- 1010,1016 ----
}
if (both && (!id_found || type_id == -1))
{
! emsg(_(e_need_id_and_type_or_types_with_both));
return;
}
***************
*** 1378,1384 ****
buf_T *buf = curbuf;
int do_all;
int id = -MAXCOL;
! int type_id = -1;
int both;
int did_remove_text = FALSE;
--- 1378,1386 ----
buf_T *buf = curbuf;
int do_all;
int id = -MAXCOL;
! int type_id = -1; // for a single "type"
! int *type_ids = NULL; // array, for a list of "types", allocated
! int num_type_ids = 0; // number of elements in "type_ids"
int both;
int did_remove_text = FALSE;
***************
*** 1420,1425 ****
--- 1422,1430 ----
if (dict_has_key(dict, "id"))
id = dict_get_number(dict, "id");
+
+ // if a specific type was supplied "type": check that (and ignore "types".
+ // Otherwise check against the list of "types".
if (dict_has_key(dict, "type"))
{
char_u *name = dict_get_string(dict, "type", FALSE);
***************
*** 1429,1445 ****
return;
type_id = type->pt_id;
}
both = dict_get_bool(dict, "both", FALSE);
! if (id == -MAXCOL && type_id == -1)
{
emsg(_(e_need_at_least_one_of_id_or_type));
! return;
}
! if (both && (id == -MAXCOL || type_id == -1))
{
! emsg(_(e_need_id_and_type_with_both));
! return;
}
if (end == 0)
--- 1434,1481 ----
return;
type_id = type->pt_id;
}
+ if (dict_has_key(dict, "types"))
+ {
+ typval_T types;
+ listitem_T *li = NULL;
+
+ dict_get_tv(dict, "types", &types);
+ if (types.v_type == VAR_LIST && types.vval.v_list->lv_len > 0)
+ {
+ type_ids = alloc( sizeof(int) * types.vval.v_list->lv_len );
+
+ FOR_ALL_LIST_ITEMS(types.vval.v_list, li)
+ {
+ proptype_T *prop_type;
+
+ if (li->li_tv.v_type != VAR_STRING)
+ continue;
+
+ prop_type = lookup_prop_type(li->li_tv.vval.v_string, buf);
+
+ if (!prop_type)
+ goto cleanup_prop_remove;
+
+ type_ids[num_type_ids++] = prop_type->pt_id;
+ }
+ }
+ }
both = dict_get_bool(dict, "both", FALSE);
! if (id == -MAXCOL && (type_id == -1 && num_type_ids == 0))
{
emsg(_(e_need_at_least_one_of_id_or_type));
! goto cleanup_prop_remove;
}
! if (both && (id == -MAXCOL || (type_id == -1 && num_type_ids == 0)))
{
! emsg(_(e_need_id_and_type_or_types_with_both));
! goto cleanup_prop_remove;
! }
! if (type_id != -1 && num_type_ids > 0)
! {
! emsg(_(e_cannot_specify_both_type_and_types));
! goto cleanup_prop_remove;
}
if (end == 0)
***************
*** 1464,1473 ****
char_u *cur_prop = buf->b_ml.ml_line_ptr + len
+ idx * sizeof(textprop_T);
size_t taillen;
mch_memmove(&textprop, cur_prop, sizeof(textprop_T));
! if (both ? textprop.tp_id == id && textprop.tp_type == type_id
! : textprop.tp_id == id || textprop.tp_type == type_id)
{
if (!(buf->b_ml.ml_flags & ML_LINE_DIRTY))
{
--- 1500,1525 ----
char_u *cur_prop = buf->b_ml.ml_line_ptr + len
+ idx * sizeof(textprop_T);
size_t taillen;
+ int matches_id = 0;
+ int matches_type = 0;
mch_memmove(&textprop, cur_prop, sizeof(textprop_T));
!
! matches_id = textprop.tp_id == id;
! if (num_type_ids > 0)
! {
! int idx2;
!
! for (idx2 = 0; !matches_type && idx2 < num_type_ids; ++idx2)
! matches_type = textprop.tp_type == type_ids[idx2];
! }
! else
! {
! matches_type = textprop.tp_type == type_id;
! }
!
! if (both ? matches_id && matches_type
! : matches_id || matches_type)
{
if (!(buf->b_ml.ml_flags & ML_LINE_DIRTY))
{
***************
*** 1475,1481 ****
// need to allocate the line to be able to change it
if (newptr == NULL)
! return;
mch_memmove(newptr, buf->b_ml.ml_line_ptr,
buf->b_ml.ml_line_len);
if (buf->b_ml.ml_flags & ML_ALLOCATED)
--- 1527,1533 ----
// need to allocate the line to be able to change it
if (newptr == NULL)
! goto cleanup_prop_remove;
mch_memmove(newptr, buf->b_ml.ml_line_ptr,
buf->b_ml.ml_line_len);
if (buf->b_ml.ml_flags & ML_ALLOCATED)
***************
*** 1537,1542 ****
--- 1589,1597 ----
&& ((char_u **)gap->ga_data)[gap->ga_len - 1] == NULL)
--gap->ga_len;
}
+
+ cleanup_prop_remove:
+ vim_free(type_ids);
}
/*
*** ../vim-9.0.0232/src/errors.h 2022-08-20 12:07:55.098022792 +0100
--- src/errors.h 2022-08-20 20:40:30.507911267 +0100
***************
*** 2208,2215 ****
INIT(= N_("E859: Failed to convert returned python object to a Vim value"));
#endif
#ifdef FEAT_PROP_POPUP
! EXTERN char e_need_id_and_type_with_both[]
! INIT(= N_("E860: Need 'id' and 'type' with 'both'"));
# ifdef FEAT_TERMINAL
EXTERN char e_cannot_open_second_popup_with_terminal[]
INIT(= N_("E861: Cannot open a second popup with a terminal"));
--- 2208,2215 ----
INIT(= N_("E859: Failed to convert returned python object to a Vim value"));
#endif
#ifdef FEAT_PROP_POPUP
! EXTERN char e_need_id_and_type_or_types_with_both[]
! INIT(= N_("E860: Need 'id' and 'type' or 'types' with 'both'"));
# ifdef FEAT_TERMINAL
EXTERN char e_cannot_open_second_popup_with_terminal[]
INIT(= N_("E861: Cannot open a second popup with a terminal"));
***************
*** 3316,3318 ****
--- 3316,3322 ----
EXTERN char e_can_only_use_text_align_when_column_is_zero[]
INIT(= N_("E1294: Can only use text_align when column is zero"));
#endif
+ #ifdef FEAT_PROP_POPUP
+ EXTERN char e_cannot_specify_both_type_and_types[]
+ INIT(= N_("E1295: Cannot specify both 'type' and 'types'"));
+ #endif
*** ../vim-9.0.0232/src/testdir/test_textprop.vim 2022-08-15 15:54:45.710375583 +0100
--- src/testdir/test_textprop.vim 2022-08-20 20:44:18.895631843 +0100
***************
*** 161,167 ****
\ ]
" 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)
--- 161,167 ----
\ ]
" 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)
***************
*** 182,188 ****
" 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)
--- 182,188 ----
" 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)
***************
*** 230,241 ****
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)
--- 230,241 ----
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)
***************
*** 426,431 ****
--- 426,462 ----
call DeletePropTypes()
bwipe!
+
+ new
+ call AddPropTypes()
+ call SetupPropsInFirstLine()
+ let props = Get_expected_props() " [whole, one, two, three]
+ call assert_equal(props, prop_list(1))
+
+ " remove one by types
+ call assert_equal(1, prop_remove({'types': ['one', 'two', 'three']}, 1))
+ unlet props[1] " [whole, two, three]
+ call assert_equal(props, prop_list(1))
+
+ " remove 'all' by types
+ call assert_equal(2, prop_remove({'types': ['three', 'whole'], 'all': 1}, 1))
+ unlet props[0] " [two, three]
+ unlet props[1] " [three]
+ call assert_equal(props, prop_list(1))
+
+ " remove none by types
+ call assert_equal(0, prop_remove({'types': ['three', 'whole'], 'all': 1}, 1))
+ call assert_equal(props, prop_list(1))
+
+ " no types
+ call assert_fails("call prop_remove({'types': []}, 1)", 'E968:')
+ call assert_fails("call prop_remove({'types': ['not_a_real_type']}, 1)", 'E971:')
+
+ " only one of types and type can be supplied
+ call assert_fails("call prop_remove({'type': 'one', 'types': ['three'], 'all': 1}, 1)", 'E1295:')
+
+ call DeletePropTypes()
+ bwipe!
endfunc
def Test_prop_add_vim9()
***************
*** 1396,1402 ****
func Test_textprop_invalid_highlight()
call assert_fails("call prop_type_add('dni', {'highlight': 'DoesNotExist'})", 'E970:')
new
! call setline(1, ['asdf','asdf'])
call prop_add(1, 1, {'length': 4, 'type': 'dni'})
redraw
bwipe!
--- 1427,1433 ----
func Test_textprop_invalid_highlight()
call assert_fails("call prop_type_add('dni', {'highlight': 'DoesNotExist'})", 'E970:')
new
! call setline(1, ['asdf', 'asdf'])
call prop_add(1, 1, {'length': 4, 'type': 'dni'})
redraw
bwipe!
***************
*** 2207,2213 ****
call prop_add(1, col, #{type: 'misspell', length: 2})
endfor
! call cursor(1,18)
let expected = [
\ #{lnum: 1, id: 0, col: 14, end: 1, type: 'misspell', type_bufnr: 0, length: 2, start: 1},
\ #{lnum: 1, id: 0, col: 24, end: 1, type: 'misspell', type_bufnr: 0, length: 2, start: 1}
--- 2238,2244 ----
call prop_add(1, col, #{type: 'misspell', length: 2})
endfor
! call cursor(1, 18)
let expected = [
\ #{lnum: 1, id: 0, col: 14, end: 1, type: 'misspell', type_bufnr: 0, length: 2, start: 1},
\ #{lnum: 1, id: 0, col: 24, end: 1, type: 'misspell', type_bufnr: 0, length: 2, start: 1}
***************
*** 2310,2316 ****
call assert_equal(lines, getline(1, '$'))
let expected = [
\ {'lnum': 1, 'id': 0, 'col': 4, 'type_bufnr': 0, 'end': 0, 'type': 'one',
! \ 'length': 4 ,'start': 1},
\ {'lnum': 2, 'id': 0, 'col': 1, 'type_bufnr': 0, 'end': 0, 'type': 'one',
\ 'length': 7, 'start': 0},
\ {'lnum': 3, 'id': 0, 'col': 1, 'type_bufnr': 0, 'end': 0, 'type': 'one',
--- 2341,2347 ----
call assert_equal(lines, getline(1, '$'))
let expected = [
\ {'lnum': 1, 'id': 0, 'col': 4, 'type_bufnr': 0, 'end': 0, 'type': 'one',
! \ 'length': 4 , 'start': 1},
\ {'lnum': 2, 'id': 0, 'col': 1, 'type_bufnr': 0, 'end': 0, 'type': 'one',
\ 'length': 7, 'start': 0},
\ {'lnum': 3, 'id': 0, 'col': 1, 'type_bufnr': 0, 'end': 0, 'type': 'one',
*** ../vim-9.0.0232/src/version.c 2022-08-20 20:09:10.598693210 +0100
--- src/version.c 2022-08-20 20:38:36.820063346 +0100
***************
*** 733,734 ****
--- 733,736 ----
{ /* Add new patch number below this line */
+ /**/
+ 233,
/**/
--
Be nice to your kids... they'll be the ones choosing your nursing home.
/// Bram Moolenaar -- Br...@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 ///