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 *)"combine", -1); 737 if (di != NULL) 738 { 739 if (tv_get_number(&di->di_tv)) 740 prop->pt_flags |= PT_FLAG_COMBINE; 741 else 742 prop->pt_flags &= ~PT_FLAG_COMBINE; 743 } 744 745 di = dict_find(dict, (char_u *)"priority", -1); 746 if (di != NULL) 747 prop->pt_priority = tv_get_number(&di->di_tv); 748 749 di = dict_find(dict, (char_u *)"start_incl", -1); 750 if (di != NULL) 751 { 752 if (tv_get_number(&di->di_tv)) 753 prop->pt_flags |= PT_FLAG_INS_START_INCL; 754 else 755 prop->pt_flags &= ~PT_FLAG_INS_START_INCL; 756 } 757 758 di = dict_find(dict, (char_u *)"end_incl", -1); 759 if (di != NULL) 760 { 761 if (tv_get_number(&di->di_tv)) 762 prop->pt_flags |= PT_FLAG_INS_END_INCL; 763 else 764 prop->pt_flags &= ~PT_FLAG_INS_END_INCL; 765 } 766 } 767 } 768 769 /* 770 * prop_type_add({name}, {props}) 771 */ 772 void 773 f_prop_type_add(typval_T *argvars, typval_T *rettv UNUSED) 774 { 775 prop_type_set(argvars, TRUE); 776 } 777 778 /* 779 * prop_type_change({name}, {props}) 780 */ 781 void 782 f_prop_type_change(typval_T *argvars, typval_T *rettv UNUSED) 783 { 784 prop_type_set(argvars, FALSE); 785 } 786 787 /* 788 * prop_type_delete({name} [, {bufnr}]) 789 */ 790 void 791 f_prop_type_delete(typval_T *argvars, typval_T *rettv UNUSED) 792 { 793 char_u *name; 794 buf_T *buf = NULL; 795 hashitem_T *hi; 796 797 name = tv_get_string(&argvars[0]); 798 if (*name == NUL) 799 { 800 emsg(_(e_invarg)); 801 return; 802 } 803 804 if (argvars[1].v_type != VAR_UNKNOWN) 805 { 806 if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL) 807 return; 808 } 809 810 hi = find_prop_hi(name, buf); 811 if (hi != NULL) 812 { 813 hashtab_T *ht; 814 proptype_T *prop = HI2PT(hi); 815 816 if (buf == NULL) 817 ht = global_proptypes; 818 else 819 ht = buf->b_proptypes; 820 hash_remove(ht, hi); 821 vim_free(prop); 822 } 823 } 824 825 /* 826 * prop_type_get({name} [, {bufnr}]) 827 */ 828 void 829 f_prop_type_get(typval_T *argvars, typval_T *rettv UNUSED) 830 { 831 char_u *name = tv_get_string(&argvars[0]); 832 833 if (*name == NUL) 834 { 835 emsg(_(e_invarg)); 836 return; 837 } 838 if (rettv_dict_alloc(rettv) == OK) 839 { 840 proptype_T *prop = NULL; 841 buf_T *buf = NULL; 842 843 if (argvars[1].v_type != VAR_UNKNOWN) 844 { 845 if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL) 846 return; 847 } 848 849 prop = find_prop(name, buf); 850 if (prop != NULL) 851 { 852 dict_T *d = rettv->vval.v_dict; 853 854 if (prop->pt_hl_id > 0) 855 dict_add_string(d, "highlight", syn_id2name(prop->pt_hl_id)); 856 dict_add_number(d, "priority", prop->pt_priority); 857 dict_add_number(d, "combine", 858 (prop->pt_flags & PT_FLAG_COMBINE) ? 1 : 0); 859 dict_add_number(d, "start_incl", 860 (prop->pt_flags & PT_FLAG_INS_START_INCL) ? 1 : 0); 861 dict_add_number(d, "end_incl", 862 (prop->pt_flags & PT_FLAG_INS_END_INCL) ? 1 : 0); 863 if (buf != NULL) 864 dict_add_number(d, "bufnr", buf->b_fnum); 865 } 866 } 867 } 868 869 static void 870 list_types(hashtab_T *ht, list_T *l) 871 { 872 long todo; 873 hashitem_T *hi; 874 875 todo = (long)ht->ht_used; 876 for (hi = ht->ht_array; todo > 0; ++hi) 877 { 878 if (!HASHITEM_EMPTY(hi)) 879 { 880 proptype_T *prop = HI2PT(hi); 881 882 list_append_string(l, prop->pt_name, -1); 883 --todo; 884 } 885 } 886 } 887 888 /* 889 * prop_type_list([{bufnr}]) 890 */ 891 void 892 f_prop_type_list(typval_T *argvars, typval_T *rettv UNUSED) 893 { 894 buf_T *buf = NULL; 895 896 if (rettv_list_alloc(rettv) == OK) 897 { 898 if (argvars[0].v_type != VAR_UNKNOWN) 899 { 900 if (get_bufnr_from_arg(&argvars[0], &buf) == FAIL) 901 return; 902 } 903 if (buf == NULL) 904 { 905 if (global_proptypes != NULL) 906 list_types(global_proptypes, rettv->vval.v_list); 907 } 908 else if (buf->b_proptypes != NULL) 909 list_types(buf->b_proptypes, rettv->vval.v_list); 910 } 911 } 912 913 /* 914 * Free all property types in "ht". 915 */ 916 static void 917 clear_ht_prop_types(hashtab_T *ht) 918 { 919 long todo; 920 hashitem_T *hi; 921 922 if (ht == NULL) 923 return; 924 925 todo = (long)ht->ht_used; 926 for (hi = ht->ht_array; todo > 0; ++hi) 927 { 928 if (!HASHITEM_EMPTY(hi)) 929 { 930 proptype_T *prop = HI2PT(hi); 931 932 vim_free(prop); 933 --todo; 934 } 935 } 936 937 hash_clear(ht); 938 vim_free(ht); 939 } 940 941 #if defined(EXITFREE) || defined(PROTO) 942 /* 943 * Free all global property types. 944 */ 945 void 946 clear_global_prop_types(void) 947 { 948 clear_ht_prop_types(global_proptypes); 949 global_proptypes = NULL; 950 } 951 #endif 952 953 /* 954 * Free all property types for "buf". 955 */ 956 void 957 clear_buf_prop_types(buf_T *buf) 958 { 959 clear_ht_prop_types(buf->b_proptypes); 960 buf->b_proptypes = NULL; 961 } 962 963 /* 964 * Adjust the columns of text properties in line "lnum" after position "col" to 965 * shift by "bytes_added" (can be negative). 966 * Note that "col" is zero-based, while tp_col is one-based. 967 * Only for the current buffer. 968 * Called is expected to check b_has_textprop and "bytes_added" being non-zero. 969 */ 970 void 971 adjust_prop_columns( 972 linenr_T lnum, 973 colnr_T col, 974 int bytes_added) 975 { 976 int proplen; 977 char_u *props; 978 textprop_T tmp_prop; 979 proptype_T *pt; 980 int dirty = FALSE; 981 int ri, wi; 982 size_t textlen; 983 984 if (text_prop_frozen > 0) 985 return; 986 987 proplen = get_text_props(curbuf, lnum, &props, TRUE); 988 if (proplen == 0) 989 return; 990 textlen = curbuf->b_ml.ml_line_len - proplen * sizeof(textprop_T); 991 992 wi = 0; // write index 993 for (ri = 0; ri < proplen; ++ri) 994 { 995 mch_memmove(&tmp_prop, props + ri * sizeof(textprop_T), 996 sizeof(textprop_T)); 997 pt = text_prop_type_by_id(curbuf, tmp_prop.tp_type); 998 999 if (bytes_added > 0 1000 ? (tmp_prop.tp_col >= col 1001 + (pt != NULL && (pt->pt_flags & PT_FLAG_INS_START_INCL) 1002 ? 2 : 1)) 1003 : (tmp_prop.tp_col > col + 1)) 1004 { 1005 tmp_prop.tp_col += bytes_added; 1006 dirty = TRUE; 1007 } 1008 else if (tmp_prop.tp_len > 0 1009 && tmp_prop.tp_col + tmp_prop.tp_len > col 1010 + ((pt != NULL && (pt->pt_flags & PT_FLAG_INS_END_INCL)) 1011 ? 0 : 1)) 1012 { 1013 tmp_prop.tp_len += bytes_added; 1014 dirty = TRUE; 1015 if (tmp_prop.tp_len <= 0) 1016 continue; // drop this text property 1017 } 1018 mch_memmove(props + wi * sizeof(textprop_T), &tmp_prop, 1019 sizeof(textprop_T)); 1020 ++wi; 1021 } 1022 if (dirty) 1023 { 1024 colnr_T newlen = (int)textlen + wi * (colnr_T)sizeof(textprop_T); 1025 1026 if ((curbuf->b_ml.ml_flags & ML_LINE_DIRTY) == 0) 1027 curbuf->b_ml.ml_line_ptr = 1028 vim_memsave(curbuf->b_ml.ml_line_ptr, newlen); 1029 curbuf->b_ml.ml_flags |= ML_LINE_DIRTY; 1030 curbuf->b_ml.ml_line_len = newlen; 1031 } 1032 } 1033 1034 /* 1035 * Adjust text properties for a line that was split in two. 1036 * "lnum" is the newly inserted line. The text properties are now on the line 1037 * below it. "kept" is the number of bytes kept in the first line, while 1038 * "deleted" is the number of bytes deleted. 1039 */ 1040 void 1041 adjust_props_for_split(linenr_T lnum, int kept, int deleted) 1042 { 1043 char_u *props; 1044 int count; 1045 garray_T prevprop; 1046 garray_T nextprop; 1047 int i; 1048 int skipped = kept + deleted; 1049 1050 if (!curbuf->b_has_textprop) 1051 return; 1052 count = get_text_props(curbuf, lnum + 1, &props, FALSE); 1053 ga_init2(&prevprop, sizeof(textprop_T), 10); 1054 ga_init2(&nextprop, sizeof(textprop_T), 10); 1055 1056 // Get the text properties, which are at "lnum + 1". 1057 // Keep the relevant ones in the first line, reducing the length if needed. 1058 // Copy the ones that include the split to the second line. 1059 // Move the ones after the split to the second line. 1060 for (i = 0; i < count; ++i) 1061 { 1062 textprop_T prop; 1063 textprop_T *p; 1064 1065 // copy the prop to an aligned structure 1066 mch_memmove(&prop, props + i * sizeof(textprop_T), sizeof(textprop_T)); 1067 1068 if (prop.tp_col < kept && ga_grow(&prevprop, 1) == OK) 1069 { 1070 p = ((textprop_T *)prevprop.ga_data) + prevprop.ga_len; 1071 *p = prop; 1072 if (p->tp_col + p->tp_len >= kept) 1073 p->tp_len = kept - p->tp_col; 1074 ++prevprop.ga_len; 1075 } 1076 1077 if (prop.tp_col + prop.tp_len >= skipped && ga_grow(&nextprop, 1) == OK) 1078 { 1079 p = ((textprop_T *)nextprop.ga_data) + nextprop.ga_len; 1080 *p = prop; 1081 if (p->tp_col > skipped) 1082 p->tp_col -= skipped - 1; 1083 else 1084 { 1085 p->tp_len -= skipped - p->tp_col; 1086 p->tp_col = 1; 1087 } 1088 ++nextprop.ga_len; 1089 } 1090 } 1091 1092 set_text_props(lnum, prevprop.ga_data, prevprop.ga_len * sizeof(textprop_T)); 1093 ga_clear(&prevprop); 1094 1095 set_text_props(lnum + 1, nextprop.ga_data, nextprop.ga_len * sizeof(textprop_T)); 1096 ga_clear(&nextprop); 1097 } 1098 1099 #endif // FEAT_TEXT_PROP 1100