mirror of
https://github.com/neovim/neovim.git
synced 2025-09-14 23:38:17 +00:00
feat(marks): restore viewport on jump #15831
** Refactor Previously most functions used to "get" a mark returned a position, changed the line number and sometimes changed even the current buffer. Now functions return a {x}fmark_T making calling context aware whether the mark is in another buffer without arcane casting. A new function is provided for switching to the mark buffer and returning a flag style Enum to convey what happen in the movement. If the cursor changed, line, columns, if it changed buffer, etc. The function to get named mark was split into multiple functions. - mark_get() -> fmark_T - mark_get_global() -> xfmark_T - mark_get_local() -> fmark_T - mark_get_motion() -> fmark_T - mark_get_visual() -> fmark_T Functions that manage the changelist and jumplist were also modified to return mark types. - get_jumplist -> fmark_T - get_changelist -> fmark_T The refactor is also seen mainly on normal.c, where all the mark movement has been siphoned through one function nv_gomark, while the other functions handle getting the mark and setting their movement flags. To handle whether context marks should be left, etc. ** Mark View While doing the refactor the concept of a mark view was also implemented: The view of a mark currently implemented as the number of lines between the mark position on creation and the window topline. This allows for moving not only back to the position of a mark but having the window look similar to when the mark was defined. This is done by carrying and extra element in the fmark_T struct, which can be extended later to also restore horizontal shift. *** User space features 1. There's a new option, jumpoptions+=view enables the mark view restoring automatically when using the jumplist, changelist, alternate-file and mark motions. <C-O> <C-I> g; g, <C-^> '[mark] `[mark] ** Limitations - The view information is not saved in shada. - Calls to get_mark should copy the value in the pointer since we are using pos_to_mark() to wrap and provide a homogeneous interfaces. This was also a limitation in the previous state of things.
This commit is contained in:
@@ -735,6 +735,11 @@ Jumping to a mark can be done in two ways:
|
||||
2. With ' (single quote): The cursor is positioned on the first non-blank
|
||||
character in the line of the specified location and
|
||||
the motion is linewise.
|
||||
*mark-view*
|
||||
3. Apart from the above if 'jumpoptions' contains "view", they will also try to
|
||||
restore the mark view. This is the number of lines between the cursor position
|
||||
and the window topline (first buffer line displayed in the window) when it was
|
||||
set.
|
||||
|
||||
*m* *mark* *Mark*
|
||||
m{a-zA-Z} Set mark {a-zA-Z} at cursor position (does not move
|
||||
|
@@ -3531,6 +3531,10 @@ A jump table for the options with a short description can be found at |Q_op|.
|
||||
jumplist and then jumping to a location.
|
||||
|jumplist-stack|
|
||||
|
||||
view When moving through the jumplist, |changelist|,
|
||||
|alternate-file| or using |mark-motions| try to
|
||||
restore the |mark-view| in which the action occurred.
|
||||
|
||||
*'joinspaces'* *'js'* *'nojoinspaces'* *'nojs'*
|
||||
'joinspaces' 'js' boolean (default off)
|
||||
global
|
||||
|
@@ -383,6 +383,8 @@ Normal commands:
|
||||
Options:
|
||||
'ttimeout', 'ttimeoutlen' behavior was simplified
|
||||
|jumpoptions| "stack" behavior
|
||||
|jumpoptions| "view" tries to restore the |mark-view| when moving through
|
||||
the |jumplist|, |changelist|, |alternate-file| or using |mark-motions|.
|
||||
'shortmess' the "F" flag does not affect output from autocommands
|
||||
|
||||
Shell:
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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,
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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).
|
||||
|
@@ -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
|
||||
|
@@ -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.
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@@ -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) {
|
||||
|
@@ -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 {
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
}
|
||||
|
606
src/nvim/mark.c
606
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);
|
||||
}
|
||||
|
||||
pos_T *getmark(int c, bool changefile)
|
||||
{
|
||||
return getmark_buf_fnum(curbuf, c, changefile, NULL);
|
||||
}
|
||||
|
||||
pos_T *getmark_buf_fnum(buf_T *buf, int c, bool changefile, int *fnum)
|
||||
{
|
||||
pos_T *posp;
|
||||
pos_T *startp, *endp;
|
||||
static pos_T pos_copy;
|
||||
|
||||
posp = NULL;
|
||||
|
||||
// Check for special key, can't be a mark name and might cause islower()
|
||||
// to crash.
|
||||
if (c < 0) {
|
||||
return posp;
|
||||
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;
|
||||
}
|
||||
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;
|
||||
} else if (name > 0 && name < NMARK_LOCAL_MAX) {
|
||||
// Local Marks
|
||||
fm = mark_get_local(buf, win, name);
|
||||
}
|
||||
if (fmp != NULL && fm != NULL) {
|
||||
*fmp = *fm;
|
||||
return fmp;
|
||||
}
|
||||
return fm;
|
||||
}
|
||||
|
||||
/// Get a global mark {A-Z0-9}.
|
||||
///
|
||||
/// @param name the name of the mark.
|
||||
/// @param resolve Whether to try resolving the mark fnum (i.e., load the buffer stored in
|
||||
/// the mark fname and update the xfmark_T (expensive)).
|
||||
///
|
||||
/// @return Mark
|
||||
xfmark_T *mark_get_global(bool resolve, int name)
|
||||
{
|
||||
xfmark_T *mark;
|
||||
|
||||
if (ascii_isdigit(name)) {
|
||||
name = name - '0' + NMARKS;
|
||||
} else if (ASCII_ISUPPER(name)) {
|
||||
name -= 'A';
|
||||
} else {
|
||||
// Not a valid mark name
|
||||
assert(false);
|
||||
}
|
||||
mark = &namedfm[name];
|
||||
|
||||
if (resolve && mark->fmark.fnum == 0) {
|
||||
// Resolve filename to fnum (SHADA marks)
|
||||
fname2fnum(mark);
|
||||
}
|
||||
return mark;
|
||||
}
|
||||
|
||||
/// Get a local mark (lowercase and symbols).
|
||||
///
|
||||
/// Some marks are not actually marks, but positions that are never adjusted or motions presented as
|
||||
/// marks. Search first for marks and fallback to finding motion type marks. If it's known
|
||||
/// ahead of time that the mark is actually a motion use the mark_get_motion() directly.
|
||||
///
|
||||
/// @note Lowercase, last_cursor '"', last insert '^', last change '.' are not statically
|
||||
/// allocated, everything else is.
|
||||
/// @param name the name of the mark.
|
||||
/// @param win window to retrieve marks that belong to it (motions and context mark).
|
||||
/// @param buf buf to retrieve marks that belong to it.
|
||||
///
|
||||
/// @return Mark, NULL if not found.
|
||||
fmark_T *mark_get_local(buf_T *buf, win_T *win, int name)
|
||||
{
|
||||
fmark_T *mark = NULL;
|
||||
if (ASCII_ISLOWER(name)) {
|
||||
// normal named mark
|
||||
mark = &buf->b_namedm[name - 'a'];
|
||||
// to start of previous operator
|
||||
} else if (name == '[') {
|
||||
mark = pos_to_mark(buf, NULL, buf->b_op_start);
|
||||
// to end of previous operator
|
||||
} else if (name == ']') {
|
||||
mark = pos_to_mark(buf, NULL, buf->b_op_end);
|
||||
// visual marks
|
||||
} else if (name == '<' || name == '>') {
|
||||
mark = mark_get_visual(buf, name);
|
||||
// previous context mark
|
||||
} else if (name == '\'' || name == '`') {
|
||||
// TODO(muniter): w_pcmark should be stored as a mark, but causes a nasty bug.
|
||||
mark = pos_to_mark(curbuf, NULL, win->w_pcmark);
|
||||
// to position when leaving buffer
|
||||
} else if (name == '"') {
|
||||
mark = &(buf->b_last_cursor);
|
||||
// to where last Insert mode stopped
|
||||
} else if (name == '^') {
|
||||
mark = &(buf->b_last_insert);
|
||||
// to where last change was made
|
||||
} else if (name == '.') {
|
||||
mark = &buf->b_last_change;
|
||||
// Mark that are actually not marks but motions, e.g {, }, (, ), ...
|
||||
} else {
|
||||
mark = mark_get_motion(buf, win, name);
|
||||
}
|
||||
|
||||
return mark;
|
||||
}
|
||||
|
||||
/// Get marks that are actually motions but return them as marks
|
||||
///
|
||||
/// Gets the following motions as marks: '{', '}', '(', ')'
|
||||
/// @param name name of the mark
|
||||
/// @param win window to retrieve the cursor to calculate the mark.
|
||||
/// @param buf buf to wrap motion marks with it's buffer number (fm->fnum).
|
||||
///
|
||||
/// @return[static] Mark.
|
||||
fmark_T *mark_get_motion(buf_T *buf, win_T *win, int name)
|
||||
{
|
||||
fmark_T *mark = NULL;
|
||||
if (name == '{' || name == '}') {
|
||||
// to previous/next paragraph
|
||||
oparg_T oa;
|
||||
bool slcb = listcmd_busy;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
} else {
|
||||
posp = 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;
|
||||
} else {
|
||||
pos_copy.col = MAXCOL;
|
||||
}
|
||||
pos_copy.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);
|
||||
|
||||
if (namedfm[c].fmark.fnum == 0) {
|
||||
fname2fnum(&namedfm[c]);
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return posp;
|
||||
return mark;
|
||||
}
|
||||
|
||||
/// Search for the next named mark in the current file.
|
||||
/// Get visual marks '<', '>'
|
||||
///
|
||||
/// @param startpos where to start
|
||||
/// @param dir direction for search
|
||||
/// 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 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[static] Mark
|
||||
fmark_T *mark_get_visual(buf_T *buf, int name)
|
||||
{
|
||||
fmark_T *mark = NULL;
|
||||
if (name == '<' || name == '>') {
|
||||
// start/end of visual area
|
||||
pos_T startp = buf->b_visual.vi_start;
|
||||
pos_T endp = buf->b_visual.vi_end;
|
||||
if (((name == '<') == lt(startp, endp) || endp.lnum == 0)
|
||||
&& startp.lnum != 0) {
|
||||
mark = pos_to_mark(buf, NULL, startp);
|
||||
} else {
|
||||
mark = pos_to_mark(buf, NULL, endp);
|
||||
}
|
||||
|
||||
if (mark != NULL && buf->b_visual.vi_mode == 'V') {
|
||||
if (name == '<') {
|
||||
mark->mark.col = 0;
|
||||
} else {
|
||||
mark->mark.col = MAXCOL;
|
||||
}
|
||||
mark->mark.coladd = 0;
|
||||
}
|
||||
}
|
||||
return mark;
|
||||
}
|
||||
|
||||
/// Wrap a pos_T into an fmark_T, used to abstract marks handling.
|
||||
///
|
||||
/// Pass an fmp if multiple c
|
||||
/// @note view fields are set to 0.
|
||||
/// @param buf for fmark->fnum.
|
||||
/// @param pos for 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;
|
||||
}
|
||||
|
||||
/// Move to the given file mark, changing the buffer and cursor position.
|
||||
///
|
||||
/// Validate the mark, switch to the buffer, and move the cursor.
|
||||
/// @param fm Mark, can be NULL will raise E78: Unknown mark
|
||||
/// @param flags MarkMove flags to configure the movement to the mark.
|
||||
///
|
||||
/// @return MarkMovekRes flags representing the outcome
|
||||
MarkMoveRes mark_move_to(fmark_T *fm, MarkMove flags)
|
||||
{
|
||||
static fmark_T fm_copy = INIT_FMARK;
|
||||
MarkMoveRes res = kMarkMoveSuccess;
|
||||
if (!mark_check(fm)) {
|
||||
res = kMarkMoveFailed;
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (fm->fnum != curbuf->handle) {
|
||||
// Need to change buffer
|
||||
fm_copy = *fm; // Copy, autocommand may change it
|
||||
fm = &fm_copy;
|
||||
res |= switch_to_mark_buf(fm, !(flags & kMarkJumpList));
|
||||
// Failed switching buffer
|
||||
if (res & kMarkMoveFailed) {
|
||||
goto end;
|
||||
}
|
||||
// Check line count now that the **destination buffer is loaded**.
|
||||
if (!mark_check_line_bounds(curbuf, fm)) {
|
||||
res |= kMarkMoveFailed;
|
||||
goto end;
|
||||
}
|
||||
} else if (flags & kMarkContext) {
|
||||
// Doing it in this condition avoids double context mark when switching buffer.
|
||||
setpcmark();
|
||||
}
|
||||
// Move the cursor while keeping track of what changed for the caller
|
||||
pos_T prev_pos = curwin->w_cursor;
|
||||
pos_T pos = fm->mark;
|
||||
curwin->w_cursor = fm->mark;
|
||||
if (flags & kMarkBeginLine) {
|
||||
beginline(BL_WHITE | BL_FIX);
|
||||
}
|
||||
res = prev_pos.lnum != pos.lnum ? res | kMarkChangedLine | kMarkChangedCursor : res;
|
||||
res = prev_pos.col != pos.col ? res | kMarkChangedCol | kMarkChangedCursor : res;
|
||||
if (flags & kMarkSetView) {
|
||||
mark_view_restore(fm);
|
||||
}
|
||||
|
||||
if (res & kMarkSwitchedBuf || res & kMarkChangedCursor) {
|
||||
check_cursor();
|
||||
}
|
||||
end:
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Restore the mark view.
|
||||
/// By remembering the offset between topline and mark lnum at the time of
|
||||
/// definition, this function restores the "view".
|
||||
/// @note Assumes the mark has been checked, is valid.
|
||||
/// @param fm the named mark.
|
||||
void mark_view_restore(fmark_T *fm)
|
||||
{
|
||||
if (fm != NULL && fm->view.topline_offset >= 0) {
|
||||
linenr_T topline = fm->mark.lnum - fm->view.topline_offset;
|
||||
if (topline >= 1) {
|
||||
set_topline(curwin, topline);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmarkv_T mark_view_make(linenr_T topline, pos_T pos)
|
||||
{
|
||||
return (fmarkv_T){ pos.lnum - topline };
|
||||
}
|
||||
|
||||
/// Search for the next named mark in the current file from a start position.
|
||||
///
|
||||
/// @param startpos where to start.
|
||||
/// @param dir direction for search.
|
||||
///
|
||||
/// @return next mark or NULL if no mark is found.
|
||||
fmark_T *getnextmark(pos_T *startpos, int dir, int begin_line)
|
||||
{
|
||||
int i;
|
||||
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;
|
||||
}
|
||||
if (pos->lnum > curbuf->b_ml.ml_line_count) {
|
||||
// Only check for valid line number if the buffer is loaded.
|
||||
if (fm->fnum == curbuf->handle && !mark_check_line_bounds(curbuf, fm)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Check if a mark line number is greater than the buffer line count, and set e_markinval.
|
||||
/// @note Should be done after the buffer is loaded into memory.
|
||||
/// @param buf Buffer where the mark is set.
|
||||
/// @param fm Mark to check.
|
||||
/// @return true if below line count else false.
|
||||
bool mark_check_line_bounds(buf_T *buf, fmark_T *fm)
|
||||
{
|
||||
if (buf != NULL && fm->mark.lnum > buf->b_ml.ml_line_count) {
|
||||
emsg(_(e_markinval));
|
||||
return 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)];
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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,
|
||||
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)
|
||||
/// 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)
|
||||
{
|
||||
if (check_mark(pos) == false) {
|
||||
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;
|
||||
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;
|
||||
name = cap->extra_char;
|
||||
flags |= KMarkNoContext;
|
||||
} 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();
|
||||
}
|
||||
} 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);
|
||||
fm = get_jumplist(curwin, (int)cap->count1);
|
||||
flags |= KMarkNoContext | kMarkJumpList;
|
||||
}
|
||||
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);
|
||||
// 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;
|
||||
|
@@ -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'
|
||||
|
@@ -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))
|
||||
|
@@ -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))
|
||||
|
@@ -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 =
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
local helpers = require('test.functional.helpers')(after_each)
|
||||
local Screen = require('test.functional.ui.screen')
|
||||
|
||||
local clear = helpers.clear
|
||||
local command = helpers.command
|
||||
@@ -7,6 +8,7 @@ local funcs = helpers.funcs
|
||||
local feed = helpers.feed
|
||||
local exec_capture = helpers.exec_capture
|
||||
local write_file = helpers.write_file
|
||||
local curbufmeths = helpers.curbufmeths
|
||||
|
||||
describe('jumplist', function()
|
||||
local fname1 = 'Xtest-functional-normal-jump'
|
||||
@@ -137,3 +139,116 @@ describe("jumpoptions=stack behaves like 'tagstack'", function()
|
||||
exec_capture('jumps'))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("jumpoptions=view", function()
|
||||
local file1 = 'Xtestfile-functional-editor-jumps'
|
||||
local file2 = 'Xtestfile-functional-editor-jumps-2'
|
||||
local function content()
|
||||
local c = {}
|
||||
for i=1,30 do
|
||||
c[i] = i .. " line"
|
||||
end
|
||||
return table.concat(c, "\n")
|
||||
end
|
||||
before_each(function()
|
||||
clear()
|
||||
write_file(file1, content(), false, false)
|
||||
write_file(file2, content(), false, false)
|
||||
command('set jumpoptions=view')
|
||||
end)
|
||||
after_each(function()
|
||||
os.remove(file1)
|
||||
os.remove(file2)
|
||||
end)
|
||||
|
||||
it('restores the view', function()
|
||||
local screen = Screen.new(5, 8)
|
||||
screen:attach()
|
||||
command("edit " .. file1)
|
||||
feed("12Gztj")
|
||||
feed("gg<C-o>")
|
||||
screen:expect([[
|
||||
12 line |
|
||||
^13 line |
|
||||
14 line |
|
||||
15 line |
|
||||
16 line |
|
||||
17 line |
|
||||
18 line |
|
||||
|
|
||||
]])
|
||||
end)
|
||||
|
||||
it('restores the view across files', function()
|
||||
local screen = Screen.new(5, 5)
|
||||
screen:attach()
|
||||
command("args " .. file1 .. " " .. file2)
|
||||
feed("12Gzt")
|
||||
command("next")
|
||||
feed("G")
|
||||
screen:expect([[
|
||||
27 line |
|
||||
28 line |
|
||||
29 line |
|
||||
^30 line |
|
||||
|
|
||||
]])
|
||||
feed("<C-o><C-o>")
|
||||
screen:expect([[
|
||||
^12 line |
|
||||
13 line |
|
||||
14 line |
|
||||
15 line |
|
||||
|
|
||||
]])
|
||||
end)
|
||||
|
||||
it('restores the view across files with <C-^>', function()
|
||||
local screen = Screen.new(5, 5)
|
||||
screen:attach()
|
||||
command("args " .. file1 .. " " .. file2)
|
||||
feed("12Gzt")
|
||||
command("next")
|
||||
feed("G")
|
||||
screen:expect([[
|
||||
27 line |
|
||||
28 line |
|
||||
29 line |
|
||||
^30 line |
|
||||
|
|
||||
]])
|
||||
feed("<C-^>")
|
||||
screen:expect([[
|
||||
^12 line |
|
||||
13 line |
|
||||
14 line |
|
||||
15 line |
|
||||
|
|
||||
]])
|
||||
end)
|
||||
|
||||
it('falls back to standard behavior when view can\'t be recovered', function()
|
||||
local screen = Screen.new(5, 8)
|
||||
screen:attach()
|
||||
command("edit " .. file1)
|
||||
feed("7GzbG")
|
||||
curbufmeths.set_lines(0, 2, true, {})
|
||||
-- Move to line 7, and set it as the last line visible on the view with zb, meaning to recover
|
||||
-- the view it needs to put the cursor 7 lines from the top line. Then go to the end of the
|
||||
-- file, delete 2 lines before line 7, meaning the jump/mark is moved 2 lines up to line 5.
|
||||
-- Therefore when trying to jump back to it it's not possible to set a 7 line offset from the
|
||||
-- mark position to the top line, since there's only 5 lines from the mark position to line 0.
|
||||
-- Therefore falls back to standard behavior which is centering the view/line.
|
||||
feed("<C-o>")
|
||||
screen:expect([[
|
||||
4 line |
|
||||
5 line |
|
||||
6 line |
|
||||
^7 line |
|
||||
8 line |
|
||||
9 line |
|
||||
10 line |
|
||||
|
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
380
test/functional/editor/mark_spec.lua
Normal file
380
test/functional/editor/mark_spec.lua
Normal file
@@ -0,0 +1,380 @@
|
||||
local helpers = require('test.functional.helpers')(after_each)
|
||||
local Screen = require('test.functional.ui.screen')
|
||||
local meths = helpers.meths
|
||||
local curbufmeths = helpers.curbufmeths
|
||||
local clear = helpers.clear
|
||||
local command = helpers.command
|
||||
local funcs = helpers.funcs
|
||||
local eq = helpers.eq
|
||||
local feed = helpers.feed
|
||||
local write_file = helpers.write_file
|
||||
local pcall_err = helpers.pcall_err
|
||||
local cursor = function() return helpers.meths.win_get_cursor(0) end
|
||||
|
||||
describe('named marks', function()
|
||||
local file1 = 'Xtestfile-functional-editor-marks'
|
||||
local file2 = 'Xtestfile-functional-editor-marks-2'
|
||||
before_each(function()
|
||||
clear()
|
||||
write_file(file1, '1test1\n1test2\n1test3\n1test4', false, false)
|
||||
write_file(file2, '2test1\n2test2\n2test3\n2test4', false, false)
|
||||
end)
|
||||
after_each(function()
|
||||
os.remove(file1)
|
||||
os.remove(file2)
|
||||
end)
|
||||
|
||||
|
||||
it("can be set", function()
|
||||
command("edit " .. file1)
|
||||
command("mark a")
|
||||
eq({1, 0}, curbufmeths.get_mark("a"))
|
||||
feed("jmb")
|
||||
eq({2, 0}, curbufmeths.get_mark("b"))
|
||||
feed("jmB")
|
||||
eq({3, 0}, curbufmeths.get_mark("B"))
|
||||
command("4kc")
|
||||
eq({4, 0}, curbufmeths.get_mark("c"))
|
||||
end)
|
||||
|
||||
it("errors when set out of range with :mark", function()
|
||||
command("edit " .. file1)
|
||||
local err = pcall_err(helpers.exec_capture, "1000mark x")
|
||||
eq("Vim(mark):E16: Invalid range: 1000mark x", err)
|
||||
end)
|
||||
|
||||
it("errors when set out of range with :k", function()
|
||||
command("edit " .. file1)
|
||||
local err = pcall_err(helpers.exec_capture, "1000kx")
|
||||
eq("Vim(k):E16: Invalid range: 1000kx", err)
|
||||
end)
|
||||
|
||||
it("errors on unknown mark name with :mark", function()
|
||||
command("edit " .. file1)
|
||||
local err = pcall_err(helpers.exec_capture, "mark #")
|
||||
eq("Vim(mark):E191: Argument must be a letter or forward/backward quote", err)
|
||||
end)
|
||||
|
||||
it("errors on unknown mark name with '", function()
|
||||
command("edit " .. file1)
|
||||
local err = pcall_err(helpers.exec_capture, "normal! '#")
|
||||
eq("Vim(normal):E78: Unknown mark", err)
|
||||
end)
|
||||
|
||||
it("errors on unknown mark name with `", function()
|
||||
command("edit " .. file1)
|
||||
local err = pcall_err(helpers.exec_capture, "normal! `#")
|
||||
eq("Vim(normal):E78: Unknown mark", err)
|
||||
end)
|
||||
|
||||
it("errors when moving to a mark that is not set with '", function()
|
||||
command("edit " .. file1)
|
||||
local err = pcall_err(helpers.exec_capture, "normal! 'z")
|
||||
eq("Vim(normal):E20: Mark not set", err)
|
||||
err = pcall_err(helpers.exec_capture, "normal! '.")
|
||||
eq("Vim(normal):E20: Mark not set", err)
|
||||
end)
|
||||
|
||||
it("errors when moving to a mark that is not set with `", function()
|
||||
command("edit " .. file1)
|
||||
local err = pcall_err(helpers.exec_capture, "normal! `z")
|
||||
eq("Vim(normal):E20: Mark not set", err)
|
||||
err = pcall_err(helpers.exec_capture, "normal! `>")
|
||||
eq("Vim(normal):E20: Mark not set", err)
|
||||
end)
|
||||
|
||||
it("errors when moving to a global mark that is not set with '", function()
|
||||
command("edit " .. file1)
|
||||
local err = pcall_err(helpers.exec_capture, "normal! 'Z")
|
||||
eq("Vim(normal):E20: Mark not set", err)
|
||||
end)
|
||||
|
||||
it("errors when moving to a global mark that is not set with `", function()
|
||||
command("edit " .. file1)
|
||||
local err = pcall_err(helpers.exec_capture, "normal! `Z")
|
||||
eq("Vim(normal):E20: Mark not set", err)
|
||||
end)
|
||||
|
||||
it("can move to them using '", function()
|
||||
command("args " .. file1 .. " " .. file2)
|
||||
feed("j")
|
||||
feed("ma")
|
||||
feed("G'a")
|
||||
eq({2, 0}, cursor())
|
||||
feed("mA")
|
||||
command("next")
|
||||
feed("'A")
|
||||
eq(1, meths.get_current_buf().id)
|
||||
eq({2, 0}, cursor())
|
||||
end)
|
||||
|
||||
it("can move to them using `", function()
|
||||
command("args " .. file1 .. " " .. file2)
|
||||
feed("jll")
|
||||
feed("ma")
|
||||
feed("G`a")
|
||||
eq({2, 2}, cursor())
|
||||
feed("mA")
|
||||
command("next")
|
||||
feed("`A")
|
||||
eq(1, meths.get_current_buf().id)
|
||||
eq({2, 2}, cursor())
|
||||
end)
|
||||
|
||||
it("can move to them using g'", function()
|
||||
command("args " .. file1 .. " " .. file2)
|
||||
feed("jll")
|
||||
feed("ma")
|
||||
feed("Gg'a")
|
||||
eq({2, 0}, cursor())
|
||||
feed("mA")
|
||||
command("next")
|
||||
feed("g'A")
|
||||
eq(1, meths.get_current_buf().id)
|
||||
eq({2, 0}, cursor())
|
||||
end)
|
||||
|
||||
it("can move to them using g`", function()
|
||||
command("args " .. file1 .. " " .. file2)
|
||||
feed("jll")
|
||||
feed("ma")
|
||||
feed("Gg`a")
|
||||
eq({2, 2}, cursor())
|
||||
feed("mA")
|
||||
command("next")
|
||||
feed("g`A")
|
||||
eq(1, meths.get_current_buf().id)
|
||||
eq({2, 2}, cursor())
|
||||
end)
|
||||
|
||||
it("errors when it can't find the buffer", function()
|
||||
command("args " .. file1 .. " " .. file2)
|
||||
feed("mA")
|
||||
command("next")
|
||||
command("bw! " .. file1 )
|
||||
local err = pcall_err(helpers.exec_capture, "normal! 'A")
|
||||
eq("Vim(normal):E92: Buffer 1 not found", err)
|
||||
os.remove(file1)
|
||||
end)
|
||||
|
||||
it("leave a context mark when moving with '", function()
|
||||
command("edit " .. file1)
|
||||
feed("llmamA")
|
||||
feed("10j0") -- first col, last line
|
||||
local pos = cursor()
|
||||
feed("'a")
|
||||
feed("<C-o>")
|
||||
eq(pos, cursor())
|
||||
feed("'A")
|
||||
feed("<C-o>")
|
||||
eq(pos, cursor())
|
||||
end)
|
||||
|
||||
it("leave a context mark when moving with `", function()
|
||||
command("edit " .. file1)
|
||||
feed("llmamA")
|
||||
feed("10j0") -- first col, last line
|
||||
local pos = cursor()
|
||||
feed("`a")
|
||||
feed("<C-o>")
|
||||
eq(pos, cursor())
|
||||
feed("`A")
|
||||
feed("<C-o>")
|
||||
eq(pos, cursor())
|
||||
end)
|
||||
|
||||
it("leave a context mark when the mark changes buffer with g'", function()
|
||||
command("args " .. file1 .. " " .. file2)
|
||||
local pos
|
||||
feed("GmA")
|
||||
command("next")
|
||||
pos = cursor()
|
||||
command("clearjumps")
|
||||
feed("g'A") -- since the mark is in another buffer, it leaves a context mark
|
||||
feed("<C-o>")
|
||||
eq(pos, cursor())
|
||||
end)
|
||||
|
||||
it("leave a context mark when the mark changes buffer with g`", function()
|
||||
command("args " .. file1 .. " " .. file2)
|
||||
local pos
|
||||
feed("GmA")
|
||||
command("next")
|
||||
pos = cursor()
|
||||
command("clearjumps")
|
||||
feed("g`A") -- since the mark is in another buffer, it leaves a context mark
|
||||
feed("<C-o>")
|
||||
eq(pos, cursor())
|
||||
end)
|
||||
|
||||
it("do not leave a context mark when moving with g'", function()
|
||||
command("edit " .. file1)
|
||||
local pos
|
||||
feed("ma")
|
||||
pos = cursor() -- Mark pos
|
||||
feed("10j0") -- first col, last line
|
||||
feed("g'a")
|
||||
feed("<C-o>") -- should do nothing
|
||||
eq(pos, cursor())
|
||||
feed("mA")
|
||||
pos = cursor() -- Mark pos
|
||||
feed("10j0") -- first col, last line
|
||||
feed("g'a")
|
||||
feed("<C-o>") -- should do nothing
|
||||
eq(pos, cursor())
|
||||
end)
|
||||
|
||||
it("do not leave a context mark when moving with g`", function()
|
||||
command("edit " .. file1)
|
||||
local pos
|
||||
feed("ma")
|
||||
pos = cursor() -- Mark pos
|
||||
feed("10j0") -- first col, last line
|
||||
feed("g`a")
|
||||
feed("<C-o>") -- should do nothing
|
||||
eq(pos, cursor())
|
||||
feed("mA")
|
||||
pos = cursor() -- Mark pos
|
||||
feed("10j0") -- first col, last line
|
||||
feed("g'a")
|
||||
feed("<C-o>") -- should do nothing
|
||||
eq(pos, cursor())
|
||||
end)
|
||||
|
||||
it("open folds when moving to them", function()
|
||||
command("edit " .. file1)
|
||||
feed("jzfG") -- Fold from the second line to the end
|
||||
command("3mark a")
|
||||
feed("G") -- On top of the fold
|
||||
assert(funcs.foldclosed('.') ~= -1) -- folded
|
||||
feed("'a")
|
||||
eq(-1, funcs.foldclosed('.'))
|
||||
|
||||
feed("zc")
|
||||
assert(funcs.foldclosed('.') ~= -1) -- folded
|
||||
-- TODO: remove this workaround after fixing #15873
|
||||
feed("k`a")
|
||||
eq(-1, funcs.foldclosed('.'))
|
||||
|
||||
feed("zc")
|
||||
assert(funcs.foldclosed('.') ~= -1) -- folded
|
||||
feed("kg'a")
|
||||
eq(-1, funcs.foldclosed('.'))
|
||||
|
||||
feed("zc")
|
||||
assert(funcs.foldclosed('.') ~= -1) -- folded
|
||||
feed("kg`a")
|
||||
eq(-1, funcs.foldclosed('.'))
|
||||
end)
|
||||
|
||||
it("do not open folds when moving to them doesn't move the cursor", function()
|
||||
command("edit " .. file1)
|
||||
feed("jzfG") -- Fold from the second line to the end
|
||||
assert(funcs.foldclosed('.') == 2) -- folded
|
||||
feed("ma")
|
||||
feed("'a")
|
||||
feed("`a")
|
||||
feed("g'a")
|
||||
feed("g`a")
|
||||
-- should still be folded
|
||||
eq(2, funcs.foldclosed('.'))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('named marks view', function()
|
||||
local file1 = 'Xtestfile-functional-editor-marks'
|
||||
local file2 = 'Xtestfile-functional-editor-marks-2'
|
||||
local function content()
|
||||
local c = {}
|
||||
for i=1,30 do
|
||||
c[i] = i .. " line"
|
||||
end
|
||||
return table.concat(c, "\n")
|
||||
end
|
||||
before_each(function()
|
||||
clear()
|
||||
write_file(file1, content(), false, false)
|
||||
write_file(file2, content(), false, false)
|
||||
command("set jumpoptions+=view")
|
||||
end)
|
||||
after_each(function()
|
||||
os.remove(file1)
|
||||
os.remove(file2)
|
||||
end)
|
||||
|
||||
it('is restored', function()
|
||||
local screen = Screen.new(5, 8)
|
||||
screen:attach()
|
||||
command("edit " .. file1)
|
||||
feed("<C-e>jWma")
|
||||
feed("G'a")
|
||||
local expected = [[
|
||||
2 line |
|
||||
^3 line |
|
||||
4 line |
|
||||
5 line |
|
||||
6 line |
|
||||
7 line |
|
||||
8 line |
|
||||
|
|
||||
]]
|
||||
screen:expect({grid=expected})
|
||||
feed("G`a")
|
||||
screen:expect([[
|
||||
2 line |
|
||||
3 ^line |
|
||||
4 line |
|
||||
5 line |
|
||||
6 line |
|
||||
7 line |
|
||||
8 line |
|
||||
|
|
||||
]])
|
||||
end)
|
||||
|
||||
it('is restored across files', function()
|
||||
local screen = Screen.new(5, 5)
|
||||
screen:attach()
|
||||
command("args " .. file1 .. " " .. file2)
|
||||
feed("<C-e>mA")
|
||||
local mark_view = [[
|
||||
^2 line |
|
||||
3 line |
|
||||
4 line |
|
||||
5 line |
|
||||
|
|
||||
]]
|
||||
screen:expect(mark_view)
|
||||
command("next")
|
||||
screen:expect([[
|
||||
^1 line |
|
||||
2 line |
|
||||
3 line |
|
||||
4 line |
|
||||
|
|
||||
]])
|
||||
feed("'A")
|
||||
screen:expect(mark_view)
|
||||
end)
|
||||
|
||||
it('fallback to standard behavior when view can\'t be recovered', function()
|
||||
local screen = Screen.new(10, 10)
|
||||
screen:attach()
|
||||
command("edit " .. file1)
|
||||
feed("7GzbmaG") -- Seven lines from the top
|
||||
command("new") -- Screen size for window is now half the height can't be restored
|
||||
feed("<C-w>p'a")
|
||||
screen:expect([[
|
||||
|
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
[No Name] |
|
||||
6 line |
|
||||
^7 line |
|
||||
8 line |
|
||||
{MATCH:.*} |
|
||||
|
|
||||
]])
|
||||
end)
|
||||
end)
|
Reference in New Issue
Block a user