To: vim_dev@googlegroups.com Subject: Patch 9.0.0233 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ 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 -- 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 ///