1 /* vi:set ts=8 sts=4 sw=4 noet: 2 * 3 * VIM - Vi IMproved by Bram Moolenaar 4 * 5 * Do ":help uganda" in Vim to read copying and usage conditions. 6 * Do ":help credits" in Vim to see a list of people who contributed. 7 * See README.txt for an overview of the Vim source code. 8 */ 9 10 /* 11 * Text properties implementation. See ":help text-properties". 12 * 13 * TODO: 14 * - Adjust text property column and length when text is inserted/deleted. 15 * -> a :substitute with a multi-line match 16 * -> search for changed_bytes() from misc1.c 17 * -> search for mark_col_adjust() 18 * - Perhaps we only need TP_FLAG_CONT_NEXT and can drop TP_FLAG_CONT_PREV? 19 * - Add an array for global_proptypes, to quickly lookup a prop type by ID 20 * - Add an array for b_proptypes, to quickly lookup a prop type by ID 21 * - Checking the text length to detect text properties is slow. Use a flag in 22 * the index, like DB_MARKED? 23 * - Also test line2byte() with many lines, so that ml_updatechunk() is taken 24 * into account. 25 * - Perhaps have a window-local option to disable highlighting from text 26 * properties? 27 */ 28 29 #include "vim.h" 30 31 #if defined(FEAT_PROP_POPUP) || defined(PROTO) 32 33 /* 34 * In a hashtable item "hi_key" points to "pt_name" in a proptype_T. 35 * This avoids adding a pointer to the hashtable item. 36 * PT2HIKEY() converts a proptype pointer to a hashitem key pointer. 37 * HIKEY2PT() converts a hashitem key pointer to a proptype pointer. 38 * HI2PT() converts a hashitem pointer to a proptype pointer. 39 */ 40 #define PT2HIKEY(p) ((p)->pt_name) 41 #define HIKEY2PT(p) ((proptype_T *)((p) - offsetof(proptype_T, pt_name))) 42 #define HI2PT(hi) HIKEY2PT((hi)->hi_key) 43 44 // The global text property types. 45 static hashtab_T *global_proptypes = NULL; 46 47 // The last used text property type ID. 48 static int proptype_id = 0; 49 50 static char_u e_type_not_exist[] = N_("E971: Property type %s does not exist"); 51 static char_u e_invalid_col[] = N_("E964: Invalid column number: %ld"); 52 static char_u e_invalid_lnum[] = N_("E966: Invalid line number: %ld"); 53 54 /* 55 * Find a property type by name, return the hashitem. 56 * Returns NULL if the item can't be found. 57 */ 58 static hashitem_T * 59 find_prop_hi(char_u *name, buf_T *buf) 60 { 61 hashtab_T *ht; 62 hashitem_T *hi; 63 64 if (*name == NUL) 65 return NULL; 66 if (buf == NULL) 67 ht = global_proptypes; 68 else 69 ht = buf->b_proptypes; 70 71 if (ht == NULL) 72 return NULL; 73 hi = hash_find(ht, name); 74 if (HASHITEM_EMPTY(hi)) 75 return NULL; 76 return hi; 77 } 78 79 /* 80 * Like find_prop_hi() but return the property type. 81 */ 82 static proptype_T * 83 find_prop(char_u *name, buf_T *buf) 84 { 85 hashitem_T *hi = find_prop_hi(name, buf); 86 87 if (hi == NULL) 88 return NULL; 89 return HI2PT(hi); 90 } 91 92 /* 93 * Get the prop type ID of "name". 94 * When not found return zero. 95 */ 96 int 97 find_prop_type_id(char_u *name, buf_T *buf) 98 { 99 proptype_T *pt = find_prop(name, buf); 100 101 if (pt == NULL) 102 return 0; 103 return pt->pt_id; 104 } 105 106 /* 107 * Lookup a property type by name. First in "buf" and when not found in the 108 * global types. 109 * When not found gives an error message and returns NULL. 110 */ 111 static proptype_T * 112 lookup_prop_type(char_u *name, buf_T *buf) 113 { 114 proptype_T *type = find_prop(name, buf); 115 116 if (type == NULL) 117 type = find_prop(name, NULL); 118 if (type == NULL) 119 semsg(_(e_type_not_exist), name); 120 return type; 121 } 122 123 /* 124 * Get an optional "bufnr" item from the dict in "arg". 125 * When the argument is not used or "bufnr" is not present then "buf" is 126 * unchanged. 127 * If "bufnr" is valid or not present return OK. 128 * When "arg" is not a dict or "bufnr" is invalid return FAIL. 129 */ 130 static int 131 get_bufnr_from_arg(typval_T *arg, buf_T **buf) 132 { 133 dictitem_T *di; 134 135 if (arg->v_type != VAR_DICT) 136 { 137 emsg(_(e_dictreq)); 138 return FAIL; 139 } 140 if (arg->vval.v_dict == NULL) 141 return OK; // NULL dict is like an empty dict 142 di = dict_find(arg->vval.v_dict, (char_u *)"bufnr", -1); 143 if (di != NULL && (di->di_tv.v_type != VAR_NUMBER 144 || di->di_tv.vval.v_number != 0)) 145 { 146 *buf = get_buf_arg(&di->di_tv); 147 if (*buf == NULL) 148 return FAIL; 149 } 150 return OK; 151 } 152 153 /* 154 * prop_add({lnum}, {col}, {props}) 155 */ 156 void 157 f_prop_add(typval_T *argvars, typval_T *rettv UNUSED) 158 { 159 linenr_T start_lnum; 160 colnr_T start_col; 161 162 if (in_vim9script() 163 && (check_for_number_arg(argvars, 0) == FAIL 164 || check_for_number_arg(argvars, 1) == FAIL 165 || check_for_dict_arg(argvars, 2) == FAIL)) 166 return; 167 168 start_lnum = tv_get_number(&argvars[0]); 169 start_col = tv_get_number(&argvars[1]); 170 if (start_col < 1) 171 { 172 semsg(_(e_invalid_col), (long)start_col); 173 return; 174 } 175 if (argvars[2].v_type != VAR_DICT) 176 { 177 emsg(_(e_dictreq)); 178 return; 179 } 180 181 prop_add_common(start_lnum, start_col, argvars[2].vval.v_dict, 182 curbuf, &argvars[2]); 183 } 184 185 /* 186 * Attach a text property 'type_name' to the text starting 187 * at [start_lnum, start_col] and ending at [end_lnum, end_col] in 188 * the buffer 'buf' and assign identifier 'id'. 189 */ 190 static int 191 prop_add_one( 192 buf_T *buf, 193 char_u *type_name, 194 int id, 195 linenr_T start_lnum, 196 linenr_T end_lnum, 197 colnr_T start_col, 198 colnr_T end_col) 199 { 200 proptype_T *type; 201 linenr_T lnum; 202 int proplen; 203 char_u *props = NULL; 204 char_u *newprops; 205 size_t textlen; 206 char_u *newtext; 207 int i; 208 textprop_T tmp_prop; 209 210 type = lookup_prop_type(type_name, buf); 211 if (type == NULL) 212 return FAIL; 213 214 if (start_lnum < 1 || start_lnum > buf->b_ml.ml_line_count) 215 { 216 semsg(_(e_invalid_lnum), (long)start_lnum); 217 return FAIL; 218 } 219 if (end_lnum < start_lnum || end_lnum > buf->b_ml.ml_line_count) 220 { 221 semsg(_(e_invalid_lnum), (long)end_lnum); 222 return FAIL; 223 } 224 225 if (buf->b_ml.ml_mfp == NULL) 226 { 227 emsg(_("E275: Cannot add text property to unloaded buffer")); 228 return FAIL; 229 } 230 231 for (lnum = start_lnum; lnum <= end_lnum; ++lnum) 232 { 233 colnr_T col; // start column 234 long length; // in bytes 235 236 // Fetch the line to get the ml_line_len field updated. 237 proplen = get_text_props(buf, lnum, &props, TRUE); 238 textlen = buf->b_ml.ml_line_len - proplen * sizeof(textprop_T); 239 240 if (lnum == start_lnum) 241 col = start_col; 242 else 243 col = 1; 244 if (col - 1 > (colnr_T)textlen) 245 { 246 semsg(_(e_invalid_col), (long)start_col); 247 return FAIL; 248 } 249 250 if (lnum == end_lnum) 251 length = end_col - col; 252 else 253 length = (int)textlen - col + 1; 254 if (length > (long)textlen) 255 length = (int)textlen; // can include the end-of-line 256 if (length < 0) 257 length = 0; // zero-width property 258 259 // Allocate the new line with space for the new property. 260 newtext = alloc(buf->b_ml.ml_line_len + sizeof(textprop_T)); 261 if (newtext == NULL) 262 return FAIL; 263 // Copy the text, including terminating NUL. 264 mch_memmove(newtext, buf->b_ml.ml_line_ptr, textlen); 265 266 // Find the index where to insert the new property. 267 // Since the text properties are not aligned properly when stored with 268 // the text, we need to copy them as bytes before using it as a struct. 269 for (i = 0; i < proplen; ++i) 270 { 271 mch_memmove(&tmp_prop, props + i * sizeof(textprop_T), 272 sizeof(textprop_T)); 273 if (tmp_prop.tp_col >= col) 274 break; 275 } 276 newprops = newtext + textlen; 277 if (i > 0) 278 mch_memmove(newprops, props, sizeof(textprop_T) * i); 279 280 tmp_prop.tp_col = col; 281 tmp_prop.tp_len = length; 282 tmp_prop.tp_id = id; 283 tmp_prop.tp_type = type->pt_id; 284 tmp_prop.tp_flags = (lnum > start_lnum ? TP_FLAG_CONT_PREV : 0) 285 | (lnum < end_lnum ? TP_FLAG_CONT_NEXT : 0); 286 mch_memmove(newprops + i * sizeof(textprop_T), &tmp_prop, 287 sizeof(textprop_T)); 288 289 if (i < proplen) 290 mch_memmove(newprops + (i + 1) * sizeof(textprop_T), 291 props + i * sizeof(textprop_T), 292 sizeof(textprop_T) * (proplen - i)); 293 294 if (buf->b_ml.ml_flags & ML_LINE_DIRTY) 295 vim_free(buf->b_ml.ml_line_ptr); 296 buf->b_ml.ml_line_ptr = newtext; 297 buf->b_ml.ml_line_len += sizeof(textprop_T); 298 buf->b_ml.ml_flags |= ML_LINE_DIRTY; 299 } 300 301 changed_lines_buf(buf, start_lnum, end_lnum + 1, 0); 302 return OK; 303 } 304 305 /* 306 * prop_add_list() 307 * First argument specifies the text property: 308 * {'type': <str>, 'id': <num>, 'bufnr': <num>} 309 * Second argument is a List where each item is a List with the following 310 * entries: [lnum, start_col, end_col] 311 */ 312 void 313 f_prop_add_list(typval_T *argvars, typval_T *rettv UNUSED) 314 { 315 dict_T *dict; 316 char_u *type_name; 317 buf_T *buf = curbuf; 318 int id = 0; 319 listitem_T *li; 320 list_T *pos_list; 321 linenr_T start_lnum; 322 colnr_T start_col; 323 linenr_T end_lnum; 324 colnr_T end_col; 325 int error = FALSE; 326 327 if (check_for_dict_arg(argvars, 0) == FAIL 328 || check_for_list_arg(argvars, 1) == FAIL) 329 return; 330 331 if (argvars[1].vval.v_list == NULL) 332 { 333 emsg(_(e_listreq)); 334 return; 335 } 336 337 dict = argvars[0].vval.v_dict; 338 if (dict == NULL || dict_find(dict, (char_u *)"type", -1) == NULL) 339 { 340 emsg(_("E965: missing property type name")); 341 return; 342 } 343 type_name = dict_get_string(dict, (char_u *)"type", FALSE); 344 345 if (dict_find(dict, (char_u *)"id", -1) != NULL) 346 id = dict_get_number(dict, (char_u *)"id"); 347 348 if (get_bufnr_from_arg(&argvars[0], &buf) == FAIL) 349 return; 350 351 FOR_ALL_LIST_ITEMS(argvars[1].vval.v_list, li) 352 { 353 if (li->li_tv.v_type != VAR_LIST || li->li_tv.vval.v_list == NULL) 354 { 355 emsg(_(e_listreq)); 356 return; 357 } 358 359 pos_list = li->li_tv.vval.v_list; 360 start_lnum = list_find_nr(pos_list, 0L, &error); 361 start_col = list_find_nr(pos_list, 1L, &error); 362 end_lnum = list_find_nr(pos_list, 2L, &error); 363 end_col = list_find_nr(pos_list, 3L, &error); 364 if (error || start_lnum <= 0 || start_col <= 0 365 || end_lnum <= 0 || end_col <= 0) 366 { 367 emsg(_(e_invarg)); 368 return; 369 } 370 if (prop_add_one(buf, type_name, id, start_lnum, end_lnum, 371 start_col, end_col) == FAIL) 372 return; 373 } 374 375 buf->b_has_textprop = TRUE; // this is never reset 376 redraw_buf_later(buf, VALID); 377 } 378 379 /* 380 * Shared between prop_add() and popup_create(). 381 * "dict_arg" is the function argument of a dict containing "bufnr". 382 * it is NULL for popup_create(). 383 */ 384 void 385 prop_add_common( 386 linenr_T start_lnum, 387 colnr_T start_col, 388 dict_T *dict, 389 buf_T *default_buf, 390 typval_T *dict_arg) 391 { 392 linenr_T end_lnum; 393 colnr_T end_col; 394 char_u *type_name; 395 buf_T *buf = default_buf; 396 int id = 0; 397 398 if (dict == NULL || dict_find(dict, (char_u *)"type", -1) == NULL) 399 { 400 emsg(_("E965: missing property type name")); 401 return; 402 } 403 type_name = dict_get_string(dict, (char_u *)"type", FALSE); 404 405 if (dict_find(dict, (char_u *)"end_lnum", -1) != NULL) 406 { 407 end_lnum = dict_get_number(dict, (char_u *)"end_lnum"); 408 if (end_lnum < start_lnum) 409 { 410 semsg(_(e_invargval), "end_lnum"); 411 return; 412 } 413 } 414 else 415 end_lnum = start_lnum; 416 417 if (dict_find(dict, (char_u *)"length", -1) != NULL) 418 { 419 long length = dict_get_number(dict, (char_u *)"length"); 420 421 if (length < 0 || end_lnum > start_lnum) 422 { 423 semsg(_(e_invargval), "length"); 424 return; 425 } 426 end_col = start_col + length; 427 } 428 else if (dict_find(dict, (char_u *)"end_col", -1) != NULL) 429 { 430 end_col = dict_get_number(dict, (char_u *)"end_col"); 431 if (end_col <= 0) 432 { 433 semsg(_(e_invargval), "end_col"); 434 return; 435 } 436 } 437 else if (start_lnum == end_lnum) 438 end_col = start_col; 439 else 440 end_col = 1; 441 442 if (dict_find(dict, (char_u *)"id", -1) != NULL) 443 id = dict_get_number(dict, (char_u *)"id"); 444 445 if (dict_arg != NULL && get_bufnr_from_arg(dict_arg, &buf) == FAIL) 446 return; 447 448 prop_add_one(buf, type_name, id, start_lnum, end_lnum, start_col, end_col); 449 450 buf->b_has_textprop = TRUE; // this is never reset 451 redraw_buf_later(buf, VALID); 452 } 453 454 /* 455 * Fetch the text properties for line "lnum" in buffer "buf". 456 * Returns the number of text properties and, when non-zero, a pointer to the 457 * first one in "props" (note that it is not aligned, therefore the char_u 458 * pointer). 459 */ 460 int 461 get_text_props(buf_T *buf, linenr_T lnum, char_u **props, int will_change) 462 { 463 char_u *text; 464 size_t textlen; 465 size_t proplen; 466 467 // Be quick when no text property types have been defined or the buffer, 468 // unless we are adding one. 469 if ((!buf->b_has_textprop && !will_change) || buf->b_ml.ml_mfp == NULL) 470 return 0; 471 472 // Fetch the line to get the ml_line_len field updated. 473 text = ml_get_buf(buf, lnum, will_change); 474 textlen = STRLEN(text) + 1; 475 proplen = buf->b_ml.ml_line_len - textlen; 476 if (proplen % sizeof(textprop_T) != 0) 477 { 478 iemsg(_("E967: text property info corrupted")); 479 return 0; 480 } 481 if (proplen > 0) 482 *props = text + textlen; 483 return (int)(proplen / sizeof(textprop_T)); 484 } 485 486 /** 487 * Return the number of text properties on line "lnum" in the current buffer. 488 * When "only_starting" is true only text properties starting in this line will 489 * be considered. 490 */ 491 int 492 count_props(linenr_T lnum, int only_starting) 493 { 494 char_u *props; 495 int proplen = get_text_props(curbuf, lnum, &props, 0); 496 int result = proplen; 497 int i; 498 textprop_T prop; 499 500 if (only_starting) 501 for (i = 0; i < proplen; ++i) 502 { 503 mch_memmove(&prop, props + i * sizeof(prop), sizeof(prop)); 504 if (prop.tp_flags & TP_FLAG_CONT_PREV) 505 --result; 506 } 507 return result; 508 } 509 510 /* 511 * Find text property "type_id" in the visible lines of window "wp". 512 * Match "id" when it is > 0. 513 * Returns FAIL when not found. 514 */ 515 int 516 find_visible_prop(win_T *wp, int type_id, int id, textprop_T *prop, 517 linenr_T *found_lnum) 518 { 519 linenr_T lnum; 520 char_u *props; 521 int count; 522 int i; 523 524 // w_botline may not have been updated yet. 525 validate_botline_win(wp); 526 for (lnum = wp->w_topline; lnum < wp->w_botline; ++lnum) 527 { 528 count = get_text_props(wp->w_buffer, lnum, &props, FALSE); 529 for (i = 0; i < count; ++i) 530 { 531 mch_memmove(prop, props + i * sizeof(textprop_T), 532 sizeof(textprop_T)); 533 if (prop->tp_type == type_id && (id <= 0 || prop->tp_id == id)) 534 { 535 *found_lnum = lnum; 536 return OK; 537 } 538 } 539 } 540 return FAIL; 541 } 542 543 /* 544 * Set the text properties for line "lnum" to "props" with length "len". 545 * If "len" is zero text properties are removed, "props" is not used. 546 * Any existing text properties are dropped. 547 * Only works for the current buffer. 548 */ 549 static void 550 set_text_props(linenr_T lnum, char_u *props, int len) 551 { 552 char_u *text; 553 char_u *newtext; 554 int textlen; 555 556 text = ml_get(lnum); 557 textlen = (int)STRLEN(text) + 1; 558 newtext = alloc(textlen + len); 559 if (newtext == NULL) 560 return; 561 mch_memmove(newtext, text, textlen); 562 if (len > 0) 563 mch_memmove(newtext + textlen, props, len); 564 if (curbuf->b_ml.ml_flags & ML_LINE_DIRTY) 565 vim_free(curbuf->b_ml.ml_line_ptr); 566 curbuf->b_ml.ml_line_ptr = newtext; 567 curbuf->b_ml.ml_line_len = textlen + len; 568 curbuf->b_ml.ml_flags |= ML_LINE_DIRTY; 569 } 570 571 static proptype_T * 572 find_type_by_id(hashtab_T *ht, int id) 573 { 574 long todo; 575 hashitem_T *hi; 576 577 if (ht == NULL) 578 return NULL; 579 580 // TODO: Make this faster by keeping a list of types sorted on ID and use 581 // a binary search. 582 583 todo = (long)ht->ht_used; 584 for (hi = ht->ht_array; todo > 0; ++hi) 585 { 586 if (!HASHITEM_EMPTY(hi)) 587 { 588 proptype_T *prop = HI2PT(hi); 589 590 if (prop->pt_id == id) 591 return prop; 592 --todo; 593 } 594 } 595 return NULL; 596 } 597 598 /* 599 * Fill 'dict' with text properties in 'prop'. 600 */ 601 static void 602 prop_fill_dict(dict_T *dict, textprop_T *prop, buf_T *buf) 603 { 604 proptype_T *pt; 605 int buflocal = TRUE; 606 607 dict_add_number(dict, "col", prop->tp_col); 608 dict_add_number(dict, "length", prop->tp_len); 609 dict_add_number(dict, "id", prop->tp_id); 610 dict_add_number(dict, "start", !(prop->tp_flags & TP_FLAG_CONT_PREV)); 611 dict_add_number(dict, "end", !(prop->tp_flags & TP_FLAG_CONT_NEXT)); 612 613 pt = find_type_by_id(buf->b_proptypes, prop->tp_type); 614 if (pt == NULL) 615 { 616 pt = find_type_by_id(global_proptypes, prop->tp_type); 617 buflocal = FALSE; 618 } 619 if (pt != NULL) 620 dict_add_string(dict, "type", pt->pt_name); 621 622 if (buflocal) 623 dict_add_number(dict, "type_bufnr", buf->b_fnum); 624 else 625 dict_add_number(dict, "type_bufnr", 0); 626 } 627 628 /* 629 * Find a property type by ID in "buf" or globally. 630 * Returns NULL if not found. 631 */ 632 proptype_T * 633 text_prop_type_by_id(buf_T *buf, int id) 634 { 635 proptype_T *type; 636 637 type = find_type_by_id(buf->b_proptypes, id); 638 if (type == NULL) 639 type = find_type_by_id(global_proptypes, id); 640 return type; 641 } 642 643 /* 644 * prop_clear({lnum} [, {lnum_end} [, {bufnr}]]) 645 */ 646 void 647 f_prop_clear(typval_T *argvars, typval_T *rettv UNUSED) 648 { 649 linenr_T start; 650 linenr_T end; 651 linenr_T lnum; 652 buf_T *buf = curbuf; 653 int did_clear = FALSE; 654 655 if (in_vim9script() 656 && (check_for_number_arg(argvars, 0) == FAIL 657 || check_for_opt_number_arg(argvars, 1) == FAIL 658 || (argvars[1].v_type != VAR_UNKNOWN 659 && check_for_opt_dict_arg(argvars, 2) == FAIL))) 660 return; 661 662 start = tv_get_number(&argvars[0]); 663 end = start; 664 if (argvars[1].v_type != VAR_UNKNOWN) 665 { 666 end = tv_get_number(&argvars[1]); 667 if (argvars[2].v_type != VAR_UNKNOWN) 668 { 669 if (get_bufnr_from_arg(&argvars[2], &buf) == FAIL) 670 return; 671 } 672 } 673 if (start < 1 || end < 1) 674 { 675 emsg(_(e_invalid_range)); 676 return; 677 } 678 679 for (lnum = start; lnum <= end; ++lnum) 680 { 681 char_u *text; 682 size_t len; 683 684 if (lnum > buf->b_ml.ml_line_count) 685 break; 686 text = ml_get_buf(buf, lnum, FALSE); 687 len = STRLEN(text) + 1; 688 if ((size_t)buf->b_ml.ml_line_len > len) 689 { 690 did_clear = TRUE; 691 if (!(buf->b_ml.ml_flags & ML_LINE_DIRTY)) 692 { 693 char_u *newtext = vim_strsave(text); 694 695 // need to allocate the line now 696 if (newtext == NULL) 697 return; 698 buf->b_ml.ml_line_ptr = newtext; 699 buf->b_ml.ml_flags |= ML_LINE_DIRTY; 700 } 701 buf->b_ml.ml_line_len = (int)len; 702 } 703 } 704 if (did_clear) 705 redraw_buf_later(buf, NOT_VALID); 706 } 707 708 /* 709 * prop_find({props} [, {direction}]) 710 */ 711 void 712 f_prop_find(typval_T *argvars, typval_T *rettv) 713 { 714 pos_T *cursor = &curwin->w_cursor; 715 dict_T *dict; 716 buf_T *buf = curbuf; 717 dictitem_T *di; 718 int lnum_start; 719 int start_pos_has_prop = 0; 720 int seen_end = 0; 721 int id = 0; 722 int id_found = FALSE; 723 int type_id = -1; 724 int skipstart = 0; 725 int lnum = -1; 726 int col = -1; 727 int dir = 1; // 1 = forward, -1 = backward 728 int both; 729 730 if (in_vim9script() 731 && (check_for_dict_arg(argvars, 0) == FAIL 732 || check_for_opt_string_arg(argvars, 1) == FAIL)) 733 return; 734 735 if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL) 736 { 737 emsg(_(e_dictreq)); 738 return; 739 } 740 dict = argvars[0].vval.v_dict; 741 742 if (get_bufnr_from_arg(&argvars[0], &buf) == FAIL) 743 return; 744 if (buf->b_ml.ml_mfp == NULL) 745 return; 746 747 if (argvars[1].v_type != VAR_UNKNOWN) 748 { 749 char_u *dir_s = tv_get_string(&argvars[1]); 750 751 if (*dir_s == 'b') 752 dir = -1; 753 else if (*dir_s != 'f') 754 { 755 emsg(_(e_invarg)); 756 return; 757 } 758 } 759 760 di = dict_find(dict, (char_u *)"lnum", -1); 761 if (di != NULL) 762 lnum = tv_get_number(&di->di_tv); 763 764 di = dict_find(dict, (char_u *)"col", -1); 765 if (di != NULL) 766 col = tv_get_number(&di->di_tv); 767 768 if (lnum == -1) 769 { 770 lnum = cursor->lnum; 771 col = cursor->col + 1; 772 } 773 else if (col == -1) 774 col = 1; 775 776 if (lnum < 1 || lnum > buf->b_ml.ml_line_count) 777 { 778 emsg(_(e_invalid_range)); 779 return; 780 } 781 782 skipstart = dict_get_bool(dict, (char_u *)"skipstart", 0); 783 784 if (dict_find(dict, (char_u *)"id", -1) != NULL) 785 { 786 id = dict_get_number(dict, (char_u *)"id"); 787 id_found = TRUE; 788 } 789 if (dict_find(dict, (char_u *)"type", -1)) 790 { 791 char_u *name = dict_get_string(dict, (char_u *)"type", FALSE); 792 proptype_T *type = lookup_prop_type(name, buf); 793 794 if (type == NULL) 795 return; 796 type_id = type->pt_id; 797 } 798 both = dict_get_bool(dict, (char_u *)"both", FALSE); 799 if (!id_found && type_id == -1) 800 { 801 emsg(_("E968: Need at least one of 'id' or 'type'")); 802 return; 803 } 804 if (both && (!id_found || type_id == -1)) 805 { 806 emsg(_("E860: Need 'id' and 'type' with 'both'")); 807 return; 808 } 809 810 lnum_start = lnum; 811 812 if (rettv_dict_alloc(rettv) == FAIL) 813 return; 814 815 while (1) 816 { 817 char_u *text = ml_get_buf(buf, lnum, FALSE); 818 size_t textlen = STRLEN(text) + 1; 819 int count = (int)((buf->b_ml.ml_line_len - textlen) 820 / sizeof(textprop_T)); 821 int i; 822 textprop_T prop; 823 int prop_start; 824 int prop_end; 825 826 for (i = 0; i < count; ++i) 827 { 828 mch_memmove(&prop, text + textlen + i * sizeof(textprop_T), 829 sizeof(textprop_T)); 830 831 if (lnum == lnum_start) 832 { 833 if (dir < 0) 834 { 835 if (col < prop.tp_col) 836 break; 837 } 838 else if (prop.tp_col + prop.tp_len - (prop.tp_len != 0) < col) 839 continue; 840 } 841 if (both ? prop.tp_id == id && prop.tp_type == type_id 842 : (id_found && prop.tp_id == id) 843 || prop.tp_type == type_id) 844 { 845 // Check if the starting position has text props. 846 if (lnum_start == lnum 847 && col >= prop.tp_col 848 && (col <= prop.tp_col + prop.tp_len 849 - (prop.tp_len != 0))) 850 start_pos_has_prop = 1; 851 852 prop_start = !(prop.tp_flags & TP_FLAG_CONT_PREV); 853 prop_end = !(prop.tp_flags & TP_FLAG_CONT_NEXT); 854 if (!prop_start && prop_end && dir > 0) 855 seen_end = 1; 856 857 // Skip lines without the start flag. 858 if (!prop_start) 859 { 860 // Always search backwards for start when search started 861 // on a prop and we're not skipping. 862 if (start_pos_has_prop && !skipstart) 863 dir = -1; 864 continue; 865 } 866 867 // If skipstart is true, skip the prop at start pos (even if 868 // continued from another line). 869 if (start_pos_has_prop && skipstart && !seen_end) 870 { 871 start_pos_has_prop = 0; 872 continue; 873 } 874 875 prop_fill_dict(rettv->vval.v_dict, &prop, buf); 876 dict_add_number(rettv->vval.v_dict, "lnum", lnum); 877 878 return; 879 } 880 } 881 882 if (dir > 0) 883 { 884 if (lnum >= buf->b_ml.ml_line_count) 885 break; 886 lnum++; 887 } 888 else 889 { 890 if (lnum <= 1) 891 break; 892 lnum--; 893 } 894 // Adjust col to indicate that we're continuing from prev/next line. 895 col = dir < 0 ? buf->b_ml.ml_line_len : 1; 896 } 897 } 898 899 /* 900 * prop_list({lnum} [, {bufnr}]) 901 */ 902 void 903 f_prop_list(typval_T *argvars, typval_T *rettv) 904 { 905 linenr_T lnum; 906 buf_T *buf = curbuf; 907 908 if (in_vim9script() 909 && (check_for_number_arg(argvars, 0) == FAIL 910 || check_for_opt_dict_arg(argvars, 1) == FAIL)) 911 return; 912 913 lnum = tv_get_number(&argvars[0]); 914 if (argvars[1].v_type != VAR_UNKNOWN) 915 { 916 if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL) 917 return; 918 } 919 if (lnum < 1 || lnum > buf->b_ml.ml_line_count) 920 { 921 emsg(_(e_invalid_range)); 922 return; 923 } 924 925 if (rettv_list_alloc(rettv) == OK) 926 { 927 char_u *text = ml_get_buf(buf, lnum, FALSE); 928 size_t textlen = STRLEN(text) + 1; 929 int count = (int)((buf->b_ml.ml_line_len - textlen) 930 / sizeof(textprop_T)); 931 int i; 932 textprop_T prop; 933 934 for (i = 0; i < count; ++i) 935 { 936 dict_T *d = dict_alloc(); 937 938 if (d == NULL) 939 break; 940 mch_memmove(&prop, text + textlen + i * sizeof(textprop_T), 941 sizeof(textprop_T)); 942 prop_fill_dict(d, &prop, buf); 943 list_append_dict(rettv->vval.v_list, d); 944 } 945 } 946 } 947 948 /* 949 * prop_remove({props} [, {lnum} [, {lnum_end}]]) 950 */ 951 void 952 f_prop_remove(typval_T *argvars, typval_T *rettv) 953 { 954 linenr_T start = 1; 955 linenr_T end = 0; 956 linenr_T lnum; 957 linenr_T first_changed = 0; 958 linenr_T last_changed = 0; 959 dict_T *dict; 960 buf_T *buf = curbuf; 961 int do_all; 962 int id = -1; 963 int type_id = -1; 964 int both; 965 966 rettv->vval.v_number = 0; 967 968 if (in_vim9script() 969 && (check_for_dict_arg(argvars, 0) == FAIL 970 || check_for_opt_number_arg(argvars, 1) == FAIL 971 || (argvars[1].v_type != VAR_UNKNOWN 972 && check_for_opt_number_arg(argvars, 2) == FAIL))) 973 return; 974 975 if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL) 976 { 977 emsg(_(e_invarg)); 978 return; 979 } 980 981 if (argvars[1].v_type != VAR_UNKNOWN) 982 { 983 start = tv_get_number(&argvars[1]); 984 end = start; 985 if (argvars[2].v_type != VAR_UNKNOWN) 986 end = tv_get_number(&argvars[2]); 987 if (start < 1 || end < 1) 988 { 989 emsg(_(e_invalid_range)); 990 return; 991 } 992 } 993 994 dict = argvars[0].vval.v_dict; 995 if (get_bufnr_from_arg(&argvars[0], &buf) == FAIL) 996 return; 997 if (buf->b_ml.ml_mfp == NULL) 998 return; 999 1000 do_all = dict_get_bool(dict, (char_u *)"all", FALSE); 1001 1002 if (dict_find(dict, (char_u *)"id", -1) != NULL) 1003 id = dict_get_number(dict, (char_u *)"id"); 1004 if (dict_find(dict, (char_u *)"type", -1)) 1005 { 1006 char_u *name = dict_get_string(dict, (char_u *)"type", FALSE); 1007 proptype_T *type = lookup_prop_type(name, buf); 1008 1009 if (type == NULL) 1010 return; 1011 type_id = type->pt_id; 1012 } 1013 both = dict_get_bool(dict, (char_u *)"both", FALSE); 1014 1015 if (id == -1 && type_id == -1) 1016 { 1017 emsg(_("E968: Need at least one of 'id' or 'type'")); 1018 return; 1019 } 1020 if (both && (id == -1 || type_id == -1)) 1021 { 1022 emsg(_("E860: Need 'id' and 'type' with 'both'")); 1023 return; 1024 } 1025 1026 if (end == 0) 1027 end = buf->b_ml.ml_line_count; 1028 for (lnum = start; lnum <= end; ++lnum) 1029 { 1030 char_u *text; 1031 size_t len; 1032 1033 if (lnum > buf->b_ml.ml_line_count) 1034 break; 1035 text = ml_get_buf(buf, lnum, FALSE); 1036 len = STRLEN(text) + 1; 1037 if ((size_t)buf->b_ml.ml_line_len > len) 1038 { 1039 static textprop_T textprop; // static because of alignment 1040 unsigned idx; 1041 1042 for (idx = 0; idx < (buf->b_ml.ml_line_len - len) 1043 / sizeof(textprop_T); ++idx) 1044 { 1045 char_u *cur_prop = buf->b_ml.ml_line_ptr + len 1046 + idx * sizeof(textprop_T); 1047 size_t taillen; 1048 1049 mch_memmove(&textprop, cur_prop, sizeof(textprop_T)); 1050 if (both ? textprop.tp_id == id && textprop.tp_type == type_id 1051 : textprop.tp_id == id || textprop.tp_type == type_id) 1052 { 1053 if (!(buf->b_ml.ml_flags & ML_LINE_DIRTY)) 1054 { 1055 char_u *newptr = alloc(buf->b_ml.ml_line_len); 1056 1057 // need to allocate the line to be able to change it 1058 if (newptr == NULL) 1059 return; 1060 mch_memmove(newptr, buf->b_ml.ml_line_ptr, 1061 buf->b_ml.ml_line_len); 1062 buf->b_ml.ml_line_ptr = newptr; 1063 buf->b_ml.ml_flags |= ML_LINE_DIRTY; 1064 1065 cur_prop = buf->b_ml.ml_line_ptr + len 1066 + idx * sizeof(textprop_T); 1067 } 1068 1069 taillen = buf->b_ml.ml_line_len - len 1070 - (idx + 1) * sizeof(textprop_T); 1071 if (taillen > 0) 1072 mch_memmove(cur_prop, cur_prop + sizeof(textprop_T), 1073 taillen); 1074 buf->b_ml.ml_line_len -= sizeof(textprop_T); 1075 --idx; 1076 1077 if (first_changed == 0) 1078 first_changed = lnum; 1079 last_changed = lnum; 1080 ++rettv->vval.v_number; 1081 if (!do_all) 1082 break; 1083 } 1084 } 1085 } 1086 } 1087 if (first_changed > 0) 1088 { 1089 changed_lines_buf(buf, first_changed, last_changed + 1, 0); 1090 redraw_buf_later(buf, VALID); 1091 } 1092 } 1093 1094 /* 1095 * Common for f_prop_type_add() and f_prop_type_change(). 1096 */ 1097 static void 1098 prop_type_set(typval_T *argvars, int add) 1099 { 1100 char_u *name; 1101 buf_T *buf = NULL; 1102 dict_T *dict; 1103 dictitem_T *di; 1104 proptype_T *prop; 1105 1106 if (in_vim9script() 1107 && (check_for_string_arg(argvars, 0) == FAIL 1108 || check_for_dict_arg(argvars, 1) == FAIL)) 1109 return; 1110 1111 name = tv_get_string(&argvars[0]); 1112 if (*name == NUL) 1113 { 1114 emsg(_(e_invarg)); 1115 return; 1116 } 1117 1118 if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL) 1119 return; 1120 dict = argvars[1].vval.v_dict; 1121 1122 prop = find_prop(name, buf); 1123 if (add) 1124 { 1125 hashtab_T **htp; 1126 1127 if (prop != NULL) 1128 { 1129 semsg(_("E969: Property type %s already defined"), name); 1130 return; 1131 } 1132 prop = alloc_clear(offsetof(proptype_T, pt_name) + STRLEN(name) + 1); 1133 if (prop == NULL) 1134 return; 1135 STRCPY(prop->pt_name, name); 1136 prop->pt_id = ++proptype_id; 1137 prop->pt_flags = PT_FLAG_COMBINE; 1138 htp = buf == NULL ? &global_proptypes : &buf->b_proptypes; 1139 if (*htp == NULL) 1140 { 1141 *htp = ALLOC_ONE(hashtab_T); 1142 if (*htp == NULL) 1143 { 1144 vim_free(prop); 1145 return; 1146 } 1147 hash_init(*htp); 1148 } 1149 hash_add(*htp, PT2HIKEY(prop)); 1150 } 1151 else 1152 { 1153 if (prop == NULL) 1154 { 1155 semsg(_(e_type_not_exist), name); 1156 return; 1157 } 1158 } 1159 1160 if (dict != NULL) 1161 { 1162 di = dict_find(dict, (char_u *)"highlight", -1); 1163 if (di != NULL) 1164 { 1165 char_u *highlight; 1166 int hl_id = 0; 1167 1168 highlight = dict_get_string(dict, (char_u *)"highlight", FALSE); 1169 if (highlight != NULL && *highlight != NUL) 1170 hl_id = syn_name2id(highlight); 1171 if (hl_id <= 0) 1172 { 1173 semsg(_("E970: Unknown highlight group name: '%s'"), 1174 highlight == NULL ? (char_u *)"" : highlight); 1175 return; 1176 } 1177 prop->pt_hl_id = hl_id; 1178 } 1179 1180 di = dict_find(dict, (char_u *)"combine", -1); 1181 if (di != NULL) 1182 { 1183 if (tv_get_bool(&di->di_tv)) 1184 prop->pt_flags |= PT_FLAG_COMBINE; 1185 else 1186 prop->pt_flags &= ~PT_FLAG_COMBINE; 1187 } 1188 1189 di = dict_find(dict, (char_u *)"priority", -1); 1190 if (di != NULL) 1191 prop->pt_priority = tv_get_number(&di->di_tv); 1192 1193 di = dict_find(dict, (char_u *)"start_incl", -1); 1194 if (di != NULL) 1195 { 1196 if (tv_get_bool(&di->di_tv)) 1197 prop->pt_flags |= PT_FLAG_INS_START_INCL; 1198 else 1199 prop->pt_flags &= ~PT_FLAG_INS_START_INCL; 1200 } 1201 1202 di = dict_find(dict, (char_u *)"end_incl", -1); 1203 if (di != NULL) 1204 { 1205 if (tv_get_bool(&di->di_tv)) 1206 prop->pt_flags |= PT_FLAG_INS_END_INCL; 1207 else 1208 prop->pt_flags &= ~PT_FLAG_INS_END_INCL; 1209 } 1210 } 1211 } 1212 1213 /* 1214 * prop_type_add({name}, {props}) 1215 */ 1216 void 1217 f_prop_type_add(typval_T *argvars, typval_T *rettv UNUSED) 1218 { 1219 prop_type_set(argvars, TRUE); 1220 } 1221 1222 /* 1223 * prop_type_change({name}, {props}) 1224 */ 1225 void 1226 f_prop_type_change(typval_T *argvars, typval_T *rettv UNUSED) 1227 { 1228 prop_type_set(argvars, FALSE); 1229 } 1230 1231 /* 1232 * prop_type_delete({name} [, {bufnr}]) 1233 */ 1234 void 1235 f_prop_type_delete(typval_T *argvars, typval_T *rettv UNUSED) 1236 { 1237 char_u *name; 1238 buf_T *buf = NULL; 1239 hashitem_T *hi; 1240 1241 if (in_vim9script() 1242 && (check_for_string_arg(argvars, 0) == FAIL 1243 || check_for_opt_dict_arg(argvars, 1) == FAIL)) 1244 return; 1245 1246 name = tv_get_string(&argvars[0]); 1247 if (*name == NUL) 1248 { 1249 emsg(_(e_invarg)); 1250 return; 1251 } 1252 1253 if (argvars[1].v_type != VAR_UNKNOWN) 1254 { 1255 if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL) 1256 return; 1257 } 1258 1259 hi = find_prop_hi(name, buf); 1260 if (hi != NULL) 1261 { 1262 hashtab_T *ht; 1263 proptype_T *prop = HI2PT(hi); 1264 1265 if (buf == NULL) 1266 ht = global_proptypes; 1267 else 1268 ht = buf->b_proptypes; 1269 hash_remove(ht, hi); 1270 vim_free(prop); 1271 } 1272 } 1273 1274 /* 1275 * prop_type_get({name} [, {props}]) 1276 */ 1277 void 1278 f_prop_type_get(typval_T *argvars, typval_T *rettv) 1279 { 1280 char_u *name; 1281 1282 if (in_vim9script() 1283 && (check_for_string_arg(argvars, 0) == FAIL 1284 || check_for_opt_dict_arg(argvars, 1) == FAIL)) 1285 return; 1286 1287 name = tv_get_string(&argvars[0]); 1288 if (*name == NUL) 1289 { 1290 emsg(_(e_invarg)); 1291 return; 1292 } 1293 if (rettv_dict_alloc(rettv) == OK) 1294 { 1295 proptype_T *prop = NULL; 1296 buf_T *buf = NULL; 1297 1298 if (argvars[1].v_type != VAR_UNKNOWN) 1299 { 1300 if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL) 1301 return; 1302 } 1303 1304 prop = find_prop(name, buf); 1305 if (prop != NULL) 1306 { 1307 dict_T *d = rettv->vval.v_dict; 1308 1309 if (prop->pt_hl_id > 0) 1310 dict_add_string(d, "highlight", syn_id2name(prop->pt_hl_id)); 1311 dict_add_number(d, "priority", prop->pt_priority); 1312 dict_add_number(d, "combine", 1313 (prop->pt_flags & PT_FLAG_COMBINE) ? 1 : 0); 1314 dict_add_number(d, "start_incl", 1315 (prop->pt_flags & PT_FLAG_INS_START_INCL) ? 1 : 0); 1316 dict_add_number(d, "end_incl", 1317 (prop->pt_flags & PT_FLAG_INS_END_INCL) ? 1 : 0); 1318 if (buf != NULL) 1319 dict_add_number(d, "bufnr", buf->b_fnum); 1320 } 1321 } 1322 } 1323 1324 static void 1325 list_types(hashtab_T *ht, list_T *l) 1326 { 1327 long todo; 1328 hashitem_T *hi; 1329 1330 todo = (long)ht->ht_used; 1331 for (hi = ht->ht_array; todo > 0; ++hi) 1332 { 1333 if (!HASHITEM_EMPTY(hi)) 1334 { 1335 proptype_T *prop = HI2PT(hi); 1336 1337 list_append_string(l, prop->pt_name, -1); 1338 --todo; 1339 } 1340 } 1341 } 1342 1343 /* 1344 * prop_type_list([{bufnr}]) 1345 */ 1346 void 1347 f_prop_type_list(typval_T *argvars, typval_T *rettv UNUSED) 1348 { 1349 buf_T *buf = NULL; 1350 1351 if (rettv_list_alloc(rettv) == OK) 1352 { 1353 if (in_vim9script() && check_for_opt_dict_arg(argvars, 0) == FAIL) 1354 return; 1355 1356 if (argvars[0].v_type != VAR_UNKNOWN) 1357 { 1358 if (get_bufnr_from_arg(&argvars[0], &buf) == FAIL) 1359 return; 1360 } 1361 if (buf == NULL) 1362 { 1363 if (global_proptypes != NULL) 1364 list_types(global_proptypes, rettv->vval.v_list); 1365 } 1366 else if (buf->b_proptypes != NULL) 1367 list_types(buf->b_proptypes, rettv->vval.v_list); 1368 } 1369 } 1370 1371 /* 1372 * Free all property types in "ht". 1373 */ 1374 static void 1375 clear_ht_prop_types(hashtab_T *ht) 1376 { 1377 long todo; 1378 hashitem_T *hi; 1379 1380 if (ht == NULL) 1381 return; 1382 1383 todo = (long)ht->ht_used; 1384 for (hi = ht->ht_array; todo > 0; ++hi) 1385 { 1386 if (!HASHITEM_EMPTY(hi)) 1387 { 1388 proptype_T *prop = HI2PT(hi); 1389 1390 vim_free(prop); 1391 --todo; 1392 } 1393 } 1394 1395 hash_clear(ht); 1396 vim_free(ht); 1397 } 1398 1399 #if defined(EXITFREE) || defined(PROTO) 1400 /* 1401 * Free all global property types. 1402 */ 1403 void 1404 clear_global_prop_types(void) 1405 { 1406 clear_ht_prop_types(global_proptypes); 1407 global_proptypes = NULL; 1408 } 1409 #endif 1410 1411 /* 1412 * Free all property types for "buf". 1413 */ 1414 void 1415 clear_buf_prop_types(buf_T *buf) 1416 { 1417 clear_ht_prop_types(buf->b_proptypes); 1418 buf->b_proptypes = NULL; 1419 } 1420 1421 // Struct used to return two values from adjust_prop(). 1422 typedef struct 1423 { 1424 int dirty; // if the property was changed 1425 int can_drop; // whether after this change, the prop may be removed 1426 } adjustres_T; 1427 1428 /* 1429 * Adjust the property for "added" bytes (can be negative) inserted at "col". 1430 * 1431 * Note that "col" is zero-based, while tp_col is one-based. 1432 * Only for the current buffer. 1433 * "flags" can have: 1434 * APC_SUBSTITUTE: Text is replaced, not inserted. 1435 */ 1436 static adjustres_T 1437 adjust_prop( 1438 textprop_T *prop, 1439 colnr_T col, 1440 int added, 1441 int flags) 1442 { 1443 proptype_T *pt = text_prop_type_by_id(curbuf, prop->tp_type); 1444 int start_incl = (pt != NULL 1445 && (pt->pt_flags & PT_FLAG_INS_START_INCL)) 1446 || (flags & APC_SUBSTITUTE); 1447 int end_incl = (pt != NULL 1448 && (pt->pt_flags & PT_FLAG_INS_END_INCL)); 1449 // Do not drop zero-width props if they later can increase in 1450 // size. 1451 int droppable = !(start_incl || end_incl); 1452 adjustres_T res = {TRUE, FALSE}; 1453 1454 if (added > 0) 1455 { 1456 if (col + 1 <= prop->tp_col 1457 - (start_incl || (prop->tp_len == 0 && end_incl))) 1458 // Change is entirely before the text property: Only shift 1459 prop->tp_col += added; 1460 else if (col + 1 < prop->tp_col + prop->tp_len + end_incl) 1461 // Insertion was inside text property 1462 prop->tp_len += added; 1463 } 1464 else if (prop->tp_col > col + 1) 1465 { 1466 if (prop->tp_col + added < col + 1) 1467 { 1468 prop->tp_len += (prop->tp_col - 1 - col) + added; 1469 prop->tp_col = col + 1; 1470 if (prop->tp_len <= 0) 1471 { 1472 prop->tp_len = 0; 1473 res.can_drop = droppable; 1474 } 1475 } 1476 else 1477 prop->tp_col += added; 1478 } 1479 else if (prop->tp_len > 0 && prop->tp_col + prop->tp_len > col) 1480 { 1481 int after = col - added - (prop->tp_col - 1 + prop->tp_len); 1482 1483 prop->tp_len += after > 0 ? added + after : added; 1484 res.can_drop = prop->tp_len <= 0 && droppable; 1485 } 1486 else 1487 res.dirty = FALSE; 1488 1489 return res; 1490 } 1491 1492 /* 1493 * Adjust the columns of text properties in line "lnum" after position "col" to 1494 * shift by "bytes_added" (can be negative). 1495 * Note that "col" is zero-based, while tp_col is one-based. 1496 * Only for the current buffer. 1497 * "flags" can have: 1498 * APC_SAVE_FOR_UNDO: Call u_savesub() before making changes to the line. 1499 * APC_SUBSTITUTE: Text is replaced, not inserted. 1500 * Caller is expected to check b_has_textprop and "bytes_added" being non-zero. 1501 * Returns TRUE when props were changed. 1502 */ 1503 int 1504 adjust_prop_columns( 1505 linenr_T lnum, 1506 colnr_T col, 1507 int bytes_added, 1508 int flags) 1509 { 1510 int proplen; 1511 char_u *props; 1512 int dirty = FALSE; 1513 int ri, wi; 1514 size_t textlen; 1515 1516 if (text_prop_frozen > 0) 1517 return FALSE; 1518 1519 proplen = get_text_props(curbuf, lnum, &props, TRUE); 1520 if (proplen == 0) 1521 return FALSE; 1522 textlen = curbuf->b_ml.ml_line_len - proplen * sizeof(textprop_T); 1523 1524 wi = 0; // write index 1525 for (ri = 0; ri < proplen; ++ri) 1526 { 1527 textprop_T prop; 1528 adjustres_T res; 1529 1530 mch_memmove(&prop, props + ri * sizeof(prop), sizeof(prop)); 1531 res = adjust_prop(&prop, col, bytes_added, flags); 1532 if (res.dirty) 1533 { 1534 // Save for undo if requested and not done yet. 1535 if ((flags & APC_SAVE_FOR_UNDO) && !dirty 1536 && u_savesub(lnum) == FAIL) 1537 return FALSE; 1538 dirty = TRUE; 1539 1540 // u_savesub() may have updated curbuf->b_ml, fetch it again 1541 if (curbuf->b_ml.ml_line_lnum != lnum) 1542 proplen = get_text_props(curbuf, lnum, &props, TRUE); 1543 } 1544 if (res.can_drop) 1545 continue; // Drop this text property 1546 mch_memmove(props + wi * sizeof(textprop_T), &prop, sizeof(textprop_T)); 1547 ++wi; 1548 } 1549 if (dirty) 1550 { 1551 colnr_T newlen = (int)textlen + wi * (colnr_T)sizeof(textprop_T); 1552 1553 if ((curbuf->b_ml.ml_flags & ML_LINE_DIRTY) == 0) 1554 curbuf->b_ml.ml_line_ptr = 1555 vim_memsave(curbuf->b_ml.ml_line_ptr, newlen); 1556 curbuf->b_ml.ml_flags |= ML_LINE_DIRTY; 1557 curbuf->b_ml.ml_line_len = newlen; 1558 } 1559 return dirty; 1560 } 1561 1562 /* 1563 * Adjust text properties for a line that was split in two. 1564 * "lnum_props" is the line that has the properties from before the split. 1565 * "lnum_top" is the top line. 1566 * "kept" is the number of bytes kept in the first line, while 1567 * "deleted" is the number of bytes deleted. 1568 */ 1569 void 1570 adjust_props_for_split( 1571 linenr_T lnum_props, 1572 linenr_T lnum_top, 1573 int kept, 1574 int deleted) 1575 { 1576 char_u *props; 1577 int count; 1578 garray_T prevprop; 1579 garray_T nextprop; 1580 int i; 1581 int skipped = kept + deleted; 1582 1583 if (!curbuf->b_has_textprop) 1584 return; 1585 1586 // Get the text properties from "lnum_props". 1587 count = get_text_props(curbuf, lnum_props, &props, FALSE); 1588 ga_init2(&prevprop, sizeof(textprop_T), 10); 1589 ga_init2(&nextprop, sizeof(textprop_T), 10); 1590 1591 // Keep the relevant ones in the first line, reducing the length if needed. 1592 // Copy the ones that include the split to the second line. 1593 // Move the ones after the split to the second line. 1594 for (i = 0; i < count; ++i) 1595 { 1596 textprop_T prop; 1597 proptype_T *pt; 1598 int start_incl, end_incl; 1599 int cont_prev, cont_next; 1600 1601 // copy the prop to an aligned structure 1602 mch_memmove(&prop, props + i * sizeof(textprop_T), sizeof(textprop_T)); 1603 1604 pt = text_prop_type_by_id(curbuf, prop.tp_type); 1605 start_incl = (pt != NULL && (pt->pt_flags & PT_FLAG_INS_START_INCL)); 1606 end_incl = (pt != NULL && (pt->pt_flags & PT_FLAG_INS_END_INCL)); 1607 cont_prev = prop.tp_col + !start_incl <= kept; 1608 cont_next = skipped <= prop.tp_col + prop.tp_len - !end_incl; 1609 1610 if (cont_prev && ga_grow(&prevprop, 1) == OK) 1611 { 1612 textprop_T *p = ((textprop_T *)prevprop.ga_data) + prevprop.ga_len; 1613 1614 *p = prop; 1615 ++prevprop.ga_len; 1616 if (p->tp_col + p->tp_len >= kept) 1617 p->tp_len = kept - p->tp_col; 1618 if (cont_next) 1619 p->tp_flags |= TP_FLAG_CONT_NEXT; 1620 } 1621 1622 // Only add the property to the next line if the length is bigger than 1623 // zero. 1624 if (cont_next && ga_grow(&nextprop, 1) == OK) 1625 { 1626 textprop_T *p = ((textprop_T *)nextprop.ga_data) + nextprop.ga_len; 1627 *p = prop; 1628 ++nextprop.ga_len; 1629 if (p->tp_col > skipped) 1630 p->tp_col -= skipped - 1; 1631 else 1632 { 1633 p->tp_len -= skipped - p->tp_col; 1634 p->tp_col = 1; 1635 } 1636 if (cont_prev) 1637 p->tp_flags |= TP_FLAG_CONT_PREV; 1638 } 1639 } 1640 1641 set_text_props(lnum_top, prevprop.ga_data, 1642 prevprop.ga_len * sizeof(textprop_T)); 1643 ga_clear(&prevprop); 1644 set_text_props(lnum_top + 1, nextprop.ga_data, 1645 nextprop.ga_len * sizeof(textprop_T)); 1646 ga_clear(&nextprop); 1647 } 1648 1649 /* 1650 * Prepend properties of joined line "lnum" to "new_props". 1651 */ 1652 void 1653 prepend_joined_props( 1654 char_u *new_props, 1655 int propcount, 1656 int *props_remaining, 1657 linenr_T lnum, 1658 int add_all, 1659 long col, 1660 int removed) 1661 { 1662 char_u *props; 1663 int proplen = get_text_props(curbuf, lnum, &props, FALSE); 1664 int i; 1665 1666 for (i = proplen; i-- > 0; ) 1667 { 1668 textprop_T prop; 1669 int end; 1670 1671 mch_memmove(&prop, props + i * sizeof(prop), sizeof(prop)); 1672 end = !(prop.tp_flags & TP_FLAG_CONT_NEXT); 1673 1674 adjust_prop(&prop, 0, -removed, 0); // Remove leading spaces 1675 adjust_prop(&prop, -1, col, 0); // Make line start at its final column 1676 1677 if (add_all || end) 1678 mch_memmove(new_props + --(*props_remaining) * sizeof(prop), 1679 &prop, sizeof(prop)); 1680 else 1681 { 1682 int j; 1683 int found = FALSE; 1684 1685 // Search for continuing prop. 1686 for (j = *props_remaining; j < propcount; ++j) 1687 { 1688 textprop_T op; 1689 1690 mch_memmove(&op, new_props + j * sizeof(op), sizeof(op)); 1691 if ((op.tp_flags & TP_FLAG_CONT_PREV) 1692 && op.tp_id == prop.tp_id && op.tp_type == prop.tp_type) 1693 { 1694 found = TRUE; 1695 op.tp_len += op.tp_col - prop.tp_col; 1696 op.tp_col = prop.tp_col; 1697 // Start/end is taken care of when deleting joined lines 1698 op.tp_flags = prop.tp_flags; 1699 mch_memmove(new_props + j * sizeof(op), &op, sizeof(op)); 1700 break; 1701 } 1702 } 1703 if (!found) 1704 internal_error("text property above joined line not found"); 1705 } 1706 } 1707 } 1708 1709 #endif // FEAT_PROP_POPUP 1710