diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/api/buffer.c | 37 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.c | 2 | ||||
-rw-r--r-- | src/nvim/api/vim.c | 10 | ||||
-rw-r--r-- | src/nvim/buffer.c | 37 | ||||
-rw-r--r-- | src/nvim/buffer_defs.h | 4 | ||||
-rw-r--r-- | src/nvim/change.c | 8 | ||||
-rw-r--r-- | src/nvim/edit.c | 3 | ||||
-rw-r--r-- | src/nvim/eval.c | 9 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 2 | ||||
-rw-r--r-- | src/nvim/ex_cmds.c | 2 | ||||
-rw-r--r-- | src/nvim/ex_docmd.c | 27 | ||||
-rw-r--r-- | src/nvim/ex_session.c | 2 | ||||
-rw-r--r-- | src/nvim/mark.c | 570 | ||||
-rw-r--r-- | src/nvim/mark.h | 17 | ||||
-rw-r--r-- | src/nvim/mark_defs.h | 42 | ||||
-rw-r--r-- | src/nvim/normal.c | 113 | ||||
-rw-r--r-- | src/nvim/option_defs.h | 3 | ||||
-rw-r--r-- | src/nvim/regexp_bt.c | 8 | ||||
-rw-r--r-- | src/nvim/regexp_nfa.c | 9 | ||||
-rw-r--r-- | src/nvim/shada.c | 3 | ||||
-rw-r--r-- | src/nvim/tag.c | 2 |
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 |