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. 12 * 13 * Text properties are attached to the text. They move with the text when 14 * text is inserted/deleted. 15 * 16 * Text properties have a user specified ID number, which can be unique. 17 * Text properties have a type, which can be used to specify highlighting. 18 * 19 * TODO: 20 * - When using 'cursorline' attributes should be merged. (#3912) 21 * - Adjust text property column and length when text is inserted/deleted. 22 * -> a :substitute with a multi-line match 23 * -> search for changed_bytes() from misc1.c 24 * - Perhaps we only need TP_FLAG_CONT_NEXT and can drop TP_FLAG_CONT_PREV? 25 * - Add an arrray for global_proptypes, to quickly lookup a prop type by ID 26 * - Add an arrray for b_proptypes, to quickly lookup a prop type by ID 27 * - Checking the text length to detect text properties is slow. Use a flag in 28 * the index, like DB_MARKED? 29 * - Also test line2byte() with many lines, so that ml_updatechunk() is taken 30 * into account. 31 * - Add mechanism to keep track of changed lines, so that plugin can update 32 * text properties in these. 33 * - Perhaps have a window-local option to disable highlighting from text 34 * properties? 35 */ 36 37 #include "vim.h" 38 39 #if defined(FEAT_TEXT_PROP) || defined(PROTO) 40 41 /* 42 * In a hashtable item "hi_key" points to "pt_name" in a proptype_T. 43 * This avoids adding a pointer to the hashtable item. 44 * PT2HIKEY() converts a proptype pointer to a hashitem key pointer. 45 * HIKEY2PT() converts a hashitem key pointer to a proptype pointer. 46 * HI2PT() converts a hashitem pointer to a proptype pointer. 47 */ 48 #define PT2HIKEY(p) ((p)->pt_name) 49 #define HIKEY2PT(p) ((proptype_T *)((p) - offsetof(proptype_T, pt_name))) 50 #define HI2PT(hi) HIKEY2PT((hi)->hi_key) 51 52 // The global text property types. 53 static hashtab_T *global_proptypes = NULL; 54 55 // The last used text property type ID. 56 static int proptype_id = 0; 57 58 static char_u e_type_not_exist[] = N_("E971: Property type %s does not exist"); 59 static char_u e_invalid_col[] = N_("E964: Invalid column number: %ld"); 60 static char_u e_invalid_lnum[] = N_("E966: Invalid line number: %ld"); 61 62 /* 63 * Find a property type by name, return the hashitem. 64 * Returns NULL if the item can't be found. 65 */ 66 static hashitem_T * 67 find_prop_hi(char_u *name, buf_T *buf) 68 { 69 hashtab_T *ht; 70 hashitem_T *hi; 71 72 if (*name == NUL) 73 return NULL; 74 if (buf == NULL) 75 ht = global_proptypes; 76 else 77 ht = buf->b_proptypes; 78 79 if (ht == NULL) 80 return NULL; 81 hi = hash_find(ht, name); 82 if (HASHITEM_EMPTY(hi)) 83 return NULL; 84 return hi; 85 } 86 87 /* 88 * Like find_prop_hi() but return the property type. 89 */ 90 static proptype_T * 91 find_prop(char_u *name, buf_T *buf) 92 { 93 hashitem_T *hi = find_prop_hi(name, buf); 94 95 if (hi == NULL) 96 return NULL; 97 return HI2PT(hi); 98 } 99 100 /* 101 * Lookup a property type by name. First in "buf" and when not found in the 102 * global types. 103 * When not found gives an error message and returns NULL. 104 */ 105 static proptype_T * 106 lookup_prop_type(char_u *name, buf_T *buf) 107 { 108 proptype_T *type = find_prop(name, buf); 109 110 if (type == NULL) 111 type = find_prop(name, NULL); 112 if (type == NULL) 113 semsg(_(e_type_not_exist), name); 114 return type; 115 } 116 117 /* 118 * Get an optional "bufnr" item from the dict in "arg". 119 * When the argument is not used or "bufnr" is not present then "buf" is 120 * unchanged. 121 * If "bufnr" is valid or not present return OK. 122 * When "arg" is not a dict or "bufnr" is invalide return FAIL. 123 */ 124 static int 125 get_bufnr_from_arg(typval_T *arg, buf_T **buf) 126 { 127 dictitem_T *di; 128 129 if (arg->v_type != VAR_DICT) 130 { 131 emsg(_(e_dictreq)); 132 return FAIL; 133 } 134 if (arg->vval.v_dict == NULL) 135 return OK; // NULL dict is like an empty dict 136 di = dict_find(arg->vval.v_dict, (char_u *)"bufnr", -1); 137 if (di != NULL) 138 { 139 *buf = tv_get_buf(&di->di_tv, FALSE); 140 if (*buf == NULL) 141 return FAIL; 142 } 143 return OK; 144 } 145 146 /* 147 * prop_add({lnum}, {col}, {props}) 148 */ 149 void 150 f_prop_add(typval_T *argvars, typval_T *rettv UNUSED) 151 { 152 linenr_T lnum; 153 linenr_T start_lnum; 154 linenr_T end_lnum; 155 colnr_T start_col; 156 colnr_T end_col; 157 dict_T *dict; 158 char_u *type_name; 159 proptype_T *type; 160 buf_T *buf = curbuf; 161 int id = 0; 162 char_u *newtext; 163 int proplen; 164 size_t textlen; 165 char_u *props = NULL; 166 char_u *newprops; 167 textprop_T tmp_prop; 168 int i; 169 170 start_lnum = tv_get_number(&argvars[0]); 171 start_col = tv_get_number(&argvars[1]); 172 if (start_col < 1) 173 { 174 semsg(_(e_invalid_col), (long)start_col); 175 return; 176 } 177 if (argvars[2].v_type != VAR_DICT) 178 { 179 emsg(_(e_dictreq)); 180 return; 181 } 182 dict = argvars[2].vval.v_dict; 183 184 if (dict == NULL || dict_find(dict, (char_u *)"type", -1) == NULL) 185 { 186 emsg(_("E965: missing property type name")); 187 return; 188 } 189 type_name = dict_get_string(dict, (char_u *)"type", FALSE); 190 191 if (dict_find(dict, (char_u *)"end_lnum", -1) != NULL) 192 { 193 end_lnum = dict_get_number(dict, (char_u *)"end_lnum"); 194 if (end_lnum < start_lnum) 195 { 196 semsg(_(e_invargval), "end_lnum"); 197 return; 198 } 199 } 200 else 201 end_lnum = start_lnum; 202 203 if (dict_find(dict, (char_u *)"length", -1) != NULL) 204 { 205 long length = dict_get_number(dict, (char_u *)"length"); 206 207 if (length < 0 || end_lnum > start_lnum) 208 { 209 semsg(_(e_invargval), "length"); 210 return; 211 } 212 end_col = start_col + length; 213 } 214 else if (dict_find(dict, (char_u *)"end_col", -1) != NULL) 215 { 216 end_col = dict_get_number(dict, (char_u *)"end_col"); 217 if (end_col <= 0) 218 { 219 semsg(_(e_invargval), "end_col"); 220 return; 221 } 222 } 223 else if (start_lnum == end_lnum) 224 end_col = start_col; 225 else 226 end_col = 1; 227 228 if (dict_find(dict, (char_u *)"id", -1) != NULL) 229 id = dict_get_number(dict, (char_u *)"id"); 230 231 if (get_bufnr_from_arg(&argvars[2], &buf) == FAIL) 232 return; 233 234 type = lookup_prop_type(type_name, buf); 235 if (type == NULL) 236 return; 237 238 if (start_lnum < 1 || start_lnum > buf->b_ml.ml_line_count) 239 { 240 semsg(_(e_invalid_lnum), (long)start_lnum); 241 return; 242 } 243 if (end_lnum < start_lnum || end_lnum > buf->b_ml.ml_line_count) 244 { 245 semsg(_(e_invalid_lnum), (long)end_lnum); 246 return; 247 } 248 249 for (lnum = start_lnum; lnum <= end_lnum; ++lnum) 250 { 251 colnr_T col; // start column 252 long length; // in bytes 253 254 // Fetch the line to get the ml_line_len field updated. 255 proplen = get_text_props(buf, lnum, &props, TRUE); 256 textlen = buf->b_ml.ml_line_len - proplen * sizeof(textprop_T); 257 258 if (lnum == start_lnum) 259 col = start_col; 260 else 261 col = 1; 262 if (col - 1 > (colnr_T)textlen) 263 { 264 semsg(_(e_invalid_col), (long)start_col); 265 return; 266 } 267 268 if (lnum == end_lnum) 269 length = end_col - col; 270 else 271 length = (int)textlen - col + 1; 272 if (length > (long)textlen) 273 length = (int)textlen; // can include the end-of-line 274 if (length < 0) 275 length = 0; // zero-width property 276 277 // Allocate the new line with space for the new proprety. 278 newtext = alloc(buf->b_ml.ml_line_len + sizeof(textprop_T)); 279 if (newtext == NULL) 280 return; 281 // Copy the text, including terminating NUL. 282 mch_memmove(newtext, buf->b_ml.ml_line_ptr, textlen); 283 284 // Find the index where to insert the new property. 285 // Since the text properties are not aligned properly when stored with the 286 // text, we need to copy them as bytes before using it as a struct. 287 for (i = 0; i < proplen; ++i) 288 { 289 mch_memmove(&tmp_prop, props + i * sizeof(textprop_T), 290 sizeof(textprop_T)); 291 if (tmp_prop.tp_col >= col) 292 break; 293 } 294 newprops = newtext + textlen; 295 if (i > 0) 296 mch_memmove(newprops, props, sizeof(textprop_T) * i); 297 298 tmp_prop.tp_col = col; 299 tmp_prop.tp_len = length; 300 tmp_prop.tp_id = id; 301 tmp_prop.tp_type = type->pt_id; 302 tmp_prop.tp_flags = (lnum > start_lnum ? TP_FLAG_CONT_PREV : 0) 303 | (lnum < end_lnum ? TP_FLAG_CONT_NEXT : 0); 304 mch_memmove(newprops + i * sizeof(textprop_T), &tmp_prop, 305 sizeof(textprop_T)); 306 307 if (i < proplen) 308 mch_memmove(newprops + (i + 1) * sizeof(textprop_T), 309 props + i * sizeof(textprop_T), 310 sizeof(textprop_T) * (proplen - i)); 311 312 if (buf->b_ml.ml_flags & ML_LINE_DIRTY) 313 vim_free(buf->b_ml.ml_line_ptr); 314 buf->b_ml.ml_line_ptr = newtext; 315 buf->b_ml.ml_line_len += sizeof(textprop_T); 316 buf->b_ml.ml_flags |= ML_LINE_DIRTY; 317 } 318 319 buf->b_has_textprop = TRUE; // this is never reset 320 redraw_buf_later(buf, NOT_VALID); 321 } 322 323 /* 324 * Fetch the text properties for line "lnum" in buffer "buf". 325 * Returns the number of text properties and, when non-zero, a pointer to the 326 * first one in "props" (note that it is not aligned, therefore the char_u 327 * pointer). 328 */ 329 int 330 get_text_props(buf_T *buf, linenr_T lnum, char_u **props, int will_change) 331 { 332 char_u *text; 333 size_t textlen; 334 size_t proplen; 335 336 // Be quick when no text property types have been defined or the buffer, 337 // unless we are adding one. 338 if (!buf->b_has_textprop && !will_change) 339 return 0; 340 341 // Fetch the line to get the ml_line_len field updated. 342 text = ml_get_buf(buf, lnum, will_change); 343 textlen = STRLEN(text) + 1; 344 proplen = buf->b_ml.ml_line_len - textlen; 345 if (proplen % sizeof(textprop_T) != 0) 346 { 347 iemsg(_("E967: text property info corrupted")); 348 return 0; 349 } 350 if (proplen > 0) 351 *props = text + textlen; 352 return (int)(proplen / sizeof(textprop_T)); 353 } 354 355 /* 356 * Set the text properties for line "lnum" to "props" with length "len". 357 * If "len" is zero text properties are removed, "props" is not used. 358 * Any existing text properties are dropped. 359 * Only works for the current buffer. 360 */ 361 static void 362 set_text_props(linenr_T lnum, char_u *props, int len) 363 { 364 char_u *text; 365 char_u *newtext; 366 int textlen; 367 368 text = ml_get(lnum); 369 textlen = (int)STRLEN(text) + 1; 370 newtext = alloc(textlen + len); 371 if (newtext == NULL) 372 return; 373 mch_memmove(newtext, text, textlen); 374 if (len > 0) 375 mch_memmove(newtext + textlen, props, len); 376 if (curbuf->b_ml.ml_flags & ML_LINE_DIRTY) 377 vim_free(curbuf->b_ml.ml_line_ptr); 378 curbuf->b_ml.ml_line_ptr = newtext; 379 curbuf->b_ml.ml_line_len = textlen + len; 380 curbuf->b_ml.ml_flags |= ML_LINE_DIRTY; 381 } 382 383 static proptype_T * 384 find_type_by_id(hashtab_T *ht, int id) 385 { 386 long todo; 387 hashitem_T *hi; 388 389 if (ht == NULL) 390 return NULL; 391 392 // TODO: Make this faster by keeping a list of types sorted on ID and use 393 // a binary search. 394 395 todo = (long)ht->ht_used; 396 for (hi = ht->ht_array; todo > 0; ++hi) 397 { 398 if (!HASHITEM_EMPTY(hi)) 399 { 400 proptype_T *prop = HI2PT(hi); 401 402 if (prop->pt_id == id) 403 return prop; 404 --todo; 405 } 406 } 407 return NULL; 408 } 409 410 /* 411 * Find a property type by ID in "buf" or globally. 412 * Returns NULL if not found. 413 */ 414 proptype_T * 415 text_prop_type_by_id(buf_T *buf, int id) 416 { 417 proptype_T *type; 418 419 type = find_type_by_id(buf->b_proptypes, id); 420 if (type == NULL) 421 type = find_type_by_id(global_proptypes, id); 422 return type; 423 } 424 425 /* 426 * prop_clear({lnum} [, {lnum_end} [, {bufnr}]]) 427 */ 428 void 429 f_prop_clear(typval_T *argvars, typval_T *rettv UNUSED) 430 { 431 linenr_T start = tv_get_number(&argvars[0]); 432 linenr_T end = start; 433 linenr_T lnum; 434 buf_T *buf = curbuf; 435 436 if (argvars[1].v_type != VAR_UNKNOWN) 437 { 438 end = tv_get_number(&argvars[1]); 439 if (argvars[2].v_type != VAR_UNKNOWN) 440 { 441 if (get_bufnr_from_arg(&argvars[2], &buf) == FAIL) 442 return; 443 } 444 } 445 if (start < 1 || end < 1) 446 { 447 emsg(_(e_invrange)); 448 return; 449 } 450 451 for (lnum = start; lnum <= end; ++lnum) 452 { 453 char_u *text; 454 size_t len; 455 456 if (lnum > buf->b_ml.ml_line_count) 457 break; 458 text = ml_get_buf(buf, lnum, FALSE); 459 len = STRLEN(text) + 1; 460 if ((size_t)buf->b_ml.ml_line_len > len) 461 { 462 if (!(buf->b_ml.ml_flags & ML_LINE_DIRTY)) 463 { 464 char_u *newtext = vim_strsave(text); 465 466 // need to allocate the line now 467 if (newtext == NULL) 468 return; 469 buf->b_ml.ml_line_ptr = newtext; 470 buf->b_ml.ml_flags |= ML_LINE_DIRTY; 471 } 472 buf->b_ml.ml_line_len = (int)len; 473 } 474 } 475 redraw_buf_later(buf, NOT_VALID); 476 } 477 478 /* 479 * prop_list({lnum} [, {bufnr}]) 480 */ 481 void 482 f_prop_list(typval_T *argvars, typval_T *rettv) 483 { 484 linenr_T lnum = tv_get_number(&argvars[0]); 485 buf_T *buf = curbuf; 486 487 if (argvars[1].v_type != VAR_UNKNOWN) 488 { 489 if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL) 490 return; 491 } 492 if (lnum < 1 || lnum > buf->b_ml.ml_line_count) 493 { 494 emsg(_(e_invrange)); 495 return; 496 } 497 498 if (rettv_list_alloc(rettv) == OK) 499 { 500 char_u *text = ml_get_buf(buf, lnum, FALSE); 501 size_t textlen = STRLEN(text) + 1; 502 int count = (int)((buf->b_ml.ml_line_len - textlen) 503 / sizeof(textprop_T)); 504 int i; 505 textprop_T prop; 506 proptype_T *pt; 507 508 for (i = 0; i < count; ++i) 509 { 510 dict_T *d = dict_alloc(); 511 512 if (d == NULL) 513 break; 514 mch_memmove(&prop, text + textlen + i * sizeof(textprop_T), 515 sizeof(textprop_T)); 516 dict_add_number(d, "col", prop.tp_col); 517 dict_add_number(d, "length", prop.tp_len); 518 dict_add_number(d, "id", prop.tp_id); 519 dict_add_number(d, "start", !(prop.tp_flags & TP_FLAG_CONT_PREV)); 520 dict_add_number(d, "end", !(prop.tp_flags & TP_FLAG_CONT_NEXT)); 521 pt = text_prop_type_by_id(buf, prop.tp_type); 522 if (pt != NULL) 523 dict_add_string(d, "type", pt->pt_name); 524 525 list_append_dict(rettv->vval.v_list, d); 526 } 527 } 528 } 529 530 /* 531 * prop_remove({props} [, {lnum} [, {lnum_end}]]) 532 */ 533 void 534 f_prop_remove(typval_T *argvars, typval_T *rettv) 535 { 536 linenr_T start = 1; 537 linenr_T end = 0; 538 linenr_T lnum; 539 dict_T *dict; 540 buf_T *buf = curbuf; 541 dictitem_T *di; 542 int do_all = FALSE; 543 int id = -1; 544 int type_id = -1; 545 546 rettv->vval.v_number = 0; 547 if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL) 548 { 549 emsg(_(e_invarg)); 550 return; 551 } 552 553 if (argvars[1].v_type != VAR_UNKNOWN) 554 { 555 start = tv_get_number(&argvars[1]); 556 end = start; 557 if (argvars[2].v_type != VAR_UNKNOWN) 558 end = tv_get_number(&argvars[2]); 559 if (start < 1 || end < 1) 560 { 561 emsg(_(e_invrange)); 562 return; 563 } 564 } 565 566 dict = argvars[0].vval.v_dict; 567 di = dict_find(dict, (char_u *)"bufnr", -1); 568 if (di != NULL) 569 { 570 buf = tv_get_buf(&di->di_tv, FALSE); 571 if (buf == NULL) 572 return; 573 } 574 575 di = dict_find(dict, (char_u*)"all", -1); 576 if (di != NULL) 577 do_all = dict_get_number(dict, (char_u *)"all"); 578 579 if (dict_find(dict, (char_u *)"id", -1) != NULL) 580 id = dict_get_number(dict, (char_u *)"id"); 581 if (dict_find(dict, (char_u *)"type", -1)) 582 { 583 char_u *name = dict_get_string(dict, (char_u *)"type", FALSE); 584 proptype_T *type = lookup_prop_type(name, buf); 585 586 if (type == NULL) 587 return; 588 type_id = type->pt_id; 589 } 590 if (id == -1 && type_id == -1) 591 { 592 emsg(_("E968: Need at least one of 'id' or 'type'")); 593 return; 594 } 595 596 if (end == 0) 597 end = buf->b_ml.ml_line_count; 598 for (lnum = start; lnum <= end; ++lnum) 599 { 600 char_u *text; 601 size_t len; 602 603 if (lnum > buf->b_ml.ml_line_count) 604 break; 605 text = ml_get_buf(buf, lnum, FALSE); 606 len = STRLEN(text) + 1; 607 if ((size_t)buf->b_ml.ml_line_len > len) 608 { 609 static textprop_T textprop; // static because of alignment 610 unsigned idx; 611 612 for (idx = 0; idx < (buf->b_ml.ml_line_len - len) 613 / sizeof(textprop_T); ++idx) 614 { 615 char_u *cur_prop = buf->b_ml.ml_line_ptr + len 616 + idx * sizeof(textprop_T); 617 size_t taillen; 618 619 mch_memmove(&textprop, cur_prop, sizeof(textprop_T)); 620 if (textprop.tp_id == id || textprop.tp_type == type_id) 621 { 622 if (!(buf->b_ml.ml_flags & ML_LINE_DIRTY)) 623 { 624 char_u *newptr = alloc(buf->b_ml.ml_line_len); 625 626 // need to allocate the line to be able to change it 627 if (newptr == NULL) 628 return; 629 mch_memmove(newptr, buf->b_ml.ml_line_ptr, 630 buf->b_ml.ml_line_len); 631 buf->b_ml.ml_line_ptr = newptr; 632 buf->b_ml.ml_flags |= ML_LINE_DIRTY; 633 634 cur_prop = buf->b_ml.ml_line_ptr + len 635 + idx * sizeof(textprop_T); 636 } 637 638 taillen = buf->b_ml.ml_line_len - len 639 - (idx + 1) * sizeof(textprop_T); 640 if (taillen > 0) 641 mch_memmove(cur_prop, cur_prop + sizeof(textprop_T), 642 taillen); 643 buf->b_ml.ml_line_len -= sizeof(textprop_T); 644 --idx; 645 646 ++rettv->vval.v_number; 647 if (!do_all) 648 break; 649 } 650 } 651 } 652 } 653 redraw_buf_later(buf, NOT_VALID); 654 } 655 656 /* 657 * Common for f_prop_type_add() and f_prop_type_change(). 658 */ 659 void 660 prop_type_set(typval_T *argvars, int add) 661 { 662 char_u *name; 663 buf_T *buf = NULL; 664 dict_T *dict; 665 dictitem_T *di; 666 proptype_T *prop; 667 668 name = tv_get_string(&argvars[0]); 669 if (*name == NUL) 670 { 671 emsg(_(e_invarg)); 672 return; 673 } 674 675 if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL) 676 return; 677 dict = argvars[1].vval.v_dict; 678 679 prop = find_prop(name, buf); 680 if (add) 681 { 682 hashtab_T **htp; 683 684 if (prop != NULL) 685 { 686 semsg(_("E969: Property type %s already defined"), name); 687 return; 688 } 689 prop = (proptype_T *)alloc_clear((int)(sizeof(proptype_T) + STRLEN(name))); 690 if (prop == NULL) 691 return; 692 STRCPY(prop->pt_name, name); 693 prop->pt_id = ++proptype_id; 694 htp = buf == NULL ? &global_proptypes : &buf->b_proptypes; 695 if (*htp == NULL) 696 { 697 *htp = (hashtab_T *)alloc(sizeof(hashtab_T)); 698 if (*htp == NULL) 699 { 700 vim_free(prop); 701 return; 702 } 703 hash_init(*htp); 704 } 705 hash_add(*htp, PT2HIKEY(prop)); 706 } 707 else 708 { 709 if (prop == NULL) 710 { 711 semsg(_(e_type_not_exist), name); 712 return; 713 } 714 } 715 716 if (dict != NULL) 717 { 718 di = dict_find(dict, (char_u *)"highlight", -1); 719 if (di != NULL) 720 { 721 char_u *highlight; 722 int hl_id = 0; 723 724 highlight = dict_get_string(dict, (char_u *)"highlight", FALSE); 725 if (highlight != NULL && *highlight != NUL) 726 hl_id = syn_name2id(highlight); 727 if (hl_id <= 0) 728 { 729 semsg(_("E970: Unknown highlight group name: '%s'"), 730 highlight == NULL ? (char_u *)"" : highlight); 731 return; 732 } 733 prop->pt_hl_id = hl_id; 734 } 735 736 di = dict_find(dict, (char_u *)"priority", -1); 737 if (di != NULL) 738 prop->pt_priority = tv_get_number(&di->di_tv); 739 740 di = dict_find(dict, (char_u *)"start_incl", -1); 741 if (di != NULL) 742 { 743 if (tv_get_number(&di->di_tv)) 744 prop->pt_flags |= PT_FLAG_INS_START_INCL; 745 else 746 prop->pt_flags &= ~PT_FLAG_INS_START_INCL; 747 } 748 749 di = dict_find(dict, (char_u *)"end_incl", -1); 750 if (di != NULL) 751 { 752 if (tv_get_number(&di->di_tv)) 753 prop->pt_flags |= PT_FLAG_INS_END_INCL; 754 else 755 prop->pt_flags &= ~PT_FLAG_INS_END_INCL; 756 } 757 } 758 } 759 760 /* 761 * prop_type_add({name}, {props}) 762 */ 763 void 764 f_prop_type_add(typval_T *argvars, typval_T *rettv UNUSED) 765 { 766 prop_type_set(argvars, TRUE); 767 } 768 769 /* 770 * prop_type_change({name}, {props}) 771 */ 772 void 773 f_prop_type_change(typval_T *argvars, typval_T *rettv UNUSED) 774 { 775 prop_type_set(argvars, FALSE); 776 } 777 778 /* 779 * prop_type_delete({name} [, {bufnr}]) 780 */ 781 void 782 f_prop_type_delete(typval_T *argvars, typval_T *rettv UNUSED) 783 { 784 char_u *name; 785 buf_T *buf = NULL; 786 hashitem_T *hi; 787 788 name = tv_get_string(&argvars[0]); 789 if (*name == NUL) 790 { 791 emsg(_(e_invarg)); 792 return; 793 } 794 795 if (argvars[1].v_type != VAR_UNKNOWN) 796 { 797 if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL) 798 return; 799 } 800 801 hi = find_prop_hi(name, buf); 802 if (hi != NULL) 803 { 804 hashtab_T *ht; 805 proptype_T *prop = HI2PT(hi); 806 807 if (buf == NULL) 808 ht = global_proptypes; 809 else 810 ht = buf->b_proptypes; 811 hash_remove(ht, hi); 812 vim_free(prop); 813 } 814 } 815 816 /* 817 * prop_type_get({name} [, {bufnr}]) 818 */ 819 void 820 f_prop_type_get(typval_T *argvars, typval_T *rettv UNUSED) 821 { 822 char_u *name = tv_get_string(&argvars[0]); 823 824 if (*name == NUL) 825 { 826 emsg(_(e_invarg)); 827 return; 828 } 829 if (rettv_dict_alloc(rettv) == OK) 830 { 831 proptype_T *prop = NULL; 832 buf_T *buf = NULL; 833 834 if (argvars[1].v_type != VAR_UNKNOWN) 835 { 836 if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL) 837 return; 838 } 839 840 prop = find_prop(name, buf); 841 if (prop != NULL) 842 { 843 dict_T *d = rettv->vval.v_dict; 844 845 if (prop->pt_hl_id > 0) 846 dict_add_string(d, "highlight", syn_id2name(prop->pt_hl_id)); 847 dict_add_number(d, "priority", prop->pt_priority); 848 dict_add_number(d, "start_incl", 849 (prop->pt_flags & PT_FLAG_INS_START_INCL) ? 1 : 0); 850 dict_add_number(d, "end_incl", 851 (prop->pt_flags & PT_FLAG_INS_END_INCL) ? 1 : 0); 852 if (buf != NULL) 853 dict_add_number(d, "bufnr", buf->b_fnum); 854 } 855 } 856 } 857 858 static void 859 list_types(hashtab_T *ht, list_T *l) 860 { 861 long todo; 862 hashitem_T *hi; 863 864 todo = (long)ht->ht_used; 865 for (hi = ht->ht_array; todo > 0; ++hi) 866 { 867 if (!HASHITEM_EMPTY(hi)) 868 { 869 proptype_T *prop = HI2PT(hi); 870 871 list_append_string(l, prop->pt_name, -1); 872 --todo; 873 } 874 } 875 } 876 877 /* 878 * prop_type_list([{bufnr}]) 879 */ 880 void 881 f_prop_type_list(typval_T *argvars, typval_T *rettv UNUSED) 882 { 883 buf_T *buf = NULL; 884 885 if (rettv_list_alloc(rettv) == OK) 886 { 887 if (argvars[0].v_type != VAR_UNKNOWN) 888 { 889 if (get_bufnr_from_arg(&argvars[0], &buf) == FAIL) 890 return; 891 } 892 if (buf == NULL) 893 { 894 if (global_proptypes != NULL) 895 list_types(global_proptypes, rettv->vval.v_list); 896 } 897 else if (buf->b_proptypes != NULL) 898 list_types(buf->b_proptypes, rettv->vval.v_list); 899 } 900 } 901 902 /* 903 * Free all property types in "ht". 904 */ 905 static void 906 clear_ht_prop_types(hashtab_T *ht) 907 { 908 long todo; 909 hashitem_T *hi; 910 911 if (ht == NULL) 912 return; 913 914 todo = (long)ht->ht_used; 915 for (hi = ht->ht_array; todo > 0; ++hi) 916 { 917 if (!HASHITEM_EMPTY(hi)) 918 { 919 proptype_T *prop = HI2PT(hi); 920 921 vim_free(prop); 922 --todo; 923 } 924 } 925 926 hash_clear(ht); 927 vim_free(ht); 928 } 929 930 #if defined(EXITFREE) || defined(PROTO) 931 /* 932 * Free all global property types. 933 */ 934 void 935 clear_global_prop_types(void) 936 { 937 clear_ht_prop_types(global_proptypes); 938 global_proptypes = NULL; 939 } 940 #endif 941 942 /* 943 * Free all property types for "buf". 944 */ 945 void 946 clear_buf_prop_types(buf_T *buf) 947 { 948 clear_ht_prop_types(buf->b_proptypes); 949 buf->b_proptypes = NULL; 950 } 951 952 /* 953 * Adjust the columns of text properties in line "lnum" after position "col" to 954 * shift by "bytes_added" (can be negative). 955 * Note that "col" is zero-based, while tp_col is one-based. 956 * Only for the current buffer. 957 * Called is expected to check b_has_textprop and "bytes_added" being non-zero. 958 */ 959 void 960 adjust_prop_columns( 961 linenr_T lnum, 962 colnr_T col, 963 int bytes_added) 964 { 965 int proplen; 966 char_u *props; 967 textprop_T tmp_prop; 968 proptype_T *pt; 969 int dirty = FALSE; 970 int ri, wi; 971 size_t textlen; 972 973 if (text_prop_frozen > 0) 974 return; 975 976 proplen = get_text_props(curbuf, lnum, &props, TRUE); 977 if (proplen == 0) 978 return; 979 textlen = curbuf->b_ml.ml_line_len - proplen * sizeof(textprop_T); 980 981 wi = 0; // write index 982 for (ri = 0; ri < proplen; ++ri) 983 { 984 mch_memmove(&tmp_prop, props + ri * sizeof(textprop_T), 985 sizeof(textprop_T)); 986 pt = text_prop_type_by_id(curbuf, tmp_prop.tp_type); 987 988 if (bytes_added > 0 989 ? (tmp_prop.tp_col >= col 990 + (pt != NULL && (pt->pt_flags & PT_FLAG_INS_START_INCL) 991 ? 2 : 1)) 992 : (tmp_prop.tp_col > col + 1)) 993 { 994 tmp_prop.tp_col += bytes_added; 995 dirty = TRUE; 996 } 997 else if (tmp_prop.tp_len > 0 998 && tmp_prop.tp_col + tmp_prop.tp_len > col 999 + ((pt != NULL && (pt->pt_flags & PT_FLAG_INS_END_INCL)) 1000 ? 0 : 1)) 1001 { 1002 tmp_prop.tp_len += bytes_added; 1003 dirty = TRUE; 1004 if (tmp_prop.tp_len <= 0) 1005 continue; // drop this text property 1006 } 1007 mch_memmove(props + wi * sizeof(textprop_T), &tmp_prop, 1008 sizeof(textprop_T)); 1009 ++wi; 1010 } 1011 if (dirty) 1012 { 1013 colnr_T newlen = (int)textlen + wi * (colnr_T)sizeof(textprop_T); 1014 1015 if ((curbuf->b_ml.ml_flags & ML_LINE_DIRTY) == 0) 1016 curbuf->b_ml.ml_line_ptr = 1017 vim_memsave(curbuf->b_ml.ml_line_ptr, newlen); 1018 curbuf->b_ml.ml_flags |= ML_LINE_DIRTY; 1019 curbuf->b_ml.ml_line_len = newlen; 1020 } 1021 } 1022 1023 /* 1024 * Adjust text properties for a line that was split in two. 1025 * "lnum" is the newly inserted line. The text properties are now on the line 1026 * below it. "kept" is the number of bytes kept in the first line, while 1027 * "deleted" is the number of bytes deleted. 1028 */ 1029 void 1030 adjust_props_for_split(linenr_T lnum, int kept, int deleted) 1031 { 1032 char_u *props; 1033 int count; 1034 garray_T prevprop; 1035 garray_T nextprop; 1036 int i; 1037 int skipped = kept + deleted; 1038 1039 if (!curbuf->b_has_textprop) 1040 return; 1041 count = get_text_props(curbuf, lnum + 1, &props, FALSE); 1042 ga_init2(&prevprop, sizeof(textprop_T), 10); 1043 ga_init2(&nextprop, sizeof(textprop_T), 10); 1044 1045 // Get the text properties, which are at "lnum + 1". 1046 // Keep the relevant ones in the first line, reducing the length if needed. 1047 // Copy the ones that include the split to the second line. 1048 // Move the ones after the split to the second line. 1049 for (i = 0; i < count; ++i) 1050 { 1051 textprop_T prop; 1052 textprop_T *p; 1053 1054 // copy the prop to an aligned structure 1055 mch_memmove(&prop, props + i * sizeof(textprop_T), sizeof(textprop_T)); 1056 1057 if (prop.tp_col < kept && ga_grow(&prevprop, 1) == OK) 1058 { 1059 p = ((textprop_T *)prevprop.ga_data) + prevprop.ga_len; 1060 *p = prop; 1061 if (p->tp_col + p->tp_len >= kept) 1062 p->tp_len = kept - p->tp_col; 1063 ++prevprop.ga_len; 1064 } 1065 1066 if (prop.tp_col + prop.tp_len >= skipped && ga_grow(&nextprop, 1) == OK) 1067 { 1068 p = ((textprop_T *)nextprop.ga_data) + nextprop.ga_len; 1069 *p = prop; 1070 if (p->tp_col > skipped) 1071 p->tp_col -= skipped - 1; 1072 else 1073 { 1074 p->tp_len -= skipped - p->tp_col; 1075 p->tp_col = 1; 1076 } 1077 ++nextprop.ga_len; 1078 } 1079 } 1080 1081 set_text_props(lnum, prevprop.ga_data, prevprop.ga_len * sizeof(textprop_T)); 1082 ga_clear(&prevprop); 1083 1084 set_text_props(lnum + 1, nextprop.ga_data, nextprop.ga_len * sizeof(textprop_T)); 1085 ga_clear(&nextprop); 1086 } 1087 1088 #endif // FEAT_TEXT_PROP 1089