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