aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/api/private/helpers.c2
-rw-r--r--src/nvim/api/private/helpers.h2
-rw-r--r--src/nvim/auevents.lua2
-rw-r--r--src/nvim/buffer.c118
-rw-r--r--src/nvim/buffer_defs.h25
-rw-r--r--src/nvim/edit.c6
-rw-r--r--src/nvim/eval.c452
-rw-r--r--src/nvim/eval.h4
-rw-r--r--src/nvim/eval_defs.h13
-rw-r--r--src/nvim/ex_cmds.c604
-rw-r--r--src/nvim/ex_cmds.h14
-rw-r--r--src/nvim/ex_cmds.lua16
-rw-r--r--src/nvim/ex_docmd.c26
-rw-r--r--src/nvim/ex_getln.c428
-rw-r--r--src/nvim/ex_getln.h29
-rw-r--r--src/nvim/fileio.c12
-rw-r--r--src/nvim/globals.h4
-rw-r--r--src/nvim/lib/khash.h433
-rw-r--r--src/nvim/lib/ringbuf.h281
-rw-r--r--src/nvim/main.c25
-rw-r--r--src/nvim/mark.c764
-rw-r--r--src/nvim/mark.h69
-rw-r--r--src/nvim/mark_defs.h38
-rw-r--r--src/nvim/mbyte.c2
-rw-r--r--src/nvim/memory.c2
-rw-r--r--src/nvim/misc1.c7
-rw-r--r--src/nvim/normal.c4
-rw-r--r--src/nvim/ops.c319
-rw-r--r--src/nvim/ops.h63
-rw-r--r--src/nvim/option.c32
-rw-r--r--src/nvim/option_defs.h2
-rw-r--r--src/nvim/options.lua10
-rw-r--r--src/nvim/os/fs_defs.h5
-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.c192
-rw-r--r--src/nvim/search.h24
-rw-r--r--src/nvim/shada.c4040
-rw-r--r--src/nvim/shada.h7
-rw-r--r--src/nvim/strings.c15
-rw-r--r--src/nvim/tag.c2
-rw-r--r--src/nvim/testdir/test8.in2
-rw-r--r--src/nvim/undo.c27
-rw-r--r--src/nvim/undo_defs.h3
-rw-r--r--src/nvim/window.c2
47 files changed, 5986 insertions, 2158 deletions
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 0485fbacd2..7a0b5191d7 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -597,7 +597,7 @@ static void init_type_metadata(Dictionary *metadata)
}
/// Creates a deep clone of an object
-static Object copy_object(Object obj)
+Object copy_object(Object obj)
{
switch (obj.type) {
case kObjectTypeNil:
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..b3eba4f5f6 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,9 +556,21 @@ static void free_buffer(buf_T *buf)
free_buffer_stuff(buf, TRUE);
unref_var_dict(buf->b_vars);
aubuflocal_remove(buf);
+ dict_unref(buf->additional_data);
+ clear_fmark(&buf->b_last_cursor);
+ clear_fmark(&buf->b_last_insert);
+ clear_fmark(&buf->b_last_change);
+ for (size_t i = 0; i < NMARKS; i++) {
+ free_fmark(buf->b_namedm[i]);
+ }
+ for (int i = 0; i < buf->b_changelistlen; i++) {
+ free_fmark(buf->b_changelist[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.
+ memset(&buf->b_namedm[0], 0, sizeof(buf->b_namedm));
+ memset(&buf->b_changelist[0], 0, sizeof(buf->b_changelist));
buf->b_next = au_pending_free_buf;
au_pending_free_buf = buf;
} else {
@@ -1978,12 +1991,18 @@ buflist_nr2name (
fullname ? buf->b_ffname : buf->b_fname);
}
-/*
- * Set the "lnum" and "col" for the buffer "buf" and the current window.
- * When "copy_options" is TRUE save the local window option values.
- * When "lnum" is 0 only do the options.
- */
-static void buflist_setfpos(buf_T *buf, win_T *win, linenr_T lnum, colnr_T col, int copy_options)
+/// Set the line and column numbers for the given buffer and window
+///
+/// @param[in,out] buf Buffer for which line and column are set.
+/// @param[in,out] win Window for which line and column are set.
+/// @param[in] lnum Line number to be set. If it is zero then only
+/// options are touched.
+/// @param[in] col Column number to be set.
+/// @param[in] copy_options If true save the local window option values.
+void buflist_setfpos(buf_T *const buf, win_T *const win,
+ linenr_T lnum, colnr_T col,
+ bool copy_options)
+ FUNC_ATTR_NONNULL_ALL
{
wininfo_T *wip;
@@ -4164,93 +4183,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..3eabb7ee43 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,21 +506,21 @@ 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
- 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_cursor; // cursor position when last unloading this
+ // buffer
+ 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
*/
- pos_T b_changelist[JUMPLISTSIZE];
+ fmark_T b_changelist[JUMPLISTSIZE];
int b_changelistlen; /* number of active entries */
bool b_new_change; /* set by u_savecommon() */
@@ -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.
@@ -757,6 +748,8 @@ struct file_buffer {
signlist_T *b_signlist; /* list of signs to draw */
Terminal *terminal; // Terminal instance associated with the buffer
+
+ dict_T *additional_data; // Additional data from shada file if any.
};
/*
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..d02348028a 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -108,18 +108,6 @@
function/variable names. */
/*
- * In a hashtab item "hi_key" points to "di_key" in a dictitem.
- * This avoids adding a pointer to the hashtab item.
- * DI2HIKEY() converts a dictitem pointer to a hashitem key pointer.
- * HIKEY2DI() converts a hashitem key pointer to a dictitem pointer.
- * HI2DI() converts a hashitem pointer to a dictitem pointer.
- */
-static dictitem_T dumdi;
-#define DI2HIKEY(di) ((di)->di_key)
-#define HIKEY2DI(p) ((dictitem_T *)(p - (dumdi.di_key - (char_u *)&dumdi)))
-#define HI2DI(hi) HIKEY2DI((hi)->hi_key)
-
-/*
* Structure returned by get_lval() and used by set_var_lval().
* For a plain name:
* "name" points to the variable name.
@@ -354,7 +342,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: */
@@ -5352,7 +5340,7 @@ static int list_concat(list_T *l1, list_T *l2, typval_T *tv)
return FAIL;
/* make a copy of the first list. */
- l = list_copy(l1, FALSE, 0);
+ l = list_copy(NULL, l1, false, 0);
if (l == NULL)
return FAIL;
tv->v_type = VAR_LIST;
@@ -5363,13 +5351,20 @@ static int list_concat(list_T *l1, list_T *l2, typval_T *tv)
return OK;
}
-/*
- * Make a copy of list "orig". Shallow if "deep" is FALSE.
- * The refcount of the new list is set to 1.
- * See item_copy() for "copyID".
- * Returns NULL if orig is NULL or some failure happens.
- */
-static list_T *list_copy(list_T *orig, int deep, int copyID)
+/// Make a copy of list
+///
+/// @param[in] conv If non-NULL, then all internal strings will be converted.
+/// @param[in] orig Original list to copy.
+/// @param[in] deep If false, then shallow copy will be done.
+/// @param[in] copyID See var_item_copy().
+///
+/// @return Copied list. May be NULL in case original list is NULL or some
+/// failure happens. The refcount of the new list is set to 1.
+static list_T *list_copy(const vimconv_T *const conv,
+ list_T *const orig,
+ const bool deep,
+ const int copyID)
+ FUNC_ATTR_WARN_UNUSED_RESULT
{
listitem_T *item;
listitem_T *ni;
@@ -5388,7 +5383,7 @@ static list_T *list_copy(list_T *orig, int deep, int copyID)
item = item->li_next) {
ni = listitem_alloc();
if (deep) {
- if (item_copy(&item->li_tv, &ni->li_tv, deep, copyID) == FAIL) {
+ if (var_item_copy(conv, &item->li_tv, &ni->li_tv, deep, copyID) == FAIL) {
xfree(ni);
break;
}
@@ -5546,6 +5541,7 @@ static int list_join(garray_T *const gap, list_T *const l,
bool garbage_collect(void)
{
bool abort = false;
+#define ABORTING(func) abort = abort || func
// Only do this once.
want_garbage_collect = false;
@@ -5564,45 +5560,117 @@ bool garbage_collect(void)
// referenced through previous_funccal. This must be first, because if
// the item is referenced elsewhere the funccal must not be freed.
for (funccall_T *fc = previous_funccal; fc != NULL; fc = fc->caller) {
- abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1, NULL);
- abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1, NULL);
+ ABORTING(set_ref_in_ht)(&fc->l_vars.dv_hashtab, copyID + 1, NULL);
+ ABORTING(set_ref_in_ht)(&fc->l_avars.dv_hashtab, copyID + 1, NULL);
}
// script-local variables
for (int i = 1; i <= ga_scripts.ga_len; ++i) {
- abort = abort || set_ref_in_ht(&SCRIPT_VARS(i), copyID, NULL);
+ ABORTING(set_ref_in_ht)(&SCRIPT_VARS(i), copyID, NULL);
}
- // buffer-local variables
FOR_ALL_BUFFERS(buf) {
- abort = abort || set_ref_in_item(&buf->b_bufvar.di_tv, copyID, NULL, NULL);
+ // buffer-local variables
+ ABORTING(set_ref_in_item)(&buf->b_bufvar.di_tv, copyID, NULL, NULL);
+ // buffer marks (ShaDa additional data)
+ ABORTING(set_ref_in_fmark)(buf->b_last_cursor, copyID);
+ ABORTING(set_ref_in_fmark)(buf->b_last_insert, copyID);
+ ABORTING(set_ref_in_fmark)(buf->b_last_change, copyID);
+ for (size_t i = 0; i < NMARKS; i++) {
+ ABORTING(set_ref_in_fmark)(buf->b_namedm[i], copyID);
+ }
+ // buffer change list (ShaDa additional data)
+ for (int i = 0; i < buf->b_changelistlen; i++) {
+ ABORTING(set_ref_in_fmark)(buf->b_changelist[i], copyID);
+ }
+ // buffer ShaDa additional data
+ ABORTING(set_ref_dict)(buf->additional_data, copyID);
}
- // window-local variables
FOR_ALL_TAB_WINDOWS(tp, wp) {
- abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID, NULL, NULL);
+ // window-local variables
+ ABORTING(set_ref_in_item)(&wp->w_winvar.di_tv, copyID, NULL, NULL);
+ // window jump list (ShaDa additional data)
+ for (int i = 0; i < wp->w_jumplistlen; i++) {
+ ABORTING(set_ref_in_fmark)(wp->w_jumplist[i].fmark, copyID);
+ }
}
if (aucmd_win != NULL) {
- abort = abort ||
- set_ref_in_item(&aucmd_win->w_winvar.di_tv, copyID, NULL, NULL);
+ ABORTING(set_ref_in_item)(&aucmd_win->w_winvar.di_tv, copyID, NULL, NULL);
+ }
+
+ // registers (ShaDa additional data)
+ {
+ const void *reg_iter = NULL;
+ do {
+ yankreg_T reg;
+ char name = NUL;
+ reg_iter = op_register_iter(reg_iter, &name, &reg);
+ if (name != NUL) {
+ ABORTING(set_ref_dict)(reg.additional_data, copyID);
+ }
+ } while (reg_iter != NULL);
+ }
+
+ // global marks (ShaDa additional data)
+ {
+ const void *mark_iter = NULL;
+ do {
+ xfmark_T fm;
+ char name = NUL;
+ mark_iter = mark_global_iter(mark_iter, &name, &fm);
+ if (name != NUL) {
+ ABORTING(set_ref_dict)(fm.fmark.additional_data, copyID);
+ }
+ } while (mark_iter != NULL);
}
// tabpage-local variables
FOR_ALL_TABS(tp) {
- abort = abort || set_ref_in_item(&tp->tp_winvar.di_tv, copyID, NULL, NULL);
+ ABORTING(set_ref_in_item)(&tp->tp_winvar.di_tv, copyID, NULL, NULL);
}
// global variables
- abort = abort || set_ref_in_ht(&globvarht, copyID, NULL);
+ ABORTING(set_ref_in_ht)(&globvarht, copyID, NULL);
// function-local variables
for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->caller) {
- abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL);
- abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL);
+ ABORTING(set_ref_in_ht)(&fc->l_vars.dv_hashtab, copyID, NULL);
+ ABORTING(set_ref_in_ht)(&fc->l_avars.dv_hashtab, copyID, NULL);
}
// v: vars
- abort = abort || set_ref_in_ht(&vimvarht, copyID, NULL);
+ ABORTING(set_ref_in_ht)(&vimvarht, copyID, NULL);
+
+ // history items (ShaDa additional elements)
+ if (p_hi) {
+ for (uint8_t i = 0; i < HIST_COUNT; i++) {
+ const void *iter = NULL;
+ do {
+ histentry_T hist;
+ iter = hist_iter(iter, i, false, &hist);
+ if (hist.hisstr != NULL) {
+ ABORTING(set_ref_list)(hist.additional_elements, copyID);
+ }
+ } while (iter != NULL);
+ }
+ }
+
+ // previously used search/substitute patterns (ShaDa additional data)
+ {
+ SearchPattern pat;
+ get_search_pattern(&pat);
+ ABORTING(set_ref_dict)(pat.additional_data, copyID);
+ get_substitute_pattern(&pat);
+ ABORTING(set_ref_dict)(pat.additional_data, copyID);
+ }
+
+ // previously used replacement string
+ {
+ SubReplacementString sub;
+ sub_get_replacement(&sub);
+ ABORTING(set_ref_list)(sub.additional_elements, copyID);
+ }
bool did_free = false;
if (!abort) {
@@ -5631,6 +5699,7 @@ bool garbage_collect(void)
verb_msg((char_u *)_(
"Not enough memory to set references, garbage collection aborted!"));
}
+#undef ABORTING
return did_free;
}
@@ -5690,6 +5759,7 @@ static int free_unref_items(int copyID)
///
/// @returns true if setting references failed somehow.
bool set_ref_in_ht(hashtab_T *ht, int copyID, list_stack_T **list_stack)
+ FUNC_ATTR_WARN_UNUSED_RESULT
{
bool abort = false;
ht_stack_T *ht_stack = NULL;
@@ -5732,6 +5802,7 @@ bool set_ref_in_ht(hashtab_T *ht, int copyID, list_stack_T **list_stack)
///
/// @returns true if setting references failed somehow.
bool set_ref_in_list(list_T *l, int copyID, ht_stack_T **ht_stack)
+ FUNC_ATTR_WARN_UNUSED_RESULT
{
bool abort = false;
list_stack_T *list_stack = NULL;
@@ -5772,6 +5843,7 @@ bool set_ref_in_list(list_T *l, int copyID, ht_stack_T **ht_stack)
/// @returns true if setting references failed somehow.
bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack,
list_stack_T **list_stack)
+ FUNC_ATTR_WARN_UNUSED_RESULT
{
bool abort = false;
@@ -5821,6 +5893,52 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack,
return abort;
}
+/// Mark all lists and dicts referenced in given mark
+///
+/// @returns true if setting references failed somehow.
+static inline bool set_ref_in_fmark(fmark_T fm, int copyID)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (fm.additional_data != NULL
+ && fm.additional_data->dv_copyID != copyID) {
+ fm.additional_data->dv_copyID = copyID;
+ return set_ref_in_ht(&fm.additional_data->dv_hashtab, copyID, NULL);
+ }
+ return false;
+}
+
+/// Mark all lists and dicts referenced in given list and the list itself
+///
+/// @returns true if setting references failed somehow.
+static inline bool set_ref_list(list_T *list, int copyID)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (list != NULL) {
+ typval_T tv = (typval_T) {
+ .v_type = VAR_LIST,
+ .vval = { .v_list = list }
+ };
+ return set_ref_in_item(&tv, copyID, NULL, NULL);
+ }
+ return false;
+}
+
+/// Mark all lists and dicts referenced in given dict and the dict itself
+///
+/// @returns true if setting references failed somehow.
+static inline bool set_ref_dict(dict_T *dict, int copyID)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (dict != NULL) {
+ typval_T tv = (typval_T) {
+ .v_type = VAR_DICT,
+ .vval = { .v_dict = dict }
+ };
+ return set_ref_in_item(&tv, copyID, NULL, NULL);
+ }
+ return false;
+}
+
/*
* Allocate an empty header for a dictionary.
*/
@@ -5964,13 +6082,20 @@ void dictitem_free(dictitem_T *item)
xfree(item);
}
-/*
- * Make a copy of dict "d". Shallow if "deep" is FALSE.
- * The refcount of the new dict is set to 1.
- * See item_copy() for "copyID".
- * Returns NULL if orig is NULL or some other failure.
- */
-static dict_T *dict_copy(dict_T *orig, int deep, int copyID)
+/// Make a copy of dictionary
+///
+/// @param[in] conv If non-NULL, then all internal strings will be converted.
+/// @param[in] orig Original dictionary to copy.
+/// @param[in] deep If false, then shallow copy will be done.
+/// @param[in] copyID See var_item_copy().
+///
+/// @return Copied dictionary. May be NULL in case original dictionary is NULL
+/// or some failure happens. The refcount of the new dictionary is set
+/// to 1.
+static dict_T *dict_copy(const vimconv_T *const conv,
+ dict_T *const orig,
+ const bool deep,
+ const int copyID)
{
dictitem_T *di;
int todo;
@@ -5990,10 +6115,21 @@ static dict_T *dict_copy(dict_T *orig, int deep, int copyID)
if (!HASHITEM_EMPTY(hi)) {
--todo;
- di = dictitem_alloc(hi->hi_key);
+ if (conv == NULL || conv->vc_type == CONV_NONE) {
+ di = dictitem_alloc(hi->hi_key);
+ } else {
+ char *const key = (char *) string_convert((vimconv_T *) conv,
+ hi->hi_key, NULL);
+ if (key == NULL) {
+ di = dictitem_alloc(hi->hi_key);
+ } else {
+ di = dictitem_alloc((char_u *) key);
+ xfree(key);
+ }
+ }
if (deep) {
- if (item_copy(&HI2DI(hi)->di_tv, &di->di_tv, deep,
- copyID) == FAIL) {
+ if (var_item_copy(conv, &HI2DI(hi)->di_tv, &di->di_tv, deep,
+ copyID) == FAIL) {
xfree(di);
break;
}
@@ -6305,7 +6441,7 @@ failret:
/// the results.
/// @param firstargname Name of the first argument.
/// @param name Name of the target converter.
-#define DEFINE_VIML_CONV_FUNCTIONS(name, firstargtype, firstargname) \
+#define DEFINE_VIML_CONV_FUNCTIONS(scope, name, firstargtype, firstargname) \
static int name##_convert_one_value(firstargtype firstargname, \
MPConvStack *const mpstack, \
typval_T *const tv, \
@@ -6543,7 +6679,7 @@ name##_convert_one_value_regular_dict: \
return OK; \
} \
\
-static int vim_to_##name(firstargtype firstargname, typval_T *const tv) \
+scope int vim_to_##name(firstargtype firstargname, typval_T *const tv) \
FUNC_ATTR_WARN_UNUSED_RESULT \
{ \
current_copyID += COPYID_INC; \
@@ -6739,7 +6875,7 @@ vim_to_msgpack_error_ret: \
#define CONV_ALLOW_SPECIAL false
-DEFINE_VIML_CONV_FUNCTIONS(string, garray_T *const, gap)
+DEFINE_VIML_CONV_FUNCTIONS(static, string, garray_T *const, gap)
#undef CONV_RECURSE
#define CONV_RECURSE(val, conv_type) \
@@ -6769,7 +6905,7 @@ DEFINE_VIML_CONV_FUNCTIONS(string, garray_T *const, gap)
return OK; \
} while (0)
-DEFINE_VIML_CONV_FUNCTIONS(echo, garray_T *const, gap)
+DEFINE_VIML_CONV_FUNCTIONS(static, echo, garray_T *const, gap)
#undef CONV_STRING
#undef CONV_STR_STRING
@@ -8344,7 +8480,7 @@ static void f_confirm(typval_T *argvars, typval_T *rettv)
*/
static void f_copy(typval_T *argvars, typval_T *rettv)
{
- item_copy(&argvars[0], rettv, FALSE, 0);
+ var_item_copy(NULL, &argvars[0], rettv, false, 0);
}
/*
@@ -8513,7 +8649,9 @@ static void f_deepcopy(typval_T *argvars, typval_T *rettv)
EMSG(_(e_invarg));
else {
current_copyID += COPYID_INC;
- item_copy(&argvars[0], rettv, TRUE, noref == 0 ? current_copyID : 0);
+ var_item_copy(NULL, &argvars[0], rettv, true, (noref == 0
+ ? current_copyID
+ : 0));
}
}
@@ -10482,6 +10620,7 @@ static void f_has(typval_T *argvars, typval_T *rettv)
"scrollbind",
"showcmd",
"cmdline_info",
+ "shada",
"signs",
"smartindent",
"startuptime",
@@ -10498,7 +10637,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",
@@ -12486,7 +12624,7 @@ static inline bool vim_list_to_buf(const list_T *const list,
#define CONV_ALLOW_SPECIAL true
-DEFINE_VIML_CONV_FUNCTIONS(msgpack, msgpack_packer *const, packer)
+DEFINE_VIML_CONV_FUNCTIONS(, msgpack, msgpack_packer *const, packer)
#undef CONV_STRING
#undef CONV_STR_STRING
@@ -12592,7 +12730,7 @@ static inline ListReaderState init_lrstate(const list_T *const list)
}
/// Convert msgpack object to a VimL one
-static int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv)
+int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
#define INIT_SPECIAL_DICT(tv, type, val) \
@@ -17353,14 +17491,14 @@ char_u *get_vim_var_str(int idx) FUNC_ATTR_PURE FUNC_ATTR_NONNULL_RET
* Get List v: variable value. Caller must take care of reference count when
* needed.
*/
-list_T *get_vim_var_list(int idx) FUNC_ATTR_PURE FUNC_ATTR_NONNULL_RET
+list_T *get_vim_var_list(int idx) FUNC_ATTR_PURE
{
return vimvars[idx].vv_list;
}
/// Get Dictionary v: variable value. Caller must take care of reference count
/// when needed.
-dict_T *get_vim_var_dict(int idx) FUNC_ATTR_PURE FUNC_ATTR_NONNULL_RET
+dict_T *get_vim_var_dict(int idx) FUNC_ATTR_PURE
{
return vimvars[idx].vv_dict;
}
@@ -18446,14 +18584,28 @@ void copy_tv(typval_T *from, typval_T *to)
}
}
-/*
- * Make a copy of an item.
- * Lists and Dictionaries are also copied. A deep copy if "deep" is set.
- * For deepcopy() "copyID" is zero for a full copy or the ID for when a
- * reference to an already copied list/dict can be used.
- * Returns FAIL or OK.
- */
-static int item_copy(typval_T *from, typval_T *to, int deep, int copyID)
+/// Make a copy of an item
+///
+/// Lists and Dictionaries are also copied.
+///
+/// @param[in] conv If not NULL, convert all copied strings.
+/// @param[in] from Value to copy.
+/// @param[out] to Location where to copy to.
+/// @param[in] deep If true, use copy the container and all of the contained
+/// containers (nested).
+/// @param[in] copyID If non-zero then when container is referenced more then
+/// once then copy of it that was already done is used. E.g.
+/// when copying list `list = [list2, list2]` (`list[0] is
+/// list[1]`) var_item_copy with zero copyID will emit
+/// a copy with (`copy[0] isnot copy[1]`), with non-zero it
+/// will emit a copy with (`copy[0] is copy[1]`) like in the
+/// original list. Not use when deep is false.
+int var_item_copy(const vimconv_T *const conv,
+ typval_T *const from,
+ typval_T *const to,
+ const bool deep,
+ const int copyID)
+ FUNC_ATTR_NONNULL_ARG(2, 3)
{
static int recurse = 0;
int ret = OK;
@@ -18467,10 +18619,23 @@ static int item_copy(typval_T *from, typval_T *to, int deep, int copyID)
switch (from->v_type) {
case VAR_NUMBER:
case VAR_FLOAT:
- case VAR_STRING:
case VAR_FUNC:
copy_tv(from, to);
break;
+ case VAR_STRING:
+ if (conv == NULL || conv->vc_type == CONV_NONE) {
+ copy_tv(from, to);
+ } else {
+ to->v_type = VAR_STRING;
+ to->v_lock = 0;
+ if ((to->vval.v_string = string_convert((vimconv_T *)conv,
+ from->vval.v_string,
+ NULL))
+ == NULL) {
+ to->vval.v_string = (char_u *) xstrdup((char *) from->vval.v_string);
+ }
+ }
+ break;
case VAR_LIST:
to->v_type = VAR_LIST;
to->v_lock = 0;
@@ -18480,8 +18645,9 @@ static int item_copy(typval_T *from, typval_T *to, int deep, int copyID)
/* use the copy made earlier */
to->vval.v_list = from->vval.v_list->lv_copylist;
++to->vval.v_list->lv_refcount;
- } else
- to->vval.v_list = list_copy(from->vval.v_list, deep, copyID);
+ } else {
+ to->vval.v_list = list_copy(conv, from->vval.v_list, deep, copyID);
+ }
if (to->vval.v_list == NULL)
ret = FAIL;
break;
@@ -18494,13 +18660,14 @@ static int item_copy(typval_T *from, typval_T *to, int deep, int copyID)
/* use the copy made earlier */
to->vval.v_dict = from->vval.v_dict->dv_copydict;
++to->vval.v_dict->dv_refcount;
- } else
- to->vval.v_dict = dict_copy(from->vval.v_dict, deep, copyID);
+ } else {
+ to->vval.v_dict = dict_copy(conv, from->vval.v_dict, deep, copyID);
+ }
if (to->vval.v_dict == NULL)
ret = FAIL;
break;
default:
- EMSG2(_(e_intern2), "item_copy()");
+ EMSG2(_(e_intern2), "var_item_copy()");
ret = FAIL;
}
--recurse;
@@ -20710,109 +20877,64 @@ static var_flavour_T var_flavour(char_u *varname)
if (ASCII_ISUPPER(*p)) {
while (*(++p))
- if (ASCII_ISLOWER(*p))
+ if (ASCII_ISLOWER(*p)) {
return VAR_FLAVOUR_SESSION;
- return VAR_FLAVOUR_VIMINFO;
- } else
+ }
+ 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_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 ((size_t) (hi - hifirst) < hinum
+ && (HASHITEM_EMPTY(hi)
+ || var_flavour(HI2DI(hi)->di_key) != VAR_FLAVOUR_SHADA)) {
+ hi++;
+ }
+ if ((size_t) (hi - hifirst) == hinum) {
+ return NULL;
+ }
+ } else {
+ hi = (const hashitem_T *) iter;
+ }
+ *name = (char *) HI2DI(hi)->di_key;
+ copy_tv(&(HI2DI(hi)->di_tv), rettv);
+ while ((size_t) (++hi - hifirst) < hinum) {
+ if (!HASHITEM_EMPTY(hi)
+ && var_flavour(HI2DI(hi)->di_key) == VAR_FLAVOUR_SHADA) {
+ return hi;
}
}
-
- return viminfo_readline(virp);
+ return NULL;
}
-/*
- * Write global vars that start with a capital to the viminfo file
- */
-void write_viminfo_varlist(FILE *fp)
+void var_set_global(const char *const name, typval_T vartv)
{
- hashitem_T *hi;
- dictitem_T *this_var;
- int todo;
- char *s;
- char_u *p;
-
- if (find_viminfo_parameter('!') == NULL)
- return;
-
- fputs(_("\n# global variables:\n"), fp);
-
- todo = (int)globvarht.ht_used;
- for (hi = globvarht.ht_array; todo > 0; ++hi) {
- if (!HASHITEM_EMPTY(hi)) {
- --todo;
- this_var = HI2DI(hi);
- if (var_flavour(this_var->di_key) == VAR_FLAVOUR_VIMINFO) {
- switch (this_var->di_tv.v_type) {
- case VAR_STRING: s = "STR"; break;
- case VAR_NUMBER: s = "NUM"; break;
- case VAR_FLOAT: s = "FLO"; break;
- case VAR_DICT: s = "DIC"; break;
- case VAR_LIST: s = "LIS"; break;
- default: continue;
- }
- fprintf(fp, "!%s\t%s\t", this_var->di_key, s);
- p = (char_u *) echo_string(&this_var->di_tv, NULL);
- if (p != NULL) {
- viminfo_writestring(fp, p);
- }
- xfree(p);
- }
- }
- }
+ funccall_T *const saved_current_funccal = current_funccal;
+ current_funccal = NULL;
+ set_var((char_u *) name, &vartv, false);
+ current_funccal = saved_current_funccal;
}
int store_session_globals(FILE *fd)
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index 8f065eda33..864daed716 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -1,6 +1,8 @@
#ifndef NVIM_EVAL_H
#define NVIM_EVAL_H
+#include <msgpack.h>
+
#include "nvim/profile.h"
/* Defines for Vim variables. These must match vimvars[] in eval.c! */
@@ -72,6 +74,8 @@ enum {
/// Maximum number of function arguments
#define MAX_FUNC_ARGS 20
+int vim_to_msgpack(msgpack_packer *const, typval_T *const);
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval.h.generated.h"
#endif
diff --git a/src/nvim/eval_defs.h b/src/nvim/eval_defs.h
index a8a8acd048..373f1e6278 100644
--- a/src/nvim/eval_defs.h
+++ b/src/nvim/eval_defs.h
@@ -2,6 +2,7 @@
#define NVIM_EVAL_DEFS_H
#include <limits.h>
+#include <stddef.h>
#include "nvim/hashtab.h"
@@ -132,4 +133,16 @@ typedef struct list_stack_S {
struct list_stack_S *prev;
} list_stack_T;
+// In a hashtab item "hi_key" points to "di_key" in a dictitem.
+// This avoids adding a pointer to the hashtab item.
+
+/// Convert a dictitem pointer to a hashitem key pointer
+#define DI2HIKEY(di) ((di)->di_key)
+
+/// Convert a hashitem key pointer to a dictitem pointer
+#define HIKEY2DI(p) ((dictitem_T *)(p - offsetof(dictitem_T, di_key)))
+
+/// Convert a hashitem pointer to a dictitem pointer
+#define HI2DI(hi) HIKEY2DI((hi)->hi_key)
+
#endif // NVIM_EVAL_DEFS_H
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 81abf2fa63..5db3880026 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -67,6 +67,7 @@
#include "nvim/os/os.h"
#include "nvim/os/shell.h"
#include "nvim/os/input.h"
+#include "nvim/os/time.h"
/*
* Struct to hold the sign properties.
@@ -1391,550 +1392,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 +2821,33 @@ 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) {
+ list_unref(old_sub.additional_elements);
+ }
+ old_sub = sub;
+}
/* do_sub()
*
@@ -3473,16 +2955,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 +3986,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..721145efd8 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/eval_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.
+ list_T *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_cmds.lua b/src/nvim/ex_cmds.lua
index 5221554306..77f7dba81b 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -2120,6 +2120,12 @@ return {
func='ex_wrongmodifier',
},
{
+ command='rshada',
+ flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN),
+ addr_type=ADDR_LINES,
+ func='ex_shada',
+ },
+ {
command='runtime',
flags=bit.bor(BANG, NEEDARG, FILES, TRLBAR, SBOXOK, CMDWIN),
addr_type=ADDR_LINES,
@@ -2153,7 +2159,7 @@ return {
command='rviminfo',
flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
- func='ex_viminfo',
+ func='ex_shada',
},
{
command='substitute',
@@ -3032,6 +3038,12 @@ return {
func='ex_wsverb',
},
{
+ command='wshada',
+ flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN),
+ addr_type=ADDR_LINES,
+ func='ex_shada',
+ },
+ {
command='wundo',
flags=bit.bor(BANG, NEEDARG, FILE1),
addr_type=ADDR_LINES,
@@ -3041,7 +3053,7 @@ return {
command='wviminfo',
flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
- func='ex_viminfo',
+ func='ex_shada',
},
{
command='xit',
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index f7162896ff..a9262ca6ea 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;
@@ -9139,22 +9140,21 @@ int put_line(FILE *fd, char *s)
}
/*
- * ":rviminfo" and ":wviminfo".
+ * ":rshada" and ":wshada".
*/
-static void ex_viminfo(exarg_T *eap)
+static void ex_shada(exarg_T *eap)
{
- char_u *save_viminfo;
+ char_u *save_shada;
- save_viminfo = p_viminfo;
- 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);
- p_viminfo = save_viminfo;
+ save_shada = p_shada;
+ if (*p_shada == NUL)
+ p_shada = (char_u *)"'100";
+ if (eap->cmdidx == CMD_rviminfo || eap->cmdidx == CMD_rshada) {
+ (void) shada_read_everything((char *) eap->arg, eap->forceit, false);
+ } else {
+ shada_write_file((char *) eap->arg, eap->forceit);
+ }
+ p_shada = save_shada;
}
/*
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 9739090d7c..24e31b1ed7 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -65,6 +65,7 @@
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/event/loop.h"
+#include "nvim/os/time.h"
/*
* Variables shared between getcmdline(), redrawcmdline() and others.
@@ -100,12 +101,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 +4225,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 +4246,18 @@ 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
+{
+ xfree(hisptr->hisstr);
+ list_unref(hisptr->additional_elements);
+ clear_hist_entry(hisptr);
+}
+
+static inline void clear_hist_entry(histentry_T *hisptr)
+ FUNC_ATTR_NONNULL_ALL
{
- hisptr->hisnum = 0;
- hisptr->viminfo = FALSE;
- hisptr->hisstr = NULL;
+ memset(hisptr, 0, sizeof(*hisptr));
}
/*
@@ -4268,9 +4268,8 @@ static int
in_history (
int type,
char_u *str,
- int move_to_front, /* Move the entry to the front if it exists */
- int sep,
- int writing /* ignore entries read from viminfo */
+ int move_to_front, // Move the entry to the front if it exists
+ int sep
)
{
int i;
@@ -4288,7 +4287,6 @@ in_history (
* well. */
p = history[type][i].hisstr;
if (STRCMP(str, p) == 0
- && !(writing && history[type][i].viminfo)
&& (type != HIST_SEARCH || sep == p[STRLEN(p) + 1])) {
if (!move_to_front)
return TRUE;
@@ -4300,6 +4298,7 @@ in_history (
} while (i != hisidx[type]);
if (last_i >= 0) {
+ list_T *const list = history[type][i].additional_elements;
str = history[type][i].hisstr;
while (i != hisidx[type]) {
if (++i >= hislen)
@@ -4307,12 +4306,14 @@ in_history (
history[type][last_i] = history[type][i];
last_i = i;
}
+ list_unref(list);
history[type][i].hisnum = ++hisnum[type];
- history[type][i].viminfo = FALSE;
history[type][i].hisstr = str;
- return TRUE;
+ history[type][i].timestamp = os_time();
+ history[type][i].additional_elements = NULL;
+ return true;
}
- return FALSE;
+ return false;
}
/*
@@ -4372,27 +4373,27 @@ 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;
}
last_maptick = -1;
}
- if (!in_history(histype, new_entry, TRUE, sep, FALSE)) {
+ if (!in_history(histype, new_entry, true, sep)) {
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];
- hisptr->viminfo = FALSE;
if (histype == HIST_SEARCH && in_map)
last_maptick = maptick;
}
@@ -4545,23 +4546,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;
@@ -4578,7 +4577,7 @@ int del_history_entry(int histype, char_u *str)
int idx;
int i;
int last;
- int found = FALSE;
+ bool found = false;
regmatch.regprog = NULL;
regmatch.rm_ic = FALSE; /* always match case */
@@ -4595,9 +4594,8 @@ int del_history_entry(int histype, char_u *str)
if (hisptr->hisstr == NULL)
break;
if (vim_regexec(&regmatch, hisptr->hisstr, (colnr_T)0)) {
- found = TRUE;
- xfree(hisptr->hisstr);
- clear_hist_entry(hisptr);
+ found = true;
+ hist_free_entry(hisptr);
} else {
if (i != last) {
history[histype][last] = *hisptr;
@@ -4628,7 +4626,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 +4639,10 @@ int del_history_idx(int histype, int idx)
history[histype][i] = history[histype][j];
i = j;
}
- clear_hist_entry(&history[histype][i]);
- if (--i < 0)
+ clear_hist_entry(&history[histype][idx]);
+ if (--i < 0) {
i += hislen;
+ }
hisidx[histype] = i;
return TRUE;
}
@@ -4762,248 +4761,31 @@ 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 '/' */
-)
+/// Translate a history type number to the associated character
+int hist_type2char(int type)
+ FUNC_ATTR_CONST
{
- if (type == HIST_CMD)
- return ':';
- if (type == HIST_SEARCH) {
- if (use_question)
- return '?';
- else
+ switch (type) {
+ case HIST_CMD: {
+ return ':';
+ }
+ case HIST_SEARCH: {
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;
+ case HIST_EXPR: {
+ return '=';
}
- 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;
+ case HIST_INPUT: {
+ return '@';
}
- idx += 1;
- idx %= hislen;
- for (i = 0; i < viminfo_hisidx[type]; i++) {
- history[type][idx++].hisnum = ++hisnum[type];
- idx %= hislen;
+ case HIST_DEBUG: {
+ return '>';
}
- 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;
- }
- }
+ default: {
+ assert(false);
}
- 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;
}
+ return NUL;
}
/*
@@ -5294,3 +5076,87 @@ 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 uint8_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);
+}
+
+/// Get array of history items
+///
+/// @param[in] history_type Type of the history to get array for.
+/// @param[out] new_hisidx Location where last index in the new array should
+/// be saved.
+/// @param[out] new_hisnum Location where last history number in the new
+/// history should be saved.
+///
+/// @return Pointer to the array or NULL.
+histentry_T *hist_get_array(const uint8_t history_type, int **const new_hisidx,
+ int **const new_hisnum)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
+{
+ init_history();
+ *new_hisidx = &(hisidx[history_type]);
+ *new_hisnum = &(hisnum[history_type]);
+ return history[history_type];
+}
diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h
index 2b82f934d5..c537d681c6 100644
--- a/src/nvim/ex_getln.h
+++ b/src/nvim/ex_getln.h
@@ -1,6 +1,7 @@
#ifndef NVIM_EX_GETLN_H
#define NVIM_EX_GETLN_H
+#include "nvim/eval_defs.h"
#include "nvim/ex_cmds.h"
/* Values for nextwild() and ExpandOne(). See ExpandOne() for meaning. */
@@ -23,18 +24,28 @@
#define WILD_ESCAPE 128
#define WILD_ICASE 256
-/*
- * There are four history tables:
- */
-#define HIST_CMD 0 /* colon commands */
-#define HIST_SEARCH 1 /* search commands */
-#define HIST_EXPR 2 /* expressions (from entering = register) */
-#define HIST_INPUT 3 /* input() lines */
-#define HIST_DEBUG 4 /* debug commands */
-#define HIST_COUNT 5 /* number of history tables */
+/// Present history tables
+typedef enum {
+ HIST_CMD, ///< Colon commands.
+ HIST_SEARCH, ///< Search commands.
+ HIST_EXPR, ///< Expressions (e.g. from entering = register).
+ HIST_INPUT, ///< input() lines.
+ HIST_DEBUG, ///< Debug commands.
+} HistoryType;
+
+/// Number of history tables
+#define HIST_COUNT (HIST_DEBUG + 1)
typedef char_u *(*CompleteListItemGetter)(expand_T *, int);
+/// History entry definition
+typedef struct hist_entry {
+ int hisnum; ///< Entry identifier number.
+ char_u *hisstr; ///< Actual entry, separator char after the NUL.
+ Timestamp timestamp; ///< Time when entry was added.
+ list_T *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..a7472b40e2 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,16 +2167,17 @@ 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);
+ if (!curbuf->b_marks_read && get_shada_parameter('\'') > 0
+ && curbuf->b_ffname != NULL) {
+ shada_read_marks();
+ }
- /* Always set b_marks_read; needed when 'viminfo' is changed to include
+ /* Always set b_marks_read; needed when 'shada' is changed to include
* the ' parameter after opening a buffer. */
curbuf->b_marks_read = true;
}
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 60d03cec0c..0ef0a12889 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -891,8 +891,8 @@ EXTERN int must_redraw INIT(= 0); /* type of redraw necessary */
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 int need_highlight_changed INIT(= true);
+EXTERN char *used_shada_file INIT(= NULL); // name of the ShaDa file to use
#define NSCRIPT 15
EXTERN FILE *scriptin[NSCRIPT]; /* streams to read script from */
diff --git a/src/nvim/lib/khash.h b/src/nvim/lib/khash.h
index 96e7ea6df0..56be29d14c 100644
--- a/src/nvim/lib/khash.h
+++ b/src/nvim/lib/khash.h
@@ -114,8 +114,8 @@ int main() {
*/
-#ifndef __AC_KHASH_H
-#define __AC_KHASH_H
+#ifndef NVIM_LIB_KHASH_H
+#define NVIM_LIB_KHASH_H
/*!
@header
@@ -194,166 +194,229 @@ static const double __ac_HASH_UPPER = 0.77;
khval_t *vals; \
} kh_##name##_t;
-#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \
- extern kh_##name##_t *kh_init_##name(void); \
- extern void kh_destroy_##name(kh_##name##_t *h); \
- extern void kh_clear_##name(kh_##name##_t *h); \
- extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \
- extern void kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \
- extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \
- extern void kh_del_##name(kh_##name##_t *h, khint_t x);
-
-#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
- SCOPE kh_##name##_t *kh_init_##name(void) { \
- return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \
- } \
- SCOPE void kh_destroy_##name(kh_##name##_t *h) \
- REAL_FATTR_UNUSED; \
- SCOPE void kh_destroy_##name(kh_##name##_t *h) \
- { \
- if (h) { \
- kfree((void *)h->keys); kfree(h->flags); \
- kfree((void *)h->vals); \
- kfree(h); \
- } \
- } \
- SCOPE void kh_clear_##name(kh_##name##_t *h) \
- REAL_FATTR_UNUSED; \
- SCOPE void kh_clear_##name(kh_##name##_t *h) \
- { \
- if (h && h->flags) { \
- memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \
- h->size = h->n_occupied = 0; \
- } \
- } \
- SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \
- REAL_FATTR_UNUSED; \
- SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \
- { \
- if (h->n_buckets) { \
- khint_t k, i, last, mask, step = 0; \
- mask = h->n_buckets - 1; \
- k = __hash_func(key); i = k & mask; \
- last = i; \
- while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
- i = (i + (++step)) & mask; \
- if (i == last) return h->n_buckets; \
- } \
- return __ac_iseither(h->flags, i)? h->n_buckets : i; \
- } else return 0; \
- } \
- SCOPE void kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \
- REAL_FATTR_UNUSED; \
- SCOPE void kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \
- { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \
- khint32_t *new_flags = 0; \
- khint_t j = 1; \
- { \
- kroundup32(new_n_buckets); \
- if (new_n_buckets < 4) new_n_buckets = 4; \
- if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \
- else { /* hash table size to be changed (shrink or expand); rehash */ \
- new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
- memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
- if (h->n_buckets < new_n_buckets) { /* expand */ \
- khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
- h->keys = new_keys; \
- if (kh_is_map) { \
- khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \
- h->vals = new_vals; \
- } \
- } /* otherwise shrink */ \
- } \
- } \
- if (j) { /* rehashing is needed */ \
- for (j = 0; j != h->n_buckets; ++j) { \
- if (__ac_iseither(h->flags, j) == 0) { \
- khkey_t key = h->keys[j]; \
- khval_t val; \
- khint_t new_mask; \
- new_mask = new_n_buckets - 1; \
- if (kh_is_map) val = h->vals[j]; \
- __ac_set_isdel_true(h->flags, j); \
- while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \
- khint_t k, i, step = 0; \
- k = __hash_func(key); \
- i = k & new_mask; \
- while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \
- __ac_set_isempty_false(new_flags, i); \
- if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \
- { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \
- if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \
- __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \
- } else { /* write the element and jump out of the loop */ \
- h->keys[i] = key; \
- if (kh_is_map) h->vals[i] = val; \
- break; \
- } \
- } \
- } \
- } \
- if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \
- h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
- if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \
- } \
- kfree(h->flags); /* free the working space */ \
- h->flags = new_flags; \
- h->n_buckets = new_n_buckets; \
- h->n_occupied = h->size; \
- h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \
- } \
- } \
- SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \
- REAL_FATTR_UNUSED; \
- SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \
- { \
- khint_t x; \
- if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \
- if (h->n_buckets > (h->size<<1)) { \
- kh_resize_##name(h, h->n_buckets - 1); /* clear "deleted" elements */ \
- } else { \
- kh_resize_##name(h, h->n_buckets + 1); /* expand the hash table */ \
- } \
- } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \
- { \
- khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \
- x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \
- if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \
- else { \
- last = i; \
- while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
- if (__ac_isdel(h->flags, i)) site = i; \
- i = (i + (++step)) & mask; \
- if (i == last) { x = site; break; } \
- } \
- if (x == h->n_buckets) { \
- if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \
- else x = i; \
- } \
- } \
- } \
- if (__ac_isempty(h->flags, x)) { /* not present at all */ \
- h->keys[x] = key; \
- __ac_set_isboth_false(h->flags, x); \
- ++h->size; ++h->n_occupied; \
- *ret = 1; \
- } else if (__ac_isdel(h->flags, x)) { /* deleted */ \
- h->keys[x] = key; \
- __ac_set_isboth_false(h->flags, x); \
- ++h->size; \
- *ret = 2; \
- } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \
- return x; \
- } \
- SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \
- REAL_FATTR_UNUSED; \
- SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \
- { \
- if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \
- __ac_set_isdel_true(h->flags, x); \
- --h->size; \
- } \
- }
+#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \
+ extern kh_##name##_t *kh_init_##name(void); \
+ extern void kh_dealloc_##name(kh_##name##_t *h); \
+ extern void kh_destroy_##name(kh_##name##_t *h); \
+ extern void kh_clear_##name(kh_##name##_t *h); \
+ extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \
+ extern void kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \
+ extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \
+ extern void kh_del_##name(kh_##name##_t *h, khint_t x);
+
+#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, \
+ __hash_equal) \
+ SCOPE kh_##name##_t *kh_init_##name(void) \
+ REAL_FATTR_UNUSED; \
+ SCOPE kh_##name##_t *kh_init_##name(void) { \
+ return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \
+ } \
+ SCOPE void kh_dealloc_##name(kh_##name##_t *h) \
+ REAL_FATTR_UNUSED; \
+ SCOPE void kh_dealloc_##name(kh_##name##_t *h) \
+ { \
+ kfree(h->keys); \
+ kfree(h->flags); \
+ kfree(h->vals); \
+ } \
+ SCOPE void kh_destroy_##name(kh_##name##_t *h) \
+ REAL_FATTR_UNUSED; \
+ SCOPE void kh_destroy_##name(kh_##name##_t *h) \
+ { \
+ if (h) { \
+ kh_dealloc_##name(h); \
+ kfree(h); \
+ } \
+ } \
+ SCOPE void kh_clear_##name(kh_##name##_t *h) \
+ REAL_FATTR_UNUSED; \
+ SCOPE void kh_clear_##name(kh_##name##_t *h) \
+ { \
+ if (h && h->flags) { \
+ memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \
+ h->size = h->n_occupied = 0; \
+ } \
+ } \
+ SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \
+ REAL_FATTR_UNUSED; \
+ SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \
+ { \
+ if (h->n_buckets) { \
+ khint_t k, i, last, mask, step = 0; \
+ mask = h->n_buckets - 1; \
+ k = __hash_func(key); i = k & mask; \
+ last = i; \
+ while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || \
+ !__hash_equal(h->keys[i], key))) { \
+ i = (i + (++step)) & mask; \
+ if (i == last) { \
+ return h->n_buckets; \
+ } \
+ } \
+ return __ac_iseither(h->flags, i) ? h->n_buckets : i; \
+ } else { \
+ return 0; \
+ } \
+ } \
+ SCOPE void kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \
+ REAL_FATTR_UNUSED; \
+ SCOPE void kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \
+ { /* This function uses 0.25*n_buckets bytes of working space instead of */ \
+ /* [sizeof(key_t+val_t)+.25]*n_buckets. */ \
+ khint32_t *new_flags = 0; \
+ khint_t j = 1; \
+ { \
+ kroundup32(new_n_buckets); \
+ if (new_n_buckets < 4) { \
+ new_n_buckets = 4; \
+ } \
+ /* requested size is too small */ \
+ if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) { \
+ j = 0; \
+ } else { /* hash table size to be changed (shrink or expand); rehash */ \
+ new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) \
+ * sizeof(khint32_t)); \
+ memset(new_flags, 0xaa, \
+ __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
+ if (h->n_buckets < new_n_buckets) { /* expand */ \
+ khkey_t *new_keys = (khkey_t*)krealloc( \
+ (void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
+ h->keys = new_keys; \
+ if (kh_is_map) { \
+ khval_t *new_vals = (khval_t*)krealloc( \
+ (void *)h->vals, new_n_buckets * sizeof(khval_t)); \
+ h->vals = new_vals; \
+ } \
+ } /* otherwise shrink */ \
+ } \
+ } \
+ if (j) { /* rehashing is needed */ \
+ for (j = 0; j != h->n_buckets; ++j) { \
+ if (__ac_iseither(h->flags, j) == 0) { \
+ khkey_t key = h->keys[j]; \
+ khval_t val; \
+ khint_t new_mask; \
+ new_mask = new_n_buckets - 1; \
+ if (kh_is_map) { \
+ val = h->vals[j]; \
+ } \
+ __ac_set_isdel_true(h->flags, j); \
+ /* kick-out process; sort of like in Cuckoo hashing */ \
+ while (1) { \
+ khint_t k, i, step = 0; \
+ k = __hash_func(key); \
+ i = k & new_mask; \
+ while (!__ac_isempty(new_flags, i)) { \
+ i = (i + (++step)) & new_mask; \
+ } \
+ __ac_set_isempty_false(new_flags, i); \
+ /* kick out the existing element */ \
+ if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { \
+ { \
+ khkey_t tmp = h->keys[i]; \
+ h->keys[i] = key; \
+ key = tmp; \
+ } \
+ if (kh_is_map) { \
+ khval_t tmp = h->vals[i]; \
+ h->vals[i] = val; \
+ val = tmp; \
+ } \
+ /* mark it as deleted in the old hash table */ \
+ __ac_set_isdel_true(h->flags, i); \
+ } else { /* write the element and jump out of the loop */ \
+ h->keys[i] = key; \
+ if (kh_is_map) { \
+ h->vals[i] = val; \
+ } \
+ break; \
+ } \
+ } \
+ } \
+ } \
+ if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \
+ h->keys = (khkey_t*)krealloc((void *)h->keys, \
+ new_n_buckets * sizeof(khkey_t)); \
+ if (kh_is_map) { \
+ h->vals = (khval_t*)krealloc((void *)h->vals, \
+ new_n_buckets * sizeof(khval_t)); \
+ } \
+ } \
+ kfree(h->flags); /* free the working space */ \
+ h->flags = new_flags; \
+ h->n_buckets = new_n_buckets; \
+ h->n_occupied = h->size; \
+ h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \
+ } \
+ } \
+ SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \
+ REAL_FATTR_UNUSED; \
+ SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \
+ { \
+ khint_t x; \
+ if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \
+ if (h->n_buckets > (h->size << 1)) { \
+ kh_resize_##name(h, h->n_buckets - 1); /* clear "deleted" elements */ \
+ } else { \
+ kh_resize_##name(h, h->n_buckets + 1); /* expand the hash table */ \
+ } \
+ } /* TODO: implement automatically shrinking; */ \
+ /* resize() already support shrinking */ \
+ { \
+ khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \
+ x = site = h->n_buckets; \
+ k = __hash_func(key); \
+ i = k & mask; \
+ if (__ac_isempty(h->flags, i)) { \
+ x = i; /* for speed up */ \
+ } else { \
+ last = i; \
+ while (!__ac_isempty(h->flags, i) \
+ && (__ac_isdel(h->flags, i) \
+ || !__hash_equal(h->keys[i], key))) { \
+ if (__ac_isdel(h->flags, i)) { \
+ site = i; \
+ } \
+ i = (i + (++step)) & mask; \
+ if (i == last) { \
+ x = site; \
+ break; \
+ } \
+ } \
+ if (x == h->n_buckets) { \
+ if (__ac_isempty(h->flags, i) && site != h->n_buckets) { \
+ x = site; \
+ } else { \
+ x = i; \
+ } \
+ } \
+ } \
+ } \
+ if (__ac_isempty(h->flags, x)) { /* not present at all */ \
+ h->keys[x] = key; \
+ __ac_set_isboth_false(h->flags, x); \
+ h->size++; \
+ h->n_occupied++; \
+ *ret = 1; \
+ } else if (__ac_isdel(h->flags, x)) { /* deleted */ \
+ h->keys[x] = key; \
+ __ac_set_isboth_false(h->flags, x); \
+ h->size++; \
+ *ret = 2; \
+ } else { \
+ *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \
+ } \
+ return x; \
+ } \
+ SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \
+ REAL_FATTR_UNUSED; \
+ SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \
+ { \
+ if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \
+ __ac_set_isdel_true(h->flags, x); \
+ --h->size; \
+ } \
+ }
#define KHASH_DECLARE(name, khkey_t, khval_t) \
__KHASH_TYPE(name, khkey_t, khval_t) \
@@ -447,6 +510,13 @@ static kh_inline khint_t __ac_Wang_hash(khint_t key)
#define kh_destroy(name, h) kh_destroy_##name(h)
/*! @function
+ @abstract Free memory referenced directly inside a hash table.
+ @param name Name of the hash table [symbol]
+ @param h Pointer to the hash table [khash_t(name)*]
+ */
+#define kh_dealloc(name, h) kh_dealloc_##name(h)
+
+/*! @function
@abstract Reset a hash table without deallocating memory.
@param name Name of the hash table [symbol]
@param h Pointer to the hash table [khash_t(name)*]
@@ -577,6 +647,24 @@ static kh_inline khint_t __ac_Wang_hash(khint_t key)
code; \
} }
+/*! @function
+ @abstract Iterate over the keys in the hash table
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param kvar Variable to which value will be assigned
+ @param code Block of code to execute
+ */
+#define kh_foreach_key(h, kvar, code) \
+ { \
+ khint_t __i; \
+ for (__i = kh_begin(h); __i != kh_end(h); __i++) { \
+ if (!kh_exist(h, __i)) { \
+ continue; \
+ } \
+ (kvar) = kh_key(h, __i); \
+ code; \
+ } \
+ }
+
/* More conenient interfaces */
/*! @function
@@ -622,6 +710,21 @@ typedef const char *kh_cstr_t;
@param name Name of the hash table [symbol]
@param khval_t Type of values [type]
*/
-#define KHASH_MAP_INIT_STR(name, khval_t) \
- KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal)
-#endif /* __AC_KHASH_H */
+#define KHASH_MAP_INIT_STR(name, khval_t) \
+ KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal)
+
+/*! @function
+ @abstract Return a literal for an empty hash table.
+ @param name Name of the hash table [symbol]
+ */
+#define KHASH_EMPTY_TABLE(name) \
+ ((kh_##name##_t) { \
+ .n_buckets = 0, \
+ .size = 0, \
+ .n_occupied = 0, \
+ .upper_bound = 0, \
+ .flags = NULL, \
+ .keys = NULL, \
+ .vals = NULL, \
+ })
+#endif // NVIM_LIB_KHASH_H
diff --git a/src/nvim/lib/ringbuf.h b/src/nvim/lib/ringbuf.h
new file mode 100644
index 0000000000..cb71500bb7
--- /dev/null
+++ b/src/nvim/lib/ringbuf.h
@@ -0,0 +1,281 @@
+/// Macros-based ring buffer implementation.
+///
+/// Supported functions:
+///
+/// - new: allocates new ring buffer.
+/// - dealloc: free ring buffer itself.
+/// - free: free ring buffer and all its elements.
+/// - push: adds element to the end of the buffer.
+/// - length: get buffer length.
+/// - size: size of the ring buffer.
+/// - idx: get element at given index.
+/// - idx_p: get pointer to the element at given index.
+/// - insert: insert element at given position.
+/// - remove: remove element from given position.
+#ifndef NVIM_LIB_RINGBUF_H
+#define NVIM_LIB_RINGBUF_H
+
+#include <string.h>
+#include <assert.h>
+#include <stdint.h>
+
+#include "nvim/memory.h"
+#include "nvim/func_attr.h"
+
+#define _RINGBUF_LENGTH(rb) \
+ ((rb)->first == NULL ? 0 \
+ : ((rb)->next == (rb)->first) ? (size_t) ((rb)->buf_end - (rb)->buf) + 1 \
+ : ((rb)->next > (rb)->first) ? (size_t) ((rb)->next - (rb)->first) \
+ : (size_t) ((rb)->next - (rb)->buf + (rb)->buf_end - (rb)->first + 1))
+
+#define _RINGBUF_NEXT(rb, var) \
+ ((var) == (rb)->buf_end ? (rb)->buf : (var) + 1)
+#define _RINGBUF_PREV(rb, var) \
+ ((var) == (rb)->buf ? (rb)->buf_end : (var) - 1)
+
+/// Iterate over all ringbuf values
+///
+/// @param rb Ring buffer to iterate over.
+/// @param RBType Type of the ring buffer element.
+/// @param varname Variable name.
+#define RINGBUF_FORALL(rb, RBType, varname) \
+ size_t varname##_length_fa_ = _RINGBUF_LENGTH(rb); \
+ for (RBType *varname = ((rb)->first == NULL ? (rb)->next : (rb)->first); \
+ varname##_length_fa_; \
+ (varname = _RINGBUF_NEXT(rb, varname)), \
+ varname##_length_fa_--)
+
+/// Iterate over all ringbuf values, from end to the beginning
+///
+/// Unlike previous RINGBUF_FORALL uses already defined variable, in place of
+/// defining variable in the cycle body.
+///
+/// @param rb Ring buffer to iterate over.
+/// @param RBType Type of the ring buffer element.
+/// @param varname Variable name.
+#define RINGBUF_ITER_BACK(rb, RBType, varname) \
+ size_t varname##_length_ib_ = _RINGBUF_LENGTH(rb); \
+ for (varname = ((rb)->next == (rb)->buf ? (rb)->buf_end : (rb)->next - 1); \
+ varname##_length_ib_; \
+ (varname = _RINGBUF_PREV(rb, varname)), \
+ varname##_length_ib_--)
+
+/// Define a ring buffer structure
+///
+/// @param TypeName Ring buffer type name. Actual type name will be
+/// `{TypeName}RingBuffer`.
+/// @param RBType Type of the single ring buffer element.
+#define RINGBUF_TYPEDEF(TypeName, RBType) \
+typedef struct { \
+ RBType *buf; \
+ RBType *next; \
+ RBType *first; \
+ RBType *buf_end; \
+} TypeName##RingBuffer;
+
+/// Initialize a new ring buffer
+///
+/// @param TypeName Ring buffer type name. Actual type name will be
+/// `{TypeName}RingBuffer`.
+/// @param funcprefix Prefix for all ring buffer functions. Function name will
+/// look like `{funcprefix}_rb_{function_name}`.
+/// @param RBType Type of the single ring buffer element.
+/// @param rbfree Function used to free ring buffer element. May be
+/// a macros like `#define RBFREE(item)` (to skip freeing).
+///
+/// Intended function signature: `void *rbfree(RBType *)`;
+#define RINGBUF_INIT(TypeName, funcprefix, RBType, rbfree) \
+ \
+ \
+static inline TypeName##RingBuffer funcprefix##_rb_new(const size_t size) \
+ REAL_FATTR_WARN_UNUSED_RESULT; \
+static inline TypeName##RingBuffer funcprefix##_rb_new(const size_t size) \
+{ \
+ assert(size != 0); \
+ RBType *buf = xmalloc(size * sizeof(RBType)); \
+ return (TypeName##RingBuffer) { \
+ .buf = buf, \
+ .next = buf, \
+ .first = NULL, \
+ .buf_end = buf + size - 1, \
+ }; \
+} \
+ \
+static inline void funcprefix##_rb_free(TypeName##RingBuffer *const rb) \
+ REAL_FATTR_UNUSED; \
+static inline void funcprefix##_rb_free(TypeName##RingBuffer *const rb) \
+{ \
+ if (rb == NULL) { \
+ return; \
+ } \
+ RINGBUF_FORALL(rb, RBType, rbitem) { \
+ rbfree(rbitem); \
+ } \
+ xfree(rb->buf); \
+} \
+ \
+static inline void funcprefix##_rb_dealloc(TypeName##RingBuffer *const rb) \
+ REAL_FATTR_UNUSED; \
+static inline void funcprefix##_rb_dealloc(TypeName##RingBuffer *const rb) \
+{ \
+ xfree(rb->buf); \
+} \
+ \
+static inline void funcprefix##_rb_push(TypeName##RingBuffer *const rb, \
+ RBType item) \
+ REAL_FATTR_NONNULL_ARG(1); \
+static inline void funcprefix##_rb_push(TypeName##RingBuffer *const rb, \
+ RBType item) \
+{ \
+ if (rb->next == rb->first) { \
+ rbfree(rb->first); \
+ rb->first = _RINGBUF_NEXT(rb, rb->first); \
+ } else if (rb->first == NULL) { \
+ rb->first = rb->next; \
+ } \
+ *rb->next = item; \
+ rb->next = _RINGBUF_NEXT(rb, rb->next); \
+} \
+ \
+static inline ptrdiff_t funcprefix##_rb_find_idx( \
+ const TypeName##RingBuffer *const rb, const RBType *const item_p) \
+ REAL_FATTR_NONNULL_ALL REAL_FATTR_PURE REAL_FATTR_UNUSED; \
+static inline ptrdiff_t funcprefix##_rb_find_idx( \
+ const TypeName##RingBuffer *const rb, const RBType *const item_p) \
+{ \
+ assert(rb->buf <= item_p); \
+ assert(rb->buf_end >= item_p); \
+ if (rb->first == NULL) { \
+ return -1; \
+ } else if (item_p >= rb->first) { \
+ return item_p - rb->first; \
+ } else { \
+ return item_p - rb->buf + rb->buf_end - rb->first + 1; \
+ } \
+} \
+ \
+static inline size_t funcprefix##_rb_size( \
+ const TypeName##RingBuffer *const rb) \
+ REAL_FATTR_NONNULL_ALL REAL_FATTR_PURE; \
+static inline size_t funcprefix##_rb_size( \
+ const TypeName##RingBuffer *const rb) \
+{ \
+ return (size_t) (rb->buf_end - rb->buf) + 1; \
+} \
+ \
+static inline size_t funcprefix##_rb_length( \
+ const TypeName##RingBuffer *const rb) \
+ REAL_FATTR_NONNULL_ALL REAL_FATTR_PURE; \
+static inline size_t funcprefix##_rb_length( \
+ const TypeName##RingBuffer *const rb) \
+{ \
+ return _RINGBUF_LENGTH(rb); \
+} \
+ \
+static inline RBType *funcprefix##_rb_idx_p( \
+ const TypeName##RingBuffer *const rb, const size_t idx) \
+ REAL_FATTR_NONNULL_ALL REAL_FATTR_PURE; \
+static inline RBType *funcprefix##_rb_idx_p( \
+ const TypeName##RingBuffer *const rb, const size_t idx) \
+{ \
+ assert(idx <= funcprefix##_rb_size(rb)); \
+ assert(idx <= funcprefix##_rb_length(rb)); \
+ if (rb->first + idx > rb->buf_end) { \
+ return rb->buf + ((rb->first + idx) - (rb->buf_end + 1)); \
+ } else { \
+ return rb->first + idx; \
+ } \
+} \
+ \
+static inline RBType funcprefix##_rb_idx(const TypeName##RingBuffer *const rb, \
+ const size_t idx) \
+ REAL_FATTR_NONNULL_ALL REAL_FATTR_PURE REAL_FATTR_UNUSED; \
+static inline RBType funcprefix##_rb_idx(const TypeName##RingBuffer *const rb, \
+ const size_t idx) \
+{ \
+ return *funcprefix##_rb_idx_p(rb, idx); \
+} \
+ \
+static inline void funcprefix##_rb_insert(TypeName##RingBuffer *const rb, \
+ const size_t idx, \
+ RBType item) \
+ REAL_FATTR_NONNULL_ARG(1) REAL_FATTR_UNUSED; \
+static inline void funcprefix##_rb_insert(TypeName##RingBuffer *const rb, \
+ const size_t idx, \
+ RBType item) \
+{ \
+ assert(idx <= funcprefix##_rb_size(rb)); \
+ assert(idx <= funcprefix##_rb_length(rb)); \
+ const size_t length = funcprefix##_rb_length(rb); \
+ if (idx == length) { \
+ funcprefix##_rb_push(rb, item); \
+ return; \
+ } \
+ RBType *const insertpos = funcprefix##_rb_idx_p(rb, idx); \
+ if (insertpos == rb->next) { \
+ funcprefix##_rb_push(rb, item); \
+ return; \
+ } \
+ if (length == funcprefix##_rb_size(rb)) { \
+ rbfree(rb->first); \
+ } \
+ if (insertpos < rb->next) { \
+ memmove(insertpos + 1, insertpos, \
+ (size_t) ((uintptr_t) rb->next - (uintptr_t) insertpos)); \
+ } else { \
+ assert(insertpos > rb->first); \
+ assert(rb->next <= rb->first); \
+ memmove(rb->buf + 1, rb->buf, \
+ (size_t) ((uintptr_t) rb->next - (uintptr_t) rb->buf)); \
+ *rb->buf = *rb->buf_end; \
+ memmove(insertpos + 1, insertpos, \
+ (size_t) ((uintptr_t) (rb->buf_end + 1) - (uintptr_t) insertpos)); \
+ } \
+ *insertpos = item; \
+ if (length == funcprefix##_rb_size(rb)) { \
+ rb->first = _RINGBUF_NEXT(rb, rb->first); \
+ } \
+ rb->next = _RINGBUF_NEXT(rb, rb->next); \
+} \
+ \
+static inline void funcprefix##_rb_remove(TypeName##RingBuffer *const rb, \
+ const size_t idx) \
+ REAL_FATTR_NONNULL_ARG(1) REAL_FATTR_UNUSED; \
+static inline void funcprefix##_rb_remove(TypeName##RingBuffer *const rb, \
+ const size_t idx) \
+{ \
+ assert(idx < funcprefix##_rb_size(rb)); \
+ assert(idx < funcprefix##_rb_length(rb)); \
+ RBType *const rmpos = funcprefix##_rb_idx_p(rb, idx); \
+ rbfree(rmpos); \
+ if (rmpos == rb->next - 1) { \
+ rb->next--; \
+ if (rb->first == rb->next) { \
+ rb->first = NULL; \
+ rb->next = rb->buf; \
+ } \
+ } else if (rmpos == rb->first) { \
+ rb->first = _RINGBUF_NEXT(rb, rb->first); \
+ if (rb->first == rb->next) { \
+ rb->first = NULL; \
+ rb->next = rb->buf; \
+ } \
+ } else if (rb->first < rb->next || rb->next == rb->buf) { \
+ assert(rmpos > rb->first); \
+ assert(rmpos <= _RINGBUF_PREV(rb, rb->next)); \
+ memmove(rb->first + 1, rb->first, \
+ (size_t) ((uintptr_t) rmpos - (uintptr_t) rb->first)); \
+ rb->first = _RINGBUF_NEXT(rb, rb->first); \
+ } else if (rmpos < rb->next) { \
+ memmove(rmpos, rmpos + 1, \
+ (size_t) ((uintptr_t) rb->next - (uintptr_t) rmpos)); \
+ rb->next = _RINGBUF_PREV(rb, rb->next); \
+ } else { \
+ assert(rb->first < rb->buf_end); \
+ memmove(rb->first + 1, rb->first, \
+ (size_t) ((uintptr_t) rmpos - (uintptr_t) rb->first)); \
+ rb->first = _RINGBUF_NEXT(rb, rb->first); \
+ } \
+}
+
+#endif // NVIM_LIB_RINGBUF_H
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 27f8340ec7..d865260295 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, 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");
+ if (*p_shada != NUL) {
+ shada_read_everything(NULL, false, true);
+ 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_shada && *p_shada != NUL) {
+ // Write out the registers, history, marks etc, to the ShaDa 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")); // NOLINT(whitespace/line_length)
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 ce44149ffa..dd49b311d3 100644
--- a/src/nvim/mark.c
+++ b/src/nvim/mark.c
@@ -39,6 +39,7 @@
#include "nvim/strings.h"
#include "nvim/ui.h"
#include "nvim/os/os.h"
+#include "nvim/os/time.h"
#include "nvim/os/input.h"
/*
@@ -48,13 +49,13 @@
/*
* 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];
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "mark.c.generated.h"
@@ -68,6 +69,27 @@ int setmark(int c)
return setmark_pos(c, &curwin->w_cursor, curbuf->b_fnum);
}
+/// Free fmark_T item
+void free_fmark(fmark_T fm)
+{
+ dict_unref(fm.additional_data);
+}
+
+/// Free xfmark_T item
+void free_xfmark(xfmark_T fm)
+{
+ xfree(fm.fname);
+ free_fmark(fm.fmark);
+}
+
+/// Free and clear fmark_T item
+void clear_fmark(fmark_T *fm)
+ FUNC_ATTR_NONNULL_ALL
+{
+ free_fmark(*fm);
+ memset(fm, 0, sizeof(*fm));
+}
+
/*
* Set named mark "c" to position "pos".
* When "c" is upper case use file "fnum".
@@ -92,7 +114,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 +145,13 @@ 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;
+ RESET_XFMARK(namedfm + i, *pos, fnum, NULL);
return OK;
}
return FAIL;
@@ -144,7 +163,6 @@ int setmark_pos(int c, pos_T *pos, int fnum)
*/
void setpcmark(void)
{
- int i;
xfmark_T *fm;
/* for :global the mark is set only once */
@@ -157,16 +175,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);
- for (i = 1; i < JUMPLISTSIZE; ++i)
- curwin->w_jumplist[i - 1] = curwin->w_jumplist[i];
+ free_xfmark(curwin->w_jumplist[0]);
+ memmove(&curwin->w_jumplist[0], &curwin->w_jumplist[1],
+ (JUMPLISTSIZE - 1) * sizeof(curwin->w_jumplist[0]));
}
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);
}
/*
@@ -260,7 +276,7 @@ pos_T *movechangelist(int count)
} else
n += count;
curwin->w_changelistidx = n;
- return curbuf->b_changelist + n;
+ return &(curbuf->b_changelist[n].mark);
}
/*
@@ -296,22 +312,21 @@ pos_T *getmark_buf_fnum(buf_T *buf, int c, int changefile, int *fnum)
* to crash. */
if (c < 0)
return posp;
- if (c > '~') /* check for islower()/isupper() */
- ;
- else if (c == '\'' || c == '`') { /* previous context mark */
- pos_copy = curwin->w_pcmark; /* need to make a copy because */
- posp = &pos_copy; /* w_pcmark may be changed soon */
- } else if (c == '"') /* to pos when leaving buffer */
- posp = &(buf->b_last_cursor);
- else if (c == '^') /* to where Insert mode stopped */
- posp = &(buf->b_last_insert);
- else if (c == '.') /* to where last change was made */
- posp = &(buf->b_last_change);
- else if (c == '[') /* to start of previous operator */
+ if (c > '~') { // check for islower()/isupper()
+ } else if (c == '\'' || c == '`') { // previous context mark
+ pos_copy = curwin->w_pcmark; // need to make a copy because
+ posp = &pos_copy; // w_pcmark may be changed soon
+ } else if (c == '"') { // to pos when leaving buffer
+ posp = &(buf->b_last_cursor.mark);
+ } else if (c == '^') { // to where Insert mode stopped
+ posp = &(buf->b_last_insert.mark);
+ } else if (c == '.') { // to where last change was made
+ posp = &(buf->b_last_change.mark);
+ } else if (c == '[') { // to start of previous operator
posp = &(buf->b_op_start);
- else if (c == ']') /* to end of previous operator */
+ } else if (c == ']') { // to end of previous operator
posp = &(buf->b_op_end);
- else if (c == '{' || c == '}') { /* to previous/next paragraph */
+ } else if (c == '{' || c == '}') { // to previous/next paragraph
pos_T pos;
oparg_T oa;
int slcb = listcmd_busy;
@@ -357,7 +372,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 +380,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 +436,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 +454,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 +491,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 +509,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 +553,12 @@ int check_mark(pos_T *pos)
*/
void clrallmarks(buf_T *buf)
{
- static int i = -1;
-
- if (i == -1) /* first call ever: initialize */
- for (i = 0; i < NMARKS + 1; i++) {
- namedfm[i].fmark.mark.lnum = 0;
- namedfm[i].fname = NULL;
- }
-
- 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;
}
@@ -610,10 +611,10 @@ void do_marks(exarg_T *eap)
if (arg != NULL && *arg == NUL)
arg = NULL;
- show_one_mark('\'', arg, &curwin->w_pcmark, NULL, TRUE);
+ 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,14 +627,14 @@ void do_marks(exarg_T *eap)
xfree(name);
}
}
- show_one_mark('"', arg, &curbuf->b_last_cursor, 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_visual.vi_start, NULL, TRUE);
- show_one_mark('>', arg, &curbuf->b_visual.vi_end, NULL, TRUE);
- show_one_mark(-1, arg, NULL, NULL, FALSE);
+ 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.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);
}
static void
@@ -727,13 +728,14 @@ void ex_delmarks(exarg_T *eap)
from = to = *p;
for (i = from; i <= to; ++i) {
- if (lower)
- curbuf->b_namedm[i - 'a'].lnum = 0;
- else {
- if (digit)
+ if (lower) {
+ curbuf->b_namedm[i - 'a'].mark.lnum = 0;
+ } else {
+ if (digit) {
n = i - '0' + NMARKS;
- else
+ } else {
n = i - 'A';
+ }
namedfm[n].fmark.mark.lnum = 0;
xfree(namedfm[n].fname);
namedfm[n].fname = NULL;
@@ -741,9 +743,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;
@@ -811,7 +813,7 @@ void ex_changes(exarg_T *eap)
MSG_PUTS_TITLE(_("\nchange line col text"));
for (i = 0; i < curbuf->b_changelistlen && !got_int; ++i) {
- if (curbuf->b_changelist[i].lnum != 0) {
+ if (curbuf->b_changelist[i].mark.lnum != 0) {
msg_putchar('\n');
if (got_int)
break;
@@ -819,10 +821,10 @@ void ex_changes(exarg_T *eap)
i == curwin->w_changelistidx ? '>' : ' ',
i > curwin->w_changelistidx ? i - curwin->w_changelistidx
: curwin->w_changelistidx - i,
- (long)curbuf->b_changelist[i].lnum,
- curbuf->b_changelist[i].col);
+ (long)curbuf->b_changelist[i].mark.lnum,
+ curbuf->b_changelist[i].mark.col);
msg_outtrans(IObuff);
- name = mark_line(&curbuf->b_changelist[i], 17);
+ name = mark_line(&curbuf->b_changelist[i].mark, 17);
msg_outtrans_attr(name, hl_attr(HLF_D));
xfree(name);
os_breakcheck();
@@ -886,29 +888,29 @@ 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 */
for (i = 0; i < curbuf->b_changelistlen; ++i)
- one_adjust_nodel(&(curbuf->b_changelist[i].lnum));
+ one_adjust_nodel(&(curbuf->b_changelist[i].mark.lnum));
/* Visual area */
one_adjust_nodel(&(curbuf->b_visual.vi_start.lnum));
@@ -1038,24 +1040,24 @@ 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)
- col_adjust(&(curbuf->b_changelist[i]));
+ col_adjust(&(curbuf->b_changelist[i].mark));
/* Visual area */
col_adjust(&(curbuf->b_visual.vi_start));
@@ -1101,7 +1103,7 @@ void mark_col_adjust(linenr_T lnum, colnr_T mincol, long lnum_amount, long col_a
* When deleting lines, this may create duplicate marks in the
* jumplist. They will be removed here for the current window.
*/
-static void cleanup_jumplist(void)
+void cleanup_jumplist(void)
{
int i;
int from, to;
@@ -1117,10 +1119,17 @@ static void cleanup_jumplist(void)
&& curwin->w_jumplist[i].fmark.mark.lnum
== curwin->w_jumplist[from].fmark.mark.lnum)
break;
- if (i >= curwin->w_jumplistlen) /* no duplicate */
- curwin->w_jumplist[to++] = curwin->w_jumplist[from];
- else
+ if (i >= curwin->w_jumplistlen) { // no duplicate
+ if (to != from) {
+ // Not using curwin->w_jumplist[to++] = curwin->w_jumplist[from] because
+ // this way valgrind complains about overlapping source and destination
+ // in memcpy() call. (clang-3.6.0, debug build with -DEXITFREE).
+ curwin->w_jumplist[to] = curwin->w_jumplist[from];
+ }
+ to++;
+ } else {
xfree(curwin->w_jumplist[from].fname);
+ }
}
if (curwin->w_jumplistidx == curwin->w_jumplistlen)
curwin->w_jumplistidx = to;
@@ -1143,382 +1152,253 @@ 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_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[0])
+ : (const xfmark_T *const) iter);
+ *fm = *iter_mark;
+ if (iter_mark == &(win->w_jumplist[win->w_jumplistlen - 1])) {
+ 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_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);
+ *name = NUL;
+ const xfmark_T *iter_mark = (iter == NULL
+ ? &(namedfm[0])
+ : (const xfmark_T *const) iter);
+ while ((size_t) (iter_mark - &(namedfm[0])) < ARRAY_SIZE(namedfm)
+ && !iter_mark->fmark.mark.lnum) {
+ iter_mark++;
+ }
+ if ((size_t) (iter_mark - &(namedfm[0])) == ARRAY_SIZE(namedfm)
+ || !iter_mark->fmark.mark.lnum) {
+ return NULL;
+ }
+ 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 FUNC_ATTR_WARN_UNUSED_RESULT
{
- 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);
}
- 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 = '^';
+ return &(buf->b_last_insert);
+ }
+ case '^': {
+ *mark_name = '.';
+ return &(buf->b_last_change);
+ }
+ case '.': {
+ *mark_name = 'a';
+ return &(buf->b_namedm[0]);
+ }
+ case 'z': {
+ return NULL;
+ }
+ default: {
+ (*mark_name)++;
+ return &(buf->b_namedm[*mark_name - 'a']);
}
}
- 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_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;
+ *name = NUL;
+ 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) {
+ 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)
+/// 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.
+///
+/// @return true on success, false on failure.
+bool mark_set_global(const char name, const xfmark_T fm, const bool update)
{
- 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);
+ const int idx = mark_global_index(name);
+ if (idx == -1) {
+ return false;
}
-
- if (fm->fmark.fnum != 0)
- xfree(name);
+ xfmark_T *const fm_tgt = &(namedfm[idx]);
+ if (update && fm.fmark.timestamp <= fm_tgt->fmark.timestamp) {
+ return false;
+ }
+ if (fm_tgt->fmark.mark.lnum != 0) {
+ free_xfmark(*fm_tgt);
+ }
+ *fm_tgt = fm;
+ return true;
}
-/*
- * Return TRUE if "name" is on removable media (depending on 'viminfo').
- */
-int removable(char_u *name)
+/// 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.
+///
+/// @return true on success, false on failure.
+bool mark_set_local(const char name, buf_T *const buf,
+ const fmark_T fm, const bool update)
+ 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;
- }
- }
+ 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 false;
+ }
+ if (update && fm.timestamp <= fm_tgt->timestamp) {
+ return false;
}
- xfree(name);
- return retval;
+ if (fm_tgt->mark.lnum != 0) {
+ free_fmark(*fm_tgt);
+ }
+ *fm_tgt = fm;
+ return true;
}
-
/*
- * Write all the named marks for all buffers.
- * Return the number of buffers for which marks have been written.
+ * Free items in the jumplist of window "wp".
*/
-int write_viminfo_marks(FILE *fp_out)
+void free_jumplist(win_T *wp)
{
- /*
- * Set b_last_cursor for the all buffers that have a window.
- */
- FOR_ALL_TAB_WINDOWS(tp, win) {
- set_last_cursor(win);
- }
+ int i;
- 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++;
- }
- }
+ for (i = 0; i < wp->w_jumplistlen; ++i) {
+ free_xfmark(wp->w_jumplist[i]);
}
-
- return count;
+ wp->w_jumplistlen = 0;
}
-static void write_one_mark(FILE *fp_out, int c, pos_T *pos)
+void set_last_cursor(win_T *win)
{
- if (pos->lnum != 0)
- fprintf(fp_out, "\t%c\t%" PRId64 "\t%d\n", c,
- (int64_t)pos->lnum, (int)pos->col);
+ if (win->w_buffer != NULL) {
+ RESET_FMARK(&win->w_buffer->b_last_cursor, win->w_cursor, 0);
+ }
}
-/*
- * 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)
+#if defined(EXITFREE)
+void free_all_marks(void)
{
- char_u *line = virp->vir_line;
- buf_T *buf;
- int num_marked_files;
- int load_marks;
- int copy_marks_out;
- char_u *str;
int i;
- char_u *p;
- char_u *name_buf;
- pos_T pos;
- list_T *list = NULL;
-
- name_buf = xmalloc(LSIZE);
- *name_buf = NUL;
-
- if (fp_out == NULL && (flags & (VIF_GET_OLDFILES | VIF_FORCEIT))) {
- list = list_alloc();
- set_vim_var_list(VV_OLDFILES, list);
- }
-
- num_marked_files = get_viminfo_parameter('\'');
- while (!eof && (count < num_marked_files || fp_out == NULL)) {
- if (line[0] != '>') {
- if (line[0] != '\n' && line[0] != '\r' && line[0] != '#') {
- if (viminfo_error("E576: ", _("Missing '>'"), line))
- break; /* too many errors, return now */
- }
- eof = vim_fgets(line, LSIZE, virp->vir_fd);
- continue; /* Skip this dud line */
- }
-
- /*
- * Handle long line and translate escaped characters.
- * Find file name, set str to start.
- * Ignore leading and trailing white space.
- */
- str = skipwhite(line + 1);
- str = viminfo_readstring(virp, (int)(str - virp->vir_line), FALSE);
- p = str + STRLEN(str);
- while (p != str && (*p == NUL || ascii_isspace(*p)))
- p--;
- if (*p)
- p++;
- *p = NUL;
-
- if (list != NULL)
- list_append_string(list, str, -1);
-
- /*
- * If fp_out == NULL, load marks for current buffer.
- * If fp_out != NULL, copy marks for buffers not in buflist.
- */
- load_marks = copy_marks_out = FALSE;
- if (fp_out == NULL) {
- if ((flags & VIF_WANT_MARKS) && curbuf->b_ffname != NULL) {
- if (*name_buf == NUL) /* only need to do this once */
- home_replace(NULL, curbuf->b_ffname, name_buf, LSIZE, TRUE);
- if (fnamecmp(str, name_buf) == 0)
- load_marks = TRUE;
- }
- } else { /* fp_out != NULL */
- /* This is slow if there are many buffers!! */
- buf = NULL;
- FOR_ALL_BUFFERS(bp) {
- if (bp->b_ffname != NULL) {
- home_replace(NULL, bp->b_ffname, name_buf, LSIZE, TRUE);
- if (fnamecmp(str, name_buf) == 0) {
- buf = bp;
- break;
- }
- }
- }
- /*
- * copy marks if the buffer has not been loaded
- */
- if (buf == NULL || !buf->b_marks_read) {
- copy_marks_out = TRUE;
- fputs("\n> ", fp_out);
- viminfo_writestring(fp_out, str);
- count++;
- }
- }
- xfree(str);
-
- pos.coladd = 0;
- while (!(eof = viminfo_readline(virp)) && line[0] == TAB) {
- if (load_marks) {
- if (line[1] != NUL) {
- int64_t lnum_64;
- unsigned int u;
- sscanf((char *)line + 2, "%" SCNd64 "%u", &lnum_64, &u);
- // safely downcast to linenr_T (long); remove when linenr_T refactored
- assert(lnum_64 <= LONG_MAX);
- pos.lnum = (linenr_T)lnum_64;
- assert(u <= INT_MAX);
- pos.col = (colnr_T)u;
- switch (line[1]) {
- case '"': curbuf->b_last_cursor = pos; break;
- case '^': curbuf->b_last_insert = pos; break;
- case '.': curbuf->b_last_change = pos; break;
- case '+':
- /* changelist positions are stored oldest
- * first */
- if (curbuf->b_changelistlen == JUMPLISTSIZE)
- /* list is full, remove oldest entry */
- memmove(curbuf->b_changelist,
- curbuf->b_changelist + 1,
- sizeof(pos_T) * (JUMPLISTSIZE - 1));
- else
- ++curbuf->b_changelistlen;
- curbuf->b_changelist[
- curbuf->b_changelistlen - 1] = pos;
- break;
- default: if ((i = line[1] - 'a') >= 0 && i < NMARKS)
- curbuf->b_namedm[i] = pos;
- }
- }
- } else if (copy_marks_out)
- fputs((char *)line, fp_out);
- }
- if (load_marks) {
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_buffer == curbuf)
- wp->w_changelistidx = curbuf->b_changelistlen;
- }
- break;
+ for (i = 0; i < NGLOBALMARKS; i++) {
+ if (namedfm[i].fmark.mark.lnum != 0) {
+ free_xfmark(namedfm[i]);
}
}
- xfree(name_buf);
+ memset(&namedfm[0], 0, sizeof(namedfm));
}
+#endif
diff --git a/src/nvim/mark.h b/src/nvim/mark.h
index aa89a5b625..aff6e7273a 100644
--- a/src/nvim/mark.h
+++ b/src/nvim/mark.h
@@ -1,9 +1,78 @@
#ifndef NVIM_MARK_H
#define NVIM_MARK_H
+#include "nvim/macros.h"
+#include "nvim/ascii.h"
#include "nvim/buffer_defs.h"
#include "nvim/mark_defs.h"
+#include "nvim/memory.h"
#include "nvim/pos.h"
+#include "nvim/os/time.h"
+
+/// Set fmark using given value
+#define SET_FMARK(fmarkp_, mark_, fnum_) \
+ do { \
+ fmark_T *const fmarkp__ = fmarkp_; \
+ fmarkp__->mark = mark_; \
+ fmarkp__->fnum = fnum_; \
+ fmarkp__->timestamp = os_time(); \
+ fmarkp__->additional_data = NULL; \
+ } while (0)
+
+/// Free and set fmark using given value
+#define RESET_FMARK(fmarkp_, mark_, fnum_) \
+ do { \
+ fmark_T *const fmarkp___ = fmarkp_; \
+ free_fmark(*fmarkp___); \
+ SET_FMARK(fmarkp___, mark_, fnum_); \
+ } while (0)
+
+/// Clear given fmark
+#define CLEAR_FMARK(fmarkp_) \
+ RESET_FMARK(fmarkp_, ((pos_T) {0, 0, 0}), 0)
+
+/// Set given extended mark (regular mark + file name)
+#define SET_XFMARK(xfmarkp_, mark_, fnum_, fname_) \
+ do { \
+ xfmark_T *const xfmarkp__ = xfmarkp_; \
+ xfmarkp__->fname = fname_; \
+ SET_FMARK(&(xfmarkp__->fmark), mark_, fnum_); \
+ } while (0)
+
+/// Free and set given extended mark (regular mark + file name)
+#define RESET_XFMARK(xfmarkp_, mark_, fnum_, fname_) \
+ do { \
+ xfmark_T *const xfmarkp__ = xfmarkp_; \
+ free_xfmark(*xfmarkp__); \
+ xfmarkp__->fname = fname_; \
+ SET_FMARK(&(xfmarkp__->fmark), mark_, fnum_); \
+ } while (0)
+
+/// Convert mark name to the offset
+static inline int mark_global_index(const char name)
+ FUNC_ATTR_CONST
+{
+ return (ASCII_ISUPPER(name)
+ ? (name - 'A')
+ : (ascii_isdigit(name)
+ ? (NMARKS + (name - '0'))
+ : -1));
+}
+
+/// Convert local mark name to the offset
+static inline int mark_local_index(const char name)
+ FUNC_ATTR_CONST
+{
+ return (ASCII_ISLOWER(name)
+ ? (name - 'a')
+ : (name == '"'
+ ? NMARKS
+ : (name == '^'
+ ? NMARKS + 1
+ : (name == '.'
+ ? NMARKS + 2
+ : -1))));
+}
#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..720b2475ed 100644
--- a/src/nvim/mark_defs.h
+++ b/src/nvim/mark_defs.h
@@ -2,25 +2,47 @@
#define NVIM_MARK_DEFS_H
#include "nvim/pos.h"
+#include "nvim/os/time.h"
+#include "nvim/eval_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)
+
+/// Total possible number of local marks
+///
+/// That are uppercase marks plus '"', '^' and '.'. There are other local marks,
+/// but they are not saved in ShaDa files.
+#define NLOCALMARKS (NMARKS + 3)
+
+/// 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.
+ dict_T *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/memory.c b/src/nvim/memory.c
index 132c895997..d25dc7c941 100644
--- a/src/nvim/memory.c
+++ b/src/nvim/memory.c
@@ -30,7 +30,7 @@
/// Try to free memory. Used when trying to recover from out of memory errors.
/// @see {xmalloc}
-static void try_to_free_memory(void)
+void try_to_free_memory(void)
{
static bool trying_to_free = false;
// avoid recursive calls
diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c
index 8407198b13..1f1b5c2aa9 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. */
@@ -2054,7 +2053,7 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra
/* Don't create a new entry when the line number is the same
* as the last one and the column is not too far away. Avoids
* creating many entries for typing "xxxxx". */
- p = &curbuf->b_changelist[curbuf->b_changelistlen - 1];
+ p = &curbuf->b_changelist[curbuf->b_changelistlen - 1].mark;
if (p->lnum != lnum)
add = TRUE;
else {
@@ -2074,7 +2073,7 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra
/* changelist is full: remove oldest entry */
curbuf->b_changelistlen = JUMPLISTSIZE - 1;
memmove(curbuf->b_changelist, curbuf->b_changelist + 1,
- sizeof(pos_T) * (JUMPLISTSIZE - 1));
+ sizeof(curbuf->b_changelist[0]) * (JUMPLISTSIZE - 1));
FOR_ALL_TAB_WINDOWS(tp, wp) {
/* Correct position in changelist for other windows on
* this buffer. */
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..3fd2c0b773 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -50,26 +50,11 @@
#include "nvim/undo.h"
#include "nvim/window.h"
#include "nvim/os/input.h"
-
-/*
- * Registers:
- * 0 = register for latest (unnamed) yank
- * 1..9 = registers '1' to '9', for deletes
- * 10..35 = registers 'a' to 'z'
- * 36 = delete register '-'
- * 37 = selection register '*'
- * 38 = clipboard register '+'
- */
-#define DELETION_REGISTER 36
-#define NUM_SAVED_REGISTERS 37
-// The following registers should not be saved in viminfo:
-#define STAR_REGISTER 37
-#define PLUS_REGISTER 38
-#define NUM_REGISTERS 39
+#include "nvim/os/time.h"
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;
@@ -778,19 +763,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 = op_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 +867,16 @@ int do_record(int c)
return retval;
}
+static void set_yreg_additional_data(yankreg_T *reg, dict_T *additional_data)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ if (reg->additional_data == additional_data) {
+ return;
+ }
+ dict_unref(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 +906,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 +2255,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 +2277,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 +2356,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 *));
+ reg->additional_data = NULL;
+ reg->timestamp = os_time();
y_idx = 0;
lnum = oap->start.lnum;
@@ -4433,171 +4422,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 +4563,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 +4796,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 +5188,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 ShaDa file.
int i = 0;
for (listitem_T *li = lines->lv_first; li != NULL; li = li->li_next) {
@@ -5411,6 +5240,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 +5309,91 @@ 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_PURE
+{
+ 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_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ *name = NUL;
+ const yankreg_T *iter_reg = (iter == NULL
+ ? &(y_regs[0])
+ : (const yankreg_T *const) iter);
+ while (iter_reg - &(y_regs[0]) < NUM_SAVED_REGISTERS && reg_empty(iter_reg)) {
+ iter_reg++;
+ }
+ if (iter_reg - &(y_regs[0]) == NUM_SAVED_REGISTERS || reg_empty(iter_reg)) {
+ 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_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
+///
+/// @param[in] name Register name.
+/// @param[in] reg Register value.
+///
+/// @return true on success, false on failure.
+bool op_register_set(const char name, const yankreg_T reg)
+{
+ int i = op_reg_index(name);
+ if (i == -1) {
+ return false;
+ }
+ free_register(&y_regs[i]);
+ y_regs[i] = reg;
+ return true;
+}
+
+/// Get register with the given name
+///
+/// @param[in] name Register name.
+///
+/// @return Pointer to the register contents or NULL.
+const yankreg_T *op_register_get(const char name)
+{
+ int i = op_reg_index(name);
+ if (i == -1) {
+ return NULL;
+ }
+ return &y_regs[i];
+}
diff --git a/src/nvim/ops.h b/src/nvim/ops.h
index 99683165f9..507f933acf 100644
--- a/src/nvim/ops.h
+++ b/src/nvim/ops.h
@@ -3,7 +3,11 @@
#include <stdbool.h>
+#include "nvim/macros.h"
+#include "nvim/ascii.h"
#include "nvim/types.h"
+#include "nvim/eval_defs.h"
+#include "nvim/os/time.h"
typedef int (*Indenter)(void);
@@ -16,6 +20,22 @@ typedef int (*Indenter)(void);
#define PUT_LINE_FORWARD 32 /* put linewise register below Visual sel. */
/*
+ * Registers:
+ * 0 = register for latest (unnamed) yank
+ * 1..9 = registers '1' to '9', for deletes
+ * 10..35 = registers 'a' to 'z'
+ * 36 = delete register '-'
+ * 37 = selection register '*'
+ * 38 = clipboard register '+'
+ */
+#define DELETION_REGISTER 36
+#define NUM_SAVED_REGISTERS 37
+// The following registers should not be saved in ShaDa file:
+#define STAR_REGISTER 37
+#define PLUS_REGISTER 38
+#define NUM_REGISTERS 39
+
+/*
* Operator IDs; The order must correspond to opchars[] in ops.c!
*/
#define OP_NOP 0 /* no pending operation */
@@ -47,14 +67,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 +74,41 @@ 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.
+ dict_T *additional_data; ///< Additional data from ShaDa file.
+} yankreg_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 op_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;
+ }
+}
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ops.h.generated.h"
#endif
diff --git a/src/nvim/option.c b/src/nvim/option.c
index c47616620c..cbb22a0546 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -1738,16 +1738,16 @@ set_options_bin (
/*
* Find the parameter represented by the given character (eg ', :, ", or /),
- * and return its associated value in the 'viminfo' string.
+ * and return its associated value in the 'shada' string.
* Only works for number parameters, not for 'r' or 'n'.
* If the parameter is not specified in the string or there is no following
* number, return -1.
*/
-int get_viminfo_parameter(int type)
+int get_shada_parameter(int type)
{
char_u *p;
- p = find_viminfo_parameter(type);
+ p = find_shada_parameter(type);
if (p != NULL && ascii_isdigit(*p))
return atoi((char *)p);
return -1;
@@ -1755,14 +1755,14 @@ int get_viminfo_parameter(int type)
/*
* Find the parameter represented by the given character (eg ''', ':', '"', or
- * '/') in the 'viminfo' option and return a pointer to the string after it.
+ * '/') in the 'shada' option and return a pointer to the string after it.
* Return NULL if the parameter is not specified in the string.
*/
-char_u *find_viminfo_parameter(int type)
+char_u *find_shada_parameter(int type)
{
char_u *p;
- for (p = p_viminfo; *p; ++p) {
+ for (p = p_shada; *p; ++p) {
if (*p == type)
return p + 1;
if (*p == 'n') /* 'n' is always the last one */
@@ -1968,6 +1968,8 @@ static void redraw_titles(void) {
redraw_tabline = TRUE;
}
+static int shada_idx = -1;
+
/*
* Set a string option to a new value (without checking the effect).
* The string is copied into allocated memory.
@@ -2001,6 +2003,8 @@ set_string_option_direct (
if (options[idx].var == NULL) /* can't set hidden option */
return;
+ assert((void *) options[idx].var != (void *) &p_shada);
+
s = vim_strsave(val);
{
varp = (char_u **)get_varp_scope(&(options[idx]),
@@ -2441,10 +2445,16 @@ did_set_string_option (
verbose_stop();
if (*p_vfile != NUL && verbose_open() == FAIL)
errmsg = e_invarg;
- }
- /* 'viminfo' */
- else if (varp == &p_viminfo) {
- for (s = p_viminfo; *s; ) {
+ /* 'shada' */
+ } else if (varp == &p_shada) {
+ // TODO(ZyX-I): Remove this code in the future, alongside with &viminfo
+ // option.
+ opt_idx = ((options[opt_idx].fullname[0] == 'v')
+ ? (shada_idx == -1
+ ? ((shada_idx = findoption((char_u *) "shada")))
+ : shada_idx)
+ : opt_idx);
+ for (s = p_shada; *s; ) {
/* Check it's a valid character */
if (vim_strchr((char_u *)"!\"%'/:<@cfhnrs", *s) == NULL) {
errmsg = illegal_char(errbuf, *s);
@@ -2486,7 +2496,7 @@ did_set_string_option (
break;
}
}
- if (*p_viminfo && errmsg == NULL && get_viminfo_parameter('\'') < 0)
+ if (*p_shada && errmsg == NULL && get_shada_parameter('\'') < 0)
errmsg = (char_u *)N_("E528: Must specify a ' value");
}
/* 'showbreak' */
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index 5340f4bdd5..ab3169bff6 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -558,7 +558,7 @@ EXTERN long p_ur; /* 'undoreload' */
EXTERN long p_uc; /* 'updatecount' */
EXTERN long p_ut; /* 'updatetime' */
EXTERN char_u *p_fcs; /* 'fillchar' */
-EXTERN char_u *p_viminfo; /* 'viminfo' */
+EXTERN char_u *p_shada; /* 'shada' */
EXTERN char_u *p_vdir; /* 'viewdir' */
EXTERN char_u *p_vop; /* 'viewoptions' */
EXTERN unsigned vop_flags; /* uses SSOP_ flags */
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index ca3882b8a0..5973e4b938 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -1992,6 +1992,14 @@ return {
}}
},
{
+ full_name='shada', abbreviation='sd',
+ type='string', list='comma', scope={'global'},
+ deny_duplicates=true,
+ secure=true,
+ varname='p_shada',
+ defaults={if_true={vi="", vim="!,'100,<50,s10,h"}}
+ },
+ {
full_name='shell', abbreviation='sh',
type='string', scope={'global'},
secure=true,
@@ -2584,7 +2592,7 @@ return {
type='string', list='comma', scope={'global'},
deny_duplicates=true,
secure=true,
- varname='p_viminfo',
+ varname='p_shada',
defaults={if_true={vi="", vim="!,'100,<50,s10,h"}}
},
{
diff --git a/src/nvim/os/fs_defs.h b/src/nvim/os/fs_defs.h
index ddd382a3cb..df1031b721 100644
--- a/src/nvim/os/fs_defs.h
+++ b/src/nvim/os/fs_defs.h
@@ -21,4 +21,9 @@ typedef struct {
uv_dirent_t ent; ///< @private The entry information.
} Directory;
+/// Function to convert -errno error to char * error description
+///
+/// -errno errors are returned by a number of os functions.
+#define os_strerror uv_strerror
+
#endif // NVIM_OS_FS_DEFS_H
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..ad4886446a 100644
--- a/src/nvim/os/time.h
+++ b/src/nvim/os/time.h
@@ -5,6 +5,8 @@
#include <stdbool.h>
#include <time.h>
+typedef uint64_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..8ba888841c 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -79,23 +79,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 +86,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 +241,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 +278,29 @@ 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);
+ dict_unref(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]);
+
+ memset(spats, 0, sizeof(spats));
if (mr_pattern_alloced) {
xfree(mr_pattern);
@@ -414,17 +409,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 +432,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 +1050,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 +1163,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 +4603,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..6947f79d49 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.
+ dict_T *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..523f8db6f0
--- /dev/null
+++ b/src/nvim/shada.c
@@ -0,0 +1,4040 @@
+#ifdef HAVE_BE64TOH
+# define _BSD_SOURCE 1
+# define _DEFAULT_SOURCE 1
+# include <endian.h>
+#endif
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+
+#include <msgpack.h>
+#include <uv.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/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/buffer.h"
+#include "nvim/buffer_defs.h"
+#include "nvim/misc2.h"
+#include "nvim/ex_getln.h"
+#include "nvim/search.h"
+#include "nvim/eval.h"
+#include "nvim/regexp.h"
+#include "nvim/eval_defs.h"
+#include "nvim/version.h"
+#include "nvim/path.h"
+#include "nvim/fileio.h"
+#include "nvim/strings.h"
+#include "nvim/lib/khash.h"
+#include "nvim/lib/kvec.h"
+
+// Note: when using bufset hash pointers are intentionally casted to uintptr_t
+// and not to khint32_t or khint64_t: this way compiler must give a warning
+// (-Wconversion) when types change.
+#ifdef ARCH_32
+KHASH_SET_INIT_INT(bufset)
+#elif defined(ARCH_64)
+KHASH_SET_INIT_INT64(bufset)
+#else
+# error Not a 64- or 32-bit architecture
+#endif
+KHASH_MAP_INIT_STR(fnamebufs, buf_T *)
+KHASH_SET_INIT_STR(strset)
+
+#define copy_option_part(src, dest, ...) \
+ ((char *) copy_option_part((char_u **) src, (char_u *) dest, __VA_ARGS__))
+#define find_shada_parameter(...) \
+ ((const char *) find_shada_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 emsgu(a, ...) emsgu((char_u *) a, __VA_ARGS__)
+#define home_replace_save(a, b) \
+ ((char *)home_replace_save(a, (char_u *)b))
+#define home_replace(a, b, c, d, e) \
+ home_replace(a, (char_u *)b, (char_u *)c, d, e)
+#define vim_rename(a, b) \
+ (vim_rename((char_u *)a, (char_u *)b))
+#define mb_strnicmp(a, b, c) \
+ (mb_strnicmp((char_u *)a, (char_u *)b, c))
+#define has_non_ascii(a) (has_non_ascii((char_u *)a))
+#define string_convert(a, b, c) \
+ ((char *)string_convert((vimconv_T *)a, (char_u *)b, c))
+#define path_shorten_fname_if_possible(b) \
+ ((char *)path_shorten_fname_if_possible((char_u *)b))
+#define buflist_new(ffname, sfname, ...) \
+ (buflist_new((char_u *)ffname, (char_u *)sfname, __VA_ARGS__))
+#define convert_setup(vcp, from, to) \
+ (convert_setup(vcp, (char_u *)from, (char_u *)to))
+#define os_getperm(f) \
+ (os_getperm((char_u *) f))
+#define os_isdir(f) (os_isdir((char_u *) f))
+#define regtilde(s, m) ((char *) regtilde((char_u *) s, m))
+#define path_tail_with_sep(f) ((char *) path_tail_with_sep((char_u *)f))
+
+#define SEARCH_KEY_MAGIC "sm"
+#define SEARCH_KEY_SMARTCASE "sc"
+#define SEARCH_KEY_HAS_LINE_OFFSET "sl"
+#define SEARCH_KEY_PLACE_CURSOR_AT_END "se"
+#define SEARCH_KEY_IS_LAST_USED "su"
+#define SEARCH_KEY_IS_SUBSTITUTE_PATTERN "ss"
+#define SEARCH_KEY_HIGHLIGHTED "sh"
+#define SEARCH_KEY_OFFSET "so"
+#define SEARCH_KEY_PAT "sp"
+
+#define REG_KEY_TYPE "rt"
+#define REG_KEY_WIDTH "rw"
+#define REG_KEY_CONTENTS "rc"
+
+#define KEY_LNUM "l"
+#define KEY_COL "c"
+#define KEY_FILE "f"
+#define KEY_NAME_CHAR "n"
+
+// Error messages formerly used by viminfo code:
+// E136: viminfo: Too many errors, skipping rest of file
+// E137: Viminfo file is not writable: %s
+// E138: Can't write viminfo file %s!
+// E195: Cannot open ShaDa file for reading
+// E574: Unknown register type %d
+// E575: Illegal starting char
+// E576: Missing '>'
+// E577: Illegal register name
+// E886: Can't rename viminfo file to %s!
+// Now only five of them are used:
+// E137: ShaDa file is not writeable (for pre-open checks)
+// E138: All %s.tmp.X files exist, cannot write ShaDa file!
+// RCERR (E576) for critical read errors.
+// RNERR (E136) for various errors when renaming.
+// RERR (E575) for various errors inside read ShaDa file.
+// SERR (E886) for various “system” errors (always contains output of
+// strerror)
+
+/// Common prefix for all errors inside ShaDa file
+///
+/// I.e. errors occurred while parsing, but not system errors occurred while
+/// reading.
+#define RERR "E575: "
+
+/// Common prefix for critical read errors
+///
+/// I.e. errors that make shada_read_next_item return kSDReadStatusNotShaDa.
+#define RCERR "E576: "
+
+/// Common prefix for all “system” errors
+#define SERR "E886: "
+
+/// Common prefix for all “rename” errors
+#define RNERR "E136: "
+
+/// Flags for shada_read_file and children
+typedef enum {
+ kShaDaWantInfo = 1, ///< Load non-mark information
+ kShaDaWantMarks = 2, ///< Load local file marks and change list
+ kShaDaForceit = 4, ///< Overwrite info already read
+ kShaDaGetOldfiles = 8, ///< Load v:oldfiles.
+ kShaDaMissingError = 16, ///< Error out when os_open returns -ENOENT.
+} ShaDaReadFileFlags;
+
+/// 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.
+ kSDItemChange = 11, ///< Item from buffer change list.
+#define SHADA_LAST_ENTRY ((uint64_t) kSDItemChange)
+} ShadaEntryType;
+
+/// Possible results when reading ShaDa file
+typedef enum {
+ kSDReadStatusSuccess, ///< Reading was successfull.
+ kSDReadStatusFinished, ///< Nothing more to read.
+ kSDReadStatusReadError, ///< Failed to read from file.
+ kSDReadStatusNotShaDa, ///< Input is most likely not a ShaDa file.
+ kSDReadStatusMalformed, ///< Error in the currently read item.
+} ShaDaReadResult;
+
+/// Possible results of shada_write function.
+typedef enum {
+ kSDWriteSuccessfull, ///< Writing was successfull.
+ kSDWriteReadNotShada, ///< Writing was successfull, but when reading it
+ ///< attempted to read file that did not look like
+ ///< a ShaDa file.
+ kSDWriteFailed, ///< Writing was not successfull (e.g. because there
+ ///< was no space left on device).
+} ShaDaWriteResult;
+
+/// Flags for shada_read_next_item
+enum SRNIFlags {
+ kSDReadHeader = (1 << kSDItemHeader), ///< Determines whether header should
+ ///< be read (it is usually ignored).
+ kSDReadUndisableableData = (
+ (1 << kSDItemSearchPattern)
+ | (1 << kSDItemSubString)
+ | (1 << kSDItemJump)), ///< Data reading which cannot be disabled by &shada
+ ///< or other options except for disabling reading
+ ///< ShaDa as a whole.
+ kSDReadRegisters = (1 << kSDItemRegister), ///< Determines whether registers
+ ///< should be read (may only be
+ ///< disabled when writing, but
+ ///< not when reading).
+ kSDReadHistory = (1 << kSDItemHistoryEntry), ///< Determines whether history
+ ///< should be read (can only be
+ ///< disabled by &history).
+ kSDReadVariables = (1 << kSDItemVariable), ///< Determines whether variables
+ ///< should be read (disabled by
+ ///< removing ! from &shada).
+ kSDReadBufferList = (1 << kSDItemBufferList), ///< Determines whether buffer
+ ///< list should be read
+ ///< (disabled by removing
+ ///< % entry from &shada).
+ kSDReadUnknown = (1 << (SHADA_LAST_ENTRY + 1)), ///< Determines whether
+ ///< unknown items should be
+ ///< read (usually disabled).
+ kSDReadGlobalMarks = (1 << kSDItemGlobalMark), ///< Determines whether global
+ ///< marks should be read. Can
+ ///< only be disabled by
+ ///< having f0 in &shada when
+ ///< writing.
+ kSDReadLocalMarks = (1 << kSDItemLocalMark), ///< Determines whether local
+ ///< marks should be read. Can
+ ///< only be disabled by
+ ///< disabling &shada or putting
+ ///< '0 there. Is also used for
+ ///< v:oldfiles.
+ kSDReadChanges = (1 << kSDItemChange), ///< Determines whether change list
+ ///< should be read. Can only be
+ ///< disabled by disabling &shada or
+ ///< putting '0 there.
+};
+// Note: SRNIFlags enum name was created only to make it possible to reference
+// it. This name is not actually used anywhere outside of the documentation.
+
+/// 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;
+ dict_T *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;
+ bool highlighted;
+ char *pat;
+ dict_T *additional_data;
+ } search_pattern;
+ struct history_item {
+ uint8_t histtype;
+ char *string;
+ char sep;
+ list_T *additional_elements;
+ } history_item;
+ struct reg {
+ char name;
+ uint8_t type;
+ char **contents;
+ size_t contents_size;
+ size_t width;
+ dict_T *additional_data;
+ } reg;
+ struct global_var {
+ char *name;
+ typval_T value;
+ list_T *additional_elements;
+ } global_var;
+ struct {
+ uint64_t type;
+ char *contents;
+ size_t size;
+ } unknown_item;
+ struct sub_string {
+ char *sub;
+ list_T *additional_elements;
+ } sub_string;
+ struct buffer_list {
+ size_t size;
+ struct buffer_list_buffer {
+ pos_T pos;
+ char *fname;
+ dict_T *additional_data;
+ } *buffers;
+ } buffer_list;
+ } data;
+} ShadaEntry;
+
+struct hm_llist_entry;
+
+/// One entry in sized linked list
+typedef struct hm_llist_entry {
+ ShadaEntry data; ///< Entry data.
+ bool can_free_entry; ///< True if data can be freed.
+ struct hm_llist_entry *next; ///< Pointer to next entry or NULL.
+ struct hm_llist_entry *prev; ///< Pointer to previous entry or NULL.
+} HMLListEntry;
+
+KHASH_MAP_INIT_STR(hmll_entries, HMLListEntry *)
+
+/// Sized linked list structure for history merger
+typedef struct {
+ HMLListEntry *entries; ///< Pointer to the start of the allocated array of
+ ///< entries.
+ HMLListEntry *first; ///< First entry in the list (is not necessary start
+ ///< of the array) or NULL.
+ HMLListEntry *last; ///< Last entry in the list or NULL.
+ HMLListEntry *free_entry; ///< Last free entry removed by hmll_remove.
+ HMLListEntry *last_free_entry; ///< Last unused element in entries array.
+ size_t size; ///< Number of allocated entries.
+ size_t num_entries; ///< Number of entries already used.
+ khash_t(hmll_entries) contained_entries; ///< Hash mapping all history entry
+ ///< strings to corresponding entry
+ ///< pointers.
+} HMLList;
+
+typedef struct {
+ HMLList hmll;
+ bool do_merge;
+ bool reading;
+ const void *iter;
+ ShadaEntry last_hist_entry;
+ uint8_t history_type;
+} HistoryMergerState;
+
+/// ShadaEntry structure that knows whether it should be freed
+typedef struct {
+ ShadaEntry data; ///< ShadaEntry data.
+ bool can_free_entry; ///< True if entry can be freed.
+} PossiblyFreedShadaEntry;
+
+/// Structure that holds one file marks.
+typedef struct {
+ PossiblyFreedShadaEntry marks[NLOCALMARKS]; ///< All file marks.
+ PossiblyFreedShadaEntry changes[JUMPLISTSIZE]; ///< All file changes.
+ size_t changes_size; ///< Number of changes occupied.
+ ShadaEntry *additional_marks; ///< All marks with unknown names.
+ size_t additional_marks_size; ///< Size of the additional_marks array.
+ Timestamp greatest_timestamp; ///< Greatest timestamp among marks.
+} FileMarks;
+
+KHASH_MAP_INIT_STR(file_marks, FileMarks)
+
+/// State structure used by shada_write
+///
+/// Before actually writing most of the data is read to this structure.
+typedef struct {
+ HistoryMergerState hms[HIST_COUNT]; ///< Structures for history merging.
+ PossiblyFreedShadaEntry global_marks[NGLOBALMARKS]; ///< All global marks.
+ PossiblyFreedShadaEntry registers[NUM_SAVED_REGISTERS]; ///< All registers.
+ PossiblyFreedShadaEntry jumps[JUMPLISTSIZE]; ///< All dumped jumps.
+ size_t jumps_size; ///< Number of jumps occupied.
+ PossiblyFreedShadaEntry search_pattern; ///< Last search pattern.
+ PossiblyFreedShadaEntry sub_search_pattern; ///< Last s/ search pattern.
+ PossiblyFreedShadaEntry replacement; ///< Last s// replacement string.
+ khash_t(strset) dumped_variables; ///< Names of already dumped variables.
+ khash_t(file_marks) file_marks; ///< All file marks.
+} WriteMergerState;
+
+struct sd_read_def;
+
+/// Function used to close files defined by ShaDaReadDef
+typedef void (*ShaDaReadCloser)(struct sd_read_def *const sd_reader)
+ REAL_FATTR_NONNULL_ALL;
+
+/// Function used to read ShaDa files
+typedef ptrdiff_t (*ShaDaFileReader)(struct sd_read_def *const sd_reader,
+ void *const dest,
+ const size_t size)
+ REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT;
+
+/// Function used to skip in ShaDa files
+typedef int (*ShaDaFileSkipper)(struct sd_read_def *const sd_reader,
+ const size_t offset)
+ REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT;
+
+/// Structure containing necessary pointers for reading ShaDa files
+typedef struct sd_read_def {
+ ShaDaFileReader read; ///< Reader function.
+ ShaDaReadCloser close; ///< Close function.
+ ShaDaFileSkipper skip; ///< Function used to skip some bytes.
+ void *cookie; ///< Data describing object read from.
+ bool eof; ///< True if reader reached end of file.
+ char *error; ///< Error message in case of error.
+ uintmax_t fpos; ///< Current position (amount of bytes read since
+ ///< reader structure initialization). May overflow.
+ vimconv_T sd_conv; ///< Structure used for converting encodings of some
+ ///< items.
+} ShaDaReadDef;
+
+struct sd_write_def;
+
+/// Function used to close files defined by ShaDaWriteDef
+typedef void (*ShaDaWriteCloser)(struct sd_write_def *const sd_writer)
+ REAL_FATTR_NONNULL_ALL;
+
+/// Function used to write ShaDa files
+typedef ptrdiff_t (*ShaDaFileWriter)(struct sd_write_def *const sd_writer,
+ const void *const src,
+ const size_t size)
+ REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT;
+
+/// Structure containing necessary pointers for writing ShaDa files
+typedef struct sd_write_def {
+ ShaDaFileWriter write; ///< Writer function.
+ ShaDaWriteCloser close; ///< Close function.
+ void *cookie; ///< Data describing object written to.
+ char *error; ///< Error message in case of error.
+ vimconv_T sd_conv; ///< Structure used for converting encodings of some
+ ///< items.
+} ShaDaWriteDef;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "shada.c.generated.h"
+#endif
+
+#define DEF_SDE(name, attr, ...) \
+ [kSDItem##name] = { \
+ .timestamp = 0, \
+ .type = kSDItem##name, \
+ .data = { \
+ .attr = { __VA_ARGS__ } \
+ } \
+ }
+#define DEFAULT_POS {1, 0, 0}
+static const pos_T default_pos = DEFAULT_POS;
+static const ShadaEntry sd_default_values[] = {
+ [kSDItemMissing] = { .type = kSDItemMissing, .timestamp = 0 },
+ DEF_SDE(Header, header, .size = 0),
+ DEF_SDE(SearchPattern, 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,
+ .highlighted = false,
+ .pat = NULL,
+ .additional_data = NULL),
+ DEF_SDE(SubString, sub_string, .sub = NULL, .additional_elements = NULL),
+ DEF_SDE(HistoryEntry, history_item,
+ .histtype = HIST_CMD,
+ .string = NULL,
+ .sep = NUL,
+ .additional_elements = NULL),
+ DEF_SDE(Register, reg,
+ .name = NUL,
+ .type = MCHAR,
+ .contents = NULL,
+ .contents_size = 0,
+ .width = 0,
+ .additional_data = NULL),
+ DEF_SDE(Variable, global_var,
+ .name = NULL,
+ .value = {
+ .v_type = VAR_UNKNOWN,
+ .vval = { .v_string = NULL }
+ },
+ .additional_elements = NULL),
+ DEF_SDE(GlobalMark, filemark,
+ .name = '"',
+ .mark = DEFAULT_POS,
+ .fname = NULL,
+ .additional_data = NULL),
+ DEF_SDE(Jump, filemark,
+ .name = NUL,
+ .mark = DEFAULT_POS,
+ .fname = NULL,
+ .additional_data = NULL),
+ DEF_SDE(BufferList, buffer_list,
+ .size = 0,
+ .buffers = NULL),
+ DEF_SDE(LocalMark, filemark,
+ .name = '"',
+ .mark = DEFAULT_POS,
+ .fname = NULL,
+ .additional_data = NULL),
+ DEF_SDE(Change, filemark,
+ .name = NUL,
+ .mark = DEFAULT_POS,
+ .fname = NULL,
+ .additional_data = NULL),
+};
+#undef DEFAULT_POS
+#undef DEF_SDE
+
+/// Initialize new linked list
+///
+/// @param[out] hmll List to initialize.
+/// @param[in] size Maximum size of the list.
+static inline void hmll_init(HMLList *const hmll, const size_t size)
+ FUNC_ATTR_NONNULL_ALL
+{
+ *hmll = (HMLList) {
+ .entries = xcalloc(size, sizeof(hmll->entries[0])),
+ .first = NULL,
+ .last = NULL,
+ .free_entry = NULL,
+ .size = size,
+ .num_entries = 0,
+ .contained_entries = KHASH_EMPTY_TABLE(hmll_entries),
+ };
+ hmll->last_free_entry = hmll->entries;
+}
+
+/// Iterate over HMLList in forward direction
+///
+/// @param hmll Pointer to the list.
+/// @param cur_entry Name of the variable to iterate over.
+///
+/// @return `for` cycle header (use `HMLL_FORALL(hmll, cur_entry) {body}`).
+#define HMLL_FORALL(hmll, cur_entry) \
+ for (HMLListEntry *cur_entry = (hmll)->first; cur_entry != NULL; \
+ cur_entry = cur_entry->next)
+
+/// Remove entry from the linked list
+///
+/// @param hmll List to remove from.
+/// @param hmll_entry Entry to remove.
+static inline void hmll_remove(HMLList *const hmll,
+ HMLListEntry *const hmll_entry)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (hmll_entry == hmll->last_free_entry - 1) {
+ hmll->last_free_entry--;
+ } else {
+ assert(hmll->free_entry == NULL);
+ hmll->free_entry = hmll_entry;
+ }
+ const khiter_t k = kh_get(hmll_entries, &hmll->contained_entries,
+ hmll_entry->data.data.history_item.string);
+ assert(k != kh_end(&hmll->contained_entries));
+ kh_del(hmll_entries, &hmll->contained_entries, k);
+ if (hmll_entry->next == NULL) {
+ hmll->last = hmll_entry->prev;
+ } else {
+ hmll_entry->next->prev = hmll_entry->prev;
+ }
+ if (hmll_entry->prev == NULL) {
+ hmll->first = hmll_entry->next;
+ } else {
+ hmll_entry->prev->next = hmll_entry->next;
+ }
+ hmll->num_entries--;
+ if (hmll_entry->can_free_entry) {
+ shada_free_shada_entry(&hmll_entry->data);
+ }
+}
+
+
+/// Insert entry to the linked list
+///
+/// @param[out] hmll List to insert to.
+/// @param[in] hmll_entry Entry to insert after or NULL if it is needed
+/// to insert at the first entry.
+/// @param[in] data Data to insert.
+/// @param[in] can_free_entry True if data can be freed.
+static inline void hmll_insert(HMLList *const hmll,
+ HMLListEntry *hmll_entry,
+ const ShadaEntry data,
+ const bool can_free_entry)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ if (hmll->num_entries == hmll->size) {
+ if (hmll_entry == hmll->first) {
+ hmll_entry = NULL;
+ }
+ hmll_remove(hmll, hmll->first);
+ }
+ HMLListEntry *target_entry;
+ if (hmll->free_entry == NULL) {
+ assert((size_t) (hmll->last_free_entry - hmll->entries)
+ == hmll->num_entries);
+ target_entry = hmll->last_free_entry++;
+ } else {
+ assert((size_t) (hmll->last_free_entry - hmll->entries) - 1
+ == hmll->num_entries);
+ target_entry = hmll->free_entry;
+ hmll->free_entry = NULL;
+ }
+ target_entry->data = data;
+ target_entry->can_free_entry = can_free_entry;
+ int kh_ret;
+ const khiter_t k = kh_put(hmll_entries, &hmll->contained_entries,
+ data.data.history_item.string, &kh_ret);
+ if (kh_ret > 0) {
+ kh_val(&hmll->contained_entries, k) = target_entry;
+ }
+ hmll->num_entries++;
+ target_entry->prev = hmll_entry;
+ if (hmll_entry == NULL) {
+ target_entry->next = hmll->first;
+ hmll->first = target_entry;
+ } else {
+ target_entry->next = hmll_entry->next;
+ hmll_entry->next = target_entry;
+ }
+ if (target_entry->next == NULL) {
+ hmll->last = target_entry;
+ } else {
+ target_entry->next->prev = target_entry;
+ }
+}
+
+/// Iterate over HMLList in backward direction
+///
+/// @param hmll Pointer to the list.
+/// @param cur_entry Name of the variable to iterate over, must be already
+/// defined.
+///
+/// @return `for` cycle header (use `HMLL_FORALL(hmll, cur_entry) {body}`).
+#define HMLL_ITER_BACK(hmll, cur_entry) \
+ for (cur_entry = (hmll)->last; cur_entry != NULL; \
+ cur_entry = cur_entry->prev)
+
+/// Free linked list
+///
+/// @param[in] hmll List to free.
+static inline void hmll_dealloc(HMLList *const hmll)
+ FUNC_ATTR_NONNULL_ALL
+{
+ kh_dealloc(hmll_entries, &hmll->contained_entries);
+ xfree(hmll->entries);
+}
+
+/// Wrapper for reading from file descriptors
+///
+/// @return -1 or number of bytes read.
+static ptrdiff_t read_file(ShaDaReadDef *const sd_reader, void *const dest,
+ const size_t size)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ size_t read_bytes = 0;
+ bool did_try_to_free = false;
+ const int fd = (int)(intptr_t) sd_reader->cookie;
+ while (read_bytes != size) {
+ const ptrdiff_t cur_read_bytes = read(fd, ((char *) dest) + read_bytes,
+ size - read_bytes);
+ if (cur_read_bytes > 0) {
+ read_bytes += (size_t) cur_read_bytes;
+ sd_reader->fpos += (uintmax_t) cur_read_bytes;
+ assert(read_bytes <= size);
+ }
+ if (cur_read_bytes < 0) {
+ if (errno == EINTR || errno == EAGAIN) {
+ errno = 0;
+ continue;
+ } else if (errno == ENOMEM && !did_try_to_free) {
+ try_to_free_memory();
+ did_try_to_free = true;
+ errno = 0;
+ continue;
+ } else {
+ sd_reader->error = strerror(errno);
+ errno = 0;
+ return -1;
+ }
+ }
+ if (cur_read_bytes == 0) {
+ sd_reader->eof = true;
+ break;
+ }
+ }
+ return (ptrdiff_t) read_bytes;
+}
+
+/// Read one character
+static int read_char(ShaDaReadDef *const sd_reader)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ uint8_t ret;
+ ptrdiff_t read_bytes = sd_reader->read(sd_reader, &ret, 1);
+ if (read_bytes != 1) {
+ return EOF;
+ }
+ return (int) ret;
+}
+
+/// Wrapper for writing to file descriptors
+///
+/// @return -1 or number of bytes written.
+static ptrdiff_t write_file(ShaDaWriteDef *const sd_writer,
+ const void *const dest,
+ const size_t size)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ size_t written_bytes = 0;
+ const int fd = (int)(intptr_t) sd_writer->cookie;
+ while (written_bytes != size) {
+ const ptrdiff_t cur_written_bytes = write(fd, (char *) dest + written_bytes,
+ size - written_bytes);
+ if (cur_written_bytes > 0) {
+ written_bytes += (size_t) cur_written_bytes;
+ }
+ if (cur_written_bytes < 0) {
+ if (errno == EINTR || errno == EAGAIN) {
+ errno = 0;
+ continue;
+ } else {
+ sd_writer->error = strerror(errno);
+ errno = 0;
+ return -1;
+ }
+ }
+ if (cur_written_bytes == 0) {
+ sd_writer->error = "Zero bytes written.";
+ return -1;
+ }
+ }
+ return (ptrdiff_t) written_bytes;
+}
+
+/// Wrapper for closing file descriptors opened for reading
+static void close_sd_reader(ShaDaReadDef *const sd_reader)
+ FUNC_ATTR_NONNULL_ALL
+{
+ close_file((int)(intptr_t) sd_reader->cookie);
+}
+
+/// Wrapper for closing file descriptors opened for writing
+static void close_sd_writer(ShaDaWriteDef *const sd_writer)
+ FUNC_ATTR_NONNULL_ALL
+{
+ const int fd = (int)(intptr_t) sd_writer->cookie;
+ if (fsync(fd) < 0) {
+ emsg2(_(SERR "System error while synchronizing ShaDa file: %s"),
+ strerror(errno));
+ errno = 0;
+ }
+ close_file(fd);
+}
+
+/// Wrapper for read that reads to IObuff and ignores bytes read
+///
+/// Used for skipping.
+///
+/// @param[in,out] sd_reader File read.
+/// @param[in] offset Amount of bytes to skip.
+///
+/// @return FAIL in case of failure, OK in case of success. May set
+/// sd_reader->eof or sd_reader->error.
+static int sd_reader_skip_read(ShaDaReadDef *const sd_reader,
+ const size_t offset)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ size_t read_bytes = 0;
+ do {
+ ptrdiff_t new_read_bytes = sd_reader->read(
+ sd_reader, IObuff, (size_t) (offset - read_bytes > IOSIZE
+ ? IOSIZE
+ : offset - read_bytes));
+ if (new_read_bytes == -1) {
+ return FAIL;
+ }
+ read_bytes += (size_t) new_read_bytes;
+ } while (read_bytes < offset && !sd_reader->eof);
+
+ return (read_bytes == offset ? OK : FAIL);
+}
+
+/// Wrapper for read that can be used when lseek cannot be used
+///
+/// E.g. when trying to read from a pipe.
+///
+/// @param[in,out] sd_reader File read.
+/// @param[in] offset Amount of bytes to skip.
+///
+/// @return kSDReadStatusReadError, kSDReadStatusNotShaDa or
+/// kSDReadStatusSuccess.
+static ShaDaReadResult sd_reader_skip(ShaDaReadDef *const sd_reader,
+ const size_t offset)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
+{
+ if (sd_reader->skip(sd_reader, offset) != OK) {
+ if (sd_reader->error != NULL) {
+ emsg2(_(SERR "System error while skipping in ShaDa file: %s"),
+ sd_reader->error);
+ return kSDReadStatusReadError;
+ } else if (sd_reader->eof) {
+ emsgu(_(RCERR "Error while reading ShaDa file: "
+ "last entry specified that it occupies %" PRIu64 " bytes, "
+ "but file ended earlier"),
+ (uint64_t) offset);
+ return kSDReadStatusNotShaDa;
+ }
+ assert(false);
+ }
+ return kSDReadStatusSuccess;
+}
+
+/// Wrapper for opening file descriptors
+///
+/// All arguments are passed to os_open().
+///
+/// @return file descriptor or -errno on failure.
+static int open_file(const char *const fname, const int flags, const int mode)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
+{
+ bool did_try_to_free = false;
+ int fd;
+open_file_start:
+ fd = os_open(fname, flags, mode);
+
+ if (fd < 0) {
+ if (-fd == ENOENT) {
+ return fd;
+ }
+ if (-fd == ENOMEM && !did_try_to_free) {
+ try_to_free_memory();
+ did_try_to_free = true;
+ goto open_file_start;
+ }
+ if (-fd != EEXIST) {
+ emsg3(_(SERR "System error while opening ShaDa file %s: %s"),
+ fname, os_strerror(fd));
+ }
+ return fd;
+ }
+ return fd;
+}
+
+/// Open ShaDa file for reading
+///
+/// @param[in] fname File name to open.
+/// @param[out] sd_reader Location where reader structure will be saved.
+///
+/// @return -errno in case of error, 0 otherwise.
+static int open_shada_file_for_reading(const char *const fname,
+ ShaDaReadDef *sd_reader)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
+{
+ const intptr_t fd = (intptr_t) open_file(fname, O_RDONLY, 0);
+
+ if (fd < 0) {
+ return (int) fd;
+ }
+
+ *sd_reader = (ShaDaReadDef) {
+ .read = &read_file,
+ .close = &close_sd_reader,
+ .skip = &sd_reader_skip_read,
+ .error = NULL,
+ .eof = false,
+ .fpos = 0,
+ .cookie = (void *) fd,
+ };
+
+ convert_setup(&sd_reader->sd_conv, "utf-8", p_enc);
+
+ return 0;
+}
+
+/// Wrapper for closing file descriptors
+static void close_file(int fd)
+{
+close_file_start:
+ if (close(fd) == -1) {
+ if (errno == EINTR) {
+ errno = 0;
+ goto close_file_start;
+ } else {
+ emsg2(_(SERR "System error while closing ShaDa file: %s"),
+ strerror(errno));
+ errno = 0;
+ }
+ }
+}
+
+/// Check whether buffer is in the given set
+///
+/// @param[in] set Set to check within.
+/// @param[in] buf Buffer to find.
+///
+/// @return true or false.
+static inline bool in_bufset(const khash_t(bufset) *const set, const buf_T *buf)
+ FUNC_ATTR_PURE
+{
+ return kh_get(bufset, set, (uintptr_t) buf) != kh_end(set);
+}
+
+/// Check whether string is in the given set
+///
+/// @param[in] set Set to check within.
+/// @param[in] buf Buffer to find.
+///
+/// @return true or false.
+static inline bool in_strset(const khash_t(strset) *const set, char *str)
+ FUNC_ATTR_PURE
+{
+ return kh_get(strset, set, str) != kh_end(set);
+}
+
+/// Msgpack callback for writing to ShaDaWriteDef*
+static int msgpack_sd_writer_write(void *data, const char *buf, size_t len)
+{
+ ShaDaWriteDef *const sd_writer = (ShaDaWriteDef *) data;
+ ptrdiff_t written_bytes = sd_writer->write(sd_writer, buf, len);
+ if (written_bytes == -1) {
+ emsg2(_(SERR "System error while writing ShaDa file: %s"),
+ sd_writer->error);
+ return -1;
+ }
+ return 0;
+}
+
+/// 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 ShaDaReadFileFlags enum.
+///
+/// @return FAIL if reading failed for some reason and OK otherwise.
+static int shada_read_file(const char *const file, const int flags)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (shada_disabled()) {
+ return FAIL;
+ }
+
+ char *const fname = shada_filename(file);
+
+ ShaDaReadDef sd_reader;
+ const int of_ret = open_shada_file_for_reading(fname, &sd_reader);
+
+ if (p_verbose > 0) {
+ verbose_enter();
+ smsg(_("Reading ShaDa file \"%s\"%s%s%s"),
+ fname,
+ (flags & kShaDaWantInfo) ? _(" info") : "",
+ (flags & kShaDaWantMarks) ? _(" marks") : "",
+ (flags & kShaDaGetOldfiles) ? _(" oldfiles") : "",
+ of_ret != 0 ? _(" FAILED") : "");
+ verbose_leave();
+ }
+
+ if (of_ret != 0) {
+ if (-of_ret == ENOENT && (flags & kShaDaMissingError)) {
+ emsg3(_(SERR "System error while opening ShaDa file %s for reading: %s"),
+ fname, os_strerror(of_ret));
+ }
+ xfree(fname);
+ return FAIL;
+ }
+ xfree(fname);
+
+ shada_read(&sd_reader, flags);
+ sd_reader.close(&sd_reader);
+
+ return OK;
+}
+
+/// Wrapper for hist_iter() function which produces ShadaEntry values
+///
+/// @param[in] iter Current iteration state.
+/// @param[in] history_type Type of the history (HIST_*).
+/// @param[in] zero If true, then item is removed from instance
+/// memory upon reading.
+/// @param[out] hist Location where iteration results should be saved.
+///
+/// @return Next iteration state.
+static const void *shada_hist_iter(const void *const iter,
+ const uint8_t history_type,
+ const bool zero,
+ ShadaEntry *const hist)
+ FUNC_ATTR_NONNULL_ARG(4) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ histentry_T hist_he;
+ const void *const ret = hist_iter(iter, history_type, zero, &hist_he);
+ if (hist_he.hisstr == NULL) {
+ *hist = (ShadaEntry) { .type = kSDItemMissing };
+ } else {
+ *hist = (ShadaEntry) {
+ .type = kSDItemHistoryEntry,
+ .timestamp = hist_he.timestamp,
+ .data = {
+ .history_item = {
+ .histtype = history_type,
+ .string = (char *) hist_he.hisstr,
+ .sep = (char) (history_type == HIST_SEARCH
+ ? (char) hist_he.hisstr[STRLEN(hist_he.hisstr) + 1]
+ : 0),
+ .additional_elements = hist_he.additional_elements,
+ }
+ }
+ };
+ }
+ return ret;
+}
+
+/// Insert history entry
+///
+/// Inserts history entry at the end of the ring buffer (may insert earlier
+/// according to the timestamp). If entry was already in the ring buffer
+/// existing entry will be removed unless it has greater timestamp.
+///
+/// Before the new entry entries from the current Neovim history will be
+/// inserted unless `do_iter` argument is false.
+///
+/// @param[in,out] hms_p Ring buffer and associated structures.
+/// @param[in] entry Inserted entry.
+/// @param[in] do_iter Determines whether Neovim own history should
+/// be used. Must be true only if inserting
+/// entry from current Neovim history.
+/// @param[in] can_free_entry True if entry can be freed.
+static void hms_insert(HistoryMergerState *const hms_p, const ShadaEntry entry,
+ const bool do_iter, const bool can_free_entry)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (do_iter) {
+ while (hms_p->last_hist_entry.type != kSDItemMissing
+ && hms_p->last_hist_entry.timestamp < entry.timestamp) {
+ hms_insert(hms_p, hms_p->last_hist_entry, false, hms_p->reading);
+ if (hms_p->iter == NULL) {
+ hms_p->last_hist_entry.type = kSDItemMissing;
+ break;
+ }
+ hms_p->iter = shada_hist_iter(hms_p->iter, hms_p->history_type,
+ hms_p->reading, &hms_p->last_hist_entry);
+ }
+ }
+ HMLList *const hmll = &hms_p->hmll;
+ const khiter_t k = kh_get(hmll_entries, &hms_p->hmll.contained_entries,
+ entry.data.history_item.string);
+ if (k != kh_end(&hmll->contained_entries)) {
+ HMLListEntry *const existing_entry = kh_val(&hmll->contained_entries, k);
+ if (entry.timestamp > existing_entry->data.timestamp) {
+ hmll_remove(hmll, existing_entry);
+ } else if (!do_iter && entry.timestamp == existing_entry->data.timestamp) {
+ // Prefer entry from the current Neovim instance.
+ if (existing_entry->can_free_entry) {
+ shada_free_shada_entry(&existing_entry->data);
+ }
+ existing_entry->data = entry;
+ existing_entry->can_free_entry = can_free_entry;
+ // Previous key was freed above, as part of freeing the ShaDa entry.
+ kh_key(&hmll->contained_entries, k) = entry.data.history_item.string;
+ return;
+ } else {
+ return;
+ }
+ }
+ HMLListEntry *insert_after;
+ HMLL_ITER_BACK(hmll, insert_after) {
+ if (insert_after->data.timestamp <= entry.timestamp) {
+ break;
+ }
+ }
+ hmll_insert(hmll, insert_after, entry, can_free_entry);
+}
+
+/// Initialize the history merger
+///
+/// @param[out] hms_p Structure to be initialized.
+/// @param[in] history_type History type (one of HIST_\* values).
+/// @param[in] num_elements Number of elements in the result.
+/// @param[in] do_merge Prepare structure for merging elements.
+/// @param[in] reading If true, then merger is reading history for use
+/// in Neovim.
+static inline void hms_init(HistoryMergerState *const hms_p,
+ const uint8_t history_type,
+ const size_t num_elements,
+ const bool do_merge,
+ const bool reading)
+ FUNC_ATTR_NONNULL_ALL
+{
+ hmll_init(&hms_p->hmll, num_elements);
+ hms_p->do_merge = do_merge;
+ hms_p->reading = reading;
+ hms_p->iter = shada_hist_iter(NULL, history_type, hms_p->reading,
+ &hms_p->last_hist_entry);
+ hms_p->history_type = history_type;
+}
+
+/// Merge in all remaining Neovim own history entries
+///
+/// @param[in,out] hms_p Merger structure into which history should be
+/// inserted.
+static inline void hms_insert_whole_neovim_history(
+ HistoryMergerState *const hms_p)
+ FUNC_ATTR_NONNULL_ALL
+{
+ while (hms_p->last_hist_entry.type != kSDItemMissing) {
+ hms_insert(hms_p, hms_p->last_hist_entry, false, hms_p->reading);
+ if (hms_p->iter == NULL) {
+ break;
+ }
+ hms_p->iter = shada_hist_iter(hms_p->iter, hms_p->history_type,
+ hms_p->reading, &hms_p->last_hist_entry);
+ }
+}
+
+/// Convert merger structure to Neovim internal structure for history
+///
+/// @param[in] hms_p Converted merger structure.
+/// @param[out] hist_array Array with the results.
+/// @param[out] new_hisidx New last history entry index.
+/// @param[out] new_hisnum Amount of history items in merger structure.
+static inline void hms_to_he_array(const HistoryMergerState *const hms_p,
+ histentry_T *const hist_array,
+ int *const new_hisidx,
+ int *const new_hisnum)
+ FUNC_ATTR_NONNULL_ALL
+{
+ histentry_T *hist = hist_array;
+ HMLL_FORALL(&hms_p->hmll, cur_entry) {
+ hist->timestamp = cur_entry->data.timestamp;
+ hist->hisnum = (int) (hist - hist_array) + 1;
+ hist->hisstr = (char_u *) cur_entry->data.data.history_item.string;
+ hist->additional_elements =
+ cur_entry->data.data.history_item.additional_elements;
+ hist++;
+ }
+ *new_hisnum = (int) (hist - hist_array);
+ *new_hisidx = *new_hisnum - 1;
+}
+
+/// Free history merger structure
+///
+/// @param[in] hms_p Structure to be freed.
+static inline void hms_dealloc(HistoryMergerState *const hms_p)
+ FUNC_ATTR_NONNULL_ALL
+{
+ hmll_dealloc(&hms_p->hmll);
+}
+
+/// Iterate over all history entries in history merger, in order
+///
+/// @param[in] hms_p Merger structure to iterate over.
+/// @param[out] cur_entry Name of the iterator variable.
+///
+/// @return for cycle header. Use `HMS_ITER(hms_p, cur_entry) {body}`.
+#define HMS_ITER(hms_p, cur_entry) \
+ HMLL_FORALL(&((hms_p)->hmll), cur_entry)
+
+/// Find buffer for given buffer name (cached)
+///
+/// @param[in,out] fname_bufs Cache containing fname to buffer mapping.
+/// @param[in] fname File name to find.
+///
+/// @return Pointer to the buffer or NULL.
+static buf_T *find_buffer(khash_t(fnamebufs) *const fname_bufs,
+ const char *const fname)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
+{
+ int kh_ret;
+ khint_t k = kh_put(fnamebufs, fname_bufs, fname, &kh_ret);
+ if (!kh_ret) {
+ return kh_val(fname_bufs, k);
+ }
+ kh_key(fname_bufs, k) = xstrdup(fname);
+ FOR_ALL_BUFFERS(buf) {
+ if (buf->b_ffname != NULL) {
+ if (fnamecmp(fname, buf->b_ffname) == 0) {
+ kh_val(fname_bufs, k) = buf;
+ return buf;
+ }
+ }
+ }
+ kh_val(fname_bufs, k) = NULL;
+ return NULL;
+}
+
+/// Compare two marks
+static inline bool marks_equal(const pos_T a, const pos_T b)
+{
+ return (a.lnum == b.lnum) && (a.col == b.col);
+}
+
+#define MERGE_JUMPS(jumps_size, jumps, jumps_type, timestamp_attr, mark_attr, \
+ entry, fname_cond, free_func, fin_func, \
+ idxadj_func, afterfree_func) \
+ do { \
+ const int jl_len = (int) jumps_size; \
+ int i; \
+ for (i = jl_len; i > 0; i--) { \
+ const jumps_type jl_entry = jumps[i - 1]; \
+ if (jl_entry.timestamp_attr <= entry.timestamp) { \
+ if (marks_equal(jl_entry.mark_attr, entry.data.filemark.mark) \
+ && fname_cond) { \
+ i = -1; \
+ } \
+ break; \
+ } \
+ } \
+ if (i > 0) { \
+ if (jl_len == JUMPLISTSIZE) { \
+ free_func(jumps[0]); \
+ i--; \
+ if (i > 0) { \
+ memmove(&jumps[0], &jumps[1], sizeof(jumps[1]) * (size_t) i); \
+ } \
+ } else if (i != jl_len) { \
+ memmove(&jumps[i + 1], &jumps[i], \
+ sizeof(jumps[0]) * (size_t) (jl_len - i)); \
+ } \
+ } else if (i == 0) { \
+ if (jl_len == JUMPLISTSIZE) { \
+ i = -1; \
+ } else if (jl_len > 0) { \
+ memmove(&jumps[1], &jumps[0], sizeof(jumps[0]) * (size_t) jl_len); \
+ } \
+ } \
+ if (i != -1) { \
+ jumps[i] = fin_func(entry); \
+ if (jl_len < JUMPLISTSIZE) { \
+ jumps_size++; \
+ } \
+ idxadj_func(i); \
+ } else { \
+ shada_free_shada_entry(&entry); \
+ afterfree_func(entry); \
+ } \
+ } while (0)
+
+/// Read data from ShaDa file
+///
+/// @param[in] sd_reader Structure containing file reader definition.
+/// @param[in] flags What to read, see ShaDaReadFileFlags enum.
+static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
+ FUNC_ATTR_NONNULL_ALL
+{
+ list_T *oldfiles_list = get_vim_var_list(VV_OLDFILES);
+ const bool force = flags & kShaDaForceit;
+ const bool get_old_files = (flags & (kShaDaGetOldfiles | kShaDaForceit)
+ && (force || oldfiles_list == NULL
+ || oldfiles_list->lv_len == 0));
+ const bool want_marks = flags & kShaDaWantMarks;
+ const unsigned srni_flags = (unsigned) (
+ (flags & kShaDaWantInfo
+ ? (kSDReadUndisableableData
+ | kSDReadRegisters
+ | kSDReadGlobalMarks
+ | (p_hi ? kSDReadHistory : 0)
+ | (find_shada_parameter('!') != NULL
+ ? kSDReadVariables
+ : 0)
+ | (find_shada_parameter('%') != NULL
+ && ARGCOUNT == 0
+ ? kSDReadBufferList
+ : 0))
+ : 0)
+ | (want_marks && get_shada_parameter('\'') > 0
+ ? kSDReadLocalMarks | kSDReadChanges
+ : 0)
+ | (get_old_files
+ ? kSDReadLocalMarks
+ : 0));
+ if (srni_flags == 0) {
+ // Nothing to do.
+ return;
+ }
+ HistoryMergerState hms[HIST_COUNT];
+ if (srni_flags & kSDReadHistory) {
+ for (uint8_t i = 0; i < HIST_COUNT; i++) {
+ hms_init(&hms[i], i, (size_t) p_hi, true, true);
+ }
+ }
+ ShadaEntry cur_entry;
+ khash_t(bufset) cl_bufs = KHASH_EMPTY_TABLE(bufset);
+ khash_t(fnamebufs) fname_bufs = KHASH_EMPTY_TABLE(fnamebufs);
+ khash_t(strset) oldfiles_set = KHASH_EMPTY_TABLE(strset);
+ if (get_old_files && (oldfiles_list == NULL || force)) {
+ oldfiles_list = list_alloc();
+ set_vim_var_list(VV_OLDFILES, oldfiles_list);
+ }
+ ShaDaReadResult srni_ret;
+ while ((srni_ret = shada_read_next_item(sd_reader, &cur_entry, srni_flags, 0))
+ != kSDReadStatusFinished) {
+ switch (srni_ret) {
+ case kSDReadStatusSuccess: {
+ break;
+ }
+ case kSDReadStatusFinished: {
+ // Should be handled by the while condition.
+ assert(false);
+ }
+ case kSDReadStatusNotShaDa:
+ case kSDReadStatusReadError: {
+ goto shada_read_main_cycle_end;
+ }
+ case kSDReadStatusMalformed: {
+ continue;
+ }
+ }
+ switch (cur_entry.type) {
+ case kSDItemMissing: {
+ assert(false);
+ }
+ case kSDItemUnknown: {
+ break;
+ }
+ case kSDItemHeader: {
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ case kSDItemSearchPattern: {
+ if (!force) {
+ SearchPattern pat;
+ (cur_entry.data.search_pattern.is_substitute_pattern
+ ? &get_substitute_pattern
+ : &get_search_pattern)(&pat);
+ if (pat.pat != NULL && pat.timestamp >= cur_entry.timestamp) {
+ 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);
+ }
+ if (cur_entry.data.search_pattern.is_last_used) {
+ SET_NO_HLSEARCH(!cur_entry.data.search_pattern.highlighted);
+ }
+ // Do not free shada entry: its allocated memory was saved above.
+ break;
+ }
+ case kSDItemSubString: {
+ if (!force) {
+ SubReplacementString sub;
+ sub_get_replacement(&sub);
+ if (sub.sub != NULL && sub.timestamp >= cur_entry.timestamp) {
+ 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,
+ });
+ // Without using regtilde and without / &cpo flag previous substitute
+ // string is close to useless: you can only use it with :& or :~ and
+ // that’s all because s//~ is not available until the first call to
+ // regtilde. Vim was not calling this for some reason.
+ (void) regtilde(cur_entry.data.sub_string.sub, p_magic);
+ // Do not free shada entry: its allocated memory was saved above.
+ break;
+ }
+ case kSDItemHistoryEntry: {
+ if (cur_entry.data.history_item.histtype >= HIST_COUNT) {
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ hms_insert(hms + cur_entry.data.history_item.histtype, cur_entry, true,
+ true);
+ // Do not free shada entry: its allocated memory was saved above.
+ break;
+ }
+ case kSDItemRegister: {
+ 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;
+ }
+ if (!force) {
+ const yankreg_T *const reg = op_register_get(cur_entry.data.reg.name);
+ if (reg == NULL || reg->timestamp >= cur_entry.timestamp) {
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ }
+ if (!op_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,
+ })) {
+ shada_free_shada_entry(&cur_entry);
+ }
+ // Do not free shada entry: its allocated memory was saved above.
+ break;
+ }
+ case kSDItemVariable: {
+ var_set_global(cur_entry.data.global_var.name,
+ cur_entry.data.global_var.value);
+ cur_entry.data.global_var.value.v_type = VAR_UNKNOWN;
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ case kSDItemJump:
+ case kSDItemGlobalMark: {
+ buf_T *buf = find_buffer(&fname_bufs, cur_entry.data.filemark.fname);
+ if (buf != NULL) {
+ xfree(cur_entry.data.filemark.fname);
+ cur_entry.data.filemark.fname = NULL;
+ }
+ xfmark_T fm = (xfmark_T) {
+ .fname = (char_u *) (buf == NULL
+ ? cur_entry.data.filemark.fname
+ : NULL),
+ .fmark = {
+ .mark = cur_entry.data.filemark.mark,
+ .fnum = (buf == NULL ? 0 : buf->b_fnum),
+ .timestamp = cur_entry.timestamp,
+ .additional_data = cur_entry.data.filemark.additional_data,
+ },
+ };
+ if (cur_entry.type == kSDItemGlobalMark) {
+ if (!mark_set_global(cur_entry.data.filemark.name, fm, !force)) {
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ } else {
+#define SDE_TO_XFMARK(entry) fm
+#define ADJUST_IDX(i) \
+ if (curwin->w_jumplistidx >= i \
+ && curwin->w_jumplistidx + 1 <= curwin->w_jumplistlen) { \
+ curwin->w_jumplistidx++; \
+ }
+#define DUMMY_AFTERFREE(entry)
+ MERGE_JUMPS(curwin->w_jumplistlen, curwin->w_jumplist, xfmark_T,
+ fmark.timestamp, fmark.mark, cur_entry,
+ (buf == NULL
+ ? (jl_entry.fname != NULL
+ && STRCMP(fm.fname, jl_entry.fname) == 0)
+ : fm.fmark.fnum == jl_entry.fmark.fnum),
+ free_xfmark, SDE_TO_XFMARK, ADJUST_IDX, DUMMY_AFTERFREE);
+#undef SDE_TO_XFMARK
+#undef ADJUST_IDX
+#undef DUMMY_AFTERFREE
+ }
+ // Do not free shada entry: its allocated memory was saved above.
+ break;
+ }
+ case kSDItemBufferList: {
+ for (size_t i = 0; i < cur_entry.data.buffer_list.size; i++) {
+ char *const sfname = path_shorten_fname_if_possible(
+ cur_entry.data.buffer_list.buffers[i].fname);
+ buf_T *const buf = buflist_new(
+ cur_entry.data.buffer_list.buffers[i].fname, sfname, 0,
+ BLN_LISTED);
+ if (buf != NULL) {
+ RESET_FMARK(&buf->b_last_cursor,
+ cur_entry.data.buffer_list.buffers[i].pos, 0);
+ buflist_setfpos(buf, curwin, buf->b_last_cursor.mark.lnum,
+ buf->b_last_cursor.mark.col, false);
+ buf->additional_data =
+ cur_entry.data.buffer_list.buffers[i].additional_data;
+ cur_entry.data.buffer_list.buffers[i].additional_data = NULL;
+ }
+ }
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ case kSDItemChange:
+ case kSDItemLocalMark: {
+ if (get_old_files && !in_strset(&oldfiles_set,
+ cur_entry.data.filemark.fname)) {
+ char *fname = cur_entry.data.filemark.fname;
+ if (want_marks) {
+ // Do not bother with allocating memory for the string if already
+ // allocated string from cur_entry can be used. It cannot be used if
+ // want_marks is set because this way it may be used for a mark.
+ fname = xstrdup(fname);
+ }
+ int kh_ret;
+ (void) kh_put(strset, &oldfiles_set, fname, &kh_ret);
+ list_append_allocated_string(oldfiles_list, fname);
+ if (!want_marks) {
+ // Avoid free because this string was already used.
+ cur_entry.data.filemark.fname = NULL;
+ }
+ }
+ if (!want_marks) {
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ buf_T *buf = find_buffer(&fname_bufs, cur_entry.data.filemark.fname);
+ if (buf == NULL) {
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ const fmark_T fm = (fmark_T) {
+ .mark = cur_entry.data.filemark.mark,
+ .fnum = 0,
+ .timestamp = cur_entry.timestamp,
+ .additional_data = cur_entry.data.filemark.additional_data,
+ };
+ if (cur_entry.type == kSDItemLocalMark) {
+ if (!mark_set_local(cur_entry.data.filemark.name, buf, fm, !force)) {
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ } else {
+ int kh_ret;
+ (void) kh_put(bufset, &cl_bufs, (uintptr_t) buf, &kh_ret);
+#define SDE_TO_FMARK(entry) fm
+#define AFTERFREE(entry) (entry).data.filemark.fname = NULL
+#define DUMMY_IDX_ADJ(i)
+ MERGE_JUMPS(buf->b_changelistlen, buf->b_changelist, fmark_T,
+ timestamp, mark, cur_entry, true,
+ free_fmark, SDE_TO_FMARK, DUMMY_IDX_ADJ, AFTERFREE);
+#undef SDE_TO_FMARK
+#undef AFTERFREE
+#undef DUMMY_IDX_ADJ
+ }
+ // Do not free shada entry: except for fname, its allocated memory (i.e.
+ // additional_data attribute contents if non-NULL) was saved above.
+ xfree(cur_entry.data.filemark.fname);
+ break;
+ }
+ }
+ }
+shada_read_main_cycle_end:
+ // Warning: shada_hist_iter returns ShadaEntry elements which use strings from
+ // original history list. This means that once such entry is removed
+ // from the history Neovim array will no longer be valid. To reduce
+ // amount of memory allocations ShaDa file reader allocates enough
+ // memory for the history string itself and separator character which
+ // may be assigned right away.
+ if (srni_flags & kSDReadHistory) {
+ for (uint8_t i = 0; i < HIST_COUNT; i++) {
+ hms_insert_whole_neovim_history(&hms[i]);
+ clr_history(i);
+ int *new_hisidx;
+ int *new_hisnum;
+ histentry_T *hist = hist_get_array(i, &new_hisidx, &new_hisnum);
+ if (hist != NULL) {
+ hms_to_he_array(&hms[i], hist, new_hisidx, new_hisnum);
+ }
+ hms_dealloc(&hms[i]);
+ }
+ }
+ if (cl_bufs.n_occupied) {
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ (void) tp;
+ if (in_bufset(&cl_bufs, wp->w_buffer)) {
+ wp->w_changelistidx = wp->w_buffer->b_changelistlen;
+ }
+ }
+ }
+ kh_dealloc(bufset, &cl_bufs);
+ const char *key;
+ kh_foreach_key(&fname_bufs, key, {
+ xfree((void *) key);
+ })
+ kh_dealloc(fnamebufs, &fname_bufs);
+ kh_dealloc(strset, &oldfiles_set);
+}
+
+/// 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 'shada' 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_shada_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)
+#define PACK_STRING(s) \
+ do { \
+ const String s_ = (s); \
+ msgpack_pack_str(spacker, s_.size); \
+ msgpack_pack_str_body(spacker, s_.data, s_.size); \
+ } while (0)
+#define PACK_BIN(s) \
+ do { \
+ const String s_ = (s); \
+ msgpack_pack_bin(spacker, s_.size); \
+ msgpack_pack_bin_body(spacker, s_.data, s_.size); \
+ } 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 bool shada_pack_entry(msgpack_packer *const packer,
+ ShadaEntry entry,
+ const size_t max_kbyte)
+ FUNC_ATTR_NONNULL_ALL
+{
+ msgpack_sbuffer sbuf;
+ msgpack_sbuffer_init(&sbuf);
+ msgpack_packer *spacker = msgpack_packer_new(&sbuf, &msgpack_sbuffer_write);
+#define DUMP_ADDITIONAL_ELEMENTS(src) \
+ do { \
+ if ((src) != NULL) { \
+ for (listitem_T *li = (src)->lv_first; li != NULL; li = li->li_next) { \
+ if (vim_to_msgpack(spacker, &li->li_tv) == FAIL) { \
+ goto shada_pack_entry_error; \
+ } \
+ } \
+ } \
+ } while (0)
+#define DUMP_ADDITIONAL_DATA(src) \
+ do { \
+ dict_T *const d = (src); \
+ if (d != NULL) { \
+ size_t todo = d->dv_hashtab.ht_used; \
+ for (const hashitem_T *hi= d->dv_hashtab.ht_array; todo; hi++) { \
+ if (!HASHITEM_EMPTY(hi)) { \
+ todo--; \
+ dictitem_T *const di = HI2DI(hi); \
+ const size_t key_len = strlen((const char *) hi->hi_key); \
+ msgpack_pack_str(spacker, key_len); \
+ msgpack_pack_str_body(spacker, (const char *) hi->hi_key, key_len); \
+ if (vim_to_msgpack(spacker, &di->di_tv) == FAIL) { \
+ goto shada_pack_entry_error; \
+ } \
+ } \
+ } \
+ } \
+ } while (0)
+#define CHECK_DEFAULT(entry, attr) \
+ (sd_default_values[entry.type].data.attr == entry.data.attr)
+#define ONE_IF_NOT_DEFAULT(entry, attr) \
+ ((size_t) (!CHECK_DEFAULT(entry, attr)))
+ switch (entry.type) {
+ case kSDItemMissing: {
+ assert(false);
+ }
+ case kSDItemUnknown: {
+ if (spacker->callback(spacker->data, entry.data.unknown_item.contents,
+ (unsigned) entry.data.unknown_item.size) == -1) {
+ goto shada_pack_entry_error;
+ }
+ break;
+ }
+ case kSDItemHistoryEntry: {
+ const bool is_hist_search =
+ entry.data.history_item.histtype == HIST_SEARCH;
+ const size_t arr_size = 2 + (size_t) is_hist_search + (size_t) (
+ entry.data.history_item.additional_elements == NULL
+ ? 0
+ : entry.data.history_item.additional_elements->lv_len);
+ msgpack_pack_array(spacker, arr_size);
+ msgpack_pack_uint8(spacker, entry.data.history_item.histtype);
+ PACK_BIN(cstr_as_string(entry.data.history_item.string));
+ if (is_hist_search) {
+ msgpack_pack_uint8(spacker, (uint8_t) entry.data.history_item.sep);
+ }
+ DUMP_ADDITIONAL_ELEMENTS(entry.data.history_item.additional_elements);
+ break;
+ }
+ case kSDItemVariable: {
+ const size_t arr_size = 2 + (size_t) (
+ entry.data.global_var.additional_elements == NULL
+ ? 0
+ : entry.data.global_var.additional_elements->lv_len);
+ msgpack_pack_array(spacker, arr_size);
+ PACK_BIN(cstr_as_string(entry.data.global_var.name));
+ if (vim_to_msgpack(spacker, &entry.data.global_var.value) == FAIL) {
+ goto shada_pack_entry_error;
+ }
+ DUMP_ADDITIONAL_ELEMENTS(entry.data.global_var.additional_elements);
+ break;
+ }
+ case kSDItemSubString: {
+ const size_t arr_size = 1 + (size_t) (
+ entry.data.sub_string.additional_elements == NULL
+ ? 0
+ : entry.data.sub_string.additional_elements->lv_len);
+ msgpack_pack_array(spacker, arr_size);
+ PACK_BIN(cstr_as_string(entry.data.sub_string.sub));
+ DUMP_ADDITIONAL_ELEMENTS(entry.data.sub_string.additional_elements);
+ break;
+ }
+ case kSDItemSearchPattern: {
+ const size_t map_size = (size_t) (
+ 1 // Search pattern is always present
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.magic)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.is_last_used)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.smartcase)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.has_line_offset)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.place_cursor_at_end)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.is_substitute_pattern)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.highlighted)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.offset)
+ // finally, additional data:
+ + (size_t) (
+ entry.data.search_pattern.additional_data
+ ? entry.data.search_pattern.additional_data->dv_hashtab.ht_used
+ : 0));
+ msgpack_pack_map(spacker, map_size);
+ PACK_STATIC_STR(SEARCH_KEY_PAT);
+ PACK_BIN(cstr_as_string(entry.data.search_pattern.pat));
+#define PACK_BOOL(entry, name, attr) \
+ do { \
+ if (!CHECK_DEFAULT(entry, search_pattern.attr)) { \
+ PACK_STATIC_STR(name); \
+ if (sd_default_values[entry.type].data.search_pattern.attr) { \
+ msgpack_pack_false(spacker); \
+ } else { \
+ msgpack_pack_true(spacker); \
+ } \
+ } \
+ } while (0)
+ PACK_BOOL(entry, SEARCH_KEY_MAGIC, magic);
+ PACK_BOOL(entry, SEARCH_KEY_IS_LAST_USED, is_last_used);
+ PACK_BOOL(entry, SEARCH_KEY_SMARTCASE, smartcase);
+ PACK_BOOL(entry, SEARCH_KEY_HAS_LINE_OFFSET, has_line_offset);
+ PACK_BOOL(entry, SEARCH_KEY_PLACE_CURSOR_AT_END, place_cursor_at_end);
+ PACK_BOOL(entry, SEARCH_KEY_IS_SUBSTITUTE_PATTERN, is_substitute_pattern);
+ PACK_BOOL(entry, SEARCH_KEY_HIGHLIGHTED, highlighted);
+ if (!CHECK_DEFAULT(entry, search_pattern.offset)) {
+ PACK_STATIC_STR(SEARCH_KEY_OFFSET);
+ msgpack_pack_int64(spacker, entry.data.search_pattern.offset);
+ }
+#undef PACK_BOOL
+ DUMP_ADDITIONAL_DATA(entry.data.search_pattern.additional_data);
+ break;
+ }
+ case kSDItemChange:
+ case kSDItemGlobalMark:
+ case kSDItemLocalMark:
+ case kSDItemJump: {
+ const size_t map_size = (size_t) (
+ 1 // File name
+ + ONE_IF_NOT_DEFAULT(entry, filemark.mark.lnum)
+ + ONE_IF_NOT_DEFAULT(entry, filemark.mark.col)
+ + ONE_IF_NOT_DEFAULT(entry, filemark.name)
+ // Additional entries, if any:
+ + (size_t) (
+ entry.data.filemark.additional_data == NULL
+ ? 0
+ : entry.data.filemark.additional_data->dv_hashtab.ht_used));
+ msgpack_pack_map(spacker, map_size);
+ PACK_STATIC_STR(KEY_FILE);
+ PACK_BIN(cstr_as_string(entry.data.filemark.fname));
+ if (!CHECK_DEFAULT(entry, filemark.mark.lnum)) {
+ PACK_STATIC_STR(KEY_LNUM);
+ msgpack_pack_long(spacker, entry.data.filemark.mark.lnum);
+ }
+ if (!CHECK_DEFAULT(entry, filemark.mark.col)) {
+ PACK_STATIC_STR(KEY_COL);
+ msgpack_pack_long(spacker, entry.data.filemark.mark.col);
+ }
+ assert(entry.type == kSDItemJump || entry.type == kSDItemChange
+ ? CHECK_DEFAULT(entry, filemark.name)
+ : true);
+ if (!CHECK_DEFAULT(entry, filemark.name)) {
+ PACK_STATIC_STR(KEY_NAME_CHAR);
+ msgpack_pack_uint8(spacker, (uint8_t) entry.data.filemark.name);
+ }
+ DUMP_ADDITIONAL_DATA(entry.data.filemark.additional_data);
+ break;
+ }
+ case kSDItemRegister: {
+ const size_t map_size = (size_t) (
+ 2 // Register contents and name
+ + ONE_IF_NOT_DEFAULT(entry, reg.type)
+ + ONE_IF_NOT_DEFAULT(entry, reg.width)
+ // Additional entries, if any:
+ + (size_t) (entry.data.reg.additional_data == NULL
+ ? 0
+ : entry.data.reg.additional_data->dv_hashtab.ht_used));
+ msgpack_pack_map(spacker, map_size);
+ PACK_STATIC_STR(REG_KEY_CONTENTS);
+ msgpack_pack_array(spacker, entry.data.reg.contents_size);
+ for (size_t i = 0; i < entry.data.reg.contents_size; i++) {
+ PACK_BIN(cstr_as_string(entry.data.reg.contents[i]));
+ }
+ PACK_STATIC_STR(KEY_NAME_CHAR);
+ msgpack_pack_char(spacker, entry.data.reg.name);
+ if (!CHECK_DEFAULT(entry, reg.type)) {
+ PACK_STATIC_STR(REG_KEY_TYPE);
+ msgpack_pack_uint8(spacker, entry.data.reg.type);
+ }
+ if (!CHECK_DEFAULT(entry, reg.width)) {
+ PACK_STATIC_STR(REG_KEY_WIDTH);
+ msgpack_pack_uint64(spacker, (uint64_t) entry.data.reg.width);
+ }
+ DUMP_ADDITIONAL_DATA(entry.data.reg.additional_data);
+ break;
+ }
+ case kSDItemBufferList: {
+ msgpack_pack_array(spacker, entry.data.buffer_list.size);
+ for (size_t i = 0; i < entry.data.buffer_list.size; i++) {
+ const size_t map_size = (size_t) (
+ 1 // Buffer name
+ + (size_t) (entry.data.buffer_list.buffers[i].pos.lnum
+ != default_pos.lnum)
+ + (size_t) (entry.data.buffer_list.buffers[i].pos.col
+ != default_pos.col)
+ // Additional entries, if any:
+ + (size_t) (
+ entry.data.buffer_list.buffers[i].additional_data == NULL
+ ? 0
+ : (entry.data.buffer_list.buffers[i].additional_data
+ ->dv_hashtab.ht_used)));
+ msgpack_pack_map(spacker, map_size);
+ PACK_STATIC_STR(KEY_FILE);
+ PACK_BIN(cstr_as_string(entry.data.buffer_list.buffers[i].fname));
+ if (entry.data.buffer_list.buffers[i].pos.lnum != 1) {
+ PACK_STATIC_STR(KEY_LNUM);
+ msgpack_pack_uint64(
+ spacker, (uint64_t) entry.data.buffer_list.buffers[i].pos.lnum);
+ }
+ if (entry.data.buffer_list.buffers[i].pos.col != 0) {
+ PACK_STATIC_STR(KEY_COL);
+ msgpack_pack_uint64(
+ spacker, (uint64_t) entry.data.buffer_list.buffers[i].pos.col);
+ }
+ DUMP_ADDITIONAL_DATA(entry.data.buffer_list.buffers[i].additional_data);
+ }
+ break;
+ }
+ case kSDItemHeader: {
+ msgpack_pack_map(spacker, entry.data.header.size);
+ for (size_t i = 0; i < entry.data.header.size; i++) {
+ PACK_STRING(entry.data.header.items[i].key);
+ const Object obj = entry.data.header.items[i].value;
+ switch (obj.type) {
+ case kObjectTypeString: {
+ PACK_BIN(obj.data.string);
+ break;
+ }
+ case kObjectTypeInteger: {
+ msgpack_pack_int64(spacker, (int64_t) obj.data.integer);
+ break;
+ }
+ default: {
+ assert(false);
+ }
+ }
+ }
+ break;
+ }
+ }
+#undef CHECK_DEFAULT
+#undef ONE_IF_NOT_DEFAULT
+ if (!max_kbyte || sbuf.size <= max_kbyte * 1024) {
+ if (entry.type == kSDItemUnknown) {
+ if (msgpack_pack_uint64(packer, entry.data.unknown_item.type) == -1) {
+ goto shada_pack_entry_error;
+ }
+ } else {
+ if (msgpack_pack_uint64(packer, (uint64_t) entry.type) == -1) {
+ goto shada_pack_entry_error;
+ }
+ }
+ if (msgpack_pack_uint64(packer, (uint64_t) entry.timestamp) == -1) {
+ goto shada_pack_entry_error;
+ }
+ if (sbuf.size > 0) {
+ if ((msgpack_pack_uint64(packer, (uint64_t) sbuf.size) == -1)
+ || (packer->callback(packer->data, sbuf.data,
+ (unsigned) sbuf.size) == -1)) {
+ goto shada_pack_entry_error;
+ }
+ }
+ }
+ msgpack_packer_free(spacker);
+ msgpack_sbuffer_destroy(&sbuf);
+ return true;
+shada_pack_entry_error:
+ msgpack_packer_free(spacker);
+ msgpack_sbuffer_destroy(&sbuf);
+ return false;
+}
+#undef PACK_STRING
+
+/// Write single ShaDa entry, converting it if needed
+///
+/// @warning Frees entry after packing.
+///
+/// @param[in] packer Packer used to write entry.
+/// @param[in] sd_conv Conversion definitions.
+/// @param[in] entry Entry written. If entry.can_free_entry is false then
+/// it assumes that entry was not converted, otherwise it
+/// is assumed that entry was already converted.
+/// @param[in] max_kbyte Maximum size of an item in KiB. Zero means no
+/// restrictions.
+static bool shada_pack_encoded_entry(msgpack_packer *const packer,
+ const vimconv_T *const sd_conv,
+ PossiblyFreedShadaEntry entry,
+ const size_t max_kbyte)
+ FUNC_ATTR_NONNULL_ALL
+{
+ bool ret = true;
+ if (entry.can_free_entry) {
+ ret = shada_pack_entry(packer, entry.data, max_kbyte);
+ shada_free_shada_entry(&entry.data);
+ return ret;
+ }
+#define RUN_WITH_CONVERTED_STRING(cstr, code) \
+ do { \
+ bool did_convert = false; \
+ if (sd_conv->vc_type != CONV_NONE && has_non_ascii((cstr))) { \
+ char *const converted_string = string_convert(sd_conv, (cstr), NULL); \
+ if (converted_string != NULL) { \
+ (cstr) = converted_string; \
+ did_convert = true; \
+ } \
+ } \
+ code \
+ if (did_convert) { \
+ xfree((cstr)); \
+ } \
+ } while (0)
+ switch (entry.data.type) {
+ case kSDItemUnknown:
+ case kSDItemMissing: {
+ assert(false);
+ }
+ case kSDItemSearchPattern: {
+ RUN_WITH_CONVERTED_STRING(entry.data.data.search_pattern.pat, {
+ ret = shada_pack_entry(packer, entry.data, max_kbyte);
+ });
+ break;
+ }
+ case kSDItemHistoryEntry: {
+ RUN_WITH_CONVERTED_STRING(entry.data.data.history_item.string, {
+ ret = shada_pack_entry(packer, entry.data, max_kbyte);
+ });
+ break;
+ }
+ case kSDItemSubString: {
+ RUN_WITH_CONVERTED_STRING(entry.data.data.sub_string.sub, {
+ ret = shada_pack_entry(packer, entry.data, max_kbyte);
+ });
+ break;
+ }
+ case kSDItemVariable: {
+ if (sd_conv->vc_type != CONV_NONE) {
+ typval_T tgttv;
+ var_item_copy(sd_conv, &entry.data.data.global_var.value, &tgttv,
+ true, 0);
+ clear_tv(&entry.data.data.global_var.value);
+ entry.data.data.global_var.value = tgttv;
+ }
+ ret = shada_pack_entry(packer, entry.data, max_kbyte);
+ break;
+ }
+ case kSDItemRegister: {
+ bool did_convert = false;
+ if (sd_conv->vc_type != CONV_NONE) {
+ size_t first_non_ascii = 0;
+ for (size_t i = 0; i < entry.data.data.reg.contents_size; i++) {
+ if (has_non_ascii(entry.data.data.reg.contents[i])) {
+ first_non_ascii = i;
+ did_convert = true;
+ break;
+ }
+ }
+ if (did_convert) {
+ entry.data.data.reg.contents =
+ xmemdup(entry.data.data.reg.contents,
+ (entry.data.data.reg.contents_size
+ * sizeof(entry.data.data.reg.contents)));
+ for (size_t i = 0; i < entry.data.data.reg.contents_size; i++) {
+ if (i >= first_non_ascii) {
+ entry.data.data.reg.contents[i] = get_converted_string(
+ sd_conv,
+ entry.data.data.reg.contents[i],
+ strlen(entry.data.data.reg.contents[i]));
+ } else {
+ entry.data.data.reg.contents[i] =
+ xstrdup(entry.data.data.reg.contents[i]);
+ }
+ }
+ }
+ }
+ ret = shada_pack_entry(packer, entry.data, max_kbyte);
+ if (did_convert) {
+ for (size_t i = 0; i < entry.data.data.reg.contents_size; i++) {
+ xfree(entry.data.data.reg.contents[i]);
+ }
+ xfree(entry.data.data.reg.contents);
+ }
+ break;
+ }
+ case kSDItemHeader:
+ case kSDItemGlobalMark:
+ case kSDItemJump:
+ case kSDItemBufferList:
+ case kSDItemLocalMark:
+ case kSDItemChange: {
+ ret = shada_pack_entry(packer, entry.data, max_kbyte);
+ break;
+ }
+ }
+#undef RUN_WITH_CONVERTED_STRING
+ return ret;
+}
+
+/// Compare two FileMarks structure to order them by greatest_timestamp
+///
+/// Order is reversed: structure with greatest greatest_timestamp comes first.
+/// Function signature is compatible with qsort.
+static int compare_file_marks(const void *a, const void *b)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ const FileMarks *const *const a_fms = a;
+ const FileMarks *const *const b_fms = b;
+ return ((*a_fms)->greatest_timestamp == (*b_fms)->greatest_timestamp
+ ? 0
+ : ((*a_fms)->greatest_timestamp > (*b_fms)->greatest_timestamp
+ ? -1
+ : 1));
+}
+
+/// Parse msgpack object that has given length
+///
+/// @param[in] sd_reader Structure containing file reader definition.
+/// @param[in] length Object length.
+/// @param[out] ret_unpacked Location where read result should be saved. If
+/// NULL then unpacked data will be freed. Must be
+/// NULL if `ret_buf` is NULL.
+/// @param[out] ret_buf Buffer containing parsed string.
+///
+/// @return kSDReadStatusNotShaDa, kSDReadStatusReadError or
+/// kSDReadStatusSuccess.
+static inline ShaDaReadResult shada_parse_msgpack(
+ ShaDaReadDef *const sd_reader, const size_t length,
+ msgpack_unpacked *ret_unpacked, char **const ret_buf)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1)
+{
+ const uintmax_t initial_fpos = sd_reader->fpos;
+ char *const buf = xmalloc(length);
+
+ const ShaDaReadResult fl_ret = fread_len(sd_reader, buf, length);
+ if (fl_ret != kSDReadStatusSuccess) {
+ xfree(buf);
+ return fl_ret;
+ }
+ bool did_try_to_free = false;
+shada_parse_msgpack_read_next: {}
+ size_t off = 0;
+ msgpack_unpacked unpacked;
+ msgpack_unpacked_init(&unpacked);
+ const msgpack_unpack_return result =
+ msgpack_unpack_next(&unpacked, buf, length, &off);
+ ShaDaReadResult ret = kSDReadStatusSuccess;
+ switch (result) {
+ case MSGPACK_UNPACK_SUCCESS: {
+ if (off < length) {
+ goto shada_parse_msgpack_extra_bytes;
+ }
+ break;
+ }
+ case MSGPACK_UNPACK_PARSE_ERROR: {
+ emsgu(_(RCERR "Failed to parse ShaDa file due to a msgpack parser error "
+ "at position %" PRIu64),
+ (uint64_t) initial_fpos);
+ ret = kSDReadStatusNotShaDa;
+ break;
+ }
+ case MSGPACK_UNPACK_NOMEM_ERROR: {
+ if (!did_try_to_free) {
+ did_try_to_free = true;
+ try_to_free_memory();
+ goto shada_parse_msgpack_read_next;
+ }
+ EMSG(_(e_outofmem));
+ ret = kSDReadStatusReadError;
+ break;
+ }
+ case MSGPACK_UNPACK_CONTINUE: {
+ emsgu(_(RCERR "Failed to parse ShaDa file: incomplete msgpack string "
+ "at position %" PRIu64),
+ (uint64_t) initial_fpos);
+ ret = kSDReadStatusNotShaDa;
+ break;
+ }
+ case MSGPACK_UNPACK_EXTRA_BYTES: {
+shada_parse_msgpack_extra_bytes:
+ emsgu(_(RCERR "Failed to parse ShaDa file: extra bytes in msgpack string "
+ "at position %" PRIu64),
+ (uint64_t) initial_fpos);
+ ret = kSDReadStatusNotShaDa;
+ break;
+ }
+ }
+ if (ret_buf != NULL && ret == kSDReadStatusSuccess) {
+ if (ret_unpacked == NULL) {
+ msgpack_unpacked_destroy(&unpacked);
+ } else {
+ *ret_unpacked = unpacked;
+ }
+ *ret_buf = buf;
+ } else {
+ assert(ret_buf == NULL || ret != kSDReadStatusSuccess);
+ msgpack_unpacked_destroy(&unpacked);
+ xfree(buf);
+ }
+ return ret;
+}
+
+/// Read and merge in ShaDa file, used when writing
+///
+/// @param[in] sd_reader Structure containing file reader definition.
+/// @param[in] srni_flags Flags determining what to read.
+/// @param[in] max_kbyte Maximum size of one element.
+/// @param[in,out] ret_wms Location where results are saved.
+/// @param[out] packer MessagePack packer for entries which are not
+/// merged.
+static inline ShaDaWriteResult shada_read_when_writing(
+ ShaDaReadDef *const sd_reader, const unsigned srni_flags,
+ const size_t max_kbyte, WriteMergerState *const wms,
+ msgpack_packer *const packer)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ ShaDaWriteResult ret = kSDWriteSuccessfull;
+ ShadaEntry entry;
+ ShaDaReadResult srni_ret;
+ while ((srni_ret = shada_read_next_item(sd_reader, &entry, srni_flags,
+ max_kbyte))
+ != kSDReadStatusFinished) {
+ switch (srni_ret) {
+ case kSDReadStatusSuccess: {
+ break;
+ }
+ case kSDReadStatusFinished: {
+ // Should be handled by the while condition.
+ assert(false);
+ }
+ case kSDReadStatusNotShaDa: {
+ ret = kSDWriteReadNotShada;
+ // fallthrough
+ }
+ case kSDReadStatusReadError: {
+ return ret;
+ }
+ case kSDReadStatusMalformed: {
+ continue;
+ }
+ }
+#define COMPARE_WITH_ENTRY(wms_entry_, entry) \
+ do { \
+ PossiblyFreedShadaEntry *const wms_entry = (wms_entry_); \
+ if (wms_entry->data.type != kSDItemMissing) { \
+ if (wms_entry->data.timestamp >= (entry).timestamp) { \
+ shada_free_shada_entry(&(entry)); \
+ break; \
+ } \
+ if (wms_entry->can_free_entry) { \
+ shada_free_shada_entry(&wms_entry->data); \
+ } \
+ } \
+ wms_entry->can_free_entry = true; \
+ wms_entry->data = (entry); \
+ } while (0)
+ switch (entry.type) {
+ case kSDItemMissing: {
+ break;
+ }
+ case kSDItemHeader:
+ case kSDItemBufferList: {
+ assert(false);
+ }
+ case kSDItemUnknown: {
+ if (!shada_pack_entry(packer, entry, 0)) {
+ ret = kSDWriteFailed;
+ }
+ shada_free_shada_entry(&entry);
+ break;
+ }
+ case kSDItemSearchPattern: {
+ COMPARE_WITH_ENTRY((entry.data.search_pattern.is_substitute_pattern
+ ? &wms->sub_search_pattern
+ : &wms->search_pattern), entry);
+ break;
+ }
+ case kSDItemSubString: {
+ COMPARE_WITH_ENTRY(&wms->replacement, entry);
+ break;
+ }
+ case kSDItemHistoryEntry: {
+ if (entry.data.history_item.histtype >= HIST_COUNT) {
+ if (!shada_pack_entry(packer, entry, 0)) {
+ ret = kSDWriteFailed;
+ }
+ shada_free_shada_entry(&entry);
+ break;
+ }
+ hms_insert(&wms->hms[entry.data.history_item.histtype], entry, true,
+ true);
+ break;
+ }
+ case kSDItemRegister: {
+ const int idx = op_reg_index(entry.data.reg.name);
+ if (idx < 0) {
+ if (!shada_pack_entry(packer, entry, 0)) {
+ ret = kSDWriteFailed;
+ }
+ shada_free_shada_entry(&entry);
+ break;
+ }
+ COMPARE_WITH_ENTRY(&wms->registers[idx], entry);
+ break;
+ }
+ case kSDItemVariable: {
+ if (!in_strset(&wms->dumped_variables, entry.data.global_var.name)) {
+ if (!shada_pack_entry(packer, entry, 0)) {
+ ret = kSDWriteFailed;
+ }
+ }
+ shada_free_shada_entry(&entry);
+ break;
+ }
+ case kSDItemGlobalMark: {
+ const int idx = mark_global_index(entry.data.filemark.name);
+ if (idx < 0) {
+ if (!shada_pack_entry(packer, entry, 0)) {
+ ret = kSDWriteFailed;
+ }
+ shada_free_shada_entry(&entry);
+ break;
+ }
+ COMPARE_WITH_ENTRY(&wms->global_marks[idx], entry);
+ break;
+ }
+ case kSDItemChange:
+ case kSDItemLocalMark: {
+ if (shada_removable(entry.data.filemark.fname)) {
+ shada_free_shada_entry(&entry);
+ break;
+ }
+ const char *const fname = (const char *) entry.data.filemark.fname;
+ khiter_t k;
+ int kh_ret;
+ k = kh_put(file_marks, &wms->file_marks, fname, &kh_ret);
+ FileMarks *const filemarks = &kh_val(&wms->file_marks, k);
+ if (kh_ret > 0) {
+ memset(filemarks, 0, sizeof(*filemarks));
+ }
+ if (entry.timestamp > filemarks->greatest_timestamp) {
+ filemarks->greatest_timestamp = entry.timestamp;
+ }
+ if (entry.type == kSDItemLocalMark) {
+ const int idx = mark_local_index(entry.data.filemark.name);
+ if (idx < 0) {
+ filemarks->additional_marks = xrealloc(
+ filemarks->additional_marks,
+ (++filemarks->additional_marks_size
+ * sizeof(filemarks->additional_marks[0])));
+ filemarks->additional_marks[filemarks->additional_marks_size - 1] =
+ entry;
+ } else {
+ PossiblyFreedShadaEntry *const wms_entry = &filemarks->marks[idx];
+ if (wms_entry->data.type != kSDItemMissing) {
+ if (wms_entry->data.timestamp >= entry.timestamp) {
+ shada_free_shada_entry(&entry);
+ break;
+ }
+ if (wms_entry->can_free_entry) {
+ if (kh_key(&wms->file_marks, k)
+ == wms_entry->data.data.filemark.fname) {
+ kh_key(&wms->file_marks, k) = entry.data.filemark.fname;
+ }
+ shada_free_shada_entry(&wms_entry->data);
+ }
+ }
+ wms_entry->can_free_entry = true;
+ wms_entry->data = entry;
+ }
+ } else {
+#define FREE_POSSIBLY_FREED_SHADA_ENTRY(entry) \
+ do { \
+ if (entry.can_free_entry) { \
+ shada_free_shada_entry(&entry.data); \
+ } \
+ } while (0)
+#define SDE_TO_PFSDE(entry) \
+ ((PossiblyFreedShadaEntry) { .can_free_entry = true, .data = entry })
+#define AFTERFREE_DUMMY(entry)
+#define DUMMY_IDX_ADJ(i)
+ MERGE_JUMPS(filemarks->changes_size, filemarks->changes,
+ PossiblyFreedShadaEntry, data.timestamp,
+ data.data.filemark.mark, entry, true,
+ FREE_POSSIBLY_FREED_SHADA_ENTRY, SDE_TO_PFSDE,
+ DUMMY_IDX_ADJ, AFTERFREE_DUMMY);
+ }
+ break;
+ }
+ case kSDItemJump: {
+ MERGE_JUMPS(wms->jumps_size, wms->jumps, PossiblyFreedShadaEntry,
+ data.timestamp, data.data.filemark.mark, entry,
+ strcmp(jl_entry.data.data.filemark.fname,
+ entry.data.filemark.fname) == 0,
+ FREE_POSSIBLY_FREED_SHADA_ENTRY, SDE_TO_PFSDE,
+ DUMMY_IDX_ADJ, AFTERFREE_DUMMY);
+#undef FREE_POSSIBLY_FREED_SHADA_ENTRY
+#undef SDE_TO_PFSDE
+#undef DUMMY_IDX_ADJ
+#undef AFTERFREE_DUMMY
+ break;
+ }
+ }
+ }
+#undef COMPARE_WITH_ENTRY
+ return ret;
+}
+
+/// Write ShaDa file
+///
+/// @param[in] sd_writer Structure containing file writer definition.
+/// @param[in] sd_reader Structure containing file reader definition. If it is
+/// not NULL then contents of this file will be merged
+/// with current Neovim runtime.
+static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer,
+ ShaDaReadDef *const sd_reader)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ ShaDaWriteResult ret = kSDWriteSuccessfull;
+ int max_kbyte_i = get_shada_parameter('s');
+ if (max_kbyte_i < 0) {
+ max_kbyte_i = 10;
+ }
+ if (max_kbyte_i == 0) {
+ return ret;
+ }
+
+ WriteMergerState *const wms = xcalloc(1, sizeof(*wms));
+ bool dump_one_history[HIST_COUNT];
+ const bool dump_global_vars = (find_shada_parameter('!') != NULL);
+ int max_reg_lines = get_shada_parameter('<');
+ if (max_reg_lines < 0) {
+ max_reg_lines = get_shada_parameter('"');
+ }
+ const bool limit_reg_lines = max_reg_lines >= 0;
+ const bool dump_registers = (max_reg_lines != 0);
+ khash_t(bufset) removable_bufs = KHASH_EMPTY_TABLE(bufset);
+ const size_t max_kbyte = (size_t) max_kbyte_i;
+ const size_t num_marked_files = (size_t) get_shada_parameter('\'');
+ const bool dump_global_marks = get_shada_parameter('f') != 0;
+ bool dump_history = false;
+
+ // Initialize history merger
+ for (uint8_t i = 0; i < HIST_COUNT; i++) {
+ long num_saved = get_shada_parameter(hist_type2char(i));
+ if (num_saved == -1) {
+ num_saved = p_hi;
+ }
+ if (num_saved > 0) {
+ dump_history = true;
+ dump_one_history[i] = true;
+ hms_init(&wms->hms[i], i, (size_t) num_saved, sd_reader != NULL, false);
+ } else {
+ dump_one_history[i] = false;
+ }
+ }
+
+ const unsigned srni_flags = (unsigned) (
+ kSDReadUndisableableData
+ | kSDReadUnknown
+ | (dump_history ? kSDReadHistory : 0)
+ | (dump_registers ? kSDReadRegisters : 0)
+ | (dump_global_vars ? kSDReadVariables : 0)
+ | (dump_global_marks ? kSDReadGlobalMarks : 0)
+ | (num_marked_files ? kSDReadLocalMarks | kSDReadChanges : 0));
+
+ msgpack_packer *const packer = msgpack_packer_new(sd_writer,
+ &msgpack_sd_writer_write);
+
+ FOR_ALL_BUFFERS(buf) {
+ if (buf->b_ffname != NULL && shada_removable((char *) buf->b_ffname)) {
+ int kh_ret;
+ (void) kh_put(bufset, &removable_bufs, (uintptr_t) buf, &kh_ret);
+ }
+ }
+
+ // Write header
+ if (!shada_pack_entry(packer, (ShadaEntry) {
+ .type = kSDItemHeader,
+ .timestamp = os_time(),
+ .data = {
+ .header = {
+ .size = 5,
+ .capacity = 5,
+ .items = ((KeyValuePair[]) {
+ { STATIC_CSTR_AS_STRING("generator"),
+ STRING_OBJ(STATIC_CSTR_AS_STRING("nvim")) },
+ { 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()) },
+ { STATIC_CSTR_AS_STRING("encoding"),
+ STRING_OBJ(cstr_as_string((char *) p_enc)) },
+ }),
+ }
+ }
+ }, 0)) {
+ ret = kSDWriteFailed;
+ goto shada_write_exit;
+ }
+
+ // Write buffer list
+ if (find_shada_parameter('%') != NULL) {
+ size_t buf_count = 0;
+ FOR_ALL_BUFFERS(buf) {
+ if (buf->b_ffname != NULL && !in_bufset(&removable_bufs, buf)) {
+ buf_count++;
+ }
+ }
+
+ ShadaEntry buflist_entry = (ShadaEntry) {
+ .type = kSDItemBufferList,
+ .timestamp = os_time(),
+ .data = {
+ .buffer_list = {
+ .size = buf_count,
+ .buffers = xmalloc(buf_count
+ * sizeof(*buflist_entry.data.buffer_list.buffers)),
+ },
+ },
+ };
+ size_t i = 0;
+ FOR_ALL_BUFFERS(buf) {
+ if (buf->b_ffname == NULL || in_bufset(&removable_bufs, buf)) {
+ continue;
+ }
+ buflist_entry.data.buffer_list.buffers[i] = (struct buffer_list_buffer) {
+ .pos = buf->b_last_cursor.mark,
+ .fname = (char *) buf->b_ffname,
+ .additional_data = buf->additional_data,
+ };
+ i++;
+ }
+ if (!shada_pack_entry(packer, buflist_entry, 0)) {
+ xfree(buflist_entry.data.buffer_list.buffers);
+ ret = kSDWriteFailed;
+ goto shada_write_exit;
+ }
+ xfree(buflist_entry.data.buffer_list.buffers);
+ }
+
+ // Write some of the variables
+ if (dump_global_vars) {
+ const void *var_iter = NULL;
+ const Timestamp cur_timestamp = os_time();
+ do {
+ typval_T vartv;
+ const char *name = NULL;
+ var_iter = var_shada_iter(var_iter, &name, &vartv);
+ if (name == NULL) {
+ break;
+ }
+ typval_T tgttv;
+ if (sd_writer->sd_conv.vc_type != CONV_NONE) {
+ var_item_copy(&sd_writer->sd_conv, &vartv, &tgttv, true, 0);
+ } else {
+ copy_tv(&vartv, &tgttv);
+ }
+ if (!shada_pack_entry(packer, (ShadaEntry) {
+ .type = kSDItemVariable,
+ .timestamp = cur_timestamp,
+ .data = {
+ .global_var = {
+ .name = (char *) name,
+ .value = tgttv,
+ .additional_elements = NULL,
+ }
+ }
+ }, max_kbyte)) {
+ clear_tv(&vartv);
+ clear_tv(&tgttv);
+ ret = kSDWriteFailed;
+ goto shada_write_exit;
+ }
+ clear_tv(&vartv);
+ clear_tv(&tgttv);
+ int kh_ret;
+ (void) kh_put(strset, &wms->dumped_variables, name, &kh_ret);
+ } while (var_iter != NULL);
+ }
+
+ const bool search_highlighted = !(no_hlsearch
+ || find_shada_parameter('h') != NULL);
+ const bool search_last_used = search_was_last_used();
+#define ADD_SEARCH_PAT(func, wms_attr, hlo, pcae, o, is_sub) \
+ do { \
+ SearchPattern pat; \
+ func(&pat); \
+ if (pat.pat != NULL) { \
+ wms->wms_attr = (PossiblyFreedShadaEntry) { \
+ .can_free_entry = false, \
+ .data = { \
+ .type = kSDItemSearchPattern, \
+ .timestamp = pat.timestamp, \
+ .data = { \
+ .search_pattern = { \
+ .magic = pat.magic, \
+ .smartcase = !pat.no_scs, \
+ .has_line_offset = hlo, \
+ .place_cursor_at_end = pcae, \
+ .offset = o, \
+ .is_last_used = (is_sub ^ search_last_used), \
+ .is_substitute_pattern = is_sub, \
+ .highlighted = ((is_sub ^ search_last_used) \
+ && search_highlighted), \
+ .pat = (char *) pat.pat, \
+ .additional_data = pat.additional_data, \
+ } \
+ } \
+ } \
+ }; \
+ } \
+ } while (0)
+
+ // Initialize search pattern
+ ADD_SEARCH_PAT(get_search_pattern, search_pattern, pat.off.line, \
+ pat.off.end, pat.off.off, false);
+
+ // Initialize substitute search pattern
+ ADD_SEARCH_PAT(get_substitute_pattern, sub_search_pattern, false, false, 0,
+ true);
+#undef ADD_SEARCH_PAT
+
+ // Initialize substitute replacement string
+ {
+ SubReplacementString sub;
+ sub_get_replacement(&sub);
+ wms->replacement = (PossiblyFreedShadaEntry) {
+ .can_free_entry = false,
+ .data = {
+ .type = kSDItemSubString,
+ .timestamp = sub.timestamp,
+ .data = {
+ .sub_string = {
+ .sub = (char *) sub.sub,
+ .additional_elements = sub.additional_elements,
+ }
+ }
+ }
+ };
+ }
+
+ // Initialize jump list
+ const void *jump_iter = NULL;
+ do {
+ xfmark_T fm;
+ cleanup_jumplist();
+ jump_iter = mark_jumplist_iter(jump_iter, curwin, &fm);
+ const buf_T *const buf = (fm.fmark.fnum == 0
+ ? NULL
+ : buflist_findnr(fm.fmark.fnum));
+ if (buf != NULL
+ ? in_bufset(&removable_bufs, buf)
+ : fm.fmark.fnum != 0) {
+ continue;
+ }
+ const char *const fname = (char *) (fm.fmark.fnum == 0
+ ? (fm.fname == NULL ? NULL : fm.fname)
+ : buf->b_ffname);
+ if (fname == NULL) {
+ continue;
+ }
+ wms->jumps[wms->jumps_size++] = (PossiblyFreedShadaEntry) {
+ .can_free_entry = false,
+ .data = {
+ .type = kSDItemJump,
+ .timestamp = fm.fmark.timestamp,
+ .data = {
+ .filemark = {
+ .name = NUL,
+ .mark = fm.fmark.mark,
+ .fname = (char *) fname,
+ .additional_data = fm.fmark.additional_data,
+ }
+ }
+ }
+ };
+ } while (jump_iter != NULL);
+
+ // Initialize global marks
+ if (dump_global_marks) {
+ const void *global_mark_iter = NULL;
+ do {
+ char name = NUL;
+ xfmark_T fm;
+ global_mark_iter = mark_global_iter(global_mark_iter, &name, &fm);
+ if (name == NUL) {
+ break;
+ }
+ const char *fname;
+ if (fm.fmark.fnum == 0) {
+ assert(fm.fname != NULL);
+ if (shada_removable((const char *) fm.fname)) {
+ continue;
+ }
+ fname = (const char *) fm.fname;
+ } else {
+ const buf_T *const buf = buflist_findnr(fm.fmark.fnum);
+ if (buf == NULL || buf->b_ffname == NULL
+ || in_bufset(&removable_bufs, buf)) {
+ continue;
+ }
+ fname = (const char *) buf->b_ffname;
+ }
+ wms->global_marks[mark_global_index(name)] = (PossiblyFreedShadaEntry) {
+ .can_free_entry = false,
+ .data = {
+ .type = kSDItemGlobalMark,
+ .timestamp = fm.fmark.timestamp,
+ .data = {
+ .filemark = {
+ .mark = fm.fmark.mark,
+ .name = name,
+ .additional_data = fm.fmark.additional_data,
+ .fname = (char *) fname,
+ }
+ }
+ },
+ };
+ } while (global_mark_iter != NULL);
+ }
+
+ // Initialize registers
+ if (dump_registers) {
+ const void *reg_iter = NULL;
+ do {
+ yankreg_T reg;
+ char name = NUL;
+ reg_iter = op_register_iter(reg_iter, &name, &reg);
+ if (name == NUL) {
+ break;
+ }
+ if (limit_reg_lines && reg.y_size > max_reg_lines) {
+ continue;
+ }
+ wms->registers[op_reg_index(name)] = (PossiblyFreedShadaEntry) {
+ .can_free_entry = false,
+ .data = {
+ .type = kSDItemRegister,
+ .timestamp = reg.timestamp,
+ .data = {
+ .reg = {
+ .contents = (char **) reg.y_array,
+ .contents_size = (size_t) reg.y_size,
+ .type = (uint8_t) reg.y_type,
+ .width = (size_t) (reg.y_type == MBLOCK ? reg.y_width : 0),
+ .additional_data = reg.additional_data,
+ .name = name,
+ }
+ }
+ }
+ };
+ } while (reg_iter != NULL);
+ }
+
+ // Initialize buffers
+ if (num_marked_files > 0) {
+ FOR_ALL_BUFFERS(buf) {
+ if (buf->b_ffname == NULL || in_bufset(&removable_bufs, buf)) {
+ continue;
+ }
+ const void *local_marks_iter = NULL;
+ const char *const fname = (const char *) buf->b_ffname;
+ khiter_t k;
+ int kh_ret;
+ k = kh_put(file_marks, &wms->file_marks, fname, &kh_ret);
+ FileMarks *const filemarks = &kh_val(&wms->file_marks, k);
+ if (kh_ret > 0) {
+ memset(filemarks, 0, sizeof(*filemarks));
+ }
+ do {
+ fmark_T fm;
+ char name = NUL;
+ local_marks_iter = mark_buffer_iter(local_marks_iter, buf, &name, &fm);
+ if (name == NUL) {
+ break;
+ }
+ filemarks->marks[mark_local_index(name)] = (PossiblyFreedShadaEntry) {
+ .can_free_entry = false,
+ .data = {
+ .type = kSDItemLocalMark,
+ .timestamp = fm.timestamp,
+ .data = {
+ .filemark = {
+ .mark = fm.mark,
+ .name = name,
+ .fname = (char *) fname,
+ .additional_data = fm.additional_data,
+ }
+ }
+ }
+ };
+ if (fm.timestamp > filemarks->greatest_timestamp) {
+ filemarks->greatest_timestamp = fm.timestamp;
+ }
+ } while (local_marks_iter != NULL);
+ for (int i = 0; i < buf->b_changelistlen; i++) {
+ const fmark_T fm = buf->b_changelist[i];
+ filemarks->changes[i] = (PossiblyFreedShadaEntry) {
+ .can_free_entry = false,
+ .data = {
+ .type = kSDItemChange,
+ .timestamp = fm.timestamp,
+ .data = {
+ .filemark = {
+ .mark = fm.mark,
+ .fname = (char *) fname,
+ .additional_data = fm.additional_data,
+ }
+ }
+ }
+ };
+ if (fm.timestamp > filemarks->greatest_timestamp) {
+ filemarks->greatest_timestamp = fm.timestamp;
+ }
+ }
+ filemarks->changes_size = (size_t) buf->b_changelistlen;
+ }
+ }
+
+ if (sd_reader != NULL) {
+ const ShaDaWriteResult srww_ret = shada_read_when_writing(
+ sd_reader, srni_flags, max_kbyte, wms, packer);
+ if (srww_ret != kSDWriteSuccessfull) {
+ ret = srww_ret;
+ }
+ }
+
+ // Write the rest
+#define PACK_WMS_ARRAY(wms_array) \
+ do { \
+ for (size_t i_ = 0; i_ < ARRAY_SIZE(wms_array); i_++) { \
+ if (wms_array[i_].data.type != kSDItemMissing) { \
+ if (!shada_pack_encoded_entry(packer, &sd_writer->sd_conv, \
+ wms_array[i_], \
+ max_kbyte)) { \
+ ret = kSDWriteFailed; \
+ goto shada_write_exit; \
+ } \
+ } \
+ } \
+ } while (0)
+ PACK_WMS_ARRAY(wms->global_marks);
+ PACK_WMS_ARRAY(wms->registers);
+ for (size_t i = 0; i < wms->jumps_size; i++) {
+ if (!shada_pack_encoded_entry(packer, &sd_writer->sd_conv, wms->jumps[i],
+ max_kbyte)) {
+ ret = kSDWriteFailed;
+ goto shada_write_exit;
+ }
+ }
+#define PACK_WMS_ENTRY(wms_entry) \
+ do { \
+ if (wms_entry.data.type != kSDItemMissing) { \
+ if (!shada_pack_encoded_entry(packer, &sd_writer->sd_conv, wms_entry, \
+ max_kbyte)) { \
+ ret = kSDWriteFailed; \
+ goto shada_write_exit; \
+ } \
+ } \
+ } while (0)
+ PACK_WMS_ENTRY(wms->search_pattern);
+ PACK_WMS_ENTRY(wms->sub_search_pattern);
+ PACK_WMS_ENTRY(wms->replacement);
+#undef PACK_WMS_ENTRY
+
+ const size_t file_markss_size = kh_size(&wms->file_marks);
+ FileMarks **const all_file_markss =
+ xmalloc(file_markss_size * sizeof(*all_file_markss));
+ FileMarks **cur_file_marks = all_file_markss;
+ for (khint_t i = kh_begin(&wms->file_marks); i != kh_end(&wms->file_marks);
+ i++) {
+ if (kh_exist(&wms->file_marks, i)) {
+ *cur_file_marks++ = &kh_val(&wms->file_marks, i);
+ }
+ }
+ qsort((void *) all_file_markss, file_markss_size, sizeof(*all_file_markss),
+ &compare_file_marks);
+ const size_t file_markss_to_dump = MIN(num_marked_files, file_markss_size);
+ for (size_t i = 0; i < file_markss_to_dump; i++) {
+ PACK_WMS_ARRAY(all_file_markss[i]->marks);
+ for (size_t j = 0; j < all_file_markss[i]->changes_size; j++) {
+ if (!shada_pack_encoded_entry(packer, &sd_writer->sd_conv,
+ all_file_markss[i]->changes[j],
+ max_kbyte)) {
+ ret = kSDWriteFailed;
+ goto shada_write_exit;
+ }
+ }
+ for (size_t j = 0; j < all_file_markss[i]->additional_marks_size; j++) {
+ if (!shada_pack_entry(packer, all_file_markss[i]->additional_marks[j],
+ 0)) {
+ shada_free_shada_entry(&all_file_markss[i]->additional_marks[j]);
+ ret = kSDWriteFailed;
+ goto shada_write_exit;
+ }
+ shada_free_shada_entry(&all_file_markss[i]->additional_marks[j]);
+ }
+ xfree(all_file_markss[i]->additional_marks);
+ }
+ xfree(all_file_markss);
+#undef PACK_WMS_ARRAY
+
+ if (dump_history) {
+ for (size_t i = 0; i < HIST_COUNT; i++) {
+ if (dump_one_history[i]) {
+ hms_insert_whole_neovim_history(&wms->hms[i]);
+ HMS_ITER(&wms->hms[i], cur_entry) {
+ if (!shada_pack_encoded_entry(
+ packer, &sd_writer->sd_conv, (PossiblyFreedShadaEntry) {
+ .data = cur_entry->data,
+ .can_free_entry = cur_entry->can_free_entry,
+ }, max_kbyte)) {
+ ret = kSDWriteFailed;
+ break;
+ }
+ }
+ hms_dealloc(&wms->hms[i]);
+ if (ret == kSDWriteFailed) {
+ goto shada_write_exit;
+ }
+ }
+ }
+ }
+
+shada_write_exit:
+ kh_dealloc(file_marks, &wms->file_marks);
+ kh_dealloc(bufset, &removable_bufs);
+ msgpack_packer_free(packer);
+ kh_dealloc(strset, &wms->dumped_variables);
+ xfree(wms);
+ return ret;
+}
+
+#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, bool nomerge)
+{
+ if (shada_disabled()) {
+ return FAIL;
+ }
+
+ char *const fname = shada_filename(file);
+ char *tempname = NULL;
+ ShaDaWriteDef sd_writer = (ShaDaWriteDef) {
+ .write = &write_file,
+ .close = &close_sd_writer,
+ .error = NULL,
+ };
+ ShaDaReadDef sd_reader;
+
+ intptr_t fd;
+
+ if (!nomerge) {
+ if (open_shada_file_for_reading(fname, &sd_reader) != 0) {
+ nomerge = true;
+ goto shada_write_file_nomerge;
+ }
+ tempname = modname(fname, ".tmp.a", false);
+ if (tempname == NULL) {
+ nomerge = true;
+ goto shada_write_file_nomerge;
+ }
+
+ // Save permissions from the original file, with modifications:
+ int perm = (int) os_getperm(fname);
+ perm = (perm >= 0) ? ((perm & 0777) | 0600) : 0600;
+ // ^3 ^1 ^2 ^2,3
+ // 1: Strip SUID bit if any.
+ // 2: Make sure that user can always read and write the result.
+ // 3: If somebody happened to delete the file after it was opened for
+ // reading use u=rw permissions.
+shada_write_file_open:
+ fd = (intptr_t) open_file(tempname, O_CREAT|O_WRONLY|O_NOFOLLOW|O_EXCL,
+ perm);
+ if (fd < 0) {
+ if (-fd == EEXIST
+#ifdef ELOOP
+ || -fd == ELOOP
+#endif
+ ) {
+ // File already exists, try another name
+ char *const wp = tempname + strlen(tempname) - 1;
+ if (*wp == 'z') {
+ // Tried names from .tmp.a to .tmp.z, all failed. Something must be
+ // wrong then.
+ EMSG2(_("E138: All %s.tmp.X files exist, cannot write ShaDa file!"),
+ fname);
+ xfree(fname);
+ xfree(tempname);
+ return FAIL;
+ } else {
+ (*wp)++;
+ goto shada_write_file_open;
+ }
+ }
+ }
+ }
+ if (nomerge) {
+shada_write_file_nomerge: {}
+ char *const tail = path_tail_with_sep(fname);
+ if (tail != fname) {
+ const char tail_save = *tail;
+ *tail = NUL;
+ if (!os_isdir(fname)) {
+ int ret;
+ char *failed_dir;
+ if ((ret = os_mkdir_recurse(fname, 0700, &failed_dir)) != 0) {
+ EMSG3(_(SERR "Failed to create directory %s "
+ "for writing ShaDa file: %s"),
+ failed_dir, os_strerror(ret));
+ xfree(fname);
+ xfree(failed_dir);
+ return FAIL;
+ }
+ }
+ *tail = tail_save;
+ }
+ fd = (intptr_t) open_file(fname, O_CREAT|O_WRONLY|O_TRUNC,
+ 0600);
+ }
+
+ if (p_verbose > 0) {
+ verbose_enter();
+ smsg(_("Writing ShaDa file \"%s\""), fname);
+ verbose_leave();
+ }
+
+ if (fd < 0) {
+ xfree(fname);
+ xfree(tempname);
+ return FAIL;
+ }
+
+ sd_writer.cookie = (void *) fd;
+
+ convert_setup(&sd_writer.sd_conv, p_enc, "utf-8");
+
+ const ShaDaWriteResult sw_ret = shada_write(&sd_writer, (nomerge
+ ? NULL
+ : &sd_reader));
+#ifndef UNIX
+ sd_writer.close(&sd_writer);
+#endif
+ if (!nomerge) {
+ sd_reader.close(&sd_reader);
+ bool did_remove = false;
+ if (sw_ret == kSDWriteSuccessfull) {
+#ifdef UNIX
+ bool closed = false;
+ // 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;
+ if (os_fileinfo((char *)fname, &old_info)) {
+ if (getuid() == ROOT_UID) {
+ if (old_info.stat.st_uid != ROOT_UID
+ || old_info.stat.st_gid != getgid()) {
+ const uv_uid_t old_uid = (uv_uid_t) old_info.stat.st_uid;
+ const uv_gid_t old_gid = (uv_gid_t) old_info.stat.st_gid;
+ const int fchown_ret = os_fchown((int) fd, old_uid, old_gid);
+ sd_writer.close(&sd_writer);
+ if (fchown_ret != 0) {
+ EMSG3(_(RNERR "Failed setting uid and gid for file %s: %s"),
+ tempname, os_strerror(fchown_ret));
+ goto shada_write_file_did_not_remove;
+ }
+ closed = true;
+ }
+ } else if (!(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)))) {
+ EMSG2(_("E137: ShaDa file is not writable: %s"), fname);
+ sd_writer.close(&sd_writer);
+ goto shada_write_file_did_not_remove;
+ }
+ }
+ if (!closed) {
+ sd_writer.close(&sd_writer);
+ }
+#endif
+ if (vim_rename(tempname, fname) == -1) {
+ EMSG3(_(RNERR "Can't rename ShaDa file from %s to %s!"),
+ tempname, fname);
+ } else {
+ did_remove = true;
+ os_remove(tempname);
+ }
+ } else {
+ if (sw_ret == kSDWriteReadNotShada) {
+ EMSG3(_(RNERR "Did not rename %s because %s "
+ "does not looks like a ShaDa file"), tempname, fname);
+ } else {
+ EMSG3(_(RNERR "Did not rename %s to %s because there were errors "
+ "during writing it"), tempname, fname);
+ }
+ }
+ if (!did_remove) {
+#ifdef UNIX
+shada_write_file_did_not_remove:
+#endif
+ EMSG3(_(RNERR "Do not forget to remove %s or rename it manually to %s."),
+ tempname, fname);
+ }
+ xfree(tempname);
+ }
+
+ xfree(fname);
+ 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
+/// @param[in] forceit If true, use forced reading (prioritize file contents
+/// over current Neovim state).
+/// @param[in] missing_ok If true, do not error out when file is missing.
+///
+/// @return OK in case of success, FAIL otherwise.
+int shada_read_everything(const char *const fname, const bool forceit,
+ const bool missing_ok)
+{
+ return shada_read_file(fname,
+ kShaDaWantInfo|kShaDaWantMarks|kShaDaGetOldfiles
+ |(forceit?kShaDaForceit:0)
+ |(missing_ok?0:kShaDaMissingError));
+}
+
+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 kSDItemChange:
+ case kSDItemJump:
+ case kSDItemGlobalMark:
+ case kSDItemLocalMark: {
+ dict_unref(entry->data.filemark.additional_data);
+ xfree(entry->data.filemark.fname);
+ break;
+ }
+ case kSDItemSearchPattern: {
+ dict_unref(entry->data.search_pattern.additional_data);
+ xfree(entry->data.search_pattern.pat);
+ break;
+ }
+ case kSDItemRegister: {
+ dict_unref(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: {
+ list_unref(entry->data.history_item.additional_elements);
+ xfree(entry->data.history_item.string);
+ break;
+ }
+ case kSDItemVariable: {
+ list_unref(entry->data.global_var.additional_elements);
+ xfree(entry->data.global_var.name);
+ clear_tv(&entry->data.global_var.value);
+ break;
+ }
+ case kSDItemSubString: {
+ list_unref(entry->data.sub_string.additional_elements);
+ xfree(entry->data.sub_string.sub);
+ break;
+ }
+ case kSDItemBufferList: {
+ for (size_t i = 0; i < entry->data.buffer_list.size; i++) {
+ xfree(entry->data.buffer_list.buffers[i].fname);
+ dict_unref(entry->data.buffer_list.buffers[i].additional_data);
+ }
+ xfree(entry->data.buffer_list.buffers);
+ break;
+ }
+ }
+}
+
+#ifndef HAVE_BE64TOH
+static inline uint64_t be64toh(uint64_t big_endian_64_bits)
+{
+#ifdef ORDER_BIG_ENDIAN
+ return big_endian_64_bits;
+#else
+ // It may appear that when !defined(ORDER_BIG_ENDIAN) actual order is big
+ // endian. This variant is suboptimal, but it works regardless of actual
+ // order.
+ uint8_t *buf = (uint8_t *) &big_endian_64_bits;
+ uint64_t ret = 0;
+ for (size_t i = 8; i; i--) {
+ ret |= ((uint64_t) buf[i - 1]) << ((8 - i) * 8);
+ }
+ return ret;
+#endif
+}
+#endif
+
+/// Read given number of bytes into given buffer, display error if needed
+///
+/// @param[in] sd_reader Structure containing file reader definition.
+/// @param[out] buffer Where to save the results.
+/// @param[in] length How many bytes should be read.
+///
+/// @return kSDReadStatusSuccess if everything was OK, kSDReadStatusNotShaDa if
+/// there were not enough bytes to read or kSDReadStatusReadError if
+/// there was some error while reading.
+static ShaDaReadResult fread_len(ShaDaReadDef *const sd_reader,
+ char *const buffer,
+ const size_t length)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ const ptrdiff_t read_bytes = sd_reader->read(sd_reader, buffer, length);
+ (void) read_bytes;
+
+ if (sd_reader->error != NULL) {
+ emsg2(_(SERR "System error while reading ShaDa file: %s"),
+ sd_reader->error);
+ return kSDReadStatusReadError;
+ } else if (sd_reader->eof) {
+ emsgu(_(RCERR "Error while reading ShaDa file: "
+ "last entry specified that it occupies %" PRIu64 " bytes, "
+ "but file ended earlier"),
+ (uint64_t) length);
+ return kSDReadStatusNotShaDa;
+ }
+ assert(read_bytes >= 0 && (size_t) read_bytes == length);
+ return kSDReadStatusSuccess;
+}
+
+/// 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] sd_reader Structure containing file reader definition.
+/// @param[out] result Location where result is saved.
+///
+/// @return kSDReadStatusSuccess if reading was successfull,
+/// kSDReadStatusNotShaDa if there were not enough bytes to read or
+/// kSDReadStatusReadError if reading failed for whatever reason.
+static ShaDaReadResult msgpack_read_uint64(ShaDaReadDef *const sd_reader,
+ const int first_char,
+ uint64_t *const result)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ const uintmax_t fpos = sd_reader->fpos - 1;
+
+ if (first_char == EOF) {
+ if (sd_reader->error) {
+ emsg2(_(SERR "System error while reading integer from ShaDa file: %s"),
+ sd_reader->error);
+ return kSDReadStatusReadError;
+ } else if (sd_reader->eof) {
+ emsgu(_(RCERR "Error while reading ShaDa file: "
+ "expected positive integer at position %" PRIu64
+ ", but got nothing"),
+ (uint64_t) fpos);
+ return kSDReadStatusNotShaDa;
+ }
+ }
+
+ 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: {
+ emsgu(_(RCERR "Error while reading ShaDa file: "
+ "expected positive integer at position %" PRIu64),
+ (uint64_t) fpos);
+ return kSDReadStatusNotShaDa;
+ }
+ }
+ uint64_t buf = 0;
+ char *buf_u8 = (char *) &buf;
+ ShaDaReadResult fl_ret;
+ if ((fl_ret = fread_len(sd_reader, &(buf_u8[sizeof(buf)-length]), length))
+ != kSDReadStatusSuccess) {
+ return fl_ret;
+ }
+ *result = be64toh(buf);
+ }
+ return kSDReadStatusSuccess;
+}
+
+/// Convert or copy and return a string
+///
+/// @param[in] sd_conv Conversion definition.
+/// @param[in] str String to convert.
+/// @param[in] len String length.
+///
+/// @return [allocated] converted string or copy of the original string.
+static inline char *get_converted_string(const vimconv_T *const sd_conv,
+ const char *const str,
+ const size_t len)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (!has_non_ascii_len(str, len)) {
+ return xmemdupz(str, len);
+ }
+ size_t new_len = len;
+ char *const new_str = string_convert(sd_conv, str, &new_len);
+ if (new_str == NULL) {
+ return xmemdupz(str, len);
+ }
+ return new_str;
+}
+
+#define READERR(entry_name, error_desc) \
+ RERR "Error while reading ShaDa file: " \
+ entry_name " entry at position %" PRIu64 " " \
+ error_desc
+#define CHECK_KEY(key, expected) ( \
+ key.via.str.size == sizeof(expected) - 1 \
+ && STRNCMP(key.via.str.ptr, expected, sizeof(expected) - 1) == 0)
+#define CLEAR_GA_AND_ERROR_OUT(ga) \
+ do { \
+ ga_clear(&ga); \
+ goto shada_read_next_item_error; \
+ } while (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)) { \
+ emsgu(_(READERR(entry_name, error_desc)), initial_fpos); \
+ CLEAR_GA_AND_ERROR_OUT(ad_ga); \
+ } \
+ 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) { \
+ emsgu(_(READERR(entry_name, "has key which is not a string")), \
+ initial_fpos); \
+ CLEAR_GA_AND_ERROR_OUT(ad_ga); \
+ } else if (unpacked.data.via.map.ptr[i].key.via.str.size == 0) { \
+ emsgu(_(READERR(entry_name, "has empty key")), initial_fpos); \
+ CLEAR_GA_AND_ERROR_OUT(ad_ga); \
+ } \
+ } 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 CONVERTED_STRING_KEY(entry_name, name, tgt) \
+ TYPED_KEY(entry_name, name, "a binary", tgt, BIN, bin, BIN_CONVERTED)
+#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++; \
+ }
+#define CONVERTED(str, len) ( \
+ sd_reader->sd_conv.vc_type != CONV_NONE \
+ ? get_converted_string(&sd_reader->sd_conv, (str), (len)) \
+ : xmemdupz((str), (len)))
+#define BIN_CONVERTED(b) CONVERTED(b.ptr, b.size)
+#define SET_ADDITIONAL_DATA(tgt, name) \
+ do { \
+ 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, \
+ } \
+ } \
+ }; \
+ typval_T adtv; \
+ if (msgpack_to_vim(obj, &adtv) == FAIL \
+ || adtv.v_type != VAR_DICT) { \
+ emsgu(_(READERR(name, \
+ "cannot be converted to a VimL dictionary")), \
+ initial_fpos); \
+ ga_clear(&ad_ga); \
+ clear_tv(&adtv); \
+ goto shada_read_next_item_error; \
+ } \
+ tgt = adtv.vval.v_dict; \
+ } \
+ ga_clear(&ad_ga); \
+ } while (0)
+#define SET_ADDITIONAL_ELEMENTS(src, src_maxsize, tgt, name) \
+ do { \
+ if ((src).size > (size_t) (src_maxsize)) { \
+ msgpack_object obj = { \
+ .type = MSGPACK_OBJECT_ARRAY, \
+ .via = { \
+ .array = { \
+ .size = ((src).size - (uint32_t) (src_maxsize)), \
+ .ptr = (src).ptr + (src_maxsize), \
+ } \
+ } \
+ }; \
+ typval_T aetv; \
+ if (msgpack_to_vim(obj, &aetv) == FAIL) { \
+ emsgu(_(READERR(name, "cannot be converted to a VimL list")), \
+ initial_fpos); \
+ clear_tv(&aetv); \
+ goto shada_read_next_item_error; \
+ } \
+ assert(aetv.v_type == VAR_LIST); \
+ (tgt) = aetv.vval.v_list; \
+ } \
+ } while (0)
+
+/// Iterate over shada file contents
+///
+/// @param[in] sd_reader Structure containing file reader definition.
+/// @param[out] entry Address where next entry contents will be saved.
+/// @param[in] flags Flags, determining whether and which items should be
+/// skipped (see SRNIFlags enum).
+/// @param[in] max_kbyte If non-zero, skip reading entries which have length
+/// greater then given.
+///
+/// @return Any value from ShaDaReadResult enum.
+static ShaDaReadResult shada_read_next_item(ShaDaReadDef *const sd_reader,
+ ShadaEntry *const entry,
+ const unsigned flags,
+ const size_t max_kbyte)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ ShaDaReadResult ret = kSDReadStatusMalformed;
+shada_read_next_item_start:
+ // Set entry type to kSDItemMissing and also make sure that all pointers in
+ // data union are NULL so they are safe to xfree(). This is needed in case
+ // somebody calls goto shada_read_next_item_error before anything is set in
+ // the switch.
+ memset(entry, 0, sizeof(*entry));
+ if (sd_reader->eof) {
+ return kSDReadStatusFinished;
+ }
+
+ // 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) kSDItemMissing;
+ uint64_t timestamp_u64;
+ uint64_t length_u64;
+
+ const uint64_t initial_fpos = (uint64_t) sd_reader->fpos;
+ const int first_char = read_char(sd_reader);
+ if (first_char == EOF && sd_reader->eof) {
+ return kSDReadStatusFinished;
+ }
+
+ ShaDaReadResult mru_ret;
+ if (((mru_ret = msgpack_read_uint64(sd_reader, first_char, &type_u64))
+ != kSDReadStatusSuccess)
+ || ((mru_ret = msgpack_read_uint64(sd_reader, read_char(sd_reader),
+ &timestamp_u64))
+ != kSDReadStatusSuccess)
+ || ((mru_ret = msgpack_read_uint64(sd_reader, read_char(sd_reader),
+ &length_u64))
+ != kSDReadStatusSuccess)) {
+ return mru_ret;
+ }
+
+ const size_t length = (size_t) length_u64;
+ entry->timestamp = (Timestamp) timestamp_u64;
+
+ if (type_u64 == 0) {
+ // kSDItemUnknown cannot possibly pass that far because it is -1 and that
+ // will fail in msgpack_read_uint64. But kSDItemMissing may and it will
+ // otherwise be skipped because (1 << 0) will never appear in flags.
+ emsgu(_(RCERR "Error while reading ShaDa file: "
+ "there is an item at position %" PRIu64 " "
+ "that must not be there: Missing items are "
+ "for internal uses only"),
+ initial_fpos);
+ return kSDReadStatusNotShaDa;
+ }
+
+ if ((type_u64 > SHADA_LAST_ENTRY
+ ? !(flags & kSDReadUnknown)
+ : !((unsigned) (1 << type_u64) & flags))
+ || (max_kbyte && length > max_kbyte * 1024)) {
+ // First entry is unknown or equal to "\n" (10)? Most likely this means that
+ // current file is not a ShaDa file because first item should normally be
+ // a header (excluding tests where first item is tested item). Check this by
+ // parsing entry contents: in non-ShaDa files this will most likely result
+ // in incomplete MessagePack string.
+ if (initial_fpos == 0
+ && (type_u64 == '\n' || type_u64 > SHADA_LAST_ENTRY)) {
+ const ShaDaReadResult spm_ret = shada_parse_msgpack(sd_reader, length,
+ NULL, NULL);
+ if (spm_ret != kSDReadStatusSuccess) {
+ return spm_ret;
+ }
+ } else {
+ const ShaDaReadResult srs_ret = sd_reader_skip(sd_reader, length);
+ if (srs_ret != kSDReadStatusSuccess) {
+ return srs_ret;
+ }
+ }
+ goto shada_read_next_item_start;
+ }
+
+ if (type_u64 > SHADA_LAST_ENTRY) {
+ entry->type = kSDItemUnknown;
+ entry->data.unknown_item.size = length;
+ entry->data.unknown_item.type = type_u64;
+ if (initial_fpos == 0) {
+ const ShaDaReadResult spm_ret = shada_parse_msgpack(
+ sd_reader, length, NULL, &entry->data.unknown_item.contents);
+ if (spm_ret != kSDReadStatusSuccess) {
+ entry->type = kSDItemMissing;
+ }
+ return spm_ret;
+ } else {
+ entry->data.unknown_item.contents = xmalloc(length);
+ const ShaDaReadResult fl_ret = fread_len(
+ sd_reader, entry->data.unknown_item.contents, length);
+ if (fl_ret != kSDReadStatusSuccess) {
+ shada_free_shada_entry(entry);
+ entry->type = kSDItemMissing;
+ }
+ return fl_ret;
+ }
+ }
+
+ msgpack_unpacked unpacked;
+ char *buf = NULL;
+
+ const ShaDaReadResult spm_ret = shada_parse_msgpack(sd_reader, length,
+ &unpacked, &buf);
+ if (spm_ret != kSDReadStatusSuccess) {
+ ret = spm_ret;
+ goto shada_read_next_item_error;
+ }
+ ret = kSDReadStatusMalformed;
+ entry->data = sd_default_values[type_u64].data;
+ switch ((ShadaEntryType) type_u64) {
+ case kSDItemHeader: {
+ if (!msgpack_rpc_to_dictionary(&(unpacked.data), &(entry->data.header))) {
+ emsgu(_(READERR("header", "is not a dictionary")), initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ break;
+ }
+ case kSDItemSearchPattern: {
+ if (unpacked.data.type != MSGPACK_OBJECT_MAP) {
+ emsgu(_(READERR("search pattern", "is not a dictionary")),
+ initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ 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", SEARCH_KEY_MAGIC,
+ entry->data.search_pattern.magic)
+ else
+ BOOLEAN_KEY("search pattern", SEARCH_KEY_SMARTCASE,
+ entry->data.search_pattern.smartcase)
+ else
+ BOOLEAN_KEY("search pattern", SEARCH_KEY_HAS_LINE_OFFSET,
+ entry->data.search_pattern.has_line_offset)
+ else
+ BOOLEAN_KEY("search pattern", SEARCH_KEY_PLACE_CURSOR_AT_END,
+ entry->data.search_pattern.place_cursor_at_end)
+ else
+ BOOLEAN_KEY("search pattern", SEARCH_KEY_IS_LAST_USED,
+ entry->data.search_pattern.is_last_used)
+ else
+ BOOLEAN_KEY("search pattern", SEARCH_KEY_IS_SUBSTITUTE_PATTERN,
+ entry->data.search_pattern.is_substitute_pattern)
+ else
+ BOOLEAN_KEY("search pattern", SEARCH_KEY_HIGHLIGHTED,
+ entry->data.search_pattern.highlighted)
+ else
+ INTEGER_KEY("search pattern", SEARCH_KEY_OFFSET,
+ entry->data.search_pattern.offset)
+ else
+ CONVERTED_STRING_KEY("search pattern", SEARCH_KEY_PAT,
+ entry->data.search_pattern.pat)
+ else
+ ADDITIONAL_KEY
+ }
+ if (entry->data.search_pattern.pat == NULL) {
+ emsgu(_(READERR("search pattern", "has no pattern")), initial_fpos);
+ CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ }
+ SET_ADDITIONAL_DATA(entry->data.search_pattern.additional_data,
+ "search pattern");
+ break;
+ }
+ case kSDItemChange:
+ case kSDItemJump:
+ case kSDItemGlobalMark:
+ case kSDItemLocalMark: {
+ if (unpacked.data.type != MSGPACK_OBJECT_MAP) {
+ emsgu(_(READERR("mark", "is not a dictionary")), initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ 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");
+ if (CHECK_KEY(unpacked.data.via.map.ptr[i].key, KEY_NAME_CHAR)) {
+ if (type_u64 == kSDItemJump || type_u64 == kSDItemChange) {
+ emsgu(_(READERR("mark", "has n key which is only valid for "
+ "local and global mark entries")), initial_fpos);
+ CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ }
+ CHECKED_ENTRY(
+ (unpacked.data.via.map.ptr[i].val.type
+ == MSGPACK_OBJECT_POSITIVE_INTEGER),
+ "has n key value which is not an unsigned integer",
+ "mark", unpacked.data.via.map.ptr[i].val,
+ entry->data.filemark.name, u64, TOCHAR);
+ } else {
+ LONG_KEY("mark", KEY_LNUM, entry->data.filemark.mark.lnum)
+ else
+ INTEGER_KEY("mark", KEY_COL, entry->data.filemark.mark.col)
+ else
+ STRING_KEY("mark", KEY_FILE, entry->data.filemark.fname)
+ else
+ ADDITIONAL_KEY
+ }
+ }
+ if (entry->data.filemark.fname == NULL) {
+ emsgu(_(READERR("mark", "is missing file name")), initial_fpos);
+ CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ }
+ if (entry->data.filemark.mark.lnum <= 0) {
+ emsgu(_(READERR("mark", "has invalid line number")), initial_fpos);
+ CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ }
+ if (entry->data.filemark.mark.col < 0) {
+ emsgu(_(READERR("mark", "has invalid column number")), initial_fpos);
+ CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ }
+ SET_ADDITIONAL_DATA(entry->data.filemark.additional_data, "mark");
+ break;
+ }
+ case kSDItemRegister: {
+ if (unpacked.data.type != MSGPACK_OBJECT_MAP) {
+ emsgu(_(READERR("register", "is not a dictionary")), initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ 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", REG_KEY_TYPE, "an unsigned integer",
+ entry->data.reg.type, POSITIVE_INTEGER, u64, TOU8)
+ else
+ TYPED_KEY("register", KEY_NAME_CHAR, "an unsigned integer",
+ entry->data.reg.name, POSITIVE_INTEGER, u64, TOCHAR)
+ else
+ TYPED_KEY("register", REG_KEY_WIDTH, "an unsigned integer",
+ entry->data.reg.width, POSITIVE_INTEGER, u64, TOSIZE)
+ else
+ if (CHECK_KEY(unpacked.data.via.map.ptr[i].key,
+ REG_KEY_CONTENTS)) {
+ if (unpacked.data.via.map.ptr[i].val.type != MSGPACK_OBJECT_ARRAY) {
+ emsgu(_(READERR(
+ "register",
+ "has " REG_KEY_CONTENTS " key with non-array value")),
+ initial_fpos);
+ CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ }
+ if (unpacked.data.via.map.ptr[i].val.via.array.size == 0) {
+ emsgu(_(READERR("register",
+ "has " REG_KEY_CONTENTS " key with empty array")),
+ initial_fpos);
+ CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ }
+ 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) {
+ emsgu(_(READERR("register", "has " REG_KEY_CONTENTS " array "
+ "with non-binary value")), initial_fpos);
+ CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ }
+ }
+ 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] = BIN_CONVERTED(arr.ptr[i].via.bin);
+ }
+ } else {
+ ADDITIONAL_KEY
+ }
+ }
+ if (entry->data.reg.contents == NULL) {
+ emsgu(_(READERR("register", "has missing " REG_KEY_CONTENTS " array")),
+ initial_fpos);
+ CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ }
+ SET_ADDITIONAL_DATA(entry->data.reg.additional_data, "register");
+ break;
+ }
+ case kSDItemHistoryEntry: {
+ if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) {
+ emsgu(_(READERR("history", "is not an array")), initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ if (unpacked.data.via.array.size < 2) {
+ emsgu(_(READERR("history", "does not have enough elements")),
+ initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ if (unpacked.data.via.array.ptr[0].type
+ != MSGPACK_OBJECT_POSITIVE_INTEGER) {
+ emsgu(_(READERR("history", "has wrong history type type")),
+ initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ if (unpacked.data.via.array.ptr[1].type
+ != MSGPACK_OBJECT_BIN) {
+ emsgu(_(READERR("history", "has wrong history string type")),
+ initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ if (memchr(unpacked.data.via.array.ptr[1].via.bin.ptr, 0,
+ unpacked.data.via.array.ptr[1].via.bin.size) != NULL) {
+ emsgu(_(READERR("history", "contains string with zero byte inside")),
+ initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ entry->data.history_item.histtype =
+ (uint8_t) unpacked.data.via.array.ptr[0].via.u64;
+ const bool is_hist_search =
+ entry->data.history_item.histtype == HIST_SEARCH;
+ if (is_hist_search) {
+ if (unpacked.data.via.array.size < 3) {
+ emsgu(_(READERR("search history",
+ "does not have separator character")), initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ if (unpacked.data.via.array.ptr[2].type
+ != MSGPACK_OBJECT_POSITIVE_INTEGER) {
+ emsgu(_(READERR("search history",
+ "has wrong history separator type")), initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ entry->data.history_item.sep =
+ (char) unpacked.data.via.array.ptr[2].via.u64;
+ }
+ size_t strsize;
+ if (sd_reader->sd_conv.vc_type == CONV_NONE
+ || !has_non_ascii_len(unpacked.data.via.array.ptr[1].via.bin.ptr,
+ unpacked.data.via.array.ptr[1].via.bin.size)) {
+shada_read_next_item_hist_no_conv:
+ strsize = (
+ unpacked.data.via.array.ptr[1].via.bin.size
+ + 1 // Zero byte
+ + 1); // Separator character
+ entry->data.history_item.string = xmalloc(strsize);
+ memcpy(entry->data.history_item.string,
+ unpacked.data.via.array.ptr[1].via.bin.ptr,
+ unpacked.data.via.array.ptr[1].via.bin.size);
+ } else {
+ size_t len = unpacked.data.via.array.ptr[1].via.bin.size;
+ char *const converted = string_convert(
+ &sd_reader->sd_conv, unpacked.data.via.array.ptr[1].via.bin.ptr,
+ &len);
+ if (converted != NULL) {
+ strsize = len + 2;
+ entry->data.history_item.string = xrealloc(converted, strsize);
+ } else {
+ goto shada_read_next_item_hist_no_conv;
+ }
+ }
+ entry->data.history_item.string[strsize - 2] = 0;
+ entry->data.history_item.string[strsize - 1] =
+ entry->data.history_item.sep;
+ SET_ADDITIONAL_ELEMENTS(unpacked.data.via.array, (2 + is_hist_search),
+ entry->data.history_item.additional_elements,
+ "history");
+ break;
+ }
+ case kSDItemVariable: {
+ if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) {
+ emsgu(_(READERR("variable", "is not an array")), initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ if (unpacked.data.via.array.size < 2) {
+ emsgu(_(READERR("variable", "does not have enough elements")),
+ initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ if (unpacked.data.via.array.ptr[0].type != MSGPACK_OBJECT_BIN) {
+ emsgu(_(READERR("variable", "has wrong variable name type")),
+ initial_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) {
+ emsgu(_(READERR("variable", "has wrong variable value type")),
+ initial_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_to_vim(unpacked.data.via.array.ptr[1],
+ &(entry->data.global_var.value)) == FAIL) {
+ emsgu(_(READERR("variable", "has value that cannot "
+ "be converted to the VimL value")), initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ if (sd_reader->sd_conv.vc_type != CONV_NONE) {
+ typval_T tgttv;
+ var_item_copy(&sd_reader->sd_conv,
+ &entry->data.global_var.value,
+ &tgttv,
+ true,
+ 0);
+ clear_tv(&entry->data.global_var.value);
+ entry->data.global_var.value = tgttv;
+ }
+ SET_ADDITIONAL_ELEMENTS(unpacked.data.via.array, 2,
+ entry->data.global_var.additional_elements,
+ "variable");
+ break;
+ }
+ case kSDItemSubString: {
+ if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) {
+ emsgu(_(READERR("sub string", "is not an array")), initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ if (unpacked.data.via.array.size < 1) {
+ emsgu(_(READERR("sub string", "does not have enough elements")),
+ initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ if (unpacked.data.via.array.ptr[0].type != MSGPACK_OBJECT_BIN) {
+ emsgu(_(READERR("sub string", "has wrong sub string type")),
+ initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ entry->data.sub_string.sub =
+ BIN_CONVERTED(unpacked.data.via.array.ptr[0].via.bin);
+ SET_ADDITIONAL_ELEMENTS(unpacked.data.via.array, 1,
+ entry->data.sub_string.additional_elements,
+ "sub string");
+ break;
+ }
+ case kSDItemBufferList: {
+ if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) {
+ emsgu(_(READERR("buffer list", "is not an array")), initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ if (unpacked.data.via.array.size == 0) {
+ break;
+ }
+ entry->data.buffer_list.buffers =
+ xcalloc(unpacked.data.via.array.size,
+ sizeof(*entry->data.buffer_list.buffers));
+ for (size_t i = 0; i < unpacked.data.via.array.size; i++) {
+ entry->data.buffer_list.size++;
+ msgpack_unpacked unpacked_2 = (msgpack_unpacked) {
+ .data = unpacked.data.via.array.ptr[i],
+ };
+ {
+ msgpack_unpacked unpacked = unpacked_2;
+ if (unpacked.data.type != MSGPACK_OBJECT_MAP) {
+ emsgu(_(RERR "Error while reading ShaDa file: "
+ "buffer list at position %" PRIu64 " "
+ "contains entry that is not a dictionary"),
+ initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ entry->data.buffer_list.buffers[i].pos = default_pos;
+ garray_T ad_ga;
+ ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1);
+ {
+ const size_t j = i;
+ {
+ for (size_t i = 0; i < unpacked.data.via.map.size; i++) {
+ CHECK_KEY_IS_STR("buffer list entry");
+ LONG_KEY("buffer list entry", KEY_LNUM,
+ entry->data.buffer_list.buffers[j].pos.lnum)
+ else
+ INTEGER_KEY("buffer list entry", KEY_COL,
+ entry->data.buffer_list.buffers[j].pos.col)
+ else
+ STRING_KEY("buffer list entry", KEY_FILE,
+ entry->data.buffer_list.buffers[j].fname)
+ else
+ ADDITIONAL_KEY
+ }
+ }
+ }
+ if (entry->data.buffer_list.buffers[i].pos.lnum <= 0) {
+ emsgu(_(RERR "Error while reading ShaDa file: "
+ "buffer list at position %" PRIu64 " "
+ "contains entry with invalid line number"),
+ initial_fpos);
+ CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ }
+ if (entry->data.buffer_list.buffers[i].pos.col < 0) {
+ emsgu(_(RERR "Error while reading ShaDa file: "
+ "buffer list at position %" PRIu64 " "
+ "contains entry with invalid column number"),
+ initial_fpos);
+ CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ }
+ if (entry->data.buffer_list.buffers[i].fname == NULL) {
+ emsgu(_(RERR "Error while reading ShaDa file: "
+ "buffer list at position %" PRIu64 " "
+ "contains entry that does not have a file name"),
+ initial_fpos);
+ CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ }
+ SET_ADDITIONAL_DATA(
+ entry->data.buffer_list.buffers[i].additional_data,
+ "buffer list entry");
+ }
+ }
+ break;
+ }
+ case kSDItemMissing:
+ case kSDItemUnknown: {
+ assert(false);
+ }
+ }
+ entry->type = (ShadaEntryType) type_u64;
+ ret = kSDReadStatusSuccess;
+shada_read_next_item_end:
+ if (buf != NULL) {
+ msgpack_unpacked_destroy(&unpacked);
+ xfree(buf);
+ }
+ return ret;
+shada_read_next_item_error:
+ entry->type = (ShadaEntryType) type_u64;
+ shada_free_shada_entry(entry);
+ entry->type = kSDItemMissing;
+ goto shada_read_next_item_end;
+}
+#undef BIN_CONVERTED
+#undef CONVERTED
+#undef CHECK_KEY
+#undef BOOLEAN_KEY
+#undef CONVERTED_STRING_KEY
+#undef STRING_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
+#undef SET_ADDITIONAL_DATA
+#undef SET_ADDITIONAL_ELEMENTS
+#undef CLEAR_GA_AND_ERROR_OUT
+
+/// Check whether "name" is on removable media (according to 'shada')
+///
+/// @param[in] name Checked name.
+///
+/// @return True if it is, false otherwise.
+static bool shada_removable(const char *name)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ char *p;
+ char part[MAXPATHL + 1];
+ bool retval = false;
+
+ char *new_name = home_replace_save(NULL, name);
+ for (p = (char *) p_shada; *p; ) {
+ (void) copy_option_part(&p, part, ARRAY_SIZE(part), ", ");
+ if (part[0] == 'r') {
+ home_replace(NULL, part + 1, NameBuff, MAXPATHL, true);
+ size_t n = STRLEN(NameBuff);
+ if (mb_strnicmp(NameBuff, new_name, n) == 0) {
+ retval = true;
+ break;
+ }
+ }
+ }
+ xfree(new_name);
+ return retval;
+}
diff --git a/src/nvim/shada.h b/src/nvim/shada.h
new file mode 100644
index 0000000000..49986ac1c1
--- /dev/null
+++ b/src/nvim/shada.h
@@ -0,0 +1,7 @@
+#ifndef NVIM_SHADA_H
+#define NVIM_SHADA_H
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "shada.h.generated.h"
+#endif
+#endif // NVIM_SHADA_H
diff --git a/src/nvim/strings.c b/src/nvim/strings.c
index b876753d57..9ffa5c6a76 100644
--- a/src/nvim/strings.c
+++ b/src/nvim/strings.c
@@ -483,6 +483,21 @@ bool has_non_ascii(const char_u *s)
return false;
}
+/// Return true if string "s" contains a non-ASCII character (128 or higher).
+/// When "s" is NULL false is returned.
+bool has_non_ascii_len(const char *const s, const size_t len)
+ FUNC_ATTR_PURE
+{
+ if (s != NULL) {
+ for (size_t i = 0; i < len; i++) {
+ if ((uint8_t) s[i] >= 128) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
/*
* Concatenate two strings and return the result in allocated memory.
*/
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/testdir/test8.in b/src/nvim/testdir/test8.in
index 0f27c813ec..41e6262e92 100644
--- a/src/nvim/testdir/test8.in
+++ b/src/nvim/testdir/test8.in
@@ -32,7 +32,7 @@ endfunc
$put ='VimLeave done'
write
endfunc
-:set viminfo='100
+:set shada='100
:au BufUnload * call CloseAll()
:au VimLeave * call WriteToOut()
:e small.vim
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index 063c13084d..2b0ffefa7e 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -325,6 +325,14 @@ 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++) {
+ dict_unref(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 +475,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 +795,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];
@@ -831,8 +841,11 @@ static u_header_T *unserialize_uhp(bufinfo_T *bi, char_u *file_name)
unserialize_pos(bi, &uhp->uh_cursor);
uhp->uh_cursor_vcol = undo_read_4c(bi);
uhp->uh_flags = undo_read_2c(bi);
+ const Timestamp cur_timestamp = os_time();
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 = cur_timestamp;
+ uhp->uh_namedm[i].fnum = 0;
}
unserialize_visualinfo(bi, &uhp->uh_visual);
uhp->uh_time = undo_read_time(bi);
@@ -2009,7 +2022,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 +2042,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 +2172,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);