aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZyX <kp-pav@yandex.ru>2015-04-25 18:47:31 +0300
committerZyX <kp-pav@yandex.ru>2015-10-08 21:59:51 +0300
commit244dbe3a77bf548f73d8781da7327f30e818b08a (patch)
tree8777a25447be219fe351106cfef37670e0278ddd
parent0fdaab995ed95250b13058a717d5bc562e1834c8 (diff)
downloadrneovim-244dbe3a77bf548f73d8781da7327f30e818b08a.tar.gz
rneovim-244dbe3a77bf548f73d8781da7327f30e818b08a.tar.bz2
rneovim-244dbe3a77bf548f73d8781da7327f30e818b08a.zip
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).
-rw-r--r--contrib/YouCompleteMe/ycm_extra_conf.py2
-rw-r--r--runtime/doc/options.txt15
-rwxr-xr-xscripts/shadacat.py73
-rw-r--r--src/nvim/api/private/helpers.h2
-rw-r--r--src/nvim/auevents.lua2
-rw-r--r--src/nvim/buffer.c94
-rw-r--r--src/nvim/buffer_defs.h19
-rw-r--r--src/nvim/edit.c6
-rw-r--r--src/nvim/eval.c143
-rw-r--r--src/nvim/ex_cmds.c609
-rw-r--r--src/nvim/ex_cmds.h14
-rw-r--r--src/nvim/ex_docmd.c11
-rw-r--r--src/nvim/ex_getln.c387
-rw-r--r--src/nvim/ex_getln.h9
-rw-r--r--src/nvim/fileio.c8
-rw-r--r--src/nvim/globals.h2
-rw-r--r--src/nvim/main.c23
-rw-r--r--src/nvim/mark.c710
-rw-r--r--src/nvim/mark.h16
-rw-r--r--src/nvim/mark_defs.h32
-rw-r--r--src/nvim/mbyte.c2
-rw-r--r--src/nvim/misc1.c5
-rw-r--r--src/nvim/normal.c4
-rw-r--r--src/nvim/ops.c315
-rw-r--r--src/nvim/ops.h20
-rw-r--r--src/nvim/os/time.c9
-rw-r--r--src/nvim/os/time.h2
-rw-r--r--src/nvim/os/unix_defs.h4
-rw-r--r--src/nvim/os/win_defs.h2
-rw-r--r--src/nvim/search.c194
-rw-r--r--src/nvim/search.h24
-rw-r--r--src/nvim/shada.c3454
-rw-r--r--src/nvim/shada.h18
-rw-r--r--src/nvim/tag.c2
-rw-r--r--src/nvim/undo.c30
-rw-r--r--src/nvim/undo_defs.h3
-rw-r--r--src/nvim/window.c2
-rw-r--r--test/functional/ex_getln/history_spec.lua40
38 files changed, 4485 insertions, 1822 deletions
diff --git a/contrib/YouCompleteMe/ycm_extra_conf.py b/contrib/YouCompleteMe/ycm_extra_conf.py
index 7c54677c8f..12ad080143 100644
--- a/contrib/YouCompleteMe/ycm_extra_conf.py
+++ b/contrib/YouCompleteMe/ycm_extra_conf.py
@@ -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
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 897137ff42..085f6f21a8 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -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,
diff --git a/scripts/shadacat.py b/scripts/shadacat.py
new file mode 100755
index 0000000000..8f5ed276f8
--- /dev/null
+++ b/scripts/shadacat.py
@@ -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('don’t 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)))
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index f29deb53f9..a0f14ac7a4 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -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
diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua
index af801c6f1a..3d8a75febd 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -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
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index b212e75283..5ebeec1b70 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -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.
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 98fbef9c87..6fbdea2c62 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -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.
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 427623e052..310191ba06 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -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.
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 65afd19bbe..5a7d4702d2 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -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)
-{
- 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);
- }
+/// 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)
+{
+ 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;
}
-
- return viminfo_readline(virp);
+ *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 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)
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 81abf2fa63..262150a8c0 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -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
diff --git a/src/nvim/ex_cmds.h b/src/nvim/ex_cmds.h
index eabbbd15ac..71fc100e41 100644
--- a/src/nvim/ex_cmds.h
+++ b/src/nvim/ex_cmds.h
@@ -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"
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index f7162896ff..c45e641706 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -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;
}
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 9739090d7c..1347feb857 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -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;
}
@@ -4763,250 +4769,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);
+}
diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h
index 2b82f934d5..9c7a688e7e 100644
--- a/src/nvim/ex_getln.h
+++ b/src/nvim/ex_getln.h
@@ -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
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index 302f6b30fb..e4b59b7316 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -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. */
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 60d03cec0c..4ad0d2ec90 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -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 */
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 27f8340ec7..0e25d6d4ec 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -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"));
diff --git a/src/nvim/mark.c b/src/nvim/mark.c
index 6ac4b1de9c..7fdf4f2bcb 100644
--- a/src/nvim/mark.c
+++ b/src/nvim/mark.c
@@ -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,382 +1168,274 @@ void copy_jumplist(win_T *from, win_T *to)
to->w_jumplistidx = from->w_jumplistidx;
}
-/*
- * Free items in the jumplist of window "wp".
- */
-void free_jumplist(win_T *wp)
-{
- int i;
-
- for (i = 0; i < wp->w_jumplistlen; ++i)
- xfree(wp->w_jumplist[i].fname);
-}
-
-void set_last_cursor(win_T *win)
+/// 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 (win->w_buffer != NULL)
- win->w_buffer->b_last_cursor = win->w_cursor;
+ 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;
+ }
}
-#if defined(EXITFREE)
-void free_all_marks(void)
+/// 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
{
- int i;
-
- for (i = 0; i < NMARKS + EXTRA_MARKS; i++)
- if (namedfm[i].fmark.mark.lnum != 0)
- xfree(namedfm[i].fname);
+ 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;
}
-#endif
-
-int read_viminfo_filemark(vir_T *virp, int force)
+/// 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
{
- 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'];
+ 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);
}
- 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);
+ case '.': {
+ *mark_name = 'a';
+ return &(buf->b_namedm[0]);
+ }
+ case 'z': {
+ return NULL;
+ }
+ default: {
+ (*mark_name)++;
+ return &(buf->b_namedm[*mark_name - 'a']);
}
}
- return vim_fgets(virp->vir_line, LSIZE, virp->vir_fd);
}
-void write_viminfo_filemarks(FILE *fp)
+/// 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
{
- 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;
+ 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);
}
-
- /* 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, '-', '\'');
+ 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;
}
-static void write_one_filemark(FILE *fp, xfmark_T *fm, int c1, int c2)
+/// Get a number of valid marks
+size_t mark_global_amount(void)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- 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);
+ size_t ret = 0;
+ for (size_t i = 0; i < NGLOBALMARKS; i++) {
+ if (namedfm[i].fmark.mark.lnum != 0) {
+ ret++;
+ }
}
-
- if (fm->fmark.fnum != 0)
- xfree(name);
+ return ret;
}
-/*
- * Return TRUE if "name" is on removable media (depending on 'viminfo').
- */
-int removable(char_u *name)
+/// 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
{
- 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;
- }
+ 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++;
}
}
- xfree(name);
- return retval;
+ return ret;
}
-
-/*
- * 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 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)
{
- /*
- * Set b_last_cursor for the all buffers that have a window.
- */
- FOR_ALL_TAB_WINDOWS(tp, win) {
- set_last_cursor(win);
+ 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;
}
-
- 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++;
- }
- }
+ if (update && fm.fmark.timestamp < fm_tgt->fmark.timestamp) {
+ return;
}
-
- return count;
+ if (fm_tgt->fmark.mark.lnum != 0) {
+ free_xfmark(*fm_tgt);
+ }
+ *fm_tgt = fm;
}
-static void write_one_mark(FILE *fp_out, int c, pos_T *pos)
+/// 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
{
- if (pos->lnum != 0)
- fprintf(fp_out, "\t%c\t%" PRId64 "\t%d\n", c,
- (int64_t)pos->lnum, (int)pos->col);
+ 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;
}
/*
- * 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
+ * Free items in the jumplist of window "wp".
*/
-void copy_viminfo_marks(vir_T *virp, FILE *fp_out, int count, int eof, int flags)
+void free_jumplist(win_T *wp)
{
- 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);
+ for (i = 0; i < wp->w_jumplistlen; ++i) {
+ free_xfmark(wp->w_jumplist[i]);
}
+}
- 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);
+void set_last_cursor(win_T *win)
+{
+ if (win->w_buffer != NULL) {
+ RESET_FMARK(&win->w_buffer->b_last_cursor, win->w_cursor, 0);
+ }
+}
- /*
- * 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;
- }
- }
- }
+#if defined(EXITFREE)
+void free_all_marks(void)
+{
+ int i;
- /*
- * 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;
+ for (i = 0; i < NGLOBALMARKS; i++) {
+ if (namedfm[i].fmark.mark.lnum != 0) {
+ free_xfmark(namedfm[i]);
}
}
- xfree(name_buf);
}
+#endif
diff --git a/src/nvim/mark.h b/src/nvim/mark.h
index aa89a5b625..a8aaf56648 100644
--- a/src/nvim/mark.h
+++ b/src/nvim/mark.h
@@ -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"
diff --git a/src/nvim/mark_defs.h b/src/nvim/mark_defs.h
index 67392234d3..2f7c9b4ed4 100644
--- a/src/nvim/mark_defs.h
+++ b/src/nvim/mark_defs.h
@@ -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
diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c
index 100b66a608..087d2e677c 100644
--- a/src/nvim/mbyte.c
+++ b/src/nvim/mbyte.c
@@ -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"
*/
diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c
index 8407198b13..a4ebdca091 100644
--- a/src/nvim/misc1.c
+++ b/src/nvim/misc1.c
@@ -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;
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 467b74f9e6..5354fb20ad 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -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) {
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 3163132b8a..55c7aa3364 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -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;
+}
diff --git a/src/nvim/ops.h b/src/nvim/ops.h
index 99683165f9..4da5cfc93d 100644
--- a/src/nvim/ops.h
+++ b/src/nvim/ops.h
@@ -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
diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c
index ee17938afc..ba1dcf631a 100644
--- a/src/nvim/os/time.c
+++ b/src/nvim/os/time.c
@@ -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);
+}
diff --git a/src/nvim/os/time.h b/src/nvim/os/time.h
index b21808307f..3a6b665b51 100644
--- a/src/nvim/os/time.h
+++ b/src/nvim/os/time.h
@@ -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
diff --git a/src/nvim/os/unix_defs.h b/src/nvim/os/unix_defs.h
index 9ab4ba1c1a..949973bf40 100644
--- a/src/nvim/os/unix_defs.h
+++ b/src/nvim/os/unix_defs.h
@@ -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'.
diff --git a/src/nvim/os/win_defs.h b/src/nvim/os/win_defs.h
index 9773b73428..b7ec50a109 100644
--- a/src/nvim/os/win_defs.h
+++ b/src/nvim/os/win_defs.h
@@ -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
diff --git a/src/nvim/search.c b/src/nvim/search.c
index a758e02105..f8dd7bd482 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -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;
+ memcpy(pat, &(spats[0]), sizeof(spats[0]));
+}
- /*
- * 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);
+/// Get last substitute pattern
+void get_substitute_pattern(SearchPattern *const pat)
+{
+ memcpy(pat, &(spats[1]), sizeof(spats[1]));
+ memset(&(pat->off), 0, sizeof(pat->off));
}
-void write_viminfo_search_pattern(FILE *fp)
+/// Set last search pattern
+void set_search_pattern(const SearchPattern 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 "), '&');
- }
+ free_spat(&spats[0]);
+ memcpy(&(spats[0]), &pat, sizeof(spats[0]));
}
-static void
-wvsp_one (
- FILE *fp, /* file to write to */
- int idx, /* spats[] index */
- char *s, /* search pat */
- int sc /* dir char */
-)
+/// Set last substitute pattern
+void set_substitute_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[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;
}
diff --git a/src/nvim/search.h b/src/nvim/search.h
index 1bcde2c7ab..691782e41c 100644
--- a/src/nvim/search.h
+++ b/src/nvim/search.h
@@ -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
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
new file mode 100644
index 0000000000..73aaa311b4
--- /dev/null
+++ b/src/nvim/shada.c
@@ -0,0 +1,3454 @@
+#include <stddef.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <msgpack.h>
+
+#include "nvim/os/os.h"
+#include "nvim/os/time.h"
+#include "nvim/vim.h"
+#include "nvim/ascii.h"
+#include "nvim/shada.h"
+#include "nvim/message.h"
+#include "nvim/globals.h"
+#include "nvim/macros.h"
+#include "nvim/memory.h"
+#include "nvim/mark.h"
+#include "nvim/ops.h"
+#include "nvim/garray.h"
+#include "nvim/option.h"
+#include "nvim/msgpack_rpc/helpers.h"
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/globals.h"
+#include "nvim/buffer.h"
+#include "nvim/misc2.h"
+#include "nvim/ex_getln.h"
+#include "nvim/search.h"
+#include "nvim/eval.h"
+#include "nvim/eval_defs.h"
+#include "nvim/version.h"
+#include "nvim/path.h"
+
+#define buflist_nr2name(...) ((char *) buflist_nr2name(__VA_ARGS__))
+#define copy_option_part(src, dest, ...) \
+ ((char *) copy_option_part((char_u **) src, (char_u *) dest, __VA_ARGS__))
+#define find_viminfo_parameter(...) \
+ ((const char *) find_viminfo_parameter(__VA_ARGS__))
+#define emsg2(a, b) emsg2((char_u *) a, (char_u *) b)
+#define emsg3(a, b, c) emsg3((char_u *) a, (char_u *) b, (char_u *) c)
+#define emsgn(a, ...) emsgn((char_u *) a, __VA_ARGS__)
+#define home_replace_save(a, b) \
+ ((char *)home_replace_save(a, (char_u *)b))
+
+/// Possible ShaDa entry types
+///
+/// @warning Enum values are part of the API and must not be altered.
+///
+/// All values that are not in enum are ignored.
+typedef enum {
+ kSDItemUnknown = -1, ///< Unknown item.
+ kSDItemMissing = 0, ///< Missing value. Should never appear in a file.
+ kSDItemHeader = 1, ///< Header. Present for debugging purposes.
+ kSDItemSearchPattern = 2, ///< Last search pattern (*not* history item).
+ ///< Comes from user searches (e.g. when typing
+ ///< "/pat") or :substitute command calls.
+ kSDItemSubString = 3, ///< Last substitute replacement string.
+ kSDItemHistoryEntry = 4, ///< History item.
+ kSDItemRegister = 5, ///< Register.
+ kSDItemVariable = 6, ///< Global variable.
+ kSDItemGlobalMark = 7, ///< Global mark definition.
+ kSDItemJump = 8, ///< Item from jump list.
+ kSDItemBufferList = 9, ///< Buffer list.
+ kSDItemLocalMark = 10, ///< Buffer-local mark.
+#define SHADA_LAST_ENTRY ((uint64_t) kSDItemLocalMark)
+} ShadaEntryType;
+
+/// Structure defining a single ShaDa file entry
+typedef struct {
+ ShadaEntryType type;
+ Timestamp timestamp;
+ union {
+ Dictionary header;
+ struct shada_filemark {
+ char name;
+ pos_T mark;
+ char *fname;
+ Dictionary *additional_data;
+ } filemark;
+ struct search_pattern {
+ bool magic;
+ bool smartcase;
+ bool has_line_offset;
+ bool place_cursor_at_end;
+ int64_t offset;
+ bool is_last_used;
+ bool is_substitute_pattern;
+ char *pat;
+ Dictionary *additional_data;
+ } search_pattern;
+ struct history_item {
+ uint8_t histtype;
+ char *string;
+ Array *additional_elements;
+ } history_item;
+ struct reg {
+ char name;
+ uint8_t type;
+ char **contents;
+ size_t contents_size;
+ size_t width;
+ Dictionary *additional_data;
+ } reg;
+ struct global_var {
+ char *name;
+ Object value;
+ Array *additional_elements;
+ } global_var;
+ struct {
+ uint64_t type;
+ char *contents;
+ size_t size;
+ } unknown_item;
+ struct sub_string {
+ char *sub;
+ Array *additional_elements;
+ } sub_string;
+ Array buffer_list;
+ } data;
+} ShadaEntry;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "shada.c.generated.h"
+#endif
+
+/// Msgpack callback for writing to FILE*
+static int msgpack_fbuffer_write(void *data, const char *buf, size_t len)
+{
+ return (fwrite(buf, len, 1, (FILE *) data) == 1) ? 0 : -1;
+}
+
+/// Check whether writing to shada file was disabled with -i NONE
+///
+/// @return true if it was disabled, false otherwise.
+static bool shada_disabled(void)
+ FUNC_ATTR_PURE
+{
+ return used_shada_file != NULL && STRCMP(used_shada_file, "NONE") == 0;
+}
+
+/// Read ShaDa file
+///
+/// @param[in] file File to read or NULL to use default name.
+/// @param[in] flags Flags, see kShaDa enum values in shada.h.
+///
+/// @return FAIL if reading failed for some reason and OK otherwise.
+int shada_read_file(const char *const file, const int flags)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ FILE *fp;
+
+ if (shada_disabled()) {
+ return FAIL;
+ }
+
+ char *const fname = shada_filename(file);
+ fp = mch_fopen(fname, READBIN);
+
+ if (p_verbose > 0) {
+ verbose_enter();
+ smsg(_("Reading viminfo file \"%s\"%s%s%s"),
+ fname,
+ (flags & kShaDaWantInfo) ? _(" info") : "",
+ (flags & kShaDaWantMarks) ? _(" marks") : "",
+ (flags & kShaDaGetOldfiles) ? _(" oldfiles") : "",
+ fp == NULL ? _(" FAILED") : "");
+ verbose_leave();
+ }
+
+ xfree(fname);
+ if (fp == NULL) {
+ return FAIL;
+ }
+
+ shada_read(fp, flags);
+
+ fclose(fp);
+ return OK;
+}
+
+/// Read data from ShaDa file
+///
+/// @param[in] fp File to read from.
+/// @param[in] flags What to read.
+static void shada_read(FILE *const fp, const int flags)
+ FUNC_ATTR_NONNULL_ALL
+{
+ ShadaEntry cur_entry;
+ buf_T *local_mark_prev_buf = NULL;
+ char *local_mark_prev_fname = NULL;
+ size_t local_mark_prev_fname_len = 0;
+ while (shada_read_next_item(fp, &cur_entry) == NOTDONE) {
+ switch (cur_entry.type) {
+ case kSDItemMissing: {
+ assert(false);
+ }
+ case kSDItemUnknown: {
+ break;
+ }
+ case kSDItemHeader: {
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ case kSDItemSearchPattern: {
+ if (!(flags & kShaDaWantInfo)) {
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ (cur_entry.data.search_pattern.is_substitute_pattern
+ ? &set_substitute_pattern
+ : &set_search_pattern)((SearchPattern) {
+ .magic = cur_entry.data.search_pattern.magic,
+ .no_scs = !cur_entry.data.search_pattern.smartcase,
+ .off = {
+ .line = cur_entry.data.search_pattern.has_line_offset,
+ .end = cur_entry.data.search_pattern.place_cursor_at_end,
+ .off = cur_entry.data.search_pattern.offset,
+ },
+ .pat = (char_u *) cur_entry.data.search_pattern.pat,
+ .additional_data = cur_entry.data.search_pattern.additional_data,
+ .timestamp = cur_entry.timestamp,
+ });
+ if (cur_entry.data.search_pattern.is_last_used) {
+ set_last_used_pattern(
+ cur_entry.data.search_pattern.is_substitute_pattern);
+ }
+ // Do not free shada entry: its allocated memory was saved above.
+ break;
+ }
+ case kSDItemSubString: {
+ if (!(flags & kShaDaWantInfo)) {
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ sub_set_replacement((SubReplacementString) {
+ .sub = cur_entry.data.sub_string.sub,
+ .timestamp = cur_entry.timestamp,
+ .additional_elements = cur_entry.data.sub_string.additional_elements,
+ });
+ // Do not free shada entry: its allocated memory was saved above.
+ break;
+ }
+ case kSDItemHistoryEntry: {
+ if (!(flags & kShaDaWantInfo)) {
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ // FIXME
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ case kSDItemRegister: {
+ if (!(flags & kShaDaWantInfo)) {
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ if (cur_entry.data.reg.type != MCHAR
+ && cur_entry.data.reg.type != MLINE
+ && cur_entry.data.reg.type != MBLOCK) {
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ register_set(cur_entry.data.reg.name, (yankreg_T) {
+ .y_array = (char_u **) cur_entry.data.reg.contents,
+ .y_size = (linenr_T) cur_entry.data.reg.contents_size,
+ .y_type = cur_entry.data.reg.type,
+ .y_width = (colnr_T) cur_entry.data.reg.width,
+ .timestamp = cur_entry.timestamp,
+ .additional_data = cur_entry.data.reg.additional_data,
+ });
+ // Do not free shada entry: its allocated memory was saved above.
+ break;
+ }
+ case kSDItemVariable: {
+ if (!(flags & kShaDaWantInfo) || find_viminfo_parameter('!') == NULL) {
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ typval_T vartv;
+ Error err;
+ if (!object_to_vim(cur_entry.data.global_var.value, &vartv, &err)) {
+ if (err.set) {
+ emsg3("Error while reading ShaDa file: "
+ "failed to read value for variable %s: %s",
+ cur_entry.data.global_var.name, err.msg);
+ }
+ break;
+ }
+ var_set_global(cur_entry.data.global_var.name, vartv);
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ case kSDItemGlobalMark: {
+ if (!(flags & kShaDaWantMarks) || get_viminfo_parameter('f') == 0) {
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ mark_set_global(cur_entry.data.filemark.name, (xfmark_T) {
+ .fname = (char_u *) cur_entry.data.filemark.fname,
+ .fmark = {
+ .mark = cur_entry.data.filemark.mark,
+ .fnum = 0,
+ .timestamp = cur_entry.timestamp,
+ .additional_data = cur_entry.data.filemark.additional_data,
+ },
+ }, !(flags & kShaDaForceit));
+ break;
+ }
+ case kSDItemJump: {
+ // FIXME
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ case kSDItemBufferList: {
+ // FIXME
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ case kSDItemLocalMark: {
+ if (!(flags & kShaDaWantMarks)) {
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ buf_T *buf = NULL;
+ if (local_mark_prev_fname != NULL
+ && strncmp(local_mark_prev_fname,
+ cur_entry.data.filemark.fname,
+ local_mark_prev_fname_len) == 0) {
+ buf = local_mark_prev_buf;
+ } else {
+ FOR_ALL_BUFFERS(bp) {
+ if (bp->b_ffname != NULL && bp != local_mark_prev_buf) {
+ if (fnamecmp(cur_entry.data.filemark.fname, bp->b_ffname) == 0) {
+ buf = bp;
+ break;
+ }
+ }
+ }
+ xfree(local_mark_prev_fname);
+ local_mark_prev_buf = buf;
+ local_mark_prev_fname_len = strlen(cur_entry.data.filemark.fname);
+ local_mark_prev_fname = xmemdupz(cur_entry.data.filemark.fname,
+ local_mark_prev_fname_len);
+ }
+ if (buf == NULL) {
+ break;
+ }
+ mark_set_local(
+ cur_entry.data.filemark.name, buf,
+ (fmark_T) {
+ .mark = cur_entry.data.filemark.mark,
+ .fnum = 0,
+ .timestamp = cur_entry.timestamp,
+ .additional_data = cur_entry.data.filemark.additional_data,
+ }, !(flags & kShaDaForceit));
+ free(cur_entry.data.filemark.fname);
+ break;
+ }
+ }
+ }
+ xfree(local_mark_prev_fname);
+ FOR_ALL_BUFFERS(buf) {
+ fmarks_check_names(buf);
+ }
+}
+
+/// Get the ShaDa 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.
+///
+/// @param[in] file Forced file name or NULL.
+///
+/// @return An allocated string containing shada file name.
+static char *shada_filename(const char *file)
+ FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (file == NULL || *file == NUL) {
+ if (used_shada_file != NULL) {
+ file = used_shada_file;
+ } else if ((file = find_viminfo_parameter('n')) == NULL || *file == NUL) {
+#ifdef SHADA_FILE2
+ // don't use $HOME when not defined (turned into "c:/"!).
+ if (os_getenv((char_u *)"HOME") == NULL) {
+ // don't use $VIM when not available.
+ expand_env((char_u *)"$VIM", NameBuff, MAXPATHL);
+ if (STRCMP("$VIM", NameBuff) != 0) { // $VIM was expanded
+ file = SHADA_FILE2;
+ } else {
+ file = SHADA_FILE;
+ }
+ } else {
+#endif
+ file = SHADA_FILE;
+#ifdef SHADA_FILE2
+ }
+#endif
+ // XXX It used to be one level lower, so that whatever is in
+ // `used_shada_file` was expanded. I intentionally moved it here
+ // because various expansions must have already be done by the shell.
+ // If shell is not performing them then they should be done in main.c
+ // where arguments are parsed, *not here*.
+ expand_env((char_u *)file, &(NameBuff[0]), MAXPATHL);
+ file = (const char *) &(NameBuff[0]);
+ }
+ }
+ return xstrdup(file);
+}
+
+#define PACK_STATIC_STR(s) \
+ do { \
+ msgpack_pack_str(spacker, sizeof(s) - 1); \
+ msgpack_pack_str_body(spacker, s, sizeof(s) - 1); \
+ } while (0)
+
+/// Write single ShaDa entry
+///
+/// @param[in] packer Packer used to write entry.
+/// @param[in] entry Entry written.
+/// @param[in] max_kbyte Maximum size of an item in KiB. Zero means no
+/// restrictions.
+static void shada_pack_entry(msgpack_packer *const packer,
+ const ShadaEntry entry,
+ const size_t max_kbyte)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (entry.type == kSDItemMissing) {
+ return;
+ }
+ msgpack_sbuffer sbuf;
+ msgpack_sbuffer_init(&sbuf);
+ msgpack_packer *spacker = msgpack_packer_new(&sbuf, &msgpack_sbuffer_write);
+ switch (entry.type) {
+ case kSDItemMissing: {
+ assert(false);
+ }
+ case kSDItemUnknown: {
+ msgpack_pack_uint64(packer, (uint64_t) entry.data.unknown_item.size);
+ packer->callback(packer->data, entry.data.unknown_item.contents,
+ (unsigned) entry.data.unknown_item.size);
+ break;
+ }
+ case kSDItemHistoryEntry: {
+ const size_t arr_size = 2 + (
+ entry.data.history_item.additional_elements == NULL
+ ? 0
+ : entry.data.history_item.additional_elements->size);
+ msgpack_pack_array(spacker, arr_size);
+ msgpack_pack_uint8(spacker, entry.data.history_item.histtype);
+ msgpack_rpc_from_string(cstr_as_string(entry.data.history_item.string),
+ spacker);
+ for (size_t i = 0; i < arr_size - 2; i++) {
+ msgpack_rpc_from_object(
+ entry.data.history_item.additional_elements->items[i], spacker);
+ }
+ break;
+ }
+ case kSDItemVariable: {
+ const size_t arr_size = 2 + (
+ entry.data.global_var.additional_elements == NULL
+ ? 0
+ : entry.data.global_var.additional_elements->size);
+ msgpack_pack_array(spacker, arr_size);
+ msgpack_rpc_from_string(cstr_as_string(entry.data.global_var.name),
+ spacker);
+ msgpack_rpc_from_object(entry.data.global_var.value, spacker);
+ for (size_t i = 0; i < arr_size - 2; i++) {
+ msgpack_rpc_from_object(
+ entry.data.global_var.additional_elements->items[i], spacker);
+ }
+ break;
+ }
+ case kSDItemSubString: {
+ const size_t arr_size = 1 + (
+ entry.data.sub_string.additional_elements == NULL
+ ? 0
+ : entry.data.sub_string.additional_elements->size);
+ msgpack_pack_array(spacker, arr_size);
+ msgpack_rpc_from_string(cstr_as_string(entry.data.sub_string.sub),
+ spacker);
+ for (size_t i = 0; i < arr_size - 1; i++) {
+ msgpack_rpc_from_object(
+ entry.data.sub_string.additional_elements->items[i], spacker);
+ }
+ break;
+ }
+ case kSDItemSearchPattern: {
+ const size_t map_size = (size_t) (
+ 1 // Search pattern is always present
+ // Following items default to true:
+ + !entry.data.search_pattern.magic
+ + !entry.data.search_pattern.is_last_used
+ // Following items default to false:
+ + entry.data.search_pattern.smartcase
+ + entry.data.search_pattern.has_line_offset
+ + entry.data.search_pattern.place_cursor_at_end
+ + entry.data.search_pattern.is_substitute_pattern
+ // offset defaults to zero:
+ + (entry.data.search_pattern.offset != 0)
+ // finally, additional data:
+ + (entry.data.search_pattern.additional_data
+ ? entry.data.search_pattern.additional_data->size
+ : 0)
+ );
+ msgpack_pack_map(spacker, map_size);
+ PACK_STATIC_STR("pat");
+ msgpack_rpc_from_string(cstr_as_string(entry.data.search_pattern.pat),
+ spacker);
+#define PACK_BOOL(name, attr, nondef_value) \
+ do { \
+ if (entry.data.search_pattern.attr == nondef_value) { \
+ PACK_STATIC_STR(name); \
+ msgpack_pack_##nondef_value(spacker); \
+ } \
+ } while (0)
+ PACK_BOOL("magic", magic, false);
+ PACK_BOOL("islast", is_last_used, false);
+ PACK_BOOL("smartcase", smartcase, true);
+ PACK_BOOL("lineoff", has_line_offset, true);
+ PACK_BOOL("curatend", place_cursor_at_end, true);
+ PACK_BOOL("sub", is_substitute_pattern, true);
+ if (entry.data.search_pattern.offset) {
+ PACK_STATIC_STR("off");
+ msgpack_pack_int64(spacker, entry.data.search_pattern.offset);
+ }
+#undef PACK_BOOL
+ if (entry.data.search_pattern.additional_data != NULL) {
+ for (size_t i = 0; i < entry.data.search_pattern.additional_data->size;
+ i++) {
+ msgpack_rpc_from_string(
+ entry.data.search_pattern.additional_data->items[i].key, spacker);
+ msgpack_rpc_from_object(
+ entry.data.search_pattern.additional_data->items[i].value,
+ spacker);
+ }
+ }
+ break;
+ }
+ case kSDItemGlobalMark:
+ case kSDItemLocalMark:
+ case kSDItemJump: {
+ const size_t map_size = (size_t) (
+ 1 // File name
+ // Line: defaults to 1
+ + (entry.data.filemark.mark.lnum != 1)
+ // Column: defaults to zero:
+ + (entry.data.filemark.mark.col != 0)
+ // Mark name: defaults to '"'
+ + (entry.type != kSDItemJump
+ && entry.data.filemark.name != '"')
+ // Additional entries, if any:
+ + (entry.data.filemark.additional_data == NULL
+ ? 0
+ : entry.data.filemark.additional_data->size)
+ );
+ msgpack_pack_map(spacker, map_size);
+ PACK_STATIC_STR("file");
+ msgpack_rpc_from_string(cstr_as_string(entry.data.filemark.fname),
+ spacker);
+ if (entry.data.filemark.mark.lnum != 1) {
+ PACK_STATIC_STR("line");
+ msgpack_pack_long(spacker, entry.data.filemark.mark.lnum);
+ }
+ if (entry.data.filemark.mark.col != 0) {
+ PACK_STATIC_STR("col");
+ msgpack_pack_long(spacker, entry.data.filemark.mark.col);
+ }
+ if (entry.data.filemark.name != '"' && entry.type != kSDItemJump) {
+ PACK_STATIC_STR("name");
+ msgpack_pack_uint8(spacker, (uint8_t) entry.data.filemark.name);
+ }
+ if (entry.data.filemark.additional_data != NULL) {
+ for (size_t i = 0; i < entry.data.filemark.additional_data->size;
+ i++) {
+ msgpack_rpc_from_string(
+ entry.data.filemark.additional_data->items[i].key, spacker);
+ msgpack_rpc_from_object(
+ entry.data.filemark.additional_data->items[i].value, spacker);
+ }
+ }
+ break;
+ }
+ case kSDItemRegister: {
+ const size_t map_size = (size_t) (
+ 2 // Register contents and name
+ // Register type: defaults to MCHAR
+ + (entry.data.reg.type != MCHAR)
+ // Register width: defaults to zero
+ + (entry.data.reg.width != 0)
+ // Additional entries, if any:
+ + (entry.data.reg.additional_data == NULL
+ ? 0
+ : entry.data.reg.additional_data->size)
+ );
+ msgpack_pack_map(spacker, map_size);
+ PACK_STATIC_STR("contents");
+ msgpack_pack_array(spacker, entry.data.reg.contents_size);
+ for (size_t i = 0; i < entry.data.reg.contents_size; i++) {
+ msgpack_rpc_from_string(cstr_as_string(entry.data.reg.contents[i]),
+ spacker);
+ }
+ PACK_STATIC_STR("name");
+ msgpack_pack_char(spacker, entry.data.reg.name);
+ if (entry.data.reg.type != MCHAR) {
+ PACK_STATIC_STR("type");
+ msgpack_pack_uint8(spacker, entry.data.reg.type);
+ }
+ if (entry.data.reg.width != 0) {
+ PACK_STATIC_STR("width");
+ msgpack_pack_uint64(spacker, (uint64_t) entry.data.reg.width);
+ }
+ if (entry.data.reg.additional_data != NULL) {
+ for (size_t i = 0; i < entry.data.reg.additional_data->size;
+ i++) {
+ msgpack_rpc_from_string(entry.data.reg.additional_data->items[i].key,
+ spacker);
+ msgpack_rpc_from_object(
+ entry.data.reg.additional_data->items[i].value, spacker);
+ }
+ }
+ break;
+ }
+ case kSDItemBufferList: {
+ msgpack_rpc_from_array(entry.data.buffer_list, spacker);
+ break;
+ }
+ case kSDItemHeader: {
+ msgpack_rpc_from_dictionary(entry.data.header, spacker);
+ break;
+ }
+ }
+ if (!max_kbyte || sbuf.size <= max_kbyte * 1024) {
+ if (entry.type == kSDItemUnknown) {
+ msgpack_pack_uint64(packer, (uint64_t) entry.data.unknown_item.type);
+ } else {
+ msgpack_pack_uint64(packer, (uint64_t) entry.type);
+ }
+ msgpack_pack_uint64(packer, (uint64_t) entry.timestamp);
+ if (sbuf.size > 0) {
+ msgpack_pack_uint64(packer, (uint64_t) sbuf.size);
+ packer->callback(packer->data, sbuf.data, (unsigned) sbuf.size);
+ }
+ }
+ msgpack_packer_free(spacker);
+ msgpack_sbuffer_destroy(&sbuf);
+}
+
+/// Write ShaDa file
+///
+/// @param[in] newfp File pointer to write to. Must not be NULL.
+/// @param[in] oldfp Pointer to the previous ShaDa file. If it is not NULL
+/// then contents of this file will be merged with current
+/// NeoVim runtime.
+static void shada_write(FILE *const newfp, FILE *const oldfp)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ int max_kbyte_i = get_viminfo_parameter('s');
+ if (max_kbyte_i < 0) {
+ max_kbyte_i = 10;
+ }
+ if (max_kbyte_i == 0) {
+ return;
+ }
+ const size_t max_kbyte = (size_t) max_kbyte_i;
+
+ msgpack_packer *packer = msgpack_packer_new(newfp, &msgpack_fbuffer_write);
+
+ // First write values that do not require merging
+ // 1. Header
+ shada_pack_entry(packer, (ShadaEntry) {
+ .type = kSDItemHeader,
+ .timestamp = os_time(),
+ .data = {
+ .header = {
+ .size = 3,
+ .capacity = 3,
+ .items = ((KeyValuePair []) {
+ { STATIC_CSTR_AS_STRING("version"),
+ STRING_OBJ(cstr_as_string(longVersion)) },
+ { STATIC_CSTR_AS_STRING("max_kbyte"),
+ INTEGER_OBJ((Integer) max_kbyte) },
+ { STATIC_CSTR_AS_STRING("pid"),
+ INTEGER_OBJ((Integer) os_get_pid()) },
+ }),
+ }
+ }
+ }, 0);
+ fflush(newfp);
+
+ // 2. Buffer list
+ if (find_viminfo_parameter('%') != NULL) {
+ msgpack_pack_uint64(packer, (uint64_t) kSDItemBufferList);
+ msgpack_pack_uint64(packer, (uint64_t) os_time());
+ msgpack_sbuffer sbuf;
+ msgpack_sbuffer_init(&sbuf);
+ msgpack_packer *spacker = msgpack_packer_new(&sbuf, &msgpack_sbuffer_write);
+
+ size_t buf_count = 0;
+ FOR_ALL_BUFFERS(buf) {
+ if (buf->b_ffname != NULL) {
+ buf_count++;
+ }
+ }
+ msgpack_pack_array(packer, buf_count);
+ FOR_ALL_BUFFERS(buf) {
+ if (buf->b_ffname == NULL) {
+ continue;
+ }
+ msgpack_pack_map(packer, 3);
+ PACK_STATIC_STR("fname");
+ msgpack_rpc_from_string(cstr_as_string((char *) buf->b_ffname), packer);
+ PACK_STATIC_STR("lnum");
+ msgpack_pack_uint64(packer, (uint64_t) buf->b_last_cursor.mark.lnum);
+ PACK_STATIC_STR("col");
+ msgpack_pack_uint64(packer, (uint64_t) buf->b_last_cursor.mark.col);
+ }
+ msgpack_pack_uint64(packer, (uint64_t) sbuf.size);
+ packer->callback(packer->data, sbuf.data, (unsigned) sbuf.size);
+ msgpack_packer_free(spacker);
+ msgpack_sbuffer_destroy(&sbuf);
+ }
+
+ // 3. Jump list
+ const void *jump_iter = NULL;
+ do {
+ xfmark_T fm;
+ jump_iter = mark_jumplist_iter(jump_iter, curwin, &fm);
+ char *fname = (fm.fmark.fnum == 0
+ ? (fm.fname == NULL
+ ? NULL
+ : xstrdup((char *) fm.fname))
+ : buflist_nr2name(fm.fmark.fnum, true, false));
+ shada_pack_entry(packer, (ShadaEntry) {
+ .type = kSDItemJump,
+ .timestamp = fm.fmark.timestamp,
+ .data = {
+ .filemark = {
+ .name = NUL,
+ .mark = fm.fmark.mark,
+ .fname = fname,
+ .additional_data = fm.fmark.additional_data,
+ }
+ }
+ }, max_kbyte);
+ xfree(fname);
+ } while (jump_iter != NULL);
+
+ // FIXME No merging currently
+
+ // 4. History
+ const void *hist_iters[HIST_COUNT] = {NULL, NULL, NULL, NULL, NULL};
+ for (uint8_t i = 0; i < HIST_COUNT; i++) {
+ do {
+ histentry_T cur_hist;
+ hist_iters[i] = hist_iter(hist_iters[i], i, false, &cur_hist);
+ if (cur_hist.hisstr == NULL) {
+ break;
+ }
+ shada_pack_entry(packer, (ShadaEntry) {
+ .type = kSDItemHistoryEntry,
+ .timestamp = cur_hist.timestamp,
+ .data = {
+ .history_item = {
+ .histtype = i,
+ .string = (char *) cur_hist.hisstr,
+ .additional_elements = cur_hist.additional_elements,
+ }
+ }
+ }, max_kbyte);
+ } while (hist_iters[i] != NULL);
+ }
+
+ // 5. Search patterns
+ SearchPattern pat;
+ get_search_pattern(&pat);
+ shada_pack_entry(packer, (ShadaEntry) {
+ .type = kSDItemSearchPattern,
+ .timestamp = pat.timestamp,
+ .data = {
+ .search_pattern = {
+ .magic = pat.magic,
+ .smartcase = !pat.no_scs,
+ .has_line_offset = pat.off.line,
+ .place_cursor_at_end = pat.off.end,
+ .offset = pat.off.off,
+ .is_last_used = search_was_last_used(),
+ .is_substitute_pattern = false,
+ .pat = (char *) pat.pat,
+ .additional_data = pat.additional_data,
+ }
+ }
+ }, max_kbyte);
+ get_substitute_pattern(&pat);
+ shada_pack_entry(packer, (ShadaEntry) {
+ .type = kSDItemSearchPattern,
+ .timestamp = pat.timestamp,
+ .data = {
+ .search_pattern = {
+ .magic = pat.magic,
+ .smartcase = !pat.no_scs,
+ .has_line_offset = false,
+ .place_cursor_at_end = false,
+ .offset = 0,
+ .is_last_used = !search_was_last_used(),
+ .is_substitute_pattern = true,
+ .pat = (char *) pat.pat,
+ .additional_data = pat.additional_data,
+ }
+ }
+ }, max_kbyte);
+
+ // 6. Substitute string
+ SubReplacementString sub;
+ sub_get_replacement(&sub);
+ shada_pack_entry(packer, (ShadaEntry) {
+ .type = kSDItemSubString,
+ .timestamp = sub.timestamp,
+ .data = {
+ .sub_string = {
+ .sub = (char *) sub.sub,
+ .additional_elements = sub.additional_elements,
+ }
+ }
+ }, max_kbyte);
+
+ // 7. Global marks
+ if (get_viminfo_parameter('f') != 0) {
+ ShadaEntry *const global_marks = list_global_marks();
+ for (ShadaEntry *mark = global_marks; mark->type != kSDItemMissing; mark++) {
+ shada_pack_entry(packer, *mark, max_kbyte);
+ }
+ xfree(global_marks);
+ }
+
+ // 8. Buffer marks
+ FOR_ALL_BUFFERS(buf) {
+ if (buf->b_ffname == NULL) {
+ continue;
+ }
+ ShadaEntry *const buffer_marks = list_buffer_marks(buf);
+ for (ShadaEntry *mark = buffer_marks; mark->type != kSDItemMissing;
+ mark++) {
+ shada_pack_entry(packer, *mark, max_kbyte);
+ }
+ xfree(buffer_marks);
+ }
+ // FIXME: Copy previous marks, up to num_marked_files
+ // size_t num_marked_files = get_viminfo_parameter('\'');
+
+ // 9. Registers
+ int max_num_lines_i = get_viminfo_parameter('<');
+ if (max_num_lines_i < 0) {
+ max_num_lines_i = get_viminfo_parameter('"');
+ }
+ if (max_num_lines_i != 0) {
+ const size_t max_num_lines = (max_num_lines_i < 0
+ ? 0
+ : (size_t) max_num_lines_i);
+ ShadaEntry *const registers = list_registers(max_num_lines);
+ for (ShadaEntry *reg = registers; reg->type != kSDItemMissing; reg++) {
+ shada_pack_entry(packer, *reg, max_kbyte);
+ }
+ xfree(registers);
+ }
+
+ // 10. Variables
+ if (find_viminfo_parameter('!') != NULL) {
+ const void *var_iter = NULL;
+ const Timestamp cur_timestamp = os_time();
+ do {
+ typval_T vartv;
+ const char *name;
+ var_iter = var_shada_iter(var_iter, &name, &vartv);
+ if (var_iter == NULL && vartv.v_type == VAR_UNKNOWN) {
+ break;
+ }
+ Object obj = vim_to_object(&vartv);
+ shada_pack_entry(packer, (ShadaEntry) {
+ .type = kSDItemVariable,
+ .timestamp = cur_timestamp,
+ .data = {
+ .global_var = {
+ .name = (char *) name,
+ .value = obj,
+ .additional_elements = NULL,
+ }
+ }
+ }, max_kbyte);
+ api_free_object(obj);
+ clear_tv(&vartv);
+ } while (var_iter != NULL);
+ }
+
+ msgpack_packer_free(packer);
+}
+
+#undef PACK_STATIC_STR
+
+/// Write ShaDa file to a given location
+///
+/// @param[in] fname File to write to. If it is NULL or empty then default
+/// location is used.
+/// @param[in] nomerge If true then old file is ignored.
+///
+/// @return OK if writing was successfull, FAIL otherwise.
+int shada_write_file(const char *const file, const bool nomerge)
+{
+ char *const fname = shada_filename(file);
+ FILE *wfp = mch_fopen(fname, WRITEBIN);
+
+ if (p_verbose > 0) {
+ verbose_enter();
+ smsg(_("Writing viminfo file \"%s\""), fname);
+ verbose_leave();
+ }
+
+ xfree(fname);
+ if (wfp == NULL) {
+ return FAIL;
+ }
+
+ shada_write(wfp, NULL);
+
+ fclose(wfp);
+ return OK;
+}
+
+/// Read marks information from ShaDa file
+///
+/// @return OK in case of success, FAIL otherwise.
+int shada_read_marks(void)
+{
+ return shada_read_file(NULL, kShaDaWantMarks);
+}
+
+/// Read all information from ShaDa file
+///
+/// @param[in] fname File to write to. If it is NULL or empty then default
+///
+/// @return OK in case of success, FAIL otherwise.
+int shada_read_everything(const char *const fname, const bool forceit)
+{
+ return shada_read_file(fname,
+ kShaDaWantInfo|kShaDaWantMarks|kShaDaGetOldfiles
+ |(forceit?kShaDaForceit:0));
+}
+
+static void shada_free_shada_entry(ShadaEntry *const entry)
+{
+ if (entry == NULL) {
+ return;
+ }
+ switch (entry->type) {
+ case kSDItemMissing: {
+ break;
+ }
+ case kSDItemUnknown: {
+ xfree(entry->data.unknown_item.contents);
+ break;
+ }
+ case kSDItemHeader: {
+ api_free_dictionary(entry->data.header);
+ break;
+ }
+ case kSDItemJump:
+ case kSDItemGlobalMark:
+ case kSDItemLocalMark: {
+ if (entry->data.filemark.additional_data != NULL) {
+ api_free_dictionary(*entry->data.filemark.additional_data);
+ xfree(entry->data.filemark.additional_data);
+ }
+ xfree(entry->data.filemark.fname);
+ break;
+ }
+ case kSDItemSearchPattern: {
+ if (entry->data.search_pattern.additional_data != NULL) {
+ api_free_dictionary(*entry->data.search_pattern.additional_data);
+ xfree(entry->data.search_pattern.additional_data);
+ }
+ xfree(entry->data.search_pattern.pat);
+ break;
+ }
+ case kSDItemRegister: {
+ if (entry->data.reg.additional_data != NULL) {
+ api_free_dictionary(*entry->data.reg.additional_data);
+ xfree(entry->data.reg.additional_data);
+ }
+ for (size_t i = 0; i < entry->data.reg.contents_size; i++) {
+ xfree(entry->data.reg.contents[i]);
+ }
+ xfree(entry->data.reg.contents);
+ break;
+ }
+ case kSDItemHistoryEntry: {
+ if (entry->data.history_item.additional_elements != NULL) {
+ api_free_array(*entry->data.history_item.additional_elements);
+ xfree(entry->data.history_item.additional_elements);
+ }
+ xfree(entry->data.history_item.string);
+ break;
+ }
+ case kSDItemVariable: {
+ if (entry->data.global_var.additional_elements != NULL) {
+ api_free_array(*entry->data.global_var.additional_elements);
+ xfree(entry->data.global_var.additional_elements);
+ }
+ xfree(entry->data.global_var.name);
+ api_free_object(entry->data.global_var.value);
+ break;
+ }
+ case kSDItemSubString: {
+ if (entry->data.sub_string.additional_elements != NULL) {
+ api_free_array(*entry->data.sub_string.additional_elements);
+ xfree(entry->data.sub_string.additional_elements);
+ }
+ xfree(entry->data.sub_string.sub);
+ break;
+ }
+ case kSDItemBufferList: {
+ api_free_array(entry->data.buffer_list);
+ break;
+ }
+ }
+}
+
+/// Read next unsigned integer from file
+///
+/// Errors out if the result is not an unsigned integer.
+///
+/// Unlike msgpack own function this one works with `FILE *` and reads *exactly*
+/// as much bytes as needed, making it possible to avoid both maintaining own
+/// buffer and calling `fseek`.
+///
+/// One byte from file stream is always consumed, even if it is not correct.
+///
+/// @param[in] fp File to read from.
+/// @param[out] result Location where result is saved.
+///
+/// @return OK if read was successfull, FAIL if it was not.
+static int msgpack_read_uint64(FILE *const fp, const int first_char,
+ uint64_t *const result)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ const long fpos = ftell(fp) - 1;
+
+ if (first_char == EOF) {
+ if (ferror(fp)) {
+ emsg2("System error while reading ShaDa file: %s",
+ strerror(errno));
+ } else if (feof(fp)) {
+ emsgn("Error while reading ShaDa file: "
+ "expected positive integer at position %" PRId64,
+ (int64_t) fpos);
+ }
+ return FAIL;
+ }
+
+ if (~first_char & 0x80) {
+ // Positive fixnum
+ *result = (uint64_t) ((uint8_t) first_char);
+ } else {
+ size_t length = 0;
+ switch (first_char) {
+ case 0xCC: { // uint8
+ length = 1;
+ break;
+ }
+ case 0xCD: { // uint16
+ length = 2;
+ break;
+ }
+ case 0xCE: { // uint32
+ length = 4;
+ break;
+ }
+ case 0xCF: { // uint64
+ length = 8;
+ break;
+ }
+ default: {
+ emsgn("Error while reading ShaDa file: "
+ "expected positive integer at position %" PRId64,
+ (int64_t) fpos);
+ return FAIL;
+ }
+ }
+ uint8_t buf[8];
+ size_t read_bytes = fread((char *) &(buf[0]), 1, length, fp);
+ if (ferror(fp)) {
+ emsg2("System error while reading ShaDa file: %s",
+ strerror(errno));
+ return FAIL;
+ } else if (read_bytes != length) {
+ emsgn("Error while reading ShaDa file: "
+ "not enough bytes for positive integer at position %" PRId64,
+ (int64_t) fpos);
+ return FAIL;
+ }
+ // TODO(ZyX-I): Just cast if current platform is big-endian.
+ *result = 0;
+ for (size_t i = length; i; i--) {
+ *result |= ((uint64_t) buf[i - 1]) << ((length - i) * 8);
+ }
+ }
+ return OK;
+}
+
+/// Iterate over shada file contents
+///
+/// @param[in] fp Pointer to the opened ShaDa file.
+/// @param[out] entry Address where next entry contents will be saved.
+///
+/// @return NOTDONE if entry was read correctly, FAIL if there were errors and
+/// OK at EOF.
+static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ entry->type = kSDItemMissing;
+ if (feof(fp)) {
+ return OK;
+ }
+
+ // First: manually unpack type, timestamp and length.
+ // This is needed to avoid both seeking and having to maintain a buffer.
+ uint64_t type_u64;
+ uint64_t timestamp_u64;
+ uint64_t length_u64;
+
+ const long initial_fpos = ftell(fp);
+ const int first_char = fgetc(fp);
+ if (first_char == EOF && feof(fp)) {
+ return OK;
+ }
+
+ if (msgpack_read_uint64(fp, first_char, &type_u64) != OK
+ || msgpack_read_uint64(fp, fgetc(fp), &timestamp_u64) != OK
+ || msgpack_read_uint64(fp, fgetc(fp), &length_u64) != OK) {
+ return FAIL;
+ }
+ if (type_u64 == 0) {
+ emsgn("Error while reading ShaDa file: "
+ "entry at position %" PRId64 "has invalid zero type",
+ (int64_t) initial_fpos);
+ return FAIL;
+ }
+
+ const size_t length = (size_t) length_u64;
+ entry->timestamp = (Timestamp) timestamp_u64;
+ if (type_u64 > SHADA_LAST_ENTRY) {
+ entry->data.unknown_item.size = length;
+ char *contents = xmalloc(length);
+ entry->data.unknown_item.contents = contents;
+ entry->data.unknown_item.type = type_u64;
+ size_t read_bytes = fread(contents, 1, length, fp);
+ if (ferror(fp)) {
+ free(entry->data.unknown_item.contents);
+ emsg2("System error while reading ShaDa file: %s",
+ strerror(errno));
+ return FAIL;
+ } else if (read_bytes != length) {
+ free(entry->data.unknown_item.contents);
+ emsgn("Error while reading ShaDa file: "
+ "last entry specified that it occupies %" PRId64 " bytes, "
+ "but file ended earlier",
+ (int64_t) length);
+ return FAIL;
+ }
+ entry->type = kSDItemUnknown;
+ return NOTDONE;
+ }
+
+ msgpack_unpacked unpacked;
+ msgpack_unpacked_init(&unpacked);
+ msgpack_unpacker *unpacker = msgpack_unpacker_new(length);
+ if (unpacker == NULL ||
+ !msgpack_unpacker_reserve_buffer(unpacker, length)) {
+ EMSG(e_outofmem);
+ goto shada_read_next_item_error;
+ }
+ msgpack_unpack_return result;
+ size_t read_bytes = 0;
+
+ const long fpos = ftell(fp);
+ read_bytes = fread(msgpack_unpacker_buffer(unpacker), 1, length, fp);
+ if (ferror(fp)) {
+ emsg2("System error while reading ShaDa file: %s",
+ strerror(errno));
+ goto shada_read_next_item_error;
+ } else if (read_bytes != length) {
+ emsgn("Error while reading ShaDa file: "
+ "last entry specified that it occupies %" PRId64 " bytes, "
+ "but file ended earlier",
+ (int64_t) fpos);
+ goto shada_read_next_item_error;
+ }
+ msgpack_unpacker_buffer_consumed(unpacker, read_bytes);
+
+ result = msgpack_unpacker_next(unpacker, &unpacked);
+ if (result != MSGPACK_UNPACK_SUCCESS) {
+ if (result == MSGPACK_UNPACK_NOMEM_ERROR) {
+ EMSG(e_outofmem);
+ goto shada_read_next_item_error;
+ }
+ if (result == MSGPACK_UNPACK_PARSE_ERROR) {
+ EMSG("Failed to parse ShaDa file");
+ goto shada_read_next_item_error;
+ }
+ }
+#define CHECK_KEY(key, expected) \
+ (key.via.str.size == sizeof(expected) - 1 \
+ && STRNCMP(key.via.str.ptr, expected, sizeof(expected) - 1) == 0)
+#define ID(s) s
+#define BINDUP(b) xmemdupz(b.ptr, b.size)
+#define TOINT(s) ((int) (s))
+#define TOLONG(s) ((long) (s))
+#define TOCHAR(s) ((char) (s))
+#define TOU8(s) ((uint8_t) (s))
+#define TOSIZE(s) ((size_t) (s))
+#define CHECKED_ENTRY(condition, error_desc, entry_name, obj, tgt, attr, \
+ proc) \
+ do { \
+ if (!(condition)) { \
+ emsgn("Error while reading ShaDa file: " \
+ entry_name " entry at position %" PRId64 " " \
+ error_desc, \
+ (int64_t) fpos); \
+ ga_clear(&ad_ga); \
+ goto shada_read_next_item_error; \
+ } \
+ tgt = proc(obj.via.attr); \
+ } while (0)
+#define CHECK_KEY_IS_STR(entry_name) \
+ do { \
+ if (unpacked.data.via.map.ptr[i].key.type != MSGPACK_OBJECT_STR) { \
+ emsgn("Error while reading ShaDa file: " \
+ entry_name " entry at position %" PRId64 " " \
+ "has key which is not a string", \
+ (int64_t) fpos); \
+ emsgn("It is %" PRId64 " instead", \
+ unpacked.data.via.map.ptr[i].key.type ); \
+ ga_clear(&ad_ga); \
+ goto shada_read_next_item_error; \
+ } \
+ } while (0)
+#define CHECKED_KEY(entry_name, name, error_desc, tgt, condition, attr, proc) \
+ if (CHECK_KEY(unpacked.data.via.map.ptr[i].key, name)) { \
+ CHECKED_ENTRY( \
+ condition, "has " name " key value " error_desc, \
+ entry_name, unpacked.data.via.map.ptr[i].val, \
+ tgt, attr, proc); \
+ }
+#define TYPED_KEY(entry_name, name, type_name, tgt, objtype, attr, proc) \
+ CHECKED_KEY( \
+ entry_name, name, " which is not " type_name, tgt, \
+ unpacked.data.via.map.ptr[i].val.type == MSGPACK_OBJECT_##objtype, \
+ attr, proc)
+#define BOOLEAN_KEY(entry_name, name, tgt) \
+ TYPED_KEY(entry_name, name, "a boolean", tgt, BOOLEAN, boolean, ID)
+#define STRING_KEY(entry_name, name, tgt) \
+ TYPED_KEY(entry_name, name, "a binary", tgt, BIN, bin, BINDUP)
+#define INT_KEY(entry_name, name, tgt, proc) \
+ CHECKED_KEY( \
+ entry_name, name, " which is not an integer", tgt, \
+ (unpacked.data.via.map.ptr[i].val.type \
+ == MSGPACK_OBJECT_POSITIVE_INTEGER \
+ || unpacked.data.via.map.ptr[i].val.type \
+ == MSGPACK_OBJECT_NEGATIVE_INTEGER), \
+ i64, proc)
+#define INTEGER_KEY(entry_name, name, tgt) \
+ INT_KEY(entry_name, name, tgt, TOINT)
+#define LONG_KEY(entry_name, name, tgt) \
+ INT_KEY(entry_name, name, tgt, TOLONG)
+#define ADDITIONAL_KEY \
+ { \
+ ga_grow(&ad_ga, 1); \
+ memcpy(((char *)ad_ga.ga_data) + ((size_t) ad_ga.ga_len \
+ * sizeof(*unpacked.data.via.map.ptr)),\
+ unpacked.data.via.map.ptr + i, \
+ sizeof(*unpacked.data.via.map.ptr)); \
+ ad_ga.ga_len++; \
+ }
+ switch ((ShadaEntryType) type_u64) {
+ case kSDItemHeader: {
+ if (!msgpack_rpc_to_dictionary(&(unpacked.data), &(entry->data.header))) {
+ emsgn("Error while reading ShaDa file: "
+ "header entry at position %" PRId64 " is not a dictionary",
+ (int64_t) fpos);
+ goto shada_read_next_item_error;
+ }
+ break;
+ }
+ case kSDItemSearchPattern: {
+ if (unpacked.data.type != MSGPACK_OBJECT_MAP) {
+ emsgn("Error while reading ShaDa file: "
+ "search pattern entry at position %" PRId64 " "
+ "is not a dictionary",
+ (int64_t) fpos);
+ goto shada_read_next_item_error;
+ }
+ entry->data.search_pattern = (struct search_pattern) {
+ .magic = true,
+ .smartcase = false,
+ .has_line_offset = false,
+ .place_cursor_at_end = false,
+ .offset = 0,
+ .is_last_used = true,
+ .is_substitute_pattern = false,
+ .pat = NULL,
+ .additional_data = NULL,
+ };
+ garray_T ad_ga;
+ ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1);
+ for (size_t i = 0; i < unpacked.data.via.map.size; i++) {
+ CHECK_KEY_IS_STR("search pattern");
+ BOOLEAN_KEY("search pattern", "magic", entry->data.search_pattern.magic)
+ else BOOLEAN_KEY("search pattern", "smartcase",
+ entry->data.search_pattern.smartcase)
+ else BOOLEAN_KEY("search pattern", "lineoff",
+ entry->data.search_pattern.has_line_offset)
+ else BOOLEAN_KEY("search pattern", "curatend",
+ entry->data.search_pattern.place_cursor_at_end)
+ else BOOLEAN_KEY("search pattern", "islast",
+ entry->data.search_pattern.is_last_used)
+ else BOOLEAN_KEY("search pattern", "sub",
+ entry->data.search_pattern.is_substitute_pattern)
+ else INTEGER_KEY("search pattern", "off",
+ entry->data.search_pattern.offset)
+ else STRING_KEY("search pattern", "pat", entry->data.search_pattern.pat)
+ else ADDITIONAL_KEY
+ }
+ if (entry->data.search_pattern.pat == NULL) {
+ emsgn("Error while reading ShaDa file: "
+ "search pattern entry at position %" PRId64 " "
+ "has no pattern",
+ (int64_t) fpos);
+ ga_clear(&ad_ga);
+ goto shada_read_next_item_error;
+ }
+ if (ad_ga.ga_len) {
+ msgpack_object obj = {
+ .type = MSGPACK_OBJECT_MAP,
+ .via = {
+ .map = {
+ .size = (uint32_t) ad_ga.ga_len,
+ .ptr = ad_ga.ga_data,
+ }
+ }
+ };
+ entry->data.search_pattern.additional_data =
+ xmalloc(sizeof(Dictionary));
+ if (!msgpack_rpc_to_dictionary(
+ &obj, entry->data.search_pattern.additional_data)) {
+ emsgu("Error while reading ShaDa file: "
+ "search pattern entry at position %" PRIu64 " "
+ "cannot be converted to a Dictionary",
+ (uint64_t) initial_fpos);
+ ga_clear(&ad_ga);
+ goto shada_read_next_item_error;
+ }
+ }
+ ga_clear(&ad_ga);
+ break;
+ }
+ case kSDItemJump:
+ case kSDItemGlobalMark:
+ case kSDItemLocalMark: {
+ if (unpacked.data.type != MSGPACK_OBJECT_MAP) {
+ emsgn("Error while reading ShaDa file: "
+ "mark entry at position %" PRId64 " "
+ "is not a dictionary",
+ (int64_t) fpos);
+ goto shada_read_next_item_error;
+ }
+ entry->data.filemark = (struct shada_filemark) {
+ .name = '"',
+ .mark = (pos_T) {1, 0, 0},
+ .fname = NULL,
+ .additional_data = NULL,
+ };
+ garray_T ad_ga;
+ ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1);
+ for (size_t i = 0; i < unpacked.data.via.map.size; i++) {
+ CHECK_KEY_IS_STR("mark");
+ CHECKED_KEY(
+ "mark", "name", " which is not an unsigned integer",
+ entry->data.filemark.name,
+ (type_u64 != kSDItemJump
+ && unpacked.data.via.map.ptr[i].val.type
+ == MSGPACK_OBJECT_POSITIVE_INTEGER),
+ u64, TOCHAR)
+ else LONG_KEY("mark", "line", entry->data.filemark.mark.lnum)
+ else INTEGER_KEY("mark", "col", entry->data.filemark.mark.col)
+ else STRING_KEY("mark", "file", entry->data.filemark.fname)
+ else ADDITIONAL_KEY
+ }
+ if (entry->data.filemark.mark.lnum == 0) {
+ emsgn("Error while reading ShaDa file: "
+ "mark entry at position %" PRId64 " "
+ "is missing line number",
+ (int64_t) fpos);
+ ga_clear(&ad_ga);
+ goto shada_read_next_item_error;
+ }
+ if (ad_ga.ga_len) {
+ msgpack_object obj = {
+ .type = MSGPACK_OBJECT_MAP,
+ .via = {
+ .map = {
+ .size = (uint32_t) ad_ga.ga_len,
+ .ptr = ad_ga.ga_data,
+ }
+ }
+ };
+ entry->data.filemark.additional_data = xmalloc(sizeof(Dictionary));
+ if (!msgpack_rpc_to_dictionary(
+ &obj, entry->data.filemark.additional_data)) {
+ emsgu("Error while reading ShaDa file: "
+ "mark entry at position %" PRIu64 " "
+ "cannot be converted to a Dictionary",
+ (uint64_t) initial_fpos);
+ ga_clear(&ad_ga);
+ goto shada_read_next_item_error;
+ }
+ }
+ ga_clear(&ad_ga);
+ break;
+ }
+ case kSDItemRegister: {
+ if (unpacked.data.type != MSGPACK_OBJECT_MAP) {
+ emsgn("Error while reading ShaDa file: "
+ "register entry at position %" PRId64 " "
+ "is not a dictionary",
+ (int64_t) fpos);
+ goto shada_read_next_item_error;
+ }
+ entry->data.reg = (struct reg) {
+ .name = NUL,
+ .type = MCHAR,
+ .contents = NULL,
+ .contents_size = 0,
+ .width = 0,
+ .additional_data = NULL,
+ };
+ garray_T ad_ga;
+ ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1);
+ for (size_t i = 0; i < unpacked.data.via.map.size; i++) {
+ CHECK_KEY_IS_STR("register");
+ TYPED_KEY("register", "type", "an unsigned integer",
+ entry->data.reg.type, POSITIVE_INTEGER, u64, TOU8)
+ else TYPED_KEY("register", "name", "an unsigned integer",
+ entry->data.reg.name, POSITIVE_INTEGER, u64, TOCHAR)
+ else TYPED_KEY("register", "width", "an unsigned integer",
+ entry->data.reg.width, POSITIVE_INTEGER, u64, TOSIZE)
+ else if (CHECK_KEY(unpacked.data.via.map.ptr[i].key, "contents")) {
+ if (unpacked.data.via.map.ptr[i].val.type != MSGPACK_OBJECT_ARRAY) {
+ emsgn("Error while reading ShaDa file: "
+ "register entry at position %" PRId64 " "
+ "has contents key with non-array value",
+ (int64_t) fpos);
+ ga_clear(&ad_ga);
+ goto shada_read_next_item_error;
+ }
+ if (unpacked.data.via.map.ptr[i].val.via.array.size == 0) {
+ emsgn("Error while reading ShaDa file: "
+ "register entry at position %" PRId64 " "
+ "has contents key with empty array",
+ (int64_t) fpos);
+ ga_clear(&ad_ga);
+ goto shada_read_next_item_error;
+ }
+ const msgpack_object_array arr =
+ unpacked.data.via.map.ptr[i].val.via.array;
+ for (size_t i = 0; i < arr.size; i++) {
+ if (arr.ptr[i].type != MSGPACK_OBJECT_BIN) {
+ emsgn("Error while reading ShaDa file: "
+ "register entry at position %" PRId64 " "
+ "has contents array with non-string value",
+ (int64_t) fpos);
+ ga_clear(&ad_ga);
+ goto shada_read_next_item_error;
+ }
+ }
+ entry->data.reg.contents_size = arr.size;
+ entry->data.reg.contents = xmalloc(arr.size * sizeof(char *));
+ for (size_t i = 0; i < arr.size; i++) {
+ entry->data.reg.contents[i] = xmemdupz(arr.ptr[i].via.bin.ptr,
+ arr.ptr[i].via.bin.size);
+ }
+ } else ADDITIONAL_KEY
+ }
+ if (entry->data.reg.contents == NULL) {
+ emsgn("Error while reading ShaDa file: "
+ "register entry at position %" PRId64 " "
+ "has missing contents array",
+ (int64_t) fpos);
+ ga_clear(&ad_ga);
+ goto shada_read_next_item_error;
+ }
+ if (ad_ga.ga_len) {
+ msgpack_object obj = {
+ .type = MSGPACK_OBJECT_MAP,
+ .via = {
+ .map = {
+ .size = (uint32_t) ad_ga.ga_len,
+ .ptr = ad_ga.ga_data,
+ }
+ }
+ };
+ entry->data.reg.additional_data = xmalloc(sizeof(Dictionary));
+ if (!msgpack_rpc_to_dictionary(
+ &obj, entry->data.reg.additional_data)) {
+ emsgu("Error while reading ShaDa file: "
+ "register entry at position %" PRIu64 " "
+ "cannot be converted to a Dictionary",
+ (uint64_t) initial_fpos);
+ ga_clear(&ad_ga);
+ goto shada_read_next_item_error;
+ }
+ }
+ ga_clear(&ad_ga);
+ break;
+ }
+ case kSDItemHistoryEntry: {
+ if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) {
+ emsgn("Error while reading ShaDa file: "
+ "history entry at position %" PRId64 " "
+ "is not an array",
+ (int64_t) fpos);
+ goto shada_read_next_item_error;
+ }
+ entry->data.history_item = (struct history_item) {
+ .histtype = 0,
+ .string = NULL,
+ .additional_elements = NULL
+ };
+ if (unpacked.data.via.array.size < 2) {
+ emsgn("Error while reading ShaDa file: "
+ "history entry at position %" PRId64 " "
+ "does not have enough elements",
+ (int64_t) fpos);
+ goto shada_read_next_item_error;
+ }
+ if (unpacked.data.via.array.ptr[0].type
+ != MSGPACK_OBJECT_POSITIVE_INTEGER) {
+ emsgn("Error while reading ShaDa file: "
+ "history entry at position %" PRId64 " "
+ "has wrong history type type",
+ (int64_t) fpos);
+ goto shada_read_next_item_error;
+ }
+ if (unpacked.data.via.array.ptr[1].type
+ != MSGPACK_OBJECT_BIN) {
+ emsgn("Error while reading ShaDa file: "
+ "history entry at position %" PRId64 " "
+ "has wrong history string type",
+ (int64_t) fpos);
+ goto shada_read_next_item_error;
+ }
+ entry->data.history_item.histtype =
+ (uint8_t) unpacked.data.via.array.ptr[0].via.u64;
+ entry->data.history_item.string =
+ xmemdupz(unpacked.data.via.array.ptr[1].via.bin.ptr,
+ unpacked.data.via.array.ptr[1].via.bin.size);
+ if (unpacked.data.via.array.size > 2) {
+ msgpack_object obj = {
+ .type = MSGPACK_OBJECT_ARRAY,
+ .via = {
+ .array = {
+ .size = unpacked.data.via.array.size - 2,
+ .ptr = unpacked.data.via.array.ptr + 2,
+ }
+ }
+ };
+ entry->data.history_item.additional_elements = xmalloc(sizeof(Array));
+ if (!msgpack_rpc_to_array(
+ &obj, entry->data.history_item.additional_elements)) {
+ emsgu("Error while reading ShaDa file: "
+ "history entry at position %" PRIu64 " "
+ "cannot be converted to an Array",
+ (uint64_t) initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ }
+ break;
+ }
+ case kSDItemVariable: {
+ if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) {
+ emsgn("Error while reading ShaDa file: "
+ "variable entry at position %" PRId64 " "
+ "is not an array",
+ (int64_t) fpos);
+ goto shada_read_next_item_error;
+ }
+ entry->data.global_var = (struct global_var) {
+ .name = NULL,
+ .value = {
+ .type = kObjectTypeNil,
+ },
+ .additional_elements = NULL
+ };
+ if (unpacked.data.via.array.size < 2) {
+ emsgn("Error while reading ShaDa file: "
+ "variable entry at position %" PRId64 " "
+ "does not have enough elements",
+ (int64_t) fpos);
+ goto shada_read_next_item_error;
+ }
+ if (unpacked.data.via.array.ptr[0].type != MSGPACK_OBJECT_BIN) {
+ emsgn("Error while reading ShaDa file: "
+ "variable entry at position %" PRId64 " "
+ "has wrong variable name type",
+ (int64_t) fpos);
+ goto shada_read_next_item_error;
+ }
+ if (unpacked.data.via.array.ptr[1].type == MSGPACK_OBJECT_NIL
+ || unpacked.data.via.array.ptr[1].type == MSGPACK_OBJECT_EXT) {
+ emsgn("Error while reading ShaDa file: "
+ "variable entry at position %" PRId64 " "
+ "has wrong variable value type",
+ (int64_t) fpos);
+ goto shada_read_next_item_error;
+ }
+ entry->data.global_var.name =
+ xmemdupz(unpacked.data.via.array.ptr[0].via.bin.ptr,
+ unpacked.data.via.array.ptr[0].via.bin.size);
+ if (!msgpack_rpc_to_object(&(unpacked.data.via.array.ptr[1]),
+ &(entry->data.global_var.value))) {
+ emsgu("Error while reading ShaDa file: "
+ "variable entry at position %" PRIu64 " "
+ "has value that cannot be converted to the object",
+ (uint64_t) initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ if (unpacked.data.via.array.size > 2) {
+ msgpack_object obj = {
+ .type = MSGPACK_OBJECT_ARRAY,
+ .via = {
+ .array = {
+ .size = unpacked.data.via.array.size - 2,
+ .ptr = unpacked.data.via.array.ptr + 2,
+ }
+ }
+ };
+ entry->data.global_var.additional_elements = xmalloc(sizeof(Array));
+ if (!msgpack_rpc_to_array(
+ &obj, entry->data.global_var.additional_elements)) {
+ emsgu("Error while reading ShaDa file: "
+ "variable entry at position %" PRIu64 " "
+ "cannot be converted to an Array",
+ (uint64_t) initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ }
+ break;
+ }
+ case kSDItemSubString: {
+ if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) {
+ emsgn("Error while reading ShaDa file: "
+ "sub string entry at position %" PRId64 " "
+ "is not an array",
+ (int64_t) fpos);
+ goto shada_read_next_item_error;
+ }
+ entry->data.sub_string = (struct sub_string) {
+ .sub = NULL,
+ .additional_elements = NULL
+ };
+ if (unpacked.data.via.array.size < 1) {
+ emsgn("Error while reading ShaDa file: "
+ "sub string entry at position %" PRId64 " "
+ "does not have enough elements",
+ (int64_t) fpos);
+ goto shada_read_next_item_error;
+ }
+ if (unpacked.data.via.array.ptr[0].type != MSGPACK_OBJECT_BIN) {
+ emsgn("Error while reading ShaDa file: "
+ "sub string entry at position %" PRId64 " "
+ "has wrong sub string type",
+ (int64_t) fpos);
+ goto shada_read_next_item_error;
+ }
+ entry->data.sub_string.sub =
+ xmemdupz(unpacked.data.via.array.ptr[0].via.bin.ptr,
+ unpacked.data.via.array.ptr[0].via.bin.size);
+ if (unpacked.data.via.array.size > 1) {
+ msgpack_object obj = {
+ .type = MSGPACK_OBJECT_ARRAY,
+ .via = {
+ .array = {
+ .size = unpacked.data.via.array.size - 1,
+ .ptr = unpacked.data.via.array.ptr + 1,
+ }
+ }
+ };
+ entry->data.sub_string.additional_elements = xmalloc(sizeof(Array));
+ if (!msgpack_rpc_to_array(
+ &obj, entry->data.sub_string.additional_elements)) {
+ emsgu("Error while reading ShaDa file: "
+ "sub string entry at position %" PRIu64 " "
+ "cannot be converted to an Array",
+ (uint64_t) initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ }
+ break;
+ }
+ case kSDItemBufferList: {
+ if (!msgpack_rpc_to_array(&(unpacked.data), &(entry->data.buffer_list))) {
+ emsgn("Error while reading ShaDa file: "
+ "buffer list entry at position %" PRId64 " is not an array",
+ (int64_t) fpos);
+ goto shada_read_next_item_error;
+ }
+ break;
+ }
+ case kSDItemMissing:
+ case kSDItemUnknown: {
+ emsgu("Error while reading ShaDa file: "
+ "there is an item at position %" PRIu64 " "
+ "that must not be there: Missing and Unknown items are "
+ "for internal uses only",
+ (uint64_t) initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ }
+ entry->type = (ShadaEntryType) type_u64;
+ goto shada_read_next_item_end;
+#undef CHECK_KEY
+#undef BOOLEAN_KEY
+#undef ADDITIONAL_KEY
+#undef ID
+#undef BINDUP
+#undef TOCHAR
+#undef TOINT
+#undef TOLONG
+#undef TYPED_KEY
+#undef INT_KEY
+#undef INTEGER_KEY
+#undef LONG_KEY
+#undef TOU8
+#undef TOSIZE
+shada_read_next_item_error:
+ msgpack_unpacked_destroy(&unpacked);
+ msgpack_unpacker_free(unpacker);
+ return FAIL;
+shada_read_next_item_end:
+ msgpack_unpacked_destroy(&unpacked);
+ msgpack_unpacker_free(unpacker);
+ return NOTDONE;
+}
+
+/// Return a list with all global marks set in current NeoVim instance
+///
+/// List is not sorted.
+///
+/// @warning Listed marks must be used before any buffer- or mark-editing
+/// function is run.
+///
+/// @return Array of ShadaEntry values, last one has type kSDItemMissing.
+///
+/// @warning Resulting ShadaEntry values must not be freed. Returned
+/// array must be freed with `xfree()`.
+static ShadaEntry *list_global_marks(void)
+{
+ const void *iter = NULL;
+ const size_t nummarks = mark_global_amount();
+ ShadaEntry *const ret = xmalloc(sizeof(ShadaEntry) * (nummarks + 1));
+ ShadaEntry *cur = ret;
+ if (nummarks) {
+ do {
+ cur->type = kSDItemGlobalMark;
+ xfmark_T cur_fm;
+ iter = mark_global_iter(iter, &(cur->data.filemark.name), &cur_fm);
+ cur->data.filemark.mark = cur_fm.fmark.mark;
+ cur->data.filemark.additional_data = cur_fm.fmark.additional_data;
+ cur->timestamp = cur_fm.fmark.timestamp;
+ if (cur_fm.fmark.fnum == 0) {
+ cur->data.filemark.fname = (char *) (cur_fm.fname == NULL
+ ? NULL
+ : cur_fm.fname);
+ } else {
+ const buf_T *const buf = buflist_findnr(cur_fm.fmark.fnum);
+ if (buf == NULL) {
+ continue;
+ } else {
+ cur->data.filemark.fname = (char *) buf->b_ffname;
+ }
+ }
+ if (cur->data.filemark.fname != NULL) {
+ if (shada_removable(cur->data.filemark.fname)) {
+ continue;
+ } else {
+ cur++;
+ }
+ }
+ } while(iter != NULL);
+ }
+ cur->type = kSDItemMissing;
+ return ret;
+}
+
+/// Return a list with all buffer marks set in some buffer
+///
+/// List is not sorted.
+///
+/// @warning Listed marks must be used before any buffer- or mark-editing
+/// function is run.
+///
+/// @param[in] buf Buffer for which marks are listed.
+///
+/// @return Array of ShadaEntry values, last one has type kSDItemMissing.
+///
+/// @warning Resulting ShadaEntry values must not be freed. Returned
+/// array must be freed with `xfree()`.
+static ShadaEntry *list_buffer_marks(const buf_T *const buf)
+{
+ const char *const fname = (char *) buf->b_ffname;
+ const void *iter = NULL;
+ const size_t nummarks = mark_buffer_amount(buf);
+ ShadaEntry *const ret = xmalloc(sizeof(ShadaEntry) * (nummarks + 1));
+ ShadaEntry *cur = ret;
+ if (nummarks) {
+ do {
+ cur->type = kSDItemLocalMark;
+ fmark_T cur_fm;
+ iter = mark_buffer_iter(iter, buf, &(cur->data.filemark.name), &cur_fm);
+ cur->data.filemark.mark = cur_fm.mark;
+ cur->data.filemark.fname = (char *) fname;
+ cur->data.filemark.additional_data = cur_fm.additional_data;
+ cur->timestamp = cur_fm.timestamp;
+ if (cur->data.filemark.mark.lnum != 0) {
+ cur++;
+ }
+ } while(iter != NULL);
+ }
+ cur->type = kSDItemMissing;
+ return ret;
+}
+
+/// Check whether "name" is on removable media (according to 'viminfo')
+///
+/// @param[in] name Checked name.
+///
+/// @return True if it is, false otherwise.
+bool shada_removable(const char *name)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ char *p;
+ char part[51];
+ int retval = FALSE;
+ size_t n;
+
+ char *new_name = home_replace_save(NULL, name);
+ for (p = (char *) p_viminfo; *p; ) {
+ (void) copy_option_part(&p, part, 51, ", ");
+ if (part[0] == 'r') {
+ n = STRLEN(part + 1);
+ if (STRNICMP(part + 1, new_name, n) == 0) {
+ retval = TRUE;
+ break;
+ }
+ }
+ }
+ xfree(new_name);
+ return retval;
+}
+
+/// Return a list with all registers and their contents
+///
+/// List is not sorted.
+///
+/// @warning Listed registers must be used before any register-editing function
+/// is run.
+///
+/// @param[in] max_num_lines Maximum number of lines in the register. If it is
+/// zero then all registers are listed.
+///
+/// @return Array of ShadaEntry values, last one has type kSDItemMissing.
+///
+/// @warning Resulting ShadaEntry values must not be freed. Returned
+/// array must be freed with `xfree()`.
+static ShadaEntry *list_registers(const size_t max_num_lines)
+{
+ const void *iter = NULL;
+ const size_t numregs = op_register_amount();
+ ShadaEntry *const ret = xmalloc(sizeof(ShadaEntry) * (numregs + 1));
+ ShadaEntry *cur = ret;
+ if (numregs) {
+ do {
+ cur->type = kSDItemRegister;
+ yankreg_T cur_reg;
+ iter = op_register_iter(iter, &(cur->data.reg.name), &cur_reg);
+ if (max_num_lines && (size_t) cur_reg.y_size > max_num_lines) {
+ continue;
+ }
+ cur->data.reg.contents = (char **) cur_reg.y_array;
+ cur->data.reg.type = (uint8_t) cur_reg.y_type;
+ cur->data.reg.contents_size = (size_t) cur_reg.y_size;
+ if (cur_reg.y_type == MBLOCK) {
+ cur->data.reg.width = (size_t) cur_reg.y_width;
+ } else {
+ cur->data.reg.width = 0;
+ }
+ cur->data.reg.additional_data = cur_reg.additional_data;
+ cur->timestamp = cur_reg.timestamp;
+ cur++;
+ } while(iter != NULL);
+ }
+ cur->type = kSDItemMissing;
+ return ret;
+}
+
+
+#if 0
+
+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);
+}
+
+/*
+ * 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++;
+ }
+ }
+ free(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;
+ }
+ }
+ free(name_buf);
+}
+
+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);
+ free(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;
+ free(name);
+
+ free(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, '-', '\'');
+ }
+}
+
+int read_viminfo_search_pattern(vir_T *virp, int force)
+{
+ 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);
+}
+
+void write_viminfo_search_pattern(FILE *fp)
+{
+ 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 "), '&');
+ }
+}
+
+static void
+wvsp_one (
+ FILE *fp, /* file to write to */
+ int idx, /* spats[] index */
+ char *s, /* search pat */
+ int sc /* dir char */
+)
+{
+ 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);
+ }
+}
+
+/*
+ * 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;
+ }
+}
+
+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]);
+ }
+ }
+}
+
+/*
+ * Restore global vars that start with a capital from the viminfo file
+ */
+int read_viminfo_varlist(vir_T *virp, int writing)
+{
+ 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 {
+ free(tv.vval.v_string);
+ tv = *etv;
+ free(etv);
+ }
+ }
+
+ set_var(virp->vir_line + 1, &tv, FALSE);
+
+ if (tv.v_type == VAR_STRING)
+ free(tv.vval.v_string);
+ else if (tv.v_type == VAR_DICT || tv.v_type == VAR_LIST)
+ clear_tv(&tv);
+ }
+ }
+ }
+
+ return viminfo_readline(virp);
+}
+
+/*
+ * Write global vars that start with a capital to the viminfo file
+ */
+void write_viminfo_varlist(FILE *fp)
+{
+ hashitem_T *hi;
+ dictitem_T *this_var;
+ int todo;
+ char *s;
+ char_u *p;
+ char_u *tofree;
+ char_u numbuf[NUMBUFLEN];
+
+ 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 = echo_string(&this_var->di_tv, &tofree, numbuf, 0);
+ if (p != NULL)
+ viminfo_writestring(fp, p);
+ free(tofree);
+ }
+ }
+ }
+}
+
+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);
+}
+
+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);
+ }
+}
+
+/*
+ * 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;
+#endif
diff --git a/src/nvim/shada.h b/src/nvim/shada.h
new file mode 100644
index 0000000000..f0935796d7
--- /dev/null
+++ b/src/nvim/shada.h
@@ -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
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index 75055cc21a..b0d1a17c89 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -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
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index 063c13084d..a04e7c2763 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -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];
}
diff --git a/src/nvim/undo_defs.h b/src/nvim/undo_defs.h
index 610adb4367..d841210815 100644
--- a/src/nvim/undo_defs.h
+++ b/src/nvim/undo_defs.h
@@ -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
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 992463a8fc..b71b2cb603 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -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);
diff --git a/test/functional/ex_getln/history_spec.lua b/test/functional/ex_getln/history_spec.lua
new file mode 100644
index 0000000000..c3ef56c8ad
--- /dev/null
+++ b/test/functional/ex_getln/history_spec.lua
@@ -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)