mark.c (59358B)
1 // mark.c: functions for setting marks and jumping to them 2 3 #include <assert.h> 4 #include <limits.h> 5 #include <stdbool.h> 6 #include <stdint.h> 7 #include <stdio.h> 8 #include <string.h> 9 10 #include "nvim/api/private/helpers.h" 11 #include "nvim/ascii_defs.h" 12 #include "nvim/autocmd.h" 13 #include "nvim/buffer.h" 14 #include "nvim/buffer_defs.h" 15 #include "nvim/charset.h" 16 #include "nvim/cursor.h" 17 #include "nvim/diff.h" 18 #include "nvim/edit.h" 19 #include "nvim/errors.h" 20 #include "nvim/eval/typval.h" 21 #include "nvim/eval/typval_defs.h" 22 #include "nvim/ex_cmds_defs.h" 23 #include "nvim/extmark.h" 24 #include "nvim/extmark_defs.h" 25 #include "nvim/fold.h" 26 #include "nvim/gettext_defs.h" 27 #include "nvim/globals.h" 28 #include "nvim/highlight_defs.h" 29 #include "nvim/mark.h" 30 #include "nvim/mbyte.h" 31 #include "nvim/memline.h" 32 #include "nvim/memory.h" 33 #include "nvim/message.h" 34 #include "nvim/move.h" 35 #include "nvim/normal_defs.h" 36 #include "nvim/option_vars.h" 37 #include "nvim/os/fs.h" 38 #include "nvim/os/input.h" 39 #include "nvim/os/os.h" 40 #include "nvim/os/os_defs.h" 41 #include "nvim/os/time.h" 42 #include "nvim/os/time_defs.h" 43 #include "nvim/path.h" 44 #include "nvim/pos_defs.h" 45 #include "nvim/quickfix.h" 46 #include "nvim/strings.h" 47 #include "nvim/tag.h" 48 #include "nvim/textobject.h" 49 #include "nvim/types_defs.h" 50 #include "nvim/vim_defs.h" 51 52 // This file contains routines to maintain and manipulate marks. 53 54 // If a named file mark's lnum is non-zero, it is valid. 55 // If a named file mark's fnum is non-zero, it is for an existing buffer, 56 // otherwise it is from .shada and namedfm[n].fname is the file name. 57 // There are marks 'A - 'Z (set by user) and '0 to '9 (set when writing 58 // shada). 59 60 #include "mark.c.generated.h" 61 62 // Set named mark "c" at current cursor position. 63 // Returns OK on success, FAIL if bad name given. 64 int setmark(int c) 65 { 66 fmarkv_T view = mark_view_make(curwin->w_topline, curwin->w_cursor); 67 return setmark_pos(c, &curwin->w_cursor, curbuf->b_fnum, &view); 68 } 69 70 /// Free fmark_T item 71 void free_fmark(fmark_T fm) 72 { 73 xfree(fm.additional_data); 74 } 75 76 /// Free xfmark_T item 77 void free_xfmark(xfmark_T fm) 78 { 79 xfree(fm.fname); 80 free_fmark(fm.fmark); 81 } 82 83 /// Free and clear fmark_T item 84 void clear_fmark(fmark_T *const fm, const Timestamp timestamp) 85 FUNC_ATTR_NONNULL_ALL 86 { 87 free_fmark(*fm); 88 *fm = (fmark_T)INIT_FMARK; 89 fm->timestamp = timestamp; 90 } 91 92 /// Schedules "MarkSet" event. 93 /// 94 /// @param c The name of the mark, e.g., 'a'. 95 /// @param pos Position of the mark in the buffer. 96 /// @param buf The buffer of the mark. 97 static void do_markset_autocmd(char c, pos_T *pos, buf_T *buf) 98 { 99 if (!has_event(EVENT_MARKSET)) { 100 return; 101 } 102 103 MAXSIZE_TEMP_DICT(data, 3); 104 char mark_str[2] = { c, '\0' }; 105 PUT_C(data, "name", STRING_OBJ(((String){ .data = mark_str, .size = 1 }))); 106 PUT_C(data, "line", INTEGER_OBJ(pos->lnum)); 107 PUT_C(data, "col", INTEGER_OBJ(pos->col)); 108 aucmd_defer(EVENT_MARKSET, mark_str, NULL, AUGROUP_ALL, buf, NULL, &DICT_OBJ(data)); 109 } 110 111 // Set named mark "c" to position "pos". 112 // When "c" is upper case use file "fnum". 113 // Returns OK on success, FAIL if bad name given. 114 int setmark_pos(int c, pos_T *pos, int fnum, fmarkv_T *view_pt) 115 { 116 int i; 117 fmarkv_T view = view_pt != NULL ? *view_pt : (fmarkv_T)INIT_FMARKV; 118 119 // Check for a special key (may cause islower() to crash). 120 if (c < 0) { 121 return FAIL; 122 } 123 124 if (c == '\'' || c == '`') { 125 if (pos == &curwin->w_cursor) { 126 setpcmark(); 127 // keep it even when the cursor doesn't move 128 curwin->w_prev_pcmark = curwin->w_pcmark; 129 } else { 130 curwin->w_pcmark = *pos; 131 } 132 return OK; 133 } 134 135 // Can't set a mark in a non-existent buffer. 136 buf_T *buf = buflist_findnr(fnum); 137 if (buf == NULL) { 138 return FAIL; 139 } 140 141 if (c == '"') { 142 RESET_FMARK(&buf->b_last_cursor, *pos, buf->b_fnum, view); 143 do_markset_autocmd((char)c, pos, buf); 144 return OK; 145 } 146 147 // Allow setting '[ and '] for an autocommand that simulates reading a 148 // file. 149 if (c == '[') { 150 buf->b_op_start = *pos; 151 do_markset_autocmd((char)c, pos, buf); 152 return OK; 153 } 154 if (c == ']') { 155 buf->b_op_end = *pos; 156 do_markset_autocmd((char)c, pos, buf); 157 return OK; 158 } 159 160 if (c == '<' || c == '>') { 161 if (c == '<') { 162 buf->b_visual.vi_start = *pos; 163 } else { 164 buf->b_visual.vi_end = *pos; 165 } 166 if (buf->b_visual.vi_mode == NUL) { 167 // Visual_mode has not yet been set, use a sane default. 168 buf->b_visual.vi_mode = 'v'; 169 } 170 do_markset_autocmd((char)c, pos, buf); 171 return OK; 172 } 173 174 if (c == ':' && bt_prompt(buf)) { 175 RESET_FMARK(&buf->b_prompt_start, *pos, buf->b_fnum, view); 176 return OK; 177 } 178 179 if (ASCII_ISLOWER(c)) { 180 i = c - 'a'; 181 RESET_FMARK(buf->b_namedm + i, *pos, fnum, view); 182 do_markset_autocmd((char)c, pos, buf); 183 return OK; 184 } 185 if (ASCII_ISUPPER(c) || ascii_isdigit(c)) { 186 if (ascii_isdigit(c)) { 187 i = c - '0' + NMARKS; 188 } else { 189 i = c - 'A'; 190 } 191 RESET_XFMARK(namedfm + i, *pos, fnum, view, NULL); 192 do_markset_autocmd((char)c, pos, buf); 193 return OK; 194 } 195 return FAIL; 196 } 197 198 /// Remove every jump list entry referring to a given buffer. 199 /// This function will also adjust the current jump list index. 200 void mark_jumplist_forget_file(win_T *wp, int fnum) 201 { 202 // Remove all jump list entries that match the deleted buffer. 203 for (int i = wp->w_jumplistlen - 1; i >= 0; i--) { 204 if (wp->w_jumplist[i].fmark.fnum == fnum) { 205 // Found an entry that we want to delete. 206 free_xfmark(wp->w_jumplist[i]); 207 208 // If the current jump list index is behind the entry we want to delete, 209 // move it back by one. 210 if (wp->w_jumplistidx > i) { 211 wp->w_jumplistidx--; 212 } 213 214 // Actually remove the entry from the jump list. 215 wp->w_jumplistlen--; 216 memmove(&wp->w_jumplist[i], &wp->w_jumplist[i + 1], 217 (size_t)(wp->w_jumplistlen - i) * sizeof(wp->w_jumplist[i])); 218 } 219 } 220 } 221 222 /// Delete every entry referring to file "fnum" from both the jumplist and the 223 /// tag stack. 224 void mark_forget_file(win_T *wp, int fnum) 225 { 226 mark_jumplist_forget_file(wp, fnum); 227 228 // Remove all tag stack entries that match the deleted buffer. 229 for (int i = wp->w_tagstacklen - 1; i >= 0; i--) { 230 if (wp->w_tagstack[i].fmark.fnum == fnum) { 231 // Found an entry that we want to delete. 232 tagstack_clear_entry(&wp->w_tagstack[i]); 233 234 // If the current tag stack index is behind the entry we want to delete, 235 // move it back by one. 236 if (wp->w_tagstackidx > i) { 237 wp->w_tagstackidx--; 238 } 239 240 // Actually remove the entry from the tag stack. 241 wp->w_tagstacklen--; 242 memmove(&wp->w_tagstack[i], &wp->w_tagstack[i + 1], 243 (size_t)(wp->w_tagstacklen - i) * sizeof(wp->w_tagstack[i])); 244 } 245 } 246 } 247 248 // Set the previous context mark to the current position and add it to the 249 // jump list. 250 void setpcmark(void) 251 { 252 xfmark_T *fm; 253 254 // for :global the mark is set only once 255 if (global_busy || listcmd_busy || (cmdmod.cmod_flags & CMOD_KEEPJUMPS)) { 256 return; 257 } 258 259 curwin->w_prev_pcmark = curwin->w_pcmark; 260 curwin->w_pcmark = curwin->w_cursor; 261 262 if (curwin->w_pcmark.lnum == 0) { 263 curwin->w_pcmark.lnum = 1; 264 } 265 266 if (jop_flags & kOptJopFlagStack) { 267 // jumpoptions=stack: if we're somewhere in the middle of the jumplist 268 // discard everything after the current index. 269 if (curwin->w_jumplistidx < curwin->w_jumplistlen - 1) { 270 // Discard the rest of the jumplist by cutting the length down to 271 // contain nothing beyond the current index. 272 curwin->w_jumplistlen = curwin->w_jumplistidx + 1; 273 } 274 } 275 276 // If jumplist is full: remove oldest entry 277 if (++curwin->w_jumplistlen > JUMPLISTSIZE) { 278 curwin->w_jumplistlen = JUMPLISTSIZE; 279 free_xfmark(curwin->w_jumplist[0]); 280 memmove(&curwin->w_jumplist[0], &curwin->w_jumplist[1], 281 (JUMPLISTSIZE - 1) * sizeof(curwin->w_jumplist[0])); 282 } 283 curwin->w_jumplistidx = curwin->w_jumplistlen; 284 fm = &curwin->w_jumplist[curwin->w_jumplistlen - 1]; 285 286 fmarkv_T view = mark_view_make(curwin->w_topline, curwin->w_pcmark); 287 SET_XFMARK(fm, curwin->w_pcmark, curbuf->b_fnum, view, NULL); 288 } 289 290 // To change context, call setpcmark(), then move the current position to 291 // where ever, then call checkpcmark(). This ensures that the previous 292 // context will only be changed if the cursor moved to a different line. 293 // If pcmark was deleted (with "dG") the previous mark is restored. 294 void checkpcmark(void) 295 { 296 if (curwin->w_prev_pcmark.lnum != 0 297 && (equalpos(curwin->w_pcmark, curwin->w_cursor) 298 || curwin->w_pcmark.lnum == 0)) { 299 curwin->w_pcmark = curwin->w_prev_pcmark; 300 } 301 curwin->w_prev_pcmark.lnum = 0; // it has been checked 302 } 303 304 /// Get mark in "count" position in the |jumplist| relative to the current index. 305 /// 306 /// If the mark is in a different buffer, it will be skipped unless the buffer exists. 307 /// 308 /// @note cleanup_jumplist() is run, which removes duplicate marks, and 309 /// changes win->w_jumplistidx. 310 /// @param[in] win window to get jumplist from. 311 /// @param[in] count count to move may be negative. 312 /// 313 /// @return mark, NULL if out of jumplist bounds. 314 fmark_T *get_jumplist(win_T *win, int count) 315 { 316 xfmark_T *jmp = NULL; 317 318 cleanup_jumplist(win, true); 319 320 if (win->w_jumplistlen == 0) { // nothing to jump to 321 return NULL; 322 } 323 324 while (true) { 325 if (win->w_jumplistidx + count < 0 326 || win->w_jumplistidx + count >= win->w_jumplistlen) { 327 return NULL; 328 } 329 330 // if first CTRL-O or CTRL-I command after a jump, add cursor position 331 // to list. Careful: If there are duplicates (CTRL-O immediately after 332 // starting Vim on a file), another entry may have been removed. 333 if (win->w_jumplistidx == win->w_jumplistlen) { 334 setpcmark(); 335 win->w_jumplistidx--; // skip the new entry 336 if (win->w_jumplistidx + count < 0) { 337 return NULL; 338 } 339 } 340 341 win->w_jumplistidx += count; 342 343 jmp = win->w_jumplist + win->w_jumplistidx; 344 if (jmp->fmark.fnum == 0) { 345 // Resolve the fnum (buff number) in the mark before returning it (shada) 346 fname2fnum(jmp); 347 } 348 if (jmp->fmark.fnum != curbuf->b_fnum) { 349 // Needs to switch buffer, if it can't find it skip the mark 350 if (buflist_findnr(jmp->fmark.fnum) == NULL) { 351 count += count < 0 ? -1 : 1; 352 continue; 353 } 354 } 355 break; 356 } 357 return &jmp->fmark; 358 } 359 360 /// Get mark in "count" position in the |changelist| relative to the current index. 361 /// 362 /// @note Changes the win->w_changelistidx. 363 /// @param[in] win window to get jumplist from. 364 /// @param[in] count count to move may be negative. 365 /// 366 /// @return mark, NULL if out of bounds. 367 fmark_T *get_changelist(buf_T *buf, win_T *win, int count) 368 { 369 int n; 370 fmark_T *fm; 371 372 if (buf->b_changelistlen == 0) { // nothing to jump to 373 return NULL; 374 } 375 376 n = win->w_changelistidx; 377 if (n + count < 0) { 378 if (n == 0) { 379 return NULL; 380 } 381 n = 0; 382 } else if (n + count >= buf->b_changelistlen) { 383 if (n == buf->b_changelistlen - 1) { 384 return NULL; 385 } 386 n = buf->b_changelistlen - 1; 387 } else { 388 n += count; 389 } 390 win->w_changelistidx = n; 391 fm = &(buf->b_changelist[n]); 392 // Changelist marks are always buffer local, Shada does not set it when loading 393 fm->fnum = curbuf->handle; 394 return &(buf->b_changelist[n]); 395 } 396 397 /// Get a named mark. 398 /// 399 /// All types of marks, even those that are not technically a mark will be returned as such. Use 400 /// mark_move_to() to move to the mark. 401 /// @note Some of the pointers are statically allocated, if in doubt make a copy. For more 402 /// information read mark_get_local(). 403 /// @param buf Buffer to get the mark from. 404 /// @param win Window to get or calculate the mark from (motion type marks, context mark). 405 /// @param fmp[out] Optional pointer to store the result in, as a workaround for the note above. 406 /// @param flag MarkGet value 407 /// @param name Name of the mark. 408 /// 409 /// @return Mark if found, otherwise NULL. For @c kMarkBufLocal, NULL is returned 410 /// when no mark is found in @a buf. 411 fmark_T *mark_get(buf_T *buf, win_T *win, fmark_T *fmp, MarkGet flag, int name) 412 { 413 fmark_T *fm = NULL; 414 if (ASCII_ISUPPER(name) || ascii_isdigit(name)) { 415 // Global marks 416 xfmark_T *xfm = mark_get_global(flag != kMarkAllNoResolve, name); 417 fm = &xfm->fmark; 418 if (flag == kMarkBufLocal && xfm->fmark.fnum != buf->handle) { 419 // Only wanted marks belonging to the buffer 420 return pos_to_mark(buf, NULL, (pos_T){ .lnum = 0 }); 421 } 422 } else if (name > 0 && name < NMARK_LOCAL_MAX) { 423 // Local Marks 424 fm = mark_get_local(buf, win, name); 425 } 426 if (fmp != NULL && fm != NULL) { 427 *fmp = *fm; 428 return fmp; 429 } 430 return fm; 431 } 432 433 /// Get a global mark {A-Z0-9}. 434 /// 435 /// @param name the name of the mark. 436 /// @param resolve Whether to try resolving the mark fnum (i.e., load the buffer stored in 437 /// the mark fname and update the xfmark_T (expensive)). 438 /// 439 /// @return Mark 440 xfmark_T *mark_get_global(bool resolve, int name) 441 { 442 xfmark_T *mark; 443 444 if (ascii_isdigit(name)) { 445 name = name - '0' + NMARKS; 446 } else if (ASCII_ISUPPER(name)) { 447 name -= 'A'; 448 } else { 449 // Not a valid mark name 450 assert(false); 451 } 452 mark = &namedfm[name]; 453 454 if (resolve && mark->fmark.fnum == 0) { 455 // Resolve filename to fnum (SHADA marks) 456 fname2fnum(mark); 457 } 458 return mark; 459 } 460 461 /// Get a local mark (lowercase and symbols). 462 /// 463 /// Some marks are not actually marks, but positions that are never adjusted or motions presented as 464 /// marks. Search first for marks and fallback to finding motion type marks. If it's known 465 /// ahead of time that the mark is actually a motion use the mark_get_motion() directly. 466 /// 467 /// @note Lowercase, last_cursor '"', last insert '^', last change '.' are not statically 468 /// allocated, everything else is. 469 /// @param name the name of the mark. 470 /// @param win window to retrieve marks that belong to it (motions and context mark). 471 /// @param buf buf to retrieve marks that belong to it. 472 /// 473 /// @return Mark, NULL if not found. 474 fmark_T *mark_get_local(buf_T *buf, win_T *win, int name) 475 { 476 fmark_T *mark = NULL; 477 if (ASCII_ISLOWER(name)) { 478 // normal named mark 479 mark = &buf->b_namedm[name - 'a']; 480 // to start of previous operator 481 } else if (name == '[') { 482 mark = pos_to_mark(buf, NULL, buf->b_op_start); 483 // to end of previous operator 484 } else if (name == ']') { 485 mark = pos_to_mark(buf, NULL, buf->b_op_end); 486 // visual marks 487 } else if (name == '<' || name == '>') { 488 mark = mark_get_visual(buf, name); 489 // previous context mark 490 } else if (name == '\'' || name == '`') { 491 // TODO(muniter): w_pcmark should be stored as a mark, but causes a nasty bug. 492 mark = pos_to_mark(curbuf, NULL, win->w_pcmark); 493 // to position when leaving buffer 494 } else if (name == '"') { 495 mark = &(buf->b_last_cursor); 496 // to where last Insert mode stopped 497 } else if (name == '^') { 498 mark = &(buf->b_last_insert); 499 // to where last change was made 500 } else if (name == '.') { 501 mark = &buf->b_last_change; 502 // prompt start location 503 } else if (name == ':' && bt_prompt(buf)) { 504 mark = &(buf->b_prompt_start); 505 // Mark that are actually not marks but motions, e.g {, }, (, ), ... 506 } else { 507 mark = mark_get_motion(buf, win, name); 508 } 509 510 if (mark) { 511 mark->fnum = buf->b_fnum; 512 } 513 514 return mark; 515 } 516 517 /// Get marks that are actually motions but return them as marks 518 /// 519 /// Gets the following motions as marks: '{', '}', '(', ')' 520 /// @param name name of the mark 521 /// @param win window to retrieve the cursor to calculate the mark. 522 /// @param buf buf to wrap motion marks with it's buffer number (fm->fnum). 523 /// 524 /// @return[static] Mark. 525 fmark_T *mark_get_motion(buf_T *buf, win_T *win, int name) 526 { 527 fmark_T *mark = NULL; 528 const pos_T pos = curwin->w_cursor; 529 const bool slcb = listcmd_busy; 530 listcmd_busy = true; // avoid that '' is changed 531 if (name == '{' || name == '}') { // to previous/next paragraph 532 oparg_T oa; 533 if (findpar(&oa.inclusive, name == '}' ? FORWARD : BACKWARD, 1, NUL, false)) { 534 mark = pos_to_mark(buf, NULL, win->w_cursor); 535 } 536 } else if (name == '(' || name == ')') { // to previous/next sentence 537 if (findsent(name == ')' ? FORWARD : BACKWARD, 1)) { 538 mark = pos_to_mark(buf, NULL, win->w_cursor); 539 } 540 } 541 curwin->w_cursor = pos; 542 listcmd_busy = slcb; 543 return mark; 544 } 545 546 /// Get visual marks '<', '>' 547 /// 548 /// This marks are different to normal marks: 549 /// 1. Never adjusted. 550 /// 2. Different behavior depending on editor state (visual mode). 551 /// 3. Not saved in shada. 552 /// 4. Re-ordered when defined in reverse. 553 /// @param buf Buffer to get the mark from. 554 /// @param name Mark name '<' or '>'. 555 /// 556 /// @return[static] Mark 557 fmark_T *mark_get_visual(buf_T *buf, int name) 558 { 559 fmark_T *mark = NULL; 560 if (name == '<' || name == '>') { 561 // start/end of visual area 562 pos_T startp = buf->b_visual.vi_start; 563 pos_T endp = buf->b_visual.vi_end; 564 if (((name == '<') == lt(startp, endp) || endp.lnum == 0) 565 && startp.lnum != 0) { 566 mark = pos_to_mark(buf, NULL, startp); 567 } else { 568 mark = pos_to_mark(buf, NULL, endp); 569 } 570 571 if (buf->b_visual.vi_mode == 'V') { 572 if (name == '<') { 573 mark->mark.col = 0; 574 } else { 575 mark->mark.col = MAXCOL; 576 } 577 mark->mark.coladd = 0; 578 } 579 } 580 return mark; 581 } 582 583 /// Wrap a pos_T into an fmark_T, used to abstract marks handling. 584 /// 585 /// Pass an fmp if multiple c 586 /// @note view fields are set to 0. 587 /// @param buf for fmark->fnum. 588 /// @param pos for fmark->mark. 589 /// @param fmp pointer to save the mark. 590 /// 591 /// @return[static] Mark with the given information. 592 fmark_T *pos_to_mark(buf_T *buf, fmark_T *fmp, pos_T pos) 593 FUNC_ATTR_NONNULL_RET 594 { 595 static fmark_T fms = INIT_FMARK; 596 fmark_T *fm = fmp == NULL ? &fms : fmp; 597 fm->fnum = buf->handle; 598 fm->mark = pos; 599 return fm; 600 } 601 602 /// Attempt to switch to the buffer of the given global mark 603 /// 604 /// @param fm 605 /// @param pcmark_on_switch leave a context mark when switching buffer. 606 /// @return whether the buffer was switched or not. 607 static MarkMoveRes switch_to_mark_buf(fmark_T *fm, bool pcmark_on_switch) 608 { 609 if (fm->fnum != curbuf->b_fnum) { 610 // Switch to another file. 611 int getfile_flag = pcmark_on_switch ? GETF_SETMARK : 0; 612 bool res = buflist_getfile(fm->fnum, fm->mark.lnum, getfile_flag, false) == OK; 613 return res == true ? kMarkSwitchedBuf : kMarkMoveFailed; 614 } 615 return 0; 616 } 617 618 /// Move to the given file mark, changing the buffer and cursor position. 619 /// 620 /// Validate the mark, switch to the buffer, and move the cursor. 621 /// @param fm Mark, can be NULL will raise E78: Unknown mark 622 /// @param flags MarkMove flags to configure the movement to the mark. 623 /// 624 /// @return MarkMovekRes flags representing the outcome 625 MarkMoveRes mark_move_to(fmark_T *fm, MarkMove flags) 626 { 627 static fmark_T fm_copy = INIT_FMARK; 628 MarkMoveRes res = kMarkMoveSuccess; 629 const char *errormsg = NULL; 630 if (!mark_check(fm, &errormsg)) { 631 if (errormsg != NULL) { 632 emsg(errormsg); 633 } 634 res = kMarkMoveFailed; 635 goto end; 636 } 637 638 if (fm->fnum != curbuf->handle) { 639 // Need to change buffer 640 fm_copy = *fm; // Copy, autocommand may change it 641 fm = &fm_copy; 642 // Jump to the file with the mark 643 res |= switch_to_mark_buf(fm, !(flags & kMarkJumpList)); 644 // Failed switching buffer 645 if (res & kMarkMoveFailed) { 646 goto end; 647 } 648 // Check line count now that the **destination buffer is loaded**. 649 if (!mark_check_line_bounds(curbuf, fm, &errormsg)) { 650 if (errormsg != NULL) { 651 emsg(errormsg); 652 } 653 res |= kMarkMoveFailed; 654 goto end; 655 } 656 } else if (flags & kMarkContext) { 657 // Doing it in this condition avoids double context mark when switching buffer. 658 setpcmark(); 659 } 660 // Move the cursor while keeping track of what changed for the caller 661 pos_T prev_pos = curwin->w_cursor; 662 pos_T pos = fm->mark; 663 // Set lnum again, autocommands my have changed it 664 curwin->w_cursor = fm->mark; 665 if (flags & kMarkBeginLine) { 666 beginline(BL_WHITE | BL_FIX); 667 } 668 res = prev_pos.lnum != pos.lnum ? res | kMarkChangedLine | kMarkChangedCursor : res; 669 res = prev_pos.col != pos.col ? res | kMarkChangedCol | kMarkChangedCursor : res; 670 if (flags & kMarkSetView) { 671 mark_view_restore(fm); 672 } 673 674 if (res & kMarkSwitchedBuf || res & kMarkChangedCursor) { 675 check_cursor(curwin); 676 } 677 end: 678 return res; 679 } 680 681 /// Restore the mark view. 682 /// By remembering the offset between topline and mark lnum at the time of 683 /// definition, this function restores the "view". 684 /// @note Assumes the mark has been checked, is valid. 685 /// @param fm the named mark. 686 void mark_view_restore(fmark_T *fm) 687 { 688 if (fm != NULL && fm->view.topline_offset >= 0) { 689 linenr_T topline = fm->mark.lnum - fm->view.topline_offset; 690 // If the mark does not have a view, topline_offset is MAXLNUM, 691 // and this check can prevent restoring mark view in that case. 692 if (topline >= 1) { 693 set_topline(curwin, topline); 694 } 695 } 696 } 697 698 fmarkv_T mark_view_make(linenr_T topline, pos_T pos) 699 { 700 return (fmarkv_T){ pos.lnum - topline }; 701 } 702 703 /// Search for the next named mark in the current file from a start position. 704 /// 705 /// @param startpos where to start. 706 /// @param dir direction for search. 707 /// 708 /// @return next mark or NULL if no mark is found. 709 fmark_T *getnextmark(pos_T *startpos, int dir, int begin_line) 710 { 711 fmark_T *result = NULL; 712 pos_T pos = *startpos; 713 714 if (dir == BACKWARD && begin_line) { 715 pos.col = 0; 716 } else if (dir == FORWARD && begin_line) { 717 pos.col = MAXCOL; 718 } 719 720 for (int i = 0; i < NMARKS; i++) { 721 if (curbuf->b_namedm[i].mark.lnum > 0) { 722 if (dir == FORWARD) { 723 if ((result == NULL || lt(curbuf->b_namedm[i].mark, result->mark)) 724 && lt(pos, curbuf->b_namedm[i].mark)) { 725 result = &curbuf->b_namedm[i]; 726 } 727 } else { 728 if ((result == NULL || lt(result->mark, curbuf->b_namedm[i].mark)) 729 && lt(curbuf->b_namedm[i].mark, pos)) { 730 result = &curbuf->b_namedm[i]; 731 } 732 } 733 } 734 } 735 736 return result; 737 } 738 739 // For an xtended filemark: set the fnum from the fname. 740 // This is used for marks obtained from the .shada file. It's postponed 741 // until the mark is used to avoid a long startup delay. 742 static void fname2fnum(xfmark_T *fm) 743 { 744 if (fm->fname == NULL) { 745 return; 746 } 747 748 // First expand "~/" in the file name to the home directory. 749 // Don't expand the whole name, it may contain other '~' chars. 750 if (fm->fname[0] == '~' && vim_ispathsep_nocolon(fm->fname[1])) { 751 size_t len = expand_env("~/", NameBuff, MAXPATHL); 752 xstrlcpy(NameBuff + len, fm->fname + 2, MAXPATHL - len); 753 } else { 754 xstrlcpy(NameBuff, fm->fname, MAXPATHL); 755 } 756 757 // Try to shorten the file name. 758 os_dirname(IObuff, IOSIZE); 759 char *p = path_shorten_fname(NameBuff, IObuff); 760 761 // buflist_new() will call fmarks_check_names() 762 (void)buflist_new(NameBuff, p, 1, 0); 763 } 764 765 // Check all file marks for a name that matches the file name in buf. 766 // May replace the name with an fnum. 767 // Used for marks that come from the .shada file. 768 void fmarks_check_names(buf_T *buf) 769 { 770 char *name = buf->b_ffname; 771 772 if (buf->b_ffname == NULL) { 773 return; 774 } 775 776 for (int i = 0; i < NGLOBALMARKS; i++) { 777 fmarks_check_one(&namedfm[i], name, buf); 778 } 779 780 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { 781 for (int i = 0; i < wp->w_jumplistlen; i++) { 782 fmarks_check_one(&wp->w_jumplist[i], name, buf); 783 } 784 } 785 } 786 787 static void fmarks_check_one(xfmark_T *fm, char *name, buf_T *buf) 788 { 789 if (fm->fmark.fnum == 0 790 && fm->fname != NULL 791 && path_fnamecmp(name, fm->fname) == 0) { 792 fm->fmark.fnum = buf->b_fnum; 793 XFREE_CLEAR(fm->fname); 794 } 795 } 796 797 /// Check the position in @a fm is valid. 798 /// 799 /// Checks for: 800 /// - NULL raising unknown mark error. 801 /// - Line number <= 0 raising mark not set. 802 /// - Line number > buffer line count, raising invalid mark. 803 /// 804 /// @param fm[in] File mark to check. 805 /// @param errormsg[out] Error message, if any. 806 /// 807 /// @return true if the mark passes all the above checks, else false. 808 bool mark_check(fmark_T *fm, const char **errormsg) 809 { 810 if (fm == NULL) { 811 *errormsg = _(e_umark); 812 return false; 813 } else if (fm->mark.lnum <= 0) { 814 // In both cases it's an error but only raise when equals to 0 815 if (fm->mark.lnum == 0) { 816 *errormsg = _(e_marknotset); 817 } 818 return false; 819 } 820 // Only check for valid line number if the buffer is loaded. 821 if (fm->fnum == curbuf->handle && !mark_check_line_bounds(curbuf, fm, errormsg)) { 822 return false; 823 } 824 return true; 825 } 826 827 /// Check if a mark line number is greater than the buffer line count, and set e_markinval. 828 /// 829 /// @note Should be done after the buffer is loaded into memory. 830 /// @param buf Buffer where the mark is set. 831 /// @param fm Mark to check. 832 /// @param errormsg[out] Error message, if any. 833 /// @return true if below line count else false. 834 bool mark_check_line_bounds(buf_T *buf, fmark_T *fm, const char **errormsg) 835 { 836 if (buf != NULL && fm->mark.lnum > buf->b_ml.ml_line_count) { 837 *errormsg = _(e_markinval); 838 return false; 839 } 840 return true; 841 } 842 843 /// Clear all marks and change list in the given buffer 844 /// 845 /// Used mainly when trashing the entire buffer during ":e" type commands. 846 /// 847 /// @param[out] buf Buffer to clear marks in. 848 void clrallmarks(buf_T *const buf, const Timestamp timestamp) 849 FUNC_ATTR_NONNULL_ALL 850 { 851 for (size_t i = 0; i < NMARKS; i++) { 852 clear_fmark(&buf->b_namedm[i], timestamp); 853 } 854 clear_fmark(&buf->b_last_cursor, timestamp); 855 buf->b_last_cursor.mark.lnum = 1; 856 clear_fmark(&buf->b_last_insert, timestamp); 857 clear_fmark(&buf->b_last_change, timestamp); 858 buf->b_op_start.lnum = 0; // start/end op mark cleared 859 buf->b_op_end.lnum = 0; 860 for (int i = 0; i < buf->b_changelistlen; i++) { 861 clear_fmark(&buf->b_changelist[i], timestamp); 862 } 863 buf->b_changelistlen = 0; 864 } 865 866 // Get name of file from a filemark. 867 // When it's in the current buffer, return the text at the mark. 868 // Returns an allocated string. 869 char *fm_getname(fmark_T *fmark, int lead_len) 870 { 871 if (fmark->fnum == curbuf->b_fnum) { // current buffer 872 return mark_line(&(fmark->mark), lead_len); 873 } 874 return buflist_nr2name(fmark->fnum, false, true); 875 } 876 877 /// Return the line at mark "mp". Truncate to fit in window. 878 /// The returned string has been allocated. 879 static char *mark_line(pos_T *mp, int lead_len) 880 FUNC_ATTR_NONNULL_RET 881 { 882 char *p; 883 884 if (mp->lnum == 0 || mp->lnum > curbuf->b_ml.ml_line_count) { 885 return xstrdup("-invalid-"); 886 } 887 assert(Columns >= 0); 888 // Allow for up to 5 bytes per character. 889 char *s = xstrnsave(skipwhite(ml_get(mp->lnum)), (size_t)Columns * 5); 890 891 // Truncate the line to fit it in the window 892 int len = 0; 893 for (p = s; *p != NUL; MB_PTR_ADV(p)) { 894 len += ptr2cells(p); 895 if (len >= Columns - lead_len) { 896 break; 897 } 898 } 899 *p = NUL; 900 return s; 901 } 902 903 // print the marks 904 void ex_marks(exarg_T *eap) 905 { 906 char *arg = eap->arg; 907 char *name; 908 pos_T *posp; 909 910 if (arg != NULL && *arg == NUL) { 911 arg = NULL; 912 } 913 914 msg_ext_set_kind("list_cmd"); 915 show_one_mark('\'', arg, &curwin->w_pcmark, NULL, true); 916 for (int i = 0; i < NMARKS; i++) { 917 show_one_mark(i + 'a', arg, &curbuf->b_namedm[i].mark, NULL, true); 918 } 919 for (int i = 0; i < NGLOBALMARKS; i++) { 920 if (namedfm[i].fmark.fnum != 0) { 921 name = fm_getname(&namedfm[i].fmark, 15); 922 } else { 923 name = namedfm[i].fname; 924 } 925 if (name != NULL) { 926 show_one_mark(i >= NMARKS ? i - NMARKS + '0' : i + 'A', 927 arg, &namedfm[i].fmark.mark, name, 928 namedfm[i].fmark.fnum == curbuf->b_fnum); 929 if (namedfm[i].fmark.fnum != 0) { 930 xfree(name); 931 } 932 } 933 } 934 show_one_mark('"', arg, &curbuf->b_last_cursor.mark, NULL, true); 935 show_one_mark('[', arg, &curbuf->b_op_start, NULL, true); 936 show_one_mark(']', arg, &curbuf->b_op_end, NULL, true); 937 show_one_mark('^', arg, &curbuf->b_last_insert.mark, NULL, true); 938 show_one_mark('.', arg, &curbuf->b_last_change.mark, NULL, true); 939 if (bt_prompt(curbuf)) { 940 show_one_mark(':', arg, &curbuf->b_prompt_start.mark, NULL, true); 941 } 942 943 // Show the marks as where they will jump to. 944 pos_T *startp = &curbuf->b_visual.vi_start; 945 pos_T *endp = &curbuf->b_visual.vi_end; 946 if ((lt(*startp, *endp) || endp->lnum == 0) && startp->lnum != 0) { 947 posp = startp; 948 } else { 949 posp = endp; 950 } 951 show_one_mark('<', arg, posp, NULL, true); 952 show_one_mark('>', arg, posp == startp ? endp : startp, NULL, true); 953 954 show_one_mark(-1, arg, NULL, NULL, false); 955 } 956 957 /// @param current in current file 958 static void show_one_mark(int c, char *arg, pos_T *p, char *name_arg, int current) 959 { 960 static bool did_title = false; 961 bool mustfree = false; 962 char *name = name_arg; 963 964 if (c == -1) { // finish up 965 if (did_title) { 966 did_title = false; 967 } else { 968 if (arg == NULL) { 969 msg(_("No marks set"), 0); 970 } else { 971 semsg(_("E283: No marks matching \"%s\""), arg); 972 } 973 } 974 } else if (!got_int 975 && (arg == NULL || vim_strchr(arg, c) != NULL) 976 && p->lnum != 0) { 977 // don't output anything if 'q' typed at --more-- prompt 978 if (name == NULL && current) { 979 name = mark_line(p, 15); 980 mustfree = true; 981 } 982 if (!message_filtered(name)) { 983 if (!did_title) { 984 // Highlight title 985 msg_puts_title(_("\nmark line col file/text")); 986 did_title = true; 987 } 988 msg_putchar('\n'); 989 if (!got_int) { 990 snprintf(IObuff, IOSIZE, " %c %6" PRIdLINENR " %4d ", c, p->lnum, p->col); 991 msg_outtrans(IObuff, 0, false); 992 if (name != NULL) { 993 msg_outtrans(name, current ? HLF_D : 0, false); 994 } 995 } 996 } 997 if (mustfree) { 998 xfree(name); 999 } 1000 } 1001 } 1002 1003 // ":delmarks[!] [marks]" 1004 void ex_delmarks(exarg_T *eap) 1005 { 1006 int from, to; 1007 int n; 1008 1009 if (*eap->arg == NUL && eap->forceit) { 1010 // clear all marks 1011 clrallmarks(curbuf, os_time()); 1012 } else if (eap->forceit) { 1013 emsg(_(e_invarg)); 1014 } else if (*eap->arg == NUL) { 1015 emsg(_(e_argreq)); 1016 } else { 1017 // clear specified marks only 1018 const Timestamp timestamp = os_time(); 1019 for (char *p = eap->arg; *p != NUL; p++) { 1020 bool lower = ASCII_ISLOWER(*p); 1021 bool digit = ascii_isdigit(*p); 1022 if (lower || digit || ASCII_ISUPPER(*p)) { 1023 if (p[1] == '-') { 1024 // clear range of marks 1025 from = (uint8_t)(*p); 1026 to = (uint8_t)p[2]; 1027 if (!(lower ? ASCII_ISLOWER(p[2]) 1028 : (digit ? ascii_isdigit(p[2]) 1029 : ASCII_ISUPPER(p[2]))) 1030 || to < from) { 1031 semsg(_(e_invarg2), p); 1032 return; 1033 } 1034 p += 2; 1035 } else { 1036 // clear one lower case mark 1037 from = to = (uint8_t)(*p); 1038 } 1039 1040 for (int i = from; i <= to; i++) { 1041 if (lower) { 1042 curbuf->b_namedm[i - 'a'].mark.lnum = 0; 1043 curbuf->b_namedm[i - 'a'].timestamp = timestamp; 1044 } else { 1045 if (digit) { 1046 n = i - '0' + NMARKS; 1047 } else { 1048 n = i - 'A'; 1049 } 1050 namedfm[n].fmark.mark.lnum = 0; 1051 namedfm[n].fmark.fnum = 0; 1052 namedfm[n].fmark.timestamp = timestamp; 1053 XFREE_CLEAR(namedfm[n].fname); 1054 } 1055 } 1056 } else { 1057 switch (*p) { 1058 case '"': 1059 clear_fmark(&curbuf->b_last_cursor, timestamp); 1060 break; 1061 case '^': 1062 clear_fmark(&curbuf->b_last_insert, timestamp); 1063 break; 1064 case ':': 1065 // Readonly mark. No deletion allowed. 1066 break; 1067 case '.': 1068 clear_fmark(&curbuf->b_last_change, timestamp); 1069 break; 1070 case '[': 1071 curbuf->b_op_start.lnum = 0; break; 1072 case ']': 1073 curbuf->b_op_end.lnum = 0; break; 1074 case '<': 1075 curbuf->b_visual.vi_start.lnum = 0; break; 1076 case '>': 1077 curbuf->b_visual.vi_end.lnum = 0; break; 1078 case ' ': 1079 break; 1080 default: 1081 semsg(_(e_invarg2), p); 1082 return; 1083 } 1084 } 1085 } 1086 } 1087 } 1088 1089 // print the jumplist 1090 void ex_jumps(exarg_T *eap) 1091 { 1092 cleanup_jumplist(curwin, true); 1093 // Highlight title 1094 msg_ext_set_kind("list_cmd"); 1095 msg_puts_title(_("\n jump line col file/text")); 1096 for (int i = 0; i < curwin->w_jumplistlen && !got_int; i++) { 1097 if (curwin->w_jumplist[i].fmark.mark.lnum != 0) { 1098 char *name = fm_getname(&curwin->w_jumplist[i].fmark, 16); 1099 1100 // Make sure to output the current indicator, even when on an wiped 1101 // out buffer. ":filter" may still skip it. 1102 if (name == NULL && i == curwin->w_jumplistidx) { 1103 name = xstrdup("-invalid-"); 1104 } 1105 // apply :filter /pat/ or file name not available 1106 if (name == NULL || message_filtered(name)) { 1107 xfree(name); 1108 continue; 1109 } 1110 1111 msg_putchar('\n'); 1112 if (got_int) { 1113 xfree(name); 1114 break; 1115 } 1116 snprintf(IObuff, IOSIZE, "%c %2d %5" PRIdLINENR " %4d ", 1117 i == curwin->w_jumplistidx ? '>' : ' ', 1118 i > curwin->w_jumplistidx ? i - curwin->w_jumplistidx : curwin->w_jumplistidx - i, 1119 curwin->w_jumplist[i].fmark.mark.lnum, curwin->w_jumplist[i].fmark.mark.col); 1120 msg_outtrans(IObuff, 0, false); 1121 msg_outtrans(name, curwin->w_jumplist[i].fmark.fnum == curbuf->b_fnum ? HLF_D : 0, false); 1122 xfree(name); 1123 os_breakcheck(); 1124 } 1125 } 1126 if (curwin->w_jumplistidx == curwin->w_jumplistlen) { 1127 msg_puts("\n>"); 1128 } 1129 } 1130 1131 void ex_clearjumps(exarg_T *eap) 1132 { 1133 free_jumplist(curwin); 1134 curwin->w_jumplistlen = 0; 1135 curwin->w_jumplistidx = 0; 1136 } 1137 1138 // print the changelist 1139 void ex_changes(exarg_T *eap) 1140 { 1141 msg_ext_set_kind("list_cmd"); 1142 // Highlight title 1143 msg_puts_title(_("\nchange line col text")); 1144 1145 for (int i = 0; i < curbuf->b_changelistlen && !got_int; i++) { 1146 if (curbuf->b_changelist[i].mark.lnum != 0) { 1147 msg_putchar('\n'); 1148 if (got_int) { 1149 break; 1150 } 1151 snprintf(IObuff, IOSIZE, "%c %3d %5" PRIdLINENR " %4d ", 1152 i == curwin->w_changelistidx ? '>' : ' ', 1153 i > 1154 curwin->w_changelistidx ? i - curwin->w_changelistidx : curwin->w_changelistidx - i, 1155 curbuf->b_changelist[i].mark.lnum, 1156 curbuf->b_changelist[i].mark.col); 1157 msg_outtrans(IObuff, 0, false); 1158 char *name = mark_line(&curbuf->b_changelist[i].mark, 17); 1159 msg_outtrans(name, HLF_D, false); 1160 xfree(name); 1161 os_breakcheck(); 1162 } 1163 } 1164 if (curwin->w_changelistidx == curbuf->b_changelistlen) { 1165 msg_puts("\n>"); 1166 } 1167 } 1168 1169 #define ONE_ADJUST(add) \ 1170 { \ 1171 lp = add; \ 1172 if (*lp >= line1 && *lp <= line2) { \ 1173 if (amount == MAXLNUM) { \ 1174 *lp = 0; \ 1175 } else { \ 1176 *lp += amount; \ 1177 } \ 1178 } else if (amount_after && *lp > line2) { \ 1179 *lp += amount_after; \ 1180 } \ 1181 } 1182 1183 // "NO DELete": don't delete the line, just put at first deleted line. 1184 #define ONE_ADJUST_NODEL(add) \ 1185 { \ 1186 lp = add; \ 1187 if (*lp >= line1 && *lp <= line2) { \ 1188 if (amount == MAXLNUM) { \ 1189 *lp = line1; \ 1190 } else { \ 1191 *lp += amount; \ 1192 } \ 1193 } else if (amount_after && *lp > line2) { \ 1194 *lp += amount_after; \ 1195 } \ 1196 } 1197 1198 // Like ONE_ADJUST_NODEL(), but if the position is within the deleted range, 1199 // move it to the start of the line before the range. 1200 #define ONE_ADJUST_CURSOR(pp) \ 1201 { \ 1202 pos_T *posp = pp; \ 1203 if (posp->lnum >= line1 && posp->lnum <= line2) { \ 1204 if (amount == MAXLNUM) { /* line with cursor is deleted */ \ 1205 posp->lnum = MAX(line1 - 1, 1); \ 1206 posp->col = 0; \ 1207 } else { /* keep cursor on the same line */ \ 1208 posp->lnum += amount; \ 1209 } \ 1210 } else if (amount_after && posp->lnum > line2) { \ 1211 posp->lnum += amount_after; \ 1212 } \ 1213 } 1214 1215 // Adjust marks between "line1" and "line2" (inclusive) to move "amount" lines. 1216 // Must be called before changed_*(), appended_lines() or deleted_lines(). 1217 // May be called before or after changing the text. 1218 // When deleting lines "line1" to "line2", use an "amount" of MAXLNUM: The 1219 // marks within this range are made invalid. 1220 // If "amount_after" is non-zero adjust marks after "line2". 1221 // Example: Delete lines 34 and 35: mark_adjust(34, 35, MAXLNUM, -2); 1222 // Example: Insert two lines below 55: mark_adjust(56, MAXLNUM, 2, 0); 1223 // or: mark_adjust(56, 55, MAXLNUM, 2); 1224 void mark_adjust(linenr_T line1, linenr_T line2, linenr_T amount, linenr_T amount_after, 1225 ExtmarkOp op) 1226 { 1227 mark_adjust_buf(curbuf, line1, line2, amount, amount_after, true, kMarkAdjustNormal, op); 1228 } 1229 1230 // mark_adjust_nofold() does the same as mark_adjust() but without adjusting 1231 // folds in any way. Folds must be adjusted manually by the caller. 1232 // This is only useful when folds need to be moved in a way different to 1233 // calling foldMarkAdjust() with arguments line1, line2, amount, amount_after, 1234 // for an example of why this may be necessary, see do_move(). 1235 void mark_adjust_nofold(linenr_T line1, linenr_T line2, linenr_T amount, linenr_T amount_after, 1236 ExtmarkOp op) 1237 { 1238 mark_adjust_buf(curbuf, line1, line2, amount, amount_after, false, kMarkAdjustNormal, op); 1239 } 1240 1241 void mark_adjust_buf(buf_T *buf, linenr_T line1, linenr_T line2, linenr_T amount, 1242 linenr_T amount_after, bool adjust_folds, MarkAdjustMode mode, ExtmarkOp op) 1243 { 1244 int fnum = buf->b_fnum; 1245 linenr_T *lp; 1246 static pos_T initpos = { 1, 0, 0 }; 1247 1248 if (line2 < line1 && amount_after == 0) { // nothing to do 1249 return; 1250 } 1251 1252 bool by_api = mode == kMarkAdjustApi; 1253 bool by_term = mode == kMarkAdjustTerm; 1254 1255 if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { 1256 // named marks, lower case and upper case 1257 for (int i = 0; i < NMARKS; i++) { 1258 ONE_ADJUST(&(buf->b_namedm[i].mark.lnum)); 1259 if (namedfm[i].fmark.fnum == fnum) { 1260 ONE_ADJUST_NODEL(&(namedfm[i].fmark.mark.lnum)); 1261 } 1262 } 1263 for (int i = NMARKS; i < NGLOBALMARKS; i++) { 1264 if (namedfm[i].fmark.fnum == fnum) { 1265 ONE_ADJUST_NODEL(&(namedfm[i].fmark.mark.lnum)); 1266 } 1267 } 1268 1269 // last Insert position 1270 ONE_ADJUST(&(buf->b_last_insert.mark.lnum)); 1271 1272 // last change position 1273 ONE_ADJUST(&(buf->b_last_change.mark.lnum)); 1274 1275 // last cursor position, if it was set 1276 if (!equalpos(buf->b_last_cursor.mark, initpos) 1277 && (!by_term || buf->b_last_cursor.mark.lnum < buf->b_ml.ml_line_count)) { 1278 ONE_ADJUST(&(buf->b_last_cursor.mark.lnum)); 1279 } 1280 1281 // on prompt buffer adjust the last prompt start location mark 1282 if (bt_prompt(buf)) { 1283 ONE_ADJUST_NODEL(&(buf->b_prompt_start.mark.lnum)); 1284 } 1285 1286 // list of change positions 1287 for (int i = 0; i < buf->b_changelistlen; i++) { 1288 ONE_ADJUST_NODEL(&(buf->b_changelist[i].mark.lnum)); 1289 } 1290 1291 // Visual area 1292 ONE_ADJUST_NODEL(&(buf->b_visual.vi_start.lnum)); 1293 ONE_ADJUST_NODEL(&(buf->b_visual.vi_end.lnum)); 1294 1295 // quickfix marks 1296 if (!qf_mark_adjust(buf, NULL, line1, line2, amount, amount_after)) { 1297 buf->b_has_qf_entry &= ~BUF_HAS_QF_ENTRY; 1298 } 1299 // location lists 1300 bool found_one = false; 1301 FOR_ALL_TAB_WINDOWS(tab, win) { 1302 found_one |= qf_mark_adjust(buf, win, line1, line2, amount, amount_after); 1303 } 1304 if (!found_one) { 1305 buf->b_has_qf_entry &= ~BUF_HAS_LL_ENTRY; 1306 } 1307 } 1308 1309 if (op != kExtmarkNOOP) { 1310 extmark_adjust(buf, line1, line2, amount, amount_after, op); 1311 } 1312 1313 if (curwin->w_buffer == buf) { 1314 // previous context mark 1315 ONE_ADJUST(&(curwin->w_pcmark.lnum)); 1316 1317 // previous pcpmark 1318 ONE_ADJUST(&(curwin->w_prev_pcmark.lnum)); 1319 1320 // saved cursor for formatting 1321 if (saved_cursor.lnum != 0) { 1322 ONE_ADJUST_NODEL(&(saved_cursor.lnum)); 1323 } 1324 } 1325 1326 // Adjust items in all windows related to the current buffer. 1327 FOR_ALL_TAB_WINDOWS(tab, win) { 1328 if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { 1329 // Marks in the jumplist. When deleting lines, this may create 1330 // duplicate marks in the jumplist, they will be removed later. 1331 for (int i = 0; i < win->w_jumplistlen; i++) { 1332 if (win->w_jumplist[i].fmark.fnum == fnum) { 1333 ONE_ADJUST_NODEL(&(win->w_jumplist[i].fmark.mark.lnum)); 1334 } 1335 } 1336 } 1337 1338 if (win->w_buffer == buf) { 1339 if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { 1340 // marks in the tag stack 1341 for (int i = 0; i < win->w_tagstacklen; i++) { 1342 if (win->w_tagstack[i].fmark.fnum == fnum) { 1343 ONE_ADJUST_NODEL(&(win->w_tagstack[i].fmark.mark.lnum)); 1344 } 1345 } 1346 } 1347 1348 // the displayed Visual area 1349 if (win->w_old_cursor_lnum != 0) { 1350 ONE_ADJUST_NODEL(&(win->w_old_cursor_lnum)); 1351 ONE_ADJUST_NODEL(&(win->w_old_visual_lnum)); 1352 } 1353 1354 // topline and cursor position for windows with the same buffer 1355 // other than the current window 1356 if (by_api || (by_term ? win->w_cursor.lnum < buf->b_ml.ml_line_count : win != curwin)) { 1357 if (win->w_topline >= line1 && win->w_topline <= line2) { 1358 if (amount == MAXLNUM) { // topline is deleted 1359 if (by_api && amount_after > line1 - line2 - 1) { 1360 // api: if the deleted region was replaced with new contents, topline will 1361 // get adjusted later as an effect of the adjusted cursor in fix_cursor() 1362 } else { 1363 win->w_topline = MAX(line1 - 1, 1); 1364 } 1365 } else if (win->w_topline > line1) { 1366 // keep topline on the same line, unless inserting just 1367 // above it (we probably want to see that line then) 1368 win->w_topline += amount; 1369 } 1370 win->w_topfill = 0; 1371 } else if (amount_after 1372 // api: display new line if inserted right at topline 1373 // TODO(bfredl): maybe always? 1374 && win->w_topline > line2 + (by_api && line2 < line1 ? 1 : 0)) { 1375 win->w_topline += amount_after; 1376 win->w_topfill = 0; 1377 } 1378 } 1379 if (!by_api && (by_term ? win->w_cursor.lnum < buf->b_ml.ml_line_count : win != curwin)) { 1380 ONE_ADJUST_CURSOR(&(win->w_cursor)); 1381 } 1382 1383 if (adjust_folds) { 1384 foldMarkAdjust(win, line1, line2, amount, amount_after); 1385 } 1386 } 1387 } 1388 1389 // adjust diffs 1390 diff_mark_adjust(buf, line1, line2, amount, amount_after); 1391 1392 // adjust per-window "last cursor" positions 1393 for (size_t i = 0; i < kv_size(buf->b_wininfo); i++) { 1394 WinInfo *wip = kv_A(buf->b_wininfo, i); 1395 if (!by_term || wip->wi_mark.mark.lnum < buf->b_ml.ml_line_count) { 1396 ONE_ADJUST_CURSOR(&(wip->wi_mark.mark)); 1397 } 1398 } 1399 } 1400 1401 // This code is used often, needs to be fast. 1402 #define COL_ADJUST(pp) \ 1403 { \ 1404 posp = pp; \ 1405 if (posp->lnum == lnum && posp->col >= mincol) { \ 1406 posp->lnum += lnum_amount; \ 1407 assert(col_amount > INT_MIN && col_amount <= INT_MAX); \ 1408 if (col_amount < 0 && posp->col <= -col_amount) { \ 1409 posp->col = 0; \ 1410 } else if (posp->col < spaces_removed) { \ 1411 posp->col = col_amount + spaces_removed; \ 1412 } else { \ 1413 posp->col += col_amount; \ 1414 } \ 1415 } \ 1416 } 1417 1418 // Adjust marks in line "lnum" at column "mincol" and further: add 1419 // "lnum_amount" to the line number and add "col_amount" to the column 1420 // position. 1421 // "spaces_removed" is the number of spaces that were removed, matters when the 1422 // cursor is inside them. 1423 void mark_col_adjust(linenr_T lnum, colnr_T mincol, linenr_T lnum_amount, colnr_T col_amount, 1424 int spaces_removed) 1425 { 1426 int fnum = curbuf->b_fnum; 1427 pos_T *posp; 1428 1429 if ((col_amount == 0 && lnum_amount == 0) || (cmdmod.cmod_flags & CMOD_LOCKMARKS)) { 1430 return; // nothing to do 1431 } 1432 // named marks, lower case and upper case 1433 for (int i = 0; i < NMARKS; i++) { 1434 COL_ADJUST(&(curbuf->b_namedm[i].mark)); 1435 if (namedfm[i].fmark.fnum == fnum) { 1436 COL_ADJUST(&(namedfm[i].fmark.mark)); 1437 } 1438 } 1439 for (int i = NMARKS; i < NGLOBALMARKS; i++) { 1440 if (namedfm[i].fmark.fnum == fnum) { 1441 COL_ADJUST(&(namedfm[i].fmark.mark)); 1442 } 1443 } 1444 1445 // last Insert position 1446 COL_ADJUST(&(curbuf->b_last_insert.mark)); 1447 1448 // last change position 1449 COL_ADJUST(&(curbuf->b_last_change.mark)); 1450 1451 if (bt_prompt(curbuf)) { 1452 COL_ADJUST(&(curbuf->b_prompt_start.mark)); 1453 } 1454 1455 // list of change positions 1456 for (int i = 0; i < curbuf->b_changelistlen; i++) { 1457 COL_ADJUST(&(curbuf->b_changelist[i].mark)); 1458 } 1459 1460 // Visual area 1461 COL_ADJUST(&(curbuf->b_visual.vi_start)); 1462 COL_ADJUST(&(curbuf->b_visual.vi_end)); 1463 1464 // previous context mark 1465 COL_ADJUST(&(curwin->w_pcmark)); 1466 1467 // previous pcmark 1468 COL_ADJUST(&(curwin->w_prev_pcmark)); 1469 1470 // saved cursor for formatting 1471 COL_ADJUST(&saved_cursor); 1472 1473 // Adjust items in all windows related to the current buffer. 1474 FOR_ALL_WINDOWS_IN_TAB(win, curtab) { 1475 // marks in the jumplist 1476 for (int i = 0; i < win->w_jumplistlen; i++) { 1477 if (win->w_jumplist[i].fmark.fnum == fnum) { 1478 COL_ADJUST(&(win->w_jumplist[i].fmark.mark)); 1479 } 1480 } 1481 1482 if (win->w_buffer == curbuf) { 1483 // marks in the tag stack 1484 for (int i = 0; i < win->w_tagstacklen; i++) { 1485 if (win->w_tagstack[i].fmark.fnum == fnum) { 1486 COL_ADJUST(&(win->w_tagstack[i].fmark.mark)); 1487 } 1488 } 1489 1490 // cursor position for other windows with the same buffer 1491 if (win != curwin) { 1492 COL_ADJUST(&win->w_cursor); 1493 } 1494 } 1495 } 1496 } 1497 1498 // When deleting lines, this may create duplicate marks in the 1499 // jumplist. They will be removed here for the specified window. 1500 // When "loadfiles" is true first ensure entries have the "fnum" field set 1501 // (this may be a bit slow). 1502 void cleanup_jumplist(win_T *wp, bool loadfiles) 1503 { 1504 int i; 1505 1506 if (loadfiles) { 1507 // If specified, load all the files from the jump list. This is 1508 // needed to properly clean up duplicate entries, but will take some 1509 // time. 1510 for (i = 0; i < wp->w_jumplistlen; i++) { 1511 if ((wp->w_jumplist[i].fmark.fnum == 0) 1512 && (wp->w_jumplist[i].fmark.mark.lnum != 0)) { 1513 fname2fnum(&wp->w_jumplist[i]); 1514 } 1515 } 1516 } 1517 1518 int to = 0; 1519 for (int from = 0; from < wp->w_jumplistlen; from++) { 1520 if (wp->w_jumplistidx == from) { 1521 wp->w_jumplistidx = to; 1522 } 1523 for (i = from + 1; i < wp->w_jumplistlen; i++) { 1524 if (wp->w_jumplist[i].fmark.fnum 1525 == wp->w_jumplist[from].fmark.fnum 1526 && wp->w_jumplist[from].fmark.fnum != 0 1527 && wp->w_jumplist[i].fmark.mark.lnum 1528 == wp->w_jumplist[from].fmark.mark.lnum) { 1529 break; 1530 } 1531 } 1532 1533 bool mustfree; 1534 if (i >= wp->w_jumplistlen) { // not duplicate 1535 mustfree = false; 1536 } else if (i > from + 1) { // non-adjacent duplicate 1537 // jumpoptions=stack: remove duplicates only when adjacent. 1538 mustfree = !(jop_flags & kOptJopFlagStack); 1539 } else { // adjacent duplicate 1540 mustfree = true; 1541 } 1542 1543 if (mustfree) { 1544 xfree(wp->w_jumplist[from].fname); 1545 } else { 1546 if (to != from) { 1547 // Not using wp->w_jumplist[to++] = wp->w_jumplist[from] because 1548 // this way valgrind complains about overlapping source and destination 1549 // in memcpy() call. (clang-3.6.0, debug build with -DEXITFREE). 1550 wp->w_jumplist[to] = wp->w_jumplist[from]; 1551 } 1552 to++; 1553 } 1554 } 1555 if (wp->w_jumplistidx == wp->w_jumplistlen) { 1556 wp->w_jumplistidx = to; 1557 } 1558 wp->w_jumplistlen = to; 1559 1560 // When pointer is below last jump, remove the jump if it matches the current 1561 // line. This avoids useless/phantom jumps. #9805 1562 if (loadfiles // otherwise (i.e.: Shada), last entry should be kept 1563 && wp->w_jumplistlen && wp->w_jumplistidx == wp->w_jumplistlen) { 1564 const xfmark_T *fm_last = &wp->w_jumplist[wp->w_jumplistlen - 1]; 1565 if (fm_last->fmark.fnum == curbuf->b_fnum 1566 && fm_last->fmark.mark.lnum == wp->w_cursor.lnum) { 1567 xfree(fm_last->fname); 1568 wp->w_jumplistlen--; 1569 wp->w_jumplistidx--; 1570 } 1571 } 1572 } 1573 1574 // Copy the jumplist from window "from" to window "to". 1575 void copy_jumplist(win_T *from, win_T *to) 1576 { 1577 for (int i = 0; i < from->w_jumplistlen; i++) { 1578 to->w_jumplist[i] = from->w_jumplist[i]; 1579 if (from->w_jumplist[i].fname != NULL) { 1580 to->w_jumplist[i].fname = xstrdup(from->w_jumplist[i].fname); 1581 } 1582 } 1583 to->w_jumplistlen = from->w_jumplistlen; 1584 to->w_jumplistidx = from->w_jumplistidx; 1585 } 1586 1587 /// Iterate over jumplist items 1588 /// 1589 /// @warning No jumplist-editing functions must be called while iteration is in 1590 /// progress. 1591 /// 1592 /// @param[in] iter Iterator. Pass NULL to start iteration. 1593 /// @param[in] win Window for which jump list is processed. 1594 /// @param[out] fm Item definition. 1595 /// 1596 /// @return Pointer that needs to be passed to next `mark_jumplist_iter` call or 1597 /// NULL if iteration is over. 1598 const void *mark_jumplist_iter(const void *const iter, const win_T *const win, xfmark_T *const fm) 1599 FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT 1600 { 1601 if (iter == NULL && win->w_jumplistlen == 0) { 1602 *fm = (xfmark_T)INIT_XFMARK; 1603 return NULL; 1604 } 1605 const xfmark_T *const iter_mark = iter == NULL ? &(win->w_jumplist[0]) 1606 : (const xfmark_T *const)iter; 1607 *fm = *iter_mark; 1608 if (iter_mark == &(win->w_jumplist[win->w_jumplistlen - 1])) { 1609 return NULL; 1610 } 1611 return iter_mark + 1; 1612 } 1613 1614 /// Iterate over global marks 1615 /// 1616 /// @warning No mark-editing functions must be called while iteration is in 1617 /// progress. 1618 /// 1619 /// @param[in] iter Iterator. Pass NULL to start iteration. 1620 /// @param[out] name Mark name. 1621 /// @param[out] fm Mark definition. 1622 /// 1623 /// @return Pointer that needs to be passed to next `mark_global_iter` call or 1624 /// NULL if iteration is over. 1625 const void *mark_global_iter(const void *const iter, char *const name, xfmark_T *const fm) 1626 FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT 1627 { 1628 *name = NUL; 1629 const xfmark_T *iter_mark = (iter == NULL 1630 ? &(namedfm[0]) 1631 : (const xfmark_T *const)iter); 1632 while ((size_t)(iter_mark - &(namedfm[0])) < ARRAY_SIZE(namedfm) 1633 && !iter_mark->fmark.mark.lnum) { 1634 iter_mark++; 1635 } 1636 if ((size_t)(iter_mark - &(namedfm[0])) == ARRAY_SIZE(namedfm) 1637 || !iter_mark->fmark.mark.lnum) { 1638 return NULL; 1639 } 1640 size_t iter_off = (size_t)(iter_mark - &(namedfm[0])); 1641 *name = (char)(iter_off < NMARKS 1642 ? 'A' + (char)iter_off 1643 : '0' + (char)(iter_off - NMARKS)); 1644 *fm = *iter_mark; 1645 while ((size_t)(++iter_mark - &(namedfm[0])) < ARRAY_SIZE(namedfm)) { 1646 if (iter_mark->fmark.mark.lnum) { 1647 return (const void *)iter_mark; 1648 } 1649 } 1650 return NULL; 1651 } 1652 1653 /// Get next mark and its name 1654 /// 1655 /// @param[in] buf Buffer for which next mark is taken. 1656 /// @param[in,out] mark_name Pointer to the current mark name. Next mark name 1657 /// will be saved at this address as well. 1658 /// 1659 /// Current mark name must either be NUL, '"', '^', 1660 /// '.' or 'a' .. 'z'. If it is neither of these 1661 /// behaviour is undefined. 1662 /// 1663 /// @return Pointer to the next mark or NULL. 1664 static inline const fmark_T *next_buffer_mark(const buf_T *const buf, char *const mark_name) 1665 FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT 1666 { 1667 switch (*mark_name) { 1668 case NUL: 1669 *mark_name = '"'; 1670 return &(buf->b_last_cursor); 1671 case '"': 1672 *mark_name = '^'; 1673 return &(buf->b_last_insert); 1674 case '^': 1675 *mark_name = '.'; 1676 return &(buf->b_last_change); 1677 case '.': 1678 *mark_name = 'a'; 1679 return &(buf->b_namedm[0]); 1680 case 'z': 1681 return NULL; 1682 default: 1683 (*mark_name)++; 1684 return &(buf->b_namedm[*mark_name - 'a']); 1685 } 1686 } 1687 1688 /// Iterate over buffer marks 1689 /// 1690 /// @warning No mark-editing functions must be called while iteration is in 1691 /// progress. 1692 /// 1693 /// @param[in] iter Iterator. Pass NULL to start iteration. 1694 /// @param[in] buf Buffer. 1695 /// @param[out] name Mark name. 1696 /// @param[out] fm Mark definition. 1697 /// 1698 /// @return Pointer that needs to be passed to next `mark_buffer_iter` call or 1699 /// NULL if iteration is over. 1700 const void *mark_buffer_iter(const void *const iter, const buf_T *const buf, char *const name, 1701 fmark_T *const fm) 1702 FUNC_ATTR_NONNULL_ARG(2, 3, 4) FUNC_ATTR_WARN_UNUSED_RESULT 1703 { 1704 *name = NUL; 1705 char mark_name = (char)(iter == NULL 1706 ? NUL 1707 : (iter == &(buf->b_last_cursor) 1708 ? '"' 1709 : (iter == &(buf->b_last_insert) 1710 ? '^' 1711 : (iter == &(buf->b_last_change) 1712 ? '.' 1713 : 'a' + (const fmark_T *)iter - &(buf->b_namedm[0]))))); 1714 const fmark_T *iter_mark = next_buffer_mark(buf, &mark_name); 1715 while (iter_mark != NULL && iter_mark->mark.lnum == 0) { 1716 iter_mark = next_buffer_mark(buf, &mark_name); 1717 } 1718 if (iter_mark == NULL) { 1719 return NULL; 1720 } 1721 size_t iter_off = (size_t)(iter_mark - &(buf->b_namedm[0])); 1722 if (mark_name) { 1723 *name = mark_name; 1724 } else { 1725 *name = (char)('a' + (char)iter_off); 1726 } 1727 *fm = *iter_mark; 1728 return (const void *)iter_mark; 1729 } 1730 1731 /// Set global mark 1732 /// 1733 /// @param[in] name Mark name. 1734 /// @param[in] fm Mark to be set. 1735 /// @param[in] update If true then only set global mark if it was created 1736 /// later then existing one. 1737 /// 1738 /// @return true on success, false on failure. 1739 bool mark_set_global(const char name, const xfmark_T fm, const bool update) 1740 { 1741 const int idx = mark_global_index(name); 1742 if (idx == -1) { 1743 return false; 1744 } 1745 xfmark_T *const fm_tgt = &(namedfm[idx]); 1746 if (update && fm.fmark.timestamp <= fm_tgt->fmark.timestamp) { 1747 return false; 1748 } 1749 if (fm_tgt->fmark.mark.lnum != 0) { 1750 free_xfmark(*fm_tgt); 1751 } 1752 *fm_tgt = fm; 1753 return true; 1754 } 1755 1756 /// Set local mark 1757 /// 1758 /// @param[in] name Mark name. 1759 /// @param[in] buf Pointer to the buffer to set mark in. 1760 /// @param[in] fm Mark to be set. 1761 /// @param[in] update If true then only set global mark if it was created 1762 /// later then existing one. 1763 /// 1764 /// @return true on success, false on failure. 1765 bool mark_set_local(const char name, buf_T *const buf, const fmark_T fm, const bool update) 1766 FUNC_ATTR_NONNULL_ALL 1767 { 1768 fmark_T *fm_tgt = NULL; 1769 if (ASCII_ISLOWER(name)) { 1770 fm_tgt = &(buf->b_namedm[name - 'a']); 1771 } else if (name == '"') { 1772 fm_tgt = &(buf->b_last_cursor); 1773 } else if (name == '^') { 1774 fm_tgt = &(buf->b_last_insert); 1775 } else if (name == ':') { 1776 fm_tgt = &(buf->b_prompt_start); 1777 } else if (name == '.') { 1778 fm_tgt = &(buf->b_last_change); 1779 } else { 1780 return false; 1781 } 1782 if (update && fm.timestamp <= fm_tgt->timestamp) { 1783 return false; 1784 } 1785 if (fm_tgt->mark.lnum != 0) { 1786 free_fmark(*fm_tgt); 1787 } 1788 *fm_tgt = fm; 1789 return true; 1790 } 1791 1792 // Free items in the jumplist of window "wp". 1793 void free_jumplist(win_T *wp) 1794 { 1795 for (int i = 0; i < wp->w_jumplistlen; i++) { 1796 free_xfmark(wp->w_jumplist[i]); 1797 } 1798 wp->w_jumplistlen = 0; 1799 } 1800 1801 void set_last_cursor(win_T *win) 1802 { 1803 if (win->w_buffer != NULL) { 1804 RESET_FMARK(&win->w_buffer->b_last_cursor, win->w_cursor, 0, ((fmarkv_T)INIT_FMARKV)); 1805 } 1806 } 1807 1808 #if defined(EXITFREE) 1809 void free_all_marks(void) 1810 { 1811 int i; 1812 1813 for (i = 0; i < NGLOBALMARKS; i++) { 1814 if (namedfm[i].fmark.mark.lnum != 0) { 1815 free_xfmark(namedfm[i]); 1816 } 1817 } 1818 CLEAR_FIELD(namedfm); 1819 } 1820 #endif 1821 1822 /// Adjust position to point to the first byte of a multi-byte character 1823 /// 1824 /// If it points to a tail byte it is move backwards to the head byte. 1825 /// 1826 /// @param[in] buf Buffer to adjust position in. 1827 /// @param[out] lp Position to adjust. 1828 void mark_mb_adjustpos(buf_T *buf, pos_T *lp) 1829 FUNC_ATTR_NONNULL_ALL 1830 { 1831 if (lp->col > 0 || lp->coladd > 1) { 1832 const char *const p = ml_get_buf(buf, lp->lnum); 1833 if (*p == NUL || ml_get_buf_len(buf, lp->lnum) < lp->col) { 1834 lp->col = 0; 1835 } else { 1836 lp->col -= utf_head_off(p, p + lp->col); 1837 } 1838 // Reset "coladd" when the cursor would be on the right half of a 1839 // double-wide character. 1840 if (lp->coladd == 1 1841 && p[lp->col] != TAB 1842 && vim_isprintc(utf_ptr2char(p + lp->col)) 1843 && ptr2cells(p + lp->col) > 1) { 1844 lp->coladd = 0; 1845 } 1846 } 1847 } 1848 1849 // Add information about mark 'mname' to list 'l' 1850 static int add_mark(list_T *l, const char *mname, const pos_T *pos, int bufnr, const char *fname) 1851 FUNC_ATTR_NONNULL_ARG(1, 2, 3) 1852 { 1853 if (pos->lnum <= 0) { 1854 return OK; 1855 } 1856 1857 dict_T *d = tv_dict_alloc(); 1858 tv_list_append_dict(l, d); 1859 1860 list_T *lpos = tv_list_alloc(kListLenMayKnow); 1861 1862 tv_list_append_number(lpos, bufnr); 1863 tv_list_append_number(lpos, pos->lnum); 1864 tv_list_append_number(lpos, pos->col < MAXCOL ? pos->col + 1 : MAXCOL); 1865 tv_list_append_number(lpos, pos->coladd); 1866 1867 if (tv_dict_add_str(d, S_LEN("mark"), mname) == FAIL 1868 || tv_dict_add_list(d, S_LEN("pos"), lpos) == FAIL 1869 || (fname != NULL && tv_dict_add_str(d, S_LEN("file"), fname) == FAIL)) { 1870 return FAIL; 1871 } 1872 1873 return OK; 1874 } 1875 1876 /// Get information about marks local to a buffer. 1877 /// 1878 /// @param[in] buf Buffer to get the marks from 1879 /// @param[out] l List to store marks 1880 void get_buf_local_marks(const buf_T *buf, list_T *l) 1881 FUNC_ATTR_NONNULL_ALL 1882 { 1883 char mname[3] = "' "; 1884 1885 // Marks 'a' to 'z' 1886 for (int i = 0; i < NMARKS; i++) { 1887 mname[1] = (char)('a' + i); 1888 add_mark(l, mname, &buf->b_namedm[i].mark, buf->b_fnum, NULL); 1889 } 1890 1891 // Mark '' is a window local mark and not a buffer local mark 1892 add_mark(l, "''", &curwin->w_pcmark, curbuf->b_fnum, NULL); 1893 1894 add_mark(l, "'\"", &buf->b_last_cursor.mark, buf->b_fnum, NULL); 1895 add_mark(l, "'[", &buf->b_op_start, buf->b_fnum, NULL); 1896 add_mark(l, "']", &buf->b_op_end, buf->b_fnum, NULL); 1897 add_mark(l, "'^", &buf->b_last_insert.mark, buf->b_fnum, NULL); 1898 add_mark(l, "'.", &buf->b_last_change.mark, buf->b_fnum, NULL); 1899 add_mark(l, "'<", &buf->b_visual.vi_start, buf->b_fnum, NULL); 1900 add_mark(l, "'>", &buf->b_visual.vi_end, buf->b_fnum, NULL); 1901 } 1902 1903 /// Get a global mark 1904 /// 1905 /// @note Mark might not have it's fnum resolved. 1906 /// @param[in] Name of named mark 1907 /// @param[out] Global/file mark 1908 xfmark_T get_raw_global_mark(char name) 1909 { 1910 return namedfm[mark_global_index(name)]; 1911 } 1912 1913 /// Get information about global marks ('A' to 'Z' and '0' to '9') 1914 /// 1915 /// @param[out] l List to store global marks 1916 void get_global_marks(list_T *l) 1917 FUNC_ATTR_NONNULL_ALL 1918 { 1919 char mname[3] = "' "; 1920 char *name; 1921 1922 // Marks 'A' to 'Z' and '0' to '9' 1923 for (int i = 0; i < NMARKS + EXTRA_MARKS; i++) { 1924 if (namedfm[i].fmark.fnum != 0) { 1925 name = buflist_nr2name(namedfm[i].fmark.fnum, true, true); 1926 } else { 1927 name = namedfm[i].fname; 1928 } 1929 if (name != NULL) { 1930 mname[1] = i >= NMARKS ? (char)(i - NMARKS + '0') : (char)(i + 'A'); 1931 1932 add_mark(l, mname, &namedfm[i].fmark.mark, namedfm[i].fmark.fnum, name); 1933 if (namedfm[i].fmark.fnum != 0) { 1934 xfree(name); 1935 } 1936 } 1937 } 1938 }