Files
neovim/src/nvim/mark.c
dundargoc 66360675cf build: allow IWYU to fix includes for all .c files
Allow Include What You Use to remove unnecessary includes and only
include what is necessary. This helps with reducing compilation times
and makes it easier to visualise which dependencies are actually
required.

Work on https://github.com/neovim/neovim/issues/549, but doesn't close
it since this only works fully for .c files and not headers.
2022-11-15 10:30:03 +01:00

1813 lines
53 KiB
C

// 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 <assert.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include "nvim/ascii.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
#include "nvim/edit.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/extmark.h"
#include "nvim/extmark_defs.h"
#include "nvim/fold.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/highlight_defs.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_defs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
#include "nvim/quickfix.h"
#include "nvim/sign.h"
#include "nvim/strings.h"
#include "nvim/textobject.h"
#include "nvim/types.h"
#include "nvim/undo_defs.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)
{
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.
#ifdef BACKSLASH_IN_FILENAME
if (fm->fname[0] == '~' && (fm->fname[1] == '/' || fm->fname[1] == '\\')) {
#else
if (fm->fname[0] == '~' && (fm->fname[1] == '/')) {
#endif
expand_env("~/", NameBuff, MAXPATHL);
int 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((char_u *)IObuff, IOSIZE);
char *p = path_shorten_fname(NameBuff, IObuff);
// buflist_new() will call fmarks_check_names()
(void)buflist_new(NameBuff, 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], (char *)name, buf);
}
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
for (i = 0; i < wp->w_jumplistlen; i++) {
fmarks_check_one(&wp->w_jumplist[i], (char *)name, buf);
}
}
}
static void fmarks_check_one(xfmark_T *fm, char *name, buf_T *buf)
{
if (fm->fmark.fnum == 0
&& fm->fname != NULL
&& path_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 (char_u *)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 *mark_line(pos_T *mp, int lead_len)
{
char *s, *p;
int len;
if (mp->lnum == 0 || mp->lnum > curbuf->b_ml.ml_line_count) {
return xstrdup("-invalid-");
}
assert(Columns >= 0);
// Allow for up to 5 bytes per character.
s = xstrnsave(skipwhite(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(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 = (char_u *)mark_line(p, 15);
mustfree = true;
}
if (!message_filtered((char *)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((char *)name, current ? HL_ATTR(HLF_D) : 0);
}
}
}
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 *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 = (char *)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 = xstrdup("-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();
}
}
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;
}
snprintf(IObuff, IOSIZE, "%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 = (char_u *)mark_line(&curbuf->b_changelist[i].mark, 17);
msg_outtrans_attr((char *)name, HL_ATTR(HLF_D));
xfree(name);
os_breakcheck();
}
}
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;
}
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 *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(p + lp->col))
&& ptr2cells(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);
}
}
}
}