viminfo: First version of ShaDa file dumping

What works:

1. ShaDa file dumping: header, registers, jump list, history, search patterns,
   substitute strings, variables.
2. ShaDa file reading: registers, global marks, variables.

Most was not tested.

TODO:

1. Merging.
2. Reading history, local marks, jump and buffer lists.
3. Documentation update.
4. Converting some data from &encoding.
5. Safer variant of dumping viminfo (dump to temporary file then rename).
6. Removing old viminfo code (currently masked with `#if 0` in a ShaDa file for
   reference).
This commit is contained in:
ZyX 2015-04-25 18:47:31 +03:00
parent 0fdaab995e
commit 244dbe3a77
38 changed files with 4511 additions and 1848 deletions

View File

@ -9,7 +9,7 @@ def DirectoryOfThisScript():
def GetDatabase():
compilation_database_folder = os.path.join(DirectoryOfThisScript(),
'..', 'build')
'..', '..', 'build')
if os.path.exists(compilation_database_folder):
return ycm_core.CompilationDatabase(compilation_database_folder)
return None

View File

@ -6770,10 +6770,14 @@ A jump table for the options with a short description can be found at |Q_op|.
e.g., for Unix: "r/tmp". Case is ignored. Maximum length of
each 'r' argument is 50 characters.
*viminfo-s*
s Maximum size of an item in Kbyte. If zero then registers are
not saved. Currently only applies to registers. The default
"s10" will exclude registers with more than 10 Kbyte of text.
Also see the '<' item above: line count limit.
s Maximum size of an item contents in KiB. If zero then nothing
is saved. Unlike Vim this applies to all items, except for
the buffer list and header. Full item size is off by three
unsigned integers: with `s10` maximum item size may be 1 byte
(type: 7-bit integer) + 9 bytes (timestamp: up to 64-bit
integer) + 3 bytes (item size: up to 16-bit integer because
2^8 < 10240 < 2^16) + 10240 bytes (requested maximum item
contents size) = 10253 bytes.
Example: >
:set viminfo='50,<1000,s100,:0,n~/vim/viminfo
@ -6782,7 +6786,8 @@ A jump table for the options with a short description can be found at |Q_op|.
edited.
<1000 Contents of registers (up to 1000 lines each) will be
remembered.
s100 Registers with more than 100 Kbyte text are skipped.
s100 Items with contents occupying more then 100 KiB are
skipped.
:0 Command-line history will not be saved.
n~/vim/viminfo The name of the file to use is "~/vim/viminfo".
no / Since '/' is not specified, the default will be used,

73
scripts/shadacat.py Executable file
View File

@ -0,0 +1,73 @@
#!/usr/bin/env python3.4
import sys
import codecs
from enum import Enum
from datetime import datetime
from functools import reduce
import msgpack
class EntryTypes(Enum):
Unknown = -1
Missing = 0
Header = 1
SearchPattern = 2
SubString = 3
HistoryEntry = 4
Register = 5
Variable = 6
GlobalMark = 7
Jump = 8
BufferList = 9
LocalMark = 10
def strtrans_errors(e):
if not isinstance(e, UnicodeDecodeError):
raise NotImplementedError('dont know how to handle {0} error'.format(
e.__class__.__name__))
return '<{0:x}>'.format(reduce((lambda a, b: a*0x100+b),
list(e.object[e.start:e.end]))), e.end
codecs.register_error('strtrans', strtrans_errors)
def idfunc(o):
return o
class CharInt(int):
def __repr__(self):
return super(CharInt, self).__repr__() + ' (\'%s\')' % chr(self)
ctable = {
bytes: lambda s: s.decode('utf-8', 'strtrans'),
dict: lambda d: dict((mnormalize(k), mnormalize(v)) for k, v in d.items()),
list: lambda l: list(mnormalize(i) for i in l),
int: lambda n: CharInt(n) if 0x20 <= n <= 0x7E else n,
}
def mnormalize(o):
return ctable.get(type(o), idfunc)(o)
with open(sys.argv[1], 'rb') as fp:
unpacker = msgpack.Unpacker(file_like=fp)
while True:
try:
typ = EntryTypes(unpacker.unpack())
except msgpack.OutOfData:
break
else:
timestamp = unpacker.unpack()
time = datetime.fromtimestamp(timestamp)
length = unpacker.unpack()
entry = unpacker.unpack()
print('{0:13} {1} {2:5} {3!r}'.format(
typ.name, time.isoformat(), length, mnormalize(entry)))

View File

@ -69,6 +69,8 @@
#define ADD(array, item) \
kv_push(Object, array, item)
#define STATIC_CSTR_AS_STRING(s) ((String) {.data = s, .size = sizeof(s) - 1})
// Helpers used by the generated msgpack-rpc api wrappers
#define api_init_boolean
#define api_init_integer

View File

@ -84,7 +84,7 @@ return {
'User', -- user defined autocommand
'VimEnter', -- after starting Vim
'VimLeave', -- before exiting Vim
'VimLeavePre', -- before exiting Vim and writing .viminfo
'VimLeavePre', -- before exiting Vim and writing ShaDa file
'VimResized', -- after Vim window was resized
'WinEnter', -- after entering a window
'WinLeave', -- before leaving a window

View File

@ -73,6 +73,7 @@
#include "nvim/undo.h"
#include "nvim/version.h"
#include "nvim/window.h"
#include "nvim/shada.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
#include "nvim/os/input.h"
@ -555,6 +556,12 @@ static void free_buffer(buf_T *buf)
free_buffer_stuff(buf, TRUE);
unref_var_dict(buf->b_vars);
aubuflocal_remove(buf);
free_fmark(buf->b_last_cursor);
free_fmark(buf->b_last_insert);
free_fmark(buf->b_last_change);
for (size_t i = 0; i < NMARKS; i++) {
free_fmark(buf->b_namedm[i]);
}
if (autocmd_busy) {
// Do not free the buffer structure while autocommands are executing,
// it's still needed. Free it when autocmd_busy is reset.
@ -4164,93 +4171,6 @@ chk_modeline (
return retval;
}
int read_viminfo_bufferlist(vir_T *virp, int writing)
{
char_u *tab;
linenr_T lnum;
colnr_T col;
buf_T *buf;
char_u *sfname;
char_u *xline;
/* Handle long line and escaped characters. */
xline = viminfo_readstring(virp, 1, FALSE);
/* don't read in if there are files on the command-line or if writing: */
if (xline != NULL && !writing && ARGCOUNT == 0
&& find_viminfo_parameter('%') != NULL) {
/* Format is: <fname> Tab <lnum> Tab <col>.
* Watch out for a Tab in the file name, work from the end. */
lnum = 0;
col = 0;
tab = vim_strrchr(xline, '\t');
if (tab != NULL) {
*tab++ = '\0';
col = (colnr_T)atoi((char *)tab);
tab = vim_strrchr(xline, '\t');
if (tab != NULL) {
*tab++ = '\0';
lnum = atol((char *)tab);
}
}
/* Expand "~/" in the file name at "line + 1" to a full path.
* Then try shortening it by comparing with the current directory */
expand_env(xline, NameBuff, MAXPATHL);
sfname = path_shorten_fname_if_possible(NameBuff);
buf = buflist_new(NameBuff, sfname, (linenr_T)0, BLN_LISTED);
if (buf != NULL) { /* just in case... */
buf->b_last_cursor.lnum = lnum;
buf->b_last_cursor.col = col;
buflist_setfpos(buf, curwin, lnum, col, FALSE);
}
}
xfree(xline);
return viminfo_readline(virp);
}
void write_viminfo_bufferlist(FILE *fp)
{
char_u *line;
int max_buffers;
if (find_viminfo_parameter('%') == NULL)
return;
/* Without a number -1 is returned: do all buffers. */
max_buffers = get_viminfo_parameter('%');
/* Allocate room for the file name, lnum and col. */
#define LINE_BUF_LEN (MAXPATHL + 40)
line = xmalloc(LINE_BUF_LEN);
FOR_ALL_TAB_WINDOWS(tp, win) {
set_last_cursor(win);
}
fputs(_("\n# Buffer list:\n"), fp);
FOR_ALL_BUFFERS(buf) {
if (buf->b_fname == NULL
|| !buf->b_p_bl
|| bt_quickfix(buf)
|| removable(buf->b_ffname))
continue;
if (max_buffers-- == 0)
break;
putc('%', fp);
home_replace(NULL, buf->b_ffname, line, MAXPATHL, TRUE);
vim_snprintf_add((char *)line, LINE_BUF_LEN, "\t%" PRId64 "\t%d",
(int64_t)buf->b_last_cursor.lnum,
buf->b_last_cursor.col);
viminfo_writestring(fp, line);
}
xfree(line);
}
/*
* Return special buffer name.
* Returns NULL when the buffer has a normal file name.

View File

@ -327,15 +327,6 @@ typedef struct {
bool vc_fail; /* fail for invalid char, don't use '?' */
} vimconv_T;
/*
* Structure used for reading from the viminfo file.
*/
typedef struct {
char_u *vir_line; /* text of the current line */
FILE *vir_fd; /* file descriptor */
vimconv_T vir_conv; /* encoding conversion */
} vir_T;
#define CONV_NONE 0
#define CONV_TO_UTF8 1
#define CONV_9_TO_UTF8 2
@ -515,16 +506,16 @@ struct file_buffer {
uint64_t b_orig_size; /* size of original file in bytes */
int b_orig_mode; /* mode of original file */
pos_T b_namedm[NMARKS]; /* current named marks (mark.c) */
fmark_T b_namedm[NMARKS]; /* current named marks (mark.c) */
/* These variables are set when VIsual_active becomes FALSE */
visualinfo_T b_visual;
int b_visual_mode_eval; /* b_visual.vi_mode for visualmode() */
pos_T b_last_cursor; /* cursor position when last unloading this
fmark_T b_last_cursor; /* cursor position when last unloading this
buffer */
pos_T b_last_insert; /* where Insert mode was left */
pos_T b_last_change; /* position of last change: '. mark */
fmark_T b_last_insert; /* where Insert mode was left */
fmark_T b_last_change; /* position of last change: '. mark */
/*
* the changelist contains old change positions
@ -553,7 +544,7 @@ struct file_buffer {
pos_T b_op_start_orig; // used for Insstart_orig
pos_T b_op_end;
bool b_marks_read; /* Have we read viminfo marks yet? */
bool b_marks_read; /* Have we read ShaDa marks yet? */
/*
* The following only used in undo.c.

View File

@ -60,6 +60,7 @@
#include "nvim/undo.h"
#include "nvim/window.h"
#include "nvim/event/loop.h"
#include "nvim/mark.h"
#include "nvim/os/input.h"
#include "nvim/os/time.h"
@ -6991,8 +6992,9 @@ ins_esc (
curwin->w_set_curswant = TRUE;
/* Remember the last Insert position in the '^ mark. */
if (!cmdmod.keepjumps)
curbuf->b_last_insert = curwin->w_cursor;
if (!cmdmod.keepjumps) {
RESET_FMARK(&curbuf->b_last_insert, curwin->w_cursor, curbuf->b_fnum);
}
/*
* The cursor should end up on the last inserted character.

View File

@ -354,7 +354,7 @@ typedef struct {
typedef enum {
VAR_FLAVOUR_DEFAULT, /* doesn't start with uppercase */
VAR_FLAVOUR_SESSION, /* starts with uppercase, some lower */
VAR_FLAVOUR_VIMINFO /* all uppercase */
VAR_FLAVOUR_SHADA /* all uppercase */
} var_flavour_T;
/* values for vv_flags: */
@ -10482,6 +10482,7 @@ static void f_has(typval_T *argvars, typval_T *rettv)
"scrollbind",
"showcmd",
"cmdline_info",
"shada",
"signs",
"smartindent",
"startuptime",
@ -10498,7 +10499,6 @@ static void f_has(typval_T *argvars, typval_T *rettv)
"title",
"user-commands", /* was accidentally included in 5.4 */
"user_commands",
"viminfo",
"vertsplit",
"virtualedit",
"visual",
@ -20712,107 +20712,62 @@ static var_flavour_T var_flavour(char_u *varname)
while (*(++p))
if (ASCII_ISLOWER(*p))
return VAR_FLAVOUR_SESSION;
return VAR_FLAVOUR_VIMINFO;
return VAR_FLAVOUR_SHADA;
} else
return VAR_FLAVOUR_DEFAULT;
}
/*
* Restore global vars that start with a capital from the viminfo file
*/
int read_viminfo_varlist(vir_T *virp, int writing)
/// Iterate over global variables
///
/// @warning No modifications to global variable dictionary must be performed
/// while iteration is in progress.
///
/// @param[in] iter Iterator. Pass NULL to start iteration.
/// @param[out] name Variable name.
/// @param[out] rettv Variable value.
///
/// @return Pointer that needs to be passed to next `var_shada_iter` invocation
/// or NULL to indicate that iteration is over.
const void *var_shada_iter(const void *const iter, const char **const name,
typval_T *rettv)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(2, 3)
{
char_u *tab;
int type = VAR_NUMBER;
typval_T tv;
if (!writing && (find_viminfo_parameter('!') != NULL)) {
tab = vim_strchr(virp->vir_line + 1, '\t');
if (tab != NULL) {
*tab++ = NUL; /* isolate the variable name */
switch (*tab) {
case 'S': type = VAR_STRING; break;
case 'F': type = VAR_FLOAT; break;
case 'D': type = VAR_DICT; break;
case 'L': type = VAR_LIST; break;
}
tab = vim_strchr(tab, '\t');
if (tab != NULL) {
tv.v_type = type;
if (type == VAR_STRING || type == VAR_DICT || type == VAR_LIST)
tv.vval.v_string = viminfo_readstring(virp,
(int)(tab - virp->vir_line + 1), TRUE);
else if (type == VAR_FLOAT)
(void)string2float(tab + 1, &tv.vval.v_float);
else
tv.vval.v_number = atol((char *)tab + 1);
if (type == VAR_DICT || type == VAR_LIST) {
typval_T *etv = eval_expr(tv.vval.v_string, NULL);
if (etv == NULL)
/* Failed to parse back the dict or list, use it as a
* string. */
tv.v_type = VAR_STRING;
else {
xfree(tv.vval.v_string);
tv = *etv;
xfree(etv);
}
}
set_var(virp->vir_line + 1, &tv, FALSE);
if (tv.v_type == VAR_STRING)
xfree(tv.vval.v_string);
else if (tv.v_type == VAR_DICT || tv.v_type == VAR_LIST)
clear_tv(&tv);
}
const hashitem_T *hi;
const hashitem_T *hifirst = globvarht.ht_array;
const size_t hinum = (size_t) globvarht.ht_mask + 1;
*name = NULL;
if (iter == NULL) {
hi = globvarht.ht_array;
while ((HASHITEM_EMPTY(hi)
|| var_flavour(HI2DI(hi)->di_key) != VAR_FLAVOUR_SHADA)
&& (size_t) (hi - hifirst) < hinum) {
hi++;
}
if (HASHITEM_EMPTY(hi)
|| var_flavour(HI2DI(hi)->di_key) != VAR_FLAVOUR_SHADA) {
*rettv = (typval_T) {.v_type = VAR_UNKNOWN};
return NULL;
}
} else {
hi = (const hashitem_T *) iter;
}
*name = (char *) HI2DI(hi)->di_key;
copy_tv(&(HI2DI(hi)->di_tv), rettv);
while ((size_t) (++hi - hifirst) < hinum) {
if (!HASHITEM_EMPTY(hi)
&& var_flavour(HI2DI(hi)->di_key) == VAR_FLAVOUR_SHADA) {
return hi;
}
}
return viminfo_readline(virp);
return NULL;
}
/*
* Write global vars that start with a capital to the viminfo file
*/
void write_viminfo_varlist(FILE *fp)
void var_set_global(const char *const name, typval_T vartv)
{
hashitem_T *hi;
dictitem_T *this_var;
int todo;
char *s;
char_u *p;
if (find_viminfo_parameter('!') == NULL)
return;
fputs(_("\n# global variables:\n"), fp);
todo = (int)globvarht.ht_used;
for (hi = globvarht.ht_array; todo > 0; ++hi) {
if (!HASHITEM_EMPTY(hi)) {
--todo;
this_var = HI2DI(hi);
if (var_flavour(this_var->di_key) == VAR_FLAVOUR_VIMINFO) {
switch (this_var->di_tv.v_type) {
case VAR_STRING: s = "STR"; break;
case VAR_NUMBER: s = "NUM"; break;
case VAR_FLOAT: s = "FLO"; break;
case VAR_DICT: s = "DIC"; break;
case VAR_LIST: s = "LIS"; break;
default: continue;
}
fprintf(fp, "!%s\t%s\t", this_var->di_key, s);
p = (char_u *) echo_string(&this_var->di_tv, NULL);
if (p != NULL) {
viminfo_writestring(fp, p);
}
xfree(p);
}
}
}
funccall_T *const saved_current_funccal = current_funccal;
current_funccal = NULL;
set_var((char_u *) name, &vartv, false);
current_funccal = saved_current_funccal;
}
int store_session_globals(FILE *fd)

View File

@ -67,6 +67,9 @@
#include "nvim/os/os.h"
#include "nvim/os/shell.h"
#include "nvim/os/input.h"
#include "nvim/os/time.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
/*
* Struct to hold the sign properties.
@ -1391,550 +1394,6 @@ void append_redir(char_u *buf, int buflen, char_u *opt, char_u *fname)
(char *)opt, (char *)fname);
}
static int viminfo_errcnt;
static int no_viminfo(void)
{
/* "vim -i NONE" does not read or write a viminfo file */
return use_viminfo != NULL && STRCMP(use_viminfo, "NONE") == 0;
}
/*
* Report an error for reading a viminfo file.
* Count the number of errors. When there are more than 10, return TRUE.
*/
int viminfo_error(char *errnum, char *message, char_u *line)
{
vim_snprintf((char *)IObuff, IOSIZE, _("%sviminfo: %s in line: "),
errnum, message);
STRNCAT(IObuff, line, IOSIZE - STRLEN(IObuff) - 1);
if (IObuff[STRLEN(IObuff) - 1] == '\n')
IObuff[STRLEN(IObuff) - 1] = NUL;
emsg(IObuff);
if (++viminfo_errcnt >= 10) {
EMSG(_("E136: viminfo: Too many errors, skipping rest of file"));
return TRUE;
}
return FALSE;
}
/*
* read_viminfo() -- Read the viminfo file. Registers etc. which are already
* set are not over-written unless "flags" includes VIF_FORCEIT. -- webb
*/
int
read_viminfo (
char_u *file, /* file name or NULL to use default name */
int flags /* VIF_WANT_INFO et al. */
)
{
FILE *fp;
char_u *fname;
if (no_viminfo())
return FAIL;
fname = viminfo_filename(file); /* get file name in allocated buffer */
fp = mch_fopen((char *)fname, READBIN);
if (p_verbose > 0) {
verbose_enter();
smsg(_("Reading viminfo file \"%s\"%s%s%s"),
fname,
(flags & VIF_WANT_INFO) ? _(" info") : "",
(flags & VIF_WANT_MARKS) ? _(" marks") : "",
(flags & VIF_GET_OLDFILES) ? _(" oldfiles") : "",
fp == NULL ? _(" FAILED") : "");
verbose_leave();
}
xfree(fname);
if (fp == NULL)
return FAIL;
viminfo_errcnt = 0;
do_viminfo(fp, NULL, flags);
fclose(fp);
return OK;
}
/*
* Write the viminfo file. The old one is read in first so that effectively a
* merge of current info and old info is done. This allows multiple vims to
* run simultaneously, without losing any marks etc.
* If "forceit" is TRUE, then the old file is not read in, and only internal
* info is written to the file.
*/
void write_viminfo(char_u *file, int forceit)
{
char_u *fname;
FILE *fp_in = NULL; /* input viminfo file, if any */
FILE *fp_out = NULL; /* output viminfo file */
char_u *tempname = NULL; /* name of temp viminfo file */
char_u *wp;
#if defined(UNIX)
mode_t umask_save;
#endif
if (no_viminfo())
return;
fname = viminfo_filename(file); /* may set to default if NULL */
fp_in = mch_fopen((char *)fname, READBIN);
if (fp_in == NULL) {
/* if it does exist, but we can't read it, don't try writing */
if (os_file_exists(fname))
goto end;
#if defined(UNIX)
/*
* For Unix we create the .viminfo non-accessible for others,
* because it may contain text from non-accessible documents.
*/
umask_save = umask(077);
#endif
fp_out = mch_fopen((char *)fname, WRITEBIN);
#if defined(UNIX)
(void)umask(umask_save);
#endif
} else {
/*
* There is an existing viminfo file. Create a temporary file to
* write the new viminfo into, in the same directory as the
* existing viminfo file, which will be renamed later.
*/
#ifdef UNIX
/*
* For Unix we check the owner of the file. It's not very nice to
* overwrite a user's viminfo file after a "su root", with a
* viminfo file that the user can't read.
*/
FileInfo old_info; // FileInfo of existing viminfo file
if (os_fileinfo((char *)fname, &old_info)
&& getuid() != ROOT_UID
&& !(old_info.stat.st_uid == getuid()
? (old_info.stat.st_mode & 0200)
: (old_info.stat.st_gid == getgid()
? (old_info.stat.st_mode & 0020)
: (old_info.stat.st_mode & 0002)))) {
int tt = msg_didany;
/* avoid a wait_return for this message, it's annoying */
EMSG2(_("E137: Viminfo file is not writable: %s"), fname);
msg_didany = tt;
fclose(fp_in);
goto end;
}
#endif
// Make tempname
tempname = (char_u *)modname((char *)fname, ".tmp", FALSE);
if (tempname != NULL) {
/*
* Check if tempfile already exists. Never overwrite an
* existing file!
*/
if (os_file_exists(tempname)) {
/*
* Try another name. Change one character, just before
* the extension.
*/
wp = tempname + STRLEN(tempname) - 5;
if (wp < path_tail(tempname)) /* empty file name? */
wp = path_tail(tempname);
for (*wp = 'z'; os_file_exists(tempname); --*wp) {
/*
* They all exist? Must be something wrong! Don't
* write the viminfo file then.
*/
if (*wp == 'a') {
xfree(tempname);
tempname = NULL;
break;
}
}
}
}
if (tempname != NULL) {
int fd;
/* Use os_open() to be able to use O_NOFOLLOW and set file
* protection:
* Unix: same as original file, but strip s-bit. Reset umask to
* avoid it getting in the way.
* Others: r&w for user only. */
# ifdef UNIX
umask_save = umask(0);
fd = os_open((char *)tempname,
O_CREAT|O_EXCL|O_WRONLY|O_NOFOLLOW,
(int)((old_info.stat.st_mode & 0777) | 0600));
(void)umask(umask_save);
# else
fd = os_open((char *)tempname,
O_CREAT|O_EXCL|O_WRONLY|O_NOFOLLOW, 0600);
# endif
if (fd < 0)
fp_out = NULL;
else
fp_out = fdopen(fd, WRITEBIN);
/*
* If we can't create in the same directory, try creating a
* "normal" temp file.
*/
if (fp_out == NULL) {
xfree(tempname);
if ((tempname = vim_tempname()) != NULL)
fp_out = mch_fopen((char *)tempname, WRITEBIN);
}
#ifdef UNIX
/*
* Make sure the owner can read/write it. This only works for
* root.
*/
if (fp_out != NULL) {
os_fchown(fileno(fp_out), old_info.stat.st_uid, old_info.stat.st_gid);
}
#endif
}
}
/*
* Check if the new viminfo file can be written to.
*/
if (fp_out == NULL) {
EMSG2(_("E138: Can't write viminfo file %s!"),
(fp_in == NULL || tempname == NULL) ? fname : tempname);
if (fp_in != NULL)
fclose(fp_in);
goto end;
}
if (p_verbose > 0) {
verbose_enter();
smsg(_("Writing viminfo file \"%s\""), fname);
verbose_leave();
}
viminfo_errcnt = 0;
do_viminfo(fp_in, fp_out, forceit ? 0 : (VIF_WANT_INFO | VIF_WANT_MARKS));
fclose(fp_out); /* errors are ignored !? */
if (fp_in != NULL) {
fclose(fp_in);
/* In case of an error keep the original viminfo file. Otherwise
* rename the newly written file. Give an error if that fails. */
if (viminfo_errcnt == 0 && vim_rename(tempname, fname) == -1) {
viminfo_errcnt++;
EMSG2(_("E886: Can't rename viminfo file to %s!"), fname);
}
if (viminfo_errcnt > 0) {
os_remove((char *)tempname);
}
}
end:
xfree(fname);
xfree(tempname);
}
/*
* Get the viminfo file name to use.
* If "file" is given and not empty, use it (has already been expanded by
* cmdline functions).
* Otherwise use "-i file_name", value from 'viminfo' or the default, and
* expand environment variables.
* Returns an allocated string.
*/
static char_u *viminfo_filename(char_u *file)
{
if (file == NULL || *file == NUL) {
if (use_viminfo != NULL)
file = use_viminfo;
else if ((file = find_viminfo_parameter('n')) == NULL || *file == NUL) {
#ifdef VIMINFO_FILE2
// don't use $HOME when not defined (turned into "c:/"!).
if (!os_env_exists("HOME")) {
// don't use $VIM when not available.
expand_env((char_u *)"$VIM", NameBuff, MAXPATHL);
if (STRCMP("$VIM", NameBuff) != 0) /* $VIM was expanded */
file = (char_u *)VIMINFO_FILE2;
else
file = (char_u *)VIMINFO_FILE;
} else
#endif
file = (char_u *)VIMINFO_FILE;
}
expand_env(file, NameBuff, MAXPATHL);
file = NameBuff;
}
return vim_strsave(file);
}
/*
* do_viminfo() -- Should only be called from read_viminfo() & write_viminfo().
*/
static void do_viminfo(FILE *fp_in, FILE *fp_out, int flags)
{
int count = 0;
int eof = FALSE;
vir_T vir;
int merge = FALSE;
vir.vir_line = xmalloc(LSIZE);
vir.vir_fd = fp_in;
vir.vir_conv.vc_type = CONV_NONE;
if (fp_in != NULL) {
if (flags & VIF_WANT_INFO) {
eof = read_viminfo_up_to_marks(&vir,
flags & VIF_FORCEIT, fp_out != NULL);
merge = TRUE;
} else if (flags != 0)
/* Skip info, find start of marks */
while (!(eof = viminfo_readline(&vir))
&& vir.vir_line[0] != '>')
;
}
if (fp_out != NULL) {
/* Write the info: */
fprintf(fp_out, _("# This viminfo file was generated by Nvim %s.\n"),
mediumVersion);
fputs(_("# You may edit it if you're careful!\n\n"), fp_out);
fputs(_("# Value of 'encoding' when this file was written\n"), fp_out);
fprintf(fp_out, "*encoding=%s\n\n", p_enc);
write_viminfo_search_pattern(fp_out);
write_viminfo_sub_string(fp_out);
write_viminfo_history(fp_out, merge);
write_viminfo_registers(fp_out);
write_viminfo_varlist(fp_out);
write_viminfo_filemarks(fp_out);
write_viminfo_bufferlist(fp_out);
count = write_viminfo_marks(fp_out);
}
if (fp_in != NULL
&& (flags & (VIF_WANT_MARKS | VIF_GET_OLDFILES | VIF_FORCEIT)))
copy_viminfo_marks(&vir, fp_out, count, eof, flags);
xfree(vir.vir_line);
if (vir.vir_conv.vc_type != CONV_NONE)
convert_setup(&vir.vir_conv, NULL, NULL);
}
/*
* read_viminfo_up_to_marks() -- Only called from do_viminfo(). Reads in the
* first part of the viminfo file which contains everything but the marks that
* are local to a file. Returns TRUE when end-of-file is reached. -- webb
*/
static int read_viminfo_up_to_marks(vir_T *virp, int forceit, int writing)
{
int eof;
prepare_viminfo_history(forceit ? 9999 : 0, writing);
eof = viminfo_readline(virp);
while (!eof && virp->vir_line[0] != '>') {
switch (virp->vir_line[0]) {
/* Characters reserved for future expansion, ignored now */
case '+': /* "+40 /path/dir file", for running vim without args */
case '|': /* to be defined */
case '^': /* to be defined */
case '<': /* long line - ignored */
/* A comment or empty line. */
case NUL:
case '\r':
case '\n':
case '#':
eof = viminfo_readline(virp);
break;
case '*': /* "*encoding=value" */
eof = viminfo_encoding(virp);
break;
case '!': /* global variable */
eof = read_viminfo_varlist(virp, writing);
break;
case '%': /* entry for buffer list */
eof = read_viminfo_bufferlist(virp, writing);
break;
case '"':
eof = read_viminfo_register(virp, forceit);
break;
case '/': /* Search string */
case '&': /* Substitute search string */
case '~': /* Last search string, followed by '/' or '&' */
eof = read_viminfo_search_pattern(virp, forceit);
break;
case '$':
eof = read_viminfo_sub_string(virp, forceit);
break;
case ':':
case '?':
case '=':
case '@':
eof = read_viminfo_history(virp, writing);
break;
case '-':
case '\'':
eof = read_viminfo_filemark(virp, forceit);
break;
default:
if (viminfo_error("E575: ", _("Illegal starting char"),
virp->vir_line))
eof = TRUE;
else
eof = viminfo_readline(virp);
break;
}
}
/* Finish reading history items. */
if (!writing)
finish_viminfo_history();
/* Change file names to buffer numbers for fmarks. */
FOR_ALL_BUFFERS(buf) {
fmarks_check_names(buf);
}
return eof;
}
/*
* Compare the 'encoding' value in the viminfo file with the current value of
* 'encoding'. If different and the 'c' flag is in 'viminfo', setup for
* conversion of text with iconv() in viminfo_readstring().
*/
static int viminfo_encoding(vir_T *virp)
{
char_u *p;
int i;
if (get_viminfo_parameter('c') != 0) {
p = vim_strchr(virp->vir_line, '=');
if (p != NULL) {
/* remove trailing newline */
++p;
for (i = 0; vim_isprintc(p[i]); ++i)
;
p[i] = NUL;
convert_setup(&virp->vir_conv, p, p_enc);
}
}
return viminfo_readline(virp);
}
/*
* Read a line from the viminfo file.
* Returns TRUE for end-of-file;
*/
int viminfo_readline(vir_T *virp)
{
return vim_fgets(virp->vir_line, LSIZE, virp->vir_fd);
}
/*
* check string read from viminfo file
* remove '\n' at the end of the line
* - replace CTRL-V CTRL-V with CTRL-V
* - replace CTRL-V 'n' with '\n'
*
* Check for a long line as written by viminfo_writestring().
*
* Return the string in allocated memory.
*/
char_u *
viminfo_readstring (
vir_T *virp,
int off, /* offset for virp->vir_line */
int convert /* convert the string */
)
FUNC_ATTR_NONNULL_RET
{
char_u *retval;
char_u *s, *d;
if (virp->vir_line[off] == Ctrl_V && ascii_isdigit(virp->vir_line[off + 1])) {
ssize_t len = atol((char *)virp->vir_line + off + 1);
retval = xmalloc(len);
// TODO(philix): change type of vim_fgets() size argument to size_t
(void)vim_fgets(retval, (int)len, virp->vir_fd);
s = retval + 1; /* Skip the leading '<' */
} else {
retval = vim_strsave(virp->vir_line + off);
s = retval;
}
/* Change CTRL-V CTRL-V to CTRL-V and CTRL-V n to \n in-place. */
d = retval;
while (*s != NUL && *s != '\n') {
if (s[0] == Ctrl_V && s[1] != NUL) {
if (s[1] == 'n')
*d++ = '\n';
else
*d++ = Ctrl_V;
s += 2;
} else
*d++ = *s++;
}
*d = NUL;
if (convert && virp->vir_conv.vc_type != CONV_NONE && *retval != NUL) {
d = string_convert(&virp->vir_conv, retval, NULL);
if (d != NULL) {
xfree(retval);
retval = d;
}
}
return retval;
}
/*
* write string to viminfo file
* - replace CTRL-V with CTRL-V CTRL-V
* - replace '\n' with CTRL-V 'n'
* - add a '\n' at the end
*
* For a long line:
* - write " CTRL-V <length> \n " in first line
* - write " < <string> \n " in second line
*/
void viminfo_writestring(FILE *fd, char_u *p)
{
int c;
char_u *s;
int len = 0;
for (s = p; *s != NUL; ++s) {
if (*s == Ctrl_V || *s == '\n')
++len;
++len;
}
/* If the string will be too long, write its length and put it in the next
* line. Take into account that some room is needed for what comes before
* the string (e.g., variable name). Add something to the length for the
* '<', NL and trailing NUL. */
if (len > LSIZE / 2)
fprintf(fd, "\026%d\n<", len + 3);
while ((c = *p++) != NUL) {
if (c == Ctrl_V || c == '\n') {
putc(Ctrl_V, fd);
if (c == '\n')
c = 'n';
}
putc(c, fd);
}
putc('\n', fd);
}
void print_line_no_prefix(linenr_T lnum, int use_number, int list)
{
char_u numbuf[30];
@ -3364,8 +2823,36 @@ int check_secure(void)
return FALSE;
}
static char_u *old_sub = NULL; /* previous substitute pattern */
static int global_need_beginline; /* call beginline() after ":g" */
/// Previous substitute replacement string
static SubReplacementString old_sub = {NULL, 0, NULL};
static int global_need_beginline; // call beginline() after ":g"
/// Get old substitute replacement string
///
/// @param[out] ret_sub Location where old string will be saved.
void sub_get_replacement(SubReplacementString *const ret_sub)
FUNC_ATTR_NONNULL_ALL
{
*ret_sub = old_sub;
}
/// Set substitute string and timestamp
///
/// @warning `sub` must be in allocated memory. It is not copied.
///
/// @param[in] sub New replacement string.
void sub_set_replacement(SubReplacementString sub)
{
xfree(old_sub.sub);
if (sub.additional_elements != old_sub.additional_elements) {
if (old_sub.additional_elements != NULL) {
api_free_array(*old_sub.additional_elements);
xfree(old_sub.additional_elements);
}
}
old_sub = sub;
}
/* do_sub()
*
@ -3473,16 +2960,19 @@ void do_sub(exarg_T *eap)
}
if (!eap->skip) {
xfree(old_sub);
old_sub = vim_strsave(sub);
sub_set_replacement((SubReplacementString) {
.sub = xstrdup((char *) sub),
.timestamp = os_time(),
.additional_elements = NULL,
});
}
} else if (!eap->skip) { /* use previous pattern and substitution */
if (old_sub == NULL) { /* there is no previous command */
if (old_sub.sub == NULL) { /* there is no previous command */
EMSG(_(e_nopresub));
return;
}
pat = NULL; /* search_regcomp() will use previous pattern */
sub = old_sub;
sub = (char_u *) old_sub.sub;
/* Vi compatibility quirk: repeating with ":s" keeps the cursor in the
* last column after using "$". */
@ -4501,27 +3991,10 @@ void global_exe(char_u *cmd)
msgmore(curbuf->b_ml.ml_line_count - old_lcount);
}
int read_viminfo_sub_string(vir_T *virp, int force)
{
if (force)
xfree(old_sub);
if (force || old_sub == NULL)
old_sub = viminfo_readstring(virp, 1, TRUE);
return viminfo_readline(virp);
}
void write_viminfo_sub_string(FILE *fp)
{
if (get_viminfo_parameter('/') != 0 && old_sub != NULL) {
fputs(_("\n# Last Substitute String:\n$"), fp);
viminfo_writestring(fp, old_sub);
}
}
#if defined(EXITFREE)
void free_old_sub(void)
{
xfree(old_sub);
sub_set_replacement((SubReplacementString) {NULL, 0, NULL});
}
#endif

View File

@ -3,6 +3,9 @@
#include <stdbool.h>
#include "nvim/os/time.h"
#include "nvim/api/private/defs.h"
/* flags for do_ecmd() */
#define ECMD_HIDE 0x01 /* don't free the current buffer */
#define ECMD_SET_HELP 0x02 /* set b_help flag of (new) buffer before
@ -16,11 +19,12 @@
#define ECMD_LAST (linenr_T)-1 /* use last position in all files */
#define ECMD_ONE (linenr_T)1 /* use first line */
/* flags for read_viminfo() and children */
#define VIF_WANT_INFO 1 /* load non-mark info */
#define VIF_WANT_MARKS 2 /* load file marks */
#define VIF_FORCEIT 4 /* overwrite info already read */
#define VIF_GET_OLDFILES 8 /* load v:oldfiles */
/// Previous :substitute replacement string definition
typedef struct {
char *sub; ///< Previous replacement string.
Timestamp timestamp; ///< Time when it was last set.
Array *additional_elements; ///< Additional data left from ShaDa file.
} SubReplacementString;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_cmds.h.generated.h"

View File

@ -75,6 +75,7 @@
#include "nvim/mouse.h"
#include "nvim/event/rstream.h"
#include "nvim/event/wstream.h"
#include "nvim/shada.h"
static int quitmore = 0;
static int ex_pressedreturn = FALSE;
@ -9149,11 +9150,11 @@ static void ex_viminfo(exarg_T *eap)
if (*p_viminfo == NUL)
p_viminfo = (char_u *)"'100";
if (eap->cmdidx == CMD_rviminfo) {
if (read_viminfo(eap->arg, VIF_WANT_INFO | VIF_WANT_MARKS
| (eap->forceit ? VIF_FORCEIT : 0)) == FAIL)
EMSG(_("E195: Cannot open viminfo file for reading"));
} else
write_viminfo(eap->arg, eap->forceit);
if (shada_read_everything((char *) eap->arg, eap->forceit) == FAIL)
EMSG(_("E195: Cannot open ShaDa file for reading"));
} else {
shada_write_file((char *) eap->arg, eap->forceit);
}
p_viminfo = save_viminfo;
}

View File

@ -65,6 +65,9 @@
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/event/loop.h"
#include "nvim/os/time.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
/*
* Variables shared between getcmdline(), redrawcmdline() and others.
@ -100,12 +103,6 @@ static int cmd_showtail; /* Only show path tail in lists ? */
static int new_cmdpos; /* position set by set_cmdline_pos() */
typedef struct hist_entry {
int hisnum; /* identifying number */
int viminfo; /* when TRUE hisstr comes from viminfo */
char_u *hisstr; /* actual entry, separator char after the NUL */
} histentry_T;
/*
* Type used by call_user_expand_func
*/
@ -4230,12 +4227,10 @@ void init_history(void)
// delete entries that don't fit in newlen, if any
for (int i = 0; i < i1; i++) {
xfree(history[type][i].hisstr);
history[type][i].hisstr = NULL;
hist_free_entry(history[type] + i);
}
for (int i = i1 + l1; i < i2; i++) {
xfree(history[type][i].hisstr);
history[type][i].hisstr = NULL;
hist_free_entry(history[type] + i);
}
}
@ -4253,11 +4248,21 @@ void init_history(void)
}
}
static void clear_hist_entry(histentry_T *hisptr)
static inline void hist_free_entry(histentry_T *hisptr)
FUNC_ATTR_NONNULL_ALL
{
hisptr->hisnum = 0;
hisptr->viminfo = FALSE;
hisptr->hisstr = NULL;
xfree(hisptr->hisstr);
if (hisptr->additional_elements != NULL) {
api_free_array(*hisptr->additional_elements);
xfree(hisptr->additional_elements);
}
clear_hist_entry(hisptr);
}
static inline void clear_hist_entry(histentry_T *hisptr)
FUNC_ATTR_NONNULL_ALL
{
memset(hisptr, 0, sizeof(*hisptr));
}
/*
@ -4310,6 +4315,8 @@ in_history (
history[type][i].hisnum = ++hisnum[type];
history[type][i].viminfo = FALSE;
history[type][i].hisstr = str;
history[type][i].timestamp = os_time();
history[type][i].additional_elements = NULL;
return TRUE;
}
return FALSE;
@ -4372,8 +4379,7 @@ add_to_history (
if (maptick == last_maptick) {
/* Current line is from the same mapping, remove it */
hisptr = &history[HIST_SEARCH][hisidx[HIST_SEARCH]];
xfree(hisptr->hisstr);
clear_hist_entry(hisptr);
hist_free_entry(hisptr);
--hisnum[histype];
if (--hisidx[HIST_SEARCH] < 0)
hisidx[HIST_SEARCH] = hislen - 1;
@ -4384,11 +4390,13 @@ add_to_history (
if (++hisidx[histype] == hislen)
hisidx[histype] = 0;
hisptr = &history[histype][hisidx[histype]];
xfree(hisptr->hisstr);
hist_free_entry(hisptr);
/* Store the separator after the NUL of the string. */
len = (int)STRLEN(new_entry);
hisptr->hisstr = vim_strnsave(new_entry, len + 2);
hisptr->timestamp = os_time();
hisptr->additional_elements = NULL;
hisptr->hisstr[len + 1] = sep;
hisptr->hisnum = ++hisnum[histype];
@ -4545,23 +4553,21 @@ char_u *get_history_entry(int histype, int idx)
return (char_u *)"";
}
/*
* Clear all entries of a history.
* "histype" may be one of the HIST_ values.
*/
int clr_history(int histype)
/// Clear all entries in a history
///
/// @param[in] histype One of the HIST_ values.
///
/// @return OK if there was something to clean and histype was one of HIST_
/// values, FAIL otherwise.
int clr_history(const int histype)
{
int i;
histentry_T *hisptr;
if (hislen != 0 && histype >= 0 && histype < HIST_COUNT) {
hisptr = history[histype];
for (i = hislen; i--; ) {
xfree(hisptr->hisstr);
clear_hist_entry(hisptr);
histentry_T *hisptr = history[histype];
for (int i = hislen; i--; hisptr++) {
hist_free_entry(hisptr);
}
hisidx[histype] = -1; /* mark history as cleared */
hisnum[histype] = 0; /* reset identifier counter */
hisidx[histype] = -1; // mark history as cleared
hisnum[histype] = 0; // reset identifier counter
return OK;
}
return FAIL;
@ -4596,8 +4602,7 @@ int del_history_entry(int histype, char_u *str)
break;
if (vim_regexec(&regmatch, hisptr->hisstr, (colnr_T)0)) {
found = TRUE;
xfree(hisptr->hisstr);
clear_hist_entry(hisptr);
hist_free_entry(hisptr);
} else {
if (i != last) {
history[histype][last] = *hisptr;
@ -4628,7 +4633,7 @@ int del_history_idx(int histype, int idx)
if (i < 0)
return FALSE;
idx = hisidx[histype];
xfree(history[histype][i].hisstr);
hist_free_entry(&history[histype][i]);
/* When deleting the last added search string in a mapping, reset
* last_maptick, so that the last added search string isn't deleted again.
@ -4641,9 +4646,10 @@ int del_history_idx(int histype, int idx)
history[histype][i] = history[histype][j];
i = j;
}
clear_hist_entry(&history[histype][i]);
if (--i < 0)
clear_hist_entry(&history[histype][idx]);
if (--i < 0) {
i += hislen;
}
hisidx[histype] = i;
return TRUE;
}
@ -4762,250 +4768,6 @@ void ex_history(exarg_T *eap)
}
}
/*
* Buffers for history read from a viminfo file. Only valid while reading.
*/
static char_u **viminfo_history[HIST_COUNT] = {NULL, NULL, NULL, NULL};
static int viminfo_hisidx[HIST_COUNT] = {0, 0, 0, 0};
static int viminfo_hislen[HIST_COUNT] = {0, 0, 0, 0};
static int viminfo_add_at_front = FALSE;
/*
* Translate a history type number to the associated character.
*/
static int
hist_type2char (
int type,
int use_question /* use '?' instead of '/' */
)
{
if (type == HIST_CMD)
return ':';
if (type == HIST_SEARCH) {
if (use_question)
return '?';
else
return '/';
}
if (type == HIST_EXPR)
return '=';
return '@';
}
/*
* Prepare for reading the history from the viminfo file.
* This allocates history arrays to store the read history lines.
*/
void prepare_viminfo_history(int asklen, int writing)
{
int i;
int num;
init_history();
viminfo_add_at_front = (asklen != 0 && !writing);
if (asklen > hislen)
asklen = hislen;
for (int type = 0; type < HIST_COUNT; ++type) {
/* Count the number of empty spaces in the history list. Entries read
* from viminfo previously are also considered empty. If there are
* more spaces available than we request, then fill them up. */
for (i = 0, num = 0; i < hislen; i++)
if (history[type][i].hisstr == NULL || history[type][i].viminfo)
num++;
int len = asklen;
if (num > len)
len = num;
if (len <= 0)
viminfo_history[type] = NULL;
else
viminfo_history[type] = xmalloc(len * sizeof(char_u *));
if (viminfo_history[type] == NULL)
len = 0;
viminfo_hislen[type] = len;
viminfo_hisidx[type] = 0;
}
}
/*
* Accept a line from the viminfo, store it in the history array when it's
* new.
*/
int read_viminfo_history(vir_T *virp, int writing)
{
int type;
char_u *val;
type = hist_char2type(virp->vir_line[0]);
if (viminfo_hisidx[type] < viminfo_hislen[type]) {
val = viminfo_readstring(virp, 1, TRUE);
if (val != NULL && *val != NUL) {
int sep = (*val == ' ' ? NUL : *val);
if (!in_history(type, val + (type == HIST_SEARCH),
viminfo_add_at_front, sep, writing)) {
/* Need to re-allocate to append the separator byte. */
size_t len = STRLEN(val);
char_u *p = xmalloc(len + 2);
if (type == HIST_SEARCH) {
/* Search entry: Move the separator from the first
* column to after the NUL. */
memmove(p, val + 1, len);
p[len] = sep;
} else {
/* Not a search entry: No separator in the viminfo
* file, add a NUL separator. */
memmove(p, val, len + 1);
p[len + 1] = NUL;
}
viminfo_history[type][viminfo_hisidx[type]++] = p;
}
}
xfree(val);
}
return viminfo_readline(virp);
}
/*
* Finish reading history lines from viminfo. Not used when writing viminfo.
*/
void finish_viminfo_history(void)
{
int idx;
int i;
int type;
for (type = 0; type < HIST_COUNT; ++type) {
if (history[type] == NULL)
continue;
idx = hisidx[type] + viminfo_hisidx[type];
if (idx >= hislen)
idx -= hislen;
else if (idx < 0)
idx = hislen - 1;
if (viminfo_add_at_front)
hisidx[type] = idx;
else {
if (hisidx[type] == -1)
hisidx[type] = hislen - 1;
do {
if (history[type][idx].hisstr != NULL
|| history[type][idx].viminfo)
break;
if (++idx == hislen)
idx = 0;
} while (idx != hisidx[type]);
if (idx != hisidx[type] && --idx < 0)
idx = hislen - 1;
}
for (i = 0; i < viminfo_hisidx[type]; i++) {
xfree(history[type][idx].hisstr);
history[type][idx].hisstr = viminfo_history[type][i];
history[type][idx].viminfo = TRUE;
if (--idx < 0)
idx = hislen - 1;
}
idx += 1;
idx %= hislen;
for (i = 0; i < viminfo_hisidx[type]; i++) {
history[type][idx++].hisnum = ++hisnum[type];
idx %= hislen;
}
xfree(viminfo_history[type]);
viminfo_history[type] = NULL;
viminfo_hisidx[type] = 0;
}
}
/*
* Write history to viminfo file in "fp".
* When "merge" is TRUE merge history lines with a previously read viminfo
* file, data is in viminfo_history[].
* When "merge" is FALSE just write all history lines. Used for ":wviminfo!".
*/
void write_viminfo_history(FILE *fp, int merge)
{
int i;
int type;
int num_saved;
char_u *p;
int c;
int round;
init_history();
if (hislen == 0)
return;
for (type = 0; type < HIST_COUNT; ++type) {
num_saved = get_viminfo_parameter(hist_type2char(type, FALSE));
if (num_saved == 0)
continue;
if (num_saved < 0) /* Use default */
num_saved = hislen;
fprintf(fp, _("\n# %s History (newest to oldest):\n"),
type == HIST_CMD ? _("Command Line") :
type == HIST_SEARCH ? _("Search String") :
type == HIST_EXPR ? _("Expression") :
_("Input Line"));
if (num_saved > hislen)
num_saved = hislen;
/*
* Merge typed and viminfo history:
* round 1: history of typed commands.
* round 2: history from recently read viminfo.
*/
for (round = 1; round <= 2; ++round) {
if (round == 1)
/* start at newest entry, somewhere in the list */
i = hisidx[type];
else if (viminfo_hisidx[type] > 0)
/* start at newest entry, first in the list */
i = 0;
else
/* empty list */
i = -1;
if (i >= 0)
while (num_saved > 0
&& !(round == 2 && i >= viminfo_hisidx[type])) {
p = round == 1 ? history[type][i].hisstr
: viminfo_history[type] == NULL ? NULL
: viminfo_history[type][i];
if (p != NULL && (round == 2
|| !merge
|| !history[type][i].viminfo)) {
--num_saved;
fputc(hist_type2char(type, TRUE), fp);
/* For the search history: put the separator in the
* second column; use a space if there isn't one. */
if (type == HIST_SEARCH) {
c = p[STRLEN(p) + 1];
putc(c == NUL ? ' ' : c, fp);
}
viminfo_writestring(fp, p);
}
if (round == 1) {
/* Decrement index, loop around and stop when back at
* the start. */
if (--i < 0)
i = hislen - 1;
if (i == hisidx[type])
break;
} else {
/* Increment index. Stop at the end in the while. */
++i;
}
}
}
for (i = 0; i < viminfo_hisidx[type]; ++i)
if (viminfo_history[type] != NULL)
xfree(viminfo_history[type][i]);
xfree(viminfo_history[type]);
viminfo_history[type] = NULL;
viminfo_hisidx[type] = 0;
}
}
/*
* Write a character at the current cursor+offset position.
* It is directly written into the command buffer block.
@ -5294,3 +5056,68 @@ char_u *script_get(exarg_T *eap, char_u *cmd)
return (char_u *)ga.ga_data;
}
/// Iterate over history items
///
/// @warning No history-editing functions must be run while iteration is in
/// progress.
///
/// @param[in] iter Pointer to the last history entry.
/// @param[in] history_type Type of the history (HIST_*). Ignored if iter
/// parameter is not NULL.
/// @param[in] zero If true then zero (but not free) returned items.
///
/// @warning When using this parameter user is
/// responsible for calling clr_history()
/// itself after iteration is over. If
/// clr_history() is not called behaviour is
/// undefined. No functions that work with
/// history must be called during iteration
/// in this case.
/// @param[out] hist Next history entry.
///
/// @return Pointer used in next iteration or NULL to indicate that iteration
/// was finished.
const void *hist_iter(const void *const iter, const size_t history_type,
const bool zero, histentry_T *const hist)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(4)
{
*hist = (histentry_T) {
.hisstr = NULL
};
if (hisidx[history_type] == -1) {
return NULL;
}
histentry_T *const hstart = &(history[history_type][0]);
histentry_T *const hlast = (
&(history[history_type][hisidx[history_type]]));
const histentry_T *const hend = &(history[history_type][hislen - 1]);
histentry_T *hiter;
if (iter == NULL) {
histentry_T *hfirst = hlast;
do {
hfirst++;
if (hfirst > hend) {
hfirst = hstart;
}
if (hfirst->hisstr != NULL) {
break;
}
} while (hfirst != hlast);
hiter = hfirst;
} else {
hiter = (histentry_T *) iter;
}
if (hiter == NULL) {
return NULL;
}
*hist = *hiter;
if (zero) {
memset(hiter, 0, sizeof(*hiter));
}
if (hiter == hlast) {
return NULL;
}
hiter++;
return (const void *) ((hiter > hend) ? hstart : hiter);
}

View File

@ -35,6 +35,15 @@
typedef char_u *(*CompleteListItemGetter)(expand_T *, int);
/// History entry definition
typedef struct hist_entry {
int hisnum; ///< Entry identifier number.
bool viminfo; ///< If true, indicates that entry comes from viminfo.
char_u *hisstr; ///< Actual entry, separator char after the NUL.
Timestamp timestamp; ///< Time when entry was added.
Array *additional_elements; ///< Additional entries from ShaDa file.
} histentry_T;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_getln.h.generated.h"
#endif

View File

@ -57,6 +57,7 @@
#include "nvim/types.h"
#include "nvim/undo.h"
#include "nvim/window.h"
#include "nvim/shada.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
#include "nvim/os/input.h"
@ -2166,14 +2167,15 @@ readfile_charconvert (
/*
* Read marks for the current buffer from the viminfo file, when we support
* Read marks for the current buffer from the ShaDa file, when we support
* buffer marks and the buffer has a name.
*/
static void check_marks_read(void)
{
if (!curbuf->b_marks_read && get_viminfo_parameter('\'') > 0
&& curbuf->b_ffname != NULL)
read_viminfo(NULL, VIF_WANT_MARKS);
&& curbuf->b_ffname != NULL) {
shada_read_marks();
}
/* Always set b_marks_read; needed when 'viminfo' is changed to include
* the ' parameter after opening a buffer. */

View File

@ -892,7 +892,7 @@ EXTERN int skip_redraw INIT(= FALSE); /* skip redraw once */
EXTERN int do_redraw INIT(= FALSE); /* extra redraw once */
EXTERN int need_highlight_changed INIT(= TRUE);
EXTERN char_u *use_viminfo INIT(= NULL); /* name of viminfo file to use */
EXTERN char *used_shada_file INIT(= NULL); /* name of viminfo file to use */
#define NSCRIPT 15
EXTERN FILE *scriptin[NSCRIPT]; /* streams to read script from */

View File

@ -49,6 +49,7 @@
#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/os_unix.h"
#include "nvim/os/os_defs.h"
#include "nvim/path.h"
#include "nvim/profile.h"
#include "nvim/quickfix.h"
@ -58,6 +59,7 @@
#include "nvim/ui.h"
#include "nvim/version.h"
#include "nvim/window.h"
#include "nvim/shada.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
@ -377,12 +379,12 @@ int main(int argc, char **argv)
}
/*
* Read in registers, history etc, but not marks, from the viminfo file.
* Read in registers, history etc, but not marks, from the ShaDa file.
* This is where v:oldfiles gets filled.
*/
if (*p_viminfo != NUL) {
read_viminfo(NULL, VIF_WANT_INFO | VIF_GET_OLDFILES);
TIME_MSG("reading viminfo");
(void) shada_read_file(NULL, kShaDaWantInfo|kShaDaGetOldfiles);
TIME_MSG("reading ShaDa");
}
/* It's better to make v:oldfiles an empty list than NULL. */
if (get_vim_var_list(VV_OLDFILES) == NULL)
@ -803,9 +805,10 @@ void getout(int exitval)
apply_autocmds(EVENT_VIMLEAVEPRE, NULL, NULL, FALSE, curbuf);
}
if (p_viminfo && *p_viminfo != NUL)
/* Write out the registers, history, marks etc, to the viminfo file */
write_viminfo(NULL, FALSE);
if (p_viminfo && *p_viminfo != NUL) {
// Write out the registers, history, marks etc, to the viminfo file
shada_write_file(NULL, false);
}
if (get_vim_var_nr(VV_DYING) <= 1)
apply_autocmds(EVENT_VIMLEAVE, NULL, NULL, FALSE, curbuf);
@ -1164,7 +1167,7 @@ static void command_line_scan(mparm_T *parmp)
}
/*FALLTHROUGH*/
case 'S': /* "-S {file}" execute Vim script */
case 'i': /* "-i {viminfo}" use for viminfo */
case 'i': /* "-i {shada}" use for ShaDa file */
case 'u': /* "-u {vimrc}" vim inits file */
case 'U': /* "-U {gvimrc}" gvim inits file */
case 'W': /* "-W {scriptout}" overwrite */
@ -1235,8 +1238,8 @@ static void command_line_scan(mparm_T *parmp)
parmp->use_ef = (char_u *)argv[0];
break;
case 'i': /* "-i {viminfo}" use for viminfo */
use_viminfo = (char_u *)argv[0];
case 'i': /* "-i {shada}" use for shada */
used_shada_file = argv[0];
break;
case 's': /* "-s {scriptin}" read from script file */
@ -2039,7 +2042,7 @@ static void usage(void)
mch_msg(_(" -r, -L List swap files and exit\n"));
mch_msg(_(" -r <file> Recover crashed session\n"));
mch_msg(_(" -u <nvimrc> Use <nvimrc> instead of the default\n"));
mch_msg(_(" -i <nviminfo> Use <nviminfo> instead of the default\n"));
mch_msg(_(" -i <shada> Use <shada> instead of the default " SHADA_FILE "\n"));
mch_msg(_(" --noplugin Don't load plugin scripts\n"));
mch_msg(_(" -o[N] Open N windows (default: one for each file)\n"));
mch_msg(_(" -O[N] Like -o but split vertically\n"));

View File

@ -39,7 +39,10 @@
#include "nvim/strings.h"
#include "nvim/ui.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
#include "nvim/os/input.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
/*
* This file contains routines to maintain and manipulate marks.
@ -48,12 +51,20 @@
/*
* If a named file mark's lnum is non-zero, it is valid.
* If a named file mark's fnum is non-zero, it is for an existing buffer,
* otherwise it is from .viminfo and namedfm[n].fname is the file name.
* otherwise it is from .shada and namedfm[n].fname is the file name.
* There are marks 'A - 'Z (set by user) and '0 to '9 (set when writing
* viminfo).
* shada).
*/
#define EXTRA_MARKS 10 /* marks 0-9 */
static xfmark_T namedfm[NMARKS + EXTRA_MARKS]; /* marks with file nr */
/// Global marks (marks with file number or name)
static xfmark_T namedfm[NGLOBALMARKS];
#define SET_XFMARK(xfmarkp_, mark_, fnum_, fname_) \
do { \
xfmark_T *const xfmarkp__ = xfmarkp_; \
xfmarkp__->fname = fname_; \
RESET_FMARK(&(xfmarkp__->fmark), mark_, fnum_); \
} while (0)
#ifdef INCLUDE_GENERATED_DECLARATIONS
@ -68,6 +79,23 @@ int setmark(int c)
return setmark_pos(c, &curwin->w_cursor, curbuf->b_fnum);
}
/// Free fmark_T item
void free_fmark(fmark_T fm)
{
if (fm.additional_data != NULL) {
api_free_dictionary(*fm.additional_data);
free(fm.additional_data);
}
}
/// Free xfmark_T item
static inline void free_xfmark(xfmark_T fm)
{
xfree(fm.fname);
fm.fname = NULL;
free_fmark(fm.fmark);
}
/*
* Set named mark "c" to position "pos".
* When "c" is upper case use file "fnum".
@ -92,7 +120,7 @@ int setmark_pos(int c, pos_T *pos, int fnum)
}
if (c == '"') {
curbuf->b_last_cursor = *pos;
RESET_FMARK(&curbuf->b_last_cursor, *pos, curbuf->b_fnum);
return OK;
}
@ -123,16 +151,14 @@ int setmark_pos(int c, pos_T *pos, int fnum)
return FAIL;
if (islower(c)) {
i = c - 'a';
curbuf->b_namedm[i] = *pos;
RESET_FMARK(curbuf->b_namedm + i, *pos, curbuf->b_fnum);
return OK;
}
if (isupper(c)) {
assert(c >= 'A' && c <= 'Z');
i = c - 'A';
namedfm[i].fmark.mark = *pos;
namedfm[i].fmark.fnum = fnum;
xfree(namedfm[i].fname);
namedfm[i].fname = NULL;
free_xfmark(namedfm[i]);
SET_XFMARK(namedfm + i, *pos, fnum, NULL);
return OK;
}
return FAIL;
@ -157,16 +183,14 @@ void setpcmark(void)
/* If jumplist is full: remove oldest entry */
if (++curwin->w_jumplistlen > JUMPLISTSIZE) {
curwin->w_jumplistlen = JUMPLISTSIZE;
xfree(curwin->w_jumplist[0].fname);
free_xfmark(curwin->w_jumplist[0]);
for (i = 1; i < JUMPLISTSIZE; ++i)
curwin->w_jumplist[i - 1] = curwin->w_jumplist[i];
}
curwin->w_jumplistidx = curwin->w_jumplistlen;
fm = &curwin->w_jumplist[curwin->w_jumplistlen - 1];
fm->fmark.mark = curwin->w_pcmark;
fm->fmark.fnum = curbuf->b_fnum;
fm->fname = NULL;
SET_XFMARK(fm, curwin->w_pcmark, curbuf->b_fnum, NULL);
}
/*
@ -302,11 +326,11 @@ pos_T *getmark_buf_fnum(buf_T *buf, int c, int changefile, int *fnum)
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);
posp = &(buf->b_last_cursor.mark);
else if (c == '^') /* to where Insert mode stopped */
posp = &(buf->b_last_insert);
posp = &(buf->b_last_insert.mark);
else if (c == '.') /* to where last change was made */
posp = &(buf->b_last_change);
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 */
@ -357,7 +381,7 @@ pos_T *getmark_buf_fnum(buf_T *buf, int c, int changefile, int *fnum)
pos_copy.coladd = 0;
}
} else if (ASCII_ISLOWER(c)) { /* normal named mark */
posp = &(buf->b_namedm[c - 'a']);
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;
@ -365,8 +389,9 @@ pos_T *getmark_buf_fnum(buf_T *buf, int c, int changefile, int *fnum)
c -= 'A';
posp = &(namedfm[c].fmark.mark);
if (namedfm[c].fmark.fnum == 0)
if (namedfm[c].fmark.fnum == 0) {
fname2fnum(&namedfm[c]);
}
if (fnum != NULL)
*fnum = namedfm[c].fmark.fnum;
@ -420,15 +445,15 @@ getnextmark (
pos.col = MAXCOL;
for (i = 0; i < NMARKS; i++) {
if (curbuf->b_namedm[i].lnum > 0) {
if (curbuf->b_namedm[i].mark.lnum > 0) {
if (dir == FORWARD) {
if ((result == NULL || lt(curbuf->b_namedm[i], *result))
&& lt(pos, curbuf->b_namedm[i]))
result = &curbuf->b_namedm[i];
if ((result == NULL || lt(curbuf->b_namedm[i].mark, *result))
&& lt(pos, curbuf->b_namedm[i].mark))
result = &curbuf->b_namedm[i].mark;
} else {
if ((result == NULL || lt(*result, curbuf->b_namedm[i]))
&& lt(curbuf->b_namedm[i], pos))
result = &curbuf->b_namedm[i];
if ((result == NULL || lt(*result, curbuf->b_namedm[i].mark))
&& lt(curbuf->b_namedm[i].mark, pos))
result = &curbuf->b_namedm[i].mark;
}
}
}
@ -438,12 +463,12 @@ getnextmark (
/*
* For an xtended filemark: set the fnum from the fname.
* This is used for marks obtained from the .viminfo file. It's postponed
* This is used for marks obtained from the .shada file. It's postponed
* until the mark is used to avoid a long startup delay.
*/
static void fname2fnum(xfmark_T *fm)
{
char_u *p;
char_u *p;
if (fm->fname != NULL) {
/*
@ -475,19 +500,17 @@ static void fname2fnum(xfmark_T *fm)
/*
* Check all file marks for a name that matches the file name in buf.
* May replace the name with an fnum.
* Used for marks that come from the .viminfo file.
* Used for marks that come from the .shada file.
*/
void fmarks_check_names(buf_T *buf)
{
char_u *name;
char_u *name = buf->b_ffname;
int i;
if (buf->b_ffname == NULL)
return;
name = home_replace_save(buf, buf->b_ffname);
for (i = 0; i < NMARKS + EXTRA_MARKS; ++i)
for (i = 0; i < NGLOBALMARKS; ++i)
fmarks_check_one(&namedfm[i], name, buf);
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
@ -495,8 +518,6 @@ void fmarks_check_names(buf_T *buf)
fmarks_check_one(&wp->w_jumplist[i], name, buf);
}
}
xfree(name);
}
static void fmarks_check_one(xfmark_T *fm, char_u *name, buf_T *buf)
@ -541,23 +562,20 @@ int check_mark(pos_T *pos)
*/
void clrallmarks(buf_T *buf)
{
static int i = -1;
static bool initialized = false;
if (i == -1) /* first call ever: initialize */
for (i = 0; i < NMARKS + 1; i++) {
namedfm[i].fmark.mark.lnum = 0;
namedfm[i].fname = NULL;
}
if (!initialized) {
// first call ever: initialize
memset(&(namedfm[0]), 0, sizeof(namedfm));
initialized = true;
}
for (i = 0; i < NMARKS; i++)
buf->b_namedm[i].lnum = 0;
memset(&(buf->b_namedm[0]), 0, sizeof(buf->b_namedm));
buf->b_op_start.lnum = 0; /* start/end op mark cleared */
buf->b_op_end.lnum = 0;
buf->b_last_cursor.lnum = 1; /* '" mark cleared */
buf->b_last_cursor.col = 0;
buf->b_last_cursor.coladd = 0;
buf->b_last_insert.lnum = 0; /* '^ mark cleared */
buf->b_last_change.lnum = 0; /* '. mark cleared */
RESET_FMARK(&buf->b_last_cursor, ((pos_T) {1, 0, 0}), 0); // '" mark
CLEAR_FMARK(&buf->b_last_insert); // '^ mark
CLEAR_FMARK(&buf->b_last_change); // '. mark
buf->b_changelistlen = 0;
}
@ -612,8 +630,8 @@ void do_marks(exarg_T *eap)
show_one_mark('\'', arg, &curwin->w_pcmark, NULL, TRUE);
for (i = 0; i < NMARKS; ++i)
show_one_mark(i + 'a', arg, &curbuf->b_namedm[i], NULL, TRUE);
for (i = 0; i < NMARKS + EXTRA_MARKS; ++i) {
show_one_mark(i + 'a', arg, &curbuf->b_namedm[i].mark, NULL, TRUE);
for (i = 0; i < NGLOBALMARKS; ++i) {
if (namedfm[i].fmark.fnum != 0)
name = fm_getname(&namedfm[i].fmark, 15);
else
@ -626,11 +644,11 @@ void do_marks(exarg_T *eap)
xfree(name);
}
}
show_one_mark('"', arg, &curbuf->b_last_cursor, NULL, TRUE);
show_one_mark('"', arg, &curbuf->b_last_cursor.mark, NULL, TRUE);
show_one_mark('[', arg, &curbuf->b_op_start, NULL, TRUE);
show_one_mark(']', arg, &curbuf->b_op_end, NULL, TRUE);
show_one_mark('^', arg, &curbuf->b_last_insert, NULL, TRUE);
show_one_mark('.', arg, &curbuf->b_last_change, NULL, TRUE);
show_one_mark('^', arg, &curbuf->b_last_insert.mark, NULL, TRUE);
show_one_mark('.', arg, &curbuf->b_last_change.mark, NULL, TRUE);
show_one_mark('<', arg, &curbuf->b_visual.vi_start, NULL, TRUE);
show_one_mark('>', arg, &curbuf->b_visual.vi_end, NULL, TRUE);
show_one_mark(-1, arg, NULL, NULL, FALSE);
@ -728,7 +746,7 @@ void ex_delmarks(exarg_T *eap)
for (i = from; i <= to; ++i) {
if (lower)
curbuf->b_namedm[i - 'a'].lnum = 0;
curbuf->b_namedm[i - 'a'].mark.lnum = 0;
else {
if (digit)
n = i - '0' + NMARKS;
@ -741,9 +759,9 @@ void ex_delmarks(exarg_T *eap)
}
} else
switch (*p) {
case '"': curbuf->b_last_cursor.lnum = 0; break;
case '^': curbuf->b_last_insert.lnum = 0; break;
case '.': curbuf->b_last_change.lnum = 0; break;
case '"': CLEAR_FMARK(&curbuf->b_last_cursor); break;
case '^': CLEAR_FMARK(&curbuf->b_last_insert); break;
case '.': CLEAR_FMARK(&curbuf->b_last_change); break;
case '[': curbuf->b_op_start.lnum = 0; break;
case ']': curbuf->b_op_end.lnum = 0; break;
case '<': curbuf->b_visual.vi_start.lnum = 0; break;
@ -886,24 +904,24 @@ void mark_adjust(linenr_T line1, linenr_T line2, long amount, long amount_after)
if (!cmdmod.lockmarks) {
/* named marks, lower case and upper case */
for (i = 0; i < NMARKS; i++) {
one_adjust(&(curbuf->b_namedm[i].lnum));
one_adjust(&(curbuf->b_namedm[i].mark.lnum));
if (namedfm[i].fmark.fnum == fnum)
one_adjust_nodel(&(namedfm[i].fmark.mark.lnum));
}
for (i = NMARKS; i < NMARKS + EXTRA_MARKS; i++) {
for (i = NMARKS; i < NGLOBALMARKS; i++) {
if (namedfm[i].fmark.fnum == fnum)
one_adjust_nodel(&(namedfm[i].fmark.mark.lnum));
}
/* last Insert position */
one_adjust(&(curbuf->b_last_insert.lnum));
one_adjust(&(curbuf->b_last_insert.mark.lnum));
/* last change position */
one_adjust(&(curbuf->b_last_change.lnum));
one_adjust(&(curbuf->b_last_change.mark.lnum));
/* last cursor position, if it was set */
if (!equalpos(curbuf->b_last_cursor, initpos))
one_adjust(&(curbuf->b_last_cursor.lnum));
if (!equalpos(curbuf->b_last_cursor.mark, initpos))
one_adjust(&(curbuf->b_last_cursor.mark.lnum));
/* list of change positions */
@ -1038,20 +1056,20 @@ void mark_col_adjust(linenr_T lnum, colnr_T mincol, long lnum_amount, long col_a
/* named marks, lower case and upper case */
for (i = 0; i < NMARKS; i++) {
col_adjust(&(curbuf->b_namedm[i]));
col_adjust(&(curbuf->b_namedm[i].mark));
if (namedfm[i].fmark.fnum == fnum)
col_adjust(&(namedfm[i].fmark.mark));
}
for (i = NMARKS; i < NMARKS + EXTRA_MARKS; i++) {
for (i = NMARKS; i < NGLOBALMARKS; i++) {
if (namedfm[i].fmark.fnum == fnum)
col_adjust(&(namedfm[i].fmark.mark));
}
/* last Insert position */
col_adjust(&(curbuf->b_last_insert));
col_adjust(&(curbuf->b_last_insert.mark));
/* last change position */
col_adjust(&(curbuf->b_last_change));
col_adjust(&(curbuf->b_last_change.mark));
/* list of change positions */
for (i = 0; i < curbuf->b_changelistlen; ++i)
@ -1150,6 +1168,246 @@ void copy_jumplist(win_T *from, win_T *to)
to->w_jumplistidx = from->w_jumplistidx;
}
/// Iterate over jumplist items
///
/// @warning No jumplist-editing functions must be run while iteration is in
/// progress.
///
/// @param[in] iter Iterator. Pass NULL to start iteration.
/// @param[in] win Window for which jump list is processed.
/// @param[out] fm Item definition.
///
/// @return Pointer that needs to be passed to next `mark_jumplist_iter` call or
/// NULL if iteration is over.
const void *mark_jumplist_iter(const void *const iter, const win_T *const win,
xfmark_T *const fm)
FUNC_ATTR_PURE 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};
return NULL;
}
const xfmark_T *const iter_mark =
(iter == NULL
? &(win->w_jumplist[win->w_jumplistlen - 1])
: (const xfmark_T *const) iter);
*fm = *iter_mark;
if (iter_mark == &(win->w_jumplist[0])) {
return NULL;
} else {
return iter_mark - 1;
}
}
/// Iterate over global marks
///
/// @warning No mark-editing functions must be run while iteration is in
/// progress.
///
/// @param[in] iter Iterator. Pass NULL to start iteration.
/// @param[out] name Mark name.
/// @param[out] fm Mark definition.
///
/// @return Pointer that needs to be passed to next `mark_global_iter` call or
/// NULL if iteration is over.
const void *mark_global_iter(const void *const iter, char *const name,
xfmark_T *const fm)
FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT
{
const xfmark_T *iter_mark = (iter == NULL
? &(namedfm[0])
: (const xfmark_T *const) iter);
while (!iter_mark->fmark.mark.lnum
&& (size_t) (iter_mark - &(namedfm[0])) < ARRAY_SIZE(namedfm)) {
iter_mark++;
}
if (!iter_mark->fmark.mark.lnum) {
*fm = (xfmark_T) {.fmark = {.mark = {.lnum = 0}}};
}
size_t iter_off = (size_t) (iter_mark - &(namedfm[0]));
*name = (char) (iter_off < NMARKS
? 'A' + (char) iter_off
: '0' + (char) (iter_off - NMARKS));
*fm = *iter_mark;
while ((size_t) (++iter_mark - &(namedfm[0])) < ARRAY_SIZE(namedfm)) {
if (iter_mark->fmark.mark.lnum) {
return (const void *) iter_mark;
}
}
return NULL;
}
/// Get next mark and its name
///
/// @param[in] buf Buffer for which next mark is taken.
/// @param[in,out] mark_name Pointer to the current mark name. Next mark name
/// will be saved at this address as well.
///
/// Current mark name must either be NUL, '"', '^',
/// '.' or 'a' .. 'z'. If it is neither of these
/// behaviour is undefined.
///
/// @return Pointer to the next mark or NULL.
static inline const fmark_T *next_buffer_mark(const buf_T *const buf,
char *const mark_name)
FUNC_ATTR_NONNULL_ALL
{
switch (*mark_name) {
case NUL: {
*mark_name = '"';
return &(buf->b_last_cursor);
}
case '"': {
*mark_name = '^';
return &(buf->b_last_insert);
}
case '^': {
*mark_name = '.';
return &(buf->b_last_change);
}
case '.': {
*mark_name = 'a';
return &(buf->b_namedm[0]);
}
case 'z': {
return NULL;
}
default: {
(*mark_name)++;
return &(buf->b_namedm[*mark_name - 'a']);
}
}
}
/// Iterate over buffer marks
///
/// @warning No mark-editing functions must be run while iteration is in
/// progress.
///
/// @param[in] iter Iterator. Pass NULL to start iteration.
/// @param[in] buf Buffer.
/// @param[out] name Mark name.
/// @param[out] fm Mark definition.
///
/// @return Pointer that needs to be passed to next `mark_buffer_iter` call or
/// NULL if iteration is over.
const void *mark_buffer_iter(const void *const iter, const buf_T *const buf,
char *const name, fmark_T *const fm)
FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ARG(2, 3, 4) FUNC_ATTR_WARN_UNUSED_RESULT
{
char mark_name = (char) (iter == NULL
? NUL
: (iter == &(buf->b_last_cursor)
? '"'
: (iter == &(buf->b_last_insert)
? '^'
: (iter == &(buf->b_last_change)
? '.'
: 'a' + (char) ((const fmark_T *)iter
- &(buf->b_namedm[0]))))));
const fmark_T *iter_mark = next_buffer_mark(buf, &mark_name);
while (iter_mark != NULL && iter_mark->mark.lnum == 0) {
iter_mark = next_buffer_mark(buf, &mark_name);
}
if (iter_mark == NULL) {
*fm = (fmark_T) {.mark = {.lnum = 0}};
return NULL;
}
size_t iter_off = (size_t) (iter_mark - &(buf->b_namedm[0]));
if (mark_name) {
*name = mark_name;
} else {
*name = (char) ('a' + (char) iter_off);
}
*fm = *iter_mark;
return (const void *) iter_mark;
}
/// Get a number of valid marks
size_t mark_global_amount(void)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
size_t ret = 0;
for (size_t i = 0; i < NGLOBALMARKS; i++) {
if (namedfm[i].fmark.mark.lnum != 0) {
ret++;
}
}
return ret;
}
/// Get a number of valid marks
size_t mark_buffer_amount(const buf_T *const buf)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
size_t ret = (size_t) ((buf->b_last_cursor.mark.lnum != 0)
+ (buf->b_last_insert.mark.lnum != 0)
+ (buf->b_last_change.mark.lnum != 0));
for (size_t i = 0; i < NMARKS; i++) {
if (buf->b_namedm[i].mark.lnum != 0) {
ret++;
}
}
return ret;
}
/// Set global mark
///
/// @param[in] name Mark name.
/// @param[in] fm Mark to be set.
/// @param[in] update If true then only set global mark if it was created
/// later then existing one.
void mark_set_global(const char name, const xfmark_T fm, const bool update)
{
xfmark_T *fm_tgt = NULL;
if (ASCII_ISUPPER(name)) {
fm_tgt = &(namedfm[name - 'A']);
} else if (ascii_isdigit(name)) {
fm_tgt = &(namedfm[NMARKS + (name - '0')]);
} else {
return;
}
if (update && fm.fmark.timestamp < fm_tgt->fmark.timestamp) {
return;
}
if (fm_tgt->fmark.mark.lnum != 0) {
free_xfmark(*fm_tgt);
}
*fm_tgt = fm;
}
/// Set local mark
///
/// @param[in] name Mark name.
/// @param[in] buf Pointer to the buffer to set mark in.
/// @param[in] fm Mark to be set.
/// @param[in] update If true then only set global mark if it was created
/// later then existing one.
void mark_set_local(const char name, buf_T *const buf,
const fmark_T fm, const bool update)
FUNC_ATTR_NONNULL_ALL
{
fmark_T *fm_tgt = NULL;
if (ASCII_ISLOWER(name)) {
fm_tgt = &(buf->b_namedm[name - 'a']);
} else if (name == '"') {
fm_tgt = &(buf->b_last_cursor);
} else if (name == '^') {
fm_tgt = &(buf->b_last_insert);
} else if (name == '.') {
fm_tgt = &(buf->b_last_change);
} else {
return;
}
if (update && fm.timestamp < fm_tgt->timestamp) {
return;
}
if (fm_tgt->mark.lnum != 0) {
free_fmark(*fm_tgt);
}
*fm_tgt = fm;
}
/*
* Free items in the jumplist of window "wp".
*/
@ -1157,14 +1415,16 @@ void free_jumplist(win_T *wp)
{
int i;
for (i = 0; i < wp->w_jumplistlen; ++i)
xfree(wp->w_jumplist[i].fname);
for (i = 0; i < wp->w_jumplistlen; ++i) {
free_xfmark(wp->w_jumplist[i]);
}
}
void set_last_cursor(win_T *win)
{
if (win->w_buffer != NULL)
win->w_buffer->b_last_cursor = win->w_cursor;
if (win->w_buffer != NULL) {
RESET_FMARK(&win->w_buffer->b_last_cursor, win->w_cursor, 0);
}
}
#if defined(EXITFREE)
@ -1172,360 +1432,10 @@ void free_all_marks(void)
{
int i;
for (i = 0; i < NMARKS + EXTRA_MARKS; i++)
if (namedfm[i].fmark.mark.lnum != 0)
xfree(namedfm[i].fname);
for (i = 0; i < NGLOBALMARKS; i++) {
if (namedfm[i].fmark.mark.lnum != 0) {
free_xfmark(namedfm[i]);
}
}
}
#endif
int read_viminfo_filemark(vir_T *virp, int force)
{
char_u *str;
xfmark_T *fm;
int i;
/* We only get here if line[0] == '\'' or '-'.
* Illegal mark names are ignored (for future expansion). */
str = virp->vir_line + 1;
if (
*str <= 127 &&
((*virp->vir_line == '\'' && (ascii_isdigit(*str) || isupper(*str)))
|| (*virp->vir_line == '-' && *str == '\''))) {
if (*str == '\'') {
/* If the jumplist isn't full insert fmark as oldest entry */
if (curwin->w_jumplistlen == JUMPLISTSIZE)
fm = NULL;
else {
for (i = curwin->w_jumplistlen; i > 0; --i)
curwin->w_jumplist[i] = curwin->w_jumplist[i - 1];
++curwin->w_jumplistidx;
++curwin->w_jumplistlen;
fm = &curwin->w_jumplist[0];
fm->fmark.mark.lnum = 0;
fm->fname = NULL;
}
} else if (ascii_isdigit(*str))
fm = &namedfm[*str - '0' + NMARKS];
else { // is uppercase
assert(*str >= 'A' && *str <= 'Z');
fm = &namedfm[*str - 'A'];
}
if (fm != NULL && (fm->fmark.mark.lnum == 0 || force)) {
str = skipwhite(str + 1);
fm->fmark.mark.lnum = getdigits_long(&str);
str = skipwhite(str);
fm->fmark.mark.col = getdigits_int(&str);
fm->fmark.mark.coladd = 0;
fm->fmark.fnum = 0;
str = skipwhite(str);
xfree(fm->fname);
fm->fname = viminfo_readstring(virp, (int)(str - virp->vir_line),
FALSE);
}
}
return vim_fgets(virp->vir_line, LSIZE, virp->vir_fd);
}
void write_viminfo_filemarks(FILE *fp)
{
int i;
char_u *name;
buf_T *buf;
xfmark_T *fm;
if (get_viminfo_parameter('f') == 0)
return;
fputs(_("\n# File marks:\n"), fp);
/*
* Find a mark that is the same file and position as the cursor.
* That one, or else the last one is deleted.
* Move '0 to '1, '1 to '2, etc. until the matching one or '9
* Set '0 mark to current cursor position.
*/
if (curbuf->b_ffname != NULL && !removable(curbuf->b_ffname)) {
name = buflist_nr2name(curbuf->b_fnum, TRUE, FALSE);
for (i = NMARKS; i < NMARKS + EXTRA_MARKS - 1; ++i)
if (namedfm[i].fmark.mark.lnum == curwin->w_cursor.lnum
&& (namedfm[i].fname == NULL
? namedfm[i].fmark.fnum == curbuf->b_fnum
: (name != NULL
&& STRCMP(name, namedfm[i].fname) == 0)))
break;
xfree(name);
xfree(namedfm[i].fname);
for (; i > NMARKS; --i)
namedfm[i] = namedfm[i - 1];
namedfm[NMARKS].fmark.mark = curwin->w_cursor;
namedfm[NMARKS].fmark.fnum = curbuf->b_fnum;
namedfm[NMARKS].fname = NULL;
}
/* Write the filemarks '0 - '9 and 'A - 'Z */
for (i = 0; i < NMARKS + EXTRA_MARKS; i++)
write_one_filemark(fp, &namedfm[i], '\'',
i < NMARKS ? i + 'A' : i - NMARKS + '0');
/* Write the jumplist with -' */
fputs(_("\n# Jumplist (newest first):\n"), fp);
setpcmark(); /* add current cursor position */
cleanup_jumplist();
for (fm = &curwin->w_jumplist[curwin->w_jumplistlen - 1];
fm >= &curwin->w_jumplist[0]; --fm) {
if (fm->fmark.fnum == 0
|| ((buf = buflist_findnr(fm->fmark.fnum)) != NULL
&& !removable(buf->b_ffname)))
write_one_filemark(fp, fm, '-', '\'');
}
}
static void write_one_filemark(FILE *fp, xfmark_T *fm, int c1, int c2)
{
char_u *name;
if (fm->fmark.mark.lnum == 0) /* not set */
return;
if (fm->fmark.fnum != 0) /* there is a buffer */
name = buflist_nr2name(fm->fmark.fnum, TRUE, FALSE);
else
name = fm->fname; /* use name from .viminfo */
if (name != NULL && *name != NUL) {
fprintf(fp, "%c%c %" PRId64 " %" PRId64 " ",
c1, c2, (int64_t)fm->fmark.mark.lnum, (int64_t)fm->fmark.mark.col);
viminfo_writestring(fp, name);
}
if (fm->fmark.fnum != 0)
xfree(name);
}
/*
* Return TRUE if "name" is on removable media (depending on 'viminfo').
*/
int removable(char_u *name)
{
char_u *p;
char_u part[51];
int retval = FALSE;
size_t n;
name = home_replace_save(NULL, name);
for (p = p_viminfo; *p; ) {
copy_option_part(&p, part, 51, ", ");
if (part[0] == 'r') {
n = STRLEN(part + 1);
if (mb_strnicmp(part + 1, name, n) == 0) {
retval = TRUE;
break;
}
}
}
xfree(name);
return retval;
}
/*
* Write all the named marks for all buffers.
* Return the number of buffers for which marks have been written.
*/
int write_viminfo_marks(FILE *fp_out)
{
/*
* Set b_last_cursor for the all buffers that have a window.
*/
FOR_ALL_TAB_WINDOWS(tp, win) {
set_last_cursor(win);
}
fputs(_("\n# History of marks within files (newest to oldest):\n"), fp_out);
int count = 0;
FOR_ALL_BUFFERS(buf) {
/*
* Only write something if buffer has been loaded and at least one
* mark is set.
*/
if (buf->b_marks_read) {
bool is_mark_set = true;
if (buf->b_last_cursor.lnum == 0) {
is_mark_set = false;
for (int i = 0; i < NMARKS; i++) {
if (buf->b_namedm[i].lnum != 0) {
is_mark_set = true;
break;
}
}
}
if (is_mark_set && buf->b_ffname != NULL
&& buf->b_ffname[0] != NUL && !removable(buf->b_ffname)) {
home_replace(NULL, buf->b_ffname, IObuff, IOSIZE, TRUE);
fprintf(fp_out, "\n> ");
viminfo_writestring(fp_out, IObuff);
write_one_mark(fp_out, '"', &buf->b_last_cursor);
write_one_mark(fp_out, '^', &buf->b_last_insert);
write_one_mark(fp_out, '.', &buf->b_last_change);
/* changelist positions are stored oldest first */
for (int i = 0; i < buf->b_changelistlen; ++i) {
write_one_mark(fp_out, '+', &buf->b_changelist[i]);
}
for (int i = 0; i < NMARKS; i++) {
write_one_mark(fp_out, 'a' + i, &buf->b_namedm[i]);
}
count++;
}
}
}
return count;
}
static void write_one_mark(FILE *fp_out, int c, pos_T *pos)
{
if (pos->lnum != 0)
fprintf(fp_out, "\t%c\t%" PRId64 "\t%d\n", c,
(int64_t)pos->lnum, (int)pos->col);
}
/*
* Handle marks in the viminfo file:
* fp_out != NULL: copy marks for buffers not in buffer list
* fp_out == NULL && (flags & VIF_WANT_MARKS): read marks for curbuf only
* fp_out == NULL && (flags & VIF_GET_OLDFILES | VIF_FORCEIT): fill v:oldfiles
*/
void copy_viminfo_marks(vir_T *virp, FILE *fp_out, int count, int eof, int flags)
{
char_u *line = virp->vir_line;
buf_T *buf;
int num_marked_files;
int load_marks;
int copy_marks_out;
char_u *str;
int i;
char_u *p;
char_u *name_buf;
pos_T pos;
list_T *list = NULL;
name_buf = xmalloc(LSIZE);
*name_buf = NUL;
if (fp_out == NULL && (flags & (VIF_GET_OLDFILES | VIF_FORCEIT))) {
list = list_alloc();
set_vim_var_list(VV_OLDFILES, list);
}
num_marked_files = get_viminfo_parameter('\'');
while (!eof && (count < num_marked_files || fp_out == NULL)) {
if (line[0] != '>') {
if (line[0] != '\n' && line[0] != '\r' && line[0] != '#') {
if (viminfo_error("E576: ", _("Missing '>'"), line))
break; /* too many errors, return now */
}
eof = vim_fgets(line, LSIZE, virp->vir_fd);
continue; /* Skip this dud line */
}
/*
* Handle long line and translate escaped characters.
* Find file name, set str to start.
* Ignore leading and trailing white space.
*/
str = skipwhite(line + 1);
str = viminfo_readstring(virp, (int)(str - virp->vir_line), FALSE);
p = str + STRLEN(str);
while (p != str && (*p == NUL || ascii_isspace(*p)))
p--;
if (*p)
p++;
*p = NUL;
if (list != NULL)
list_append_string(list, str, -1);
/*
* If fp_out == NULL, load marks for current buffer.
* If fp_out != NULL, copy marks for buffers not in buflist.
*/
load_marks = copy_marks_out = FALSE;
if (fp_out == NULL) {
if ((flags & VIF_WANT_MARKS) && curbuf->b_ffname != NULL) {
if (*name_buf == NUL) /* only need to do this once */
home_replace(NULL, curbuf->b_ffname, name_buf, LSIZE, TRUE);
if (fnamecmp(str, name_buf) == 0)
load_marks = TRUE;
}
} else { /* fp_out != NULL */
/* This is slow if there are many buffers!! */
buf = NULL;
FOR_ALL_BUFFERS(bp) {
if (bp->b_ffname != NULL) {
home_replace(NULL, bp->b_ffname, name_buf, LSIZE, TRUE);
if (fnamecmp(str, name_buf) == 0) {
buf = bp;
break;
}
}
}
/*
* copy marks if the buffer has not been loaded
*/
if (buf == NULL || !buf->b_marks_read) {
copy_marks_out = TRUE;
fputs("\n> ", fp_out);
viminfo_writestring(fp_out, str);
count++;
}
}
xfree(str);
pos.coladd = 0;
while (!(eof = viminfo_readline(virp)) && line[0] == TAB) {
if (load_marks) {
if (line[1] != NUL) {
int64_t lnum_64;
unsigned int u;
sscanf((char *)line + 2, "%" SCNd64 "%u", &lnum_64, &u);
// safely downcast to linenr_T (long); remove when linenr_T refactored
assert(lnum_64 <= LONG_MAX);
pos.lnum = (linenr_T)lnum_64;
assert(u <= INT_MAX);
pos.col = (colnr_T)u;
switch (line[1]) {
case '"': curbuf->b_last_cursor = pos; break;
case '^': curbuf->b_last_insert = pos; break;
case '.': curbuf->b_last_change = pos; break;
case '+':
/* changelist positions are stored oldest
* first */
if (curbuf->b_changelistlen == JUMPLISTSIZE)
/* list is full, remove oldest entry */
memmove(curbuf->b_changelist,
curbuf->b_changelist + 1,
sizeof(pos_T) * (JUMPLISTSIZE - 1));
else
++curbuf->b_changelistlen;
curbuf->b_changelist[
curbuf->b_changelistlen - 1] = pos;
break;
default: if ((i = line[1] - 'a') >= 0 && i < NMARKS)
curbuf->b_namedm[i] = pos;
}
}
} else if (copy_marks_out)
fputs((char *)line, fp_out);
}
if (load_marks) {
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_buffer == curbuf)
wp->w_changelistidx = curbuf->b_changelistlen;
}
break;
}
}
xfree(name_buf);
}

View File

@ -4,6 +4,22 @@
#include "nvim/buffer_defs.h"
#include "nvim/mark_defs.h"
#include "nvim/pos.h"
#include "nvim/os/time.h"
/// Free and set fmark using given value
#define RESET_FMARK(fmarkp_, mark_, fnum_) \
do { \
fmark_T *const fmarkp__ = fmarkp_; \
free_fmark(*fmarkp__); \
fmarkp__->mark = mark_; \
fmarkp__->fnum = fnum_; \
fmarkp__->timestamp = os_time(); \
fmarkp__->additional_data = NULL; \
} while (0)
/// Clear given fmark
#define CLEAR_FMARK(fmarkp_) \
RESET_FMARK(fmarkp_, ((pos_T) {0, 0, 0}), 0)
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "mark.h.generated.h"

View File

@ -2,25 +2,41 @@
#define NVIM_MARK_DEFS_H
#include "nvim/pos.h"
#include "nvim/os/time.h"
#include "nvim/api/private/defs.h"
/*
* marks: positions in a file
* (a normal mark is a lnum/col pair, the same as a file position)
*/
#define NMARKS ('z' - 'a' + 1) /* max. # of named marks */
#define JUMPLISTSIZE 100 /* max. # of marks in jump list */
#define TAGSTACKSIZE 20 /* max. # of tags in tag stack */
/// Number of possible numbered global marks
#define EXTRA_MARKS ('9' - '0' + 1)
/// Maximum possible number of letter marks
#define NMARKS ('z' - 'a' + 1)
/// Total possible number of global marks
#define NGLOBALMARKS (NMARKS + EXTRA_MARKS)
/// Maximum number of marks in jump list
#define JUMPLISTSIZE 100
/// Maximum number of tags in tag stack
#define TAGSTACKSIZE 20
/// Structure defining single local mark
typedef struct filemark {
pos_T mark; /* cursor position */
int fnum; /* file number */
pos_T mark; ///< Cursor position.
int fnum; ///< File number.
Timestamp timestamp; ///< Time when this mark was last set.
Dictionary *additional_data; ///< Additional data from ShaDa file.
} fmark_T;
/* Xtended file mark: also has a file name */
/// Structure defining extended mark (mark with file name attached)
typedef struct xfilemark {
fmark_T fmark;
char_u *fname; /* file name, used when fnum == 0 */
fmark_T fmark; ///< Actual mark.
char_u *fname; ///< File name, used when fnum == 0.
} xfmark_T;
#endif // NVIM_MARK_DEFS_H

View File

@ -66,7 +66,7 @@
* (4) The encoding of the file is specified with 'fileencoding'. Conversion
* is to be done when it's different from 'encoding'.
*
* The viminfo file is a special case: Only text is converted, not file names.
* The ShaDa file is a special case: Only text is converted, not file names.
* Vim scripts may contain an ":encoding" command. This has an effect for
* some commands, like ":menutrans"
*/

View File

@ -2042,8 +2042,7 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra
/* set the '. mark */
if (!cmdmod.keepjumps) {
curbuf->b_last_change.lnum = lnum;
curbuf->b_last_change.col = col;
RESET_FMARK(&curbuf->b_last_change, ((pos_T) {lnum, col, 0}), 0);
/* Create a new entry if a new undo-able change was started or we
* don't have an entry yet. */
@ -2095,7 +2094,7 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra
}
}
curbuf->b_changelist[curbuf->b_changelistlen - 1] =
curbuf->b_last_change;
curbuf->b_last_change.mark;
/* The current window is always after the last change, so that "g,"
* takes you back to it. */
curwin->w_changelistidx = curbuf->b_changelistlen;

View File

@ -6327,8 +6327,8 @@ static void nv_g_cmd(cmdarg_T *cap)
* "gi": start Insert at the last position.
*/
case 'i':
if (curbuf->b_last_insert.lnum != 0) {
curwin->w_cursor = curbuf->b_last_insert;
if (curbuf->b_last_insert.mark.lnum != 0) {
curwin->w_cursor = curbuf->b_last_insert.mark;
check_cursor_lnum();
i = (int)STRLEN(get_cursor_line_ptr());
if (curwin->w_cursor.col > (colnr_T)i) {

View File

@ -50,6 +50,9 @@
#include "nvim/undo.h"
#include "nvim/window.h"
#include "nvim/os/input.h"
#include "nvim/os/time.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
/*
* Registers:
@ -62,14 +65,14 @@
*/
#define DELETION_REGISTER 36
#define NUM_SAVED_REGISTERS 37
// The following registers should not be saved in viminfo:
// The following registers should not be saved in ShaDa file:
#define STAR_REGISTER 37
#define PLUS_REGISTER 38
#define NUM_REGISTERS 39
static yankreg_T y_regs[NUM_REGISTERS];
static yankreg_T *y_previous = NULL; /* ptr to last written yankreg */
static yankreg_T *y_previous = NULL; /* ptr to last written yankreg */
static bool clipboard_didwarn_unnamed = false;
@ -746,6 +749,31 @@ typedef enum {
YREG_PUT,
} yreg_mode_t;
/// Convert register name into register index
///
/// @param[in] regname Register name.
///
/// @return Index in y_regs array or -1 if register name was not recognized.
static inline int reg_index(const int regname)
FUNC_ATTR_CONST
{
if (ascii_isdigit(regname)) {
return regname - '0';
} else if (ASCII_ISLOWER(regname)) {
return CharOrdLow(regname) + 10;
} else if (ASCII_ISUPPER(regname)) {
return CharOrdUp(regname) + 10;
} else if (regname == '-') {
return DELETION_REGISTER;
} else if (regname == '*') {
return STAR_REGISTER;
} else if (regname == '+') {
return PLUS_REGISTER;
} else {
return -1;
}
}
/// Return yankreg_T to use, according to the value of `regname`.
/// Cannot handle the '_' (black hole) register.
/// Must only be called with a valid register name!
@ -778,19 +806,11 @@ yankreg_T *get_yank_register(int regname, int mode)
return y_previous;
}
int i = 0; // when not 0-9, a-z, A-Z or '-'/'+'/'*': use register 0
if (ascii_isdigit(regname))
i = regname - '0';
else if (ASCII_ISLOWER(regname))
i = CharOrdLow(regname) + 10;
else if (ASCII_ISUPPER(regname)) {
i = CharOrdUp(regname) + 10;
} else if (regname == '-')
i = DELETION_REGISTER;
else if (regname == '*')
i = STAR_REGISTER;
else if (regname == '+')
i = PLUS_REGISTER;
int i = reg_index(regname);
// when not 0-9, a-z, A-Z or '-'/'+'/'*': use register 0
if (i == -1) {
i = 0;
}
reg = &y_regs[i];
if (mode == YREG_YANK) {
@ -890,6 +910,20 @@ int do_record(int c)
return retval;
}
static void set_yreg_additional_data(yankreg_T *reg,
Dictionary *additional_data)
FUNC_ATTR_NONNULL_ARG(1)
{
if (reg->additional_data == additional_data) {
return;
}
if (reg->additional_data != NULL) {
api_free_dictionary(*reg->additional_data);
free(reg->additional_data);
}
reg->additional_data = additional_data;
}
/*
* Stuff string "p" into yank register "regname" as a single line (append if
* uppercase). "p" must have been alloced.
@ -919,11 +953,13 @@ static int stuff_yank(int regname, char_u *p)
*pp = lp;
} else {
free_register(reg);
set_yreg_additional_data(reg, NULL);
reg->y_array = (char_u **)xmalloc(sizeof(char_u *));
reg->y_array[0] = p;
reg->y_size = 1;
reg->y_type = MCHAR; /* used to be MLINE, why? */
}
reg->timestamp = os_time();
return OK;
}
@ -2266,10 +2302,7 @@ int op_change(oparg_T *oap)
*/
void init_yank(void)
{
int i;
for (i = 0; i < NUM_REGISTERS; i++)
y_regs[i].y_array = NULL;
memset(&(y_regs[0]), 0, sizeof(y_regs));
}
#if defined(EXITFREE)
@ -2291,6 +2324,7 @@ void clear_registers(void)
void free_register(yankreg_T *reg)
FUNC_ATTR_NONNULL_ALL
{
set_yreg_additional_data(reg, NULL);
if (reg->y_array != NULL) {
long i;
@ -2369,6 +2403,8 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
reg->y_type = yanktype; /* set the yank register type */
reg->y_width = 0;
reg->y_array = xcalloc(yanklines, sizeof(char_u *));
set_yreg_additional_data(reg, NULL);
reg->timestamp = os_time();
y_idx = 0;
lnum = oap->start.lnum;
@ -4433,171 +4469,6 @@ int do_addsub(int command, linenr_T Prenum1)
return OK;
}
int read_viminfo_register(vir_T *virp, int force)
{
int eof;
int do_it = TRUE;
int size;
int limit;
int set_prev = FALSE;
char_u *str;
char_u **array = NULL;
/* We only get here (hopefully) if line[0] == '"' */
str = virp->vir_line + 1;
/* If the line starts with "" this is the y_previous register. */
if (*str == '"') {
set_prev = TRUE;
str++;
}
if (!ASCII_ISALNUM(*str) && *str != '-') {
if (viminfo_error("E577: ", _("Illegal register name"), virp->vir_line))
return TRUE; /* too many errors, pretend end-of-file */
do_it = FALSE;
}
yankreg_T *reg = get_yank_register(*str++, YREG_PUT);
if (!force && reg->y_array != NULL)
do_it = FALSE;
if (*str == '@') {
/* "x@: register x used for @@ */
if (force || execreg_lastc == NUL)
execreg_lastc = str[-1];
}
size = 0;
limit = 100; /* Optimized for registers containing <= 100 lines */
if (do_it) {
if (set_prev) {
y_previous = reg;
}
free_register(reg);
array = xmalloc(limit * sizeof(char_u *));
str = skipwhite(skiptowhite(str));
if (STRNCMP(str, "CHAR", 4) == 0) {
reg->y_type = MCHAR;
} else if (STRNCMP(str, "BLOCK", 5) == 0) {
reg->y_type = MBLOCK;
} else {
reg->y_type = MLINE;
}
/* get the block width; if it's missing we get a zero, which is OK */
str = skipwhite(skiptowhite(str));
reg->y_width = getdigits_int(&str);
}
while (!(eof = viminfo_readline(virp))
&& (virp->vir_line[0] == TAB || virp->vir_line[0] == '<')) {
if (do_it) {
if (size >= limit) {
limit *= 2;
array = xrealloc(array, limit * sizeof(char_u *));
}
array[size++] = viminfo_readstring(virp, 1, TRUE);
}
}
if (do_it) {
if (size == 0) {
xfree(array);
} else if (size < limit) {
reg->y_array = xrealloc(array, size * sizeof(char_u *));
} else {
reg->y_array = array;
}
reg->y_size = size;
}
return eof;
}
void write_viminfo_registers(FILE *fp)
{
int i, j;
char_u *type;
char_u c;
int num_lines;
int max_num_lines;
int max_kbyte;
long len;
fputs(_("\n# Registers:\n"), fp);
/* Get '<' value, use old '"' value if '<' is not found. */
max_num_lines = get_viminfo_parameter('<');
if (max_num_lines < 0)
max_num_lines = get_viminfo_parameter('"');
if (max_num_lines == 0)
return;
max_kbyte = get_viminfo_parameter('s');
if (max_kbyte == 0)
return;
// don't include clipboard registers '*'/'+'
for (i = 0; i < NUM_SAVED_REGISTERS; i++) {
if (y_regs[i].y_array == NULL)
continue;
/* Skip empty registers. */
num_lines = y_regs[i].y_size;
if (num_lines == 0
|| (num_lines == 1 && y_regs[i].y_type == MCHAR
&& *y_regs[i].y_array[0] == NUL))
continue;
if (max_kbyte > 0) {
/* Skip register if there is more text than the maximum size. */
len = 0;
for (j = 0; j < num_lines; j++)
len += (long)STRLEN(y_regs[i].y_array[j]) + 1L;
if (len > (long)max_kbyte * 1024L)
continue;
}
switch (y_regs[i].y_type) {
case MLINE:
type = (char_u *)"LINE";
break;
case MCHAR:
type = (char_u *)"CHAR";
break;
case MBLOCK:
type = (char_u *)"BLOCK";
break;
default:
sprintf((char *)IObuff, _("E574: Unknown register type %d"),
y_regs[i].y_type);
emsg(IObuff);
type = (char_u *)"LINE";
break;
}
if (y_previous == &y_regs[i])
fprintf(fp, "\"");
c = get_register_name(i);
fprintf(fp, "\"%c", c);
if (c == execreg_lastc)
fprintf(fp, "@");
fprintf(fp, "\t%s\t%d\n", type,
(int)y_regs[i].y_width
);
/* If max_num_lines < 0, then we save ALL the lines in the register */
if (max_num_lines > 0 && num_lines > max_num_lines)
num_lines = max_num_lines;
for (j = 0; j < num_lines; j++) {
putc('\t', fp);
viminfo_writestring(fp, y_regs[i].y_array[j]);
}
}
}
/*
* Return the type of a register.
* Used for getregtype()
@ -4739,7 +4610,6 @@ void *get_reg_contents(int regname, int flags)
return retval;
}
static yankreg_T *init_write_reg(int name, yankreg_T **old_y_previous, bool must_append)
{
if (!valid_yank_reg(name, true)) { // check for valid reg name
@ -4973,6 +4843,8 @@ static void str_to_reg(yankreg_T *y_ptr, int yank_type, const char_u *str,
}
y_ptr->y_type = type;
y_ptr->y_size = lnum;
set_yreg_additional_data(y_ptr, NULL);
y_ptr->timestamp = os_time();
if (type == MBLOCK) {
y_ptr->y_width = (blocklen == -1 ? (colnr_T) maxlen - 1 : blocklen);
} else {
@ -5363,6 +5235,10 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet)
reg->y_array = xcalloc(lines->lv_len, sizeof(uint8_t *));
reg->y_size = lines->lv_len;
reg->additional_data = NULL;
reg->timestamp = 0;
// Timestamp is not saved for clipboard registers because clipboard registers
// are not saved in the viminfo.
int i = 0;
for (listitem_T *li = lines->lv_first; li != NULL; li = li->li_next) {
@ -5411,6 +5287,8 @@ err:
}
reg->y_array = NULL;
reg->y_size = 0;
reg->additional_data = NULL;
reg->timestamp = 0;
if (errmsg) {
EMSG("clipboard: provider returned invalid data");
}
@ -5478,3 +5356,70 @@ void end_global_changes(void)
clipboard_needs_update = false;
}
}
/// Check whether register is empty
static inline bool reg_empty(const yankreg_T *const reg)
FUNC_ATTR_CONST
{
return (reg->y_array == NULL
|| reg->y_size == 0
|| (reg->y_size == 1
&& reg->y_type == MCHAR
&& *(reg->y_array[0]) == NUL));
}
/// Iterate over registerrs
///
/// @param[in] iter Iterator. Pass NULL to start iteration.
/// @param[out] name Register name.
/// @param[out] reg Register contents.
///
/// @return Pointer that needs to be passed to next `op_register_iter` call or
/// NULL if iteration is over.
const void *op_register_iter(const void *const iter, char *const name,
yankreg_T *const reg)
FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT
{
const yankreg_T *iter_reg = (iter == NULL
? &(y_regs[0])
: (const yankreg_T *const) iter);
while (reg_empty(iter_reg) && iter_reg - &(y_regs[0]) < NUM_SAVED_REGISTERS) {
iter_reg++;
}
if (reg_empty(iter_reg)) {
*reg = (yankreg_T) {.y_array = NULL};
return NULL;
}
size_t iter_off = iter_reg - &(y_regs[0]);
*name = (char) get_register_name(iter_off);
*reg = *iter_reg;
while (++iter_reg - &(y_regs[0]) < NUM_SAVED_REGISTERS) {
if (!reg_empty(iter_reg)) {
return (void *) iter_reg;
}
}
return NULL;
}
/// Get a number of non-empty registers
size_t op_register_amount(void)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
size_t ret = 0;
for (size_t i = 0; i < NUM_SAVED_REGISTERS; i++) {
if (!reg_empty(y_regs + i)) {
ret++;
}
}
return ret;
}
/// Set register to a given value
void register_set(const char name, const yankreg_T reg)
{
int i = reg_index(name);
if (i == -1) {
return;
}
y_regs[i] = reg;
}

View File

@ -4,6 +4,8 @@
#include <stdbool.h>
#include "nvim/types.h"
#include "nvim/api/private/defs.h"
#include "nvim/os/time.h"
typedef int (*Indenter)(void);
@ -47,14 +49,6 @@ typedef int (*Indenter)(void);
#define OP_FORMAT2 26 /* "gw" format operator, keeps cursor pos */
#define OP_FUNCTION 27 /* "g@" call 'operatorfunc' */
/// Contents of a yank (read-write) register
typedef struct yankreg {
char_u **y_array; ///< pointer to array of line pointers
linenr_T y_size; ///< number of lines in y_array
char_u y_type; ///< MLINE, MCHAR or MBLOCK
colnr_T y_width; ///< only set if y_type == MBLOCK
} yankreg_T;
/// Flags for get_reg_contents().
enum GRegFlags {
kGRegNoExpr = 1, ///< Do not allow expression register.
@ -62,6 +56,16 @@ enum GRegFlags {
kGRegList = 4 ///< Return list.
};
/// Definition of one register
typedef struct yankreg {
char_u **y_array; ///< Pointer to an array of line pointers.
linenr_T y_size; ///< Number of lines in y_array.
char_u y_type; ///< Register type: MLINE, MCHAR or MBLOCK.
colnr_T y_width; ///< Register width (only valid for y_type == MBLOCK).
Timestamp timestamp; ///< Time when register was last modified.
Dictionary *additional_data; ///< Additional data from ShaDa file.
} yankreg_T;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ops.h.generated.h"
#endif

View File

@ -103,3 +103,12 @@ struct tm *os_get_localtime(struct tm *result) FUNC_ATTR_NONNULL_ALL
time_t rawtime = time(NULL);
return os_localtime_r(&rawtime, result);
}
/// Obtains the current UNIX timestamp
///
/// @return Seconds since epoch.
Timestamp os_time(void)
FUNC_ATTR_WARN_UNUSED_RESULT
{
return (Timestamp) time(NULL);
}

View File

@ -5,6 +5,8 @@
#include <stdbool.h>
#include <time.h>
typedef time_t Timestamp;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/time.h.generated.h"
#endif

View File

@ -43,8 +43,8 @@
#ifndef VIMRC_FILE
# define VIMRC_FILE ".nvimrc"
#endif
#ifndef VIMINFO_FILE
# define VIMINFO_FILE "~/.nviminfo"
#ifndef SHADA_FILE
# define SHADA_FILE "~/.nvim/shada/main.shada"
#endif
// Default for 'backupdir'.

View File

@ -9,7 +9,7 @@
// Defines needed to fix the build on Windows:
// - USR_EXRC_FILE
// - USR_VIMRC_FILE
// - VIMINFO_FILE
// - SHADA_FILE
// - DFLT_DIR
// - DFLT_BDIR
// - DFLT_VDIR

View File

@ -51,6 +51,7 @@
#include "nvim/ui.h"
#include "nvim/window.h"
#include "nvim/os/time.h"
#include "nvim/api/private/helpers.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@ -79,23 +80,6 @@
* Henry Spencer's regular expression library. See regexp.c.
*/
/* The offset for a search command is store in a soff struct */
/* Note: only spats[0].off is really used */
struct soffset {
int dir; /* search direction, '/' or '?' */
int line; /* search has line offset */
int end; /* search set cursor at end */
long off; /* line or char offset */
};
/* A search pattern and its attributes are stored in a spat struct */
struct spat {
char_u *pat; /* the pattern (in allocated memory) or NULL */
int magic; /* magicness of the pattern */
int no_scs; /* no smartcase for this pattern */
struct soffset off;
};
/*
* Two search patterns are remembered: One for the :substitute command and
* one for other searches. last_idx points to the one that was used the last
@ -103,8 +87,10 @@ struct spat {
*/
static struct spat spats[2] =
{
{NULL, TRUE, FALSE, {'/', 0, 0, 0L}}, /* last used search pat */
{NULL, TRUE, FALSE, {'/', 0, 0, 0L}} /* last used substitute pat */
// Last used search pattern
[0] = {NULL, true, false, 0, {'/', false, false, 0L}, NULL},
// Last used substitute pattern
[1] = {NULL, true, false, 0, {'/', false, false, 0L}, NULL}
};
static int last_idx = 0; /* index in spats[] for RE_LAST */
@ -256,10 +242,12 @@ char_u *reverse_text(char_u *s) FUNC_ATTR_NONNULL_RET
void save_re_pat(int idx, char_u *pat, int magic)
{
if (spats[idx].pat != pat) {
xfree(spats[idx].pat);
free_spat(&spats[idx]);
spats[idx].pat = vim_strsave(pat);
spats[idx].magic = magic;
spats[idx].no_scs = no_smartcase;
spats[idx].timestamp = os_time();
spats[idx].additional_data = NULL;
last_idx = idx;
/* If 'hlsearch' set and search pat changed: need redraw. */
if (p_hls)
@ -291,21 +279,30 @@ void save_search_patterns(void)
void restore_search_patterns(void)
{
if (--save_level == 0) {
xfree(spats[0].pat);
free_spat(&spats[0]);
spats[0] = saved_spats[0];
set_vv_searchforward();
xfree(spats[1].pat);
free_spat(&spats[1]);
spats[1] = saved_spats[1];
last_idx = saved_last_idx;
SET_NO_HLSEARCH(saved_no_hlsearch);
}
}
static inline void free_spat(struct spat *const spat)
{
xfree(spat->pat);
if (spat->additional_data != NULL) {
api_free_dictionary(*spat->additional_data);
xfree(spat->additional_data);
}
}
#if defined(EXITFREE)
void free_search_patterns(void)
{
xfree(spats[0].pat);
xfree(spats[1].pat);
free_spat(&spats[0]);
free_spat(&spats[1]);
if (mr_pattern_alloced) {
xfree(mr_pattern);
@ -414,17 +411,19 @@ void reset_search_dir(void)
}
/*
* Set the last search pattern. For ":let @/ =" and viminfo.
* Set the last search pattern. For ":let @/ =" and ShaDa file.
* Also set the saved search pattern, so that this works in an autocommand.
*/
void set_last_search_pat(const char_u *s, int idx, int magic, int setlast)
{
xfree(spats[idx].pat);
free_spat(&spats[idx]);
/* An empty string means that nothing should be matched. */
if (*s == NUL)
spats[idx].pat = NULL;
else
spats[idx].pat = (char_u *) xstrdup((char *) s);
spats[idx].timestamp = os_time();
spats[idx].additional_data = NULL;
spats[idx].magic = magic;
spats[idx].no_scs = FALSE;
spats[idx].off.dir = '/';
@ -435,7 +434,7 @@ void set_last_search_pat(const char_u *s, int idx, int magic, int setlast)
if (setlast)
last_idx = idx;
if (save_level) {
xfree(saved_spats[idx].pat);
free_spat(&saved_spats[idx]);
saved_spats[idx] = spats[0];
if (spats[idx].pat == NULL)
saved_spats[idx].pat = NULL;
@ -1053,7 +1052,7 @@ int do_search(
else if ((options & SEARCH_OPT) &&
(*p == 'e' || *p == 's' || *p == 'b')) {
if (*p == 'e') /* end */
spats[0].off.end = SEARCH_END;
spats[0].off.end = true;
++p;
}
if (ascii_isdigit(*p) || *p == '+' || *p == '-') { /* got an offset */
@ -1166,12 +1165,13 @@ int do_search(
lrFswap(searchstr,0);
c = searchit(curwin, curbuf, &pos, dirc == '/' ? FORWARD : BACKWARD,
searchstr, count, spats[0].off.end + (options &
(SEARCH_KEEP + SEARCH_PEEK +
SEARCH_HIS
+ SEARCH_MSG + SEARCH_START
+ ((pat != NULL && *pat ==
';') ? 0 : SEARCH_NOOF))),
searchstr, count, (spats[0].off.end * SEARCH_END
+ (options &
(SEARCH_KEEP + SEARCH_PEEK +
SEARCH_HIS
+ SEARCH_MSG + SEARCH_START
+ ((pat != NULL && *pat ==
';') ? 0 : SEARCH_NOOF)))),
RE_LAST, (linenr_T)0, tm);
if (dircp != NULL)
@ -4605,105 +4605,45 @@ static void show_pat_in_path(char_u *line, int type, int did_show, int action, F
}
}
int read_viminfo_search_pattern(vir_T *virp, int force)
/// Get last search pattern
void get_search_pattern(SearchPattern *const pat)
{
char_u *lp;
int idx = -1;
int magic = FALSE;
int no_scs = FALSE;
int off_line = FALSE;
int off_end = 0;
long off = 0;
int setlast = FALSE;
static int hlsearch_on = FALSE;
char_u *val;
/*
* Old line types:
* "/pat", "&pat": search/subst. pat
* "~/pat", "~&pat": last used search/subst. pat
* New line types:
* "~h", "~H": hlsearch highlighting off/on
* "~<magic><smartcase><line><end><off><last><which>pat"
* <magic>: 'm' off, 'M' on
* <smartcase>: 's' off, 'S' on
* <line>: 'L' line offset, 'l' char offset
* <end>: 'E' from end, 'e' from start
* <off>: decimal, offset
* <last>: '~' last used pattern
* <which>: '/' search pat, '&' subst. pat
*/
lp = virp->vir_line;
if (lp[0] == '~' && (lp[1] == 'm' || lp[1] == 'M')) { /* new line type */
if (lp[1] == 'M') /* magic on */
magic = TRUE;
if (lp[2] == 's')
no_scs = TRUE;
if (lp[3] == 'L')
off_line = TRUE;
if (lp[4] == 'E')
off_end = SEARCH_END;
lp += 5;
off = getdigits_long(&lp);
}
if (lp[0] == '~') { /* use this pattern for last-used pattern */
setlast = TRUE;
lp++;
}
if (lp[0] == '/')
idx = RE_SEARCH;
else if (lp[0] == '&')
idx = RE_SUBST;
else if (lp[0] == 'h') /* ~h: 'hlsearch' highlighting off */
hlsearch_on = FALSE;
else if (lp[0] == 'H') /* ~H: 'hlsearch' highlighting on */
hlsearch_on = TRUE;
if (idx >= 0) {
if (force || spats[idx].pat == NULL) {
val = viminfo_readstring(virp, (int)(lp - virp->vir_line + 1), TRUE);
set_last_search_pat(val, idx, magic, setlast);
xfree(val);
spats[idx].no_scs = no_scs;
spats[idx].off.line = off_line;
spats[idx].off.end = off_end;
spats[idx].off.off = off;
if (setlast) {
SET_NO_HLSEARCH(!hlsearch_on);
}
}
}
return viminfo_readline(virp);
memcpy(pat, &(spats[0]), sizeof(spats[0]));
}
void write_viminfo_search_pattern(FILE *fp)
/// Get last substitute pattern
void get_substitute_pattern(SearchPattern *const pat)
{
if (get_viminfo_parameter('/') != 0) {
fprintf(fp, "\n# hlsearch on (H) or off (h):\n~%c",
(no_hlsearch || find_viminfo_parameter('h') != NULL) ? 'h' : 'H');
wvsp_one(fp, RE_SEARCH, "", '/');
wvsp_one(fp, RE_SUBST, _("Substitute "), '&');
}
memcpy(pat, &(spats[1]), sizeof(spats[1]));
memset(&(pat->off), 0, sizeof(pat->off));
}
static void
wvsp_one (
FILE *fp, /* file to write to */
int idx, /* spats[] index */
char *s, /* search pat */
int sc /* dir char */
)
/// Set last search pattern
void set_search_pattern(const SearchPattern pat)
{
if (spats[idx].pat != NULL) {
fprintf(fp, _("\n# Last %sSearch Pattern:\n~"), s);
/* off.dir is not stored, it's reset to forward */
fprintf(fp, "%c%c%c%c%" PRId64 "%s%c",
spats[idx].magic ? 'M' : 'm', /* magic */
spats[idx].no_scs ? 's' : 'S', /* smartcase */
spats[idx].off.line ? 'L' : 'l', /* line offset */
spats[idx].off.end ? 'E' : 'e', /* offset from end */
(int64_t)spats[idx].off.off, /* offset */
last_idx == idx ? "~" : "", /* last used pat */
sc);
viminfo_writestring(fp, spats[idx].pat);
}
free_spat(&spats[0]);
memcpy(&(spats[0]), &pat, sizeof(spats[0]));
}
/// Set last substitute pattern
void set_substitute_pattern(const SearchPattern pat)
{
free_spat(&spats[1]);
memcpy(&(spats[1]), &pat, sizeof(spats[1]));
memset(&(spats[1].off), 0, sizeof(spats[1].off));
}
/// Set last used search pattern
///
/// @param[in] is_substitute_pattern If true set substitute pattern as last
/// used. Otherwise sets search pattern.
void set_last_used_pattern(const bool is_substitute_pattern)
{
last_idx = (is_substitute_pattern ? 1 : 0);
}
/// Returns true if search pattern was the last used one
bool search_was_last_used(void)
{
return last_idx == 0;
}

View File

@ -1,6 +1,9 @@
#ifndef NVIM_SEARCH_H
#define NVIM_SEARCH_H
#include <stdbool.h>
#include <stdint.h>
/* Values for the find_pattern_in_path() function args 'type' and 'action': */
#define FIND_ANY 1
#define FIND_DEFINE 2
@ -39,6 +42,27 @@
#define RE_BOTH 2 /* save pat in both patterns */
#define RE_LAST 2 /* use last used pattern if "pat" is NULL */
/// Structure containing offset definition for the last search pattern
///
/// @note Only offset for the last search pattern is used, not for the last
/// substitute pattern.
typedef struct soffset {
char dir; ///< Search direction: forward ('/') or backward ('?')
bool line; ///< True if search has line offset.
bool end; ///< True if search sets cursor at the end.
int64_t off; ///< Actual offset value.
} SearchOffset;
/// Structure containing last search pattern and its attributes.
typedef struct spat {
char_u *pat; ///< The pattern (in allocated memory) or NULL.
bool magic; ///< Magicness of the pattern.
bool no_scs; ///< No smartcase for this pattern.
Timestamp timestamp; ///< Time of the last change.
SearchOffset off; ///< Pattern offset.
Dictionary *additional_data; ///< Additional data from ShaDa file.
} SearchPattern;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "search.h.generated.h"
#endif

3454
src/nvim/shada.c Normal file

File diff suppressed because it is too large Load Diff

18
src/nvim/shada.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef NVIM_SHADA_H
#define NVIM_SHADA_H
typedef long ShadaPosition;
/// Flags for shada_read_file and children
enum {
kShaDaWantInfo = 1, ///< Load non-mark information
kShaDaWantMarks = 2, ///< Load file marks
kShaDaForceit = 4, ///< Overwrite info already read
kShaDaGetOldfiles = 8, ///< Load v:oldfiles.
};
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "shada.h.generated.h"
#endif
#endif // NVIM_SHADA_H

View File

@ -130,7 +130,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, {INIT_POS_T(0, 0, 0), 0}, 0, 0};
static taggy_T ptag_entry = {NULL, {INIT_POS_T(0, 0, 0), 0, 0, NULL}, 0, 0};
/*
* Jump to tag; handling of tag commands and tag stack

View File

@ -111,6 +111,7 @@
#include "nvim/types.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
#include "nvim/api/private/helpers.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "undo.c.generated.h"
@ -325,6 +326,17 @@ static long get_undolevel(void)
return curbuf->b_p_ul;
}
static inline void zero_fmark_additional_data(fmark_T *fmarks)
{
for (size_t i = 0; i < NMARKS; i++) {
if (fmarks[i].additional_data != NULL) {
api_free_dictionary(*fmarks[i].additional_data);
free(fmarks[i].additional_data);
fmarks[i].additional_data = NULL;
}
}
}
/*
* Common code for various ways to save text before a change.
* "top" is the line above the first changed line.
@ -467,7 +479,9 @@ int u_savecommon(linenr_T top, linenr_T bot, linenr_T newbot, int reload)
((curbuf->b_ml.ml_flags & ML_EMPTY) ? UH_EMPTYBUF : 0);
/* save named marks and Visual marks for undo */
memmove(uhp->uh_namedm, curbuf->b_namedm, sizeof(pos_T) * NMARKS);
zero_fmark_additional_data(curbuf->b_namedm);
memmove(uhp->uh_namedm, curbuf->b_namedm,
sizeof(curbuf->b_namedm[0]) * NMARKS);
uhp->uh_visual = curbuf->b_visual;
curbuf->b_u_newhead = uhp;
@ -785,7 +799,7 @@ static bool serialize_uhp(bufinfo_T *bi, u_header_T *uhp)
undo_write_bytes(bi, (uintmax_t)uhp->uh_flags, 2);
// Assume NMARKS will stay the same.
for (size_t i = 0; i < (size_t)NMARKS; i++) {
serialize_pos(bi, uhp->uh_namedm[i]);
serialize_pos(bi, uhp->uh_namedm[i].mark);
}
serialize_visualinfo(bi, &uhp->uh_visual);
uint8_t time_buf[8];
@ -832,7 +846,9 @@ static u_header_T *unserialize_uhp(bufinfo_T *bi, char_u *file_name)
uhp->uh_cursor_vcol = undo_read_4c(bi);
uhp->uh_flags = undo_read_2c(bi);
for (size_t i = 0; i < (size_t)NMARKS; i++) {
unserialize_pos(bi, &uhp->uh_namedm[i]);
unserialize_pos(bi, &uhp->uh_namedm[i].mark);
uhp->uh_namedm[i].timestamp = 0;
uhp->uh_namedm[i].fnum = 0;
}
unserialize_visualinfo(bi, &uhp->uh_visual);
uhp->uh_time = undo_read_time(bi);
@ -2009,7 +2025,7 @@ static void u_undoredo(int undo)
u_entry_T *newlist = NULL;
int old_flags;
int new_flags;
pos_T namedm[NMARKS];
fmark_T namedm[NMARKS];
visualinfo_T visualinfo;
int empty_buffer; /* buffer became empty */
u_header_T *curhead = curbuf->b_u_curhead;
@ -2029,7 +2045,8 @@ static void u_undoredo(int undo)
/*
* save marks before undo/redo
*/
memmove(namedm, curbuf->b_namedm, sizeof(pos_T) * NMARKS);
zero_fmark_additional_data(curbuf->b_namedm);
memmove(namedm, curbuf->b_namedm, sizeof(curbuf->b_namedm[0]) * NMARKS);
visualinfo = curbuf->b_visual;
curbuf->b_op_start.lnum = curbuf->b_ml.ml_line_count;
curbuf->b_op_start.col = 0;
@ -2158,7 +2175,8 @@ static void u_undoredo(int undo)
* restore marks from before undo/redo
*/
for (i = 0; i < NMARKS; ++i)
if (curhead->uh_namedm[i].lnum != 0) {
if (curhead->uh_namedm[i].mark.lnum != 0) {
free_fmark(curbuf->b_namedm[i]);
curbuf->b_namedm[i] = curhead->uh_namedm[i];
curhead->uh_namedm[i] = namedm[i];
}

View File

@ -5,6 +5,7 @@
#include "nvim/pos.h"
#include "nvim/buffer_defs.h"
#include "nvim/mark_defs.h"
/* Structure to store info about the Visual area. */
typedef struct {
@ -54,7 +55,7 @@ struct u_header {
pos_T uh_cursor; /* cursor position before saving */
long uh_cursor_vcol;
int uh_flags; /* see below */
pos_T uh_namedm[NMARKS]; /* marks before undo/after redo */
fmark_T uh_namedm[NMARKS]; /* marks before undo/after redo */
visualinfo_T uh_visual; /* Visual areas before undo/after redo */
time_t uh_time; /* timestamp when the change was made */
long uh_save_nr; /* set when the file was saved after the

View File

@ -1925,7 +1925,7 @@ int win_close(win_T *win, int free_buf)
&& (last_window() || curtab != prev_curtab
|| close_last_window_tabpage(win, free_buf, prev_curtab))) {
/* Autocommands have close all windows, quit now. Restore
* curwin->w_buffer, otherwise writing viminfo may fail. */
* curwin->w_buffer, otherwise writing ShaDa file may fail. */
if (curwin->w_buffer == NULL)
curwin->w_buffer = curbuf;
getout(0);

View File

@ -0,0 +1,40 @@
local helpers = require('test.functional.helpers')
local clear, nvim, call, eq =
helpers.clear, helpers.nvim, helpers.call, helpers.eq
describe('history support code', function()
before_each(clear)
local histadd = function(...) return call('histadd', ...) end
local histget = function(...) return call('histget', ...) end
local histdel = function(...) return call('histdel', ...) end
it('correctly clears start of the history', function()
-- Regression test: check absense of the memory leak when clearing start of
-- the history using ex_getln.c/clr_history().
eq(1, histadd(':', 'foo'))
eq(1, histdel(':'))
eq('', histget(':', -1))
end)
it('correctly clears end of the history', function()
-- Regression test: check absense of the memory leak when clearing end of
-- the history using ex_getln.c/clr_history().
nvim('set_option', 'history', 1)
eq(1, histadd(':', 'foo'))
eq(1, histdel(':'))
eq('', histget(':', -1))
end)
it('correctly removes item from history', function()
-- Regression test: check that ex_getln.c/del_history_idx() correctly clears
-- history index after removing history entry. If it does not then deleting
-- history will result in a double free.
eq(1, histadd(':', 'foo'))
eq(1, histadd(':', 'bar'))
eq(1, histadd(':', 'baz'))
eq(1, histdel(':', -2))
eq(1, histdel(':'))
eq('', histget(':', -1))
end)
end)