// This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com /* * mark.c: functions for setting marks and jumping to them */ #include #include #include #include #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/diff.h" #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/ex_cmds.h" #include "nvim/extmark.h" #include "nvim/fold.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/path.h" #include "nvim/quickfix.h" #include "nvim/search.h" #include "nvim/sign.h" #include "nvim/strings.h" #include "nvim/ui.h" #include "nvim/vim.h" /* * This file contains routines to maintain and manipulate marks. */ /* * If a named file mark's lnum is non-zero, it is valid. * If a named file mark's fnum is non-zero, it is for an existing buffer, * otherwise it is from .shada and namedfm[n].fname is the file name. * There are marks 'A - 'Z (set by user) and '0 to '9 (set when writing * shada). */ /// Global marks (marks with file number or name) static xfmark_T namedfm[NGLOBALMARKS]; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "mark.c.generated.h" #endif /* * Set named mark "c" at current cursor position. * Returns OK on success, FAIL if bad name given. */ int setmark(int c) { fmarkv_T view = mark_view_make(curwin->w_topline, curwin->w_cursor); return setmark_pos(c, &curwin->w_cursor, curbuf->b_fnum, &view); } /// Free fmark_T item void free_fmark(fmark_T fm) { tv_dict_unref(fm.additional_data); } /// Free xfmark_T item void free_xfmark(xfmark_T fm) { xfree(fm.fname); free_fmark(fm.fmark); } /// Free and clear fmark_T item void clear_fmark(fmark_T *fm) FUNC_ATTR_NONNULL_ALL { free_fmark(*fm); CLEAR_POINTER(fm); } /* * Set named mark "c" to position "pos". * When "c" is upper case use file "fnum". * Returns OK on success, FAIL if bad name given. */ int setmark_pos(int c, pos_T *pos, int fnum, fmarkv_T *view_pt) { int i; fmarkv_T view = view_pt != NULL ? *view_pt : (fmarkv_T)INIT_FMARKV; // Check for a special key (may cause islower() to crash). if (c < 0) { return FAIL; } if (c == '\'' || c == '`') { if (pos == &curwin->w_cursor) { setpcmark(); // keep it even when the cursor doesn't move curwin->w_prev_pcmark = curwin->w_pcmark; } else { curwin->w_pcmark = *pos; } return OK; } // Can't set a mark in a non-existent buffer. buf_T *buf = buflist_findnr(fnum); if (buf == NULL) { return FAIL; } if (c == '"') { RESET_FMARK(&buf->b_last_cursor, *pos, buf->b_fnum, view); return OK; } // Allow setting '[ and '] for an autocommand that simulates reading a // file. if (c == '[') { buf->b_op_start = *pos; return OK; } if (c == ']') { buf->b_op_end = *pos; return OK; } if (c == '<' || c == '>') { if (c == '<') { buf->b_visual.vi_start = *pos; } else { buf->b_visual.vi_end = *pos; } if (buf->b_visual.vi_mode == NUL) { // Visual_mode has not yet been set, use a sane default. buf->b_visual.vi_mode = 'v'; } return OK; } if (ASCII_ISLOWER(c)) { i = c - 'a'; RESET_FMARK(buf->b_namedm + i, *pos, fnum, view); return OK; } if (ASCII_ISUPPER(c) || ascii_isdigit(c)) { if (ascii_isdigit(c)) { i = c - '0' + NMARKS; } else { i = c - 'A'; } RESET_XFMARK(namedfm + i, *pos, fnum, view, NULL); return OK; } return FAIL; } /* * Set the previous context mark to the current position and add it to the * jump list. */ void setpcmark(void) { xfmark_T *fm; // for :global the mark is set only once if (global_busy || listcmd_busy || (cmdmod.cmod_flags & CMOD_KEEPJUMPS)) { return; } curwin->w_prev_pcmark = curwin->w_pcmark; curwin->w_pcmark = curwin->w_cursor; if (curwin->w_pcmark.lnum == 0) { curwin->w_pcmark.lnum = 1; } if (jop_flags & JOP_STACK) { // jumpoptions=stack: if we're somewhere in the middle of the jumplist // discard everything after the current index. if (curwin->w_jumplistidx < curwin->w_jumplistlen - 1) { // Discard the rest of the jumplist by cutting the length down to // contain nothing beyond the current index. curwin->w_jumplistlen = curwin->w_jumplistidx + 1; } } // If jumplist is full: remove oldest entry if (++curwin->w_jumplistlen > JUMPLISTSIZE) { curwin->w_jumplistlen = JUMPLISTSIZE; free_xfmark(curwin->w_jumplist[0]); memmove(&curwin->w_jumplist[0], &curwin->w_jumplist[1], (JUMPLISTSIZE - 1) * sizeof(curwin->w_jumplist[0])); } curwin->w_jumplistidx = curwin->w_jumplistlen; fm = &curwin->w_jumplist[curwin->w_jumplistlen - 1]; fmarkv_T view = mark_view_make(curwin->w_topline, curwin->w_pcmark); SET_XFMARK(fm, curwin->w_pcmark, curbuf->b_fnum, view, NULL); } /* * To change context, call setpcmark(), then move the current position to * where ever, then call checkpcmark(). This ensures that the previous * context will only be changed if the cursor moved to a different line. * If pcmark was deleted (with "dG") the previous mark is restored. */ void checkpcmark(void) { if (curwin->w_prev_pcmark.lnum != 0 && (equalpos(curwin->w_pcmark, curwin->w_cursor) || curwin->w_pcmark.lnum == 0)) { curwin->w_pcmark = curwin->w_prev_pcmark; } curwin->w_prev_pcmark.lnum = 0; // it has been checked } /// Get mark in "count" position in the |jumplist| relative to the current index. /// /// If the mark is in a different buffer, it will be skipped unless the buffer exists. /// /// @note cleanup_jumplist() is run, which removes duplicate marks, and /// changes win->w_jumplistidx. /// @param[in] win window to get jumplist from. /// @param[in] count count to move may be negative. /// /// @return mark, NULL if out of jumplist bounds. fmark_T *get_jumplist(win_T *win, int count) { xfmark_T *jmp = NULL; cleanup_jumplist(win, true); if (win->w_jumplistlen == 0) { // nothing to jump to return NULL; } for (;;) { if (win->w_jumplistidx + count < 0 || win->w_jumplistidx + count >= win->w_jumplistlen) { return NULL; } // if first CTRL-O or CTRL-I command after a jump, add cursor position // to list. Careful: If there are duplicates (CTRL-O immediately after // starting Vim on a file), another entry may have been removed. if (win->w_jumplistidx == win->w_jumplistlen) { setpcmark(); win->w_jumplistidx--; // skip the new entry if (win->w_jumplistidx + count < 0) { return NULL; } } win->w_jumplistidx += count; jmp = win->w_jumplist + win->w_jumplistidx; if (jmp->fmark.fnum == 0) { // Resolve the fnum (buff number) in the mark before returning it (shada) fname2fnum(jmp); } if (jmp->fmark.fnum != curbuf->b_fnum) { // Needs to switch buffer, if it can't find it skip the mark if (buflist_findnr(jmp->fmark.fnum) == NULL) { count += count < 0 ? -1 : 1; continue; } } break; } return &jmp->fmark; } /// Get mark in "count" position in the |changelist| relative to the current index. /// /// @note Changes the win->w_changelistidx. /// @param[in] win window to get jumplist from. /// @param[in] count count to move may be negative. /// /// @return mark, NULL if out of bounds. fmark_T *get_changelist(buf_T *buf, win_T *win, int count) { int n; fmark_T *fm; if (buf->b_changelistlen == 0) { // nothing to jump to return NULL; } n = win->w_changelistidx; if (n + count < 0) { if (n == 0) { return NULL; } n = 0; } else if (n + count >= buf->b_changelistlen) { if (n == buf->b_changelistlen - 1) { return NULL; } n = buf->b_changelistlen - 1; } else { n += count; } win->w_changelistidx = n; fm = &(buf->b_changelist[n]); // Changelist marks are always buffer local, Shada does not set it when loading fm->fnum = curbuf->handle; return &(buf->b_changelist[n]); } /// Get a named mark. /// /// All types of marks, even those that are not technically a mark will be returned as such. Use /// mark_move_to() to move to the mark. /// @note Some of the pointers are statically allocated, if in doubt make a copy. For more /// information read mark_get_local(). /// @param buf Buffer to get the mark from. /// @param win Window to get or calculate the mark from (motion type marks, context mark). /// @param fmp[out] Optional pointer to store the result in, as a workaround for the note above. /// @param flag MarkGet value /// @param name Name of the mark. /// /// @return Mark if found, otherwise NULL. For @c kMarkBufLocal, NULL is returned /// when no mark is found in @a buf. fmark_T *mark_get(buf_T *buf, win_T *win, fmark_T *fmp, MarkGet flag, int name) { fmark_T *fm = NULL; if (ASCII_ISUPPER(name) || ascii_isdigit(name)) { // Global marks xfmark_T *xfm = mark_get_global(flag != kMarkAllNoResolve, name); fm = &xfm->fmark; if (flag == kMarkBufLocal && xfm->fmark.fnum != buf->handle) { // Only wanted marks belonging to the buffer return pos_to_mark(buf, NULL, (pos_T){ .lnum = 0 }); } } else if (name > 0 && name < NMARK_LOCAL_MAX) { // Local Marks fm = mark_get_local(buf, win, name); } if (fmp != NULL && fm != NULL) { *fmp = *fm; return fmp; } return fm; } /// Get a global mark {A-Z0-9}. /// /// @param name the name of the mark. /// @param resolve Whether to try resolving the mark fnum (i.e., load the buffer stored in /// the mark fname and update the xfmark_T (expensive)). /// /// @return Mark xfmark_T *mark_get_global(bool resolve, int name) { xfmark_T *mark; if (ascii_isdigit(name)) { name = name - '0' + NMARKS; } else if (ASCII_ISUPPER(name)) { name -= 'A'; } else { // Not a valid mark name assert(false); } mark = &namedfm[name]; if (resolve && mark->fmark.fnum == 0) { // Resolve filename to fnum (SHADA marks) fname2fnum(mark); } return mark; } /// Get a local mark (lowercase and symbols). /// /// Some marks are not actually marks, but positions that are never adjusted or motions presented as /// marks. Search first for marks and fallback to finding motion type marks. If it's known /// ahead of time that the mark is actually a motion use the mark_get_motion() directly. /// /// @note Lowercase, last_cursor '"', last insert '^', last change '.' are not statically /// allocated, everything else is. /// @param name the name of the mark. /// @param win window to retrieve marks that belong to it (motions and context mark). /// @param buf buf to retrieve marks that belong to it. /// /// @return Mark, NULL if not found. fmark_T *mark_get_local(buf_T *buf, win_T *win, int name) { fmark_T *mark = NULL; if (ASCII_ISLOWER(name)) { // normal named mark mark = &buf->b_namedm[name - 'a']; // to start of previous operator } else if (name == '[') { mark = pos_to_mark(buf, NULL, buf->b_op_start); // to end of previous operator } else if (name == ']') { mark = pos_to_mark(buf, NULL, buf->b_op_end); // visual marks } else if (name == '<' || name == '>') { mark = mark_get_visual(buf, name); // previous context mark } else if (name == '\'' || name == '`') { // TODO(muniter): w_pcmark should be stored as a mark, but causes a nasty bug. mark = pos_to_mark(curbuf, NULL, win->w_pcmark); // to position when leaving buffer } else if (name == '"') { mark = &(buf->b_last_cursor); // to where last Insert mode stopped } else if (name == '^') { mark = &(buf->b_last_insert); // to where last change was made } else if (name == '.') { mark = &buf->b_last_change; // Mark that are actually not marks but motions, e.g {, }, (, ), ... } else { mark = mark_get_motion(buf, win, name); } if (mark) { mark->fnum = buf->b_fnum; } return mark; } /// Get marks that are actually motions but return them as marks /// /// Gets the following motions as marks: '{', '}', '(', ')' /// @param name name of the mark /// @param win window to retrieve the cursor to calculate the mark. /// @param buf buf to wrap motion marks with it's buffer number (fm->fnum). /// /// @return[static] Mark. fmark_T *mark_get_motion(buf_T *buf, win_T *win, int name) { fmark_T *mark = NULL; const pos_T pos = curwin->w_cursor; const bool slcb = listcmd_busy; listcmd_busy = true; // avoid that '' is changed if (name == '{' || name == '}') { // to previous/next paragraph oparg_T oa; if (findpar(&oa.inclusive, name == '}' ? FORWARD : BACKWARD, 1L, NUL, false)) { mark = pos_to_mark(buf, NULL, win->w_cursor); } } else if (name == '(' || name == ')') { // to previous/next sentence if (findsent(name == ')' ? FORWARD : BACKWARD, 1L)) { mark = pos_to_mark(buf, NULL, win->w_cursor); } } curwin->w_cursor = pos; listcmd_busy = slcb; return mark; } /// Get visual marks '<', '>' /// /// This marks are different to normal marks: /// 1. Never adjusted. /// 2. Different behavior depending on editor state (visual mode). /// 3. Not saved in shada. /// 4. Re-ordered when defined in reverse. /// @param buf Buffer to get the mark from. /// @param name Mark name '<' or '>'. /// /// @return[static] Mark fmark_T *mark_get_visual(buf_T *buf, int name) { fmark_T *mark = NULL; if (name == '<' || name == '>') { // start/end of visual area pos_T startp = buf->b_visual.vi_start; pos_T endp = buf->b_visual.vi_end; if (((name == '<') == lt(startp, endp) || endp.lnum == 0) && startp.lnum != 0) { mark = pos_to_mark(buf, NULL, startp); } else { mark = pos_to_mark(buf, NULL, endp); } if (buf->b_visual.vi_mode == 'V') { if (name == '<') { mark->mark.col = 0; } else { mark->mark.col = MAXCOL; } mark->mark.coladd = 0; } } return mark; } /// Wrap a pos_T into an fmark_T, used to abstract marks handling. /// /// Pass an fmp if multiple c /// @note view fields are set to 0. /// @param buf for fmark->fnum. /// @param pos for fmark->mark. /// @param fmp pointer to save the mark. /// /// @return[static] Mark with the given information. fmark_T *pos_to_mark(buf_T *buf, fmark_T *fmp, pos_T pos) FUNC_ATTR_NONNULL_RET { static fmark_T fms = INIT_FMARK; fmark_T *fm = fmp == NULL ? &fms : fmp; fm->fnum = buf->handle; fm->mark = pos; return fm; } /// Attempt to switch to the buffer of the given global mark /// /// @param fm /// @param pcmark_on_switch leave a context mark when switching buffer. /// @return whether the buffer was switched or not. static MarkMoveRes switch_to_mark_buf(fmark_T *fm, bool pcmark_on_switch) { bool res; if (fm->fnum != curbuf->b_fnum) { // Switch to another file. int getfile_flag = pcmark_on_switch ? GETF_SETMARK : 0; res = buflist_getfile(fm->fnum, (linenr_T)1, getfile_flag, false) == OK; return res == true ? kMarkSwitchedBuf : kMarkMoveFailed; } return 0; } /// Move to the given file mark, changing the buffer and cursor position. /// /// Validate the mark, switch to the buffer, and move the cursor. /// @param fm Mark, can be NULL will raise E78: Unknown mark /// @param flags MarkMove flags to configure the movement to the mark. /// /// @return MarkMovekRes flags representing the outcome MarkMoveRes mark_move_to(fmark_T *fm, MarkMove flags) { static fmark_T fm_copy = INIT_FMARK; MarkMoveRes res = kMarkMoveSuccess; if (!mark_check(fm)) { res = kMarkMoveFailed; goto end; } if (fm->fnum != curbuf->handle) { // Need to change buffer fm_copy = *fm; // Copy, autocommand may change it fm = &fm_copy; res |= switch_to_mark_buf(fm, !(flags & kMarkJumpList)); // Failed switching buffer if (res & kMarkMoveFailed) { goto end; } // Check line count now that the **destination buffer is loaded**. if (!mark_check_line_bounds(curbuf, fm)) { res |= kMarkMoveFailed; goto end; } } else if (flags & kMarkContext) { // Doing it in this condition avoids double context mark when switching buffer. setpcmark(); } // Move the cursor while keeping track of what changed for the caller pos_T prev_pos = curwin->w_cursor; pos_T pos = fm->mark; curwin->w_cursor = fm->mark; if (flags & kMarkBeginLine) { beginline(BL_WHITE | BL_FIX); } res = prev_pos.lnum != pos.lnum ? res | kMarkChangedLine | kMarkChangedCursor : res; res = prev_pos.col != pos.col ? res | kMarkChangedCol | kMarkChangedCursor : res; if (flags & kMarkSetView) { mark_view_restore(fm); } if (res & kMarkSwitchedBuf || res & kMarkChangedCursor) { check_cursor(); } end: return res; } /// Restore the mark view. /// By remembering the offset between topline and mark lnum at the time of /// definition, this function restores the "view". /// @note Assumes the mark has been checked, is valid. /// @param fm the named mark. void mark_view_restore(fmark_T *fm) { if (fm != NULL && fm->view.topline_offset >= 0) { linenr_T topline = fm->mark.lnum - fm->view.topline_offset; // If the mark does not have a view, topline_offset is MAXLNUM, // and this check can prevent restoring mark view in that case. if (topline >= 1) { set_topline(curwin, topline); } } } fmarkv_T mark_view_make(linenr_T topline, pos_T pos) { return (fmarkv_T){ pos.lnum - topline }; } /// Search for the next named mark in the current file from a start position. /// /// @param startpos where to start. /// @param dir direction for search. /// /// @return next mark or NULL if no mark is found. fmark_T *getnextmark(pos_T *startpos, int dir, int begin_line) { int i; fmark_T *result = NULL; pos_T pos; pos = *startpos; if (dir == BACKWARD && begin_line) { pos.col = 0; } else if (dir == FORWARD && begin_line) { pos.col = MAXCOL; } for (i = 0; i < NMARKS; i++) { if (curbuf->b_namedm[i].mark.lnum > 0) { if (dir == FORWARD) { if ((result == NULL || lt(curbuf->b_namedm[i].mark, result->mark)) && lt(pos, curbuf->b_namedm[i].mark)) { result = &curbuf->b_namedm[i]; } } else { if ((result == NULL || lt(result->mark, curbuf->b_namedm[i].mark)) && lt(curbuf->b_namedm[i].mark, pos)) { result = &curbuf->b_namedm[i]; } } } } return result; } /* * For an xtended filemark: set the fnum from the fname. * This is used for marks obtained from the .shada file. It's postponed * until the mark is used to avoid a long startup delay. */ static void fname2fnum(xfmark_T *fm) { char_u *p; if (fm->fname != NULL) { /* * First expand "~/" in the file name to the home directory. * Don't expand the whole name, it may contain other '~' chars. */ if (fm->fname[0] == '~' && (fm->fname[1] == '/' #ifdef BACKSLASH_IN_FILENAME || fm->fname[1] == '\\' #endif )) { int len; expand_env((char_u *)"~/", NameBuff, MAXPATHL); len = (int)STRLEN(NameBuff); STRLCPY(NameBuff + len, fm->fname + 2, MAXPATHL - len); } else { STRLCPY(NameBuff, fm->fname, MAXPATHL); } // Try to shorten the file name. os_dirname(IObuff, IOSIZE); p = path_shorten_fname(NameBuff, IObuff); // buflist_new() will call fmarks_check_names() (void)buflist_new((char *)NameBuff, (char *)p, (linenr_T)1, 0); } } /* * Check all file marks for a name that matches the file name in buf. * May replace the name with an fnum. * Used for marks that come from the .shada file. */ void fmarks_check_names(buf_T *buf) { char_u *name = (char_u *)buf->b_ffname; int i; if (buf->b_ffname == NULL) { return; } for (i = 0; i < NGLOBALMARKS; ++i) { fmarks_check_one(&namedfm[i], name, buf); } FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { for (i = 0; i < wp->w_jumplistlen; ++i) { fmarks_check_one(&wp->w_jumplist[i], name, buf); } } } static void fmarks_check_one(xfmark_T *fm, char_u *name, buf_T *buf) { if (fm->fmark.fnum == 0 && fm->fname != NULL && FNAMECMP(name, fm->fname) == 0) { fm->fmark.fnum = buf->b_fnum; XFREE_CLEAR(fm->fname); } } /// Check the position in @a fm is valid. /// /// Emit error message and return accordingly. /// /// Checks for: /// - NULL raising unknown mark error. /// - Line number <= 0 raising mark not set. /// - Line number > buffer line count, raising invalid mark. /// @param fm[in] File mark to check. /// /// @return true if the mark passes all the above checks, else false. bool mark_check(fmark_T *fm) { if (fm == NULL) { emsg(_(e_umark)); return false; } else if (fm->mark.lnum <= 0) { // In both cases it's an error but only raise when equals to 0 if (fm->mark.lnum == 0) { emsg(_(e_marknotset)); } return false; } // Only check for valid line number if the buffer is loaded. if (fm->fnum == curbuf->handle && !mark_check_line_bounds(curbuf, fm)) { return false; } return true; } /// Check if a mark line number is greater than the buffer line count, and set e_markinval. /// @note Should be done after the buffer is loaded into memory. /// @param buf Buffer where the mark is set. /// @param fm Mark to check. /// @return true if below line count else false. bool mark_check_line_bounds(buf_T *buf, fmark_T *fm) { if (buf != NULL && fm->mark.lnum > buf->b_ml.ml_line_count) { emsg(_(e_markinval)); return false; } return true; } /// Clear all marks and change list in the given buffer /// /// Used mainly when trashing the entire buffer during ":e" type commands. /// /// @param[out] buf Buffer to clear marks in. void clrallmarks(buf_T *const buf) FUNC_ATTR_NONNULL_ALL { for (size_t i = 0; i < NMARKS; i++) { clear_fmark(&buf->b_namedm[i]); } clear_fmark(&buf->b_last_cursor); buf->b_last_cursor.mark.lnum = 1; clear_fmark(&buf->b_last_insert); clear_fmark(&buf->b_last_change); buf->b_op_start.lnum = 0; // start/end op mark cleared buf->b_op_end.lnum = 0; for (int i = 0; i < buf->b_changelistlen; i++) { clear_fmark(&buf->b_changelist[i]); } buf->b_changelistlen = 0; } /* * Get name of file from a filemark. * When it's in the current buffer, return the text at the mark. * Returns an allocated string. */ char_u *fm_getname(fmark_T *fmark, int lead_len) { if (fmark->fnum == curbuf->b_fnum) { // current buffer return mark_line(&(fmark->mark), lead_len); } return (char_u *)buflist_nr2name(fmark->fnum, false, true); } /* * Return the line at mark "mp". Truncate to fit in window. * The returned string has been allocated. */ static char_u *mark_line(pos_T *mp, int lead_len) { char_u *s, *p; int len; if (mp->lnum == 0 || mp->lnum > curbuf->b_ml.ml_line_count) { return vim_strsave((char_u *)"-invalid-"); } assert(Columns >= 0); // Allow for up to 5 bytes per character. s = vim_strnsave((char_u *)skipwhite((char *)ml_get(mp->lnum)), (size_t)Columns * 5); // Truncate the line to fit it in the window len = 0; for (p = s; *p != NUL; MB_PTR_ADV(p)) { len += ptr2cells((char *)p); if (len >= Columns - lead_len) { break; } } *p = NUL; return s; } /* * print the marks */ void ex_marks(exarg_T *eap) { char_u *arg = (char_u *)eap->arg; int i; char_u *name; pos_T *posp, *startp, *endp; if (arg != NULL && *arg == NUL) { arg = NULL; } show_one_mark('\'', arg, &curwin->w_pcmark, NULL, true); for (i = 0; i < NMARKS; ++i) { show_one_mark(i + 'a', arg, &curbuf->b_namedm[i].mark, NULL, true); } for (i = 0; i < NGLOBALMARKS; ++i) { if (namedfm[i].fmark.fnum != 0) { name = fm_getname(&namedfm[i].fmark, 15); } else { name = (char_u *)namedfm[i].fname; } if (name != NULL) { show_one_mark(i >= NMARKS ? i - NMARKS + '0' : i + 'A', arg, &namedfm[i].fmark.mark, name, namedfm[i].fmark.fnum == curbuf->b_fnum); if (namedfm[i].fmark.fnum != 0) { xfree(name); } } } show_one_mark('"', arg, &curbuf->b_last_cursor.mark, NULL, true); show_one_mark('[', arg, &curbuf->b_op_start, NULL, true); show_one_mark(']', arg, &curbuf->b_op_end, NULL, true); show_one_mark('^', arg, &curbuf->b_last_insert.mark, NULL, true); show_one_mark('.', arg, &curbuf->b_last_change.mark, NULL, true); // Show the marks as where they will jump to. startp = &curbuf->b_visual.vi_start; endp = &curbuf->b_visual.vi_end; if ((lt(*startp, *endp) || endp->lnum == 0) && startp->lnum != 0) { posp = startp; } else { posp = endp; } show_one_mark('<', arg, posp, NULL, true); show_one_mark('>', arg, posp == startp ? endp : startp, NULL, true); show_one_mark(-1, arg, NULL, NULL, false); } /// @param current in current file static void show_one_mark(int c, char_u *arg, pos_T *p, char_u *name_arg, int current) { static bool did_title = false; bool mustfree = false; char_u *name = name_arg; if (c == -1) { // finish up if (did_title) { did_title = false; } else { if (arg == NULL) { msg(_("No marks set")); } else { semsg(_("E283: No marks matching \"%s\""), arg); } } } else if (!got_int && (arg == NULL || vim_strchr((char *)arg, c) != NULL) && p->lnum != 0) { // don't output anything if 'q' typed at --more-- prompt if (name == NULL && current) { name = mark_line(p, 15); mustfree = true; } if (!message_filtered(name)) { if (!did_title) { // Highlight title msg_puts_title(_("\nmark line col file/text")); did_title = true; } msg_putchar('\n'); if (!got_int) { snprintf((char *)IObuff, IOSIZE, " %c %6" PRIdLINENR " %4d ", c, p->lnum, p->col); msg_outtrans((char *)IObuff); if (name != NULL) { msg_outtrans_attr(name, current ? HL_ATTR(HLF_D) : 0); } } ui_flush(); // show one line at a time } if (mustfree) { xfree(name); } } } /* * ":delmarks[!] [marks]" */ void ex_delmarks(exarg_T *eap) { char_u *p; int from, to; int i; int lower; int digit; int n; if (*eap->arg == NUL && eap->forceit) { // clear all marks clrallmarks(curbuf); } else if (eap->forceit) { emsg(_(e_invarg)); } else if (*eap->arg == NUL) { emsg(_(e_argreq)); } else { // clear specified marks only for (p = (char_u *)eap->arg; *p != NUL; p++) { lower = ASCII_ISLOWER(*p); digit = ascii_isdigit(*p); if (lower || digit || ASCII_ISUPPER(*p)) { if (p[1] == '-') { // clear range of marks from = *p; to = p[2]; if (!(lower ? ASCII_ISLOWER(p[2]) : (digit ? ascii_isdigit(p[2]) : ASCII_ISUPPER(p[2]))) || to < from) { semsg(_(e_invarg2), p); return; } p += 2; } else { // clear one lower case mark from = to = *p; } for (i = from; i <= to; ++i) { if (lower) { curbuf->b_namedm[i - 'a'].mark.lnum = 0; } else { if (digit) { n = i - '0' + NMARKS; } else { n = i - 'A'; } namedfm[n].fmark.mark.lnum = 0; namedfm[n].fmark.fnum = 0; XFREE_CLEAR(namedfm[n].fname); } } } else { switch (*p) { case '"': CLEAR_FMARK(&curbuf->b_last_cursor); break; case '^': CLEAR_FMARK(&curbuf->b_last_insert); break; case '.': CLEAR_FMARK(&curbuf->b_last_change); break; case '[': curbuf->b_op_start.lnum = 0; break; case ']': curbuf->b_op_end.lnum = 0; break; case '<': curbuf->b_visual.vi_start.lnum = 0; break; case '>': curbuf->b_visual.vi_end.lnum = 0; break; case ' ': break; default: semsg(_(e_invarg2), p); return; } } } } } /* * print the jumplist */ void ex_jumps(exarg_T *eap) { int i; char_u *name; cleanup_jumplist(curwin, true); // Highlight title msg_puts_title(_("\n jump line col file/text")); for (i = 0; i < curwin->w_jumplistlen && !got_int; ++i) { if (curwin->w_jumplist[i].fmark.mark.lnum != 0) { name = fm_getname(&curwin->w_jumplist[i].fmark, 16); // Make sure to output the current indicator, even when on an wiped // out buffer. ":filter" may still skip it. if (name == NULL && i == curwin->w_jumplistidx) { name = vim_strsave((char_u *)"-invalid-"); } // apply :filter /pat/ or file name not available if (name == NULL || message_filtered(name)) { xfree(name); continue; } msg_putchar('\n'); if (got_int) { xfree(name); break; } snprintf((char *)IObuff, IOSIZE, "%c %2d %5" PRIdLINENR " %4d ", i == curwin->w_jumplistidx ? '>' : ' ', i > curwin->w_jumplistidx ? i - curwin->w_jumplistidx : curwin->w_jumplistidx - i, curwin->w_jumplist[i].fmark.mark.lnum, curwin->w_jumplist[i].fmark.mark.col); msg_outtrans((char *)IObuff); msg_outtrans_attr(name, curwin->w_jumplist[i].fmark.fnum == curbuf->b_fnum ? HL_ATTR(HLF_D) : 0); xfree(name); os_breakcheck(); } ui_flush(); } if (curwin->w_jumplistidx == curwin->w_jumplistlen) { msg_puts("\n>"); } } void ex_clearjumps(exarg_T *eap) { free_jumplist(curwin); curwin->w_jumplistlen = 0; curwin->w_jumplistidx = 0; } /* * print the changelist */ void ex_changes(exarg_T *eap) { int i; char_u *name; // Highlight title msg_puts_title(_("\nchange line col text")); for (i = 0; i < curbuf->b_changelistlen && !got_int; ++i) { if (curbuf->b_changelist[i].mark.lnum != 0) { msg_putchar('\n'); if (got_int) { break; } sprintf((char *)IObuff, "%c %3d %5ld %4d ", i == curwin->w_changelistidx ? '>' : ' ', i > curwin->w_changelistidx ? i - curwin->w_changelistidx : curwin->w_changelistidx - i, (long)curbuf->b_changelist[i].mark.lnum, curbuf->b_changelist[i].mark.col); msg_outtrans((char *)IObuff); name = mark_line(&curbuf->b_changelist[i].mark, 17); msg_outtrans_attr(name, HL_ATTR(HLF_D)); xfree(name); os_breakcheck(); } ui_flush(); } if (curwin->w_changelistidx == curbuf->b_changelistlen) { msg_puts("\n>"); } } #define ONE_ADJUST(add) \ { \ lp = add; \ if (*lp >= line1 && *lp <= line2) \ { \ if (amount == MAXLNUM) \ *lp = 0; \ else \ *lp += amount; \ } \ else if (amount_after && *lp > line2) \ *lp += amount_after; \ } // don't delete the line, just put at first deleted line #define ONE_ADJUST_NODEL(add) \ { \ lp = add; \ if (*lp >= line1 && *lp <= line2) \ { \ if (amount == MAXLNUM) \ *lp = line1; \ else \ *lp += amount; \ } \ else if (amount_after && *lp > line2) \ *lp += amount_after; \ } /* * Adjust marks between line1 and line2 (inclusive) to move 'amount' lines. * Must be called before changed_*(), appended_lines() or deleted_lines(). * May be called before or after changing the text. * When deleting lines line1 to line2, use an 'amount' of MAXLNUM: The marks * within this range are made invalid. * If 'amount_after' is non-zero adjust marks after line2. * Example: Delete lines 34 and 35: mark_adjust(34, 35, MAXLNUM, -2); * Example: Insert two lines below 55: mark_adjust(56, MAXLNUM, 2, 0); * or: mark_adjust(56, 55, MAXLNUM, 2); */ void mark_adjust(linenr_T line1, linenr_T line2, linenr_T amount, linenr_T amount_after, ExtmarkOp op) { mark_adjust_internal(line1, line2, amount, amount_after, true, op); } // mark_adjust_nofold() does the same as mark_adjust() but without adjusting // folds in any way. Folds must be adjusted manually by the caller. // This is only useful when folds need to be moved in a way different to // calling foldMarkAdjust() with arguments line1, line2, amount, amount_after, // for an example of why this may be necessary, see do_move(). void mark_adjust_nofold(linenr_T line1, linenr_T line2, linenr_T amount, linenr_T amount_after, ExtmarkOp op) { mark_adjust_internal(line1, line2, amount, amount_after, false, op); } static void mark_adjust_internal(linenr_T line1, linenr_T line2, linenr_T amount, linenr_T amount_after, bool adjust_folds, ExtmarkOp op) { int i; int fnum = curbuf->b_fnum; linenr_T *lp; static pos_T initpos = { 1, 0, 0 }; if (line2 < line1 && amount_after == 0L) { // nothing to do return; } if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { // named marks, lower case and upper case for (i = 0; i < NMARKS; i++) { ONE_ADJUST(&(curbuf->b_namedm[i].mark.lnum)); if (namedfm[i].fmark.fnum == fnum) { ONE_ADJUST_NODEL(&(namedfm[i].fmark.mark.lnum)); } } for (i = NMARKS; i < NGLOBALMARKS; i++) { if (namedfm[i].fmark.fnum == fnum) { ONE_ADJUST_NODEL(&(namedfm[i].fmark.mark.lnum)); } } // last Insert position ONE_ADJUST(&(curbuf->b_last_insert.mark.lnum)); // last change position ONE_ADJUST(&(curbuf->b_last_change.mark.lnum)); // last cursor position, if it was set if (!equalpos(curbuf->b_last_cursor.mark, initpos)) { ONE_ADJUST(&(curbuf->b_last_cursor.mark.lnum)); } // list of change positions for (i = 0; i < curbuf->b_changelistlen; i++) { ONE_ADJUST_NODEL(&(curbuf->b_changelist[i].mark.lnum)); } // Visual area ONE_ADJUST_NODEL(&(curbuf->b_visual.vi_start.lnum)); ONE_ADJUST_NODEL(&(curbuf->b_visual.vi_end.lnum)); // quickfix marks if (!qf_mark_adjust(NULL, line1, line2, amount, amount_after)) { curbuf->b_has_qf_entry &= ~BUF_HAS_QF_ENTRY; } // location lists bool found_one = false; FOR_ALL_TAB_WINDOWS(tab, win) { found_one |= qf_mark_adjust(win, line1, line2, amount, amount_after); } if (!found_one) { curbuf->b_has_qf_entry &= ~BUF_HAS_LL_ENTRY; } sign_mark_adjust(line1, line2, amount, amount_after); } if (op != kExtmarkNOOP) { extmark_adjust(curbuf, line1, line2, amount, amount_after, op); } // previous context mark ONE_ADJUST(&(curwin->w_pcmark.lnum)); // previous pcmark ONE_ADJUST(&(curwin->w_prev_pcmark.lnum)); // saved cursor for formatting if (saved_cursor.lnum != 0) { ONE_ADJUST_NODEL(&(saved_cursor.lnum)); } /* * Adjust items in all windows related to the current buffer. */ FOR_ALL_TAB_WINDOWS(tab, win) { if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { // Marks in the jumplist. When deleting lines, this may create // duplicate marks in the jumplist, they will be removed later. for (i = 0; i < win->w_jumplistlen; i++) { if (win->w_jumplist[i].fmark.fnum == fnum) { ONE_ADJUST_NODEL(&(win->w_jumplist[i].fmark.mark.lnum)); } } } if (win->w_buffer == curbuf) { if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { // marks in the tag stack for (i = 0; i < win->w_tagstacklen; i++) { if (win->w_tagstack[i].fmark.fnum == fnum) { ONE_ADJUST_NODEL(&(win->w_tagstack[i].fmark.mark.lnum)); } } } // the displayed Visual area if (win->w_old_cursor_lnum != 0) { ONE_ADJUST_NODEL(&(win->w_old_cursor_lnum)); ONE_ADJUST_NODEL(&(win->w_old_visual_lnum)); } // topline and cursor position for windows with the same buffer // other than the current window if (win != curwin) { if (win->w_topline >= line1 && win->w_topline <= line2) { if (amount == MAXLNUM) { // topline is deleted if (line1 <= 1) { win->w_topline = 1; } else { win->w_topline = line1 - 1; } } else { // keep topline on the same line win->w_topline += amount; } win->w_topfill = 0; } else if (amount_after && win->w_topline > line2) { win->w_topline += amount_after; win->w_topfill = 0; } if (win->w_cursor.lnum >= line1 && win->w_cursor.lnum <= line2) { if (amount == MAXLNUM) { // line with cursor is deleted if (line1 <= 1) { win->w_cursor.lnum = 1; } else { win->w_cursor.lnum = line1 - 1; } win->w_cursor.col = 0; } else { // keep cursor on the same line win->w_cursor.lnum += amount; } } else if (amount_after && win->w_cursor.lnum > line2) { win->w_cursor.lnum += amount_after; } } if (adjust_folds) { foldMarkAdjust(win, line1, line2, amount, amount_after); } } } // adjust diffs diff_mark_adjust(line1, line2, amount, amount_after); } // This code is used often, needs to be fast. #define COL_ADJUST(pp) \ { \ posp = pp; \ if (posp->lnum == lnum && posp->col >= mincol) \ { \ posp->lnum += lnum_amount; \ assert(col_amount > INT_MIN && col_amount <= INT_MAX); \ if (col_amount < 0 && posp->col <= (colnr_T) - col_amount) { \ posp->col = 0; \ } else if (posp->col < spaces_removed) { \ posp->col = (int)col_amount + spaces_removed; \ } else { \ posp->col += (colnr_T)col_amount; \ } \ } \ } // Adjust marks in line "lnum" at column "mincol" and further: add // "lnum_amount" to the line number and add "col_amount" to the column // position. // "spaces_removed" is the number of spaces that were removed, matters when the // cursor is inside them. void mark_col_adjust(linenr_T lnum, colnr_T mincol, linenr_T lnum_amount, long col_amount, int spaces_removed) { int i; int fnum = curbuf->b_fnum; pos_T *posp; if ((col_amount == 0L && lnum_amount == 0L) || (cmdmod.cmod_flags & CMOD_LOCKMARKS)) { return; // nothing to do } // named marks, lower case and upper case for (i = 0; i < NMARKS; i++) { COL_ADJUST(&(curbuf->b_namedm[i].mark)); if (namedfm[i].fmark.fnum == fnum) { COL_ADJUST(&(namedfm[i].fmark.mark)); } } for (i = NMARKS; i < NGLOBALMARKS; i++) { if (namedfm[i].fmark.fnum == fnum) { COL_ADJUST(&(namedfm[i].fmark.mark)); } } // last Insert position COL_ADJUST(&(curbuf->b_last_insert.mark)); // last change position COL_ADJUST(&(curbuf->b_last_change.mark)); // list of change positions for (i = 0; i < curbuf->b_changelistlen; i++) { COL_ADJUST(&(curbuf->b_changelist[i].mark)); } // Visual area COL_ADJUST(&(curbuf->b_visual.vi_start)); COL_ADJUST(&(curbuf->b_visual.vi_end)); // previous context mark COL_ADJUST(&(curwin->w_pcmark)); // previous pcmark COL_ADJUST(&(curwin->w_prev_pcmark)); // saved cursor for formatting COL_ADJUST(&saved_cursor); /* * Adjust items in all windows related to the current buffer. */ FOR_ALL_WINDOWS_IN_TAB(win, curtab) { // marks in the jumplist for (i = 0; i < win->w_jumplistlen; ++i) { if (win->w_jumplist[i].fmark.fnum == fnum) { COL_ADJUST(&(win->w_jumplist[i].fmark.mark)); } } if (win->w_buffer == curbuf) { // marks in the tag stack for (i = 0; i < win->w_tagstacklen; i++) { if (win->w_tagstack[i].fmark.fnum == fnum) { COL_ADJUST(&(win->w_tagstack[i].fmark.mark)); } } // cursor position for other windows with the same buffer if (win != curwin) { COL_ADJUST(&win->w_cursor); } } } } // When deleting lines, this may create duplicate marks in the // jumplist. They will be removed here for the specified window. // When "checktail" is true, removes tail jump if it matches current position. void cleanup_jumplist(win_T *wp, bool checktail) { int i; // Load all the files from the jump list. This is // needed to properly clean up duplicate entries, but will take some // time. for (i = 0; i < wp->w_jumplistlen; i++) { if ((wp->w_jumplist[i].fmark.fnum == 0) && (wp->w_jumplist[i].fmark.mark.lnum != 0)) { fname2fnum(&wp->w_jumplist[i]); } } int to = 0; for (int from = 0; from < wp->w_jumplistlen; from++) { if (wp->w_jumplistidx == from) { wp->w_jumplistidx = to; } for (i = from + 1; i < wp->w_jumplistlen; i++) { if (wp->w_jumplist[i].fmark.fnum == wp->w_jumplist[from].fmark.fnum && wp->w_jumplist[from].fmark.fnum != 0 && wp->w_jumplist[i].fmark.mark.lnum == wp->w_jumplist[from].fmark.mark.lnum) { break; } } bool mustfree; if (i >= wp->w_jumplistlen) { // not duplicate mustfree = false; } else if (i > from + 1) { // non-adjacent duplicate // jumpoptions=stack: remove duplicates only when adjacent. mustfree = !(jop_flags & JOP_STACK); } else { // adjacent duplicate mustfree = true; } if (mustfree) { xfree(wp->w_jumplist[from].fname); } else { if (to != from) { // Not using wp->w_jumplist[to++] = wp->w_jumplist[from] because // this way valgrind complains about overlapping source and destination // in memcpy() call. (clang-3.6.0, debug build with -DEXITFREE). wp->w_jumplist[to] = wp->w_jumplist[from]; } to++; } } if (wp->w_jumplistidx == wp->w_jumplistlen) { wp->w_jumplistidx = to; } wp->w_jumplistlen = to; // When pointer is below last jump, remove the jump if it matches the current // line. This avoids useless/phantom jumps. #9805 if (checktail && wp->w_jumplistlen && wp->w_jumplistidx == wp->w_jumplistlen) { const xfmark_T *fm_last = &wp->w_jumplist[wp->w_jumplistlen - 1]; if (fm_last->fmark.fnum == curbuf->b_fnum && fm_last->fmark.mark.lnum == wp->w_cursor.lnum) { xfree(fm_last->fname); wp->w_jumplistlen--; wp->w_jumplistidx--; } } } /* * Copy the jumplist from window "from" to window "to". */ void copy_jumplist(win_T *from, win_T *to) { int i; for (i = 0; i < from->w_jumplistlen; ++i) { to->w_jumplist[i] = from->w_jumplist[i]; if (from->w_jumplist[i].fname != NULL) { to->w_jumplist[i].fname = xstrdup(from->w_jumplist[i].fname); } } to->w_jumplistlen = from->w_jumplistlen; to->w_jumplistidx = from->w_jumplistidx; } /// Iterate over jumplist items /// /// @warning No jumplist-editing functions must be called while iteration is in /// progress. /// /// @param[in] iter Iterator. Pass NULL to start iteration. /// @param[in] win Window for which jump list is processed. /// @param[out] fm Item definition. /// /// @return Pointer that needs to be passed to next `mark_jumplist_iter` call or /// NULL if iteration is over. const void *mark_jumplist_iter(const void *const iter, const win_T *const win, xfmark_T *const fm) FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT { if (iter == NULL && win->w_jumplistlen == 0) { *fm = (xfmark_T)INIT_XFMARK; return NULL; } const xfmark_T *const iter_mark = (iter == NULL ? &(win->w_jumplist[0]) : (const xfmark_T *const)iter); *fm = *iter_mark; if (iter_mark == &(win->w_jumplist[win->w_jumplistlen - 1])) { return NULL; } else { return iter_mark + 1; } } /// Iterate over global marks /// /// @warning No mark-editing functions must be called while iteration is in /// progress. /// /// @param[in] iter Iterator. Pass NULL to start iteration. /// @param[out] name Mark name. /// @param[out] fm Mark definition. /// /// @return Pointer that needs to be passed to next `mark_global_iter` call or /// NULL if iteration is over. const void *mark_global_iter(const void *const iter, char *const name, xfmark_T *const fm) FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT { *name = NUL; const xfmark_T *iter_mark = (iter == NULL ? &(namedfm[0]) : (const xfmark_T *const)iter); while ((size_t)(iter_mark - &(namedfm[0])) < ARRAY_SIZE(namedfm) && !iter_mark->fmark.mark.lnum) { iter_mark++; } if ((size_t)(iter_mark - &(namedfm[0])) == ARRAY_SIZE(namedfm) || !iter_mark->fmark.mark.lnum) { return NULL; } size_t iter_off = (size_t)(iter_mark - &(namedfm[0])); *name = (char)(iter_off < NMARKS ? 'A' + (char)iter_off : '0' + (char)(iter_off - NMARKS)); *fm = *iter_mark; while ((size_t)(++iter_mark - &(namedfm[0])) < ARRAY_SIZE(namedfm)) { if (iter_mark->fmark.mark.lnum) { return (const void *)iter_mark; } } return NULL; } /// Get next mark and its name /// /// @param[in] buf Buffer for which next mark is taken. /// @param[in,out] mark_name Pointer to the current mark name. Next mark name /// will be saved at this address as well. /// /// Current mark name must either be NUL, '"', '^', /// '.' or 'a' .. 'z'. If it is neither of these /// behaviour is undefined. /// /// @return Pointer to the next mark or NULL. static inline const fmark_T *next_buffer_mark(const buf_T *const buf, char *const mark_name) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { switch (*mark_name) { case NUL: *mark_name = '"'; return &(buf->b_last_cursor); case '"': *mark_name = '^'; return &(buf->b_last_insert); case '^': *mark_name = '.'; return &(buf->b_last_change); case '.': *mark_name = 'a'; return &(buf->b_namedm[0]); case 'z': return NULL; default: (*mark_name)++; return &(buf->b_namedm[*mark_name - 'a']); } } /// Iterate over buffer marks /// /// @warning No mark-editing functions must be called while iteration is in /// progress. /// /// @param[in] iter Iterator. Pass NULL to start iteration. /// @param[in] buf Buffer. /// @param[out] name Mark name. /// @param[out] fm Mark definition. /// /// @return Pointer that needs to be passed to next `mark_buffer_iter` call or /// NULL if iteration is over. const void *mark_buffer_iter(const void *const iter, const buf_T *const buf, char *const name, fmark_T *const fm) FUNC_ATTR_NONNULL_ARG(2, 3, 4) FUNC_ATTR_WARN_UNUSED_RESULT { *name = NUL; char mark_name = (char)(iter == NULL ? NUL : (iter == &(buf->b_last_cursor) ? '"' : (iter == &(buf->b_last_insert) ? '^' : (iter == &(buf->b_last_change) ? '.' : 'a' + (char)((const fmark_T *)iter - &(buf->b_namedm[0])))))); const fmark_T *iter_mark = next_buffer_mark(buf, &mark_name); while (iter_mark != NULL && iter_mark->mark.lnum == 0) { iter_mark = next_buffer_mark(buf, &mark_name); } if (iter_mark == NULL) { return NULL; } size_t iter_off = (size_t)(iter_mark - &(buf->b_namedm[0])); if (mark_name) { *name = mark_name; } else { *name = (char)('a' + (char)iter_off); } *fm = *iter_mark; return (const void *)iter_mark; } /// Set global mark /// /// @param[in] name Mark name. /// @param[in] fm Mark to be set. /// @param[in] update If true then only set global mark if it was created /// later then existing one. /// /// @return true on success, false on failure. bool mark_set_global(const char name, const xfmark_T fm, const bool update) { const int idx = mark_global_index(name); if (idx == -1) { return false; } xfmark_T *const fm_tgt = &(namedfm[idx]); if (update && fm.fmark.timestamp <= fm_tgt->fmark.timestamp) { return false; } if (fm_tgt->fmark.mark.lnum != 0) { free_xfmark(*fm_tgt); } *fm_tgt = fm; return true; } /// Set local mark /// /// @param[in] name Mark name. /// @param[in] buf Pointer to the buffer to set mark in. /// @param[in] fm Mark to be set. /// @param[in] update If true then only set global mark if it was created /// later then existing one. /// /// @return true on success, false on failure. bool mark_set_local(const char name, buf_T *const buf, const fmark_T fm, const bool update) FUNC_ATTR_NONNULL_ALL { fmark_T *fm_tgt = NULL; if (ASCII_ISLOWER(name)) { fm_tgt = &(buf->b_namedm[name - 'a']); } else if (name == '"') { fm_tgt = &(buf->b_last_cursor); } else if (name == '^') { fm_tgt = &(buf->b_last_insert); } else if (name == '.') { fm_tgt = &(buf->b_last_change); } else { return false; } if (update && fm.timestamp <= fm_tgt->timestamp) { return false; } if (fm_tgt->mark.lnum != 0) { free_fmark(*fm_tgt); } *fm_tgt = fm; return true; } /* * Free items in the jumplist of window "wp". */ void free_jumplist(win_T *wp) { int i; for (i = 0; i < wp->w_jumplistlen; ++i) { free_xfmark(wp->w_jumplist[i]); } wp->w_jumplistlen = 0; } void set_last_cursor(win_T *win) { if (win->w_buffer != NULL) { RESET_FMARK(&win->w_buffer->b_last_cursor, win->w_cursor, 0, ((fmarkv_T)INIT_FMARKV)); } } #if defined(EXITFREE) void free_all_marks(void) { int i; for (i = 0; i < NGLOBALMARKS; i++) { if (namedfm[i].fmark.mark.lnum != 0) { free_xfmark(namedfm[i]); } } CLEAR_FIELD(namedfm); } #endif /// Adjust position to point to the first byte of a multi-byte character /// /// If it points to a tail byte it is move backwards to the head byte. /// /// @param[in] buf Buffer to adjust position in. /// @param[out] lp Position to adjust. void mark_mb_adjustpos(buf_T *buf, pos_T *lp) FUNC_ATTR_NONNULL_ALL { if (lp->col > 0 || lp->coladd > 1) { const char_u *const p = ml_get_buf(buf, lp->lnum, false); if (*p == NUL || (int)STRLEN(p) < lp->col) { lp->col = 0; } else { lp->col -= utf_head_off(p, p + lp->col); } // Reset "coladd" when the cursor would be on the right half of a // double-wide character. if (lp->coladd == 1 && p[lp->col] != TAB && vim_isprintc(utf_ptr2char((char *)p + lp->col)) && ptr2cells((char *)p + lp->col) > 1) { lp->coladd = 0; } } } // Add information about mark 'mname' to list 'l' static int add_mark(list_T *l, const char *mname, const pos_T *pos, int bufnr, const char *fname) FUNC_ATTR_NONNULL_ARG(1, 2, 3) { if (pos->lnum <= 0) { return OK; } dict_T *d = tv_dict_alloc(); tv_list_append_dict(l, d); list_T *lpos = tv_list_alloc(kListLenMayKnow); tv_list_append_number(lpos, bufnr); tv_list_append_number(lpos, pos->lnum); tv_list_append_number(lpos, pos->col + 1); tv_list_append_number(lpos, pos->coladd); if (tv_dict_add_str(d, S_LEN("mark"), mname) == FAIL || tv_dict_add_list(d, S_LEN("pos"), lpos) == FAIL || (fname != NULL && tv_dict_add_str(d, S_LEN("file"), fname) == FAIL)) { return FAIL; } return OK; } /// Get information about marks local to a buffer. /// /// @param[in] buf Buffer to get the marks from /// @param[out] l List to store marks void get_buf_local_marks(const buf_T *buf, list_T *l) FUNC_ATTR_NONNULL_ALL { char mname[3] = "' "; // Marks 'a' to 'z' for (int i = 0; i < NMARKS; i++) { mname[1] = (char)('a' + i); add_mark(l, mname, &buf->b_namedm[i].mark, buf->b_fnum, NULL); } // Mark '' is a window local mark and not a buffer local mark add_mark(l, "''", &curwin->w_pcmark, curbuf->b_fnum, NULL); add_mark(l, "'\"", &buf->b_last_cursor.mark, buf->b_fnum, NULL); add_mark(l, "'[", &buf->b_op_start, buf->b_fnum, NULL); add_mark(l, "']", &buf->b_op_end, buf->b_fnum, NULL); add_mark(l, "'^", &buf->b_last_insert.mark, buf->b_fnum, NULL); add_mark(l, "'.", &buf->b_last_change.mark, buf->b_fnum, NULL); add_mark(l, "'<", &buf->b_visual.vi_start, buf->b_fnum, NULL); add_mark(l, "'>", &buf->b_visual.vi_end, buf->b_fnum, NULL); } /// Get a global mark /// /// @note Mark might not have it's fnum resolved. /// @param[in] Name of named mark /// @param[out] Global/file mark xfmark_T get_raw_global_mark(char name) { return namedfm[mark_global_index(name)]; } /// Get information about global marks ('A' to 'Z' and '0' to '9') /// /// @param[out] l List to store global marks void get_global_marks(list_T *l) FUNC_ATTR_NONNULL_ALL { char mname[3] = "' "; char *name; // Marks 'A' to 'Z' and '0' to '9' for (int i = 0; i < NMARKS + EXTRA_MARKS; i++) { if (namedfm[i].fmark.fnum != 0) { name = buflist_nr2name(namedfm[i].fmark.fnum, true, true); } else { name = namedfm[i].fname; } if (name != NULL) { mname[1] = i >= NMARKS ? (char)(i - NMARKS + '0') : (char)(i + 'A'); add_mark(l, mname, &namedfm[i].fmark.mark, namedfm[i].fmark.fnum, name); if (namedfm[i].fmark.fnum != 0) { xfree(name); } } } }