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 * mark.c: functions for setting marks and jumping to them 12 */ 13 14 #include "vim.h" 15 16 /* 17 * This file contains routines to maintain and manipulate marks. 18 */ 19 20 /* 21 * If a named file mark's lnum is non-zero, it is valid. 22 * If a named file mark's fnum is non-zero, it is for an existing buffer, 23 * otherwise it is from .viminfo and namedfm[n].fname is the file name. 24 * There are marks 'A - 'Z (set by user) and '0 to '9 (set when writing 25 * viminfo). 26 */ 27 static xfmark_T namedfm[NMARKS + EXTRA_MARKS]; /* marks with file nr */ 28 29 static void fmarks_check_one(xfmark_T *fm, char_u *name, buf_T *buf); 30 static char_u *mark_line(pos_T *mp, int lead_len); 31 static void show_one_mark(int, char_u *, pos_T *, char_u *, int current); 32 static void mark_adjust_internal(linenr_T line1, linenr_T line2, long amount, 33 long amount_after, int adjust_folds); 34 35 /* 36 * Set named mark "c" at current cursor position. 37 * Returns OK on success, FAIL if bad name given. 38 */ 39 int 40 setmark(int c) 41 { 42 return setmark_pos(c, &curwin->w_cursor, curbuf->b_fnum); 43 } 44 45 /* 46 * Set named mark "c" to position "pos". 47 * When "c" is upper case use file "fnum". 48 * Returns OK on success, FAIL if bad name given. 49 */ 50 int 51 setmark_pos(int c, pos_T *pos, int fnum) 52 { 53 int i; 54 buf_T *buf; 55 56 /* Check for a special key (may cause islower() to crash). */ 57 if (c < 0) 58 return FAIL; 59 60 if (c == '\'' || c == '`') 61 { 62 if (pos == &curwin->w_cursor) 63 { 64 setpcmark(); 65 /* keep it even when the cursor doesn't move */ 66 curwin->w_prev_pcmark = curwin->w_pcmark; 67 } 68 else 69 curwin->w_pcmark = *pos; 70 return OK; 71 } 72 73 buf = buflist_findnr(fnum); 74 if (buf == NULL) 75 return FAIL; 76 77 if (c == '"') 78 { 79 buf->b_last_cursor = *pos; 80 return OK; 81 } 82 83 /* Allow setting '[ and '] for an autocommand that simulates reading a 84 * file. */ 85 if (c == '[') 86 { 87 buf->b_op_start = *pos; 88 return OK; 89 } 90 if (c == ']') 91 { 92 buf->b_op_end = *pos; 93 return OK; 94 } 95 96 if (c == '<' || c == '>') 97 { 98 if (c == '<') 99 buf->b_visual.vi_start = *pos; 100 else 101 buf->b_visual.vi_end = *pos; 102 if (buf->b_visual.vi_mode == NUL) 103 /* Visual_mode has not yet been set, use a sane default. */ 104 buf->b_visual.vi_mode = 'v'; 105 return OK; 106 } 107 108 if (ASCII_ISLOWER(c)) 109 { 110 i = c - 'a'; 111 buf->b_namedm[i] = *pos; 112 return OK; 113 } 114 if (ASCII_ISUPPER(c) || VIM_ISDIGIT(c)) 115 { 116 if (VIM_ISDIGIT(c)) 117 i = c - '0' + NMARKS; 118 else 119 i = c - 'A'; 120 namedfm[i].fmark.mark = *pos; 121 namedfm[i].fmark.fnum = fnum; 122 VIM_CLEAR(namedfm[i].fname); 123 #ifdef FEAT_VIMINFO 124 namedfm[i].time_set = vim_time(); 125 #endif 126 return OK; 127 } 128 return FAIL; 129 } 130 131 /* 132 * Set the previous context mark to the current position and add it to the 133 * jump list. 134 */ 135 void 136 setpcmark(void) 137 { 138 #ifdef FEAT_JUMPLIST 139 int i; 140 xfmark_T *fm; 141 #endif 142 #ifdef JUMPLIST_ROTATE 143 xfmark_T tempmark; 144 #endif 145 146 /* for :global the mark is set only once */ 147 if (global_busy || listcmd_busy || cmdmod.keepjumps) 148 return; 149 150 curwin->w_prev_pcmark = curwin->w_pcmark; 151 curwin->w_pcmark = curwin->w_cursor; 152 153 #ifdef FEAT_JUMPLIST 154 # ifdef JUMPLIST_ROTATE 155 /* 156 * If last used entry is not at the top, put it at the top by rotating 157 * the stack until it is (the newer entries will be at the bottom). 158 * Keep one entry (the last used one) at the top. 159 */ 160 if (curwin->w_jumplistidx < curwin->w_jumplistlen) 161 ++curwin->w_jumplistidx; 162 while (curwin->w_jumplistidx < curwin->w_jumplistlen) 163 { 164 tempmark = curwin->w_jumplist[curwin->w_jumplistlen - 1]; 165 for (i = curwin->w_jumplistlen - 1; i > 0; --i) 166 curwin->w_jumplist[i] = curwin->w_jumplist[i - 1]; 167 curwin->w_jumplist[0] = tempmark; 168 ++curwin->w_jumplistidx; 169 } 170 # endif 171 172 /* If jumplist is full: remove oldest entry */ 173 if (++curwin->w_jumplistlen > JUMPLISTSIZE) 174 { 175 curwin->w_jumplistlen = JUMPLISTSIZE; 176 vim_free(curwin->w_jumplist[0].fname); 177 for (i = 1; i < JUMPLISTSIZE; ++i) 178 curwin->w_jumplist[i - 1] = curwin->w_jumplist[i]; 179 } 180 curwin->w_jumplistidx = curwin->w_jumplistlen; 181 fm = &curwin->w_jumplist[curwin->w_jumplistlen - 1]; 182 183 fm->fmark.mark = curwin->w_pcmark; 184 fm->fmark.fnum = curbuf->b_fnum; 185 fm->fname = NULL; 186 # ifdef FEAT_VIMINFO 187 fm->time_set = vim_time(); 188 # endif 189 #endif 190 } 191 192 /* 193 * To change context, call setpcmark(), then move the current position to 194 * where ever, then call checkpcmark(). This ensures that the previous 195 * context will only be changed if the cursor moved to a different line. 196 * If pcmark was deleted (with "dG") the previous mark is restored. 197 */ 198 void 199 checkpcmark(void) 200 { 201 if (curwin->w_prev_pcmark.lnum != 0 202 && (EQUAL_POS(curwin->w_pcmark, curwin->w_cursor) 203 || curwin->w_pcmark.lnum == 0)) 204 { 205 curwin->w_pcmark = curwin->w_prev_pcmark; 206 curwin->w_prev_pcmark.lnum = 0; /* Show it has been checked */ 207 } 208 } 209 210 #if defined(FEAT_JUMPLIST) || defined(PROTO) 211 /* 212 * move "count" positions in the jump list (count may be negative) 213 */ 214 pos_T * 215 movemark(int count) 216 { 217 pos_T *pos; 218 xfmark_T *jmp; 219 220 cleanup_jumplist(curwin, TRUE); 221 222 if (curwin->w_jumplistlen == 0) /* nothing to jump to */ 223 return (pos_T *)NULL; 224 225 for (;;) 226 { 227 if (curwin->w_jumplistidx + count < 0 228 || curwin->w_jumplistidx + count >= curwin->w_jumplistlen) 229 return (pos_T *)NULL; 230 231 /* 232 * if first CTRL-O or CTRL-I command after a jump, add cursor position 233 * to list. Careful: If there are duplicates (CTRL-O immediately after 234 * starting Vim on a file), another entry may have been removed. 235 */ 236 if (curwin->w_jumplistidx == curwin->w_jumplistlen) 237 { 238 setpcmark(); 239 --curwin->w_jumplistidx; /* skip the new entry */ 240 if (curwin->w_jumplistidx + count < 0) 241 return (pos_T *)NULL; 242 } 243 244 curwin->w_jumplistidx += count; 245 246 jmp = curwin->w_jumplist + curwin->w_jumplistidx; 247 if (jmp->fmark.fnum == 0) 248 fname2fnum(jmp); 249 if (jmp->fmark.fnum != curbuf->b_fnum) 250 { 251 /* jump to other file */ 252 if (buflist_findnr(jmp->fmark.fnum) == NULL) 253 { /* Skip this one .. */ 254 count += count < 0 ? -1 : 1; 255 continue; 256 } 257 if (buflist_getfile(jmp->fmark.fnum, jmp->fmark.mark.lnum, 258 0, FALSE) == FAIL) 259 return (pos_T *)NULL; 260 /* Set lnum again, autocommands my have changed it */ 261 curwin->w_cursor = jmp->fmark.mark; 262 pos = (pos_T *)-1; 263 } 264 else 265 pos = &(jmp->fmark.mark); 266 return pos; 267 } 268 } 269 270 /* 271 * Move "count" positions in the changelist (count may be negative). 272 */ 273 pos_T * 274 movechangelist(int count) 275 { 276 int n; 277 278 if (curbuf->b_changelistlen == 0) /* nothing to jump to */ 279 return (pos_T *)NULL; 280 281 n = curwin->w_changelistidx; 282 if (n + count < 0) 283 { 284 if (n == 0) 285 return (pos_T *)NULL; 286 n = 0; 287 } 288 else if (n + count >= curbuf->b_changelistlen) 289 { 290 if (n == curbuf->b_changelistlen - 1) 291 return (pos_T *)NULL; 292 n = curbuf->b_changelistlen - 1; 293 } 294 else 295 n += count; 296 curwin->w_changelistidx = n; 297 return curbuf->b_changelist + n; 298 } 299 #endif 300 301 /* 302 * Find mark "c" in buffer pointed to by "buf". 303 * If "changefile" is TRUE it's allowed to edit another file for '0, 'A, etc. 304 * If "fnum" is not NULL store the fnum there for '0, 'A etc., don't edit 305 * another file. 306 * Returns: 307 * - pointer to pos_T if found. lnum is 0 when mark not set, -1 when mark is 308 * in another file which can't be gotten. (caller needs to check lnum!) 309 * - NULL if there is no mark called 'c'. 310 * - -1 if mark is in other file and jumped there (only if changefile is TRUE) 311 */ 312 pos_T * 313 getmark_buf(buf_T *buf, int c, int changefile) 314 { 315 return getmark_buf_fnum(buf, c, changefile, NULL); 316 } 317 318 pos_T * 319 getmark(int c, int changefile) 320 { 321 return getmark_buf_fnum(curbuf, c, changefile, NULL); 322 } 323 324 pos_T * 325 getmark_buf_fnum( 326 buf_T *buf, 327 int c, 328 int changefile, 329 int *fnum) 330 { 331 pos_T *posp; 332 pos_T *startp, *endp; 333 static pos_T pos_copy; 334 335 posp = NULL; 336 337 /* Check for special key, can't be a mark name and might cause islower() 338 * to crash. */ 339 if (c < 0) 340 return posp; 341 #ifndef EBCDIC 342 if (c > '~') /* check for islower()/isupper() */ 343 ; 344 else 345 #endif 346 if (c == '\'' || c == '`') /* previous context mark */ 347 { 348 pos_copy = curwin->w_pcmark; /* need to make a copy because */ 349 posp = &pos_copy; /* w_pcmark may be changed soon */ 350 } 351 else if (c == '"') /* to pos when leaving buffer */ 352 posp = &(buf->b_last_cursor); 353 else if (c == '^') /* to where Insert mode stopped */ 354 posp = &(buf->b_last_insert); 355 else if (c == '.') /* to where last change was made */ 356 posp = &(buf->b_last_change); 357 else if (c == '[') /* to start of previous operator */ 358 posp = &(buf->b_op_start); 359 else if (c == ']') /* to end of previous operator */ 360 posp = &(buf->b_op_end); 361 else if (c == '{' || c == '}') /* to previous/next paragraph */ 362 { 363 pos_T pos; 364 oparg_T oa; 365 int slcb = listcmd_busy; 366 367 pos = curwin->w_cursor; 368 listcmd_busy = TRUE; /* avoid that '' is changed */ 369 if (findpar(&oa.inclusive, 370 c == '}' ? FORWARD : BACKWARD, 1L, NUL, FALSE)) 371 { 372 pos_copy = curwin->w_cursor; 373 posp = &pos_copy; 374 } 375 curwin->w_cursor = pos; 376 listcmd_busy = slcb; 377 } 378 else if (c == '(' || c == ')') /* to previous/next sentence */ 379 { 380 pos_T pos; 381 int slcb = listcmd_busy; 382 383 pos = curwin->w_cursor; 384 listcmd_busy = TRUE; /* avoid that '' is changed */ 385 if (findsent(c == ')' ? FORWARD : BACKWARD, 1L)) 386 { 387 pos_copy = curwin->w_cursor; 388 posp = &pos_copy; 389 } 390 curwin->w_cursor = pos; 391 listcmd_busy = slcb; 392 } 393 else if (c == '<' || c == '>') /* start/end of visual area */ 394 { 395 startp = &buf->b_visual.vi_start; 396 endp = &buf->b_visual.vi_end; 397 if (((c == '<') == LT_POS(*startp, *endp) || endp->lnum == 0) 398 && startp->lnum != 0) 399 posp = startp; 400 else 401 posp = endp; 402 /* 403 * For Visual line mode, set mark at begin or end of line 404 */ 405 if (buf->b_visual.vi_mode == 'V') 406 { 407 pos_copy = *posp; 408 posp = &pos_copy; 409 if (c == '<') 410 pos_copy.col = 0; 411 else 412 pos_copy.col = MAXCOL; 413 pos_copy.coladd = 0; 414 } 415 } 416 else if (ASCII_ISLOWER(c)) /* normal named mark */ 417 { 418 posp = &(buf->b_namedm[c - 'a']); 419 } 420 else if (ASCII_ISUPPER(c) || VIM_ISDIGIT(c)) /* named file mark */ 421 { 422 if (VIM_ISDIGIT(c)) 423 c = c - '0' + NMARKS; 424 else 425 c -= 'A'; 426 posp = &(namedfm[c].fmark.mark); 427 428 if (namedfm[c].fmark.fnum == 0) 429 fname2fnum(&namedfm[c]); 430 431 if (fnum != NULL) 432 *fnum = namedfm[c].fmark.fnum; 433 else if (namedfm[c].fmark.fnum != buf->b_fnum) 434 { 435 /* mark is in another file */ 436 posp = &pos_copy; 437 438 if (namedfm[c].fmark.mark.lnum != 0 439 && changefile && namedfm[c].fmark.fnum) 440 { 441 if (buflist_getfile(namedfm[c].fmark.fnum, 442 (linenr_T)1, GETF_SETMARK, FALSE) == OK) 443 { 444 /* Set the lnum now, autocommands could have changed it */ 445 curwin->w_cursor = namedfm[c].fmark.mark; 446 return (pos_T *)-1; 447 } 448 pos_copy.lnum = -1; /* can't get file */ 449 } 450 else 451 pos_copy.lnum = 0; /* mark exists, but is not valid in 452 current buffer */ 453 } 454 } 455 456 return posp; 457 } 458 459 /* 460 * Search for the next named mark in the current file. 461 * 462 * Returns pointer to pos_T of the next mark or NULL if no mark is found. 463 */ 464 pos_T * 465 getnextmark( 466 pos_T *startpos, /* where to start */ 467 int dir, /* direction for search */ 468 int begin_line) 469 { 470 int i; 471 pos_T *result = NULL; 472 pos_T pos; 473 474 pos = *startpos; 475 476 /* When searching backward and leaving the cursor on the first non-blank, 477 * position must be in a previous line. 478 * When searching forward and leaving the cursor on the first non-blank, 479 * position must be in a next line. */ 480 if (dir == BACKWARD && begin_line) 481 pos.col = 0; 482 else if (dir == FORWARD && begin_line) 483 pos.col = MAXCOL; 484 485 for (i = 0; i < NMARKS; i++) 486 { 487 if (curbuf->b_namedm[i].lnum > 0) 488 { 489 if (dir == FORWARD) 490 { 491 if ((result == NULL || LT_POS(curbuf->b_namedm[i], *result)) 492 && LT_POS(pos, curbuf->b_namedm[i])) 493 result = &curbuf->b_namedm[i]; 494 } 495 else 496 { 497 if ((result == NULL || LT_POS(*result, curbuf->b_namedm[i])) 498 && LT_POS(curbuf->b_namedm[i], pos)) 499 result = &curbuf->b_namedm[i]; 500 } 501 } 502 } 503 504 return result; 505 } 506 507 /* 508 * For an xtended filemark: set the fnum from the fname. 509 * This is used for marks obtained from the .viminfo file. It's postponed 510 * until the mark is used to avoid a long startup delay. 511 */ 512 void 513 fname2fnum(xfmark_T *fm) 514 { 515 char_u *p; 516 517 if (fm->fname != NULL) 518 { 519 /* 520 * First expand "~/" in the file name to the home directory. 521 * Don't expand the whole name, it may contain other '~' chars. 522 */ 523 if (fm->fname[0] == '~' && (fm->fname[1] == '/' 524 #ifdef BACKSLASH_IN_FILENAME 525 || fm->fname[1] == '\\' 526 #endif 527 )) 528 { 529 int len; 530 531 expand_env((char_u *)"~/", NameBuff, MAXPATHL); 532 len = (int)STRLEN(NameBuff); 533 vim_strncpy(NameBuff + len, fm->fname + 2, MAXPATHL - len - 1); 534 } 535 else 536 vim_strncpy(NameBuff, fm->fname, MAXPATHL - 1); 537 538 /* Try to shorten the file name. */ 539 mch_dirname(IObuff, IOSIZE); 540 p = shorten_fname(NameBuff, IObuff); 541 542 /* buflist_new() will call fmarks_check_names() */ 543 (void)buflist_new(NameBuff, p, (linenr_T)1, 0); 544 } 545 } 546 547 /* 548 * Check all file marks for a name that matches the file name in buf. 549 * May replace the name with an fnum. 550 * Used for marks that come from the .viminfo file. 551 */ 552 void 553 fmarks_check_names(buf_T *buf) 554 { 555 char_u *name; 556 int i; 557 #ifdef FEAT_JUMPLIST 558 win_T *wp; 559 #endif 560 561 if (buf->b_ffname == NULL) 562 return; 563 564 name = home_replace_save(buf, buf->b_ffname); 565 if (name == NULL) 566 return; 567 568 for (i = 0; i < NMARKS + EXTRA_MARKS; ++i) 569 fmarks_check_one(&namedfm[i], name, buf); 570 571 #ifdef FEAT_JUMPLIST 572 FOR_ALL_WINDOWS(wp) 573 { 574 for (i = 0; i < wp->w_jumplistlen; ++i) 575 fmarks_check_one(&wp->w_jumplist[i], name, buf); 576 } 577 #endif 578 579 vim_free(name); 580 } 581 582 static void 583 fmarks_check_one(xfmark_T *fm, char_u *name, buf_T *buf) 584 { 585 if (fm->fmark.fnum == 0 586 && fm->fname != NULL 587 && fnamecmp(name, fm->fname) == 0) 588 { 589 fm->fmark.fnum = buf->b_fnum; 590 VIM_CLEAR(fm->fname); 591 } 592 } 593 594 /* 595 * Check a if a position from a mark is valid. 596 * Give and error message and return FAIL if not. 597 */ 598 int 599 check_mark(pos_T *pos) 600 { 601 if (pos == NULL) 602 { 603 emsg(_(e_umark)); 604 return FAIL; 605 } 606 if (pos->lnum <= 0) 607 { 608 /* lnum is negative if mark is in another file can can't get that 609 * file, error message already give then. */ 610 if (pos->lnum == 0) 611 emsg(_(e_marknotset)); 612 return FAIL; 613 } 614 if (pos->lnum > curbuf->b_ml.ml_line_count) 615 { 616 emsg(_(e_markinval)); 617 return FAIL; 618 } 619 return OK; 620 } 621 622 /* 623 * clrallmarks() - clear all marks in the buffer 'buf' 624 * 625 * Used mainly when trashing the entire buffer during ":e" type commands 626 */ 627 void 628 clrallmarks(buf_T *buf) 629 { 630 static int i = -1; 631 632 if (i == -1) /* first call ever: initialize */ 633 for (i = 0; i < NMARKS + 1; i++) 634 { 635 namedfm[i].fmark.mark.lnum = 0; 636 namedfm[i].fname = NULL; 637 #ifdef FEAT_VIMINFO 638 namedfm[i].time_set = 0; 639 #endif 640 } 641 642 for (i = 0; i < NMARKS; i++) 643 buf->b_namedm[i].lnum = 0; 644 buf->b_op_start.lnum = 0; /* start/end op mark cleared */ 645 buf->b_op_end.lnum = 0; 646 buf->b_last_cursor.lnum = 1; /* '" mark cleared */ 647 buf->b_last_cursor.col = 0; 648 buf->b_last_cursor.coladd = 0; 649 buf->b_last_insert.lnum = 0; /* '^ mark cleared */ 650 buf->b_last_change.lnum = 0; /* '. mark cleared */ 651 #ifdef FEAT_JUMPLIST 652 buf->b_changelistlen = 0; 653 #endif 654 } 655 656 /* 657 * Get name of file from a filemark. 658 * When it's in the current buffer, return the text at the mark. 659 * Returns an allocated string. 660 */ 661 char_u * 662 fm_getname(fmark_T *fmark, int lead_len) 663 { 664 if (fmark->fnum == curbuf->b_fnum) /* current buffer */ 665 return mark_line(&(fmark->mark), lead_len); 666 return buflist_nr2name(fmark->fnum, FALSE, TRUE); 667 } 668 669 /* 670 * Return the line at mark "mp". Truncate to fit in window. 671 * The returned string has been allocated. 672 */ 673 static char_u * 674 mark_line(pos_T *mp, int lead_len) 675 { 676 char_u *s, *p; 677 int len; 678 679 if (mp->lnum == 0 || mp->lnum > curbuf->b_ml.ml_line_count) 680 return vim_strsave((char_u *)"-invalid-"); 681 // Allow for up to 5 bytes per character. 682 s = vim_strnsave(skipwhite(ml_get(mp->lnum)), (int)Columns * 5); 683 if (s == NULL) 684 return NULL; 685 // Truncate the line to fit it in the window. 686 len = 0; 687 for (p = s; *p != NUL; MB_PTR_ADV(p)) 688 { 689 len += ptr2cells(p); 690 if (len >= Columns - lead_len) 691 break; 692 } 693 *p = NUL; 694 return s; 695 } 696 697 /* 698 * print the marks 699 */ 700 void 701 do_marks(exarg_T *eap) 702 { 703 char_u *arg = eap->arg; 704 int i; 705 char_u *name; 706 707 if (arg != NULL && *arg == NUL) 708 arg = NULL; 709 710 show_one_mark('\'', arg, &curwin->w_pcmark, NULL, TRUE); 711 for (i = 0; i < NMARKS; ++i) 712 show_one_mark(i + 'a', arg, &curbuf->b_namedm[i], NULL, TRUE); 713 for (i = 0; i < NMARKS + EXTRA_MARKS; ++i) 714 { 715 if (namedfm[i].fmark.fnum != 0) 716 name = fm_getname(&namedfm[i].fmark, 15); 717 else 718 name = namedfm[i].fname; 719 if (name != NULL) 720 { 721 show_one_mark(i >= NMARKS ? i - NMARKS + '0' : i + 'A', 722 arg, &namedfm[i].fmark.mark, name, 723 namedfm[i].fmark.fnum == curbuf->b_fnum); 724 if (namedfm[i].fmark.fnum != 0) 725 vim_free(name); 726 } 727 } 728 show_one_mark('"', arg, &curbuf->b_last_cursor, NULL, TRUE); 729 show_one_mark('[', arg, &curbuf->b_op_start, NULL, TRUE); 730 show_one_mark(']', arg, &curbuf->b_op_end, NULL, TRUE); 731 show_one_mark('^', arg, &curbuf->b_last_insert, NULL, TRUE); 732 show_one_mark('.', arg, &curbuf->b_last_change, NULL, TRUE); 733 show_one_mark('<', arg, &curbuf->b_visual.vi_start, NULL, TRUE); 734 show_one_mark('>', arg, &curbuf->b_visual.vi_end, NULL, TRUE); 735 show_one_mark(-1, arg, NULL, NULL, FALSE); 736 } 737 738 static void 739 show_one_mark( 740 int c, 741 char_u *arg, 742 pos_T *p, 743 char_u *name_arg, 744 int current) /* in current file */ 745 { 746 static int did_title = FALSE; 747 int mustfree = FALSE; 748 char_u *name = name_arg; 749 750 if (c == -1) /* finish up */ 751 { 752 if (did_title) 753 did_title = FALSE; 754 else 755 { 756 if (arg == NULL) 757 msg(_("No marks set")); 758 else 759 semsg(_("E283: No marks matching \"%s\""), arg); 760 } 761 } 762 // don't output anything if 'q' typed at --more-- prompt 763 else if (!got_int 764 && (arg == NULL || vim_strchr(arg, c) != NULL) 765 && p->lnum != 0) 766 { 767 if (name == NULL && current) 768 { 769 name = mark_line(p, 15); 770 mustfree = TRUE; 771 } 772 if (!message_filtered(name)) 773 { 774 if (!did_title) 775 { 776 // Highlight title 777 msg_puts_title(_("\nmark line col file/text")); 778 did_title = TRUE; 779 } 780 msg_putchar('\n'); 781 if (!got_int) 782 { 783 sprintf((char *)IObuff, " %c %6ld %4d ", c, p->lnum, p->col); 784 msg_outtrans(IObuff); 785 if (name != NULL) 786 { 787 msg_outtrans_attr(name, current ? HL_ATTR(HLF_D) : 0); 788 } 789 } 790 out_flush(); // show one line at a time 791 } 792 if (mustfree) 793 vim_free(name); 794 } 795 } 796 797 /* 798 * ":delmarks[!] [marks]" 799 */ 800 void 801 ex_delmarks(exarg_T *eap) 802 { 803 char_u *p; 804 int from, to; 805 int i; 806 int lower; 807 int digit; 808 int n; 809 810 if (*eap->arg == NUL && eap->forceit) 811 /* clear all marks */ 812 clrallmarks(curbuf); 813 else if (eap->forceit) 814 emsg(_(e_invarg)); 815 else if (*eap->arg == NUL) 816 emsg(_(e_argreq)); 817 else 818 { 819 /* clear specified marks only */ 820 for (p = eap->arg; *p != NUL; ++p) 821 { 822 lower = ASCII_ISLOWER(*p); 823 digit = VIM_ISDIGIT(*p); 824 if (lower || digit || ASCII_ISUPPER(*p)) 825 { 826 if (p[1] == '-') 827 { 828 /* clear range of marks */ 829 from = *p; 830 to = p[2]; 831 if (!(lower ? ASCII_ISLOWER(p[2]) 832 : (digit ? VIM_ISDIGIT(p[2]) 833 : ASCII_ISUPPER(p[2]))) 834 || to < from) 835 { 836 semsg(_(e_invarg2), p); 837 return; 838 } 839 p += 2; 840 } 841 else 842 /* clear one lower case mark */ 843 from = to = *p; 844 845 for (i = from; i <= to; ++i) 846 { 847 if (lower) 848 curbuf->b_namedm[i - 'a'].lnum = 0; 849 else 850 { 851 if (digit) 852 n = i - '0' + NMARKS; 853 else 854 n = i - 'A'; 855 namedfm[n].fmark.mark.lnum = 0; 856 VIM_CLEAR(namedfm[n].fname); 857 #ifdef FEAT_VIMINFO 858 namedfm[n].time_set = 0; 859 #endif 860 } 861 } 862 } 863 else 864 switch (*p) 865 { 866 case '"': curbuf->b_last_cursor.lnum = 0; break; 867 case '^': curbuf->b_last_insert.lnum = 0; break; 868 case '.': curbuf->b_last_change.lnum = 0; break; 869 case '[': curbuf->b_op_start.lnum = 0; break; 870 case ']': curbuf->b_op_end.lnum = 0; break; 871 case '<': curbuf->b_visual.vi_start.lnum = 0; break; 872 case '>': curbuf->b_visual.vi_end.lnum = 0; break; 873 case ' ': break; 874 default: semsg(_(e_invarg2), p); 875 return; 876 } 877 } 878 } 879 } 880 881 #if defined(FEAT_JUMPLIST) || defined(PROTO) 882 /* 883 * print the jumplist 884 */ 885 void 886 ex_jumps(exarg_T *eap UNUSED) 887 { 888 int i; 889 char_u *name; 890 891 cleanup_jumplist(curwin, TRUE); 892 893 /* Highlight title */ 894 msg_puts_title(_("\n jump line col file/text")); 895 for (i = 0; i < curwin->w_jumplistlen && !got_int; ++i) 896 { 897 if (curwin->w_jumplist[i].fmark.mark.lnum != 0) 898 { 899 name = fm_getname(&curwin->w_jumplist[i].fmark, 16); 900 901 // apply :filter /pat/ or file name not available 902 if (name == NULL || message_filtered(name)) 903 { 904 vim_free(name); 905 continue; 906 } 907 908 msg_putchar('\n'); 909 if (got_int) 910 { 911 vim_free(name); 912 break; 913 } 914 sprintf((char *)IObuff, "%c %2d %5ld %4d ", 915 i == curwin->w_jumplistidx ? '>' : ' ', 916 i > curwin->w_jumplistidx ? i - curwin->w_jumplistidx 917 : curwin->w_jumplistidx - i, 918 curwin->w_jumplist[i].fmark.mark.lnum, 919 curwin->w_jumplist[i].fmark.mark.col); 920 msg_outtrans(IObuff); 921 msg_outtrans_attr(name, 922 curwin->w_jumplist[i].fmark.fnum == curbuf->b_fnum 923 ? HL_ATTR(HLF_D) : 0); 924 vim_free(name); 925 ui_breakcheck(); 926 } 927 out_flush(); 928 } 929 if (curwin->w_jumplistidx == curwin->w_jumplistlen) 930 msg_puts("\n>"); 931 } 932 933 void 934 ex_clearjumps(exarg_T *eap UNUSED) 935 { 936 free_jumplist(curwin); 937 curwin->w_jumplistlen = 0; 938 curwin->w_jumplistidx = 0; 939 } 940 941 /* 942 * print the changelist 943 */ 944 void 945 ex_changes(exarg_T *eap UNUSED) 946 { 947 int i; 948 char_u *name; 949 950 /* Highlight title */ 951 msg_puts_title(_("\nchange line col text")); 952 953 for (i = 0; i < curbuf->b_changelistlen && !got_int; ++i) 954 { 955 if (curbuf->b_changelist[i].lnum != 0) 956 { 957 msg_putchar('\n'); 958 if (got_int) 959 break; 960 sprintf((char *)IObuff, "%c %3d %5ld %4d ", 961 i == curwin->w_changelistidx ? '>' : ' ', 962 i > curwin->w_changelistidx ? i - curwin->w_changelistidx 963 : curwin->w_changelistidx - i, 964 (long)curbuf->b_changelist[i].lnum, 965 curbuf->b_changelist[i].col); 966 msg_outtrans(IObuff); 967 name = mark_line(&curbuf->b_changelist[i], 17); 968 if (name == NULL) 969 break; 970 msg_outtrans_attr(name, HL_ATTR(HLF_D)); 971 vim_free(name); 972 ui_breakcheck(); 973 } 974 out_flush(); 975 } 976 if (curwin->w_changelistidx == curbuf->b_changelistlen) 977 msg_puts("\n>"); 978 } 979 #endif 980 981 #define one_adjust(add) \ 982 { \ 983 lp = add; \ 984 if (*lp >= line1 && *lp <= line2) \ 985 { \ 986 if (amount == MAXLNUM) \ 987 *lp = 0; \ 988 else \ 989 *lp += amount; \ 990 } \ 991 else if (amount_after && *lp > line2) \ 992 *lp += amount_after; \ 993 } 994 995 /* don't delete the line, just put at first deleted line */ 996 #define one_adjust_nodel(add) \ 997 { \ 998 lp = add; \ 999 if (*lp >= line1 && *lp <= line2) \ 1000 { \ 1001 if (amount == MAXLNUM) \ 1002 *lp = line1; \ 1003 else \ 1004 *lp += amount; \ 1005 } \ 1006 else if (amount_after && *lp > line2) \ 1007 *lp += amount_after; \ 1008 } 1009 1010 /* 1011 * Adjust marks between line1 and line2 (inclusive) to move 'amount' lines. 1012 * Must be called before changed_*(), appended_lines() or deleted_lines(). 1013 * May be called before or after changing the text. 1014 * When deleting lines line1 to line2, use an 'amount' of MAXLNUM: The marks 1015 * within this range are made invalid. 1016 * If 'amount_after' is non-zero adjust marks after line2. 1017 * Example: Delete lines 34 and 35: mark_adjust(34, 35, MAXLNUM, -2); 1018 * Example: Insert two lines below 55: mark_adjust(56, MAXLNUM, 2, 0); 1019 * or: mark_adjust(56, 55, MAXLNUM, 2); 1020 */ 1021 void 1022 mark_adjust( 1023 linenr_T line1, 1024 linenr_T line2, 1025 long amount, 1026 long amount_after) 1027 { 1028 mark_adjust_internal(line1, line2, amount, amount_after, TRUE); 1029 } 1030 1031 void 1032 mark_adjust_nofold( 1033 linenr_T line1, 1034 linenr_T line2, 1035 long amount, 1036 long amount_after) 1037 { 1038 mark_adjust_internal(line1, line2, amount, amount_after, FALSE); 1039 } 1040 1041 static void 1042 mark_adjust_internal( 1043 linenr_T line1, 1044 linenr_T line2, 1045 long amount, 1046 long amount_after, 1047 int adjust_folds UNUSED) 1048 { 1049 int i; 1050 int fnum = curbuf->b_fnum; 1051 linenr_T *lp; 1052 win_T *win; 1053 tabpage_T *tab; 1054 static pos_T initpos = {1, 0, 0}; 1055 1056 if (line2 < line1 && amount_after == 0L) /* nothing to do */ 1057 return; 1058 1059 if (!cmdmod.lockmarks) 1060 { 1061 /* named marks, lower case and upper case */ 1062 for (i = 0; i < NMARKS; i++) 1063 { 1064 one_adjust(&(curbuf->b_namedm[i].lnum)); 1065 if (namedfm[i].fmark.fnum == fnum) 1066 one_adjust_nodel(&(namedfm[i].fmark.mark.lnum)); 1067 } 1068 for (i = NMARKS; i < NMARKS + EXTRA_MARKS; i++) 1069 { 1070 if (namedfm[i].fmark.fnum == fnum) 1071 one_adjust_nodel(&(namedfm[i].fmark.mark.lnum)); 1072 } 1073 1074 /* last Insert position */ 1075 one_adjust(&(curbuf->b_last_insert.lnum)); 1076 1077 /* last change position */ 1078 one_adjust(&(curbuf->b_last_change.lnum)); 1079 1080 /* last cursor position, if it was set */ 1081 if (!EQUAL_POS(curbuf->b_last_cursor, initpos)) 1082 one_adjust(&(curbuf->b_last_cursor.lnum)); 1083 1084 1085 #ifdef FEAT_JUMPLIST 1086 /* list of change positions */ 1087 for (i = 0; i < curbuf->b_changelistlen; ++i) 1088 one_adjust_nodel(&(curbuf->b_changelist[i].lnum)); 1089 #endif 1090 1091 /* Visual area */ 1092 one_adjust_nodel(&(curbuf->b_visual.vi_start.lnum)); 1093 one_adjust_nodel(&(curbuf->b_visual.vi_end.lnum)); 1094 1095 #ifdef FEAT_QUICKFIX 1096 /* quickfix marks */ 1097 qf_mark_adjust(NULL, line1, line2, amount, amount_after); 1098 /* location lists */ 1099 FOR_ALL_TAB_WINDOWS(tab, win) 1100 qf_mark_adjust(win, line1, line2, amount, amount_after); 1101 #endif 1102 1103 #ifdef FEAT_SIGNS 1104 sign_mark_adjust(line1, line2, amount, amount_after); 1105 #endif 1106 } 1107 1108 /* previous context mark */ 1109 one_adjust(&(curwin->w_pcmark.lnum)); 1110 1111 /* previous pcmark */ 1112 one_adjust(&(curwin->w_prev_pcmark.lnum)); 1113 1114 /* saved cursor for formatting */ 1115 if (saved_cursor.lnum != 0) 1116 one_adjust_nodel(&(saved_cursor.lnum)); 1117 1118 /* 1119 * Adjust items in all windows related to the current buffer. 1120 */ 1121 FOR_ALL_TAB_WINDOWS(tab, win) 1122 { 1123 #ifdef FEAT_JUMPLIST 1124 if (!cmdmod.lockmarks) 1125 /* Marks in the jumplist. When deleting lines, this may create 1126 * duplicate marks in the jumplist, they will be removed later. */ 1127 for (i = 0; i < win->w_jumplistlen; ++i) 1128 if (win->w_jumplist[i].fmark.fnum == fnum) 1129 one_adjust_nodel(&(win->w_jumplist[i].fmark.mark.lnum)); 1130 #endif 1131 1132 if (win->w_buffer == curbuf) 1133 { 1134 if (!cmdmod.lockmarks) 1135 /* marks in the tag stack */ 1136 for (i = 0; i < win->w_tagstacklen; i++) 1137 if (win->w_tagstack[i].fmark.fnum == fnum) 1138 one_adjust_nodel(&(win->w_tagstack[i].fmark.mark.lnum)); 1139 1140 /* the displayed Visual area */ 1141 if (win->w_old_cursor_lnum != 0) 1142 { 1143 one_adjust_nodel(&(win->w_old_cursor_lnum)); 1144 one_adjust_nodel(&(win->w_old_visual_lnum)); 1145 } 1146 1147 /* topline and cursor position for windows with the same buffer 1148 * other than the current window */ 1149 if (win != curwin) 1150 { 1151 if (win->w_topline >= line1 && win->w_topline <= line2) 1152 { 1153 if (amount == MAXLNUM) /* topline is deleted */ 1154 { 1155 if (line1 <= 1) 1156 win->w_topline = 1; 1157 else 1158 win->w_topline = line1 - 1; 1159 } 1160 else /* keep topline on the same line */ 1161 win->w_topline += amount; 1162 #ifdef FEAT_DIFF 1163 win->w_topfill = 0; 1164 #endif 1165 } 1166 else if (amount_after && win->w_topline > line2) 1167 { 1168 win->w_topline += amount_after; 1169 #ifdef FEAT_DIFF 1170 win->w_topfill = 0; 1171 #endif 1172 } 1173 if (win->w_cursor.lnum >= line1 && win->w_cursor.lnum <= line2) 1174 { 1175 if (amount == MAXLNUM) /* line with cursor is deleted */ 1176 { 1177 if (line1 <= 1) 1178 win->w_cursor.lnum = 1; 1179 else 1180 win->w_cursor.lnum = line1 - 1; 1181 win->w_cursor.col = 0; 1182 } 1183 else /* keep cursor on the same line */ 1184 win->w_cursor.lnum += amount; 1185 } 1186 else if (amount_after && win->w_cursor.lnum > line2) 1187 win->w_cursor.lnum += amount_after; 1188 } 1189 1190 #ifdef FEAT_FOLDING 1191 /* adjust folds */ 1192 if (adjust_folds) 1193 foldMarkAdjust(win, line1, line2, amount, amount_after); 1194 #endif 1195 } 1196 } 1197 1198 #ifdef FEAT_DIFF 1199 /* adjust diffs */ 1200 diff_mark_adjust(line1, line2, amount, amount_after); 1201 #endif 1202 } 1203 1204 /* This code is used often, needs to be fast. */ 1205 #define col_adjust(pp) \ 1206 { \ 1207 posp = pp; \ 1208 if (posp->lnum == lnum && posp->col >= mincol) \ 1209 { \ 1210 posp->lnum += lnum_amount; \ 1211 if (col_amount < 0 && posp->col <= (colnr_T)-col_amount) \ 1212 posp->col = 0; \ 1213 else if (posp->col < spaces_removed) \ 1214 posp->col = col_amount + spaces_removed; \ 1215 else \ 1216 posp->col += col_amount; \ 1217 } \ 1218 } 1219 1220 /* 1221 * Adjust marks in line "lnum" at column "mincol" and further: add 1222 * "lnum_amount" to the line number and add "col_amount" to the column 1223 * position. 1224 * "spaces_removed" is the number of spaces that were removed, matters when the 1225 * cursor is inside them. 1226 */ 1227 void 1228 mark_col_adjust( 1229 linenr_T lnum, 1230 colnr_T mincol, 1231 long lnum_amount, 1232 long col_amount, 1233 int spaces_removed) 1234 { 1235 int i; 1236 int fnum = curbuf->b_fnum; 1237 win_T *win; 1238 pos_T *posp; 1239 1240 if ((col_amount == 0L && lnum_amount == 0L) || cmdmod.lockmarks) 1241 return; /* nothing to do */ 1242 1243 /* named marks, lower case and upper case */ 1244 for (i = 0; i < NMARKS; i++) 1245 { 1246 col_adjust(&(curbuf->b_namedm[i])); 1247 if (namedfm[i].fmark.fnum == fnum) 1248 col_adjust(&(namedfm[i].fmark.mark)); 1249 } 1250 for (i = NMARKS; i < NMARKS + EXTRA_MARKS; i++) 1251 { 1252 if (namedfm[i].fmark.fnum == fnum) 1253 col_adjust(&(namedfm[i].fmark.mark)); 1254 } 1255 1256 /* last Insert position */ 1257 col_adjust(&(curbuf->b_last_insert)); 1258 1259 /* last change position */ 1260 col_adjust(&(curbuf->b_last_change)); 1261 1262 #ifdef FEAT_JUMPLIST 1263 /* list of change positions */ 1264 for (i = 0; i < curbuf->b_changelistlen; ++i) 1265 col_adjust(&(curbuf->b_changelist[i])); 1266 #endif 1267 1268 /* Visual area */ 1269 col_adjust(&(curbuf->b_visual.vi_start)); 1270 col_adjust(&(curbuf->b_visual.vi_end)); 1271 1272 /* previous context mark */ 1273 col_adjust(&(curwin->w_pcmark)); 1274 1275 /* previous pcmark */ 1276 col_adjust(&(curwin->w_prev_pcmark)); 1277 1278 /* saved cursor for formatting */ 1279 col_adjust(&saved_cursor); 1280 1281 /* 1282 * Adjust items in all windows related to the current buffer. 1283 */ 1284 FOR_ALL_WINDOWS(win) 1285 { 1286 #ifdef FEAT_JUMPLIST 1287 /* marks in the jumplist */ 1288 for (i = 0; i < win->w_jumplistlen; ++i) 1289 if (win->w_jumplist[i].fmark.fnum == fnum) 1290 col_adjust(&(win->w_jumplist[i].fmark.mark)); 1291 #endif 1292 1293 if (win->w_buffer == curbuf) 1294 { 1295 /* marks in the tag stack */ 1296 for (i = 0; i < win->w_tagstacklen; i++) 1297 if (win->w_tagstack[i].fmark.fnum == fnum) 1298 col_adjust(&(win->w_tagstack[i].fmark.mark)); 1299 1300 /* cursor position for other windows with the same buffer */ 1301 if (win != curwin) 1302 col_adjust(&win->w_cursor); 1303 } 1304 } 1305 } 1306 1307 #ifdef FEAT_JUMPLIST 1308 /* 1309 * When deleting lines, this may create duplicate marks in the 1310 * jumplist. They will be removed here for the specified window. 1311 * When "loadfiles" is TRUE first ensure entries have the "fnum" field set 1312 * (this may be a bit slow). 1313 */ 1314 void 1315 cleanup_jumplist(win_T *wp, int loadfiles) 1316 { 1317 int i; 1318 int from, to; 1319 1320 if (loadfiles) 1321 { 1322 /* If specified, load all the files from the jump list. This is 1323 * needed to properly clean up duplicate entries, but will take some 1324 * time. */ 1325 for (i = 0; i < wp->w_jumplistlen; ++i) 1326 { 1327 if ((wp->w_jumplist[i].fmark.fnum == 0) && 1328 (wp->w_jumplist[i].fmark.mark.lnum != 0)) 1329 fname2fnum(&wp->w_jumplist[i]); 1330 } 1331 } 1332 1333 to = 0; 1334 for (from = 0; from < wp->w_jumplistlen; ++from) 1335 { 1336 if (wp->w_jumplistidx == from) 1337 wp->w_jumplistidx = to; 1338 for (i = from + 1; i < wp->w_jumplistlen; ++i) 1339 if (wp->w_jumplist[i].fmark.fnum 1340 == wp->w_jumplist[from].fmark.fnum 1341 && wp->w_jumplist[from].fmark.fnum != 0 1342 && wp->w_jumplist[i].fmark.mark.lnum 1343 == wp->w_jumplist[from].fmark.mark.lnum) 1344 break; 1345 if (i >= wp->w_jumplistlen) /* no duplicate */ 1346 wp->w_jumplist[to++] = wp->w_jumplist[from]; 1347 else 1348 vim_free(wp->w_jumplist[from].fname); 1349 } 1350 if (wp->w_jumplistidx == wp->w_jumplistlen) 1351 wp->w_jumplistidx = to; 1352 wp->w_jumplistlen = to; 1353 } 1354 1355 /* 1356 * Copy the jumplist from window "from" to window "to". 1357 */ 1358 void 1359 copy_jumplist(win_T *from, win_T *to) 1360 { 1361 int i; 1362 1363 for (i = 0; i < from->w_jumplistlen; ++i) 1364 { 1365 to->w_jumplist[i] = from->w_jumplist[i]; 1366 if (from->w_jumplist[i].fname != NULL) 1367 to->w_jumplist[i].fname = vim_strsave(from->w_jumplist[i].fname); 1368 } 1369 to->w_jumplistlen = from->w_jumplistlen; 1370 to->w_jumplistidx = from->w_jumplistidx; 1371 } 1372 1373 /* 1374 * Free items in the jumplist of window "wp". 1375 */ 1376 void 1377 free_jumplist(win_T *wp) 1378 { 1379 int i; 1380 1381 for (i = 0; i < wp->w_jumplistlen; ++i) 1382 vim_free(wp->w_jumplist[i].fname); 1383 } 1384 #endif /* FEAT_JUMPLIST */ 1385 1386 void 1387 set_last_cursor(win_T *win) 1388 { 1389 if (win->w_buffer != NULL) 1390 win->w_buffer->b_last_cursor = win->w_cursor; 1391 } 1392 1393 #if defined(EXITFREE) || defined(PROTO) 1394 void 1395 free_all_marks(void) 1396 { 1397 int i; 1398 1399 for (i = 0; i < NMARKS + EXTRA_MARKS; i++) 1400 if (namedfm[i].fmark.mark.lnum != 0) 1401 vim_free(namedfm[i].fname); 1402 } 1403 #endif 1404 1405 /* 1406 * Return a pointer to the named file marks. 1407 */ 1408 xfmark_T * 1409 get_namedfm(void) 1410 { 1411 return namedfm; 1412 } 1413