aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/api/buffer.c37
-rw-r--r--src/nvim/api/private/helpers.c2
-rw-r--r--src/nvim/api/vim.c10
-rw-r--r--src/nvim/buffer.c37
-rw-r--r--src/nvim/buffer_defs.h4
-rw-r--r--src/nvim/change.c8
-rw-r--r--src/nvim/edit.c3
-rw-r--r--src/nvim/eval.c9
-rw-r--r--src/nvim/eval/funcs.c2
-rw-r--r--src/nvim/ex_cmds.c2
-rw-r--r--src/nvim/ex_docmd.c27
-rw-r--r--src/nvim/ex_session.c2
-rw-r--r--src/nvim/mark.c570
-rw-r--r--src/nvim/mark.h17
-rw-r--r--src/nvim/mark_defs.h42
-rw-r--r--src/nvim/normal.c113
-rw-r--r--src/nvim/option_defs.h3
-rw-r--r--src/nvim/regexp_bt.c8
-rw-r--r--src/nvim/regexp_nfa.c9
-rw-r--r--src/nvim/shada.c3
-rw-r--r--src/nvim/tag.c2
21 files changed, 582 insertions, 328 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 1504004c6c..806b649ce6 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -1156,17 +1156,17 @@ Boolean nvim_buf_del_mark(Buffer buffer, String name, Error *err)
return res;
}
- pos_T *pos = getmark_buf(buf, *name.data, false);
+ fmark_T *fm = mark_get(buf, curwin, NULL, kMarkAllNoResolve, *name.data);
- // pos point to NULL when there's no mark with name
- if (pos == NULL) {
+ // fm is NULL when there's no mark with the given name
+ if (fm == NULL) {
api_set_error(err, kErrorTypeValidation, "Invalid mark name: '%c'",
*name.data);
return res;
}
- // pos->lnum is 0 when the mark is not valid in the buffer, or is not set.
- if (pos->lnum != 0) {
+ // mark.lnum is 0 when the mark is not valid in the buffer, or is not set.
+ if (fm->mark.lnum != 0 && fm->fnum == buf->handle) {
// since the mark belongs to the buffer delete it.
res = set_mark(buf, name, 0, 0, err);
}
@@ -1239,26 +1239,25 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err)
return rv;
}
- pos_T *posp;
+ fmark_T *fm;
+ pos_T pos;
char mark = *name.data;
- try_start();
- bufref_T save_buf;
- switch_buffer(&save_buf, buf);
- posp = getmark(mark, false);
- restore_buffer(&save_buf);
-
- if (try_end(err)) {
- return rv;
- }
-
- if (posp == NULL) {
+ fm = mark_get(buf, curwin, NULL, kMarkAllNoResolve, mark);
+ if (fm == NULL) {
api_set_error(err, kErrorTypeValidation, "Invalid mark name");
return rv;
}
+ // (0, 0) uppercase/file mark set in another buffer.
+ if (fm->fnum != buf->handle) {
+ pos.lnum = 0;
+ pos.col = 0;
+ } else {
+ pos = fm->mark;
+ }
- ADD(rv, INTEGER_OBJ(posp->lnum));
- ADD(rv, INTEGER_OBJ(posp->col));
+ ADD(rv, INTEGER_OBJ(pos.lnum));
+ ADD(rv, INTEGER_OBJ(pos.col));
return rv;
}
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 436bcd5212..fad75d55be 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -916,7 +916,7 @@ bool set_mark(buf_T *buf, String name, Integer line, Integer col, Error *err)
}
assert(INT32_MIN <= line && line <= INT32_MAX);
pos_T pos = { (linenr_T)line, (int)col, (int)col };
- res = setmark_pos(*name.data, &pos, buf->handle);
+ res = setmark_pos(*name.data, &pos, buf->handle, NULL);
if (!res) {
if (deleting) {
api_set_error(err, kErrorTypeException,
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index f91b74cd31..56516b2ac7 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -2027,20 +2027,20 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err)
return rv;
}
- xfmark_T mark = get_global_mark(*name.data);
- pos_T pos = mark.fmark.mark;
+ xfmark_T *mark = mark_get_global(false, *name.data); // false avoids loading the mark buffer
+ pos_T pos = mark->fmark.mark;
bool allocated = false;
int bufnr;
char *filename;
// Marks are from an open buffer it fnum is non zero
- if (mark.fmark.fnum != 0) {
- bufnr = mark.fmark.fnum;
+ if (mark->fmark.fnum != 0) {
+ bufnr = mark->fmark.fnum;
filename = (char *)buflist_nr2name(bufnr, true, true);
allocated = true;
// Marks comes from shada
} else {
- filename = mark.fname;
+ filename = mark->fname;
bufnr = 0;
}
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index baecda8e3c..a2ecb69ac0 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -55,6 +55,7 @@
#include "nvim/main.h"
#include "nvim/mapping.h"
#include "nvim/mark.h"
+#include "nvim/mark_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
@@ -1789,7 +1790,8 @@ buf_T *buflist_new(char_u *ffname_arg, char_u *sfname_arg, linenr_T lnum, int fl
buf_copy_options(buf, BCO_ALWAYS);
}
- buf->b_wininfo->wi_fpos.lnum = lnum;
+ buf->b_wininfo->wi_mark = (fmark_T)INIT_FMARK;
+ buf->b_wininfo->wi_mark.mark.lnum = lnum;
buf->b_wininfo->wi_win = curwin;
hash_init(&buf->b_s.b_keywtab);
@@ -1937,7 +1939,7 @@ int buflist_getfile(int n, linenr_T lnum, int options, int forceit)
{
buf_T *buf;
win_T *wp = NULL;
- pos_T *fpos;
+ fmark_T *fm = NULL;
colnr_T col;
buf = buflist_findnr(n);
@@ -1963,11 +1965,13 @@ int buflist_getfile(int n, linenr_T lnum, int options, int forceit)
return FAIL;
}
+ bool restore_view = false;
// altfpos may be changed by getfile(), get it now
if (lnum == 0) {
- fpos = buflist_findfpos(buf);
- lnum = fpos->lnum;
- col = fpos->col;
+ fm = buflist_findfmark(buf);
+ lnum = fm->mark.lnum;
+ col = fm->mark.col;
+ restore_view = true;
} else {
col = 0;
}
@@ -2011,6 +2015,9 @@ int buflist_getfile(int n, linenr_T lnum, int options, int forceit)
curwin->w_cursor.coladd = 0;
curwin->w_set_curswant = true;
}
+ if (jop_flags & JOP_VIEW && restore_view) {
+ mark_view_restore(fm);
+ }
return OK;
}
RedrawingDisabled--;
@@ -2022,7 +2029,7 @@ void buflist_getfpos(void)
{
pos_T *fpos;
- fpos = buflist_findfpos(curbuf);
+ fpos = &buflist_findfmark(curbuf)->mark;
curwin->w_cursor.lnum = fpos->lnum;
check_cursor_lnum();
@@ -2462,8 +2469,11 @@ void buflist_setfpos(buf_T *const buf, win_T *const win, linenr_T lnum, colnr_T
}
}
if (lnum != 0) {
- wip->wi_fpos.lnum = lnum;
- wip->wi_fpos.col = col;
+ wip->wi_mark.mark.lnum = lnum;
+ wip->wi_mark.mark.col = col;
+ if (win != NULL) {
+ wip->wi_mark.view = mark_view_make(win->w_topline, wip->wi_mark.mark);
+ }
}
if (copy_options && win != NULL) {
// Save the window-specific option values.
@@ -2581,24 +2591,23 @@ void get_winopts(buf_T *buf)
didset_window_options(curwin);
}
-/// Find the position (lnum and col) for the buffer 'buf' for the current
-/// window.
+/// Find the mark for the buffer 'buf' for the current window.
///
/// @return a pointer to no_position if no position is found.
-pos_T *buflist_findfpos(buf_T *buf)
+fmark_T *buflist_findfmark(buf_T *buf)
FUNC_ATTR_PURE
{
- static pos_T no_position = { 1, 0, 0 };
+ static fmark_T no_position = { { 1, 0, 0 }, 0, 0, { 0 }, NULL };
wininfo_T *const wip = find_wininfo(buf, false, false);
- return (wip == NULL) ? &no_position : &(wip->wi_fpos);
+ return (wip == NULL) ? &no_position : &(wip->wi_mark);
}
/// Find the lnum for the buffer 'buf' for the current window.
linenr_T buflist_findlnum(buf_T *buf)
FUNC_ATTR_PURE
{
- return buflist_findfpos(buf)->lnum;
+ return buflist_findfmark(buf)->mark.lnum;
}
/// List all known file names (for :files and :buffers command).
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 5d1135f91c..a32d2b10a9 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -283,8 +283,8 @@ typedef struct {
struct wininfo_S {
wininfo_T *wi_next; // next entry or NULL for last entry
wininfo_T *wi_prev; // previous entry or NULL for first entry
- win_T *wi_win; // pointer to window that did set wi_fpos
- pos_T wi_fpos; // last cursor position in the file
+ win_T *wi_win; // pointer to window that did set wi_mark
+ fmark_T wi_mark; // last cursor mark in the file
bool wi_optset; // true when wi_opt has useful values
winopt_T wi_opt; // local window options
bool wi_fold_manual; // copy of w_fold_manual
diff --git a/src/nvim/change.c b/src/nvim/change.c
index a383fe6bb3..f1273c802e 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -149,7 +149,13 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T
// set the '. mark
if ((cmdmod.cmod_flags & CMOD_KEEPJUMPS) == 0) {
- RESET_FMARK(&curbuf->b_last_change, ((pos_T) { lnum, col, 0 }), 0);
+ fmarkv_T view = INIT_FMARKV;
+ // Set the markview only if lnum is visible, as changes might be done
+ // outside of the current window view.
+ if (lnum >= curwin->w_topline && lnum <= curwin->w_botline) {
+ view = mark_view_make(curwin->w_topline, curwin->w_cursor);
+ }
+ RESET_FMARK(&curbuf->b_last_change, ((pos_T) { lnum, col, 0 }), curbuf->handle, view);
// Create a new entry if a new undo-able change was started or we
// don't have an entry yet.
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 1223d98fbf..cba2f812e0 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -7928,7 +7928,8 @@ static bool ins_esc(long *count, int cmdchar, bool nomove)
// Remember the last Insert position in the '^ mark.
if ((cmdmod.cmod_flags & CMOD_KEEPJUMPS) == 0) {
- RESET_FMARK(&curbuf->b_last_insert, curwin->w_cursor, curbuf->b_fnum);
+ fmarkv_T view = mark_view_make(curwin->w_topline, curwin->w_cursor);
+ RESET_FMARK(&curbuf->b_last_insert, curwin->w_cursor, curbuf->b_fnum, view);
}
/*
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index bb2404750b..2ce48479b7 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -7754,11 +7754,14 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
}
} else if (name[0] == '\'') {
// mark
- const pos_T *const pp = getmark_buf_fnum(curbuf, (uint8_t)name[1], false, ret_fnum);
- if (pp == NULL || pp == (pos_T *)-1 || pp->lnum <= 0) {
+ int mname = (uint8_t)name[1];
+ const fmark_T *const fm = mark_get(curbuf, curwin, NULL, kMarkAll, mname);
+ if (fm == NULL || fm->mark.lnum <= 0) {
return NULL;
}
- pos = *pp;
+ pos = fm->mark;
+ // Vimscript behavior, only provide fnum if mark is global.
+ *ret_fnum = ASCII_ISUPPER(mname) || ascii_isdigit(mname) ? fm->fnum: *ret_fnum;
}
if (pos.lnum != 0) {
if (charcol) {
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index f50b355045..96c6a4704c 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -8521,7 +8521,7 @@ static void set_position(typval_T *argvars, typval_T *rettv, bool charpos)
rettv->vval.v_number = 0;
} else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) {
// set mark
- if (setmark_pos((uint8_t)name[1], &pos, fnum) == OK) {
+ if (setmark_pos((uint8_t)name[1], &pos, fnum, NULL) == OK) {
rettv->vval.v_number = 0;
}
} else {
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 8016c69bcb..45d08d40bc 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -2480,7 +2480,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
// May jump to last used line number for a loaded buffer or when asked
// for explicitly
if ((oldbuf && newlnum == ECMD_LASTL) || newlnum == ECMD_LAST) {
- pos = buflist_findfpos(buf);
+ pos = &buflist_findfmark(buf)->mark;
newlnum = pos->lnum;
solcol = pos->col;
}
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index fee98a18dc..4c9c1665fd 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -2820,16 +2820,16 @@ int parse_cmd_address(exarg_T *eap, char **errormsg, bool silent)
eap->cmd++;
if (!eap->skip) {
- pos_T *fp = getmark('<', false);
- if (check_mark(fp) == FAIL) {
+ fmark_T *fm = mark_get_visual(curbuf, '<');
+ if (!mark_check(fm)) {
goto theend;
}
- eap->line1 = fp->lnum;
- fp = getmark('>', false);
- if (check_mark(fp) == FAIL) {
+ eap->line1 = fm->mark.lnum;
+ fm = mark_get_visual(curbuf, '>');
+ if (!mark_check(fm)) {
goto theend;
}
- eap->line2 = fp->lnum;
+ eap->line2 = fm->mark.lnum;
eap->addr_count++;
}
}
@@ -4230,7 +4230,6 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
linenr_T n;
char *cmd;
pos_T pos;
- pos_T *fp;
linenr_T lnum;
buf_T *buf;
@@ -4340,17 +4339,19 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
} else {
// Only accept a mark in another file when it is
// used by itself: ":'M".
- fp = getmark(*cmd, to_other_file && cmd[1] == NUL);
- ++cmd;
- if (fp == (pos_T *)-1) {
- // Jumped to another file.
+ MarkGet flag = to_other_file && cmd[1] == NUL ? kMarkAll : kMarkBufLocal;
+ fmark_T *fm = mark_get(curbuf, curwin, NULL, flag, *cmd);
+ MarkMoveRes move_res = mark_move_to(fm, kMarkBeginLine);
+ cmd++;
+ // Switched buffer
+ if (move_res & kMarkSwitchedBuf) {
lnum = curwin->w_cursor.lnum;
} else {
- if (check_mark(fp) == FAIL) {
+ if (fm == NULL) {
cmd = NULL;
goto error;
}
- lnum = fp->lnum;
+ lnum = fm->mark.lnum;
}
}
break;
diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c
index 08c232ea69..a02232b402 100644
--- a/src/nvim/ex_session.c
+++ b/src/nvim/ex_session.c
@@ -621,7 +621,7 @@ static int makeopens(FILE *fd, char_u *dirnow)
if (fprintf(fd, "badd +%" PRId64 " ",
buf->b_wininfo == NULL
? (int64_t)1L
- : (int64_t)buf->b_wininfo->wi_fpos.lnum) < 0
+ : (int64_t)buf->b_wininfo->wi_mark.mark.lnum) < 0
|| ses_fname(fd, buf, &ssop_flags, true) == FAIL) {
return FAIL;
}
diff --git a/src/nvim/mark.c b/src/nvim/mark.c
index a0fed19a98..6ca1dde270 100644
--- a/src/nvim/mark.c
+++ b/src/nvim/mark.c
@@ -13,7 +13,9 @@
#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"
@@ -24,6 +26,7 @@
#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"
@@ -61,7 +64,8 @@ static xfmark_T namedfm[NGLOBALMARKS];
*/
int setmark(int c)
{
- return setmark_pos(c, &curwin->w_cursor, curbuf->b_fnum);
+ 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
@@ -90,9 +94,10 @@ void clear_fmark(fmark_T *fm)
* 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)
+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) {
@@ -117,7 +122,7 @@ int setmark_pos(int c, pos_T *pos, int fnum)
}
if (c == '"') {
- RESET_FMARK(&buf->b_last_cursor, *pos, buf->b_fnum);
+ RESET_FMARK(&buf->b_last_cursor, *pos, buf->b_fnum, view);
return OK;
}
@@ -147,7 +152,7 @@ int setmark_pos(int c, pos_T *pos, int fnum)
if (ASCII_ISLOWER(c)) {
i = c - 'a';
- RESET_FMARK(buf->b_namedm + i, *pos, fnum);
+ RESET_FMARK(buf->b_namedm + i, *pos, fnum, view);
return OK;
}
if (ASCII_ISUPPER(c) || ascii_isdigit(c)) {
@@ -156,7 +161,7 @@ int setmark_pos(int c, pos_T *pos, int fnum)
} else {
i = c - 'A';
}
- RESET_XFMARK(namedfm + i, *pos, fnum, NULL);
+ RESET_XFMARK(namedfm + i, *pos, fnum, view, NULL);
return OK;
}
return FAIL;
@@ -202,7 +207,8 @@ void setpcmark(void)
curwin->w_jumplistidx = curwin->w_jumplistlen;
fm = &curwin->w_jumplist[curwin->w_jumplistlen - 1];
- SET_XFMARK(fm, curwin->w_pcmark, curbuf->b_fnum, NULL);
+ fmarkv_T view = mark_view_make(curwin->w_topline, curwin->w_pcmark);
+ SET_XFMARK(fm, curwin->w_pcmark, curbuf->b_fnum, view, NULL);
}
/*
@@ -221,245 +227,407 @@ void checkpcmark(void)
curwin->w_prev_pcmark.lnum = 0; // it has been checked
}
-/*
- * move "count" positions in the jump list (count may be negative)
- */
-pos_T *movemark(int count)
+/// 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)
{
- pos_T *pos;
- xfmark_T *jmp;
+ xfmark_T *jmp = NULL;
- cleanup_jumplist(curwin, true);
+ cleanup_jumplist(win, true);
- if (curwin->w_jumplistlen == 0) { // nothing to jump to
- return (pos_T *)NULL;
+ if (win->w_jumplistlen == 0) { // nothing to jump to
+ return NULL;
}
for (;;) {
- if (curwin->w_jumplistidx + count < 0
- || curwin->w_jumplistidx + count >= curwin->w_jumplistlen) {
- return (pos_T *)NULL;
+ 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 (curwin->w_jumplistidx == curwin->w_jumplistlen) {
+ // 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();
- --curwin->w_jumplistidx; // skip the new entry
- if (curwin->w_jumplistidx + count < 0) {
- return (pos_T *)NULL;
+ win->w_jumplistidx--; // skip the new entry
+ if (win->w_jumplistidx + count < 0) {
+ return NULL;
}
}
- curwin->w_jumplistidx += count;
+ win->w_jumplistidx += count;
- jmp = curwin->w_jumplist + curwin->w_jumplistidx;
+ 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) {
- // jump to other file
- if (buflist_findnr(jmp->fmark.fnum) == NULL) { // Skip this one ..
+ // 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;
}
- if (buflist_getfile(jmp->fmark.fnum, jmp->fmark.mark.lnum,
- 0, FALSE) == FAIL) {
- return (pos_T *)NULL;
- }
- // Set lnum again, autocommands my have changed it
- curwin->w_cursor = jmp->fmark.mark;
- pos = (pos_T *)-1;
- } else {
- pos = &(jmp->fmark.mark);
}
- return pos;
+ break;
}
+ return jmp != NULL ? &jmp->fmark : NULL;
}
-/*
- * Move "count" positions in the changelist (count may be negative).
- */
-pos_T *movechangelist(int count)
+/// 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 (curbuf->b_changelistlen == 0) { // nothing to jump to
- return (pos_T *)NULL;
+ if (buf->b_changelistlen == 0) { // nothing to jump to
+ return NULL;
}
- n = curwin->w_changelistidx;
+ n = win->w_changelistidx;
if (n + count < 0) {
if (n == 0) {
- return (pos_T *)NULL;
+ return NULL;
}
n = 0;
- } else if (n + count >= curbuf->b_changelistlen) {
- if (n == curbuf->b_changelistlen - 1) {
- return (pos_T *)NULL;
+ } else if (n + count >= buf->b_changelistlen) {
+ if (n == buf->b_changelistlen - 1) {
+ return NULL;
}
- n = curbuf->b_changelistlen - 1;
+ n = buf->b_changelistlen - 1;
} else {
n += count;
}
- curwin->w_changelistidx = n;
- return &(curbuf->b_changelist[n].mark);
+ 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]);
}
-/*
- * Find mark "c" in buffer pointed to by "buf".
- * If "changefile" is TRUE it's allowed to edit another file for '0, 'A, etc.
- * If "fnum" is not NULL store the fnum there for '0, 'A etc., don't edit
- * another file.
- * Returns:
- * - pointer to pos_T if found. lnum is 0 when mark not set, -1 when mark is
- * in another file which can't be gotten. (caller needs to check lnum!)
- * - NULL if there is no mark called 'c'.
- * - -1 if mark is in other file and jumped there (only if changefile is TRUE)
- */
-pos_T *getmark_buf(buf_T *buf, int c, bool changefile)
+/// 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)
{
- return getmark_buf_fnum(buf, c, changefile, NULL);
+ 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;
+ // Only wanted marks belonging to the buffer
+ if ((flag & kMarkBufLocal) && xfm->fmark.fnum != buf->handle) {
+ return NULL;
+ }
+ } 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;
}
-pos_T *getmark(int c, bool changefile)
+/// 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)
{
- return getmark_buf_fnum(curbuf, c, changefile, NULL);
+ 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;
}
-pos_T *getmark_buf_fnum(buf_T *buf, int c, bool changefile, int *fnum)
+/// 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)
{
- pos_T *posp;
- pos_T *startp, *endp;
- static pos_T pos_copy;
+ 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);
+ }
- posp = NULL;
+ return mark;
+}
- // Check for special key, can't be a mark name and might cause islower()
- // to crash.
- if (c < 0) {
- return posp;
- }
- if (c > '~') { // check for islower()/isupper()
- } else if (c == '\'' || c == '`') { // previous context mark
- pos_copy = curwin->w_pcmark; // need to make a copy because
- posp = &pos_copy; // w_pcmark may be changed soon
- } else if (c == '"') { // to pos when leaving buffer
- posp = &(buf->b_last_cursor.mark);
- } else if (c == '^') { // to where Insert mode stopped
- posp = &(buf->b_last_insert.mark);
- } else if (c == '.') { // to where last change was made
- posp = &(buf->b_last_change.mark);
- } else if (c == '[') { // to start of previous operator
- posp = &(buf->b_op_start);
- } else if (c == ']') { // to end of previous operator
- posp = &(buf->b_op_end);
- } else if (c == '{' || c == '}') { // to previous/next paragraph
- pos_T pos;
+/// 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;
+ if (name == '{' || name == '}') {
+ // to previous/next paragraph
oparg_T oa;
bool slcb = listcmd_busy;
+ listcmd_busy = true; // avoid that '' is changed
- pos = curwin->w_cursor;
- listcmd_busy = true; // avoid that '' is changed
if (findpar(&oa.inclusive,
- c == '}' ? FORWARD : BACKWARD, 1L, NUL, FALSE)) {
- pos_copy = curwin->w_cursor;
- posp = &pos_copy;
+ name == '}' ? FORWARD : BACKWARD, 1L, NUL, false)) {
+ mark = pos_to_mark(buf, NULL, win->w_cursor);
}
- curwin->w_cursor = pos;
listcmd_busy = slcb;
- } else if (c == '(' || c == ')') { // to previous/next sentence
- pos_T pos;
+ // to previous/next sentence
+ } else if (name == '(' || name == ')') {
bool slcb = listcmd_busy;
+ listcmd_busy = true; // avoid that '' is changed
- pos = curwin->w_cursor;
- listcmd_busy = true; // avoid that '' is changed
- if (findsent(c == ')' ? FORWARD : BACKWARD, 1L)) {
- pos_copy = curwin->w_cursor;
- posp = &pos_copy;
+ if (findsent(name == ')' ? FORWARD : BACKWARD, 1L)) {
+ mark = pos_to_mark(buf, NULL, win->w_cursor);
}
- curwin->w_cursor = pos;
listcmd_busy = slcb;
- } else if (c == '<' || c == '>') { // start/end of visual area
- startp = &buf->b_visual.vi_start;
- endp = &buf->b_visual.vi_end;
- if (((c == '<') == lt(*startp, *endp) || endp->lnum == 0)
- && startp->lnum != 0) {
- posp = startp;
+ }
+ 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 {
- posp = endp;
+ mark = pos_to_mark(buf, NULL, endp);
}
- // For Visual line mode, set mark at begin or end of line
- if (buf->b_visual.vi_mode == 'V') {
- pos_copy = *posp;
- posp = &pos_copy;
- if (c == '<') {
- pos_copy.col = 0;
+ if (mark != NULL && buf->b_visual.vi_mode == 'V') {
+ if (name == '<') {
+ mark->mark.col = 0;
} else {
- pos_copy.col = MAXCOL;
+ mark->mark.col = MAXCOL;
}
- pos_copy.coladd = 0;
+ mark->mark.coladd = 0;
}
- } else if (ASCII_ISLOWER(c)) { // normal named mark
- posp = &(buf->b_namedm[c - 'a'].mark);
- } else if (ASCII_ISUPPER(c) || ascii_isdigit(c)) { // named file mark
- if (ascii_isdigit(c)) {
- c = c - '0' + NMARKS;
- } else {
- c -= 'A';
- }
- posp = &(namedfm[c].fmark.mark);
+ }
+ 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 fmrak->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)
+{
+ 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;
+}
- if (namedfm[c].fmark.fnum == 0) {
- fname2fnum(&namedfm[c]);
+/// 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 (fnum != NULL) {
- *fnum = namedfm[c].fmark.fnum;
- } else if (namedfm[c].fmark.fnum != buf->b_fnum) {
- // mark is in another file
- posp = &pos_copy;
-
- if (namedfm[c].fmark.mark.lnum != 0
- && changefile && namedfm[c].fmark.fnum) {
- if (buflist_getfile(namedfm[c].fmark.fnum,
- (linenr_T)1, GETF_SETMARK, FALSE) == OK) {
- // Set the lnum now, autocommands could have changed it
- curwin->w_cursor = namedfm[c].fmark.mark;
- return (pos_T *)-1;
- }
- pos_copy.lnum = -1; // can't get file
- } else {
- pos_copy.lnum = 0; // mark exists, but is not valid in current buffer
- }
+ 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 (topline >= 1) {
+ set_topline(curwin, topline);
}
}
+}
- return posp;
+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.
+/// Search for the next named mark in the current file from a start position.
///
-/// @param startpos where to start
-/// @param dir direction for search
+/// @param startpos where to start.
+/// @param dir direction for search.
///
-/// @return pointer to pos_T of the next mark or NULL if no mark is found.
-pos_T *getnextmark(pos_T *startpos, int dir, int begin_line)
+/// @return next mark or NULL if no mark is found.
+fmark_T *getnextmark(pos_T *startpos, int dir, int begin_line)
{
int i;
- pos_T *result = NULL;
+ fmark_T *result = NULL;
pos_T pos;
pos = *startpos;
- // When searching backward and leaving the cursor on the first non-blank,
- // position must be in a previous line.
- // When searching forward and leaving the cursor on the first non-blank,
- // position must be in a next line.
if (dir == BACKWARD && begin_line) {
pos.col = 0;
} else if (dir == FORWARD && begin_line) {
@@ -469,14 +637,14 @@ pos_T *getnextmark(pos_T *startpos, int dir, int begin_line)
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))
+ if ((result == NULL || lt(curbuf->b_namedm[i].mark, result->mark))
&& lt(pos, curbuf->b_namedm[i].mark)) {
- result = &curbuf->b_namedm[i].mark;
+ result = &curbuf->b_namedm[i];
}
} else {
- if ((result == NULL || lt(*result, curbuf->b_namedm[i].mark))
+ if ((result == NULL || lt(result->mark, curbuf->b_namedm[i].mark))
&& lt(curbuf->b_namedm[i].mark, pos)) {
- result = &curbuf->b_namedm[i].mark;
+ result = &curbuf->b_namedm[i];
}
}
}
@@ -557,29 +725,48 @@ static void fmarks_check_one(xfmark_T *fm, char_u *name, buf_T *buf)
}
}
-/*
- * Check a if a position from a mark is valid.
- * Give and error message and return FAIL if not.
- */
-int check_mark(pos_T *pos)
+/// 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 (pos == NULL) {
+ if (fm == NULL) {
emsg(_(e_umark));
- return FAIL;
- }
- if (pos->lnum <= 0) {
- // lnum is negative if mark is in another file can can't get that
- // file, error message already give then.
- if (pos->lnum == 0) {
+ 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 FAIL;
+ 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;
}
- if (pos->lnum > curbuf->b_ml.ml_line_count) {
+ 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 FAIL;
+ return false;
}
- return OK;
+ return true;
}
/// Clear all marks and change list in the given buffer
@@ -1318,7 +1505,7 @@ void copy_jumplist(win_T *from, win_T *to)
/// Iterate over jumplist items
///
-/// @warning No jumplist-editing functions must be run while iteration is in
+/// @warning No jumplist-editing functions must be called while iteration is in
/// progress.
///
/// @param[in] iter Iterator. Pass NULL to start iteration.
@@ -1331,7 +1518,7 @@ const void *mark_jumplist_iter(const void *const iter, const win_T *const win, x
FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT
{
if (iter == NULL && win->w_jumplistlen == 0) {
- *fm = (xfmark_T) { { { 0, 0, 0 }, 0, 0, NULL }, NULL };
+ *fm = (xfmark_T)INIT_XFMARK;
return NULL;
}
const xfmark_T *const iter_mark =
@@ -1348,7 +1535,7 @@ const void *mark_jumplist_iter(const void *const iter, const win_T *const win, x
/// Iterate over global marks
///
-/// @warning No mark-editing functions must be run while iteration is in
+/// @warning No mark-editing functions must be called while iteration is in
/// progress.
///
/// @param[in] iter Iterator. Pass NULL to start iteration.
@@ -1422,7 +1609,7 @@ static inline const fmark_T *next_buffer_mark(const buf_T *const buf, char *cons
/// Iterate over buffer marks
///
-/// @warning No mark-editing functions must be run while iteration is in
+/// @warning No mark-editing functions must be called while iteration is in
/// progress.
///
/// @param[in] iter Iterator. Pass NULL to start iteration.
@@ -1539,7 +1726,7 @@ void free_jumplist(win_T *wp)
void set_last_cursor(win_T *win)
{
if (win->w_buffer != NULL) {
- RESET_FMARK(&win->w_buffer->b_last_cursor, win->w_cursor, 0);
+ RESET_FMARK(&win->w_buffer->b_last_cursor, win->w_cursor, 0, ((fmarkv_T)INIT_FMARKV));
}
}
@@ -1640,9 +1827,10 @@ void get_buf_local_marks(const buf_T *buf, list_T *l)
/// 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_global_mark(char name)
+xfmark_T get_raw_global_mark(char name)
{
return namedfm[mark_global_index(name)];
}
diff --git a/src/nvim/mark.h b/src/nvim/mark.h
index a55f733d9a..6da976e8d3 100644
--- a/src/nvim/mark.h
+++ b/src/nvim/mark.h
@@ -13,42 +13,43 @@
#include "nvim/pos.h"
/// Set fmark using given value
-#define SET_FMARK(fmarkp_, mark_, fnum_) \
+#define SET_FMARK(fmarkp_, mark_, fnum_, view_) \
do { \
fmark_T *const fmarkp__ = fmarkp_; \
fmarkp__->mark = mark_; \
fmarkp__->fnum = fnum_; \
fmarkp__->timestamp = os_time(); \
+ fmarkp__->view = view_; \
fmarkp__->additional_data = NULL; \
} while (0)
/// Free and set fmark using given value
-#define RESET_FMARK(fmarkp_, mark_, fnum_) \
+#define RESET_FMARK(fmarkp_, mark_, fnum_, view_) \
do { \
fmark_T *const fmarkp___ = fmarkp_; \
free_fmark(*fmarkp___); \
- SET_FMARK(fmarkp___, mark_, fnum_); \
+ SET_FMARK(fmarkp___, mark_, fnum_, view_); \
} while (0)
/// Clear given fmark
#define CLEAR_FMARK(fmarkp_) \
- RESET_FMARK(fmarkp_, ((pos_T) { 0, 0, 0 }), 0)
+ RESET_FMARK(fmarkp_, ((pos_T) { 0, 0, 0 }), 0, ((fmarkv_T) { 0 }))
/// Set given extended mark (regular mark + file name)
-#define SET_XFMARK(xfmarkp_, mark_, fnum_, fname_) \
+#define SET_XFMARK(xfmarkp_, mark_, fnum_, view_, fname_) \
do { \
xfmark_T *const xfmarkp__ = xfmarkp_; \
xfmarkp__->fname = fname_; \
- SET_FMARK(&(xfmarkp__->fmark), mark_, fnum_); \
+ SET_FMARK(&(xfmarkp__->fmark), mark_, fnum_, view_); \
} while (0)
/// Free and set given extended mark (regular mark + file name)
-#define RESET_XFMARK(xfmarkp_, mark_, fnum_, fname_) \
+#define RESET_XFMARK(xfmarkp_, mark_, fnum_, view_, fname_) \
do { \
xfmark_T *const xfmarkp__ = xfmarkp_; \
free_xfmark(*xfmarkp__); \
xfmarkp__->fname = fname_; \
- SET_FMARK(&(xfmarkp__->fmark), mark_, fnum_); \
+ SET_FMARK(&(xfmarkp__->fmark), mark_, fnum_, view_); \
} while (0)
/// Convert mark name to the offset
diff --git a/src/nvim/mark_defs.h b/src/nvim/mark_defs.h
index 994ad30633..16d85a6e51 100644
--- a/src/nvim/mark_defs.h
+++ b/src/nvim/mark_defs.h
@@ -10,6 +10,33 @@
* (a normal mark is a lnum/col pair, the same as a file position)
*/
+/// Flags for outcomes when moving to a mark.
+typedef enum {
+ kMarkMoveSuccess = 1, ///< Successful move.
+ kMarkMoveFailed = 2, ///< Failed to move.
+ kMarkSwitchedBuf = 4, ///< Switched curbuf.
+ kMarkChangedCol = 8, ///< Changed the cursor col.
+ kMarkChangedLine = 16, ///< Changed the cursor line.
+ kMarkChangedCursor = 32, ///< Changed the cursor.
+ kMarkChangedView = 64, ///< Changed the view.
+} MarkMoveRes;
+
+/// Flags to configure the movement to a mark.
+typedef enum {
+ kMarkBeginLine = 1, ///< Move cursor to the beginning of the line.
+ kMarkContext = 2, ///< Leave context mark when moving the cursor.
+ KMarkNoContext = 4, ///< Don't leave a context mark.
+ kMarkSetView = 8, ///< Set the mark view after moving
+ kMarkJumpList = 16, ///< Special case, don't leave context mark when switching buffer
+} MarkMove;
+
+/// Options when getting a mark
+typedef enum {
+ kMarkBufLocal, ///< Only return marks that belong to the buffer.
+ kMarkAll, ///< Return all types of marks.
+ kMarkAllNoResolve, ///< Return all types of marks but don't resolve fnum (global marks).
+} MarkGet;
+
/// Number of possible numbered global marks
#define EXTRA_MARKS ('9' - '0' + 1)
@@ -25,24 +52,39 @@
/// but they are not saved in ShaDa files.
#define NLOCALMARKS (NMARKS + 3)
+/// Max value of local mark
+#define NMARK_LOCAL_MAX 126 // Index of '~'
+
/// Maximum number of marks in jump list
#define JUMPLISTSIZE 100
/// Maximum number of tags in tag stack
#define TAGSTACKSIZE 20
+/// Represents view in which the mark was created
+typedef struct fmarkv {
+ linenr_T topline_offset; ///< Amount of lines from the mark lnum to the top of the window.
+} fmarkv_T;
+
+#define INIT_FMARKV { 0 }
+
/// Structure defining single local mark
typedef struct filemark {
pos_T mark; ///< Cursor position.
int fnum; ///< File number.
Timestamp timestamp; ///< Time when this mark was last set.
+ fmarkv_T view; ///< View the mark was created on
dict_T *additional_data; ///< Additional data from ShaDa file.
} fmark_T;
+#define INIT_FMARK { { 0, 0, 0 }, 0, 0, INIT_FMARKV, NULL }
+
/// Structure defining extended mark (mark with file name attached)
typedef struct xfilemark {
fmark_T fmark; ///< Actual mark.
char *fname; ///< File name, used when fnum == 0.
} xfmark_T;
+#define INIT_XFMARK { INIT_FMARK, NULL }
+
#endif // NVIM_MARK_DEFS_H
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index aeb85eba1c..d491a0ce84 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -4983,19 +4983,22 @@ static void nv_brackets(cmdarg_T *cap)
nv_put_opt(cap, true);
} else if (cap->nchar == '\'' || cap->nchar == '`') {
// "['", "[`", "]'" and "]`": jump to next mark
+ fmark_T *fm = NULL;
pos = &curwin->w_cursor;
for (n = cap->count1; n > 0; n--) {
prev_pos = *pos;
- pos = getnextmark(pos, cap->cmdchar == '[' ? BACKWARD : FORWARD,
- cap->nchar == '\'');
+ fm = getnextmark(pos, cap->cmdchar == '[' ? BACKWARD : FORWARD,
+ cap->nchar == '\'');
if (pos == NULL) {
break;
+ } else {
+ pos = fm != NULL ? &fm->mark : NULL; // Adjust for the next iteration.
}
}
- if (pos == NULL) {
- pos = &prev_pos;
- }
- nv_cursormark(cap, cap->nchar == '\'', pos);
+ fm = fm == NULL ? pos_to_mark(curbuf, NULL, curwin->w_cursor) : fm;
+ MarkMove flags = kMarkContext;
+ flags |= cap->nchar == '\'' ? kMarkBeginLine: 0;
+ nv_mark_move_to(cap, flags, fm);
} else if (cap->nchar >= K_RIGHTRELEASE && cap->nchar <= K_LEFTMOUSE) {
// [ or ] followed by a middle mouse click: put selected text with
// indent adjustment. Any other button just does as usual.
@@ -5465,31 +5468,28 @@ static void n_swapchar(cmdarg_T *cap)
}
}
-/// Move cursor to mark.
-static void nv_cursormark(cmdarg_T *cap, int flag, pos_T *pos)
-{
- if (check_mark(pos) == false) {
+/// Move the cursor to the mark position
+///
+/// Wrapper to mark_move_to() that also handles normal mode command arguments.
+/// @note It will switch the buffer if neccesarry, move the cursor and set the
+/// view depending on the given flags.
+/// @param cap command line arguments
+/// @param flags for mark_move_to()
+/// @param mark mark
+/// @return The result of calling mark_move_to()
+static MarkMoveRes nv_mark_move_to(cmdarg_T *cap, MarkMove flags, fmark_T *fm)
+{
+ MarkMoveRes res = mark_move_to(fm, flags);
+ if (res & kMarkMoveFailed) {
clearop(cap->oap);
- } else {
- if (cap->cmdchar == '\''
- || cap->cmdchar == '`'
- || cap->cmdchar == '['
- || cap->cmdchar == ']') {
- setpcmark();
- }
- curwin->w_cursor = *pos;
- if (flag) {
- beginline(BL_WHITE | BL_FIX);
- } else {
- check_cursor();
- }
}
- cap->oap->motion_type = flag ? kMTLineWise : kMTCharWise;
+ cap->oap->motion_type = flags & kMarkBeginLine ? kMTLineWise : kMTCharWise;
if (cap->cmdchar == '`') {
cap->oap->use_reg_one = true;
}
cap->oap->inclusive = false; // ignored if not kMTCharWise
curwin->w_set_curswant = true;
+ return res;
}
/// Handle commands that are operators in Visual mode.
@@ -5564,36 +5564,32 @@ static void nv_optrans(cmdarg_T *cap)
/// cap->arg is true for "'" and "g'".
static void nv_gomark(cmdarg_T *cap)
{
- pos_T *pos;
- int c;
- pos_T old_cursor = curwin->w_cursor;
- const bool old_KeyTyped = KeyTyped; // getting file may reset it
+ int name;
+ MarkMove flags = jop_flags & JOP_VIEW ? kMarkSetView : 0; // flags for moving to the mark
+ MarkMoveRes move_res = 0; // Result from moving to the mark
+ const bool old_KeyTyped = KeyTyped; // getting file may reset it
if (cap->cmdchar == 'g') {
- c = cap->extra_char;
- } else {
- c = cap->nchar;
- }
- pos = getmark(c, (cap->oap->op_type == OP_NOP));
- if (pos == (pos_T *)-1) { // jumped to other file
- if (cap->arg) {
- check_cursor_lnum();
- beginline(BL_WHITE | BL_FIX);
- } else {
- check_cursor();
- }
+ name = cap->extra_char;
+ flags |= KMarkNoContext;
} else {
- nv_cursormark(cap, cap->arg, pos);
+ name = cap->nchar;
+ flags |= kMarkContext;
}
+ flags |= cap->arg ? kMarkBeginLine : 0;
+ flags |= cap->count0 ? kMarkSetView : 0;
+
+ fmark_T *fm = mark_get(curbuf, curwin, NULL, kMarkAll, name);
+ move_res = nv_mark_move_to(cap, flags, fm);
// May need to clear the coladd that a mark includes.
if (!virtual_active()) {
curwin->w_cursor.coladd = 0;
}
- check_cursor_col();
+
if (cap->oap->op_type == OP_NOP
- && pos != NULL
- && (pos == (pos_T *)-1 || !equalpos(old_cursor, *pos))
+ && move_res & kMarkMoveSuccess
+ && (move_res & kMarkSwitchedBuf || move_res & kMarkChangedCursor)
&& (fdo_flags & FDO_MARK)
&& old_KeyTyped) {
foldOpenCursor();
@@ -5601,11 +5597,13 @@ static void nv_gomark(cmdarg_T *cap)
}
/// Handle CTRL-O, CTRL-I, "g;", "g,", and "CTRL-Tab" commands.
+/// Movement in the jumplist and changelist.
static void nv_pcmark(cmdarg_T *cap)
{
- pos_T *pos;
- linenr_T lnum = curwin->w_cursor.lnum;
- const bool old_KeyTyped = KeyTyped; // getting file may reset it
+ fmark_T *fm = NULL;
+ MarkMove flags = jop_flags & JOP_VIEW ? kMarkSetView : 0; // flags for moving to the mark
+ MarkMoveRes move_res = 0; // Result from moving to the mark
+ const bool old_KeyTyped = KeyTyped; // getting file may reset it.
if (!checkclearopq(cap->oap)) {
if (cap->cmdchar == TAB && mod_mask == MOD_MASK_CTRL) {
@@ -5614,16 +5612,18 @@ static void nv_pcmark(cmdarg_T *cap)
}
return;
}
+
if (cap->cmdchar == 'g') {
- pos = movechangelist((int)cap->count1);
+ fm = get_changelist(curbuf, curwin, (int)cap->count1);
} else {
- pos = movemark((int)cap->count1);
- }
- if (pos == (pos_T *)-1) { // jump to other file
- curwin->w_set_curswant = true;
- check_cursor();
- } else if (pos != NULL) { // can jump
- nv_cursormark(cap, false, pos);
+ fm = get_jumplist(curwin, (int)cap->count1);
+ flags |= KMarkNoContext | kMarkJumpList;
+ }
+ // Changelist and jumplist have their own error messages. Therefore avoid
+ // calling nv_mark_move_to() when not found to avoid incorrect error
+ // messages.
+ if (fm != NULL) {
+ move_res = nv_mark_move_to(cap, flags, fm);
} else if (cap->cmdchar == 'g') {
if (curbuf->b_changelistlen == 0) {
emsg(_("E664: changelist is empty"));
@@ -5636,7 +5636,7 @@ static void nv_pcmark(cmdarg_T *cap)
clearopbeep(cap->oap);
}
if (cap->oap->op_type == OP_NOP
- && (pos == (pos_T *)-1 || lnum != curwin->w_cursor.lnum)
+ && (move_res & kMarkSwitchedBuf || move_res & kMarkChangedLine)
&& (fdo_flags & FDO_MARK)
&& old_KeyTyped) {
foldOpenCursor();
@@ -6804,7 +6804,6 @@ void set_cursor_for_append_to_line(void)
curwin->w_set_curswant = true;
if (get_ve_flags() == VE_ALL) {
const int save_State = State;
-
// Pretend Insert mode here to allow the cursor on the
// character past the end of the line
State = MODE_INSERT;
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index 9268b1eff6..531527ea3c 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -491,9 +491,10 @@ EXTERN int p_js; // 'joinspaces'
EXTERN char_u *p_jop; // 'jumpooptions'
EXTERN unsigned jop_flags;
#ifdef IN_OPTION_C
-static char *(p_jop_values[]) = { "stack", NULL };
+static char *(p_jop_values[]) = { "stack", "view", NULL };
#endif
#define JOP_STACK 0x01
+#define JOP_VIEW 0x02
EXTERN char_u *p_kp; // 'keywordprg'
EXTERN char_u *p_km; // 'keymodel'
EXTERN char_u *p_langmap; // 'langmap'
diff --git a/src/nvim/regexp_bt.c b/src/nvim/regexp_bt.c
index 03e4d74f14..272429bb91 100644
--- a/src/nvim/regexp_bt.c
+++ b/src/nvim/regexp_bt.c
@@ -3706,7 +3706,8 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out)
pos_T *pos;
size_t col = REG_MULTI ? rex.input - rex.line : 0;
- pos = getmark_buf(rex.reg_buf, mark, false);
+ // fm will be NULL if the mark is not set in reg_buf
+ fmark_T *fm = mark_get(rex.reg_buf, curwin, NULL, kMarkBufLocal, mark);
// Line may have been freed, get it again.
if (REG_MULTI) {
@@ -3714,10 +3715,11 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out)
rex.input = rex.line + col;
}
- if (pos == NULL // mark doesn't exist
- || pos->lnum <= 0) { // mark isn't set in reg_buf
+ if (fm == NULL // mark doesn't exist
+ || fm->mark.lnum <= 0) { // mark isn't set in reg_buf
status = RA_NOMATCH;
} else {
+ pos = &fm->mark;
const colnr_T pos_col = pos->lnum == rex.lnum + rex.reg_firstlnum
&& pos->col == MAXCOL
? (colnr_T)STRLEN(reg_getline(pos->lnum - rex.reg_firstlnum))
diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c
index 1e8204085c..870af3eafc 100644
--- a/src/nvim/regexp_nfa.c
+++ b/src/nvim/regexp_nfa.c
@@ -6930,10 +6930,10 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm
case NFA_MARK:
case NFA_MARK_GT:
case NFA_MARK_LT: {
- pos_T *pos;
+ fmark_T *fm;
size_t col = REG_MULTI ? rex.input - rex.line : 0;
-
- pos = getmark_buf(rex.reg_buf, t->state->val, false);
+ // fm will be NULL if the mark is not set, doesn't belong to reg_buf
+ fm = mark_get(rex.reg_buf, curwin, NULL, kMarkBufLocal, t->state->val);
// Line may have been freed, get it again.
if (REG_MULTI) {
@@ -6943,7 +6943,8 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm
// Compare the mark position to the match position, if the mark
// exists and mark is set in reg_buf.
- if (pos != NULL && pos->lnum > 0) {
+ if (fm != NULL && fm->mark.lnum > 0) {
+ pos_T *pos = &fm->mark;
const colnr_T pos_col = pos->lnum == rex.lnum + rex.reg_firstlnum
&& pos->col == MAXCOL
? (colnr_T)STRLEN(reg_getline(pos->lnum - rex.reg_firstlnum))
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index 32a0f3902d..834355f2b7 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -1331,8 +1331,9 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
buflist_new((char_u *)cur_entry.data.buffer_list.buffers[i].fname, (char_u *)sfname, 0,
BLN_LISTED);
if (buf != NULL) {
+ fmarkv_T view = INIT_FMARKV;
RESET_FMARK(&buf->b_last_cursor,
- cur_entry.data.buffer_list.buffers[i].pos, 0);
+ cur_entry.data.buffer_list.buffers[i].pos, 0, view);
buflist_setfpos(buf, curwin, buf->b_last_cursor.mark.lnum,
buf->b_last_cursor.mark.col, false);
buf->additional_data =
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index 796a2fa5f3..bb884ffb3a 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -116,7 +116,7 @@ static char_u *tagmatchname = NULL; // name of last used tag
* Tag for preview window is remembered separately, to avoid messing up the
* normal tagstack.
*/
-static taggy_T ptag_entry = { NULL, { { 0, 0, 0 }, 0, 0, NULL }, 0, 0, NULL };
+static taggy_T ptag_entry = { NULL, INIT_FMARK, 0, 0, NULL };
static int tfu_in_use = false; // disallow recursive call of tagfunc