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