From c71dca0cbd5043f449a74e98eca8906e5d85451e Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 17 Jul 2015 17:54:44 +0300 Subject: os/fs: Define os_strerror as an alias to uv_strerror It is not needed to know that os/* uses libuv. --- src/nvim/os/fs_defs.h | 5 +++++ 1 file changed, 5 insertions(+) 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 -- cgit From 0fdaab995ed95250b13058a717d5bc562e1834c8 Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 2 Jul 2015 20:15:41 +0300 Subject: mark: Fix valgrind error in mark.c Caused by using memcpy for assigning one structure to another. --- src/nvim/mark.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/nvim/mark.c b/src/nvim/mark.c index ce44149ffa..6ac4b1de9c 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -1117,10 +1117,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; -- cgit From 244dbe3a77bf548f73d8781da7327f30e818b08a Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 25 Apr 2015 18:47:31 +0300 Subject: viminfo: First version of ShaDa file dumping What works: 1. ShaDa file dumping: header, registers, jump list, history, search patterns, substitute strings, variables. 2. ShaDa file reading: registers, global marks, variables. Most was not tested. TODO: 1. Merging. 2. Reading history, local marks, jump and buffer lists. 3. Documentation update. 4. Converting some data from &encoding. 5. Safer variant of dumping viminfo (dump to temporary file then rename). 6. Removing old viminfo code (currently masked with `#if 0` in a ShaDa file for reference). --- contrib/YouCompleteMe/ycm_extra_conf.py | 2 +- runtime/doc/options.txt | 15 +- scripts/shadacat.py | 73 + src/nvim/api/private/helpers.h | 2 + src/nvim/auevents.lua | 2 +- src/nvim/buffer.c | 94 +- src/nvim/buffer_defs.h | 19 +- src/nvim/edit.c | 6 +- src/nvim/eval.c | 143 +- src/nvim/ex_cmds.c | 609 +---- src/nvim/ex_cmds.h | 14 +- src/nvim/ex_docmd.c | 11 +- src/nvim/ex_getln.c | 387 +--- src/nvim/ex_getln.h | 9 + src/nvim/fileio.c | 8 +- src/nvim/globals.h | 2 +- src/nvim/main.c | 23 +- src/nvim/mark.c | 710 +++--- src/nvim/mark.h | 16 + src/nvim/mark_defs.h | 32 +- src/nvim/mbyte.c | 2 +- src/nvim/misc1.c | 5 +- src/nvim/normal.c | 4 +- src/nvim/ops.c | 315 ++- src/nvim/ops.h | 20 +- src/nvim/os/time.c | 9 + src/nvim/os/time.h | 2 + src/nvim/os/unix_defs.h | 4 +- src/nvim/os/win_defs.h | 2 +- src/nvim/search.c | 194 +- src/nvim/search.h | 24 + src/nvim/shada.c | 3454 +++++++++++++++++++++++++++++ src/nvim/shada.h | 18 + src/nvim/tag.c | 2 +- src/nvim/undo.c | 30 +- src/nvim/undo_defs.h | 3 +- src/nvim/window.c | 2 +- test/functional/ex_getln/history_spec.lua | 40 + 38 files changed, 4485 insertions(+), 1822 deletions(-) create mode 100755 scripts/shadacat.py create mode 100644 src/nvim/shada.c create mode 100644 src/nvim/shada.h create mode 100644 test/functional/ex_getln/history_spec.lua diff --git a/contrib/YouCompleteMe/ycm_extra_conf.py b/contrib/YouCompleteMe/ycm_extra_conf.py index 7c54677c8f..12ad080143 100644 --- a/contrib/YouCompleteMe/ycm_extra_conf.py +++ b/contrib/YouCompleteMe/ycm_extra_conf.py @@ -9,7 +9,7 @@ def DirectoryOfThisScript(): def GetDatabase(): compilation_database_folder = os.path.join(DirectoryOfThisScript(), - '..', 'build') + '..', '..', 'build') if os.path.exists(compilation_database_folder): return ycm_core.CompilationDatabase(compilation_database_folder) return None diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 897137ff42..085f6f21a8 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -6770,10 +6770,14 @@ A jump table for the options with a short description can be found at |Q_op|. e.g., for Unix: "r/tmp". Case is ignored. Maximum length of each 'r' argument is 50 characters. *viminfo-s* - s Maximum size of an item in Kbyte. If zero then registers are - not saved. Currently only applies to registers. The default - "s10" will exclude registers with more than 10 Kbyte of text. - Also see the '<' item above: line count limit. + s Maximum size of an item contents in KiB. If zero then nothing + is saved. Unlike Vim this applies to all items, except for + the buffer list and header. Full item size is off by three + unsigned integers: with `s10` maximum item size may be 1 byte + (type: 7-bit integer) + 9 bytes (timestamp: up to 64-bit + integer) + 3 bytes (item size: up to 16-bit integer because + 2^8 < 10240 < 2^16) + 10240 bytes (requested maximum item + contents size) = 10253 bytes. Example: > :set viminfo='50,<1000,s100,:0,n~/vim/viminfo @@ -6782,7 +6786,8 @@ A jump table for the options with a short description can be found at |Q_op|. edited. <1000 Contents of registers (up to 1000 lines each) will be remembered. - s100 Registers with more than 100 Kbyte text are skipped. + s100 Items with contents occupying more then 100 KiB are + skipped. :0 Command-line history will not be saved. n~/vim/viminfo The name of the file to use is "~/vim/viminfo". no / Since '/' is not specified, the default will be used, diff --git a/scripts/shadacat.py b/scripts/shadacat.py new file mode 100755 index 0000000000..8f5ed276f8 --- /dev/null +++ b/scripts/shadacat.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3.4 + +import sys +import codecs + +from enum import Enum +from datetime import datetime +from functools import reduce + +import msgpack + + +class EntryTypes(Enum): + Unknown = -1 + Missing = 0 + Header = 1 + SearchPattern = 2 + SubString = 3 + HistoryEntry = 4 + Register = 5 + Variable = 6 + GlobalMark = 7 + Jump = 8 + BufferList = 9 + LocalMark = 10 + + +def strtrans_errors(e): + if not isinstance(e, UnicodeDecodeError): + raise NotImplementedError('don’t know how to handle {0} error'.format( + e.__class__.__name__)) + return '<{0:x}>'.format(reduce((lambda a, b: a*0x100+b), + list(e.object[e.start:e.end]))), e.end + + +codecs.register_error('strtrans', strtrans_errors) + + +def idfunc(o): + return o + + +class CharInt(int): + def __repr__(self): + return super(CharInt, self).__repr__() + ' (\'%s\')' % chr(self) + + +ctable = { + bytes: lambda s: s.decode('utf-8', 'strtrans'), + dict: lambda d: dict((mnormalize(k), mnormalize(v)) for k, v in d.items()), + list: lambda l: list(mnormalize(i) for i in l), + int: lambda n: CharInt(n) if 0x20 <= n <= 0x7E else n, +} + + +def mnormalize(o): + return ctable.get(type(o), idfunc)(o) + + +with open(sys.argv[1], 'rb') as fp: + unpacker = msgpack.Unpacker(file_like=fp) + while True: + try: + typ = EntryTypes(unpacker.unpack()) + except msgpack.OutOfData: + break + else: + timestamp = unpacker.unpack() + time = datetime.fromtimestamp(timestamp) + length = unpacker.unpack() + entry = unpacker.unpack() + print('{0:13} {1} {2:5} {3!r}'.format( + typ.name, time.isoformat(), length, mnormalize(entry))) diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index f29deb53f9..a0f14ac7a4 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -69,6 +69,8 @@ #define ADD(array, item) \ kv_push(Object, array, item) +#define STATIC_CSTR_AS_STRING(s) ((String) {.data = s, .size = sizeof(s) - 1}) + // Helpers used by the generated msgpack-rpc api wrappers #define api_init_boolean #define api_init_integer diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index af801c6f1a..3d8a75febd 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -84,7 +84,7 @@ return { 'User', -- user defined autocommand 'VimEnter', -- after starting Vim 'VimLeave', -- before exiting Vim - 'VimLeavePre', -- before exiting Vim and writing .viminfo + 'VimLeavePre', -- before exiting Vim and writing ShaDa file 'VimResized', -- after Vim window was resized 'WinEnter', -- after entering a window 'WinLeave', -- before leaving a window diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index b212e75283..5ebeec1b70 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -73,6 +73,7 @@ #include "nvim/undo.h" #include "nvim/version.h" #include "nvim/window.h" +#include "nvim/shada.h" #include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/os/input.h" @@ -555,6 +556,12 @@ static void free_buffer(buf_T *buf) free_buffer_stuff(buf, TRUE); unref_var_dict(buf->b_vars); aubuflocal_remove(buf); + free_fmark(buf->b_last_cursor); + free_fmark(buf->b_last_insert); + free_fmark(buf->b_last_change); + for (size_t i = 0; i < NMARKS; i++) { + free_fmark(buf->b_namedm[i]); + } if (autocmd_busy) { // Do not free the buffer structure while autocommands are executing, // it's still needed. Free it when autocmd_busy is reset. @@ -4164,93 +4171,6 @@ chk_modeline ( return retval; } -int read_viminfo_bufferlist(vir_T *virp, int writing) -{ - char_u *tab; - linenr_T lnum; - colnr_T col; - buf_T *buf; - char_u *sfname; - char_u *xline; - - /* Handle long line and escaped characters. */ - xline = viminfo_readstring(virp, 1, FALSE); - - /* don't read in if there are files on the command-line or if writing: */ - if (xline != NULL && !writing && ARGCOUNT == 0 - && find_viminfo_parameter('%') != NULL) { - /* Format is: Tab Tab . - * Watch out for a Tab in the file name, work from the end. */ - lnum = 0; - col = 0; - tab = vim_strrchr(xline, '\t'); - if (tab != NULL) { - *tab++ = '\0'; - col = (colnr_T)atoi((char *)tab); - tab = vim_strrchr(xline, '\t'); - if (tab != NULL) { - *tab++ = '\0'; - lnum = atol((char *)tab); - } - } - - /* Expand "~/" in the file name at "line + 1" to a full path. - * Then try shortening it by comparing with the current directory */ - expand_env(xline, NameBuff, MAXPATHL); - sfname = path_shorten_fname_if_possible(NameBuff); - - buf = buflist_new(NameBuff, sfname, (linenr_T)0, BLN_LISTED); - if (buf != NULL) { /* just in case... */ - buf->b_last_cursor.lnum = lnum; - buf->b_last_cursor.col = col; - buflist_setfpos(buf, curwin, lnum, col, FALSE); - } - } - xfree(xline); - - return viminfo_readline(virp); -} - -void write_viminfo_bufferlist(FILE *fp) -{ - char_u *line; - int max_buffers; - - if (find_viminfo_parameter('%') == NULL) - return; - - /* Without a number -1 is returned: do all buffers. */ - max_buffers = get_viminfo_parameter('%'); - - /* Allocate room for the file name, lnum and col. */ -#define LINE_BUF_LEN (MAXPATHL + 40) - line = xmalloc(LINE_BUF_LEN); - - FOR_ALL_TAB_WINDOWS(tp, win) { - set_last_cursor(win); - } - - fputs(_("\n# Buffer list:\n"), fp); - FOR_ALL_BUFFERS(buf) { - if (buf->b_fname == NULL - || !buf->b_p_bl - || bt_quickfix(buf) - || removable(buf->b_ffname)) - continue; - - if (max_buffers-- == 0) - break; - putc('%', fp); - home_replace(NULL, buf->b_ffname, line, MAXPATHL, TRUE); - vim_snprintf_add((char *)line, LINE_BUF_LEN, "\t%" PRId64 "\t%d", - (int64_t)buf->b_last_cursor.lnum, - buf->b_last_cursor.col); - viminfo_writestring(fp, line); - } - xfree(line); -} - - /* * Return special buffer name. * Returns NULL when the buffer has a normal file name. diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 98fbef9c87..6fbdea2c62 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -327,15 +327,6 @@ typedef struct { bool vc_fail; /* fail for invalid char, don't use '?' */ } vimconv_T; -/* - * Structure used for reading from the viminfo file. - */ -typedef struct { - char_u *vir_line; /* text of the current line */ - FILE *vir_fd; /* file descriptor */ - vimconv_T vir_conv; /* encoding conversion */ -} vir_T; - #define CONV_NONE 0 #define CONV_TO_UTF8 1 #define CONV_9_TO_UTF8 2 @@ -515,16 +506,16 @@ struct file_buffer { uint64_t b_orig_size; /* size of original file in bytes */ int b_orig_mode; /* mode of original file */ - pos_T b_namedm[NMARKS]; /* current named marks (mark.c) */ + fmark_T b_namedm[NMARKS]; /* current named marks (mark.c) */ /* These variables are set when VIsual_active becomes FALSE */ visualinfo_T b_visual; int b_visual_mode_eval; /* b_visual.vi_mode for visualmode() */ - pos_T b_last_cursor; /* cursor position when last unloading this + fmark_T b_last_cursor; /* cursor position when last unloading this buffer */ - pos_T b_last_insert; /* where Insert mode was left */ - pos_T b_last_change; /* position of last change: '. mark */ + fmark_T b_last_insert; /* where Insert mode was left */ + fmark_T b_last_change; /* position of last change: '. mark */ /* * the changelist contains old change positions @@ -553,7 +544,7 @@ struct file_buffer { pos_T b_op_start_orig; // used for Insstart_orig pos_T b_op_end; - bool b_marks_read; /* Have we read viminfo marks yet? */ + bool b_marks_read; /* Have we read ShaDa marks yet? */ /* * The following only used in undo.c. diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 427623e052..310191ba06 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -60,6 +60,7 @@ #include "nvim/undo.h" #include "nvim/window.h" #include "nvim/event/loop.h" +#include "nvim/mark.h" #include "nvim/os/input.h" #include "nvim/os/time.h" @@ -6991,8 +6992,9 @@ ins_esc ( curwin->w_set_curswant = TRUE; /* Remember the last Insert position in the '^ mark. */ - if (!cmdmod.keepjumps) - curbuf->b_last_insert = curwin->w_cursor; + if (!cmdmod.keepjumps) { + RESET_FMARK(&curbuf->b_last_insert, curwin->w_cursor, curbuf->b_fnum); + } /* * The cursor should end up on the last inserted character. diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 65afd19bbe..5a7d4702d2 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -354,7 +354,7 @@ typedef struct { typedef enum { VAR_FLAVOUR_DEFAULT, /* doesn't start with uppercase */ VAR_FLAVOUR_SESSION, /* starts with uppercase, some lower */ - VAR_FLAVOUR_VIMINFO /* all uppercase */ + VAR_FLAVOUR_SHADA /* all uppercase */ } var_flavour_T; /* values for vv_flags: */ @@ -10482,6 +10482,7 @@ static void f_has(typval_T *argvars, typval_T *rettv) "scrollbind", "showcmd", "cmdline_info", + "shada", "signs", "smartindent", "startuptime", @@ -10498,7 +10499,6 @@ static void f_has(typval_T *argvars, typval_T *rettv) "title", "user-commands", /* was accidentally included in 5.4 */ "user_commands", - "viminfo", "vertsplit", "virtualedit", "visual", @@ -20712,107 +20712,62 @@ static var_flavour_T var_flavour(char_u *varname) while (*(++p)) if (ASCII_ISLOWER(*p)) return VAR_FLAVOUR_SESSION; - return VAR_FLAVOUR_VIMINFO; + return VAR_FLAVOUR_SHADA; } else return VAR_FLAVOUR_DEFAULT; } -/* - * Restore global vars that start with a capital from the viminfo file - */ -int read_viminfo_varlist(vir_T *virp, int writing) -{ - char_u *tab; - int type = VAR_NUMBER; - typval_T tv; - - if (!writing && (find_viminfo_parameter('!') != NULL)) { - tab = vim_strchr(virp->vir_line + 1, '\t'); - if (tab != NULL) { - *tab++ = NUL; /* isolate the variable name */ - switch (*tab) { - case 'S': type = VAR_STRING; break; - case 'F': type = VAR_FLOAT; break; - case 'D': type = VAR_DICT; break; - case 'L': type = VAR_LIST; break; - } - - tab = vim_strchr(tab, '\t'); - if (tab != NULL) { - tv.v_type = type; - if (type == VAR_STRING || type == VAR_DICT || type == VAR_LIST) - tv.vval.v_string = viminfo_readstring(virp, - (int)(tab - virp->vir_line + 1), TRUE); - else if (type == VAR_FLOAT) - (void)string2float(tab + 1, &tv.vval.v_float); - else - tv.vval.v_number = atol((char *)tab + 1); - if (type == VAR_DICT || type == VAR_LIST) { - typval_T *etv = eval_expr(tv.vval.v_string, NULL); - - if (etv == NULL) - /* Failed to parse back the dict or list, use it as a - * string. */ - tv.v_type = VAR_STRING; - else { - xfree(tv.vval.v_string); - tv = *etv; - xfree(etv); - } - } - - set_var(virp->vir_line + 1, &tv, FALSE); - - if (tv.v_type == VAR_STRING) - xfree(tv.vval.v_string); - else if (tv.v_type == VAR_DICT || tv.v_type == VAR_LIST) - clear_tv(&tv); - } +/// Iterate over global variables +/// +/// @warning No modifications to global variable dictionary must be performed +/// while iteration is in progress. +/// +/// @param[in] iter Iterator. Pass NULL to start iteration. +/// @param[out] name Variable name. +/// @param[out] rettv Variable value. +/// +/// @return Pointer that needs to be passed to next `var_shada_iter` invocation +/// or NULL to indicate that iteration is over. +const void *var_shada_iter(const void *const iter, const char **const name, + typval_T *rettv) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(2, 3) +{ + const hashitem_T *hi; + const hashitem_T *hifirst = globvarht.ht_array; + const size_t hinum = (size_t) globvarht.ht_mask + 1; + *name = NULL; + if (iter == NULL) { + hi = globvarht.ht_array; + while ((HASHITEM_EMPTY(hi) + || var_flavour(HI2DI(hi)->di_key) != VAR_FLAVOUR_SHADA) + && (size_t) (hi - hifirst) < hinum) { + hi++; + } + if (HASHITEM_EMPTY(hi) + || var_flavour(HI2DI(hi)->di_key) != VAR_FLAVOUR_SHADA) { + *rettv = (typval_T) {.v_type = VAR_UNKNOWN}; + return NULL; } + } else { + hi = (const hashitem_T *) iter; } - - return viminfo_readline(virp); + *name = (char *) HI2DI(hi)->di_key; + copy_tv(&(HI2DI(hi)->di_tv), rettv); + while ((size_t) (++hi - hifirst) < hinum) { + if (!HASHITEM_EMPTY(hi) + && var_flavour(HI2DI(hi)->di_key) == VAR_FLAVOUR_SHADA) { + return hi; + } + } + return NULL; } -/* - * Write global vars that start with a capital to the viminfo file - */ -void write_viminfo_varlist(FILE *fp) +void var_set_global(const char *const name, typval_T vartv) { - hashitem_T *hi; - dictitem_T *this_var; - int todo; - char *s; - char_u *p; - - if (find_viminfo_parameter('!') == NULL) - return; - - fputs(_("\n# global variables:\n"), fp); - - todo = (int)globvarht.ht_used; - for (hi = globvarht.ht_array; todo > 0; ++hi) { - if (!HASHITEM_EMPTY(hi)) { - --todo; - this_var = HI2DI(hi); - if (var_flavour(this_var->di_key) == VAR_FLAVOUR_VIMINFO) { - switch (this_var->di_tv.v_type) { - case VAR_STRING: s = "STR"; break; - case VAR_NUMBER: s = "NUM"; break; - case VAR_FLOAT: s = "FLO"; break; - case VAR_DICT: s = "DIC"; break; - case VAR_LIST: s = "LIS"; break; - default: continue; - } - fprintf(fp, "!%s\t%s\t", this_var->di_key, s); - p = (char_u *) echo_string(&this_var->di_tv, NULL); - if (p != NULL) { - viminfo_writestring(fp, p); - } - xfree(p); - } - } - } + funccall_T *const saved_current_funccal = current_funccal; + current_funccal = NULL; + set_var((char_u *) name, &vartv, false); + current_funccal = saved_current_funccal; } int store_session_globals(FILE *fd) diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 81abf2fa63..262150a8c0 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -67,6 +67,9 @@ #include "nvim/os/os.h" #include "nvim/os/shell.h" #include "nvim/os/input.h" +#include "nvim/os/time.h" +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" /* * Struct to hold the sign properties. @@ -1391,550 +1394,6 @@ void append_redir(char_u *buf, int buflen, char_u *opt, char_u *fname) (char *)opt, (char *)fname); } - -static int viminfo_errcnt; - -static int no_viminfo(void) -{ - /* "vim -i NONE" does not read or write a viminfo file */ - return use_viminfo != NULL && STRCMP(use_viminfo, "NONE") == 0; -} - -/* - * Report an error for reading a viminfo file. - * Count the number of errors. When there are more than 10, return TRUE. - */ -int viminfo_error(char *errnum, char *message, char_u *line) -{ - vim_snprintf((char *)IObuff, IOSIZE, _("%sviminfo: %s in line: "), - errnum, message); - STRNCAT(IObuff, line, IOSIZE - STRLEN(IObuff) - 1); - if (IObuff[STRLEN(IObuff) - 1] == '\n') - IObuff[STRLEN(IObuff) - 1] = NUL; - emsg(IObuff); - if (++viminfo_errcnt >= 10) { - EMSG(_("E136: viminfo: Too many errors, skipping rest of file")); - return TRUE; - } - return FALSE; -} - -/* - * read_viminfo() -- Read the viminfo file. Registers etc. which are already - * set are not over-written unless "flags" includes VIF_FORCEIT. -- webb - */ -int -read_viminfo ( - char_u *file, /* file name or NULL to use default name */ - int flags /* VIF_WANT_INFO et al. */ -) -{ - FILE *fp; - char_u *fname; - - if (no_viminfo()) - return FAIL; - - fname = viminfo_filename(file); /* get file name in allocated buffer */ - fp = mch_fopen((char *)fname, READBIN); - - if (p_verbose > 0) { - verbose_enter(); - smsg(_("Reading viminfo file \"%s\"%s%s%s"), - fname, - (flags & VIF_WANT_INFO) ? _(" info") : "", - (flags & VIF_WANT_MARKS) ? _(" marks") : "", - (flags & VIF_GET_OLDFILES) ? _(" oldfiles") : "", - fp == NULL ? _(" FAILED") : ""); - verbose_leave(); - } - - xfree(fname); - if (fp == NULL) - return FAIL; - - viminfo_errcnt = 0; - do_viminfo(fp, NULL, flags); - - fclose(fp); - return OK; -} - -/* - * Write the viminfo file. The old one is read in first so that effectively a - * merge of current info and old info is done. This allows multiple vims to - * run simultaneously, without losing any marks etc. - * If "forceit" is TRUE, then the old file is not read in, and only internal - * info is written to the file. - */ -void write_viminfo(char_u *file, int forceit) -{ - char_u *fname; - FILE *fp_in = NULL; /* input viminfo file, if any */ - FILE *fp_out = NULL; /* output viminfo file */ - char_u *tempname = NULL; /* name of temp viminfo file */ - char_u *wp; -#if defined(UNIX) - mode_t umask_save; -#endif - - if (no_viminfo()) - return; - - fname = viminfo_filename(file); /* may set to default if NULL */ - - fp_in = mch_fopen((char *)fname, READBIN); - if (fp_in == NULL) { - /* if it does exist, but we can't read it, don't try writing */ - if (os_file_exists(fname)) - goto end; -#if defined(UNIX) - /* - * For Unix we create the .viminfo non-accessible for others, - * because it may contain text from non-accessible documents. - */ - umask_save = umask(077); -#endif - fp_out = mch_fopen((char *)fname, WRITEBIN); -#if defined(UNIX) - (void)umask(umask_save); -#endif - } else { - /* - * There is an existing viminfo file. Create a temporary file to - * write the new viminfo into, in the same directory as the - * existing viminfo file, which will be renamed later. - */ -#ifdef UNIX - /* - * For Unix we check the owner of the file. It's not very nice to - * overwrite a user's viminfo file after a "su root", with a - * viminfo file that the user can't read. - */ - - FileInfo old_info; // FileInfo of existing viminfo file - if (os_fileinfo((char *)fname, &old_info) - && getuid() != ROOT_UID - && !(old_info.stat.st_uid == getuid() - ? (old_info.stat.st_mode & 0200) - : (old_info.stat.st_gid == getgid() - ? (old_info.stat.st_mode & 0020) - : (old_info.stat.st_mode & 0002)))) { - int tt = msg_didany; - - /* avoid a wait_return for this message, it's annoying */ - EMSG2(_("E137: Viminfo file is not writable: %s"), fname); - msg_didany = tt; - fclose(fp_in); - goto end; - } -#endif - - // Make tempname - tempname = (char_u *)modname((char *)fname, ".tmp", FALSE); - if (tempname != NULL) { - /* - * Check if tempfile already exists. Never overwrite an - * existing file! - */ - if (os_file_exists(tempname)) { - /* - * Try another name. Change one character, just before - * the extension. - */ - wp = tempname + STRLEN(tempname) - 5; - if (wp < path_tail(tempname)) /* empty file name? */ - wp = path_tail(tempname); - for (*wp = 'z'; os_file_exists(tempname); --*wp) { - /* - * They all exist? Must be something wrong! Don't - * write the viminfo file then. - */ - if (*wp == 'a') { - xfree(tempname); - tempname = NULL; - break; - } - } - } - } - - if (tempname != NULL) { - int fd; - - /* Use os_open() to be able to use O_NOFOLLOW and set file - * protection: - * Unix: same as original file, but strip s-bit. Reset umask to - * avoid it getting in the way. - * Others: r&w for user only. */ -# ifdef UNIX - umask_save = umask(0); - fd = os_open((char *)tempname, - O_CREAT|O_EXCL|O_WRONLY|O_NOFOLLOW, - (int)((old_info.stat.st_mode & 0777) | 0600)); - (void)umask(umask_save); -# else - fd = os_open((char *)tempname, - O_CREAT|O_EXCL|O_WRONLY|O_NOFOLLOW, 0600); -# endif - if (fd < 0) - fp_out = NULL; - else - fp_out = fdopen(fd, WRITEBIN); - - /* - * If we can't create in the same directory, try creating a - * "normal" temp file. - */ - if (fp_out == NULL) { - xfree(tempname); - if ((tempname = vim_tempname()) != NULL) - fp_out = mch_fopen((char *)tempname, WRITEBIN); - } - -#ifdef UNIX - /* - * Make sure the owner can read/write it. This only works for - * root. - */ - if (fp_out != NULL) { - os_fchown(fileno(fp_out), old_info.stat.st_uid, old_info.stat.st_gid); - } -#endif - } - } - - /* - * Check if the new viminfo file can be written to. - */ - if (fp_out == NULL) { - EMSG2(_("E138: Can't write viminfo file %s!"), - (fp_in == NULL || tempname == NULL) ? fname : tempname); - if (fp_in != NULL) - fclose(fp_in); - goto end; - } - - if (p_verbose > 0) { - verbose_enter(); - smsg(_("Writing viminfo file \"%s\""), fname); - verbose_leave(); - } - - viminfo_errcnt = 0; - do_viminfo(fp_in, fp_out, forceit ? 0 : (VIF_WANT_INFO | VIF_WANT_MARKS)); - - fclose(fp_out); /* errors are ignored !? */ - if (fp_in != NULL) { - fclose(fp_in); - - /* In case of an error keep the original viminfo file. Otherwise - * rename the newly written file. Give an error if that fails. */ - if (viminfo_errcnt == 0 && vim_rename(tempname, fname) == -1) { - viminfo_errcnt++; - EMSG2(_("E886: Can't rename viminfo file to %s!"), fname); - } - if (viminfo_errcnt > 0) { - os_remove((char *)tempname); - } - } - -end: - xfree(fname); - xfree(tempname); -} - -/* - * Get the viminfo file name to use. - * If "file" is given and not empty, use it (has already been expanded by - * cmdline functions). - * Otherwise use "-i file_name", value from 'viminfo' or the default, and - * expand environment variables. - * Returns an allocated string. - */ -static char_u *viminfo_filename(char_u *file) -{ - if (file == NULL || *file == NUL) { - if (use_viminfo != NULL) - file = use_viminfo; - else if ((file = find_viminfo_parameter('n')) == NULL || *file == NUL) { -#ifdef VIMINFO_FILE2 - // don't use $HOME when not defined (turned into "c:/"!). - if (!os_env_exists("HOME")) { - // don't use $VIM when not available. - expand_env((char_u *)"$VIM", NameBuff, MAXPATHL); - if (STRCMP("$VIM", NameBuff) != 0) /* $VIM was expanded */ - file = (char_u *)VIMINFO_FILE2; - else - file = (char_u *)VIMINFO_FILE; - } else -#endif - file = (char_u *)VIMINFO_FILE; - } - expand_env(file, NameBuff, MAXPATHL); - file = NameBuff; - } - return vim_strsave(file); -} - -/* - * do_viminfo() -- Should only be called from read_viminfo() & write_viminfo(). - */ -static void do_viminfo(FILE *fp_in, FILE *fp_out, int flags) -{ - int count = 0; - int eof = FALSE; - vir_T vir; - int merge = FALSE; - - vir.vir_line = xmalloc(LSIZE); - vir.vir_fd = fp_in; - vir.vir_conv.vc_type = CONV_NONE; - - if (fp_in != NULL) { - if (flags & VIF_WANT_INFO) { - eof = read_viminfo_up_to_marks(&vir, - flags & VIF_FORCEIT, fp_out != NULL); - merge = TRUE; - } else if (flags != 0) - /* Skip info, find start of marks */ - while (!(eof = viminfo_readline(&vir)) - && vir.vir_line[0] != '>') - ; - } - if (fp_out != NULL) { - /* Write the info: */ - fprintf(fp_out, _("# This viminfo file was generated by Nvim %s.\n"), - mediumVersion); - fputs(_("# You may edit it if you're careful!\n\n"), fp_out); - fputs(_("# Value of 'encoding' when this file was written\n"), fp_out); - fprintf(fp_out, "*encoding=%s\n\n", p_enc); - write_viminfo_search_pattern(fp_out); - write_viminfo_sub_string(fp_out); - write_viminfo_history(fp_out, merge); - write_viminfo_registers(fp_out); - write_viminfo_varlist(fp_out); - write_viminfo_filemarks(fp_out); - write_viminfo_bufferlist(fp_out); - count = write_viminfo_marks(fp_out); - } - if (fp_in != NULL - && (flags & (VIF_WANT_MARKS | VIF_GET_OLDFILES | VIF_FORCEIT))) - copy_viminfo_marks(&vir, fp_out, count, eof, flags); - - xfree(vir.vir_line); - if (vir.vir_conv.vc_type != CONV_NONE) - convert_setup(&vir.vir_conv, NULL, NULL); -} - -/* - * read_viminfo_up_to_marks() -- Only called from do_viminfo(). Reads in the - * first part of the viminfo file which contains everything but the marks that - * are local to a file. Returns TRUE when end-of-file is reached. -- webb - */ -static int read_viminfo_up_to_marks(vir_T *virp, int forceit, int writing) -{ - int eof; - - prepare_viminfo_history(forceit ? 9999 : 0, writing); - eof = viminfo_readline(virp); - while (!eof && virp->vir_line[0] != '>') { - switch (virp->vir_line[0]) { - /* Characters reserved for future expansion, ignored now */ - case '+': /* "+40 /path/dir file", for running vim without args */ - case '|': /* to be defined */ - case '^': /* to be defined */ - case '<': /* long line - ignored */ - /* A comment or empty line. */ - case NUL: - case '\r': - case '\n': - case '#': - eof = viminfo_readline(virp); - break; - case '*': /* "*encoding=value" */ - eof = viminfo_encoding(virp); - break; - case '!': /* global variable */ - eof = read_viminfo_varlist(virp, writing); - break; - case '%': /* entry for buffer list */ - eof = read_viminfo_bufferlist(virp, writing); - break; - case '"': - eof = read_viminfo_register(virp, forceit); - break; - case '/': /* Search string */ - case '&': /* Substitute search string */ - case '~': /* Last search string, followed by '/' or '&' */ - eof = read_viminfo_search_pattern(virp, forceit); - break; - case '$': - eof = read_viminfo_sub_string(virp, forceit); - break; - case ':': - case '?': - case '=': - case '@': - eof = read_viminfo_history(virp, writing); - break; - case '-': - case '\'': - eof = read_viminfo_filemark(virp, forceit); - break; - default: - if (viminfo_error("E575: ", _("Illegal starting char"), - virp->vir_line)) - eof = TRUE; - else - eof = viminfo_readline(virp); - break; - } - } - - /* Finish reading history items. */ - if (!writing) - finish_viminfo_history(); - - /* Change file names to buffer numbers for fmarks. */ - FOR_ALL_BUFFERS(buf) { - fmarks_check_names(buf); - } - - return eof; -} - -/* - * Compare the 'encoding' value in the viminfo file with the current value of - * 'encoding'. If different and the 'c' flag is in 'viminfo', setup for - * conversion of text with iconv() in viminfo_readstring(). - */ -static int viminfo_encoding(vir_T *virp) -{ - char_u *p; - int i; - - if (get_viminfo_parameter('c') != 0) { - p = vim_strchr(virp->vir_line, '='); - if (p != NULL) { - /* remove trailing newline */ - ++p; - for (i = 0; vim_isprintc(p[i]); ++i) - ; - p[i] = NUL; - - convert_setup(&virp->vir_conv, p, p_enc); - } - } - return viminfo_readline(virp); -} - -/* - * Read a line from the viminfo file. - * Returns TRUE for end-of-file; - */ -int viminfo_readline(vir_T *virp) -{ - return vim_fgets(virp->vir_line, LSIZE, virp->vir_fd); -} - -/* - * check string read from viminfo file - * remove '\n' at the end of the line - * - replace CTRL-V CTRL-V with CTRL-V - * - replace CTRL-V 'n' with '\n' - * - * Check for a long line as written by viminfo_writestring(). - * - * Return the string in allocated memory. - */ -char_u * -viminfo_readstring ( - vir_T *virp, - int off, /* offset for virp->vir_line */ - int convert /* convert the string */ -) - FUNC_ATTR_NONNULL_RET -{ - char_u *retval; - char_u *s, *d; - - if (virp->vir_line[off] == Ctrl_V && ascii_isdigit(virp->vir_line[off + 1])) { - ssize_t len = atol((char *)virp->vir_line + off + 1); - retval = xmalloc(len); - // TODO(philix): change type of vim_fgets() size argument to size_t - (void)vim_fgets(retval, (int)len, virp->vir_fd); - s = retval + 1; /* Skip the leading '<' */ - } else { - retval = vim_strsave(virp->vir_line + off); - s = retval; - } - - /* Change CTRL-V CTRL-V to CTRL-V and CTRL-V n to \n in-place. */ - d = retval; - while (*s != NUL && *s != '\n') { - if (s[0] == Ctrl_V && s[1] != NUL) { - if (s[1] == 'n') - *d++ = '\n'; - else - *d++ = Ctrl_V; - s += 2; - } else - *d++ = *s++; - } - *d = NUL; - - if (convert && virp->vir_conv.vc_type != CONV_NONE && *retval != NUL) { - d = string_convert(&virp->vir_conv, retval, NULL); - if (d != NULL) { - xfree(retval); - retval = d; - } - } - - return retval; -} - -/* - * write string to viminfo file - * - replace CTRL-V with CTRL-V CTRL-V - * - replace '\n' with CTRL-V 'n' - * - add a '\n' at the end - * - * For a long line: - * - write " CTRL-V \n " in first line - * - write " < \n " in second line - */ -void viminfo_writestring(FILE *fd, char_u *p) -{ - int c; - char_u *s; - int len = 0; - - for (s = p; *s != NUL; ++s) { - if (*s == Ctrl_V || *s == '\n') - ++len; - ++len; - } - - /* If the string will be too long, write its length and put it in the next - * line. Take into account that some room is needed for what comes before - * the string (e.g., variable name). Add something to the length for the - * '<', NL and trailing NUL. */ - if (len > LSIZE / 2) - fprintf(fd, "\026%d\n<", len + 3); - - while ((c = *p++) != NUL) { - if (c == Ctrl_V || c == '\n') { - putc(Ctrl_V, fd); - if (c == '\n') - c = 'n'; - } - putc(c, fd); - } - putc('\n', fd); -} - void print_line_no_prefix(linenr_T lnum, int use_number, int list) { char_u numbuf[30]; @@ -3364,8 +2823,36 @@ int check_secure(void) return FALSE; } -static char_u *old_sub = NULL; /* previous substitute pattern */ -static int global_need_beginline; /* call beginline() after ":g" */ +/// Previous substitute replacement string +static SubReplacementString old_sub = {NULL, 0, NULL}; + +static int global_need_beginline; // call beginline() after ":g" + +/// Get old substitute replacement string +/// +/// @param[out] ret_sub Location where old string will be saved. +void sub_get_replacement(SubReplacementString *const ret_sub) + FUNC_ATTR_NONNULL_ALL +{ + *ret_sub = old_sub; +} + +/// Set substitute string and timestamp +/// +/// @warning `sub` must be in allocated memory. It is not copied. +/// +/// @param[in] sub New replacement string. +void sub_set_replacement(SubReplacementString sub) +{ + xfree(old_sub.sub); + if (sub.additional_elements != old_sub.additional_elements) { + if (old_sub.additional_elements != NULL) { + api_free_array(*old_sub.additional_elements); + xfree(old_sub.additional_elements); + } + } + old_sub = sub; +} /* do_sub() * @@ -3473,16 +2960,19 @@ void do_sub(exarg_T *eap) } if (!eap->skip) { - xfree(old_sub); - old_sub = vim_strsave(sub); + sub_set_replacement((SubReplacementString) { + .sub = xstrdup((char *) sub), + .timestamp = os_time(), + .additional_elements = NULL, + }); } } else if (!eap->skip) { /* use previous pattern and substitution */ - if (old_sub == NULL) { /* there is no previous command */ + if (old_sub.sub == NULL) { /* there is no previous command */ EMSG(_(e_nopresub)); return; } pat = NULL; /* search_regcomp() will use previous pattern */ - sub = old_sub; + sub = (char_u *) old_sub.sub; /* Vi compatibility quirk: repeating with ":s" keeps the cursor in the * last column after using "$". */ @@ -4501,27 +3991,10 @@ void global_exe(char_u *cmd) msgmore(curbuf->b_ml.ml_line_count - old_lcount); } -int read_viminfo_sub_string(vir_T *virp, int force) -{ - if (force) - xfree(old_sub); - if (force || old_sub == NULL) - old_sub = viminfo_readstring(virp, 1, TRUE); - return viminfo_readline(virp); -} - -void write_viminfo_sub_string(FILE *fp) -{ - if (get_viminfo_parameter('/') != 0 && old_sub != NULL) { - fputs(_("\n# Last Substitute String:\n$"), fp); - viminfo_writestring(fp, old_sub); - } -} - #if defined(EXITFREE) void free_old_sub(void) { - xfree(old_sub); + sub_set_replacement((SubReplacementString) {NULL, 0, NULL}); } #endif diff --git a/src/nvim/ex_cmds.h b/src/nvim/ex_cmds.h index eabbbd15ac..71fc100e41 100644 --- a/src/nvim/ex_cmds.h +++ b/src/nvim/ex_cmds.h @@ -3,6 +3,9 @@ #include +#include "nvim/os/time.h" +#include "nvim/api/private/defs.h" + /* flags for do_ecmd() */ #define ECMD_HIDE 0x01 /* don't free the current buffer */ #define ECMD_SET_HELP 0x02 /* set b_help flag of (new) buffer before @@ -16,11 +19,12 @@ #define ECMD_LAST (linenr_T)-1 /* use last position in all files */ #define ECMD_ONE (linenr_T)1 /* use first line */ -/* flags for read_viminfo() and children */ -#define VIF_WANT_INFO 1 /* load non-mark info */ -#define VIF_WANT_MARKS 2 /* load file marks */ -#define VIF_FORCEIT 4 /* overwrite info already read */ -#define VIF_GET_OLDFILES 8 /* load v:oldfiles */ +/// Previous :substitute replacement string definition +typedef struct { + char *sub; ///< Previous replacement string. + Timestamp timestamp; ///< Time when it was last set. + Array *additional_elements; ///< Additional data left from ShaDa file. +} SubReplacementString; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_cmds.h.generated.h" diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index f7162896ff..c45e641706 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -75,6 +75,7 @@ #include "nvim/mouse.h" #include "nvim/event/rstream.h" #include "nvim/event/wstream.h" +#include "nvim/shada.h" static int quitmore = 0; static int ex_pressedreturn = FALSE; @@ -9149,11 +9150,11 @@ static void ex_viminfo(exarg_T *eap) if (*p_viminfo == NUL) p_viminfo = (char_u *)"'100"; if (eap->cmdidx == CMD_rviminfo) { - if (read_viminfo(eap->arg, VIF_WANT_INFO | VIF_WANT_MARKS - | (eap->forceit ? VIF_FORCEIT : 0)) == FAIL) - EMSG(_("E195: Cannot open viminfo file for reading")); - } else - write_viminfo(eap->arg, eap->forceit); + if (shada_read_everything((char *) eap->arg, eap->forceit) == FAIL) + EMSG(_("E195: Cannot open ShaDa file for reading")); + } else { + shada_write_file((char *) eap->arg, eap->forceit); + } p_viminfo = save_viminfo; } diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 9739090d7c..1347feb857 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -65,6 +65,9 @@ #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/event/loop.h" +#include "nvim/os/time.h" +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" /* * Variables shared between getcmdline(), redrawcmdline() and others. @@ -100,12 +103,6 @@ static int cmd_showtail; /* Only show path tail in lists ? */ static int new_cmdpos; /* position set by set_cmdline_pos() */ -typedef struct hist_entry { - int hisnum; /* identifying number */ - int viminfo; /* when TRUE hisstr comes from viminfo */ - char_u *hisstr; /* actual entry, separator char after the NUL */ -} histentry_T; - /* * Type used by call_user_expand_func */ @@ -4230,12 +4227,10 @@ void init_history(void) // delete entries that don't fit in newlen, if any for (int i = 0; i < i1; i++) { - xfree(history[type][i].hisstr); - history[type][i].hisstr = NULL; + hist_free_entry(history[type] + i); } for (int i = i1 + l1; i < i2; i++) { - xfree(history[type][i].hisstr); - history[type][i].hisstr = NULL; + hist_free_entry(history[type] + i); } } @@ -4253,11 +4248,21 @@ void init_history(void) } } -static void clear_hist_entry(histentry_T *hisptr) +static inline void hist_free_entry(histentry_T *hisptr) + FUNC_ATTR_NONNULL_ALL { - hisptr->hisnum = 0; - hisptr->viminfo = FALSE; - hisptr->hisstr = NULL; + xfree(hisptr->hisstr); + if (hisptr->additional_elements != NULL) { + api_free_array(*hisptr->additional_elements); + xfree(hisptr->additional_elements); + } + clear_hist_entry(hisptr); +} + +static inline void clear_hist_entry(histentry_T *hisptr) + FUNC_ATTR_NONNULL_ALL +{ + memset(hisptr, 0, sizeof(*hisptr)); } /* @@ -4310,6 +4315,8 @@ in_history ( history[type][i].hisnum = ++hisnum[type]; history[type][i].viminfo = FALSE; history[type][i].hisstr = str; + history[type][i].timestamp = os_time(); + history[type][i].additional_elements = NULL; return TRUE; } return FALSE; @@ -4372,8 +4379,7 @@ add_to_history ( if (maptick == last_maptick) { /* Current line is from the same mapping, remove it */ hisptr = &history[HIST_SEARCH][hisidx[HIST_SEARCH]]; - xfree(hisptr->hisstr); - clear_hist_entry(hisptr); + hist_free_entry(hisptr); --hisnum[histype]; if (--hisidx[HIST_SEARCH] < 0) hisidx[HIST_SEARCH] = hislen - 1; @@ -4384,11 +4390,13 @@ add_to_history ( if (++hisidx[histype] == hislen) hisidx[histype] = 0; hisptr = &history[histype][hisidx[histype]]; - xfree(hisptr->hisstr); + hist_free_entry(hisptr); /* Store the separator after the NUL of the string. */ len = (int)STRLEN(new_entry); hisptr->hisstr = vim_strnsave(new_entry, len + 2); + hisptr->timestamp = os_time(); + hisptr->additional_elements = NULL; hisptr->hisstr[len + 1] = sep; hisptr->hisnum = ++hisnum[histype]; @@ -4545,23 +4553,21 @@ char_u *get_history_entry(int histype, int idx) return (char_u *)""; } -/* - * Clear all entries of a history. - * "histype" may be one of the HIST_ values. - */ -int clr_history(int histype) +/// Clear all entries in a history +/// +/// @param[in] histype One of the HIST_ values. +/// +/// @return OK if there was something to clean and histype was one of HIST_ +/// values, FAIL otherwise. +int clr_history(const int histype) { - int i; - histentry_T *hisptr; - if (hislen != 0 && histype >= 0 && histype < HIST_COUNT) { - hisptr = history[histype]; - for (i = hislen; i--; ) { - xfree(hisptr->hisstr); - clear_hist_entry(hisptr); + histentry_T *hisptr = history[histype]; + for (int i = hislen; i--; hisptr++) { + hist_free_entry(hisptr); } - hisidx[histype] = -1; /* mark history as cleared */ - hisnum[histype] = 0; /* reset identifier counter */ + hisidx[histype] = -1; // mark history as cleared + hisnum[histype] = 0; // reset identifier counter return OK; } return FAIL; @@ -4596,8 +4602,7 @@ int del_history_entry(int histype, char_u *str) break; if (vim_regexec(®match, hisptr->hisstr, (colnr_T)0)) { found = TRUE; - xfree(hisptr->hisstr); - clear_hist_entry(hisptr); + hist_free_entry(hisptr); } else { if (i != last) { history[histype][last] = *hisptr; @@ -4628,7 +4633,7 @@ int del_history_idx(int histype, int idx) if (i < 0) return FALSE; idx = hisidx[histype]; - xfree(history[histype][i].hisstr); + hist_free_entry(&history[histype][i]); /* When deleting the last added search string in a mapping, reset * last_maptick, so that the last added search string isn't deleted again. @@ -4641,9 +4646,10 @@ int del_history_idx(int histype, int idx) history[histype][i] = history[histype][j]; i = j; } - clear_hist_entry(&history[histype][i]); - if (--i < 0) + clear_hist_entry(&history[histype][idx]); + if (--i < 0) { i += hislen; + } hisidx[histype] = i; return TRUE; } @@ -4762,250 +4768,6 @@ void ex_history(exarg_T *eap) } } -/* - * Buffers for history read from a viminfo file. Only valid while reading. - */ -static char_u **viminfo_history[HIST_COUNT] = {NULL, NULL, NULL, NULL}; -static int viminfo_hisidx[HIST_COUNT] = {0, 0, 0, 0}; -static int viminfo_hislen[HIST_COUNT] = {0, 0, 0, 0}; -static int viminfo_add_at_front = FALSE; - - -/* - * Translate a history type number to the associated character. - */ -static int -hist_type2char ( - int type, - int use_question /* use '?' instead of '/' */ -) -{ - if (type == HIST_CMD) - return ':'; - if (type == HIST_SEARCH) { - if (use_question) - return '?'; - else - return '/'; - } - if (type == HIST_EXPR) - return '='; - return '@'; -} - -/* - * Prepare for reading the history from the viminfo file. - * This allocates history arrays to store the read history lines. - */ -void prepare_viminfo_history(int asklen, int writing) -{ - int i; - int num; - - init_history(); - viminfo_add_at_front = (asklen != 0 && !writing); - if (asklen > hislen) - asklen = hislen; - - for (int type = 0; type < HIST_COUNT; ++type) { - /* Count the number of empty spaces in the history list. Entries read - * from viminfo previously are also considered empty. If there are - * more spaces available than we request, then fill them up. */ - for (i = 0, num = 0; i < hislen; i++) - if (history[type][i].hisstr == NULL || history[type][i].viminfo) - num++; - int len = asklen; - if (num > len) - len = num; - if (len <= 0) - viminfo_history[type] = NULL; - else - viminfo_history[type] = xmalloc(len * sizeof(char_u *)); - if (viminfo_history[type] == NULL) - len = 0; - viminfo_hislen[type] = len; - viminfo_hisidx[type] = 0; - } -} - -/* - * Accept a line from the viminfo, store it in the history array when it's - * new. - */ -int read_viminfo_history(vir_T *virp, int writing) -{ - int type; - char_u *val; - - type = hist_char2type(virp->vir_line[0]); - if (viminfo_hisidx[type] < viminfo_hislen[type]) { - val = viminfo_readstring(virp, 1, TRUE); - if (val != NULL && *val != NUL) { - int sep = (*val == ' ' ? NUL : *val); - - if (!in_history(type, val + (type == HIST_SEARCH), - viminfo_add_at_front, sep, writing)) { - /* Need to re-allocate to append the separator byte. */ - size_t len = STRLEN(val); - char_u *p = xmalloc(len + 2); - if (type == HIST_SEARCH) { - /* Search entry: Move the separator from the first - * column to after the NUL. */ - memmove(p, val + 1, len); - p[len] = sep; - } else { - /* Not a search entry: No separator in the viminfo - * file, add a NUL separator. */ - memmove(p, val, len + 1); - p[len + 1] = NUL; - } - viminfo_history[type][viminfo_hisidx[type]++] = p; - } - } - xfree(val); - } - return viminfo_readline(virp); -} - -/* - * Finish reading history lines from viminfo. Not used when writing viminfo. - */ -void finish_viminfo_history(void) -{ - int idx; - int i; - int type; - - for (type = 0; type < HIST_COUNT; ++type) { - if (history[type] == NULL) - continue; - idx = hisidx[type] + viminfo_hisidx[type]; - if (idx >= hislen) - idx -= hislen; - else if (idx < 0) - idx = hislen - 1; - if (viminfo_add_at_front) - hisidx[type] = idx; - else { - if (hisidx[type] == -1) - hisidx[type] = hislen - 1; - do { - if (history[type][idx].hisstr != NULL - || history[type][idx].viminfo) - break; - if (++idx == hislen) - idx = 0; - } while (idx != hisidx[type]); - if (idx != hisidx[type] && --idx < 0) - idx = hislen - 1; - } - for (i = 0; i < viminfo_hisidx[type]; i++) { - xfree(history[type][idx].hisstr); - history[type][idx].hisstr = viminfo_history[type][i]; - history[type][idx].viminfo = TRUE; - if (--idx < 0) - idx = hislen - 1; - } - idx += 1; - idx %= hislen; - for (i = 0; i < viminfo_hisidx[type]; i++) { - history[type][idx++].hisnum = ++hisnum[type]; - idx %= hislen; - } - xfree(viminfo_history[type]); - viminfo_history[type] = NULL; - viminfo_hisidx[type] = 0; - } -} - -/* - * Write history to viminfo file in "fp". - * When "merge" is TRUE merge history lines with a previously read viminfo - * file, data is in viminfo_history[]. - * When "merge" is FALSE just write all history lines. Used for ":wviminfo!". - */ -void write_viminfo_history(FILE *fp, int merge) -{ - int i; - int type; - int num_saved; - char_u *p; - int c; - int round; - - init_history(); - if (hislen == 0) - return; - for (type = 0; type < HIST_COUNT; ++type) { - num_saved = get_viminfo_parameter(hist_type2char(type, FALSE)); - if (num_saved == 0) - continue; - if (num_saved < 0) /* Use default */ - num_saved = hislen; - fprintf(fp, _("\n# %s History (newest to oldest):\n"), - type == HIST_CMD ? _("Command Line") : - type == HIST_SEARCH ? _("Search String") : - type == HIST_EXPR ? _("Expression") : - _("Input Line")); - if (num_saved > hislen) - num_saved = hislen; - - /* - * Merge typed and viminfo history: - * round 1: history of typed commands. - * round 2: history from recently read viminfo. - */ - for (round = 1; round <= 2; ++round) { - if (round == 1) - /* start at newest entry, somewhere in the list */ - i = hisidx[type]; - else if (viminfo_hisidx[type] > 0) - /* start at newest entry, first in the list */ - i = 0; - else - /* empty list */ - i = -1; - if (i >= 0) - while (num_saved > 0 - && !(round == 2 && i >= viminfo_hisidx[type])) { - p = round == 1 ? history[type][i].hisstr - : viminfo_history[type] == NULL ? NULL - : viminfo_history[type][i]; - if (p != NULL && (round == 2 - || !merge - || !history[type][i].viminfo)) { - --num_saved; - fputc(hist_type2char(type, TRUE), fp); - /* For the search history: put the separator in the - * second column; use a space if there isn't one. */ - if (type == HIST_SEARCH) { - c = p[STRLEN(p) + 1]; - putc(c == NUL ? ' ' : c, fp); - } - viminfo_writestring(fp, p); - } - if (round == 1) { - /* Decrement index, loop around and stop when back at - * the start. */ - if (--i < 0) - i = hislen - 1; - if (i == hisidx[type]) - break; - } else { - /* Increment index. Stop at the end in the while. */ - ++i; - } - } - } - for (i = 0; i < viminfo_hisidx[type]; ++i) - if (viminfo_history[type] != NULL) - xfree(viminfo_history[type][i]); - xfree(viminfo_history[type]); - viminfo_history[type] = NULL; - viminfo_hisidx[type] = 0; - } -} - /* * Write a character at the current cursor+offset position. * It is directly written into the command buffer block. @@ -5294,3 +5056,68 @@ char_u *script_get(exarg_T *eap, char_u *cmd) return (char_u *)ga.ga_data; } + +/// Iterate over history items +/// +/// @warning No history-editing functions must be run while iteration is in +/// progress. +/// +/// @param[in] iter Pointer to the last history entry. +/// @param[in] history_type Type of the history (HIST_*). Ignored if iter +/// parameter is not NULL. +/// @param[in] zero If true then zero (but not free) returned items. +/// +/// @warning When using this parameter user is +/// responsible for calling clr_history() +/// itself after iteration is over. If +/// clr_history() is not called behaviour is +/// undefined. No functions that work with +/// history must be called during iteration +/// in this case. +/// @param[out] hist Next history entry. +/// +/// @return Pointer used in next iteration or NULL to indicate that iteration +/// was finished. +const void *hist_iter(const void *const iter, const size_t history_type, + const bool zero, histentry_T *const hist) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(4) +{ + *hist = (histentry_T) { + .hisstr = NULL + }; + if (hisidx[history_type] == -1) { + return NULL; + } + histentry_T *const hstart = &(history[history_type][0]); + histentry_T *const hlast = ( + &(history[history_type][hisidx[history_type]])); + const histentry_T *const hend = &(history[history_type][hislen - 1]); + histentry_T *hiter; + if (iter == NULL) { + histentry_T *hfirst = hlast; + do { + hfirst++; + if (hfirst > hend) { + hfirst = hstart; + } + if (hfirst->hisstr != NULL) { + break; + } + } while (hfirst != hlast); + hiter = hfirst; + } else { + hiter = (histentry_T *) iter; + } + if (hiter == NULL) { + return NULL; + } + *hist = *hiter; + if (zero) { + memset(hiter, 0, sizeof(*hiter)); + } + if (hiter == hlast) { + return NULL; + } + hiter++; + return (const void *) ((hiter > hend) ? hstart : hiter); +} diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h index 2b82f934d5..9c7a688e7e 100644 --- a/src/nvim/ex_getln.h +++ b/src/nvim/ex_getln.h @@ -35,6 +35,15 @@ typedef char_u *(*CompleteListItemGetter)(expand_T *, int); +/// History entry definition +typedef struct hist_entry { + int hisnum; ///< Entry identifier number. + bool viminfo; ///< If true, indicates that entry comes from viminfo. + char_u *hisstr; ///< Actual entry, separator char after the NUL. + Timestamp timestamp; ///< Time when entry was added. + Array *additional_elements; ///< Additional entries from ShaDa file. +} histentry_T; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_getln.h.generated.h" #endif diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 302f6b30fb..e4b59b7316 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -57,6 +57,7 @@ #include "nvim/types.h" #include "nvim/undo.h" #include "nvim/window.h" +#include "nvim/shada.h" #include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/os/input.h" @@ -2166,14 +2167,15 @@ readfile_charconvert ( /* - * Read marks for the current buffer from the viminfo file, when we support + * Read marks for the current buffer from the ShaDa file, when we support * buffer marks and the buffer has a name. */ static void check_marks_read(void) { if (!curbuf->b_marks_read && get_viminfo_parameter('\'') > 0 - && curbuf->b_ffname != NULL) - read_viminfo(NULL, VIF_WANT_MARKS); + && curbuf->b_ffname != NULL) { + shada_read_marks(); + } /* Always set b_marks_read; needed when 'viminfo' is changed to include * the ' parameter after opening a buffer. */ diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 60d03cec0c..4ad0d2ec90 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -892,7 +892,7 @@ EXTERN int skip_redraw INIT(= FALSE); /* skip redraw once */ EXTERN int do_redraw INIT(= FALSE); /* extra redraw once */ EXTERN int need_highlight_changed INIT(= TRUE); -EXTERN char_u *use_viminfo INIT(= NULL); /* name of viminfo file to use */ +EXTERN char *used_shada_file INIT(= NULL); /* name of viminfo file to use */ #define NSCRIPT 15 EXTERN FILE *scriptin[NSCRIPT]; /* streams to read script from */ diff --git a/src/nvim/main.c b/src/nvim/main.c index 27f8340ec7..0e25d6d4ec 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -49,6 +49,7 @@ #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/os_unix.h" +#include "nvim/os/os_defs.h" #include "nvim/path.h" #include "nvim/profile.h" #include "nvim/quickfix.h" @@ -58,6 +59,7 @@ #include "nvim/ui.h" #include "nvim/version.h" #include "nvim/window.h" +#include "nvim/shada.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/time.h" @@ -377,12 +379,12 @@ int main(int argc, char **argv) } /* - * Read in registers, history etc, but not marks, from the viminfo file. + * Read in registers, history etc, but not marks, from the ShaDa file. * This is where v:oldfiles gets filled. */ if (*p_viminfo != NUL) { - read_viminfo(NULL, VIF_WANT_INFO | VIF_GET_OLDFILES); - TIME_MSG("reading viminfo"); + (void) shada_read_file(NULL, kShaDaWantInfo|kShaDaGetOldfiles); + TIME_MSG("reading ShaDa"); } /* It's better to make v:oldfiles an empty list than NULL. */ if (get_vim_var_list(VV_OLDFILES) == NULL) @@ -803,9 +805,10 @@ void getout(int exitval) apply_autocmds(EVENT_VIMLEAVEPRE, NULL, NULL, FALSE, curbuf); } - if (p_viminfo && *p_viminfo != NUL) - /* Write out the registers, history, marks etc, to the viminfo file */ - write_viminfo(NULL, FALSE); + if (p_viminfo && *p_viminfo != NUL) { + // Write out the registers, history, marks etc, to the viminfo file + shada_write_file(NULL, false); + } if (get_vim_var_nr(VV_DYING) <= 1) apply_autocmds(EVENT_VIMLEAVE, NULL, NULL, FALSE, curbuf); @@ -1164,7 +1167,7 @@ static void command_line_scan(mparm_T *parmp) } /*FALLTHROUGH*/ case 'S': /* "-S {file}" execute Vim script */ - case 'i': /* "-i {viminfo}" use for viminfo */ + case 'i': /* "-i {shada}" use for ShaDa file */ case 'u': /* "-u {vimrc}" vim inits file */ case 'U': /* "-U {gvimrc}" gvim inits file */ case 'W': /* "-W {scriptout}" overwrite */ @@ -1235,8 +1238,8 @@ static void command_line_scan(mparm_T *parmp) parmp->use_ef = (char_u *)argv[0]; break; - case 'i': /* "-i {viminfo}" use for viminfo */ - use_viminfo = (char_u *)argv[0]; + case 'i': /* "-i {shada}" use for shada */ + used_shada_file = argv[0]; break; case 's': /* "-s {scriptin}" read from script file */ @@ -2039,7 +2042,7 @@ static void usage(void) mch_msg(_(" -r, -L List swap files and exit\n")); mch_msg(_(" -r Recover crashed session\n")); mch_msg(_(" -u Use instead of the default\n")); - mch_msg(_(" -i Use instead of the default\n")); + mch_msg(_(" -i Use instead of the default " SHADA_FILE "\n")); mch_msg(_(" --noplugin Don't load plugin scripts\n")); mch_msg(_(" -o[N] Open N windows (default: one for each file)\n")); mch_msg(_(" -O[N] Like -o but split vertically\n")); diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 6ac4b1de9c..7fdf4f2bcb 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -39,7 +39,10 @@ #include "nvim/strings.h" #include "nvim/ui.h" #include "nvim/os/os.h" +#include "nvim/os/time.h" #include "nvim/os/input.h" +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" /* * This file contains routines to maintain and manipulate marks. @@ -48,12 +51,20 @@ /* * If a named file mark's lnum is non-zero, it is valid. * If a named file mark's fnum is non-zero, it is for an existing buffer, - * otherwise it is from .viminfo and namedfm[n].fname is the file name. + * otherwise it is from .shada and namedfm[n].fname is the file name. * There are marks 'A - 'Z (set by user) and '0 to '9 (set when writing - * viminfo). + * shada). */ -#define EXTRA_MARKS 10 /* marks 0-9 */ -static xfmark_T namedfm[NMARKS + EXTRA_MARKS]; /* marks with file nr */ + +/// Global marks (marks with file number or name) +static xfmark_T namedfm[NGLOBALMARKS]; + +#define SET_XFMARK(xfmarkp_, mark_, fnum_, fname_) \ + do { \ + xfmark_T *const xfmarkp__ = xfmarkp_; \ + xfmarkp__->fname = fname_; \ + RESET_FMARK(&(xfmarkp__->fmark), mark_, fnum_); \ + } while (0) #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -68,6 +79,23 @@ int setmark(int c) return setmark_pos(c, &curwin->w_cursor, curbuf->b_fnum); } +/// Free fmark_T item +void free_fmark(fmark_T fm) +{ + if (fm.additional_data != NULL) { + api_free_dictionary(*fm.additional_data); + free(fm.additional_data); + } +} + +/// Free xfmark_T item +static inline void free_xfmark(xfmark_T fm) +{ + xfree(fm.fname); + fm.fname = NULL; + free_fmark(fm.fmark); +} + /* * Set named mark "c" to position "pos". * When "c" is upper case use file "fnum". @@ -92,7 +120,7 @@ int setmark_pos(int c, pos_T *pos, int fnum) } if (c == '"') { - curbuf->b_last_cursor = *pos; + RESET_FMARK(&curbuf->b_last_cursor, *pos, curbuf->b_fnum); return OK; } @@ -123,16 +151,14 @@ int setmark_pos(int c, pos_T *pos, int fnum) return FAIL; if (islower(c)) { i = c - 'a'; - curbuf->b_namedm[i] = *pos; + RESET_FMARK(curbuf->b_namedm + i, *pos, curbuf->b_fnum); return OK; } if (isupper(c)) { assert(c >= 'A' && c <= 'Z'); i = c - 'A'; - namedfm[i].fmark.mark = *pos; - namedfm[i].fmark.fnum = fnum; - xfree(namedfm[i].fname); - namedfm[i].fname = NULL; + free_xfmark(namedfm[i]); + SET_XFMARK(namedfm + i, *pos, fnum, NULL); return OK; } return FAIL; @@ -157,16 +183,14 @@ void setpcmark(void) /* If jumplist is full: remove oldest entry */ if (++curwin->w_jumplistlen > JUMPLISTSIZE) { curwin->w_jumplistlen = JUMPLISTSIZE; - xfree(curwin->w_jumplist[0].fname); + free_xfmark(curwin->w_jumplist[0]); for (i = 1; i < JUMPLISTSIZE; ++i) curwin->w_jumplist[i - 1] = curwin->w_jumplist[i]; } curwin->w_jumplistidx = curwin->w_jumplistlen; fm = &curwin->w_jumplist[curwin->w_jumplistlen - 1]; - fm->fmark.mark = curwin->w_pcmark; - fm->fmark.fnum = curbuf->b_fnum; - fm->fname = NULL; + SET_XFMARK(fm, curwin->w_pcmark, curbuf->b_fnum, NULL); } /* @@ -302,11 +326,11 @@ pos_T *getmark_buf_fnum(buf_T *buf, int c, int changefile, int *fnum) pos_copy = curwin->w_pcmark; /* need to make a copy because */ posp = &pos_copy; /* w_pcmark may be changed soon */ } else if (c == '"') /* to pos when leaving buffer */ - posp = &(buf->b_last_cursor); + posp = &(buf->b_last_cursor.mark); else if (c == '^') /* to where Insert mode stopped */ - posp = &(buf->b_last_insert); + posp = &(buf->b_last_insert.mark); else if (c == '.') /* to where last change was made */ - posp = &(buf->b_last_change); + posp = &(buf->b_last_change.mark); else if (c == '[') /* to start of previous operator */ posp = &(buf->b_op_start); else if (c == ']') /* to end of previous operator */ @@ -357,7 +381,7 @@ pos_T *getmark_buf_fnum(buf_T *buf, int c, int changefile, int *fnum) pos_copy.coladd = 0; } } else if (ASCII_ISLOWER(c)) { /* normal named mark */ - posp = &(buf->b_namedm[c - 'a']); + posp = &(buf->b_namedm[c - 'a'].mark); } else if (ASCII_ISUPPER(c) || ascii_isdigit(c)) { /* named file mark */ if (ascii_isdigit(c)) c = c - '0' + NMARKS; @@ -365,8 +389,9 @@ pos_T *getmark_buf_fnum(buf_T *buf, int c, int changefile, int *fnum) c -= 'A'; posp = &(namedfm[c].fmark.mark); - if (namedfm[c].fmark.fnum == 0) + if (namedfm[c].fmark.fnum == 0) { fname2fnum(&namedfm[c]); + } if (fnum != NULL) *fnum = namedfm[c].fmark.fnum; @@ -420,15 +445,15 @@ getnextmark ( pos.col = MAXCOL; for (i = 0; i < NMARKS; i++) { - if (curbuf->b_namedm[i].lnum > 0) { + if (curbuf->b_namedm[i].mark.lnum > 0) { if (dir == FORWARD) { - if ((result == NULL || lt(curbuf->b_namedm[i], *result)) - && lt(pos, curbuf->b_namedm[i])) - result = &curbuf->b_namedm[i]; + if ((result == NULL || lt(curbuf->b_namedm[i].mark, *result)) + && lt(pos, curbuf->b_namedm[i].mark)) + result = &curbuf->b_namedm[i].mark; } else { - if ((result == NULL || lt(*result, curbuf->b_namedm[i])) - && lt(curbuf->b_namedm[i], pos)) - result = &curbuf->b_namedm[i]; + if ((result == NULL || lt(*result, curbuf->b_namedm[i].mark)) + && lt(curbuf->b_namedm[i].mark, pos)) + result = &curbuf->b_namedm[i].mark; } } } @@ -438,12 +463,12 @@ getnextmark ( /* * For an xtended filemark: set the fnum from the fname. - * This is used for marks obtained from the .viminfo file. It's postponed + * This is used for marks obtained from the .shada file. It's postponed * until the mark is used to avoid a long startup delay. */ static void fname2fnum(xfmark_T *fm) { - char_u *p; + char_u *p; if (fm->fname != NULL) { /* @@ -475,19 +500,17 @@ static void fname2fnum(xfmark_T *fm) /* * Check all file marks for a name that matches the file name in buf. * May replace the name with an fnum. - * Used for marks that come from the .viminfo file. + * Used for marks that come from the .shada file. */ void fmarks_check_names(buf_T *buf) { - char_u *name; + char_u *name = buf->b_ffname; int i; if (buf->b_ffname == NULL) return; - name = home_replace_save(buf, buf->b_ffname); - - for (i = 0; i < NMARKS + EXTRA_MARKS; ++i) + for (i = 0; i < NGLOBALMARKS; ++i) fmarks_check_one(&namedfm[i], name, buf); FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { @@ -495,8 +518,6 @@ void fmarks_check_names(buf_T *buf) fmarks_check_one(&wp->w_jumplist[i], name, buf); } } - - xfree(name); } static void fmarks_check_one(xfmark_T *fm, char_u *name, buf_T *buf) @@ -541,23 +562,20 @@ int check_mark(pos_T *pos) */ void clrallmarks(buf_T *buf) { - static int i = -1; + static bool initialized = false; - if (i == -1) /* first call ever: initialize */ - for (i = 0; i < NMARKS + 1; i++) { - namedfm[i].fmark.mark.lnum = 0; - namedfm[i].fname = NULL; - } + if (!initialized) { + // first call ever: initialize + memset(&(namedfm[0]), 0, sizeof(namedfm)); + initialized = true; + } - for (i = 0; i < NMARKS; i++) - buf->b_namedm[i].lnum = 0; + memset(&(buf->b_namedm[0]), 0, sizeof(buf->b_namedm)); buf->b_op_start.lnum = 0; /* start/end op mark cleared */ buf->b_op_end.lnum = 0; - buf->b_last_cursor.lnum = 1; /* '" mark cleared */ - buf->b_last_cursor.col = 0; - buf->b_last_cursor.coladd = 0; - buf->b_last_insert.lnum = 0; /* '^ mark cleared */ - buf->b_last_change.lnum = 0; /* '. mark cleared */ + RESET_FMARK(&buf->b_last_cursor, ((pos_T) {1, 0, 0}), 0); // '" mark + CLEAR_FMARK(&buf->b_last_insert); // '^ mark + CLEAR_FMARK(&buf->b_last_change); // '. mark buf->b_changelistlen = 0; } @@ -612,8 +630,8 @@ void do_marks(exarg_T *eap) show_one_mark('\'', arg, &curwin->w_pcmark, NULL, TRUE); for (i = 0; i < NMARKS; ++i) - show_one_mark(i + 'a', arg, &curbuf->b_namedm[i], NULL, TRUE); - for (i = 0; i < NMARKS + EXTRA_MARKS; ++i) { + show_one_mark(i + 'a', arg, &curbuf->b_namedm[i].mark, NULL, TRUE); + for (i = 0; i < NGLOBALMARKS; ++i) { if (namedfm[i].fmark.fnum != 0) name = fm_getname(&namedfm[i].fmark, 15); else @@ -626,11 +644,11 @@ void do_marks(exarg_T *eap) xfree(name); } } - show_one_mark('"', arg, &curbuf->b_last_cursor, NULL, TRUE); + show_one_mark('"', arg, &curbuf->b_last_cursor.mark, NULL, TRUE); show_one_mark('[', arg, &curbuf->b_op_start, NULL, TRUE); show_one_mark(']', arg, &curbuf->b_op_end, NULL, TRUE); - show_one_mark('^', arg, &curbuf->b_last_insert, NULL, TRUE); - show_one_mark('.', arg, &curbuf->b_last_change, NULL, TRUE); + show_one_mark('^', arg, &curbuf->b_last_insert.mark, NULL, TRUE); + show_one_mark('.', arg, &curbuf->b_last_change.mark, NULL, TRUE); show_one_mark('<', arg, &curbuf->b_visual.vi_start, NULL, TRUE); show_one_mark('>', arg, &curbuf->b_visual.vi_end, NULL, TRUE); show_one_mark(-1, arg, NULL, NULL, FALSE); @@ -728,7 +746,7 @@ void ex_delmarks(exarg_T *eap) for (i = from; i <= to; ++i) { if (lower) - curbuf->b_namedm[i - 'a'].lnum = 0; + curbuf->b_namedm[i - 'a'].mark.lnum = 0; else { if (digit) n = i - '0' + NMARKS; @@ -741,9 +759,9 @@ void ex_delmarks(exarg_T *eap) } } else switch (*p) { - case '"': curbuf->b_last_cursor.lnum = 0; break; - case '^': curbuf->b_last_insert.lnum = 0; break; - case '.': curbuf->b_last_change.lnum = 0; break; + case '"': CLEAR_FMARK(&curbuf->b_last_cursor); break; + case '^': CLEAR_FMARK(&curbuf->b_last_insert); break; + case '.': CLEAR_FMARK(&curbuf->b_last_change); break; case '[': curbuf->b_op_start.lnum = 0; break; case ']': curbuf->b_op_end.lnum = 0; break; case '<': curbuf->b_visual.vi_start.lnum = 0; break; @@ -886,24 +904,24 @@ void mark_adjust(linenr_T line1, linenr_T line2, long amount, long amount_after) if (!cmdmod.lockmarks) { /* named marks, lower case and upper case */ for (i = 0; i < NMARKS; i++) { - one_adjust(&(curbuf->b_namedm[i].lnum)); + one_adjust(&(curbuf->b_namedm[i].mark.lnum)); if (namedfm[i].fmark.fnum == fnum) one_adjust_nodel(&(namedfm[i].fmark.mark.lnum)); } - for (i = NMARKS; i < NMARKS + EXTRA_MARKS; i++) { + for (i = NMARKS; i < NGLOBALMARKS; i++) { if (namedfm[i].fmark.fnum == fnum) one_adjust_nodel(&(namedfm[i].fmark.mark.lnum)); } /* last Insert position */ - one_adjust(&(curbuf->b_last_insert.lnum)); + one_adjust(&(curbuf->b_last_insert.mark.lnum)); /* last change position */ - one_adjust(&(curbuf->b_last_change.lnum)); + one_adjust(&(curbuf->b_last_change.mark.lnum)); /* last cursor position, if it was set */ - if (!equalpos(curbuf->b_last_cursor, initpos)) - one_adjust(&(curbuf->b_last_cursor.lnum)); + if (!equalpos(curbuf->b_last_cursor.mark, initpos)) + one_adjust(&(curbuf->b_last_cursor.mark.lnum)); /* list of change positions */ @@ -1038,20 +1056,20 @@ void mark_col_adjust(linenr_T lnum, colnr_T mincol, long lnum_amount, long col_a /* named marks, lower case and upper case */ for (i = 0; i < NMARKS; i++) { - col_adjust(&(curbuf->b_namedm[i])); + col_adjust(&(curbuf->b_namedm[i].mark)); if (namedfm[i].fmark.fnum == fnum) col_adjust(&(namedfm[i].fmark.mark)); } - for (i = NMARKS; i < NMARKS + EXTRA_MARKS; i++) { + for (i = NMARKS; i < NGLOBALMARKS; i++) { if (namedfm[i].fmark.fnum == fnum) col_adjust(&(namedfm[i].fmark.mark)); } /* last Insert position */ - col_adjust(&(curbuf->b_last_insert)); + col_adjust(&(curbuf->b_last_insert.mark)); /* last change position */ - col_adjust(&(curbuf->b_last_change)); + col_adjust(&(curbuf->b_last_change.mark)); /* list of change positions */ for (i = 0; i < curbuf->b_changelistlen; ++i) @@ -1150,382 +1168,274 @@ void copy_jumplist(win_T *from, win_T *to) to->w_jumplistidx = from->w_jumplistidx; } -/* - * Free items in the jumplist of window "wp". - */ -void free_jumplist(win_T *wp) -{ - int i; - - for (i = 0; i < wp->w_jumplistlen; ++i) - xfree(wp->w_jumplist[i].fname); -} - -void set_last_cursor(win_T *win) +/// Iterate over jumplist items +/// +/// @warning No jumplist-editing functions must be run while iteration is in +/// progress. +/// +/// @param[in] iter Iterator. Pass NULL to start iteration. +/// @param[in] win Window for which jump list is processed. +/// @param[out] fm Item definition. +/// +/// @return Pointer that needs to be passed to next `mark_jumplist_iter` call or +/// NULL if iteration is over. +const void *mark_jumplist_iter(const void *const iter, const win_T *const win, + xfmark_T *const fm) + FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT { - if (win->w_buffer != NULL) - win->w_buffer->b_last_cursor = win->w_cursor; + if (iter == NULL && win->w_jumplistlen == 0) { + *fm = (xfmark_T) {{{0, 0, 0}, 0, 0, NULL}, NULL}; + return NULL; + } + const xfmark_T *const iter_mark = + (iter == NULL + ? &(win->w_jumplist[win->w_jumplistlen - 1]) + : (const xfmark_T *const) iter); + *fm = *iter_mark; + if (iter_mark == &(win->w_jumplist[0])) { + return NULL; + } else { + return iter_mark - 1; + } } -#if defined(EXITFREE) -void free_all_marks(void) +/// Iterate over global marks +/// +/// @warning No mark-editing functions must be run while iteration is in +/// progress. +/// +/// @param[in] iter Iterator. Pass NULL to start iteration. +/// @param[out] name Mark name. +/// @param[out] fm Mark definition. +/// +/// @return Pointer that needs to be passed to next `mark_global_iter` call or +/// NULL if iteration is over. +const void *mark_global_iter(const void *const iter, char *const name, + xfmark_T *const fm) + FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT { - int i; - - for (i = 0; i < NMARKS + EXTRA_MARKS; i++) - if (namedfm[i].fmark.mark.lnum != 0) - xfree(namedfm[i].fname); + const xfmark_T *iter_mark = (iter == NULL + ? &(namedfm[0]) + : (const xfmark_T *const) iter); + while (!iter_mark->fmark.mark.lnum + && (size_t) (iter_mark - &(namedfm[0])) < ARRAY_SIZE(namedfm)) { + iter_mark++; + } + if (!iter_mark->fmark.mark.lnum) { + *fm = (xfmark_T) {.fmark = {.mark = {.lnum = 0}}}; + } + size_t iter_off = (size_t) (iter_mark - &(namedfm[0])); + *name = (char) (iter_off < NMARKS + ? 'A' + (char) iter_off + : '0' + (char) (iter_off - NMARKS)); + *fm = *iter_mark; + while ((size_t) (++iter_mark - &(namedfm[0])) < ARRAY_SIZE(namedfm)) { + if (iter_mark->fmark.mark.lnum) { + return (const void *) iter_mark; + } + } + return NULL; } -#endif - -int read_viminfo_filemark(vir_T *virp, int force) +/// Get next mark and its name +/// +/// @param[in] buf Buffer for which next mark is taken. +/// @param[in,out] mark_name Pointer to the current mark name. Next mark name +/// will be saved at this address as well. +/// +/// Current mark name must either be NUL, '"', '^', +/// '.' or 'a' .. 'z'. If it is neither of these +/// behaviour is undefined. +/// +/// @return Pointer to the next mark or NULL. +static inline const fmark_T *next_buffer_mark(const buf_T *const buf, + char *const mark_name) + FUNC_ATTR_NONNULL_ALL { - char_u *str; - xfmark_T *fm; - int i; - - /* We only get here if line[0] == '\'' or '-'. - * Illegal mark names are ignored (for future expansion). */ - str = virp->vir_line + 1; - if ( - *str <= 127 && - ((*virp->vir_line == '\'' && (ascii_isdigit(*str) || isupper(*str))) - || (*virp->vir_line == '-' && *str == '\''))) { - if (*str == '\'') { - /* If the jumplist isn't full insert fmark as oldest entry */ - if (curwin->w_jumplistlen == JUMPLISTSIZE) - fm = NULL; - else { - for (i = curwin->w_jumplistlen; i > 0; --i) - curwin->w_jumplist[i] = curwin->w_jumplist[i - 1]; - ++curwin->w_jumplistidx; - ++curwin->w_jumplistlen; - fm = &curwin->w_jumplist[0]; - fm->fmark.mark.lnum = 0; - fm->fname = NULL; - } - } else if (ascii_isdigit(*str)) - fm = &namedfm[*str - '0' + NMARKS]; - else { // is uppercase - assert(*str >= 'A' && *str <= 'Z'); - fm = &namedfm[*str - 'A']; + switch (*mark_name) { + case NUL: { + *mark_name = '"'; + return &(buf->b_last_cursor); + } + case '"': { + *mark_name = '^'; + return &(buf->b_last_insert); + } + case '^': { + *mark_name = '.'; + return &(buf->b_last_change); } - if (fm != NULL && (fm->fmark.mark.lnum == 0 || force)) { - str = skipwhite(str + 1); - fm->fmark.mark.lnum = getdigits_long(&str); - str = skipwhite(str); - fm->fmark.mark.col = getdigits_int(&str); - fm->fmark.mark.coladd = 0; - fm->fmark.fnum = 0; - str = skipwhite(str); - xfree(fm->fname); - fm->fname = viminfo_readstring(virp, (int)(str - virp->vir_line), - FALSE); + case '.': { + *mark_name = 'a'; + return &(buf->b_namedm[0]); + } + case 'z': { + return NULL; + } + default: { + (*mark_name)++; + return &(buf->b_namedm[*mark_name - 'a']); } } - return vim_fgets(virp->vir_line, LSIZE, virp->vir_fd); } -void write_viminfo_filemarks(FILE *fp) +/// Iterate over buffer marks +/// +/// @warning No mark-editing functions must be run while iteration is in +/// progress. +/// +/// @param[in] iter Iterator. Pass NULL to start iteration. +/// @param[in] buf Buffer. +/// @param[out] name Mark name. +/// @param[out] fm Mark definition. +/// +/// @return Pointer that needs to be passed to next `mark_buffer_iter` call or +/// NULL if iteration is over. +const void *mark_buffer_iter(const void *const iter, const buf_T *const buf, + char *const name, fmark_T *const fm) + FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ARG(2, 3, 4) FUNC_ATTR_WARN_UNUSED_RESULT { - int i; - char_u *name; - buf_T *buf; - xfmark_T *fm; - - if (get_viminfo_parameter('f') == 0) - return; - - fputs(_("\n# File marks:\n"), fp); - - /* - * Find a mark that is the same file and position as the cursor. - * That one, or else the last one is deleted. - * Move '0 to '1, '1 to '2, etc. until the matching one or '9 - * Set '0 mark to current cursor position. - */ - if (curbuf->b_ffname != NULL && !removable(curbuf->b_ffname)) { - name = buflist_nr2name(curbuf->b_fnum, TRUE, FALSE); - for (i = NMARKS; i < NMARKS + EXTRA_MARKS - 1; ++i) - if (namedfm[i].fmark.mark.lnum == curwin->w_cursor.lnum - && (namedfm[i].fname == NULL - ? namedfm[i].fmark.fnum == curbuf->b_fnum - : (name != NULL - && STRCMP(name, namedfm[i].fname) == 0))) - break; - xfree(name); - - xfree(namedfm[i].fname); - for (; i > NMARKS; --i) - namedfm[i] = namedfm[i - 1]; - namedfm[NMARKS].fmark.mark = curwin->w_cursor; - namedfm[NMARKS].fmark.fnum = curbuf->b_fnum; - namedfm[NMARKS].fname = NULL; + char mark_name = (char) (iter == NULL + ? NUL + : (iter == &(buf->b_last_cursor) + ? '"' + : (iter == &(buf->b_last_insert) + ? '^' + : (iter == &(buf->b_last_change) + ? '.' + : 'a' + (char) ((const fmark_T *)iter + - &(buf->b_namedm[0])))))); + const fmark_T *iter_mark = next_buffer_mark(buf, &mark_name); + while (iter_mark != NULL && iter_mark->mark.lnum == 0) { + iter_mark = next_buffer_mark(buf, &mark_name); } - - /* Write the filemarks '0 - '9 and 'A - 'Z */ - for (i = 0; i < NMARKS + EXTRA_MARKS; i++) - write_one_filemark(fp, &namedfm[i], '\'', - i < NMARKS ? i + 'A' : i - NMARKS + '0'); - - /* Write the jumplist with -' */ - fputs(_("\n# Jumplist (newest first):\n"), fp); - setpcmark(); /* add current cursor position */ - cleanup_jumplist(); - for (fm = &curwin->w_jumplist[curwin->w_jumplistlen - 1]; - fm >= &curwin->w_jumplist[0]; --fm) { - if (fm->fmark.fnum == 0 - || ((buf = buflist_findnr(fm->fmark.fnum)) != NULL - && !removable(buf->b_ffname))) - write_one_filemark(fp, fm, '-', '\''); + if (iter_mark == NULL) { + *fm = (fmark_T) {.mark = {.lnum = 0}}; + return NULL; + } + size_t iter_off = (size_t) (iter_mark - &(buf->b_namedm[0])); + if (mark_name) { + *name = mark_name; + } else { + *name = (char) ('a' + (char) iter_off); } + *fm = *iter_mark; + return (const void *) iter_mark; } -static void write_one_filemark(FILE *fp, xfmark_T *fm, int c1, int c2) +/// Get a number of valid marks +size_t mark_global_amount(void) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - char_u *name; - - if (fm->fmark.mark.lnum == 0) /* not set */ - return; - - if (fm->fmark.fnum != 0) /* there is a buffer */ - name = buflist_nr2name(fm->fmark.fnum, TRUE, FALSE); - else - name = fm->fname; /* use name from .viminfo */ - if (name != NULL && *name != NUL) { - fprintf(fp, "%c%c %" PRId64 " %" PRId64 " ", - c1, c2, (int64_t)fm->fmark.mark.lnum, (int64_t)fm->fmark.mark.col); - viminfo_writestring(fp, name); + size_t ret = 0; + for (size_t i = 0; i < NGLOBALMARKS; i++) { + if (namedfm[i].fmark.mark.lnum != 0) { + ret++; + } } - - if (fm->fmark.fnum != 0) - xfree(name); + return ret; } -/* - * Return TRUE if "name" is on removable media (depending on 'viminfo'). - */ -int removable(char_u *name) +/// Get a number of valid marks +size_t mark_buffer_amount(const buf_T *const buf) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - char_u *p; - char_u part[51]; - int retval = FALSE; - size_t n; - - name = home_replace_save(NULL, name); - for (p = p_viminfo; *p; ) { - copy_option_part(&p, part, 51, ", "); - if (part[0] == 'r') { - n = STRLEN(part + 1); - if (mb_strnicmp(part + 1, name, n) == 0) { - retval = TRUE; - break; - } + size_t ret = (size_t) ((buf->b_last_cursor.mark.lnum != 0) + + (buf->b_last_insert.mark.lnum != 0) + + (buf->b_last_change.mark.lnum != 0)); + for (size_t i = 0; i < NMARKS; i++) { + if (buf->b_namedm[i].mark.lnum != 0) { + ret++; } } - xfree(name); - return retval; + return ret; } - -/* - * Write all the named marks for all buffers. - * Return the number of buffers for which marks have been written. - */ -int write_viminfo_marks(FILE *fp_out) +/// Set global mark +/// +/// @param[in] name Mark name. +/// @param[in] fm Mark to be set. +/// @param[in] update If true then only set global mark if it was created +/// later then existing one. +void mark_set_global(const char name, const xfmark_T fm, const bool update) { - /* - * Set b_last_cursor for the all buffers that have a window. - */ - FOR_ALL_TAB_WINDOWS(tp, win) { - set_last_cursor(win); + xfmark_T *fm_tgt = NULL; + if (ASCII_ISUPPER(name)) { + fm_tgt = &(namedfm[name - 'A']); + } else if (ascii_isdigit(name)) { + fm_tgt = &(namedfm[NMARKS + (name - '0')]); + } else { + return; } - - fputs(_("\n# History of marks within files (newest to oldest):\n"), fp_out); - int count = 0; - FOR_ALL_BUFFERS(buf) { - /* - * Only write something if buffer has been loaded and at least one - * mark is set. - */ - if (buf->b_marks_read) { - bool is_mark_set = true; - if (buf->b_last_cursor.lnum == 0) { - is_mark_set = false; - for (int i = 0; i < NMARKS; i++) { - if (buf->b_namedm[i].lnum != 0) { - is_mark_set = true; - break; - } - } - } - if (is_mark_set && buf->b_ffname != NULL - && buf->b_ffname[0] != NUL && !removable(buf->b_ffname)) { - home_replace(NULL, buf->b_ffname, IObuff, IOSIZE, TRUE); - fprintf(fp_out, "\n> "); - viminfo_writestring(fp_out, IObuff); - write_one_mark(fp_out, '"', &buf->b_last_cursor); - write_one_mark(fp_out, '^', &buf->b_last_insert); - write_one_mark(fp_out, '.', &buf->b_last_change); - /* changelist positions are stored oldest first */ - for (int i = 0; i < buf->b_changelistlen; ++i) { - write_one_mark(fp_out, '+', &buf->b_changelist[i]); - } - for (int i = 0; i < NMARKS; i++) { - write_one_mark(fp_out, 'a' + i, &buf->b_namedm[i]); - } - count++; - } - } + if (update && fm.fmark.timestamp < fm_tgt->fmark.timestamp) { + return; } - - return count; + if (fm_tgt->fmark.mark.lnum != 0) { + free_xfmark(*fm_tgt); + } + *fm_tgt = fm; } -static void write_one_mark(FILE *fp_out, int c, pos_T *pos) +/// Set local mark +/// +/// @param[in] name Mark name. +/// @param[in] buf Pointer to the buffer to set mark in. +/// @param[in] fm Mark to be set. +/// @param[in] update If true then only set global mark if it was created +/// later then existing one. +void mark_set_local(const char name, buf_T *const buf, + const fmark_T fm, const bool update) + FUNC_ATTR_NONNULL_ALL { - if (pos->lnum != 0) - fprintf(fp_out, "\t%c\t%" PRId64 "\t%d\n", c, - (int64_t)pos->lnum, (int)pos->col); + fmark_T *fm_tgt = NULL; + if (ASCII_ISLOWER(name)) { + fm_tgt = &(buf->b_namedm[name - 'a']); + } else if (name == '"') { + fm_tgt = &(buf->b_last_cursor); + } else if (name == '^') { + fm_tgt = &(buf->b_last_insert); + } else if (name == '.') { + fm_tgt = &(buf->b_last_change); + } else { + return; + } + if (update && fm.timestamp < fm_tgt->timestamp) { + return; + } + if (fm_tgt->mark.lnum != 0) { + free_fmark(*fm_tgt); + } + *fm_tgt = fm; } /* - * Handle marks in the viminfo file: - * fp_out != NULL: copy marks for buffers not in buffer list - * fp_out == NULL && (flags & VIF_WANT_MARKS): read marks for curbuf only - * fp_out == NULL && (flags & VIF_GET_OLDFILES | VIF_FORCEIT): fill v:oldfiles + * Free items in the jumplist of window "wp". */ -void copy_viminfo_marks(vir_T *virp, FILE *fp_out, int count, int eof, int flags) +void free_jumplist(win_T *wp) { - char_u *line = virp->vir_line; - buf_T *buf; - int num_marked_files; - int load_marks; - int copy_marks_out; - char_u *str; int i; - char_u *p; - char_u *name_buf; - pos_T pos; - list_T *list = NULL; - name_buf = xmalloc(LSIZE); - *name_buf = NUL; - - if (fp_out == NULL && (flags & (VIF_GET_OLDFILES | VIF_FORCEIT))) { - list = list_alloc(); - set_vim_var_list(VV_OLDFILES, list); + for (i = 0; i < wp->w_jumplistlen; ++i) { + free_xfmark(wp->w_jumplist[i]); } +} - num_marked_files = get_viminfo_parameter('\''); - while (!eof && (count < num_marked_files || fp_out == NULL)) { - if (line[0] != '>') { - if (line[0] != '\n' && line[0] != '\r' && line[0] != '#') { - if (viminfo_error("E576: ", _("Missing '>'"), line)) - break; /* too many errors, return now */ - } - eof = vim_fgets(line, LSIZE, virp->vir_fd); - continue; /* Skip this dud line */ - } - - /* - * Handle long line and translate escaped characters. - * Find file name, set str to start. - * Ignore leading and trailing white space. - */ - str = skipwhite(line + 1); - str = viminfo_readstring(virp, (int)(str - virp->vir_line), FALSE); - p = str + STRLEN(str); - while (p != str && (*p == NUL || ascii_isspace(*p))) - p--; - if (*p) - p++; - *p = NUL; - - if (list != NULL) - list_append_string(list, str, -1); +void set_last_cursor(win_T *win) +{ + if (win->w_buffer != NULL) { + RESET_FMARK(&win->w_buffer->b_last_cursor, win->w_cursor, 0); + } +} - /* - * If fp_out == NULL, load marks for current buffer. - * If fp_out != NULL, copy marks for buffers not in buflist. - */ - load_marks = copy_marks_out = FALSE; - if (fp_out == NULL) { - if ((flags & VIF_WANT_MARKS) && curbuf->b_ffname != NULL) { - if (*name_buf == NUL) /* only need to do this once */ - home_replace(NULL, curbuf->b_ffname, name_buf, LSIZE, TRUE); - if (fnamecmp(str, name_buf) == 0) - load_marks = TRUE; - } - } else { /* fp_out != NULL */ - /* This is slow if there are many buffers!! */ - buf = NULL; - FOR_ALL_BUFFERS(bp) { - if (bp->b_ffname != NULL) { - home_replace(NULL, bp->b_ffname, name_buf, LSIZE, TRUE); - if (fnamecmp(str, name_buf) == 0) { - buf = bp; - break; - } - } - } +#if defined(EXITFREE) +void free_all_marks(void) +{ + int i; - /* - * copy marks if the buffer has not been loaded - */ - if (buf == NULL || !buf->b_marks_read) { - copy_marks_out = TRUE; - fputs("\n> ", fp_out); - viminfo_writestring(fp_out, str); - count++; - } - } - xfree(str); - - pos.coladd = 0; - while (!(eof = viminfo_readline(virp)) && line[0] == TAB) { - if (load_marks) { - if (line[1] != NUL) { - int64_t lnum_64; - unsigned int u; - sscanf((char *)line + 2, "%" SCNd64 "%u", &lnum_64, &u); - // safely downcast to linenr_T (long); remove when linenr_T refactored - assert(lnum_64 <= LONG_MAX); - pos.lnum = (linenr_T)lnum_64; - assert(u <= INT_MAX); - pos.col = (colnr_T)u; - switch (line[1]) { - case '"': curbuf->b_last_cursor = pos; break; - case '^': curbuf->b_last_insert = pos; break; - case '.': curbuf->b_last_change = pos; break; - case '+': - /* changelist positions are stored oldest - * first */ - if (curbuf->b_changelistlen == JUMPLISTSIZE) - /* list is full, remove oldest entry */ - memmove(curbuf->b_changelist, - curbuf->b_changelist + 1, - sizeof(pos_T) * (JUMPLISTSIZE - 1)); - else - ++curbuf->b_changelistlen; - curbuf->b_changelist[ - curbuf->b_changelistlen - 1] = pos; - break; - default: if ((i = line[1] - 'a') >= 0 && i < NMARKS) - curbuf->b_namedm[i] = pos; - } - } - } else if (copy_marks_out) - fputs((char *)line, fp_out); - } - if (load_marks) { - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer == curbuf) - wp->w_changelistidx = curbuf->b_changelistlen; - } - break; + for (i = 0; i < NGLOBALMARKS; i++) { + if (namedfm[i].fmark.mark.lnum != 0) { + free_xfmark(namedfm[i]); } } - xfree(name_buf); } +#endif diff --git a/src/nvim/mark.h b/src/nvim/mark.h index aa89a5b625..a8aaf56648 100644 --- a/src/nvim/mark.h +++ b/src/nvim/mark.h @@ -4,6 +4,22 @@ #include "nvim/buffer_defs.h" #include "nvim/mark_defs.h" #include "nvim/pos.h" +#include "nvim/os/time.h" + +/// Free and set fmark using given value +#define RESET_FMARK(fmarkp_, mark_, fnum_) \ + do { \ + fmark_T *const fmarkp__ = fmarkp_; \ + free_fmark(*fmarkp__); \ + fmarkp__->mark = mark_; \ + fmarkp__->fnum = fnum_; \ + fmarkp__->timestamp = os_time(); \ + fmarkp__->additional_data = NULL; \ + } while (0) + +/// Clear given fmark +#define CLEAR_FMARK(fmarkp_) \ + RESET_FMARK(fmarkp_, ((pos_T) {0, 0, 0}), 0) #ifdef INCLUDE_GENERATED_DECLARATIONS # include "mark.h.generated.h" diff --git a/src/nvim/mark_defs.h b/src/nvim/mark_defs.h index 67392234d3..2f7c9b4ed4 100644 --- a/src/nvim/mark_defs.h +++ b/src/nvim/mark_defs.h @@ -2,25 +2,41 @@ #define NVIM_MARK_DEFS_H #include "nvim/pos.h" +#include "nvim/os/time.h" +#include "nvim/api/private/defs.h" /* * marks: positions in a file * (a normal mark is a lnum/col pair, the same as a file position) */ -#define NMARKS ('z' - 'a' + 1) /* max. # of named marks */ -#define JUMPLISTSIZE 100 /* max. # of marks in jump list */ -#define TAGSTACKSIZE 20 /* max. # of tags in tag stack */ +/// Number of possible numbered global marks +#define EXTRA_MARKS ('9' - '0' + 1) +/// Maximum possible number of letter marks +#define NMARKS ('z' - 'a' + 1) + +/// Total possible number of global marks +#define NGLOBALMARKS (NMARKS + EXTRA_MARKS) + +/// Maximum number of marks in jump list +#define JUMPLISTSIZE 100 + +/// Maximum number of tags in tag stack +#define TAGSTACKSIZE 20 + +/// Structure defining single local mark typedef struct filemark { - pos_T mark; /* cursor position */ - int fnum; /* file number */ + pos_T mark; ///< Cursor position. + int fnum; ///< File number. + Timestamp timestamp; ///< Time when this mark was last set. + Dictionary *additional_data; ///< Additional data from ShaDa file. } fmark_T; -/* Xtended file mark: also has a file name */ +/// Structure defining extended mark (mark with file name attached) typedef struct xfilemark { - fmark_T fmark; - char_u *fname; /* file name, used when fnum == 0 */ + fmark_T fmark; ///< Actual mark. + char_u *fname; ///< File name, used when fnum == 0. } xfmark_T; #endif // NVIM_MARK_DEFS_H diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 100b66a608..087d2e677c 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -66,7 +66,7 @@ * (4) The encoding of the file is specified with 'fileencoding'. Conversion * is to be done when it's different from 'encoding'. * - * The viminfo file is a special case: Only text is converted, not file names. + * The ShaDa file is a special case: Only text is converted, not file names. * Vim scripts may contain an ":encoding" command. This has an effect for * some commands, like ":menutrans" */ diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index 8407198b13..a4ebdca091 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -2042,8 +2042,7 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra /* set the '. mark */ if (!cmdmod.keepjumps) { - curbuf->b_last_change.lnum = lnum; - curbuf->b_last_change.col = col; + RESET_FMARK(&curbuf->b_last_change, ((pos_T) {lnum, col, 0}), 0); /* Create a new entry if a new undo-able change was started or we * don't have an entry yet. */ @@ -2095,7 +2094,7 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra } } curbuf->b_changelist[curbuf->b_changelistlen - 1] = - curbuf->b_last_change; + curbuf->b_last_change.mark; /* The current window is always after the last change, so that "g," * takes you back to it. */ curwin->w_changelistidx = curbuf->b_changelistlen; diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 467b74f9e6..5354fb20ad 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -6327,8 +6327,8 @@ static void nv_g_cmd(cmdarg_T *cap) * "gi": start Insert at the last position. */ case 'i': - if (curbuf->b_last_insert.lnum != 0) { - curwin->w_cursor = curbuf->b_last_insert; + if (curbuf->b_last_insert.mark.lnum != 0) { + curwin->w_cursor = curbuf->b_last_insert.mark; check_cursor_lnum(); i = (int)STRLEN(get_cursor_line_ptr()); if (curwin->w_cursor.col > (colnr_T)i) { diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 3163132b8a..55c7aa3364 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -50,6 +50,9 @@ #include "nvim/undo.h" #include "nvim/window.h" #include "nvim/os/input.h" +#include "nvim/os/time.h" +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" /* * Registers: @@ -62,14 +65,14 @@ */ #define DELETION_REGISTER 36 #define NUM_SAVED_REGISTERS 37 -// The following registers should not be saved in viminfo: +// The following registers should not be saved in ShaDa file: #define STAR_REGISTER 37 #define PLUS_REGISTER 38 #define NUM_REGISTERS 39 static yankreg_T y_regs[NUM_REGISTERS]; -static yankreg_T *y_previous = NULL; /* ptr to last written yankreg */ +static yankreg_T *y_previous = NULL; /* ptr to last written yankreg */ static bool clipboard_didwarn_unnamed = false; @@ -746,6 +749,31 @@ typedef enum { YREG_PUT, } yreg_mode_t; +/// Convert register name into register index +/// +/// @param[in] regname Register name. +/// +/// @return Index in y_regs array or -1 if register name was not recognized. +static inline int reg_index(const int regname) + FUNC_ATTR_CONST +{ + if (ascii_isdigit(regname)) { + return regname - '0'; + } else if (ASCII_ISLOWER(regname)) { + return CharOrdLow(regname) + 10; + } else if (ASCII_ISUPPER(regname)) { + return CharOrdUp(regname) + 10; + } else if (regname == '-') { + return DELETION_REGISTER; + } else if (regname == '*') { + return STAR_REGISTER; + } else if (regname == '+') { + return PLUS_REGISTER; + } else { + return -1; + } +} + /// Return yankreg_T to use, according to the value of `regname`. /// Cannot handle the '_' (black hole) register. /// Must only be called with a valid register name! @@ -778,19 +806,11 @@ yankreg_T *get_yank_register(int regname, int mode) return y_previous; } - int i = 0; // when not 0-9, a-z, A-Z or '-'/'+'/'*': use register 0 - if (ascii_isdigit(regname)) - i = regname - '0'; - else if (ASCII_ISLOWER(regname)) - i = CharOrdLow(regname) + 10; - else if (ASCII_ISUPPER(regname)) { - i = CharOrdUp(regname) + 10; - } else if (regname == '-') - i = DELETION_REGISTER; - else if (regname == '*') - i = STAR_REGISTER; - else if (regname == '+') - i = PLUS_REGISTER; + int i = reg_index(regname); + // when not 0-9, a-z, A-Z or '-'/'+'/'*': use register 0 + if (i == -1) { + i = 0; + } reg = &y_regs[i]; if (mode == YREG_YANK) { @@ -890,6 +910,20 @@ int do_record(int c) return retval; } +static void set_yreg_additional_data(yankreg_T *reg, + Dictionary *additional_data) + FUNC_ATTR_NONNULL_ARG(1) +{ + if (reg->additional_data == additional_data) { + return; + } + if (reg->additional_data != NULL) { + api_free_dictionary(*reg->additional_data); + free(reg->additional_data); + } + reg->additional_data = additional_data; +} + /* * Stuff string "p" into yank register "regname" as a single line (append if * uppercase). "p" must have been alloced. @@ -919,11 +953,13 @@ static int stuff_yank(int regname, char_u *p) *pp = lp; } else { free_register(reg); + set_yreg_additional_data(reg, NULL); reg->y_array = (char_u **)xmalloc(sizeof(char_u *)); reg->y_array[0] = p; reg->y_size = 1; reg->y_type = MCHAR; /* used to be MLINE, why? */ } + reg->timestamp = os_time(); return OK; } @@ -2266,10 +2302,7 @@ int op_change(oparg_T *oap) */ void init_yank(void) { - int i; - - for (i = 0; i < NUM_REGISTERS; i++) - y_regs[i].y_array = NULL; + memset(&(y_regs[0]), 0, sizeof(y_regs)); } #if defined(EXITFREE) @@ -2291,6 +2324,7 @@ void clear_registers(void) void free_register(yankreg_T *reg) FUNC_ATTR_NONNULL_ALL { + set_yreg_additional_data(reg, NULL); if (reg->y_array != NULL) { long i; @@ -2369,6 +2403,8 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) reg->y_type = yanktype; /* set the yank register type */ reg->y_width = 0; reg->y_array = xcalloc(yanklines, sizeof(char_u *)); + set_yreg_additional_data(reg, NULL); + reg->timestamp = os_time(); y_idx = 0; lnum = oap->start.lnum; @@ -4433,171 +4469,6 @@ int do_addsub(int command, linenr_T Prenum1) return OK; } -int read_viminfo_register(vir_T *virp, int force) -{ - int eof; - int do_it = TRUE; - int size; - int limit; - int set_prev = FALSE; - char_u *str; - char_u **array = NULL; - - /* We only get here (hopefully) if line[0] == '"' */ - str = virp->vir_line + 1; - - /* If the line starts with "" this is the y_previous register. */ - if (*str == '"') { - set_prev = TRUE; - str++; - } - - if (!ASCII_ISALNUM(*str) && *str != '-') { - if (viminfo_error("E577: ", _("Illegal register name"), virp->vir_line)) - return TRUE; /* too many errors, pretend end-of-file */ - do_it = FALSE; - } - yankreg_T *reg = get_yank_register(*str++, YREG_PUT); - if (!force && reg->y_array != NULL) - do_it = FALSE; - - if (*str == '@') { - /* "x@: register x used for @@ */ - if (force || execreg_lastc == NUL) - execreg_lastc = str[-1]; - } - - size = 0; - limit = 100; /* Optimized for registers containing <= 100 lines */ - if (do_it) { - if (set_prev) { - y_previous = reg; - } - - free_register(reg); - array = xmalloc(limit * sizeof(char_u *)); - - str = skipwhite(skiptowhite(str)); - if (STRNCMP(str, "CHAR", 4) == 0) { - reg->y_type = MCHAR; - } else if (STRNCMP(str, "BLOCK", 5) == 0) { - reg->y_type = MBLOCK; - } else { - reg->y_type = MLINE; - } - /* get the block width; if it's missing we get a zero, which is OK */ - str = skipwhite(skiptowhite(str)); - reg->y_width = getdigits_int(&str); - } - - while (!(eof = viminfo_readline(virp)) - && (virp->vir_line[0] == TAB || virp->vir_line[0] == '<')) { - if (do_it) { - if (size >= limit) { - limit *= 2; - array = xrealloc(array, limit * sizeof(char_u *)); - } - array[size++] = viminfo_readstring(virp, 1, TRUE); - } - } - - if (do_it) { - if (size == 0) { - xfree(array); - } else if (size < limit) { - reg->y_array = xrealloc(array, size * sizeof(char_u *)); - } else { - reg->y_array = array; - } - reg->y_size = size; - } - return eof; -} - -void write_viminfo_registers(FILE *fp) -{ - int i, j; - char_u *type; - char_u c; - int num_lines; - int max_num_lines; - int max_kbyte; - long len; - - fputs(_("\n# Registers:\n"), fp); - - /* Get '<' value, use old '"' value if '<' is not found. */ - max_num_lines = get_viminfo_parameter('<'); - if (max_num_lines < 0) - max_num_lines = get_viminfo_parameter('"'); - if (max_num_lines == 0) - return; - max_kbyte = get_viminfo_parameter('s'); - if (max_kbyte == 0) - return; - - // don't include clipboard registers '*'/'+' - for (i = 0; i < NUM_SAVED_REGISTERS; i++) { - if (y_regs[i].y_array == NULL) - continue; - - /* Skip empty registers. */ - num_lines = y_regs[i].y_size; - if (num_lines == 0 - || (num_lines == 1 && y_regs[i].y_type == MCHAR - && *y_regs[i].y_array[0] == NUL)) - continue; - - if (max_kbyte > 0) { - /* Skip register if there is more text than the maximum size. */ - len = 0; - for (j = 0; j < num_lines; j++) - len += (long)STRLEN(y_regs[i].y_array[j]) + 1L; - if (len > (long)max_kbyte * 1024L) - continue; - } - - switch (y_regs[i].y_type) { - case MLINE: - type = (char_u *)"LINE"; - break; - case MCHAR: - type = (char_u *)"CHAR"; - break; - case MBLOCK: - type = (char_u *)"BLOCK"; - break; - default: - sprintf((char *)IObuff, _("E574: Unknown register type %d"), - y_regs[i].y_type); - emsg(IObuff); - type = (char_u *)"LINE"; - break; - } - if (y_previous == &y_regs[i]) - fprintf(fp, "\""); - c = get_register_name(i); - fprintf(fp, "\"%c", c); - if (c == execreg_lastc) - fprintf(fp, "@"); - fprintf(fp, "\t%s\t%d\n", type, - (int)y_regs[i].y_width - ); - - /* If max_num_lines < 0, then we save ALL the lines in the register */ - if (max_num_lines > 0 && num_lines > max_num_lines) - num_lines = max_num_lines; - for (j = 0; j < num_lines; j++) { - putc('\t', fp); - viminfo_writestring(fp, y_regs[i].y_array[j]); - } - } -} - - - - - /* * Return the type of a register. * Used for getregtype() @@ -4739,7 +4610,6 @@ void *get_reg_contents(int regname, int flags) return retval; } - static yankreg_T *init_write_reg(int name, yankreg_T **old_y_previous, bool must_append) { if (!valid_yank_reg(name, true)) { // check for valid reg name @@ -4973,6 +4843,8 @@ static void str_to_reg(yankreg_T *y_ptr, int yank_type, const char_u *str, } y_ptr->y_type = type; y_ptr->y_size = lnum; + set_yreg_additional_data(y_ptr, NULL); + y_ptr->timestamp = os_time(); if (type == MBLOCK) { y_ptr->y_width = (blocklen == -1 ? (colnr_T) maxlen - 1 : blocklen); } else { @@ -5363,6 +5235,10 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet) reg->y_array = xcalloc(lines->lv_len, sizeof(uint8_t *)); reg->y_size = lines->lv_len; + reg->additional_data = NULL; + reg->timestamp = 0; + // Timestamp is not saved for clipboard registers because clipboard registers + // are not saved in the viminfo. int i = 0; for (listitem_T *li = lines->lv_first; li != NULL; li = li->li_next) { @@ -5411,6 +5287,8 @@ err: } reg->y_array = NULL; reg->y_size = 0; + reg->additional_data = NULL; + reg->timestamp = 0; if (errmsg) { EMSG("clipboard: provider returned invalid data"); } @@ -5478,3 +5356,70 @@ void end_global_changes(void) clipboard_needs_update = false; } } + +/// Check whether register is empty +static inline bool reg_empty(const yankreg_T *const reg) + FUNC_ATTR_CONST +{ + return (reg->y_array == NULL + || reg->y_size == 0 + || (reg->y_size == 1 + && reg->y_type == MCHAR + && *(reg->y_array[0]) == NUL)); +} + +/// Iterate over registerrs +/// +/// @param[in] iter Iterator. Pass NULL to start iteration. +/// @param[out] name Register name. +/// @param[out] reg Register contents. +/// +/// @return Pointer that needs to be passed to next `op_register_iter` call or +/// NULL if iteration is over. +const void *op_register_iter(const void *const iter, char *const name, + yankreg_T *const reg) + FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT +{ + const yankreg_T *iter_reg = (iter == NULL + ? &(y_regs[0]) + : (const yankreg_T *const) iter); + while (reg_empty(iter_reg) && iter_reg - &(y_regs[0]) < NUM_SAVED_REGISTERS) { + iter_reg++; + } + if (reg_empty(iter_reg)) { + *reg = (yankreg_T) {.y_array = NULL}; + return NULL; + } + size_t iter_off = iter_reg - &(y_regs[0]); + *name = (char) get_register_name(iter_off); + *reg = *iter_reg; + while (++iter_reg - &(y_regs[0]) < NUM_SAVED_REGISTERS) { + if (!reg_empty(iter_reg)) { + return (void *) iter_reg; + } + } + return NULL; +} + +/// Get a number of non-empty registers +size_t op_register_amount(void) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + size_t ret = 0; + for (size_t i = 0; i < NUM_SAVED_REGISTERS; i++) { + if (!reg_empty(y_regs + i)) { + ret++; + } + } + return ret; +} + +/// Set register to a given value +void register_set(const char name, const yankreg_T reg) +{ + int i = reg_index(name); + if (i == -1) { + return; + } + y_regs[i] = reg; +} diff --git a/src/nvim/ops.h b/src/nvim/ops.h index 99683165f9..4da5cfc93d 100644 --- a/src/nvim/ops.h +++ b/src/nvim/ops.h @@ -4,6 +4,8 @@ #include #include "nvim/types.h" +#include "nvim/api/private/defs.h" +#include "nvim/os/time.h" typedef int (*Indenter)(void); @@ -47,14 +49,6 @@ typedef int (*Indenter)(void); #define OP_FORMAT2 26 /* "gw" format operator, keeps cursor pos */ #define OP_FUNCTION 27 /* "g@" call 'operatorfunc' */ -/// Contents of a yank (read-write) register -typedef struct yankreg { - char_u **y_array; ///< pointer to array of line pointers - linenr_T y_size; ///< number of lines in y_array - char_u y_type; ///< MLINE, MCHAR or MBLOCK - colnr_T y_width; ///< only set if y_type == MBLOCK -} yankreg_T; - /// Flags for get_reg_contents(). enum GRegFlags { kGRegNoExpr = 1, ///< Do not allow expression register. @@ -62,6 +56,16 @@ enum GRegFlags { kGRegList = 4 ///< Return list. }; +/// Definition of one register +typedef struct yankreg { + char_u **y_array; ///< Pointer to an array of line pointers. + linenr_T y_size; ///< Number of lines in y_array. + char_u y_type; ///< Register type: MLINE, MCHAR or MBLOCK. + colnr_T y_width; ///< Register width (only valid for y_type == MBLOCK). + Timestamp timestamp; ///< Time when register was last modified. + Dictionary *additional_data; ///< Additional data from ShaDa file. +} yankreg_T; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ops.h.generated.h" #endif diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c index ee17938afc..ba1dcf631a 100644 --- a/src/nvim/os/time.c +++ b/src/nvim/os/time.c @@ -103,3 +103,12 @@ struct tm *os_get_localtime(struct tm *result) FUNC_ATTR_NONNULL_ALL time_t rawtime = time(NULL); return os_localtime_r(&rawtime, result); } + +/// Obtains the current UNIX timestamp +/// +/// @return Seconds since epoch. +Timestamp os_time(void) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + return (Timestamp) time(NULL); +} diff --git a/src/nvim/os/time.h b/src/nvim/os/time.h index b21808307f..3a6b665b51 100644 --- a/src/nvim/os/time.h +++ b/src/nvim/os/time.h @@ -5,6 +5,8 @@ #include #include +typedef time_t Timestamp; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/time.h.generated.h" #endif diff --git a/src/nvim/os/unix_defs.h b/src/nvim/os/unix_defs.h index 9ab4ba1c1a..949973bf40 100644 --- a/src/nvim/os/unix_defs.h +++ b/src/nvim/os/unix_defs.h @@ -43,8 +43,8 @@ #ifndef VIMRC_FILE # define VIMRC_FILE ".nvimrc" #endif -#ifndef VIMINFO_FILE -# define VIMINFO_FILE "~/.nviminfo" +#ifndef SHADA_FILE +# define SHADA_FILE "~/.nvim/shada/main.shada" #endif // Default for 'backupdir'. diff --git a/src/nvim/os/win_defs.h b/src/nvim/os/win_defs.h index 9773b73428..b7ec50a109 100644 --- a/src/nvim/os/win_defs.h +++ b/src/nvim/os/win_defs.h @@ -9,7 +9,7 @@ // Defines needed to fix the build on Windows: // - USR_EXRC_FILE // - USR_VIMRC_FILE -// - VIMINFO_FILE +// - SHADA_FILE // - DFLT_DIR // - DFLT_BDIR // - DFLT_VDIR diff --git a/src/nvim/search.c b/src/nvim/search.c index a758e02105..f8dd7bd482 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -51,6 +51,7 @@ #include "nvim/ui.h" #include "nvim/window.h" #include "nvim/os/time.h" +#include "nvim/api/private/helpers.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -79,23 +80,6 @@ * Henry Spencer's regular expression library. See regexp.c. */ -/* The offset for a search command is store in a soff struct */ -/* Note: only spats[0].off is really used */ -struct soffset { - int dir; /* search direction, '/' or '?' */ - int line; /* search has line offset */ - int end; /* search set cursor at end */ - long off; /* line or char offset */ -}; - -/* A search pattern and its attributes are stored in a spat struct */ -struct spat { - char_u *pat; /* the pattern (in allocated memory) or NULL */ - int magic; /* magicness of the pattern */ - int no_scs; /* no smartcase for this pattern */ - struct soffset off; -}; - /* * Two search patterns are remembered: One for the :substitute command and * one for other searches. last_idx points to the one that was used the last @@ -103,8 +87,10 @@ struct spat { */ static struct spat spats[2] = { - {NULL, TRUE, FALSE, {'/', 0, 0, 0L}}, /* last used search pat */ - {NULL, TRUE, FALSE, {'/', 0, 0, 0L}} /* last used substitute pat */ + // Last used search pattern + [0] = {NULL, true, false, 0, {'/', false, false, 0L}, NULL}, + // Last used substitute pattern + [1] = {NULL, true, false, 0, {'/', false, false, 0L}, NULL} }; static int last_idx = 0; /* index in spats[] for RE_LAST */ @@ -256,10 +242,12 @@ char_u *reverse_text(char_u *s) FUNC_ATTR_NONNULL_RET void save_re_pat(int idx, char_u *pat, int magic) { if (spats[idx].pat != pat) { - xfree(spats[idx].pat); + free_spat(&spats[idx]); spats[idx].pat = vim_strsave(pat); spats[idx].magic = magic; spats[idx].no_scs = no_smartcase; + spats[idx].timestamp = os_time(); + spats[idx].additional_data = NULL; last_idx = idx; /* If 'hlsearch' set and search pat changed: need redraw. */ if (p_hls) @@ -291,21 +279,30 @@ void save_search_patterns(void) void restore_search_patterns(void) { if (--save_level == 0) { - xfree(spats[0].pat); + free_spat(&spats[0]); spats[0] = saved_spats[0]; set_vv_searchforward(); - xfree(spats[1].pat); + free_spat(&spats[1]); spats[1] = saved_spats[1]; last_idx = saved_last_idx; SET_NO_HLSEARCH(saved_no_hlsearch); } } +static inline void free_spat(struct spat *const spat) +{ + xfree(spat->pat); + if (spat->additional_data != NULL) { + api_free_dictionary(*spat->additional_data); + xfree(spat->additional_data); + } +} + #if defined(EXITFREE) void free_search_patterns(void) { - xfree(spats[0].pat); - xfree(spats[1].pat); + free_spat(&spats[0]); + free_spat(&spats[1]); if (mr_pattern_alloced) { xfree(mr_pattern); @@ -414,17 +411,19 @@ void reset_search_dir(void) } /* - * Set the last search pattern. For ":let @/ =" and viminfo. + * Set the last search pattern. For ":let @/ =" and ShaDa file. * Also set the saved search pattern, so that this works in an autocommand. */ void set_last_search_pat(const char_u *s, int idx, int magic, int setlast) { - xfree(spats[idx].pat); + free_spat(&spats[idx]); /* An empty string means that nothing should be matched. */ if (*s == NUL) spats[idx].pat = NULL; else spats[idx].pat = (char_u *) xstrdup((char *) s); + spats[idx].timestamp = os_time(); + spats[idx].additional_data = NULL; spats[idx].magic = magic; spats[idx].no_scs = FALSE; spats[idx].off.dir = '/'; @@ -435,7 +434,7 @@ void set_last_search_pat(const char_u *s, int idx, int magic, int setlast) if (setlast) last_idx = idx; if (save_level) { - xfree(saved_spats[idx].pat); + free_spat(&saved_spats[idx]); saved_spats[idx] = spats[0]; if (spats[idx].pat == NULL) saved_spats[idx].pat = NULL; @@ -1053,7 +1052,7 @@ int do_search( else if ((options & SEARCH_OPT) && (*p == 'e' || *p == 's' || *p == 'b')) { if (*p == 'e') /* end */ - spats[0].off.end = SEARCH_END; + spats[0].off.end = true; ++p; } if (ascii_isdigit(*p) || *p == '+' || *p == '-') { /* got an offset */ @@ -1166,12 +1165,13 @@ int do_search( lrFswap(searchstr,0); c = searchit(curwin, curbuf, &pos, dirc == '/' ? FORWARD : BACKWARD, - searchstr, count, spats[0].off.end + (options & - (SEARCH_KEEP + SEARCH_PEEK + - SEARCH_HIS - + SEARCH_MSG + SEARCH_START - + ((pat != NULL && *pat == - ';') ? 0 : SEARCH_NOOF))), + searchstr, count, (spats[0].off.end * SEARCH_END + + (options & + (SEARCH_KEEP + SEARCH_PEEK + + SEARCH_HIS + + SEARCH_MSG + SEARCH_START + + ((pat != NULL && *pat == + ';') ? 0 : SEARCH_NOOF)))), RE_LAST, (linenr_T)0, tm); if (dircp != NULL) @@ -4605,105 +4605,45 @@ static void show_pat_in_path(char_u *line, int type, int did_show, int action, F } } -int read_viminfo_search_pattern(vir_T *virp, int force) +/// Get last search pattern +void get_search_pattern(SearchPattern *const pat) { - char_u *lp; - int idx = -1; - int magic = FALSE; - int no_scs = FALSE; - int off_line = FALSE; - int off_end = 0; - long off = 0; - int setlast = FALSE; - static int hlsearch_on = FALSE; - char_u *val; + memcpy(pat, &(spats[0]), sizeof(spats[0])); +} - /* - * Old line types: - * "/pat", "&pat": search/subst. pat - * "~/pat", "~&pat": last used search/subst. pat - * New line types: - * "~h", "~H": hlsearch highlighting off/on - * "~pat" - * : 'm' off, 'M' on - * : 's' off, 'S' on - * : 'L' line offset, 'l' char offset - * : 'E' from end, 'e' from start - * : decimal, offset - * : '~' last used pattern - * : '/' search pat, '&' subst. pat - */ - lp = virp->vir_line; - if (lp[0] == '~' && (lp[1] == 'm' || lp[1] == 'M')) { /* new line type */ - if (lp[1] == 'M') /* magic on */ - magic = TRUE; - if (lp[2] == 's') - no_scs = TRUE; - if (lp[3] == 'L') - off_line = TRUE; - if (lp[4] == 'E') - off_end = SEARCH_END; - lp += 5; - off = getdigits_long(&lp); - } - if (lp[0] == '~') { /* use this pattern for last-used pattern */ - setlast = TRUE; - lp++; - } - if (lp[0] == '/') - idx = RE_SEARCH; - else if (lp[0] == '&') - idx = RE_SUBST; - else if (lp[0] == 'h') /* ~h: 'hlsearch' highlighting off */ - hlsearch_on = FALSE; - else if (lp[0] == 'H') /* ~H: 'hlsearch' highlighting on */ - hlsearch_on = TRUE; - if (idx >= 0) { - if (force || spats[idx].pat == NULL) { - val = viminfo_readstring(virp, (int)(lp - virp->vir_line + 1), TRUE); - set_last_search_pat(val, idx, magic, setlast); - xfree(val); - spats[idx].no_scs = no_scs; - spats[idx].off.line = off_line; - spats[idx].off.end = off_end; - spats[idx].off.off = off; - if (setlast) { - SET_NO_HLSEARCH(!hlsearch_on); - } - } - } - return viminfo_readline(virp); +/// Get last substitute pattern +void get_substitute_pattern(SearchPattern *const pat) +{ + memcpy(pat, &(spats[1]), sizeof(spats[1])); + memset(&(pat->off), 0, sizeof(pat->off)); } -void write_viminfo_search_pattern(FILE *fp) +/// Set last search pattern +void set_search_pattern(const SearchPattern pat) { - if (get_viminfo_parameter('/') != 0) { - fprintf(fp, "\n# hlsearch on (H) or off (h):\n~%c", - (no_hlsearch || find_viminfo_parameter('h') != NULL) ? 'h' : 'H'); - wvsp_one(fp, RE_SEARCH, "", '/'); - wvsp_one(fp, RE_SUBST, _("Substitute "), '&'); - } + free_spat(&spats[0]); + memcpy(&(spats[0]), &pat, sizeof(spats[0])); } -static void -wvsp_one ( - FILE *fp, /* file to write to */ - int idx, /* spats[] index */ - char *s, /* search pat */ - int sc /* dir char */ -) +/// Set last substitute pattern +void set_substitute_pattern(const SearchPattern pat) { - if (spats[idx].pat != NULL) { - fprintf(fp, _("\n# Last %sSearch Pattern:\n~"), s); - /* off.dir is not stored, it's reset to forward */ - fprintf(fp, "%c%c%c%c%" PRId64 "%s%c", - spats[idx].magic ? 'M' : 'm', /* magic */ - spats[idx].no_scs ? 's' : 'S', /* smartcase */ - spats[idx].off.line ? 'L' : 'l', /* line offset */ - spats[idx].off.end ? 'E' : 'e', /* offset from end */ - (int64_t)spats[idx].off.off, /* offset */ - last_idx == idx ? "~" : "", /* last used pat */ - sc); - viminfo_writestring(fp, spats[idx].pat); - } + free_spat(&spats[1]); + memcpy(&(spats[1]), &pat, sizeof(spats[1])); + memset(&(spats[1].off), 0, sizeof(spats[1].off)); +} + +/// Set last used search pattern +/// +/// @param[in] is_substitute_pattern If true set substitute pattern as last +/// used. Otherwise sets search pattern. +void set_last_used_pattern(const bool is_substitute_pattern) +{ + last_idx = (is_substitute_pattern ? 1 : 0); +} + +/// Returns true if search pattern was the last used one +bool search_was_last_used(void) +{ + return last_idx == 0; } diff --git a/src/nvim/search.h b/src/nvim/search.h index 1bcde2c7ab..691782e41c 100644 --- a/src/nvim/search.h +++ b/src/nvim/search.h @@ -1,6 +1,9 @@ #ifndef NVIM_SEARCH_H #define NVIM_SEARCH_H +#include +#include + /* Values for the find_pattern_in_path() function args 'type' and 'action': */ #define FIND_ANY 1 #define FIND_DEFINE 2 @@ -39,6 +42,27 @@ #define RE_BOTH 2 /* save pat in both patterns */ #define RE_LAST 2 /* use last used pattern if "pat" is NULL */ +/// Structure containing offset definition for the last search pattern +/// +/// @note Only offset for the last search pattern is used, not for the last +/// substitute pattern. +typedef struct soffset { + char dir; ///< Search direction: forward ('/') or backward ('?') + bool line; ///< True if search has line offset. + bool end; ///< True if search sets cursor at the end. + int64_t off; ///< Actual offset value. +} SearchOffset; + +/// Structure containing last search pattern and its attributes. +typedef struct spat { + char_u *pat; ///< The pattern (in allocated memory) or NULL. + bool magic; ///< Magicness of the pattern. + bool no_scs; ///< No smartcase for this pattern. + Timestamp timestamp; ///< Time of the last change. + SearchOffset off; ///< Pattern offset. + Dictionary *additional_data; ///< Additional data from ShaDa file. +} SearchPattern; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "search.h.generated.h" #endif diff --git a/src/nvim/shada.c b/src/nvim/shada.c new file mode 100644 index 0000000000..73aaa311b4 --- /dev/null +++ b/src/nvim/shada.c @@ -0,0 +1,3454 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "nvim/os/os.h" +#include "nvim/os/time.h" +#include "nvim/vim.h" +#include "nvim/ascii.h" +#include "nvim/shada.h" +#include "nvim/message.h" +#include "nvim/globals.h" +#include "nvim/macros.h" +#include "nvim/memory.h" +#include "nvim/mark.h" +#include "nvim/ops.h" +#include "nvim/garray.h" +#include "nvim/option.h" +#include "nvim/msgpack_rpc/helpers.h" +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" +#include "nvim/globals.h" +#include "nvim/buffer.h" +#include "nvim/misc2.h" +#include "nvim/ex_getln.h" +#include "nvim/search.h" +#include "nvim/eval.h" +#include "nvim/eval_defs.h" +#include "nvim/version.h" +#include "nvim/path.h" + +#define buflist_nr2name(...) ((char *) buflist_nr2name(__VA_ARGS__)) +#define copy_option_part(src, dest, ...) \ + ((char *) copy_option_part((char_u **) src, (char_u *) dest, __VA_ARGS__)) +#define find_viminfo_parameter(...) \ + ((const char *) find_viminfo_parameter(__VA_ARGS__)) +#define emsg2(a, b) emsg2((char_u *) a, (char_u *) b) +#define emsg3(a, b, c) emsg3((char_u *) a, (char_u *) b, (char_u *) c) +#define emsgn(a, ...) emsgn((char_u *) a, __VA_ARGS__) +#define home_replace_save(a, b) \ + ((char *)home_replace_save(a, (char_u *)b)) + +/// Possible ShaDa entry types +/// +/// @warning Enum values are part of the API and must not be altered. +/// +/// All values that are not in enum are ignored. +typedef enum { + kSDItemUnknown = -1, ///< Unknown item. + kSDItemMissing = 0, ///< Missing value. Should never appear in a file. + kSDItemHeader = 1, ///< Header. Present for debugging purposes. + kSDItemSearchPattern = 2, ///< Last search pattern (*not* history item). + ///< Comes from user searches (e.g. when typing + ///< "/pat") or :substitute command calls. + kSDItemSubString = 3, ///< Last substitute replacement string. + kSDItemHistoryEntry = 4, ///< History item. + kSDItemRegister = 5, ///< Register. + kSDItemVariable = 6, ///< Global variable. + kSDItemGlobalMark = 7, ///< Global mark definition. + kSDItemJump = 8, ///< Item from jump list. + kSDItemBufferList = 9, ///< Buffer list. + kSDItemLocalMark = 10, ///< Buffer-local mark. +#define SHADA_LAST_ENTRY ((uint64_t) kSDItemLocalMark) +} ShadaEntryType; + +/// Structure defining a single ShaDa file entry +typedef struct { + ShadaEntryType type; + Timestamp timestamp; + union { + Dictionary header; + struct shada_filemark { + char name; + pos_T mark; + char *fname; + Dictionary *additional_data; + } filemark; + struct search_pattern { + bool magic; + bool smartcase; + bool has_line_offset; + bool place_cursor_at_end; + int64_t offset; + bool is_last_used; + bool is_substitute_pattern; + char *pat; + Dictionary *additional_data; + } search_pattern; + struct history_item { + uint8_t histtype; + char *string; + Array *additional_elements; + } history_item; + struct reg { + char name; + uint8_t type; + char **contents; + size_t contents_size; + size_t width; + Dictionary *additional_data; + } reg; + struct global_var { + char *name; + Object value; + Array *additional_elements; + } global_var; + struct { + uint64_t type; + char *contents; + size_t size; + } unknown_item; + struct sub_string { + char *sub; + Array *additional_elements; + } sub_string; + Array buffer_list; + } data; +} ShadaEntry; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "shada.c.generated.h" +#endif + +/// Msgpack callback for writing to FILE* +static int msgpack_fbuffer_write(void *data, const char *buf, size_t len) +{ + return (fwrite(buf, len, 1, (FILE *) data) == 1) ? 0 : -1; +} + +/// Check whether writing to shada file was disabled with -i NONE +/// +/// @return true if it was disabled, false otherwise. +static bool shada_disabled(void) + FUNC_ATTR_PURE +{ + return used_shada_file != NULL && STRCMP(used_shada_file, "NONE") == 0; +} + +/// Read ShaDa file +/// +/// @param[in] file File to read or NULL to use default name. +/// @param[in] flags Flags, see kShaDa enum values in shada.h. +/// +/// @return FAIL if reading failed for some reason and OK otherwise. +int shada_read_file(const char *const file, const int flags) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + FILE *fp; + + if (shada_disabled()) { + return FAIL; + } + + char *const fname = shada_filename(file); + fp = mch_fopen(fname, READBIN); + + if (p_verbose > 0) { + verbose_enter(); + smsg(_("Reading viminfo file \"%s\"%s%s%s"), + fname, + (flags & kShaDaWantInfo) ? _(" info") : "", + (flags & kShaDaWantMarks) ? _(" marks") : "", + (flags & kShaDaGetOldfiles) ? _(" oldfiles") : "", + fp == NULL ? _(" FAILED") : ""); + verbose_leave(); + } + + xfree(fname); + if (fp == NULL) { + return FAIL; + } + + shada_read(fp, flags); + + fclose(fp); + return OK; +} + +/// Read data from ShaDa file +/// +/// @param[in] fp File to read from. +/// @param[in] flags What to read. +static void shada_read(FILE *const fp, const int flags) + FUNC_ATTR_NONNULL_ALL +{ + ShadaEntry cur_entry; + buf_T *local_mark_prev_buf = NULL; + char *local_mark_prev_fname = NULL; + size_t local_mark_prev_fname_len = 0; + while (shada_read_next_item(fp, &cur_entry) == NOTDONE) { + switch (cur_entry.type) { + case kSDItemMissing: { + assert(false); + } + case kSDItemUnknown: { + break; + } + case kSDItemHeader: { + shada_free_shada_entry(&cur_entry); + break; + } + case kSDItemSearchPattern: { + if (!(flags & kShaDaWantInfo)) { + shada_free_shada_entry(&cur_entry); + break; + } + (cur_entry.data.search_pattern.is_substitute_pattern + ? &set_substitute_pattern + : &set_search_pattern)((SearchPattern) { + .magic = cur_entry.data.search_pattern.magic, + .no_scs = !cur_entry.data.search_pattern.smartcase, + .off = { + .line = cur_entry.data.search_pattern.has_line_offset, + .end = cur_entry.data.search_pattern.place_cursor_at_end, + .off = cur_entry.data.search_pattern.offset, + }, + .pat = (char_u *) cur_entry.data.search_pattern.pat, + .additional_data = cur_entry.data.search_pattern.additional_data, + .timestamp = cur_entry.timestamp, + }); + if (cur_entry.data.search_pattern.is_last_used) { + set_last_used_pattern( + cur_entry.data.search_pattern.is_substitute_pattern); + } + // Do not free shada entry: its allocated memory was saved above. + break; + } + case kSDItemSubString: { + if (!(flags & kShaDaWantInfo)) { + shada_free_shada_entry(&cur_entry); + break; + } + sub_set_replacement((SubReplacementString) { + .sub = cur_entry.data.sub_string.sub, + .timestamp = cur_entry.timestamp, + .additional_elements = cur_entry.data.sub_string.additional_elements, + }); + // Do not free shada entry: its allocated memory was saved above. + break; + } + case kSDItemHistoryEntry: { + if (!(flags & kShaDaWantInfo)) { + shada_free_shada_entry(&cur_entry); + break; + } + // FIXME + shada_free_shada_entry(&cur_entry); + break; + } + case kSDItemRegister: { + if (!(flags & kShaDaWantInfo)) { + shada_free_shada_entry(&cur_entry); + break; + } + if (cur_entry.data.reg.type != MCHAR + && cur_entry.data.reg.type != MLINE + && cur_entry.data.reg.type != MBLOCK) { + shada_free_shada_entry(&cur_entry); + break; + } + register_set(cur_entry.data.reg.name, (yankreg_T) { + .y_array = (char_u **) cur_entry.data.reg.contents, + .y_size = (linenr_T) cur_entry.data.reg.contents_size, + .y_type = cur_entry.data.reg.type, + .y_width = (colnr_T) cur_entry.data.reg.width, + .timestamp = cur_entry.timestamp, + .additional_data = cur_entry.data.reg.additional_data, + }); + // Do not free shada entry: its allocated memory was saved above. + break; + } + case kSDItemVariable: { + if (!(flags & kShaDaWantInfo) || find_viminfo_parameter('!') == NULL) { + shada_free_shada_entry(&cur_entry); + break; + } + typval_T vartv; + Error err; + if (!object_to_vim(cur_entry.data.global_var.value, &vartv, &err)) { + if (err.set) { + emsg3("Error while reading ShaDa file: " + "failed to read value for variable %s: %s", + cur_entry.data.global_var.name, err.msg); + } + break; + } + var_set_global(cur_entry.data.global_var.name, vartv); + shada_free_shada_entry(&cur_entry); + break; + } + case kSDItemGlobalMark: { + if (!(flags & kShaDaWantMarks) || get_viminfo_parameter('f') == 0) { + shada_free_shada_entry(&cur_entry); + break; + } + mark_set_global(cur_entry.data.filemark.name, (xfmark_T) { + .fname = (char_u *) cur_entry.data.filemark.fname, + .fmark = { + .mark = cur_entry.data.filemark.mark, + .fnum = 0, + .timestamp = cur_entry.timestamp, + .additional_data = cur_entry.data.filemark.additional_data, + }, + }, !(flags & kShaDaForceit)); + break; + } + case kSDItemJump: { + // FIXME + shada_free_shada_entry(&cur_entry); + break; + } + case kSDItemBufferList: { + // FIXME + shada_free_shada_entry(&cur_entry); + break; + } + case kSDItemLocalMark: { + if (!(flags & kShaDaWantMarks)) { + shada_free_shada_entry(&cur_entry); + break; + } + buf_T *buf = NULL; + if (local_mark_prev_fname != NULL + && strncmp(local_mark_prev_fname, + cur_entry.data.filemark.fname, + local_mark_prev_fname_len) == 0) { + buf = local_mark_prev_buf; + } else { + FOR_ALL_BUFFERS(bp) { + if (bp->b_ffname != NULL && bp != local_mark_prev_buf) { + if (fnamecmp(cur_entry.data.filemark.fname, bp->b_ffname) == 0) { + buf = bp; + break; + } + } + } + xfree(local_mark_prev_fname); + local_mark_prev_buf = buf; + local_mark_prev_fname_len = strlen(cur_entry.data.filemark.fname); + local_mark_prev_fname = xmemdupz(cur_entry.data.filemark.fname, + local_mark_prev_fname_len); + } + if (buf == NULL) { + break; + } + mark_set_local( + cur_entry.data.filemark.name, buf, + (fmark_T) { + .mark = cur_entry.data.filemark.mark, + .fnum = 0, + .timestamp = cur_entry.timestamp, + .additional_data = cur_entry.data.filemark.additional_data, + }, !(flags & kShaDaForceit)); + free(cur_entry.data.filemark.fname); + break; + } + } + } + xfree(local_mark_prev_fname); + FOR_ALL_BUFFERS(buf) { + fmarks_check_names(buf); + } +} + +/// Get the ShaDa file name to use +/// +/// If "file" is given and not empty, use it (has already been expanded by +/// cmdline functions). Otherwise use "-i file_name", value from 'viminfo' or +/// the default, and expand environment variables. +/// +/// @param[in] file Forced file name or NULL. +/// +/// @return An allocated string containing shada file name. +static char *shada_filename(const char *file) + FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (file == NULL || *file == NUL) { + if (used_shada_file != NULL) { + file = used_shada_file; + } else if ((file = find_viminfo_parameter('n')) == NULL || *file == NUL) { +#ifdef SHADA_FILE2 + // don't use $HOME when not defined (turned into "c:/"!). + if (os_getenv((char_u *)"HOME") == NULL) { + // don't use $VIM when not available. + expand_env((char_u *)"$VIM", NameBuff, MAXPATHL); + if (STRCMP("$VIM", NameBuff) != 0) { // $VIM was expanded + file = SHADA_FILE2; + } else { + file = SHADA_FILE; + } + } else { +#endif + file = SHADA_FILE; +#ifdef SHADA_FILE2 + } +#endif + // XXX It used to be one level lower, so that whatever is in + // `used_shada_file` was expanded. I intentionally moved it here + // because various expansions must have already be done by the shell. + // If shell is not performing them then they should be done in main.c + // where arguments are parsed, *not here*. + expand_env((char_u *)file, &(NameBuff[0]), MAXPATHL); + file = (const char *) &(NameBuff[0]); + } + } + return xstrdup(file); +} + +#define PACK_STATIC_STR(s) \ + do { \ + msgpack_pack_str(spacker, sizeof(s) - 1); \ + msgpack_pack_str_body(spacker, s, sizeof(s) - 1); \ + } while (0) + +/// Write single ShaDa entry +/// +/// @param[in] packer Packer used to write entry. +/// @param[in] entry Entry written. +/// @param[in] max_kbyte Maximum size of an item in KiB. Zero means no +/// restrictions. +static void shada_pack_entry(msgpack_packer *const packer, + const ShadaEntry entry, + const size_t max_kbyte) + FUNC_ATTR_NONNULL_ALL +{ + if (entry.type == kSDItemMissing) { + return; + } + msgpack_sbuffer sbuf; + msgpack_sbuffer_init(&sbuf); + msgpack_packer *spacker = msgpack_packer_new(&sbuf, &msgpack_sbuffer_write); + switch (entry.type) { + case kSDItemMissing: { + assert(false); + } + case kSDItemUnknown: { + msgpack_pack_uint64(packer, (uint64_t) entry.data.unknown_item.size); + packer->callback(packer->data, entry.data.unknown_item.contents, + (unsigned) entry.data.unknown_item.size); + break; + } + case kSDItemHistoryEntry: { + const size_t arr_size = 2 + ( + entry.data.history_item.additional_elements == NULL + ? 0 + : entry.data.history_item.additional_elements->size); + msgpack_pack_array(spacker, arr_size); + msgpack_pack_uint8(spacker, entry.data.history_item.histtype); + msgpack_rpc_from_string(cstr_as_string(entry.data.history_item.string), + spacker); + for (size_t i = 0; i < arr_size - 2; i++) { + msgpack_rpc_from_object( + entry.data.history_item.additional_elements->items[i], spacker); + } + break; + } + case kSDItemVariable: { + const size_t arr_size = 2 + ( + entry.data.global_var.additional_elements == NULL + ? 0 + : entry.data.global_var.additional_elements->size); + msgpack_pack_array(spacker, arr_size); + msgpack_rpc_from_string(cstr_as_string(entry.data.global_var.name), + spacker); + msgpack_rpc_from_object(entry.data.global_var.value, spacker); + for (size_t i = 0; i < arr_size - 2; i++) { + msgpack_rpc_from_object( + entry.data.global_var.additional_elements->items[i], spacker); + } + break; + } + case kSDItemSubString: { + const size_t arr_size = 1 + ( + entry.data.sub_string.additional_elements == NULL + ? 0 + : entry.data.sub_string.additional_elements->size); + msgpack_pack_array(spacker, arr_size); + msgpack_rpc_from_string(cstr_as_string(entry.data.sub_string.sub), + spacker); + for (size_t i = 0; i < arr_size - 1; i++) { + msgpack_rpc_from_object( + entry.data.sub_string.additional_elements->items[i], spacker); + } + break; + } + case kSDItemSearchPattern: { + const size_t map_size = (size_t) ( + 1 // Search pattern is always present + // Following items default to true: + + !entry.data.search_pattern.magic + + !entry.data.search_pattern.is_last_used + // Following items default to false: + + entry.data.search_pattern.smartcase + + entry.data.search_pattern.has_line_offset + + entry.data.search_pattern.place_cursor_at_end + + entry.data.search_pattern.is_substitute_pattern + // offset defaults to zero: + + (entry.data.search_pattern.offset != 0) + // finally, additional data: + + (entry.data.search_pattern.additional_data + ? entry.data.search_pattern.additional_data->size + : 0) + ); + msgpack_pack_map(spacker, map_size); + PACK_STATIC_STR("pat"); + msgpack_rpc_from_string(cstr_as_string(entry.data.search_pattern.pat), + spacker); +#define PACK_BOOL(name, attr, nondef_value) \ + do { \ + if (entry.data.search_pattern.attr == nondef_value) { \ + PACK_STATIC_STR(name); \ + msgpack_pack_##nondef_value(spacker); \ + } \ + } while (0) + PACK_BOOL("magic", magic, false); + PACK_BOOL("islast", is_last_used, false); + PACK_BOOL("smartcase", smartcase, true); + PACK_BOOL("lineoff", has_line_offset, true); + PACK_BOOL("curatend", place_cursor_at_end, true); + PACK_BOOL("sub", is_substitute_pattern, true); + if (entry.data.search_pattern.offset) { + PACK_STATIC_STR("off"); + msgpack_pack_int64(spacker, entry.data.search_pattern.offset); + } +#undef PACK_BOOL + if (entry.data.search_pattern.additional_data != NULL) { + for (size_t i = 0; i < entry.data.search_pattern.additional_data->size; + i++) { + msgpack_rpc_from_string( + entry.data.search_pattern.additional_data->items[i].key, spacker); + msgpack_rpc_from_object( + entry.data.search_pattern.additional_data->items[i].value, + spacker); + } + } + break; + } + case kSDItemGlobalMark: + case kSDItemLocalMark: + case kSDItemJump: { + const size_t map_size = (size_t) ( + 1 // File name + // Line: defaults to 1 + + (entry.data.filemark.mark.lnum != 1) + // Column: defaults to zero: + + (entry.data.filemark.mark.col != 0) + // Mark name: defaults to '"' + + (entry.type != kSDItemJump + && entry.data.filemark.name != '"') + // Additional entries, if any: + + (entry.data.filemark.additional_data == NULL + ? 0 + : entry.data.filemark.additional_data->size) + ); + msgpack_pack_map(spacker, map_size); + PACK_STATIC_STR("file"); + msgpack_rpc_from_string(cstr_as_string(entry.data.filemark.fname), + spacker); + if (entry.data.filemark.mark.lnum != 1) { + PACK_STATIC_STR("line"); + msgpack_pack_long(spacker, entry.data.filemark.mark.lnum); + } + if (entry.data.filemark.mark.col != 0) { + PACK_STATIC_STR("col"); + msgpack_pack_long(spacker, entry.data.filemark.mark.col); + } + if (entry.data.filemark.name != '"' && entry.type != kSDItemJump) { + PACK_STATIC_STR("name"); + msgpack_pack_uint8(spacker, (uint8_t) entry.data.filemark.name); + } + if (entry.data.filemark.additional_data != NULL) { + for (size_t i = 0; i < entry.data.filemark.additional_data->size; + i++) { + msgpack_rpc_from_string( + entry.data.filemark.additional_data->items[i].key, spacker); + msgpack_rpc_from_object( + entry.data.filemark.additional_data->items[i].value, spacker); + } + } + break; + } + case kSDItemRegister: { + const size_t map_size = (size_t) ( + 2 // Register contents and name + // Register type: defaults to MCHAR + + (entry.data.reg.type != MCHAR) + // Register width: defaults to zero + + (entry.data.reg.width != 0) + // Additional entries, if any: + + (entry.data.reg.additional_data == NULL + ? 0 + : entry.data.reg.additional_data->size) + ); + msgpack_pack_map(spacker, map_size); + PACK_STATIC_STR("contents"); + msgpack_pack_array(spacker, entry.data.reg.contents_size); + for (size_t i = 0; i < entry.data.reg.contents_size; i++) { + msgpack_rpc_from_string(cstr_as_string(entry.data.reg.contents[i]), + spacker); + } + PACK_STATIC_STR("name"); + msgpack_pack_char(spacker, entry.data.reg.name); + if (entry.data.reg.type != MCHAR) { + PACK_STATIC_STR("type"); + msgpack_pack_uint8(spacker, entry.data.reg.type); + } + if (entry.data.reg.width != 0) { + PACK_STATIC_STR("width"); + msgpack_pack_uint64(spacker, (uint64_t) entry.data.reg.width); + } + if (entry.data.reg.additional_data != NULL) { + for (size_t i = 0; i < entry.data.reg.additional_data->size; + i++) { + msgpack_rpc_from_string(entry.data.reg.additional_data->items[i].key, + spacker); + msgpack_rpc_from_object( + entry.data.reg.additional_data->items[i].value, spacker); + } + } + break; + } + case kSDItemBufferList: { + msgpack_rpc_from_array(entry.data.buffer_list, spacker); + break; + } + case kSDItemHeader: { + msgpack_rpc_from_dictionary(entry.data.header, spacker); + break; + } + } + if (!max_kbyte || sbuf.size <= max_kbyte * 1024) { + if (entry.type == kSDItemUnknown) { + msgpack_pack_uint64(packer, (uint64_t) entry.data.unknown_item.type); + } else { + msgpack_pack_uint64(packer, (uint64_t) entry.type); + } + msgpack_pack_uint64(packer, (uint64_t) entry.timestamp); + if (sbuf.size > 0) { + msgpack_pack_uint64(packer, (uint64_t) sbuf.size); + packer->callback(packer->data, sbuf.data, (unsigned) sbuf.size); + } + } + msgpack_packer_free(spacker); + msgpack_sbuffer_destroy(&sbuf); +} + +/// Write ShaDa file +/// +/// @param[in] newfp File pointer to write to. Must not be NULL. +/// @param[in] oldfp Pointer to the previous ShaDa file. If it is not NULL +/// then contents of this file will be merged with current +/// NeoVim runtime. +static void shada_write(FILE *const newfp, FILE *const oldfp) + FUNC_ATTR_NONNULL_ARG(1) +{ + int max_kbyte_i = get_viminfo_parameter('s'); + if (max_kbyte_i < 0) { + max_kbyte_i = 10; + } + if (max_kbyte_i == 0) { + return; + } + const size_t max_kbyte = (size_t) max_kbyte_i; + + msgpack_packer *packer = msgpack_packer_new(newfp, &msgpack_fbuffer_write); + + // First write values that do not require merging + // 1. Header + shada_pack_entry(packer, (ShadaEntry) { + .type = kSDItemHeader, + .timestamp = os_time(), + .data = { + .header = { + .size = 3, + .capacity = 3, + .items = ((KeyValuePair []) { + { STATIC_CSTR_AS_STRING("version"), + STRING_OBJ(cstr_as_string(longVersion)) }, + { STATIC_CSTR_AS_STRING("max_kbyte"), + INTEGER_OBJ((Integer) max_kbyte) }, + { STATIC_CSTR_AS_STRING("pid"), + INTEGER_OBJ((Integer) os_get_pid()) }, + }), + } + } + }, 0); + fflush(newfp); + + // 2. Buffer list + if (find_viminfo_parameter('%') != NULL) { + msgpack_pack_uint64(packer, (uint64_t) kSDItemBufferList); + msgpack_pack_uint64(packer, (uint64_t) os_time()); + msgpack_sbuffer sbuf; + msgpack_sbuffer_init(&sbuf); + msgpack_packer *spacker = msgpack_packer_new(&sbuf, &msgpack_sbuffer_write); + + size_t buf_count = 0; + FOR_ALL_BUFFERS(buf) { + if (buf->b_ffname != NULL) { + buf_count++; + } + } + msgpack_pack_array(packer, buf_count); + FOR_ALL_BUFFERS(buf) { + if (buf->b_ffname == NULL) { + continue; + } + msgpack_pack_map(packer, 3); + PACK_STATIC_STR("fname"); + msgpack_rpc_from_string(cstr_as_string((char *) buf->b_ffname), packer); + PACK_STATIC_STR("lnum"); + msgpack_pack_uint64(packer, (uint64_t) buf->b_last_cursor.mark.lnum); + PACK_STATIC_STR("col"); + msgpack_pack_uint64(packer, (uint64_t) buf->b_last_cursor.mark.col); + } + msgpack_pack_uint64(packer, (uint64_t) sbuf.size); + packer->callback(packer->data, sbuf.data, (unsigned) sbuf.size); + msgpack_packer_free(spacker); + msgpack_sbuffer_destroy(&sbuf); + } + + // 3. Jump list + const void *jump_iter = NULL; + do { + xfmark_T fm; + jump_iter = mark_jumplist_iter(jump_iter, curwin, &fm); + char *fname = (fm.fmark.fnum == 0 + ? (fm.fname == NULL + ? NULL + : xstrdup((char *) fm.fname)) + : buflist_nr2name(fm.fmark.fnum, true, false)); + shada_pack_entry(packer, (ShadaEntry) { + .type = kSDItemJump, + .timestamp = fm.fmark.timestamp, + .data = { + .filemark = { + .name = NUL, + .mark = fm.fmark.mark, + .fname = fname, + .additional_data = fm.fmark.additional_data, + } + } + }, max_kbyte); + xfree(fname); + } while (jump_iter != NULL); + + // FIXME No merging currently + + // 4. History + const void *hist_iters[HIST_COUNT] = {NULL, NULL, NULL, NULL, NULL}; + for (uint8_t i = 0; i < HIST_COUNT; i++) { + do { + histentry_T cur_hist; + hist_iters[i] = hist_iter(hist_iters[i], i, false, &cur_hist); + if (cur_hist.hisstr == NULL) { + break; + } + shada_pack_entry(packer, (ShadaEntry) { + .type = kSDItemHistoryEntry, + .timestamp = cur_hist.timestamp, + .data = { + .history_item = { + .histtype = i, + .string = (char *) cur_hist.hisstr, + .additional_elements = cur_hist.additional_elements, + } + } + }, max_kbyte); + } while (hist_iters[i] != NULL); + } + + // 5. Search patterns + SearchPattern pat; + get_search_pattern(&pat); + shada_pack_entry(packer, (ShadaEntry) { + .type = kSDItemSearchPattern, + .timestamp = pat.timestamp, + .data = { + .search_pattern = { + .magic = pat.magic, + .smartcase = !pat.no_scs, + .has_line_offset = pat.off.line, + .place_cursor_at_end = pat.off.end, + .offset = pat.off.off, + .is_last_used = search_was_last_used(), + .is_substitute_pattern = false, + .pat = (char *) pat.pat, + .additional_data = pat.additional_data, + } + } + }, max_kbyte); + get_substitute_pattern(&pat); + shada_pack_entry(packer, (ShadaEntry) { + .type = kSDItemSearchPattern, + .timestamp = pat.timestamp, + .data = { + .search_pattern = { + .magic = pat.magic, + .smartcase = !pat.no_scs, + .has_line_offset = false, + .place_cursor_at_end = false, + .offset = 0, + .is_last_used = !search_was_last_used(), + .is_substitute_pattern = true, + .pat = (char *) pat.pat, + .additional_data = pat.additional_data, + } + } + }, max_kbyte); + + // 6. Substitute string + SubReplacementString sub; + sub_get_replacement(&sub); + shada_pack_entry(packer, (ShadaEntry) { + .type = kSDItemSubString, + .timestamp = sub.timestamp, + .data = { + .sub_string = { + .sub = (char *) sub.sub, + .additional_elements = sub.additional_elements, + } + } + }, max_kbyte); + + // 7. Global marks + if (get_viminfo_parameter('f') != 0) { + ShadaEntry *const global_marks = list_global_marks(); + for (ShadaEntry *mark = global_marks; mark->type != kSDItemMissing; mark++) { + shada_pack_entry(packer, *mark, max_kbyte); + } + xfree(global_marks); + } + + // 8. Buffer marks + FOR_ALL_BUFFERS(buf) { + if (buf->b_ffname == NULL) { + continue; + } + ShadaEntry *const buffer_marks = list_buffer_marks(buf); + for (ShadaEntry *mark = buffer_marks; mark->type != kSDItemMissing; + mark++) { + shada_pack_entry(packer, *mark, max_kbyte); + } + xfree(buffer_marks); + } + // FIXME: Copy previous marks, up to num_marked_files + // size_t num_marked_files = get_viminfo_parameter('\''); + + // 9. Registers + int max_num_lines_i = get_viminfo_parameter('<'); + if (max_num_lines_i < 0) { + max_num_lines_i = get_viminfo_parameter('"'); + } + if (max_num_lines_i != 0) { + const size_t max_num_lines = (max_num_lines_i < 0 + ? 0 + : (size_t) max_num_lines_i); + ShadaEntry *const registers = list_registers(max_num_lines); + for (ShadaEntry *reg = registers; reg->type != kSDItemMissing; reg++) { + shada_pack_entry(packer, *reg, max_kbyte); + } + xfree(registers); + } + + // 10. Variables + if (find_viminfo_parameter('!') != NULL) { + const void *var_iter = NULL; + const Timestamp cur_timestamp = os_time(); + do { + typval_T vartv; + const char *name; + var_iter = var_shada_iter(var_iter, &name, &vartv); + if (var_iter == NULL && vartv.v_type == VAR_UNKNOWN) { + break; + } + Object obj = vim_to_object(&vartv); + shada_pack_entry(packer, (ShadaEntry) { + .type = kSDItemVariable, + .timestamp = cur_timestamp, + .data = { + .global_var = { + .name = (char *) name, + .value = obj, + .additional_elements = NULL, + } + } + }, max_kbyte); + api_free_object(obj); + clear_tv(&vartv); + } while (var_iter != NULL); + } + + msgpack_packer_free(packer); +} + +#undef PACK_STATIC_STR + +/// Write ShaDa file to a given location +/// +/// @param[in] fname File to write to. If it is NULL or empty then default +/// location is used. +/// @param[in] nomerge If true then old file is ignored. +/// +/// @return OK if writing was successfull, FAIL otherwise. +int shada_write_file(const char *const file, const bool nomerge) +{ + char *const fname = shada_filename(file); + FILE *wfp = mch_fopen(fname, WRITEBIN); + + if (p_verbose > 0) { + verbose_enter(); + smsg(_("Writing viminfo file \"%s\""), fname); + verbose_leave(); + } + + xfree(fname); + if (wfp == NULL) { + return FAIL; + } + + shada_write(wfp, NULL); + + fclose(wfp); + return OK; +} + +/// Read marks information from ShaDa file +/// +/// @return OK in case of success, FAIL otherwise. +int shada_read_marks(void) +{ + return shada_read_file(NULL, kShaDaWantMarks); +} + +/// Read all information from ShaDa file +/// +/// @param[in] fname File to write to. If it is NULL or empty then default +/// +/// @return OK in case of success, FAIL otherwise. +int shada_read_everything(const char *const fname, const bool forceit) +{ + return shada_read_file(fname, + kShaDaWantInfo|kShaDaWantMarks|kShaDaGetOldfiles + |(forceit?kShaDaForceit:0)); +} + +static void shada_free_shada_entry(ShadaEntry *const entry) +{ + if (entry == NULL) { + return; + } + switch (entry->type) { + case kSDItemMissing: { + break; + } + case kSDItemUnknown: { + xfree(entry->data.unknown_item.contents); + break; + } + case kSDItemHeader: { + api_free_dictionary(entry->data.header); + break; + } + case kSDItemJump: + case kSDItemGlobalMark: + case kSDItemLocalMark: { + if (entry->data.filemark.additional_data != NULL) { + api_free_dictionary(*entry->data.filemark.additional_data); + xfree(entry->data.filemark.additional_data); + } + xfree(entry->data.filemark.fname); + break; + } + case kSDItemSearchPattern: { + if (entry->data.search_pattern.additional_data != NULL) { + api_free_dictionary(*entry->data.search_pattern.additional_data); + xfree(entry->data.search_pattern.additional_data); + } + xfree(entry->data.search_pattern.pat); + break; + } + case kSDItemRegister: { + if (entry->data.reg.additional_data != NULL) { + api_free_dictionary(*entry->data.reg.additional_data); + xfree(entry->data.reg.additional_data); + } + for (size_t i = 0; i < entry->data.reg.contents_size; i++) { + xfree(entry->data.reg.contents[i]); + } + xfree(entry->data.reg.contents); + break; + } + case kSDItemHistoryEntry: { + if (entry->data.history_item.additional_elements != NULL) { + api_free_array(*entry->data.history_item.additional_elements); + xfree(entry->data.history_item.additional_elements); + } + xfree(entry->data.history_item.string); + break; + } + case kSDItemVariable: { + if (entry->data.global_var.additional_elements != NULL) { + api_free_array(*entry->data.global_var.additional_elements); + xfree(entry->data.global_var.additional_elements); + } + xfree(entry->data.global_var.name); + api_free_object(entry->data.global_var.value); + break; + } + case kSDItemSubString: { + if (entry->data.sub_string.additional_elements != NULL) { + api_free_array(*entry->data.sub_string.additional_elements); + xfree(entry->data.sub_string.additional_elements); + } + xfree(entry->data.sub_string.sub); + break; + } + case kSDItemBufferList: { + api_free_array(entry->data.buffer_list); + break; + } + } +} + +/// Read next unsigned integer from file +/// +/// Errors out if the result is not an unsigned integer. +/// +/// Unlike msgpack own function this one works with `FILE *` and reads *exactly* +/// as much bytes as needed, making it possible to avoid both maintaining own +/// buffer and calling `fseek`. +/// +/// One byte from file stream is always consumed, even if it is not correct. +/// +/// @param[in] fp File to read from. +/// @param[out] result Location where result is saved. +/// +/// @return OK if read was successfull, FAIL if it was not. +static int msgpack_read_uint64(FILE *const fp, const int first_char, + uint64_t *const result) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + const long fpos = ftell(fp) - 1; + + if (first_char == EOF) { + if (ferror(fp)) { + emsg2("System error while reading ShaDa file: %s", + strerror(errno)); + } else if (feof(fp)) { + emsgn("Error while reading ShaDa file: " + "expected positive integer at position %" PRId64, + (int64_t) fpos); + } + return FAIL; + } + + if (~first_char & 0x80) { + // Positive fixnum + *result = (uint64_t) ((uint8_t) first_char); + } else { + size_t length = 0; + switch (first_char) { + case 0xCC: { // uint8 + length = 1; + break; + } + case 0xCD: { // uint16 + length = 2; + break; + } + case 0xCE: { // uint32 + length = 4; + break; + } + case 0xCF: { // uint64 + length = 8; + break; + } + default: { + emsgn("Error while reading ShaDa file: " + "expected positive integer at position %" PRId64, + (int64_t) fpos); + return FAIL; + } + } + uint8_t buf[8]; + size_t read_bytes = fread((char *) &(buf[0]), 1, length, fp); + if (ferror(fp)) { + emsg2("System error while reading ShaDa file: %s", + strerror(errno)); + return FAIL; + } else if (read_bytes != length) { + emsgn("Error while reading ShaDa file: " + "not enough bytes for positive integer at position %" PRId64, + (int64_t) fpos); + return FAIL; + } + // TODO(ZyX-I): Just cast if current platform is big-endian. + *result = 0; + for (size_t i = length; i; i--) { + *result |= ((uint64_t) buf[i - 1]) << ((length - i) * 8); + } + } + return OK; +} + +/// Iterate over shada file contents +/// +/// @param[in] fp Pointer to the opened ShaDa file. +/// @param[out] entry Address where next entry contents will be saved. +/// +/// @return NOTDONE if entry was read correctly, FAIL if there were errors and +/// OK at EOF. +static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + entry->type = kSDItemMissing; + if (feof(fp)) { + return OK; + } + + // First: manually unpack type, timestamp and length. + // This is needed to avoid both seeking and having to maintain a buffer. + uint64_t type_u64; + uint64_t timestamp_u64; + uint64_t length_u64; + + const long initial_fpos = ftell(fp); + const int first_char = fgetc(fp); + if (first_char == EOF && feof(fp)) { + return OK; + } + + if (msgpack_read_uint64(fp, first_char, &type_u64) != OK + || msgpack_read_uint64(fp, fgetc(fp), ×tamp_u64) != OK + || msgpack_read_uint64(fp, fgetc(fp), &length_u64) != OK) { + return FAIL; + } + if (type_u64 == 0) { + emsgn("Error while reading ShaDa file: " + "entry at position %" PRId64 "has invalid zero type", + (int64_t) initial_fpos); + return FAIL; + } + + const size_t length = (size_t) length_u64; + entry->timestamp = (Timestamp) timestamp_u64; + if (type_u64 > SHADA_LAST_ENTRY) { + entry->data.unknown_item.size = length; + char *contents = xmalloc(length); + entry->data.unknown_item.contents = contents; + entry->data.unknown_item.type = type_u64; + size_t read_bytes = fread(contents, 1, length, fp); + if (ferror(fp)) { + free(entry->data.unknown_item.contents); + emsg2("System error while reading ShaDa file: %s", + strerror(errno)); + return FAIL; + } else if (read_bytes != length) { + free(entry->data.unknown_item.contents); + emsgn("Error while reading ShaDa file: " + "last entry specified that it occupies %" PRId64 " bytes, " + "but file ended earlier", + (int64_t) length); + return FAIL; + } + entry->type = kSDItemUnknown; + return NOTDONE; + } + + msgpack_unpacked unpacked; + msgpack_unpacked_init(&unpacked); + msgpack_unpacker *unpacker = msgpack_unpacker_new(length); + if (unpacker == NULL || + !msgpack_unpacker_reserve_buffer(unpacker, length)) { + EMSG(e_outofmem); + goto shada_read_next_item_error; + } + msgpack_unpack_return result; + size_t read_bytes = 0; + + const long fpos = ftell(fp); + read_bytes = fread(msgpack_unpacker_buffer(unpacker), 1, length, fp); + if (ferror(fp)) { + emsg2("System error while reading ShaDa file: %s", + strerror(errno)); + goto shada_read_next_item_error; + } else if (read_bytes != length) { + emsgn("Error while reading ShaDa file: " + "last entry specified that it occupies %" PRId64 " bytes, " + "but file ended earlier", + (int64_t) fpos); + goto shada_read_next_item_error; + } + msgpack_unpacker_buffer_consumed(unpacker, read_bytes); + + result = msgpack_unpacker_next(unpacker, &unpacked); + if (result != MSGPACK_UNPACK_SUCCESS) { + if (result == MSGPACK_UNPACK_NOMEM_ERROR) { + EMSG(e_outofmem); + goto shada_read_next_item_error; + } + if (result == MSGPACK_UNPACK_PARSE_ERROR) { + EMSG("Failed to parse ShaDa file"); + goto shada_read_next_item_error; + } + } +#define CHECK_KEY(key, expected) \ + (key.via.str.size == sizeof(expected) - 1 \ + && STRNCMP(key.via.str.ptr, expected, sizeof(expected) - 1) == 0) +#define ID(s) s +#define BINDUP(b) xmemdupz(b.ptr, b.size) +#define TOINT(s) ((int) (s)) +#define TOLONG(s) ((long) (s)) +#define TOCHAR(s) ((char) (s)) +#define TOU8(s) ((uint8_t) (s)) +#define TOSIZE(s) ((size_t) (s)) +#define CHECKED_ENTRY(condition, error_desc, entry_name, obj, tgt, attr, \ + proc) \ + do { \ + if (!(condition)) { \ + emsgn("Error while reading ShaDa file: " \ + entry_name " entry at position %" PRId64 " " \ + error_desc, \ + (int64_t) fpos); \ + ga_clear(&ad_ga); \ + goto shada_read_next_item_error; \ + } \ + tgt = proc(obj.via.attr); \ + } while (0) +#define CHECK_KEY_IS_STR(entry_name) \ + do { \ + if (unpacked.data.via.map.ptr[i].key.type != MSGPACK_OBJECT_STR) { \ + emsgn("Error while reading ShaDa file: " \ + entry_name " entry at position %" PRId64 " " \ + "has key which is not a string", \ + (int64_t) fpos); \ + emsgn("It is %" PRId64 " instead", \ + unpacked.data.via.map.ptr[i].key.type ); \ + ga_clear(&ad_ga); \ + goto shada_read_next_item_error; \ + } \ + } while (0) +#define CHECKED_KEY(entry_name, name, error_desc, tgt, condition, attr, proc) \ + if (CHECK_KEY(unpacked.data.via.map.ptr[i].key, name)) { \ + CHECKED_ENTRY( \ + condition, "has " name " key value " error_desc, \ + entry_name, unpacked.data.via.map.ptr[i].val, \ + tgt, attr, proc); \ + } +#define TYPED_KEY(entry_name, name, type_name, tgt, objtype, attr, proc) \ + CHECKED_KEY( \ + entry_name, name, " which is not " type_name, tgt, \ + unpacked.data.via.map.ptr[i].val.type == MSGPACK_OBJECT_##objtype, \ + attr, proc) +#define BOOLEAN_KEY(entry_name, name, tgt) \ + TYPED_KEY(entry_name, name, "a boolean", tgt, BOOLEAN, boolean, ID) +#define STRING_KEY(entry_name, name, tgt) \ + TYPED_KEY(entry_name, name, "a binary", tgt, BIN, bin, BINDUP) +#define INT_KEY(entry_name, name, tgt, proc) \ + CHECKED_KEY( \ + entry_name, name, " which is not an integer", tgt, \ + (unpacked.data.via.map.ptr[i].val.type \ + == MSGPACK_OBJECT_POSITIVE_INTEGER \ + || unpacked.data.via.map.ptr[i].val.type \ + == MSGPACK_OBJECT_NEGATIVE_INTEGER), \ + i64, proc) +#define INTEGER_KEY(entry_name, name, tgt) \ + INT_KEY(entry_name, name, tgt, TOINT) +#define LONG_KEY(entry_name, name, tgt) \ + INT_KEY(entry_name, name, tgt, TOLONG) +#define ADDITIONAL_KEY \ + { \ + ga_grow(&ad_ga, 1); \ + memcpy(((char *)ad_ga.ga_data) + ((size_t) ad_ga.ga_len \ + * sizeof(*unpacked.data.via.map.ptr)),\ + unpacked.data.via.map.ptr + i, \ + sizeof(*unpacked.data.via.map.ptr)); \ + ad_ga.ga_len++; \ + } + switch ((ShadaEntryType) type_u64) { + case kSDItemHeader: { + if (!msgpack_rpc_to_dictionary(&(unpacked.data), &(entry->data.header))) { + emsgn("Error while reading ShaDa file: " + "header entry at position %" PRId64 " is not a dictionary", + (int64_t) fpos); + goto shada_read_next_item_error; + } + break; + } + case kSDItemSearchPattern: { + if (unpacked.data.type != MSGPACK_OBJECT_MAP) { + emsgn("Error while reading ShaDa file: " + "search pattern entry at position %" PRId64 " " + "is not a dictionary", + (int64_t) fpos); + goto shada_read_next_item_error; + } + entry->data.search_pattern = (struct search_pattern) { + .magic = true, + .smartcase = false, + .has_line_offset = false, + .place_cursor_at_end = false, + .offset = 0, + .is_last_used = true, + .is_substitute_pattern = false, + .pat = NULL, + .additional_data = NULL, + }; + garray_T ad_ga; + ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1); + for (size_t i = 0; i < unpacked.data.via.map.size; i++) { + CHECK_KEY_IS_STR("search pattern"); + BOOLEAN_KEY("search pattern", "magic", entry->data.search_pattern.magic) + else BOOLEAN_KEY("search pattern", "smartcase", + entry->data.search_pattern.smartcase) + else BOOLEAN_KEY("search pattern", "lineoff", + entry->data.search_pattern.has_line_offset) + else BOOLEAN_KEY("search pattern", "curatend", + entry->data.search_pattern.place_cursor_at_end) + else BOOLEAN_KEY("search pattern", "islast", + entry->data.search_pattern.is_last_used) + else BOOLEAN_KEY("search pattern", "sub", + entry->data.search_pattern.is_substitute_pattern) + else INTEGER_KEY("search pattern", "off", + entry->data.search_pattern.offset) + else STRING_KEY("search pattern", "pat", entry->data.search_pattern.pat) + else ADDITIONAL_KEY + } + if (entry->data.search_pattern.pat == NULL) { + emsgn("Error while reading ShaDa file: " + "search pattern entry at position %" PRId64 " " + "has no pattern", + (int64_t) fpos); + ga_clear(&ad_ga); + goto shada_read_next_item_error; + } + if (ad_ga.ga_len) { + msgpack_object obj = { + .type = MSGPACK_OBJECT_MAP, + .via = { + .map = { + .size = (uint32_t) ad_ga.ga_len, + .ptr = ad_ga.ga_data, + } + } + }; + entry->data.search_pattern.additional_data = + xmalloc(sizeof(Dictionary)); + if (!msgpack_rpc_to_dictionary( + &obj, entry->data.search_pattern.additional_data)) { + emsgu("Error while reading ShaDa file: " + "search pattern entry at position %" PRIu64 " " + "cannot be converted to a Dictionary", + (uint64_t) initial_fpos); + ga_clear(&ad_ga); + goto shada_read_next_item_error; + } + } + ga_clear(&ad_ga); + break; + } + case kSDItemJump: + case kSDItemGlobalMark: + case kSDItemLocalMark: { + if (unpacked.data.type != MSGPACK_OBJECT_MAP) { + emsgn("Error while reading ShaDa file: " + "mark entry at position %" PRId64 " " + "is not a dictionary", + (int64_t) fpos); + goto shada_read_next_item_error; + } + entry->data.filemark = (struct shada_filemark) { + .name = '"', + .mark = (pos_T) {1, 0, 0}, + .fname = NULL, + .additional_data = NULL, + }; + garray_T ad_ga; + ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1); + for (size_t i = 0; i < unpacked.data.via.map.size; i++) { + CHECK_KEY_IS_STR("mark"); + CHECKED_KEY( + "mark", "name", " which is not an unsigned integer", + entry->data.filemark.name, + (type_u64 != kSDItemJump + && unpacked.data.via.map.ptr[i].val.type + == MSGPACK_OBJECT_POSITIVE_INTEGER), + u64, TOCHAR) + else LONG_KEY("mark", "line", entry->data.filemark.mark.lnum) + else INTEGER_KEY("mark", "col", entry->data.filemark.mark.col) + else STRING_KEY("mark", "file", entry->data.filemark.fname) + else ADDITIONAL_KEY + } + if (entry->data.filemark.mark.lnum == 0) { + emsgn("Error while reading ShaDa file: " + "mark entry at position %" PRId64 " " + "is missing line number", + (int64_t) fpos); + ga_clear(&ad_ga); + goto shada_read_next_item_error; + } + if (ad_ga.ga_len) { + msgpack_object obj = { + .type = MSGPACK_OBJECT_MAP, + .via = { + .map = { + .size = (uint32_t) ad_ga.ga_len, + .ptr = ad_ga.ga_data, + } + } + }; + entry->data.filemark.additional_data = xmalloc(sizeof(Dictionary)); + if (!msgpack_rpc_to_dictionary( + &obj, entry->data.filemark.additional_data)) { + emsgu("Error while reading ShaDa file: " + "mark entry at position %" PRIu64 " " + "cannot be converted to a Dictionary", + (uint64_t) initial_fpos); + ga_clear(&ad_ga); + goto shada_read_next_item_error; + } + } + ga_clear(&ad_ga); + break; + } + case kSDItemRegister: { + if (unpacked.data.type != MSGPACK_OBJECT_MAP) { + emsgn("Error while reading ShaDa file: " + "register entry at position %" PRId64 " " + "is not a dictionary", + (int64_t) fpos); + goto shada_read_next_item_error; + } + entry->data.reg = (struct reg) { + .name = NUL, + .type = MCHAR, + .contents = NULL, + .contents_size = 0, + .width = 0, + .additional_data = NULL, + }; + garray_T ad_ga; + ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1); + for (size_t i = 0; i < unpacked.data.via.map.size; i++) { + CHECK_KEY_IS_STR("register"); + TYPED_KEY("register", "type", "an unsigned integer", + entry->data.reg.type, POSITIVE_INTEGER, u64, TOU8) + else TYPED_KEY("register", "name", "an unsigned integer", + entry->data.reg.name, POSITIVE_INTEGER, u64, TOCHAR) + else TYPED_KEY("register", "width", "an unsigned integer", + entry->data.reg.width, POSITIVE_INTEGER, u64, TOSIZE) + else if (CHECK_KEY(unpacked.data.via.map.ptr[i].key, "contents")) { + if (unpacked.data.via.map.ptr[i].val.type != MSGPACK_OBJECT_ARRAY) { + emsgn("Error while reading ShaDa file: " + "register entry at position %" PRId64 " " + "has contents key with non-array value", + (int64_t) fpos); + ga_clear(&ad_ga); + goto shada_read_next_item_error; + } + if (unpacked.data.via.map.ptr[i].val.via.array.size == 0) { + emsgn("Error while reading ShaDa file: " + "register entry at position %" PRId64 " " + "has contents key with empty array", + (int64_t) fpos); + ga_clear(&ad_ga); + goto shada_read_next_item_error; + } + const msgpack_object_array arr = + unpacked.data.via.map.ptr[i].val.via.array; + for (size_t i = 0; i < arr.size; i++) { + if (arr.ptr[i].type != MSGPACK_OBJECT_BIN) { + emsgn("Error while reading ShaDa file: " + "register entry at position %" PRId64 " " + "has contents array with non-string value", + (int64_t) fpos); + ga_clear(&ad_ga); + goto shada_read_next_item_error; + } + } + entry->data.reg.contents_size = arr.size; + entry->data.reg.contents = xmalloc(arr.size * sizeof(char *)); + for (size_t i = 0; i < arr.size; i++) { + entry->data.reg.contents[i] = xmemdupz(arr.ptr[i].via.bin.ptr, + arr.ptr[i].via.bin.size); + } + } else ADDITIONAL_KEY + } + if (entry->data.reg.contents == NULL) { + emsgn("Error while reading ShaDa file: " + "register entry at position %" PRId64 " " + "has missing contents array", + (int64_t) fpos); + ga_clear(&ad_ga); + goto shada_read_next_item_error; + } + if (ad_ga.ga_len) { + msgpack_object obj = { + .type = MSGPACK_OBJECT_MAP, + .via = { + .map = { + .size = (uint32_t) ad_ga.ga_len, + .ptr = ad_ga.ga_data, + } + } + }; + entry->data.reg.additional_data = xmalloc(sizeof(Dictionary)); + if (!msgpack_rpc_to_dictionary( + &obj, entry->data.reg.additional_data)) { + emsgu("Error while reading ShaDa file: " + "register entry at position %" PRIu64 " " + "cannot be converted to a Dictionary", + (uint64_t) initial_fpos); + ga_clear(&ad_ga); + goto shada_read_next_item_error; + } + } + ga_clear(&ad_ga); + break; + } + case kSDItemHistoryEntry: { + if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) { + emsgn("Error while reading ShaDa file: " + "history entry at position %" PRId64 " " + "is not an array", + (int64_t) fpos); + goto shada_read_next_item_error; + } + entry->data.history_item = (struct history_item) { + .histtype = 0, + .string = NULL, + .additional_elements = NULL + }; + if (unpacked.data.via.array.size < 2) { + emsgn("Error while reading ShaDa file: " + "history entry at position %" PRId64 " " + "does not have enough elements", + (int64_t) fpos); + goto shada_read_next_item_error; + } + if (unpacked.data.via.array.ptr[0].type + != MSGPACK_OBJECT_POSITIVE_INTEGER) { + emsgn("Error while reading ShaDa file: " + "history entry at position %" PRId64 " " + "has wrong history type type", + (int64_t) fpos); + goto shada_read_next_item_error; + } + if (unpacked.data.via.array.ptr[1].type + != MSGPACK_OBJECT_BIN) { + emsgn("Error while reading ShaDa file: " + "history entry at position %" PRId64 " " + "has wrong history string type", + (int64_t) fpos); + goto shada_read_next_item_error; + } + entry->data.history_item.histtype = + (uint8_t) unpacked.data.via.array.ptr[0].via.u64; + entry->data.history_item.string = + xmemdupz(unpacked.data.via.array.ptr[1].via.bin.ptr, + unpacked.data.via.array.ptr[1].via.bin.size); + if (unpacked.data.via.array.size > 2) { + msgpack_object obj = { + .type = MSGPACK_OBJECT_ARRAY, + .via = { + .array = { + .size = unpacked.data.via.array.size - 2, + .ptr = unpacked.data.via.array.ptr + 2, + } + } + }; + entry->data.history_item.additional_elements = xmalloc(sizeof(Array)); + if (!msgpack_rpc_to_array( + &obj, entry->data.history_item.additional_elements)) { + emsgu("Error while reading ShaDa file: " + "history entry at position %" PRIu64 " " + "cannot be converted to an Array", + (uint64_t) initial_fpos); + goto shada_read_next_item_error; + } + } + break; + } + case kSDItemVariable: { + if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) { + emsgn("Error while reading ShaDa file: " + "variable entry at position %" PRId64 " " + "is not an array", + (int64_t) fpos); + goto shada_read_next_item_error; + } + entry->data.global_var = (struct global_var) { + .name = NULL, + .value = { + .type = kObjectTypeNil, + }, + .additional_elements = NULL + }; + if (unpacked.data.via.array.size < 2) { + emsgn("Error while reading ShaDa file: " + "variable entry at position %" PRId64 " " + "does not have enough elements", + (int64_t) fpos); + goto shada_read_next_item_error; + } + if (unpacked.data.via.array.ptr[0].type != MSGPACK_OBJECT_BIN) { + emsgn("Error while reading ShaDa file: " + "variable entry at position %" PRId64 " " + "has wrong variable name type", + (int64_t) fpos); + goto shada_read_next_item_error; + } + if (unpacked.data.via.array.ptr[1].type == MSGPACK_OBJECT_NIL + || unpacked.data.via.array.ptr[1].type == MSGPACK_OBJECT_EXT) { + emsgn("Error while reading ShaDa file: " + "variable entry at position %" PRId64 " " + "has wrong variable value type", + (int64_t) fpos); + goto shada_read_next_item_error; + } + entry->data.global_var.name = + xmemdupz(unpacked.data.via.array.ptr[0].via.bin.ptr, + unpacked.data.via.array.ptr[0].via.bin.size); + if (!msgpack_rpc_to_object(&(unpacked.data.via.array.ptr[1]), + &(entry->data.global_var.value))) { + emsgu("Error while reading ShaDa file: " + "variable entry at position %" PRIu64 " " + "has value that cannot be converted to the object", + (uint64_t) initial_fpos); + goto shada_read_next_item_error; + } + if (unpacked.data.via.array.size > 2) { + msgpack_object obj = { + .type = MSGPACK_OBJECT_ARRAY, + .via = { + .array = { + .size = unpacked.data.via.array.size - 2, + .ptr = unpacked.data.via.array.ptr + 2, + } + } + }; + entry->data.global_var.additional_elements = xmalloc(sizeof(Array)); + if (!msgpack_rpc_to_array( + &obj, entry->data.global_var.additional_elements)) { + emsgu("Error while reading ShaDa file: " + "variable entry at position %" PRIu64 " " + "cannot be converted to an Array", + (uint64_t) initial_fpos); + goto shada_read_next_item_error; + } + } + break; + } + case kSDItemSubString: { + if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) { + emsgn("Error while reading ShaDa file: " + "sub string entry at position %" PRId64 " " + "is not an array", + (int64_t) fpos); + goto shada_read_next_item_error; + } + entry->data.sub_string = (struct sub_string) { + .sub = NULL, + .additional_elements = NULL + }; + if (unpacked.data.via.array.size < 1) { + emsgn("Error while reading ShaDa file: " + "sub string entry at position %" PRId64 " " + "does not have enough elements", + (int64_t) fpos); + goto shada_read_next_item_error; + } + if (unpacked.data.via.array.ptr[0].type != MSGPACK_OBJECT_BIN) { + emsgn("Error while reading ShaDa file: " + "sub string entry at position %" PRId64 " " + "has wrong sub string type", + (int64_t) fpos); + goto shada_read_next_item_error; + } + entry->data.sub_string.sub = + xmemdupz(unpacked.data.via.array.ptr[0].via.bin.ptr, + unpacked.data.via.array.ptr[0].via.bin.size); + if (unpacked.data.via.array.size > 1) { + msgpack_object obj = { + .type = MSGPACK_OBJECT_ARRAY, + .via = { + .array = { + .size = unpacked.data.via.array.size - 1, + .ptr = unpacked.data.via.array.ptr + 1, + } + } + }; + entry->data.sub_string.additional_elements = xmalloc(sizeof(Array)); + if (!msgpack_rpc_to_array( + &obj, entry->data.sub_string.additional_elements)) { + emsgu("Error while reading ShaDa file: " + "sub string entry at position %" PRIu64 " " + "cannot be converted to an Array", + (uint64_t) initial_fpos); + goto shada_read_next_item_error; + } + } + break; + } + case kSDItemBufferList: { + if (!msgpack_rpc_to_array(&(unpacked.data), &(entry->data.buffer_list))) { + emsgn("Error while reading ShaDa file: " + "buffer list entry at position %" PRId64 " is not an array", + (int64_t) fpos); + goto shada_read_next_item_error; + } + break; + } + case kSDItemMissing: + case kSDItemUnknown: { + emsgu("Error while reading ShaDa file: " + "there is an item at position %" PRIu64 " " + "that must not be there: Missing and Unknown items are " + "for internal uses only", + (uint64_t) initial_fpos); + goto shada_read_next_item_error; + } + } + entry->type = (ShadaEntryType) type_u64; + goto shada_read_next_item_end; +#undef CHECK_KEY +#undef BOOLEAN_KEY +#undef ADDITIONAL_KEY +#undef ID +#undef BINDUP +#undef TOCHAR +#undef TOINT +#undef TOLONG +#undef TYPED_KEY +#undef INT_KEY +#undef INTEGER_KEY +#undef LONG_KEY +#undef TOU8 +#undef TOSIZE +shada_read_next_item_error: + msgpack_unpacked_destroy(&unpacked); + msgpack_unpacker_free(unpacker); + return FAIL; +shada_read_next_item_end: + msgpack_unpacked_destroy(&unpacked); + msgpack_unpacker_free(unpacker); + return NOTDONE; +} + +/// Return a list with all global marks set in current NeoVim instance +/// +/// List is not sorted. +/// +/// @warning Listed marks must be used before any buffer- or mark-editing +/// function is run. +/// +/// @return Array of ShadaEntry values, last one has type kSDItemMissing. +/// +/// @warning Resulting ShadaEntry values must not be freed. Returned +/// array must be freed with `xfree()`. +static ShadaEntry *list_global_marks(void) +{ + const void *iter = NULL; + const size_t nummarks = mark_global_amount(); + ShadaEntry *const ret = xmalloc(sizeof(ShadaEntry) * (nummarks + 1)); + ShadaEntry *cur = ret; + if (nummarks) { + do { + cur->type = kSDItemGlobalMark; + xfmark_T cur_fm; + iter = mark_global_iter(iter, &(cur->data.filemark.name), &cur_fm); + cur->data.filemark.mark = cur_fm.fmark.mark; + cur->data.filemark.additional_data = cur_fm.fmark.additional_data; + cur->timestamp = cur_fm.fmark.timestamp; + if (cur_fm.fmark.fnum == 0) { + cur->data.filemark.fname = (char *) (cur_fm.fname == NULL + ? NULL + : cur_fm.fname); + } else { + const buf_T *const buf = buflist_findnr(cur_fm.fmark.fnum); + if (buf == NULL) { + continue; + } else { + cur->data.filemark.fname = (char *) buf->b_ffname; + } + } + if (cur->data.filemark.fname != NULL) { + if (shada_removable(cur->data.filemark.fname)) { + continue; + } else { + cur++; + } + } + } while(iter != NULL); + } + cur->type = kSDItemMissing; + return ret; +} + +/// Return a list with all buffer marks set in some buffer +/// +/// List is not sorted. +/// +/// @warning Listed marks must be used before any buffer- or mark-editing +/// function is run. +/// +/// @param[in] buf Buffer for which marks are listed. +/// +/// @return Array of ShadaEntry values, last one has type kSDItemMissing. +/// +/// @warning Resulting ShadaEntry values must not be freed. Returned +/// array must be freed with `xfree()`. +static ShadaEntry *list_buffer_marks(const buf_T *const buf) +{ + const char *const fname = (char *) buf->b_ffname; + const void *iter = NULL; + const size_t nummarks = mark_buffer_amount(buf); + ShadaEntry *const ret = xmalloc(sizeof(ShadaEntry) * (nummarks + 1)); + ShadaEntry *cur = ret; + if (nummarks) { + do { + cur->type = kSDItemLocalMark; + fmark_T cur_fm; + iter = mark_buffer_iter(iter, buf, &(cur->data.filemark.name), &cur_fm); + cur->data.filemark.mark = cur_fm.mark; + cur->data.filemark.fname = (char *) fname; + cur->data.filemark.additional_data = cur_fm.additional_data; + cur->timestamp = cur_fm.timestamp; + if (cur->data.filemark.mark.lnum != 0) { + cur++; + } + } while(iter != NULL); + } + cur->type = kSDItemMissing; + return ret; +} + +/// Check whether "name" is on removable media (according to 'viminfo') +/// +/// @param[in] name Checked name. +/// +/// @return True if it is, false otherwise. +bool shada_removable(const char *name) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + char *p; + char part[51]; + int retval = FALSE; + size_t n; + + char *new_name = home_replace_save(NULL, name); + for (p = (char *) p_viminfo; *p; ) { + (void) copy_option_part(&p, part, 51, ", "); + if (part[0] == 'r') { + n = STRLEN(part + 1); + if (STRNICMP(part + 1, new_name, n) == 0) { + retval = TRUE; + break; + } + } + } + xfree(new_name); + return retval; +} + +/// Return a list with all registers and their contents +/// +/// List is not sorted. +/// +/// @warning Listed registers must be used before any register-editing function +/// is run. +/// +/// @param[in] max_num_lines Maximum number of lines in the register. If it is +/// zero then all registers are listed. +/// +/// @return Array of ShadaEntry values, last one has type kSDItemMissing. +/// +/// @warning Resulting ShadaEntry values must not be freed. Returned +/// array must be freed with `xfree()`. +static ShadaEntry *list_registers(const size_t max_num_lines) +{ + const void *iter = NULL; + const size_t numregs = op_register_amount(); + ShadaEntry *const ret = xmalloc(sizeof(ShadaEntry) * (numregs + 1)); + ShadaEntry *cur = ret; + if (numregs) { + do { + cur->type = kSDItemRegister; + yankreg_T cur_reg; + iter = op_register_iter(iter, &(cur->data.reg.name), &cur_reg); + if (max_num_lines && (size_t) cur_reg.y_size > max_num_lines) { + continue; + } + cur->data.reg.contents = (char **) cur_reg.y_array; + cur->data.reg.type = (uint8_t) cur_reg.y_type; + cur->data.reg.contents_size = (size_t) cur_reg.y_size; + if (cur_reg.y_type == MBLOCK) { + cur->data.reg.width = (size_t) cur_reg.y_width; + } else { + cur->data.reg.width = 0; + } + cur->data.reg.additional_data = cur_reg.additional_data; + cur->timestamp = cur_reg.timestamp; + cur++; + } while(iter != NULL); + } + cur->type = kSDItemMissing; + return ret; +} + + +#if 0 + +static int viminfo_errcnt; + +static int no_viminfo(void) +{ + /* "vim -i NONE" does not read or write a viminfo file */ + return use_viminfo != NULL && STRCMP(use_viminfo, "NONE") == 0; +} + +/* + * Report an error for reading a viminfo file. + * Count the number of errors. When there are more than 10, return TRUE. + */ +int viminfo_error(char *errnum, char *message, char_u *line) +{ + vim_snprintf((char *)IObuff, IOSIZE, _("%sviminfo: %s in line: "), + errnum, message); + STRNCAT(IObuff, line, IOSIZE - STRLEN(IObuff) - 1); + if (IObuff[STRLEN(IObuff) - 1] == '\n') + IObuff[STRLEN(IObuff) - 1] = NUL; + emsg(IObuff); + if (++viminfo_errcnt >= 10) { + EMSG(_("E136: viminfo: Too many errors, skipping rest of file")); + return TRUE; + } + return FALSE; +} + +/* + * read_viminfo() -- Read the viminfo file. Registers etc. which are already + * set are not over-written unless "flags" includes VIF_FORCEIT. -- webb + */ +int +read_viminfo ( + char_u *file, /* file name or NULL to use default name */ + int flags /* VIF_WANT_INFO et al. */ +) +{ + FILE *fp; + char_u *fname; + + if (no_viminfo()) + return FAIL; + + fname = viminfo_filename(file); /* get file name in allocated buffer */ + fp = mch_fopen((char *)fname, READBIN); + + if (p_verbose > 0) { + verbose_enter(); + smsg(_("Reading viminfo file \"%s\"%s%s%s"), + fname, + (flags & VIF_WANT_INFO) ? _(" info") : "", + (flags & VIF_WANT_MARKS) ? _(" marks") : "", + (flags & VIF_GET_OLDFILES) ? _(" oldfiles") : "", + fp == NULL ? _(" FAILED") : ""); + verbose_leave(); + } + + xfree(fname); + if (fp == NULL) + return FAIL; + + viminfo_errcnt = 0; + do_viminfo(fp, NULL, flags); + + fclose(fp); + return OK; +} + +/* + * Write the viminfo file. The old one is read in first so that effectively a + * merge of current info and old info is done. This allows multiple vims to + * run simultaneously, without losing any marks etc. + * If "forceit" is TRUE, then the old file is not read in, and only internal + * info is written to the file. + */ +void write_viminfo(char_u *file, int forceit) +{ + char_u *fname; + FILE *fp_in = NULL; /* input viminfo file, if any */ + FILE *fp_out = NULL; /* output viminfo file */ + char_u *tempname = NULL; /* name of temp viminfo file */ + char_u *wp; +#if defined(UNIX) + mode_t umask_save; +#endif + + if (no_viminfo()) + return; + + fname = viminfo_filename(file); /* may set to default if NULL */ + + fp_in = mch_fopen((char *)fname, READBIN); + if (fp_in == NULL) { + /* if it does exist, but we can't read it, don't try writing */ + if (os_file_exists(fname)) + goto end; +#if defined(UNIX) + /* + * For Unix we create the .viminfo non-accessible for others, + * because it may contain text from non-accessible documents. + */ + umask_save = umask(077); +#endif + fp_out = mch_fopen((char *)fname, WRITEBIN); +#if defined(UNIX) + (void)umask(umask_save); +#endif + } else { + /* + * There is an existing viminfo file. Create a temporary file to + * write the new viminfo into, in the same directory as the + * existing viminfo file, which will be renamed later. + */ +#ifdef UNIX + /* + * For Unix we check the owner of the file. It's not very nice to + * overwrite a user's viminfo file after a "su root", with a + * viminfo file that the user can't read. + */ + + FileInfo old_info; // FileInfo of existing viminfo file + if (os_fileinfo((char *)fname, &old_info) + && getuid() != ROOT_UID + && !(old_info.stat.st_uid == getuid() + ? (old_info.stat.st_mode & 0200) + : (old_info.stat.st_gid == getgid() + ? (old_info.stat.st_mode & 0020) + : (old_info.stat.st_mode & 0002)))) { + int tt = msg_didany; + + /* avoid a wait_return for this message, it's annoying */ + EMSG2(_("E137: Viminfo file is not writable: %s"), fname); + msg_didany = tt; + fclose(fp_in); + goto end; + } +#endif + + // Make tempname + tempname = (char_u *)modname((char *)fname, ".tmp", FALSE); + if (tempname != NULL) { + /* + * Check if tempfile already exists. Never overwrite an + * existing file! + */ + if (os_file_exists(tempname)) { + /* + * Try another name. Change one character, just before + * the extension. + */ + wp = tempname + STRLEN(tempname) - 5; + if (wp < path_tail(tempname)) /* empty file name? */ + wp = path_tail(tempname); + for (*wp = 'z'; os_file_exists(tempname); --*wp) { + /* + * They all exist? Must be something wrong! Don't + * write the viminfo file then. + */ + if (*wp == 'a') { + xfree(tempname); + tempname = NULL; + break; + } + } + } + } + + if (tempname != NULL) { + int fd; + + /* Use os_open() to be able to use O_NOFOLLOW and set file + * protection: + * Unix: same as original file, but strip s-bit. Reset umask to + * avoid it getting in the way. + * Others: r&w for user only. */ +# ifdef UNIX + umask_save = umask(0); + fd = os_open((char *)tempname, + O_CREAT|O_EXCL|O_WRONLY|O_NOFOLLOW, + (int)((old_info.stat.st_mode & 0777) | 0600)); + (void)umask(umask_save); +# else + fd = os_open((char *)tempname, + O_CREAT|O_EXCL|O_WRONLY|O_NOFOLLOW, 0600); +# endif + if (fd < 0) + fp_out = NULL; + else + fp_out = fdopen(fd, WRITEBIN); + + /* + * If we can't create in the same directory, try creating a + * "normal" temp file. + */ + if (fp_out == NULL) { + xfree(tempname); + if ((tempname = vim_tempname()) != NULL) + fp_out = mch_fopen((char *)tempname, WRITEBIN); + } + +#ifdef UNIX + /* + * Make sure the owner can read/write it. This only works for + * root. + */ + if (fp_out != NULL) { + os_fchown(fileno(fp_out), old_info.stat.st_uid, old_info.stat.st_gid); + } +#endif + } + } + + /* + * Check if the new viminfo file can be written to. + */ + if (fp_out == NULL) { + EMSG2(_("E138: Can't write viminfo file %s!"), + (fp_in == NULL || tempname == NULL) ? fname : tempname); + if (fp_in != NULL) + fclose(fp_in); + goto end; + } + + if (p_verbose > 0) { + verbose_enter(); + smsg(_("Writing viminfo file \"%s\""), fname); + verbose_leave(); + } + + viminfo_errcnt = 0; + do_viminfo(fp_in, fp_out, forceit ? 0 : (VIF_WANT_INFO | VIF_WANT_MARKS)); + + fclose(fp_out); /* errors are ignored !? */ + if (fp_in != NULL) { + fclose(fp_in); + + /* In case of an error keep the original viminfo file. Otherwise + * rename the newly written file. Give an error if that fails. */ + if (viminfo_errcnt == 0 && vim_rename(tempname, fname) == -1) { + viminfo_errcnt++; + EMSG2(_("E886: Can't rename viminfo file to %s!"), fname); + } + if (viminfo_errcnt > 0) { + os_remove((char *)tempname); + } + } + +end: + xfree(fname); + xfree(tempname); +} + +/* + * Get the viminfo file name to use. + * If "file" is given and not empty, use it (has already been expanded by + * cmdline functions). + * Otherwise use "-i file_name", value from 'viminfo' or the default, and + * expand environment variables. + * Returns an allocated string. + */ +static char_u *viminfo_filename(char_u *file) +{ + if (file == NULL || *file == NUL) { + if (use_viminfo != NULL) + file = use_viminfo; + else if ((file = find_viminfo_parameter('n')) == NULL || *file == NUL) { +#ifdef VIMINFO_FILE2 + // don't use $HOME when not defined (turned into "c:/"!). + if (!os_env_exists("HOME")) { + // don't use $VIM when not available. + expand_env((char_u *)"$VIM", NameBuff, MAXPATHL); + if (STRCMP("$VIM", NameBuff) != 0) /* $VIM was expanded */ + file = (char_u *)VIMINFO_FILE2; + else + file = (char_u *)VIMINFO_FILE; + } else +#endif + file = (char_u *)VIMINFO_FILE; + } + expand_env(file, NameBuff, MAXPATHL); + file = NameBuff; + } + return vim_strsave(file); +} + +/* + * do_viminfo() -- Should only be called from read_viminfo() & write_viminfo(). + */ +static void do_viminfo(FILE *fp_in, FILE *fp_out, int flags) +{ + int count = 0; + int eof = FALSE; + vir_T vir; + int merge = FALSE; + + vir.vir_line = xmalloc(LSIZE); + vir.vir_fd = fp_in; + vir.vir_conv.vc_type = CONV_NONE; + + if (fp_in != NULL) { + if (flags & VIF_WANT_INFO) { + eof = read_viminfo_up_to_marks(&vir, + flags & VIF_FORCEIT, fp_out != NULL); + merge = TRUE; + } else if (flags != 0) + /* Skip info, find start of marks */ + while (!(eof = viminfo_readline(&vir)) + && vir.vir_line[0] != '>') + ; + } + if (fp_out != NULL) { + /* Write the info: */ + fprintf(fp_out, _("# This viminfo file was generated by Nvim %s.\n"), + mediumVersion); + fputs(_("# You may edit it if you're careful!\n\n"), fp_out); + fputs(_("# Value of 'encoding' when this file was written\n"), fp_out); + fprintf(fp_out, "*encoding=%s\n\n", p_enc); + write_viminfo_search_pattern(fp_out); + write_viminfo_sub_string(fp_out); + write_viminfo_history(fp_out, merge); + write_viminfo_registers(fp_out); + write_viminfo_varlist(fp_out); + write_viminfo_filemarks(fp_out); + write_viminfo_bufferlist(fp_out); + count = write_viminfo_marks(fp_out); + } + if (fp_in != NULL + && (flags & (VIF_WANT_MARKS | VIF_GET_OLDFILES | VIF_FORCEIT))) + copy_viminfo_marks(&vir, fp_out, count, eof, flags); + + xfree(vir.vir_line); + if (vir.vir_conv.vc_type != CONV_NONE) + convert_setup(&vir.vir_conv, NULL, NULL); +} + +/* + * read_viminfo_up_to_marks() -- Only called from do_viminfo(). Reads in the + * first part of the viminfo file which contains everything but the marks that + * are local to a file. Returns TRUE when end-of-file is reached. -- webb + */ +static int read_viminfo_up_to_marks(vir_T *virp, int forceit, int writing) +{ + int eof; + + prepare_viminfo_history(forceit ? 9999 : 0, writing); + eof = viminfo_readline(virp); + while (!eof && virp->vir_line[0] != '>') { + switch (virp->vir_line[0]) { + /* Characters reserved for future expansion, ignored now */ + case '+': /* "+40 /path/dir file", for running vim without args */ + case '|': /* to be defined */ + case '^': /* to be defined */ + case '<': /* long line - ignored */ + /* A comment or empty line. */ + case NUL: + case '\r': + case '\n': + case '#': + eof = viminfo_readline(virp); + break; + case '*': /* "*encoding=value" */ + eof = viminfo_encoding(virp); + break; + case '!': /* global variable */ + eof = read_viminfo_varlist(virp, writing); + break; + case '%': /* entry for buffer list */ + eof = read_viminfo_bufferlist(virp, writing); + break; + case '"': + eof = read_viminfo_register(virp, forceit); + break; + case '/': /* Search string */ + case '&': /* Substitute search string */ + case '~': /* Last search string, followed by '/' or '&' */ + eof = read_viminfo_search_pattern(virp, forceit); + break; + case '$': + eof = read_viminfo_sub_string(virp, forceit); + break; + case ':': + case '?': + case '=': + case '@': + eof = read_viminfo_history(virp, writing); + break; + case '-': + case '\'': + eof = read_viminfo_filemark(virp, forceit); + break; + default: + if (viminfo_error("E575: ", _("Illegal starting char"), + virp->vir_line)) + eof = TRUE; + else + eof = viminfo_readline(virp); + break; + } + } + + /* Finish reading history items. */ + if (!writing) + finish_viminfo_history(); + + /* Change file names to buffer numbers for fmarks. */ + FOR_ALL_BUFFERS(buf) { + fmarks_check_names(buf); + } + + return eof; +} + +/* + * Compare the 'encoding' value in the viminfo file with the current value of + * 'encoding'. If different and the 'c' flag is in 'viminfo', setup for + * conversion of text with iconv() in viminfo_readstring(). + */ +static int viminfo_encoding(vir_T *virp) +{ + char_u *p; + int i; + + if (get_viminfo_parameter('c') != 0) { + p = vim_strchr(virp->vir_line, '='); + if (p != NULL) { + /* remove trailing newline */ + ++p; + for (i = 0; vim_isprintc(p[i]); ++i) + ; + p[i] = NUL; + + convert_setup(&virp->vir_conv, p, p_enc); + } + } + return viminfo_readline(virp); +} + +/* + * Read a line from the viminfo file. + * Returns TRUE for end-of-file; + */ +int viminfo_readline(vir_T *virp) +{ + return vim_fgets(virp->vir_line, LSIZE, virp->vir_fd); +} + +/* + * check string read from viminfo file + * remove '\n' at the end of the line + * - replace CTRL-V CTRL-V with CTRL-V + * - replace CTRL-V 'n' with '\n' + * + * Check for a long line as written by viminfo_writestring(). + * + * Return the string in allocated memory. + */ +char_u * +viminfo_readstring ( + vir_T *virp, + int off, /* offset for virp->vir_line */ + int convert /* convert the string */ +) + FUNC_ATTR_NONNULL_RET +{ + char_u *retval; + char_u *s, *d; + + if (virp->vir_line[off] == Ctrl_V && ascii_isdigit(virp->vir_line[off + 1])) { + ssize_t len = atol((char *)virp->vir_line + off + 1); + retval = xmalloc(len); + // TODO(philix): change type of vim_fgets() size argument to size_t + (void)vim_fgets(retval, (int)len, virp->vir_fd); + s = retval + 1; /* Skip the leading '<' */ + } else { + retval = vim_strsave(virp->vir_line + off); + s = retval; + } + + /* Change CTRL-V CTRL-V to CTRL-V and CTRL-V n to \n in-place. */ + d = retval; + while (*s != NUL && *s != '\n') { + if (s[0] == Ctrl_V && s[1] != NUL) { + if (s[1] == 'n') + *d++ = '\n'; + else + *d++ = Ctrl_V; + s += 2; + } else + *d++ = *s++; + } + *d = NUL; + + if (convert && virp->vir_conv.vc_type != CONV_NONE && *retval != NUL) { + d = string_convert(&virp->vir_conv, retval, NULL); + if (d != NULL) { + xfree(retval); + retval = d; + } + } + + return retval; +} + +/* + * write string to viminfo file + * - replace CTRL-V with CTRL-V CTRL-V + * - replace '\n' with CTRL-V 'n' + * - add a '\n' at the end + * + * For a long line: + * - write " CTRL-V \n " in first line + * - write " < \n " in second line + */ +void viminfo_writestring(FILE *fd, char_u *p) +{ + int c; + char_u *s; + int len = 0; + + for (s = p; *s != NUL; ++s) { + if (*s == Ctrl_V || *s == '\n') + ++len; + ++len; + } + + /* If the string will be too long, write its length and put it in the next + * line. Take into account that some room is needed for what comes before + * the string (e.g., variable name). Add something to the length for the + * '<', NL and trailing NUL. */ + if (len > LSIZE / 2) + fprintf(fd, "\026%d\n<", len + 3); + + while ((c = *p++) != NUL) { + if (c == Ctrl_V || c == '\n') { + putc(Ctrl_V, fd); + if (c == '\n') + c = 'n'; + } + putc(c, fd); + } + putc('\n', fd); +} + +/* + * Write all the named marks for all buffers. + * Return the number of buffers for which marks have been written. + */ +int write_viminfo_marks(FILE *fp_out) +{ + /* + * Set b_last_cursor for the all buffers that have a window. + */ + FOR_ALL_TAB_WINDOWS(tp, win) { + set_last_cursor(win); + } + + fputs(_("\n# History of marks within files (newest to oldest):\n"), fp_out); + int count = 0; + FOR_ALL_BUFFERS(buf) { + /* + * Only write something if buffer has been loaded and at least one + * mark is set. + */ + if (buf->b_marks_read) { + bool is_mark_set = true; + if (buf->b_last_cursor.lnum == 0) { + is_mark_set = false; + for (int i = 0; i < NMARKS; i++) { + if (buf->b_namedm[i].lnum != 0) { + is_mark_set = true; + break; + } + } + } + if (is_mark_set && buf->b_ffname != NULL + && buf->b_ffname[0] != NUL && !removable(buf->b_ffname)) { + home_replace(NULL, buf->b_ffname, IObuff, IOSIZE, TRUE); + fprintf(fp_out, "\n> "); + viminfo_writestring(fp_out, IObuff); + write_one_mark(fp_out, '"', &buf->b_last_cursor); + write_one_mark(fp_out, '^', &buf->b_last_insert); + write_one_mark(fp_out, '.', &buf->b_last_change); + /* changelist positions are stored oldest first */ + for (int i = 0; i < buf->b_changelistlen; ++i) { + write_one_mark(fp_out, '+', &buf->b_changelist[i]); + } + for (int i = 0; i < NMARKS; i++) { + write_one_mark(fp_out, 'a' + i, &buf->b_namedm[i]); + } + count++; + } + } + } + + return count; +} + +static void write_one_mark(FILE *fp_out, int c, pos_T *pos) +{ + if (pos->lnum != 0) + fprintf(fp_out, "\t%c\t%" PRId64 "\t%d\n", c, + (int64_t)pos->lnum, (int)pos->col); +} + +/* + * Handle marks in the viminfo file: + * fp_out != NULL: copy marks for buffers not in buffer list + * fp_out == NULL && (flags & VIF_WANT_MARKS): read marks for curbuf only + * fp_out == NULL && (flags & VIF_GET_OLDFILES | VIF_FORCEIT): fill v:oldfiles + */ +void copy_viminfo_marks(vir_T *virp, FILE *fp_out, int count, int eof, int flags) +{ + char_u *line = virp->vir_line; + buf_T *buf; + int num_marked_files; + int load_marks; + int copy_marks_out; + char_u *str; + int i; + char_u *p; + char_u *name_buf; + pos_T pos; + list_T *list = NULL; + + name_buf = xmalloc(LSIZE); + *name_buf = NUL; + + if (fp_out == NULL && (flags & (VIF_GET_OLDFILES | VIF_FORCEIT))) { + list = list_alloc(); + set_vim_var_list(VV_OLDFILES, list); + } + + num_marked_files = get_viminfo_parameter('\''); + while (!eof && (count < num_marked_files || fp_out == NULL)) { + if (line[0] != '>') { + if (line[0] != '\n' && line[0] != '\r' && line[0] != '#') { + if (viminfo_error("E576: ", _("Missing '>'"), line)) + break; /* too many errors, return now */ + } + eof = vim_fgets(line, LSIZE, virp->vir_fd); + continue; /* Skip this dud line */ + } + + /* + * Handle long line and translate escaped characters. + * Find file name, set str to start. + * Ignore leading and trailing white space. + */ + str = skipwhite(line + 1); + str = viminfo_readstring(virp, (int)(str - virp->vir_line), FALSE); + p = str + STRLEN(str); + while (p != str && (*p == NUL || ascii_isspace(*p))) + p--; + if (*p) + p++; + *p = NUL; + + if (list != NULL) + list_append_string(list, str, -1); + + /* + * If fp_out == NULL, load marks for current buffer. + * If fp_out != NULL, copy marks for buffers not in buflist. + */ + load_marks = copy_marks_out = FALSE; + if (fp_out == NULL) { + if ((flags & VIF_WANT_MARKS) && curbuf->b_ffname != NULL) { + if (*name_buf == NUL) /* only need to do this once */ + home_replace(NULL, curbuf->b_ffname, name_buf, LSIZE, TRUE); + if (fnamecmp(str, name_buf) == 0) + load_marks = TRUE; + } + } else { /* fp_out != NULL */ + /* This is slow if there are many buffers!! */ + buf = NULL; + FOR_ALL_BUFFERS(bp) { + if (bp->b_ffname != NULL) { + home_replace(NULL, bp->b_ffname, name_buf, LSIZE, TRUE); + if (fnamecmp(str, name_buf) == 0) { + buf = bp; + break; + } + } + } + + /* + * copy marks if the buffer has not been loaded + */ + if (buf == NULL || !buf->b_marks_read) { + copy_marks_out = TRUE; + fputs("\n> ", fp_out); + viminfo_writestring(fp_out, str); + count++; + } + } + free(str); + + pos.coladd = 0; + while (!(eof = viminfo_readline(virp)) && line[0] == TAB) { + if (load_marks) { + if (line[1] != NUL) { + int64_t lnum_64; + unsigned int u; + sscanf((char *)line + 2, "%" SCNd64 "%u", &lnum_64, &u); + // safely downcast to linenr_T (long); remove when linenr_T refactored + assert(lnum_64 <= LONG_MAX); + pos.lnum = (linenr_T)lnum_64; + assert(u <= INT_MAX); + pos.col = (colnr_T)u; + switch (line[1]) { + case '"': curbuf->b_last_cursor = pos; break; + case '^': curbuf->b_last_insert = pos; break; + case '.': curbuf->b_last_change = pos; break; + case '+': + /* changelist positions are stored oldest + * first */ + if (curbuf->b_changelistlen == JUMPLISTSIZE) + /* list is full, remove oldest entry */ + memmove(curbuf->b_changelist, + curbuf->b_changelist + 1, + sizeof(pos_T) * (JUMPLISTSIZE - 1)); + else + ++curbuf->b_changelistlen; + curbuf->b_changelist[ + curbuf->b_changelistlen - 1] = pos; + break; + default: if ((i = line[1] - 'a') >= 0 && i < NMARKS) + curbuf->b_namedm[i] = pos; + } + } + } else if (copy_marks_out) + fputs((char *)line, fp_out); + } + if (load_marks) { + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == curbuf) + wp->w_changelistidx = curbuf->b_changelistlen; + } + break; + } + } + free(name_buf); +} + +int read_viminfo_filemark(vir_T *virp, int force) +{ + char_u *str; + xfmark_T *fm; + int i; + + /* We only get here if line[0] == '\'' or '-'. + * Illegal mark names are ignored (for future expansion). */ + str = virp->vir_line + 1; + if ( + *str <= 127 && + ((*virp->vir_line == '\'' && (ascii_isdigit(*str) || isupper(*str))) + || (*virp->vir_line == '-' && *str == '\''))) { + if (*str == '\'') { + /* If the jumplist isn't full insert fmark as oldest entry */ + if (curwin->w_jumplistlen == JUMPLISTSIZE) + fm = NULL; + else { + for (i = curwin->w_jumplistlen; i > 0; --i) + curwin->w_jumplist[i] = curwin->w_jumplist[i - 1]; + ++curwin->w_jumplistidx; + ++curwin->w_jumplistlen; + fm = &curwin->w_jumplist[0]; + fm->fmark.mark.lnum = 0; + fm->fname = NULL; + } + } else if (ascii_isdigit(*str)) + fm = &namedfm[*str - '0' + NMARKS]; + else { // is uppercase + assert(*str >= 'A' && *str <= 'Z'); + fm = &namedfm[*str - 'A']; + } + if (fm != NULL && (fm->fmark.mark.lnum == 0 || force)) { + str = skipwhite(str + 1); + fm->fmark.mark.lnum = getdigits_long(&str); + str = skipwhite(str); + fm->fmark.mark.col = getdigits_int(&str); + fm->fmark.mark.coladd = 0; + fm->fmark.fnum = 0; + str = skipwhite(str); + free(fm->fname); + fm->fname = viminfo_readstring(virp, (int)(str - virp->vir_line), + FALSE); + } + } + return vim_fgets(virp->vir_line, LSIZE, virp->vir_fd); +} + +void write_viminfo_filemarks(FILE *fp) +{ + int i; + char_u *name; + buf_T *buf; + xfmark_T *fm; + + if (get_viminfo_parameter('f') == 0) + return; + + fputs(_("\n# File marks:\n"), fp); + + /* + * Find a mark that is the same file and position as the cursor. + * That one, or else the last one is deleted. + * Move '0 to '1, '1 to '2, etc. until the matching one or '9 + * Set '0 mark to current cursor position. + */ + if (curbuf->b_ffname != NULL && !removable(curbuf->b_ffname)) { + name = buflist_nr2name(curbuf->b_fnum, TRUE, FALSE); + for (i = NMARKS; i < NMARKS + EXTRA_MARKS - 1; ++i) + if (namedfm[i].fmark.mark.lnum == curwin->w_cursor.lnum + && (namedfm[i].fname == NULL + ? namedfm[i].fmark.fnum == curbuf->b_fnum + : (name != NULL + && STRCMP(name, namedfm[i].fname) == 0))) + break; + free(name); + + free(namedfm[i].fname); + for (; i > NMARKS; --i) + namedfm[i] = namedfm[i - 1]; + namedfm[NMARKS].fmark.mark = curwin->w_cursor; + namedfm[NMARKS].fmark.fnum = curbuf->b_fnum; + namedfm[NMARKS].fname = NULL; + } + + /* Write the filemarks '0 - '9 and 'A - 'Z */ + for (i = 0; i < NMARKS + EXTRA_MARKS; i++) + write_one_filemark(fp, &namedfm[i], '\'', + i < NMARKS ? i + 'A' : i - NMARKS + '0'); + + /* Write the jumplist with -' */ + fputs(_("\n# Jumplist (newest first):\n"), fp); + setpcmark(); /* add current cursor position */ + cleanup_jumplist(); + for (fm = &curwin->w_jumplist[curwin->w_jumplistlen - 1]; + fm >= &curwin->w_jumplist[0]; --fm) { + if (fm->fmark.fnum == 0 + || ((buf = buflist_findnr(fm->fmark.fnum)) != NULL + && !removable(buf->b_ffname))) + write_one_filemark(fp, fm, '-', '\''); + } +} + +int read_viminfo_search_pattern(vir_T *virp, int force) +{ + char_u *lp; + int idx = -1; + int magic = FALSE; + int no_scs = FALSE; + int off_line = FALSE; + int off_end = 0; + long off = 0; + int setlast = FALSE; + static int hlsearch_on = FALSE; + char_u *val; + + /* + * Old line types: + * "/pat", "&pat": search/subst. pat + * "~/pat", "~&pat": last used search/subst. pat + * New line types: + * "~h", "~H": hlsearch highlighting off/on + * "~pat" + * : 'm' off, 'M' on + * : 's' off, 'S' on + * : 'L' line offset, 'l' char offset + * : 'E' from end, 'e' from start + * : decimal, offset + * : '~' last used pattern + * : '/' search pat, '&' subst. pat + */ + lp = virp->vir_line; + if (lp[0] == '~' && (lp[1] == 'm' || lp[1] == 'M')) { /* new line type */ + if (lp[1] == 'M') /* magic on */ + magic = TRUE; + if (lp[2] == 's') + no_scs = TRUE; + if (lp[3] == 'L') + off_line = TRUE; + if (lp[4] == 'E') + off_end = SEARCH_END; + lp += 5; + off = getdigits_long(&lp); + } + if (lp[0] == '~') { /* use this pattern for last-used pattern */ + setlast = TRUE; + lp++; + } + if (lp[0] == '/') + idx = RE_SEARCH; + else if (lp[0] == '&') + idx = RE_SUBST; + else if (lp[0] == 'h') /* ~h: 'hlsearch' highlighting off */ + hlsearch_on = FALSE; + else if (lp[0] == 'H') /* ~H: 'hlsearch' highlighting on */ + hlsearch_on = TRUE; + if (idx >= 0) { + if (force || spats[idx].pat == NULL) { + val = viminfo_readstring(virp, (int)(lp - virp->vir_line + 1), TRUE); + set_last_search_pat(val, idx, magic, setlast); + xfree(val); + spats[idx].no_scs = no_scs; + spats[idx].off.line = off_line; + spats[idx].off.end = off_end; + spats[idx].off.off = off; + if (setlast) { + SET_NO_HLSEARCH(!hlsearch_on); + } + } + } + return viminfo_readline(virp); +} + +void write_viminfo_search_pattern(FILE *fp) +{ + if (get_viminfo_parameter('/') != 0) { + fprintf(fp, "\n# hlsearch on (H) or off (h):\n~%c", + (no_hlsearch || find_viminfo_parameter('h') != NULL) ? 'h' : 'H'); + wvsp_one(fp, RE_SEARCH, "", '/'); + wvsp_one(fp, RE_SUBST, _("Substitute "), '&'); + } +} + +static void +wvsp_one ( + FILE *fp, /* file to write to */ + int idx, /* spats[] index */ + char *s, /* search pat */ + int sc /* dir char */ +) +{ + if (spats[idx].pat != NULL) { + fprintf(fp, _("\n# Last %sSearch Pattern:\n~"), s); + /* off.dir is not stored, it's reset to forward */ + fprintf(fp, "%c%c%c%c%" PRId64 "%s%c", + spats[idx].magic ? 'M' : 'm', /* magic */ + spats[idx].no_scs ? 's' : 'S', /* smartcase */ + spats[idx].off.line ? 'L' : 'l', /* line offset */ + spats[idx].off.end ? 'E' : 'e', /* offset from end */ + (int64_t)spats[idx].off.off, /* offset */ + last_idx == idx ? "~" : "", /* last used pat */ + sc); + viminfo_writestring(fp, spats[idx].pat); + } +} + +/* + * Prepare for reading the history from the viminfo file. + * This allocates history arrays to store the read history lines. + */ +void prepare_viminfo_history(int asklen, int writing) +{ + int i; + int num; + + init_history(); + viminfo_add_at_front = (asklen != 0 && !writing); + if (asklen > hislen) + asklen = hislen; + + for (int type = 0; type < HIST_COUNT; ++type) { + /* Count the number of empty spaces in the history list. Entries read + * from viminfo previously are also considered empty. If there are + * more spaces available than we request, then fill them up. */ + for (i = 0, num = 0; i < hislen; i++) + if (history[type][i].hisstr == NULL || history[type][i].viminfo) + num++; + int len = asklen; + if (num > len) + len = num; + if (len <= 0) + viminfo_history[type] = NULL; + else + viminfo_history[type] = xmalloc(len * sizeof(char_u *)); + if (viminfo_history[type] == NULL) + len = 0; + viminfo_hislen[type] = len; + viminfo_hisidx[type] = 0; + } +} + +/* + * Accept a line from the viminfo, store it in the history array when it's + * new. + */ +int read_viminfo_history(vir_T *virp, int writing) +{ + int type; + char_u *val; + + type = hist_char2type(virp->vir_line[0]); + if (viminfo_hisidx[type] < viminfo_hislen[type]) { + val = viminfo_readstring(virp, 1, TRUE); + if (val != NULL && *val != NUL) { + int sep = (*val == ' ' ? NUL : *val); + + if (!in_history(type, val + (type == HIST_SEARCH), + viminfo_add_at_front, sep, writing)) { + /* Need to re-allocate to append the separator byte. */ + size_t len = STRLEN(val); + char_u *p = xmalloc(len + 2); + if (type == HIST_SEARCH) { + /* Search entry: Move the separator from the first + * column to after the NUL. */ + memmove(p, val + 1, len); + p[len] = sep; + } else { + /* Not a search entry: No separator in the viminfo + * file, add a NUL separator. */ + memmove(p, val, len + 1); + p[len + 1] = NUL; + } + viminfo_history[type][viminfo_hisidx[type]++] = p; + } + } + xfree(val); + } + return viminfo_readline(virp); +} + +/* + * Finish reading history lines from viminfo. Not used when writing viminfo. + */ +void finish_viminfo_history(void) +{ + int idx; + int i; + int type; + + for (type = 0; type < HIST_COUNT; ++type) { + if (history[type] == NULL) + continue; + idx = hisidx[type] + viminfo_hisidx[type]; + if (idx >= hislen) + idx -= hislen; + else if (idx < 0) + idx = hislen - 1; + if (viminfo_add_at_front) + hisidx[type] = idx; + else { + if (hisidx[type] == -1) + hisidx[type] = hislen - 1; + do { + if (history[type][idx].hisstr != NULL + || history[type][idx].viminfo) + break; + if (++idx == hislen) + idx = 0; + } while (idx != hisidx[type]); + if (idx != hisidx[type] && --idx < 0) + idx = hislen - 1; + } + for (i = 0; i < viminfo_hisidx[type]; i++) { + xfree(history[type][idx].hisstr); + history[type][idx].hisstr = viminfo_history[type][i]; + history[type][idx].viminfo = TRUE; + if (--idx < 0) + idx = hislen - 1; + } + idx += 1; + idx %= hislen; + for (i = 0; i < viminfo_hisidx[type]; i++) { + history[type][idx++].hisnum = ++hisnum[type]; + idx %= hislen; + } + xfree(viminfo_history[type]); + viminfo_history[type] = NULL; + viminfo_hisidx[type] = 0; + } +} + +/* + * Write history to viminfo file in "fp". + * When "merge" is TRUE merge history lines with a previously read viminfo + * file, data is in viminfo_history[]. + * When "merge" is FALSE just write all history lines. Used for ":wviminfo!". + */ +void write_viminfo_history(FILE *fp, int merge) +{ + int i; + int type; + int num_saved; + char_u *p; + int c; + int round; + + init_history(); + if (hislen == 0) + return; + for (type = 0; type < HIST_COUNT; ++type) { + num_saved = get_viminfo_parameter(hist_type2char(type, FALSE)); + if (num_saved == 0) + continue; + if (num_saved < 0) /* Use default */ + num_saved = hislen; + fprintf(fp, _("\n# %s History (newest to oldest):\n"), + type == HIST_CMD ? _("Command Line") : + type == HIST_SEARCH ? _("Search String") : + type == HIST_EXPR ? _("Expression") : + _("Input Line")); + if (num_saved > hislen) + num_saved = hislen; + + /* + * Merge typed and viminfo history: + * round 1: history of typed commands. + * round 2: history from recently read viminfo. + */ + for (round = 1; round <= 2; ++round) { + if (round == 1) + /* start at newest entry, somewhere in the list */ + i = hisidx[type]; + else if (viminfo_hisidx[type] > 0) + /* start at newest entry, first in the list */ + i = 0; + else + /* empty list */ + i = -1; + if (i >= 0) + while (num_saved > 0 + && !(round == 2 && i >= viminfo_hisidx[type])) { + p = round == 1 ? history[type][i].hisstr + : viminfo_history[type] == NULL ? NULL + : viminfo_history[type][i]; + if (p != NULL && (round == 2 + || !merge + || !history[type][i].viminfo)) { + --num_saved; + fputc(hist_type2char(type, TRUE), fp); + /* For the search history: put the separator in the + * second column; use a space if there isn't one. */ + if (type == HIST_SEARCH) { + c = p[STRLEN(p) + 1]; + putc(c == NUL ? ' ' : c, fp); + } + viminfo_writestring(fp, p); + } + if (round == 1) { + /* Decrement index, loop around and stop when back at + * the start. */ + if (--i < 0) + i = hislen - 1; + if (i == hisidx[type]) + break; + } else { + /* Increment index. Stop at the end in the while. */ + ++i; + } + } + } + for (i = 0; i < viminfo_hisidx[type]; ++i) + if (viminfo_history[type] != NULL) + xfree(viminfo_history[type][i]); + xfree(viminfo_history[type]); + viminfo_history[type] = NULL; + viminfo_hisidx[type] = 0; + } +} + +int read_viminfo_register(vir_T *virp, int force) +{ + int eof; + int do_it = TRUE; + int size; + int limit; + int set_prev = FALSE; + char_u *str; + char_u **array = NULL; + + /* We only get here (hopefully) if line[0] == '"' */ + str = virp->vir_line + 1; + + /* If the line starts with "" this is the y_previous register. */ + if (*str == '"') { + set_prev = TRUE; + str++; + } + + if (!ASCII_ISALNUM(*str) && *str != '-') { + if (viminfo_error("E577: ", _("Illegal register name"), virp->vir_line)) + return TRUE; /* too many errors, pretend end-of-file */ + do_it = FALSE; + } + yankreg_T *reg = get_yank_register(*str++, YREG_PUT); + if (!force && reg->y_array != NULL) + do_it = FALSE; + + if (*str == '@') { + /* "x@: register x used for @@ */ + if (force || execreg_lastc == NUL) + execreg_lastc = str[-1]; + } + + size = 0; + limit = 100; /* Optimized for registers containing <= 100 lines */ + if (do_it) { + if (set_prev) { + y_previous = reg; + } + + free_register(reg); + array = xmalloc(limit * sizeof(char_u *)); + + str = skipwhite(skiptowhite(str)); + if (STRNCMP(str, "CHAR", 4) == 0) { + reg->y_type = MCHAR; + } else if (STRNCMP(str, "BLOCK", 5) == 0) { + reg->y_type = MBLOCK; + } else { + reg->y_type = MLINE; + } + /* get the block width; if it's missing we get a zero, which is OK */ + str = skipwhite(skiptowhite(str)); + reg->y_width = getdigits_int(&str); + } + + while (!(eof = viminfo_readline(virp)) + && (virp->vir_line[0] == TAB || virp->vir_line[0] == '<')) { + if (do_it) { + if (size >= limit) { + limit *= 2; + array = xrealloc(array, limit * sizeof(char_u *)); + } + array[size++] = viminfo_readstring(virp, 1, TRUE); + } + } + + if (do_it) { + if (size == 0) { + xfree(array); + } else if (size < limit) { + reg->y_array = xrealloc(array, size * sizeof(char_u *)); + } else { + reg->y_array = array; + } + reg->y_size = size; + } + return eof; +} + +void write_viminfo_registers(FILE *fp) +{ + int i, j; + char_u *type; + char_u c; + int num_lines; + int max_num_lines; + int max_kbyte; + long len; + + fputs(_("\n# Registers:\n"), fp); + + /* Get '<' value, use old '"' value if '<' is not found. */ + max_num_lines = get_viminfo_parameter('<'); + if (max_num_lines < 0) + max_num_lines = get_viminfo_parameter('"'); + if (max_num_lines == 0) + return; + max_kbyte = get_viminfo_parameter('s'); + if (max_kbyte == 0) + return; + + // don't include clipboard registers '*'/'+' + for (i = 0; i < NUM_SAVED_REGISTERS; i++) { + if (y_regs[i].y_array == NULL) + continue; + + /* Skip empty registers. */ + num_lines = y_regs[i].y_size; + if (num_lines == 0 + || (num_lines == 1 && y_regs[i].y_type == MCHAR + && *y_regs[i].y_array[0] == NUL)) + continue; + + if (max_kbyte > 0) { + /* Skip register if there is more text than the maximum size. */ + len = 0; + for (j = 0; j < num_lines; j++) + len += (long)STRLEN(y_regs[i].y_array[j]) + 1L; + if (len > (long)max_kbyte * 1024L) + continue; + } + + switch (y_regs[i].y_type) { + case MLINE: + type = (char_u *)"LINE"; + break; + case MCHAR: + type = (char_u *)"CHAR"; + break; + case MBLOCK: + type = (char_u *)"BLOCK"; + break; + default: + sprintf((char *)IObuff, _("E574: Unknown register type %d"), + y_regs[i].y_type); + emsg(IObuff); + type = (char_u *)"LINE"; + break; + } + if (y_previous == &y_regs[i]) + fprintf(fp, "\""); + c = get_register_name(i); + fprintf(fp, "\"%c", c); + if (c == execreg_lastc) + fprintf(fp, "@"); + fprintf(fp, "\t%s\t%d\n", type, + (int)y_regs[i].y_width + ); + + /* If max_num_lines < 0, then we save ALL the lines in the register */ + if (max_num_lines > 0 && num_lines > max_num_lines) + num_lines = max_num_lines; + for (j = 0; j < num_lines; j++) { + putc('\t', fp); + viminfo_writestring(fp, y_regs[i].y_array[j]); + } + } +} + +/* + * Restore global vars that start with a capital from the viminfo file + */ +int read_viminfo_varlist(vir_T *virp, int writing) +{ + char_u *tab; + int type = VAR_NUMBER; + typval_T tv; + + if (!writing && (find_viminfo_parameter('!') != NULL)) { + tab = vim_strchr(virp->vir_line + 1, '\t'); + if (tab != NULL) { + *tab++ = NUL; /* isolate the variable name */ + switch (*tab) { + case 'S': type = VAR_STRING; break; + case 'F': type = VAR_FLOAT; break; + case 'D': type = VAR_DICT; break; + case 'L': type = VAR_LIST; break; + } + + tab = vim_strchr(tab, '\t'); + if (tab != NULL) { + tv.v_type = type; + if (type == VAR_STRING || type == VAR_DICT || type == VAR_LIST) + tv.vval.v_string = viminfo_readstring(virp, + (int)(tab - virp->vir_line + 1), TRUE); + else if (type == VAR_FLOAT) + (void)string2float(tab + 1, &tv.vval.v_float); + else + tv.vval.v_number = atol((char *)tab + 1); + if (type == VAR_DICT || type == VAR_LIST) { + typval_T *etv = eval_expr(tv.vval.v_string, NULL); + + if (etv == NULL) + /* Failed to parse back the dict or list, use it as a + * string. */ + tv.v_type = VAR_STRING; + else { + free(tv.vval.v_string); + tv = *etv; + free(etv); + } + } + + set_var(virp->vir_line + 1, &tv, FALSE); + + if (tv.v_type == VAR_STRING) + free(tv.vval.v_string); + else if (tv.v_type == VAR_DICT || tv.v_type == VAR_LIST) + clear_tv(&tv); + } + } + } + + return viminfo_readline(virp); +} + +/* + * Write global vars that start with a capital to the viminfo file + */ +void write_viminfo_varlist(FILE *fp) +{ + hashitem_T *hi; + dictitem_T *this_var; + int todo; + char *s; + char_u *p; + char_u *tofree; + char_u numbuf[NUMBUFLEN]; + + if (find_viminfo_parameter('!') == NULL) + return; + + fputs(_("\n# global variables:\n"), fp); + + todo = (int)globvarht.ht_used; + for (hi = globvarht.ht_array; todo > 0; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + --todo; + this_var = HI2DI(hi); + if (var_flavour(this_var->di_key) == VAR_FLAVOUR_VIMINFO) { + switch (this_var->di_tv.v_type) { + case VAR_STRING: s = "STR"; break; + case VAR_NUMBER: s = "NUM"; break; + case VAR_FLOAT: s = "FLO"; break; + case VAR_DICT: s = "DIC"; break; + case VAR_LIST: s = "LIS"; break; + default: continue; + } + fprintf(fp, "!%s\t%s\t", this_var->di_key, s); + p = echo_string(&this_var->di_tv, &tofree, numbuf, 0); + if (p != NULL) + viminfo_writestring(fp, p); + free(tofree); + } + } + } +} + +int read_viminfo_bufferlist(vir_T *virp, int writing) +{ + char_u *tab; + linenr_T lnum; + colnr_T col; + buf_T *buf; + char_u *sfname; + char_u *xline; + + /* Handle long line and escaped characters. */ + xline = viminfo_readstring(virp, 1, FALSE); + + /* don't read in if there are files on the command-line or if writing: */ + if (xline != NULL && !writing && ARGCOUNT == 0 + && find_viminfo_parameter('%') != NULL) { + /* Format is: Tab Tab . + * Watch out for a Tab in the file name, work from the end. */ + lnum = 0; + col = 0; + tab = vim_strrchr(xline, '\t'); + if (tab != NULL) { + *tab++ = '\0'; + col = (colnr_T)atoi((char *)tab); + tab = vim_strrchr(xline, '\t'); + if (tab != NULL) { + *tab++ = '\0'; + lnum = atol((char *)tab); + } + } + + /* Expand "~/" in the file name at "line + 1" to a full path. + * Then try shortening it by comparing with the current directory */ + expand_env(xline, NameBuff, MAXPATHL); + sfname = path_shorten_fname_if_possible(NameBuff); + + buf = buflist_new(NameBuff, sfname, (linenr_T)0, BLN_LISTED); + if (buf != NULL) { /* just in case... */ + buf->b_last_cursor.lnum = lnum; + buf->b_last_cursor.col = col; + buflist_setfpos(buf, curwin, lnum, col, FALSE); + } + } + xfree(xline); + + return viminfo_readline(virp); +} + +void write_viminfo_bufferlist(FILE *fp) +{ + char_u *line; + int max_buffers; + + if (find_viminfo_parameter('%') == NULL) + return; + + /* Without a number -1 is returned: do all buffers. */ + max_buffers = get_viminfo_parameter('%'); + + /* Allocate room for the file name, lnum and col. */ +#define LINE_BUF_LEN (MAXPATHL + 40) + line = xmalloc(LINE_BUF_LEN); + + FOR_ALL_TAB_WINDOWS(tp, win) { + set_last_cursor(win); + } + + fputs(_("\n# Buffer list:\n"), fp); + FOR_ALL_BUFFERS(buf) { + if (buf->b_fname == NULL + || !buf->b_p_bl + || bt_quickfix(buf) + || removable(buf->b_ffname)) + continue; + + if (max_buffers-- == 0) + break; + putc('%', fp); + home_replace(NULL, buf->b_ffname, line, MAXPATHL, TRUE); + vim_snprintf_add((char *)line, LINE_BUF_LEN, "\t%" PRId64 "\t%d", + (int64_t)buf->b_last_cursor.lnum, + buf->b_last_cursor.col); + viminfo_writestring(fp, line); + } + xfree(line); +} + +int read_viminfo_sub_string(vir_T *virp, int force) +{ + if (force) + xfree(old_sub); + if (force || old_sub == NULL) + old_sub = viminfo_readstring(virp, 1, TRUE); + return viminfo_readline(virp); +} + +void write_viminfo_sub_string(FILE *fp) +{ + if (get_viminfo_parameter('/') != 0 && old_sub != NULL) { + fputs(_("\n# Last Substitute String:\n$"), fp); + viminfo_writestring(fp, old_sub); + } +} + +/* + * Structure used for reading from the viminfo file. + */ +typedef struct { + char_u *vir_line; /* text of the current line */ + FILE *vir_fd; /* file descriptor */ + vimconv_T vir_conv; /* encoding conversion */ +} vir_T; +#endif diff --git a/src/nvim/shada.h b/src/nvim/shada.h new file mode 100644 index 0000000000..f0935796d7 --- /dev/null +++ b/src/nvim/shada.h @@ -0,0 +1,18 @@ +#ifndef NVIM_SHADA_H +#define NVIM_SHADA_H + +typedef long ShadaPosition; + +/// Flags for shada_read_file and children +enum { + kShaDaWantInfo = 1, ///< Load non-mark information + kShaDaWantMarks = 2, ///< Load file marks + kShaDaForceit = 4, ///< Overwrite info already read + kShaDaGetOldfiles = 8, ///< Load v:oldfiles. +}; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "shada.h.generated.h" +#endif + +#endif // NVIM_SHADA_H diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 75055cc21a..b0d1a17c89 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -130,7 +130,7 @@ static char_u *tagmatchname = NULL; /* name of last used tag */ * Tag for preview window is remembered separately, to avoid messing up the * normal tagstack. */ -static taggy_T ptag_entry = {NULL, {INIT_POS_T(0, 0, 0), 0}, 0, 0}; +static taggy_T ptag_entry = {NULL, {INIT_POS_T(0, 0, 0), 0, 0, NULL}, 0, 0}; /* * Jump to tag; handling of tag commands and tag stack diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 063c13084d..a04e7c2763 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -111,6 +111,7 @@ #include "nvim/types.h" #include "nvim/os/os.h" #include "nvim/os/time.h" +#include "nvim/api/private/helpers.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "undo.c.generated.h" @@ -325,6 +326,17 @@ static long get_undolevel(void) return curbuf->b_p_ul; } +static inline void zero_fmark_additional_data(fmark_T *fmarks) +{ + for (size_t i = 0; i < NMARKS; i++) { + if (fmarks[i].additional_data != NULL) { + api_free_dictionary(*fmarks[i].additional_data); + free(fmarks[i].additional_data); + fmarks[i].additional_data = NULL; + } + } +} + /* * Common code for various ways to save text before a change. * "top" is the line above the first changed line. @@ -467,7 +479,9 @@ int u_savecommon(linenr_T top, linenr_T bot, linenr_T newbot, int reload) ((curbuf->b_ml.ml_flags & ML_EMPTY) ? UH_EMPTYBUF : 0); /* save named marks and Visual marks for undo */ - memmove(uhp->uh_namedm, curbuf->b_namedm, sizeof(pos_T) * NMARKS); + zero_fmark_additional_data(curbuf->b_namedm); + memmove(uhp->uh_namedm, curbuf->b_namedm, + sizeof(curbuf->b_namedm[0]) * NMARKS); uhp->uh_visual = curbuf->b_visual; curbuf->b_u_newhead = uhp; @@ -785,7 +799,7 @@ static bool serialize_uhp(bufinfo_T *bi, u_header_T *uhp) undo_write_bytes(bi, (uintmax_t)uhp->uh_flags, 2); // Assume NMARKS will stay the same. for (size_t i = 0; i < (size_t)NMARKS; i++) { - serialize_pos(bi, uhp->uh_namedm[i]); + serialize_pos(bi, uhp->uh_namedm[i].mark); } serialize_visualinfo(bi, &uhp->uh_visual); uint8_t time_buf[8]; @@ -832,7 +846,9 @@ static u_header_T *unserialize_uhp(bufinfo_T *bi, char_u *file_name) uhp->uh_cursor_vcol = undo_read_4c(bi); uhp->uh_flags = undo_read_2c(bi); for (size_t i = 0; i < (size_t)NMARKS; i++) { - unserialize_pos(bi, &uhp->uh_namedm[i]); + unserialize_pos(bi, &uhp->uh_namedm[i].mark); + uhp->uh_namedm[i].timestamp = 0; + uhp->uh_namedm[i].fnum = 0; } unserialize_visualinfo(bi, &uhp->uh_visual); uhp->uh_time = undo_read_time(bi); @@ -2009,7 +2025,7 @@ static void u_undoredo(int undo) u_entry_T *newlist = NULL; int old_flags; int new_flags; - pos_T namedm[NMARKS]; + fmark_T namedm[NMARKS]; visualinfo_T visualinfo; int empty_buffer; /* buffer became empty */ u_header_T *curhead = curbuf->b_u_curhead; @@ -2029,7 +2045,8 @@ static void u_undoredo(int undo) /* * save marks before undo/redo */ - memmove(namedm, curbuf->b_namedm, sizeof(pos_T) * NMARKS); + zero_fmark_additional_data(curbuf->b_namedm); + memmove(namedm, curbuf->b_namedm, sizeof(curbuf->b_namedm[0]) * NMARKS); visualinfo = curbuf->b_visual; curbuf->b_op_start.lnum = curbuf->b_ml.ml_line_count; curbuf->b_op_start.col = 0; @@ -2158,7 +2175,8 @@ static void u_undoredo(int undo) * restore marks from before undo/redo */ for (i = 0; i < NMARKS; ++i) - if (curhead->uh_namedm[i].lnum != 0) { + if (curhead->uh_namedm[i].mark.lnum != 0) { + free_fmark(curbuf->b_namedm[i]); curbuf->b_namedm[i] = curhead->uh_namedm[i]; curhead->uh_namedm[i] = namedm[i]; } diff --git a/src/nvim/undo_defs.h b/src/nvim/undo_defs.h index 610adb4367..d841210815 100644 --- a/src/nvim/undo_defs.h +++ b/src/nvim/undo_defs.h @@ -5,6 +5,7 @@ #include "nvim/pos.h" #include "nvim/buffer_defs.h" +#include "nvim/mark_defs.h" /* Structure to store info about the Visual area. */ typedef struct { @@ -54,7 +55,7 @@ struct u_header { pos_T uh_cursor; /* cursor position before saving */ long uh_cursor_vcol; int uh_flags; /* see below */ - pos_T uh_namedm[NMARKS]; /* marks before undo/after redo */ + fmark_T uh_namedm[NMARKS]; /* marks before undo/after redo */ visualinfo_T uh_visual; /* Visual areas before undo/after redo */ time_t uh_time; /* timestamp when the change was made */ long uh_save_nr; /* set when the file was saved after the diff --git a/src/nvim/window.c b/src/nvim/window.c index 992463a8fc..b71b2cb603 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -1925,7 +1925,7 @@ int win_close(win_T *win, int free_buf) && (last_window() || curtab != prev_curtab || close_last_window_tabpage(win, free_buf, prev_curtab))) { /* Autocommands have close all windows, quit now. Restore - * curwin->w_buffer, otherwise writing viminfo may fail. */ + * curwin->w_buffer, otherwise writing ShaDa file may fail. */ if (curwin->w_buffer == NULL) curwin->w_buffer = curbuf; getout(0); diff --git a/test/functional/ex_getln/history_spec.lua b/test/functional/ex_getln/history_spec.lua new file mode 100644 index 0000000000..c3ef56c8ad --- /dev/null +++ b/test/functional/ex_getln/history_spec.lua @@ -0,0 +1,40 @@ +local helpers = require('test.functional.helpers') +local clear, nvim, call, eq = + helpers.clear, helpers.nvim, helpers.call, helpers.eq + +describe('history support code', function() + before_each(clear) + + local histadd = function(...) return call('histadd', ...) end + local histget = function(...) return call('histget', ...) end + local histdel = function(...) return call('histdel', ...) end + + it('correctly clears start of the history', function() + -- Regression test: check absense of the memory leak when clearing start of + -- the history using ex_getln.c/clr_history(). + eq(1, histadd(':', 'foo')) + eq(1, histdel(':')) + eq('', histget(':', -1)) + end) + + it('correctly clears end of the history', function() + -- Regression test: check absense of the memory leak when clearing end of + -- the history using ex_getln.c/clr_history(). + nvim('set_option', 'history', 1) + eq(1, histadd(':', 'foo')) + eq(1, histdel(':')) + eq('', histget(':', -1)) + end) + + it('correctly removes item from history', function() + -- Regression test: check that ex_getln.c/del_history_idx() correctly clears + -- history index after removing history entry. If it does not then deleting + -- history will result in a double free. + eq(1, histadd(':', 'foo')) + eq(1, histadd(':', 'bar')) + eq(1, histadd(':', 'baz')) + eq(1, histdel(':', -2)) + eq(1, histdel(':')) + eq('', histget(':', -1)) + end) +end) -- cgit From 38c688d7be52439ae5d610ff6deb22a0bff732e7 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 26 Apr 2015 03:36:06 +0300 Subject: shada: Use glibc functions to convert from big endian, add a fallback --- src/nvim/shada.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 66 insertions(+), 7 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 73aaa311b4..881e613114 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -5,6 +5,15 @@ #include #include #include +#if defined (__GLIBC__) +# ifndef _BSD_SOURCE +# define _BSD_SOURCE 1 +# endif +# ifndef _DEFAULT_SOURCE +# define _DEFAULT_SOURCE 1 +# endif +# include +#endif #include @@ -45,6 +54,43 @@ #define home_replace_save(a, b) \ ((char *)home_replace_save(a, (char_u *)b)) +// From http://www.boost.org/doc/libs/1_43_0/boost/detail/endian.hpp + some +// additional checks done after examining `{compiler} -dM -E - < /dev/null` +// output. +#if defined (__GLIBC__) +# if (__BYTE_ORDER == __BIG_ENDIAN) +# define SHADA_BIG_ENDIAN +# endif +#elif defined(_BIG_ENDIAN) || defined(_LITTLE_ENDIAN) +# if defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN) +# define SHADA_BIG_ENDIAN +# endif +// clang-specific +#elif defined(__BIG_ENDIAN__) || defined(__LITTLE_ENDIAN__) +# if defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN) +# define SHADA_BIG_ENDIAN +# endif +// pcc-, gcc- and clang-specific +#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) +# if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# define SHADA_BIG_ENDIAN +# endif +#elif defined(__sparc) || defined(__sparc__) \ + || defined(_POWER) || defined(__powerpc__) \ + || defined(__ppc__) || defined(__hpux) || defined(__hppa) \ + || defined(_MIPSEB) || defined(_POWER) \ + || defined(__s390__) +# define SHADA_BIG_ENDIAN +#elif defined(__i386__) || defined(__alpha__) \ + || defined(__ia64) || defined(__ia64__) \ + || defined(_M_IX86) || defined(_M_IA64) \ + || defined(_M_ALPHA) || defined(__amd64) \ + || defined(__amd64__) || defined(_M_AMD64) \ + || defined(__x86_64) || defined(__x86_64__) \ + || defined(_M_X64) || defined(__bfin__) +// Define nothing +#endif + /// Possible ShaDa entry types /// /// @warning Enum values are part of the API and must not be altered. @@ -1027,6 +1073,22 @@ static void shada_free_shada_entry(ShadaEntry *const entry) } } +#ifndef __GLIBC__ +static inline uint64_t be64toh(uint64_t big_endian_64_bits) +{ +#ifdef SHADA_BIG_ENDIAN + return big_endian_64_bits; +#else + uint8_t *buf = &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 next unsigned integer from file /// /// Errors out if the result is not an unsigned integer. @@ -1088,8 +1150,9 @@ static int msgpack_read_uint64(FILE *const fp, const int first_char, return FAIL; } } - uint8_t buf[8]; - size_t read_bytes = fread((char *) &(buf[0]), 1, length, fp); + uint8_t buf[sizeof(uint64_t)] = {0, 0, 0, 0, 0, 0, 0, 0}; + size_t read_bytes = fread((char *) &(buf[sizeof(uint64_t)-length]), 1, + length, fp); if (ferror(fp)) { emsg2("System error while reading ShaDa file: %s", strerror(errno)); @@ -1100,11 +1163,7 @@ static int msgpack_read_uint64(FILE *const fp, const int first_char, (int64_t) fpos); return FAIL; } - // TODO(ZyX-I): Just cast if current platform is big-endian. - *result = 0; - for (size_t i = length; i; i--) { - *result |= ((uint64_t) buf[i - 1]) << ((length - i) * 8); - } + *result = be64toh(*((uint64_t *) &(buf[0]))); } return OK; } -- cgit From 5e34d4873b760e038093596e5d44fa0e8ee20036 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 28 Apr 2015 22:42:00 +0300 Subject: main: Read marks at startup I do not know why marks were not read originally, but without this change marks are not going to be read at startup when Vim starts without arguments. --- src/nvim/main.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/nvim/main.c b/src/nvim/main.c index 0e25d6d4ec..ae89d341be 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -379,11 +379,13 @@ int main(int argc, char **argv) } /* - * Read in registers, history etc, but not marks, from the ShaDa file. + * Read in registers, history etc, from the ShaDa file. * This is where v:oldfiles gets filled. */ if (*p_viminfo != NUL) { - (void) shada_read_file(NULL, kShaDaWantInfo|kShaDaGetOldfiles); + (void) shada_read_file(NULL, (kShaDaWantInfo + | kShaDaGetOldfiles + | kShaDaWantMarks)); TIME_MSG("reading ShaDa"); } /* It's better to make v:oldfiles an empty list than NULL. */ -- cgit From e143be7f3da154db20a0fc3b709a3ab240c0ec7f Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 26 Apr 2015 15:31:39 +0300 Subject: functests: Add tests for ShaDa variables dumping/reading --- test/functional/helpers.lua | 9 +++- test/functional/shada/helpers.lua | 53 ++++++++++++++++++++ test/functional/shada/variables_spec.lua | 84 ++++++++++++++++++++++++++++++++ 3 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 test/functional/shada/helpers.lua create mode 100644 test/functional/shada/variables_spec.lua diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 80cb1e5ce3..a6f4f7c2e5 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -44,11 +44,15 @@ elseif os.getenv('GDB') then end if prepend_argv then + local new_nvim_argv = {} local len = #prepend_argv + for i = 1, len do + new_nvim_argv[i] = prepend_argv[i] + end for i = 1, #nvim_argv do - prepend_argv[i + len] = nvim_argv[i] + new_nvim_argv[i + len] = nvim_argv[i] end - nvim_argv = prepend_argv + nvim_argv = new_nvim_argv end local session, loop_running, loop_stopped, last_error @@ -338,6 +342,7 @@ local exc_exec = function(cmd) end return { + prepend_argv = prepend_argv, clear = clear, spawn = spawn, dedent = dedent, diff --git a/test/functional/shada/helpers.lua b/test/functional/shada/helpers.lua new file mode 100644 index 0000000000..7e93fcb915 --- /dev/null +++ b/test/functional/shada/helpers.lua @@ -0,0 +1,53 @@ +local helpers = require('test.functional.helpers') +local spawn, set_session, nvim, nvim_prog = + helpers.spawn, helpers.set_session, helpers.nvim, helpers.nvim_prog + +local tmpname = os.tmpname() +local additional_cmd = '' + +local function nvim_argv() + local ret + local nvim_argv = {nvim_prog, '-u', 'NONE', '-i', tmpname, '-N', + '--cmd', 'set shortmess+=I background=light noswapfile', + '--cmd', additional_cmd, + '--embed'} + if helpers.prepend_argv then + ret = {} + for i, v in ipairs(helpers.prepend_argv) do + ret[i] = v + end + local shift = #ret + for i, v in ipairs(nvim_argv) do + ret[i + shift] = v + end + else + ret = nvim_argv + end + return ret +end + +local session = nil + +local reset = function() + if session then + session:exit(0) + end + session = spawn(nvim_argv()) + set_session(session) + nvim('set_var', 'tmpname', tmpname) +end + +local set_additional_cmd = function(s) + additional_cmd = s +end + +local clear = function() + os.remove(tmpname) + set_additional_cmd('') +end + +return { + reset=reset, + set_additional_cmd=set_additional_cmd, + clear=clear, +} diff --git a/test/functional/shada/variables_spec.lua b/test/functional/shada/variables_spec.lua new file mode 100644 index 0000000000..c0b94b102e --- /dev/null +++ b/test/functional/shada/variables_spec.lua @@ -0,0 +1,84 @@ +-- ShaDa variables saving/reading support +local helpers = require('test.functional.helpers') +local nvim, nvim_command, nvim_eval, eq = + helpers.nvim, helpers.command, helpers.eval, helpers.eq + +local shada_helpers = require('test.functional.shada.helpers') +local reset, set_additional_cmd, clear = + shada_helpers.reset, shada_helpers.set_additional_cmd, + shada_helpers.clear + +describe('ShaDa support code', function() + before_each(reset) + after_each(clear) + + it('is able to dump and read back string variable', function() + nvim('set_var', 'STRVAR', 'foo') + nvim_command('set viminfo+=!') + nvim_command('wviminfo') + reset() + nvim_command('set viminfo+=!') + nvim_command('rviminfo') + eq('foo', nvim('get_var', 'STRVAR')) + end) + + local autotest = function(tname, varname, varval) + it('is able to dump and read back ' .. tname .. ' variable automatically', + function() + set_additional_cmd('set viminfo+=!') + reset() + nvim('set_var', varname, varval) + -- Exit during `reset` is not a regular exit: it does not write viminfo + -- automatically + nvim_command('qall') + reset() + eq(varval, nvim('get_var', varname)) + end) + end + + autotest('string', 'STRVAR', 'foo') + autotest('number', 'NUMVAR', 42) + autotest('float', 'FLTVAR', 42.5) + autotest('dictionary', 'DCTVAR', {a=10}) + autotest('list', 'LSTVAR', {{a=10}, {b=10.5}, {c='str'}}) + + it('does not read back variables without `!` in &viminfo', function() + nvim('set_var', 'STRVAR', 'foo') + nvim_command('set viminfo+=!') + nvim_command('wviminfo') + set_additional_cmd('set viminfo-=!') + reset() + nvim_command('rviminfo') + eq(0, nvim_eval('exists("g:STRVAR")')) + end) + + it('does not dump variables without `!` in &viminfo', function() + nvim_command('set viminfo-=!') + nvim('set_var', 'STRVAR', 'foo') + nvim_command('wviminfo') + reset() + nvim_command('set viminfo+=!') + nvim_command('rviminfo') + eq(0, nvim_eval('exists("g:STRVAR")')) + end) + + it('does not dump session variables', function() + nvim_command('set viminfo+=!') + nvim('set_var', 'StrVar', 'foo') + nvim_command('wviminfo') + reset() + nvim_command('set viminfo+=!') + nvim_command('rviminfo') + eq(0, nvim_eval('exists("g:StrVar")')) + end) + + it('does not dump regular variables', function() + nvim_command('set viminfo+=!') + nvim('set_var', 'str_var', 'foo') + nvim_command('wviminfo') + reset() + nvim_command('set viminfo+=!') + nvim_command('rviminfo') + eq(0, nvim_eval('exists("g:str_var")')) + end) +end) -- cgit From 46387b9aacb12546679603ecff88503f755c3a42 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 12 Jul 2015 03:29:31 +0300 Subject: functests: Add tests for mark dumping/reading --- test/functional/shada/marks_spec.lua | 77 ++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 test/functional/shada/marks_spec.lua diff --git a/test/functional/shada/marks_spec.lua b/test/functional/shada/marks_spec.lua new file mode 100644 index 0000000000..04a49a6d44 --- /dev/null +++ b/test/functional/shada/marks_spec.lua @@ -0,0 +1,77 @@ +-- ShaDa marks saving/reading support +local helpers = require('test.functional.helpers') +local nvim, nvim_window, nvim_curwin, nvim_command, nvim_eval, eq = + helpers.nvim, helpers.window, helpers.curwin, helpers.command, helpers.eval, + helpers.eq + +local shada_helpers = require('test.functional.shada.helpers') +local reset, set_additional_cmd, clear = + shada_helpers.reset, shada_helpers.set_additional_cmd, + shada_helpers.clear + +local nvim_current_line = function() + return nvim_window('get_cursor', nvim_curwin())[1] +end + +describe('ShaDa support code', function() + testfilename = 'Xtestfile-functional-shada-marks' + before_each(function() + reset() + local fd = io.open(testfilename, 'w') + fd:write('test\n') + fd:write('test2\n') + fd:close() + end) + after_each(function() + clear() + os.remove(testfilename) + end) + + it('is able to dump and read back global mark', function() + nvim_command('edit ' .. testfilename) + nvim_command('mark A') + nvim_command('2') + nvim_command('kB') + nvim_command('wviminfo') + reset() + nvim_command('rviminfo') + nvim_command('normal! `A') + eq(testfilename, nvim_eval('fnamemodify(@%, ":t")')) + eq(1, nvim_current_line()) + nvim_command('normal! `B') + eq(2, nvim_current_line()) + end) + + it('does not read back global mark without `f0` in viminfo', function() + nvim_command('edit ' .. testfilename) + nvim_command('mark A') + nvim_command('2') + nvim_command('kB') + nvim_command('wviminfo') + set_additional_cmd('set viminfo+=f0') + reset() + nvim_command('language C') + nvim_command([[ + try + execute "normal! `A" + catch + let exception = v:exception + endtry]]) + eq('Vim(normal):E20: Mark not set', nvim('get_var', 'exception')) + end) + + it('is able to dump and read back local mark', function() + nvim_command('edit ' .. testfilename) + nvim_command('mark a') + nvim_command('2') + nvim_command('kb') + nvim_command('qall') + reset() + nvim_command('edit ' .. testfilename) + nvim_command('normal! `a') + eq(testfilename, nvim_eval('fnamemodify(@%, ":t")')) + eq(1, nvim_current_line()) + nvim_command('normal! `b') + eq(2, nvim_current_line()) + end) +end) -- cgit From 94ed7ba03b39a9d047fdb809af081e13ec32ddd5 Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 29 Apr 2015 23:40:20 +0300 Subject: shada: Skip reading some items if needed Avoids unneeded malloc()s/free()s and lots of code for parsing. --- src/nvim/shada.c | 226 +++++++++++++++++++++++++++++++++++-------------------- src/nvim/shada.h | 1 + 2 files changed, 146 insertions(+), 81 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 881e613114..6a729597db 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -238,7 +238,7 @@ static void shada_read(FILE *const fp, const int flags) buf_T *local_mark_prev_buf = NULL; char *local_mark_prev_fname = NULL; size_t local_mark_prev_fname_len = 0; - while (shada_read_next_item(fp, &cur_entry) == NOTDONE) { + while (shada_read_next_item(fp, &cur_entry, flags) == NOTDONE) { switch (cur_entry.type) { case kSDItemMissing: { assert(false); @@ -1089,6 +1089,39 @@ static inline uint64_t be64toh(uint64_t big_endian_64_bits) } #endif +/// Read given number of bytes into given buffer, display error if needed +/// +/// @param[in] fp File to read from. +/// @param[out] buffer Where to save the results. May be NULL. +/// @param[in] length How many bytes should be read. +/// +/// @return FAIL if reading was not successfull, OK otherwise. +static int fread_len(FILE *const fp, char *const buffer, const size_t length) + FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT +{ + size_t read_bytes = 0; + if (buffer == NULL) { + do { + read_bytes += fread(IObuff, 1, length > IOSIZE ? IOSIZE : length, fp); + } while (read_bytes < length && !ferror(fp) && !feof(fp)); + } else { + read_bytes = fread(buffer, 1, length, fp); + } + + if (ferror(fp)) { + emsg2("System error while reading ShaDa file: %s", + strerror(errno)); + return FAIL; + } else if (read_bytes != length) { + emsgn("Error while reading ShaDa file: " + "last entry specified that it occupies %" PRId64 " bytes, " + "but file ended earlier", + (int64_t) length); + return FAIL; + } + return OK; +} + /// Read next unsigned integer from file /// /// Errors out if the result is not an unsigned integer. @@ -1108,6 +1141,9 @@ static int msgpack_read_uint64(FILE *const fp, const int first_char, FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { const long fpos = ftell(fp) - 1; + if (fpos == -2) { + clearerr(fp); + } if (first_char == EOF) { if (ferror(fp)) { @@ -1151,16 +1187,7 @@ static int msgpack_read_uint64(FILE *const fp, const int first_char, } } uint8_t buf[sizeof(uint64_t)] = {0, 0, 0, 0, 0, 0, 0, 0}; - size_t read_bytes = fread((char *) &(buf[sizeof(uint64_t)-length]), 1, - length, fp); - if (ferror(fp)) { - emsg2("System error while reading ShaDa file: %s", - strerror(errno)); - return FAIL; - } else if (read_bytes != length) { - emsgn("Error while reading ShaDa file: " - "not enough bytes for positive integer at position %" PRId64, - (int64_t) fpos); + if (fread_len(fp, (char *) &(buf[sizeof(uint64_t)-length]), length) != OK) { return FAIL; } *result = be64toh(*((uint64_t *) &(buf[0]))); @@ -1172,12 +1199,16 @@ static int msgpack_read_uint64(FILE *const fp, const int first_char, /// /// @param[in] fp Pointer to the opened ShaDa file. /// @param[out] entry Address where next entry contents will be saved. +/// @param[in] flags Flags, determining whether and which items should be +/// skipped. /// /// @return NOTDONE if entry was read correctly, FAIL if there were errors and /// OK at EOF. -static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry) +static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry, + const int flags) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { +shada_read_next_item_start: entry->type = kSDItemMissing; if (feof(fp)) { return OK; @@ -1190,6 +1221,9 @@ static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry) uint64_t length_u64; const long initial_fpos = ftell(fp); + if (initial_fpos == -1) { + clearerr(fp); + } const int first_char = fgetc(fp); if (first_char == EOF && feof(fp)) { return OK; @@ -1200,65 +1234,93 @@ static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry) || msgpack_read_uint64(fp, fgetc(fp), &length_u64) != OK) { return FAIL; } - if (type_u64 == 0) { - emsgn("Error while reading ShaDa file: " - "entry at position %" PRId64 "has invalid zero type", - (int64_t) initial_fpos); - return FAIL; - } const size_t length = (size_t) length_u64; entry->timestamp = (Timestamp) timestamp_u64; - if (type_u64 > SHADA_LAST_ENTRY) { - entry->data.unknown_item.size = length; - char *contents = xmalloc(length); - entry->data.unknown_item.contents = contents; - entry->data.unknown_item.type = type_u64; - size_t read_bytes = fread(contents, 1, length, fp); - if (ferror(fp)) { - free(entry->data.unknown_item.contents); - emsg2("System error while reading ShaDa file: %s", - strerror(errno)); - return FAIL; - } else if (read_bytes != length) { - free(entry->data.unknown_item.contents); + +#define SKIP \ + do { \ + if (fread_len(fp, NULL, length) != OK) { \ + return FAIL; \ + } \ + goto shada_read_next_item_start; \ + } while (0) + // TODO(ZyX-I): More precise skipping: skip reading some things depending on + // 'viminfo': e.g. variables if viminfo does not contain `!`. + // + // Option value should probably be checked elsewhere. + switch (type_u64) { + case kSDItemMissing: { emsgn("Error while reading ShaDa file: " - "last entry specified that it occupies %" PRId64 " bytes, " - "but file ended earlier", - (int64_t) length); + "entry at position %" PRId64 "has invalid zero type", + (int64_t) initial_fpos); return FAIL; } + case kSDItemHeader: { + if (!(flags & kShaDaWantHeader)) { + SKIP; + } + break; + } + case kSDItemBufferList: + case kSDItemJump: + case kSDItemVariable: + case kSDItemRegister: + case kSDItemHistoryEntry: + case kSDItemSubString: + case kSDItemSearchPattern: { + if (!(flags & kShaDaWantInfo)) { + SKIP; + } + break; + } + case kSDItemGlobalMark: + case kSDItemLocalMark: { + if (!(flags & kShaDaWantMarks)) { + SKIP; + } + break; + } + default: { + entry->data.unknown_item.size = length; + char *contents = xmalloc(length); + entry->data.unknown_item.contents = contents; + entry->data.unknown_item.type = type_u64; + if (fread_len(fp, contents, length) != OK) { + return FAIL; + } + entry->type = kSDItemUnknown; + return NOTDONE; + } + } +#undef SKIP + + if (type_u64 > SHADA_LAST_ENTRY) { entry->type = kSDItemUnknown; - return NOTDONE; + entry->data.unknown_item.size = length; + entry->data.unknown_item.type = type_u64; + entry->data.unknown_item.contents = xmalloc(length); + return fread_len(fp, entry->data.unknown_item.contents, length); } - msgpack_unpacked unpacked; - msgpack_unpacked_init(&unpacked); - msgpack_unpacker *unpacker = msgpack_unpacker_new(length); + msgpack_unpacker *const unpacker = msgpack_unpacker_new(length); if (unpacker == NULL || !msgpack_unpacker_reserve_buffer(unpacker, length)) { EMSG(e_outofmem); goto shada_read_next_item_error; } - msgpack_unpack_return result; - size_t read_bytes = 0; - const long fpos = ftell(fp); - read_bytes = fread(msgpack_unpacker_buffer(unpacker), 1, length, fp); - if (ferror(fp)) { - emsg2("System error while reading ShaDa file: %s", - strerror(errno)); - goto shada_read_next_item_error; - } else if (read_bytes != length) { - emsgn("Error while reading ShaDa file: " - "last entry specified that it occupies %" PRId64 " bytes, " - "but file ended earlier", - (int64_t) fpos); - goto shada_read_next_item_error; + if (fread_len(fp, msgpack_unpacker_buffer(unpacker), length) != OK) { + msgpack_unpacker_free(unpacker); + return FAIL; } - msgpack_unpacker_buffer_consumed(unpacker, read_bytes); + msgpack_unpacker_buffer_consumed(unpacker, length); - result = msgpack_unpacker_next(unpacker, &unpacked); + msgpack_unpacked unpacked; + msgpack_unpacked_init(&unpacked); + + const msgpack_unpack_return result = + msgpack_unpacker_next(unpacker, &unpacked); if (result != MSGPACK_UNPACK_SUCCESS) { if (result == MSGPACK_UNPACK_NOMEM_ERROR) { EMSG(e_outofmem); @@ -1286,7 +1348,7 @@ static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry) emsgn("Error while reading ShaDa file: " \ entry_name " entry at position %" PRId64 " " \ error_desc, \ - (int64_t) fpos); \ + (int64_t) initial_fpos); \ ga_clear(&ad_ga); \ goto shada_read_next_item_error; \ } \ @@ -1298,7 +1360,7 @@ static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry) emsgn("Error while reading ShaDa file: " \ entry_name " entry at position %" PRId64 " " \ "has key which is not a string", \ - (int64_t) fpos); \ + (int64_t) initial_fpos); \ emsgn("It is %" PRId64 " instead", \ unpacked.data.via.map.ptr[i].key.type ); \ ga_clear(&ad_ga); \ @@ -1347,7 +1409,7 @@ static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry) if (!msgpack_rpc_to_dictionary(&(unpacked.data), &(entry->data.header))) { emsgn("Error while reading ShaDa file: " "header entry at position %" PRId64 " is not a dictionary", - (int64_t) fpos); + (int64_t) initial_fpos); goto shada_read_next_item_error; } break; @@ -1357,7 +1419,7 @@ static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry) emsgn("Error while reading ShaDa file: " "search pattern entry at position %" PRId64 " " "is not a dictionary", - (int64_t) fpos); + (int64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.search_pattern = (struct search_pattern) { @@ -1395,7 +1457,7 @@ static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry) emsgn("Error while reading ShaDa file: " "search pattern entry at position %" PRId64 " " "has no pattern", - (int64_t) fpos); + (int64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; } @@ -1431,7 +1493,7 @@ static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry) emsgn("Error while reading ShaDa file: " "mark entry at position %" PRId64 " " "is not a dictionary", - (int64_t) fpos); + (int64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.filemark = (struct shada_filemark) { @@ -1460,7 +1522,7 @@ static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry) emsgn("Error while reading ShaDa file: " "mark entry at position %" PRId64 " " "is missing line number", - (int64_t) fpos); + (int64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; } @@ -1493,7 +1555,7 @@ static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry) emsgn("Error while reading ShaDa file: " "register entry at position %" PRId64 " " "is not a dictionary", - (int64_t) fpos); + (int64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.reg = (struct reg) { @@ -1519,7 +1581,7 @@ static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry) emsgn("Error while reading ShaDa file: " "register entry at position %" PRId64 " " "has contents key with non-array value", - (int64_t) fpos); + (int64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; } @@ -1527,7 +1589,7 @@ static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry) emsgn("Error while reading ShaDa file: " "register entry at position %" PRId64 " " "has contents key with empty array", - (int64_t) fpos); + (int64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; } @@ -1538,7 +1600,7 @@ static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry) emsgn("Error while reading ShaDa file: " "register entry at position %" PRId64 " " "has contents array with non-string value", - (int64_t) fpos); + (int64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; } @@ -1555,7 +1617,7 @@ static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry) emsgn("Error while reading ShaDa file: " "register entry at position %" PRId64 " " "has missing contents array", - (int64_t) fpos); + (int64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; } @@ -1588,7 +1650,7 @@ static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry) emsgn("Error while reading ShaDa file: " "history entry at position %" PRId64 " " "is not an array", - (int64_t) fpos); + (int64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.history_item = (struct history_item) { @@ -1600,7 +1662,7 @@ static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry) emsgn("Error while reading ShaDa file: " "history entry at position %" PRId64 " " "does not have enough elements", - (int64_t) fpos); + (int64_t) initial_fpos); goto shada_read_next_item_error; } if (unpacked.data.via.array.ptr[0].type @@ -1608,7 +1670,7 @@ static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry) emsgn("Error while reading ShaDa file: " "history entry at position %" PRId64 " " "has wrong history type type", - (int64_t) fpos); + (int64_t) initial_fpos); goto shada_read_next_item_error; } if (unpacked.data.via.array.ptr[1].type @@ -1616,7 +1678,7 @@ static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry) emsgn("Error while reading ShaDa file: " "history entry at position %" PRId64 " " "has wrong history string type", - (int64_t) fpos); + (int64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.history_item.histtype = @@ -1651,7 +1713,7 @@ static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry) emsgn("Error while reading ShaDa file: " "variable entry at position %" PRId64 " " "is not an array", - (int64_t) fpos); + (int64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.global_var = (struct global_var) { @@ -1665,14 +1727,14 @@ static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry) emsgn("Error while reading ShaDa file: " "variable entry at position %" PRId64 " " "does not have enough elements", - (int64_t) fpos); + (int64_t) initial_fpos); goto shada_read_next_item_error; } if (unpacked.data.via.array.ptr[0].type != MSGPACK_OBJECT_BIN) { emsgn("Error while reading ShaDa file: " "variable entry at position %" PRId64 " " "has wrong variable name type", - (int64_t) fpos); + (int64_t) initial_fpos); goto shada_read_next_item_error; } if (unpacked.data.via.array.ptr[1].type == MSGPACK_OBJECT_NIL @@ -1680,7 +1742,7 @@ static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry) emsgn("Error while reading ShaDa file: " "variable entry at position %" PRId64 " " "has wrong variable value type", - (int64_t) fpos); + (int64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.global_var.name = @@ -1721,7 +1783,7 @@ static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry) emsgn("Error while reading ShaDa file: " "sub string entry at position %" PRId64 " " "is not an array", - (int64_t) fpos); + (int64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.sub_string = (struct sub_string) { @@ -1732,14 +1794,14 @@ static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry) emsgn("Error while reading ShaDa file: " "sub string entry at position %" PRId64 " " "does not have enough elements", - (int64_t) fpos); + (int64_t) initial_fpos); goto shada_read_next_item_error; } if (unpacked.data.via.array.ptr[0].type != MSGPACK_OBJECT_BIN) { emsgn("Error while reading ShaDa file: " "sub string entry at position %" PRId64 " " "has wrong sub string type", - (int64_t) fpos); + (int64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.sub_string.sub = @@ -1771,20 +1833,22 @@ static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry) if (!msgpack_rpc_to_array(&(unpacked.data), &(entry->data.buffer_list))) { emsgn("Error while reading ShaDa file: " "buffer list entry at position %" PRId64 " is not an array", - (int64_t) fpos); + (int64_t) initial_fpos); goto shada_read_next_item_error; } break; } - case kSDItemMissing: - case kSDItemUnknown: { + case kSDItemMissing: { emsgu("Error while reading ShaDa file: " "there is an item at position %" PRIu64 " " - "that must not be there: Missing and Unknown items are " + "that must not be there: Missing items are " "for internal uses only", (uint64_t) initial_fpos); goto shada_read_next_item_error; } + case kSDItemUnknown: { + assert(false); + } } entry->type = (ShadaEntryType) type_u64; goto shada_read_next_item_end; diff --git a/src/nvim/shada.h b/src/nvim/shada.h index f0935796d7..ef7527677e 100644 --- a/src/nvim/shada.h +++ b/src/nvim/shada.h @@ -9,6 +9,7 @@ enum { kShaDaWantMarks = 2, ///< Load file marks kShaDaForceit = 4, ///< Overwrite info already read kShaDaGetOldfiles = 8, ///< Load v:oldfiles. + kShaDaWantHeader = 16, ///< Do not skip header (shada_read_next_item). }; #ifdef INCLUDE_GENERATED_DECLARATIONS -- cgit From 0fe9679101037daa6f74deaa52900c077be9ab17 Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 8 May 2015 20:05:34 +0300 Subject: shada: Initial support for merging history Currently only merges history when reading ShaDa file. No tests yet. --- src/nvim/ex_getln.c | 55 ++++++++-- src/nvim/ex_getln.h | 1 - src/nvim/lib/ringbuf.h | 281 +++++++++++++++++++++++++++++++++++++++++++++++++ src/nvim/shada.c | 268 ++++++++++++++++++++++++++++++++++++++++------ 4 files changed, 564 insertions(+), 41 deletions(-) create mode 100644 src/nvim/lib/ringbuf.h diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 1347feb857..38f84f2e7f 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -4274,8 +4274,7 @@ 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 sep ) { int i; @@ -4293,7 +4292,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; @@ -4313,7 +4311,6 @@ in_history ( last_i = i; } history[type][i].hisnum = ++hisnum[type]; - history[type][i].viminfo = FALSE; history[type][i].hisstr = str; history[type][i].timestamp = os_time(); history[type][i].additional_elements = NULL; @@ -4386,7 +4383,7 @@ add_to_history ( } 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]]; @@ -4400,7 +4397,6 @@ add_to_history ( hisptr->hisstr[len + 1] = sep; hisptr->hisnum = ++hisnum[histype]; - hisptr->viminfo = FALSE; if (histype == HIST_SEARCH && in_map) last_maptick = maptick; } @@ -4768,6 +4764,32 @@ void ex_history(exarg_T *eap) } } +/// Translate a history type number to the associated character +int hist_type2char(int type) + FUNC_ATTR_CONST +{ + switch (type) { + case HIST_CMD: { + return ':'; + } + case HIST_SEARCH: { + return '/'; + } + case HIST_EXPR: { + return '='; + } + case HIST_INPUT: { + return '@'; + } + case HIST_DEBUG: { + return '>'; + } + default: { + assert(false); + } + } +} + /* * Write a character at the current cursor+offset position. * It is directly written into the command buffer block. @@ -5078,7 +5100,7 @@ char_u *script_get(exarg_T *eap, char_u *cmd) /// /// @return Pointer used in next iteration or NULL to indicate that iteration /// was finished. -const void *hist_iter(const void *const iter, const size_t history_type, +const 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) { @@ -5121,3 +5143,22 @@ const void *hist_iter(const void *const iter, const size_t history_type, 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 9c7a688e7e..738e515f21 100644 --- a/src/nvim/ex_getln.h +++ b/src/nvim/ex_getln.h @@ -38,7 +38,6 @@ typedef char_u *(*CompleteListItemGetter)(expand_T *, int); /// History entry definition typedef struct hist_entry { int hisnum; ///< Entry identifier number. - bool viminfo; ///< If true, indicates that entry comes from viminfo. char_u *hisstr; ///< Actual entry, separator char after the NUL. Timestamp timestamp; ///< Time when entry was added. Array *additional_elements; ///< Additional entries from ShaDa file. diff --git a/src/nvim/lib/ringbuf.h b/src/nvim/lib/ringbuf.h new file mode 100644 index 0000000000..f86d656deb --- /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 +#include +#include + +#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/shada.c b/src/nvim/shada.c index 6a729597db..de2e89736f 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -42,6 +42,7 @@ #include "nvim/eval_defs.h" #include "nvim/version.h" #include "nvim/path.h" +#include "nvim/lib/ringbuf.h" #define buflist_nr2name(...) ((char *) buflist_nr2name(__VA_ARGS__)) #define copy_option_part(src, dest, ...) \ @@ -140,6 +141,8 @@ typedef struct { struct history_item { uint8_t histtype; char *string; + char sep; + bool canfree; Array *additional_elements; } history_item; struct reg { @@ -168,10 +171,22 @@ typedef struct { } data; } ShadaEntry; +RINGBUF_TYPEDEF(HM, ShadaEntry) + +typedef struct { + HMRingBuffer hmrb; + bool do_merge; + const void *iter; + ShadaEntry last_hist_entry; + uint8_t history_type; +} HistoryMergerState; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "shada.c.generated.h" #endif +RINGBUF_INIT(HM, hm, ShadaEntry, shada_free_shada_entry) + /// Msgpack callback for writing to FILE* static int msgpack_fbuffer_write(void *data, const char *buf, size_t len) { @@ -227,6 +242,93 @@ int shada_read_file(const char *const file, const int flags) return OK; } +/// Wrapper for hist_iter() function which produces ShadaEntry values +/// +/// @warning Zeroes original items in process. +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, + .canfree = zero, + } + } + }; + } + 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. +static void insert_history_entry(HistoryMergerState *const hms_p, + const ShadaEntry entry, + const bool no_iter) +{ + HMRingBuffer *const rb = &(hms_p->hmrb); + RINGBUF_FORALL(rb, ShadaEntry, cur_entry) { + if (STRCMP(cur_entry->data.history_item.string, + entry.data.history_item.string) == 0) { + if (entry.timestamp > cur_entry->timestamp) { + hm_rb_remove(rb, (size_t) hm_rb_find_idx(rb, cur_entry)); + } else { + return; + } + } + } + if (!no_iter) { + if (hms_p->iter == NULL) { + if (hms_p->last_hist_entry.type != kSDItemMissing + && hms_p->last_hist_entry.timestamp < entry.timestamp) { + insert_history_entry(hms_p, hms_p->last_hist_entry, false); + hms_p->last_hist_entry.type = kSDItemMissing; + } + } else { + while (hms_p->iter != NULL + && hms_p->last_hist_entry.type != kSDItemMissing + && hms_p->last_hist_entry.timestamp < entry.timestamp) { + insert_history_entry(hms_p, hms_p->last_hist_entry, false); + hms_p->iter = shada_hist_iter(hms_p->iter, hms_p->history_type, true, + &(hms_p->last_hist_entry)); + } + } + } + ShadaEntry *insert_after; + RINGBUF_ITER_BACK(rb, ShadaEntry, insert_after) { + if (insert_after->timestamp <= entry.timestamp) { + break; + } + } + hm_rb_insert(rb, (size_t) (hm_rb_find_idx(rb, insert_after) + 1), entry); +} + /// Read data from ShaDa file /// /// @param[in] fp File to read from. @@ -234,6 +336,15 @@ int shada_read_file(const char *const file, const int flags) static void shada_read(FILE *const fp, const int flags) FUNC_ATTR_NONNULL_ALL { + HistoryMergerState hms[HIST_COUNT]; + if (flags & kShaDaWantInfo && p_hi) { + for (uint8_t i = 0; i < HIST_COUNT; i++) { + hms[i].hmrb = hm_rb_new((size_t) p_hi); + hms[i].do_merge = true; + hms[i].iter = shada_hist_iter(NULL, i, true, &(hms[i].last_hist_entry)); + hms[i].history_type = i; + } + } ShadaEntry cur_entry; buf_T *local_mark_prev_buf = NULL; char *local_mark_prev_fname = NULL; @@ -290,12 +401,17 @@ static void shada_read(FILE *const fp, const int flags) break; } case kSDItemHistoryEntry: { - if (!(flags & kShaDaWantInfo)) { + if (!(flags & kShaDaWantInfo && p_hi)) { shada_free_shada_entry(&cur_entry); break; } - // FIXME - shada_free_shada_entry(&cur_entry); + if (cur_entry.data.history_item.histtype >= HIST_COUNT) { + shada_free_shada_entry(&cur_entry); + break; + } + insert_history_entry(hms + cur_entry.data.history_item.histtype, + cur_entry, true); + // Do not free shada entry: its allocated memory was saved above. break; } case kSDItemRegister: { @@ -353,6 +469,7 @@ static void shada_read(FILE *const fp, const int flags) .additional_data = cur_entry.data.filemark.additional_data, }, }, !(flags & kShaDaForceit)); + // Do not free shada entry: its allocated memory was saved above. break; } case kSDItemJump: { @@ -407,6 +524,43 @@ static void shada_read(FILE *const fp, const int flags) } } } + // 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 (flags & kShaDaWantInfo && p_hi) { + for (uint8_t i = 0; i < HIST_COUNT; i++) { + if (hms[i].last_hist_entry.type != kSDItemMissing) { + insert_history_entry(&(hms[i]), hms[i].last_hist_entry, false); + } + while (hms[i].iter != NULL + && hms[i].last_hist_entry.type != kSDItemMissing) { + hms[i].iter = shada_hist_iter(hms[i].iter, hms[i].history_type, true, + &(hms[i].last_hist_entry)); + insert_history_entry(&(hms[i]), hms[i].last_hist_entry, false); + } + clr_history(i); + int *new_hisidx; + int *new_hisnum; + histentry_T *hist = hist_get_array(i, &new_hisidx, &new_hisnum); + if (hist != NULL) { + histentry_T *const hist_init = hist; + RINGBUF_FORALL(&(hms[i].hmrb), ShadaEntry, cur_entry) { + hist->timestamp = cur_entry->timestamp; + hist->hisnum = (int) (hist - hist_init) + 1; + hist->hisstr = (char_u *) cur_entry->data.history_item.string; + hist->additional_elements = + cur_entry->data.history_item.additional_elements; + hist++; + } + *new_hisnum = (int) hm_rb_length(&(hms[i].hmrb)); + *new_hisidx = *new_hisnum - 1; + } + hm_rb_dealloc(&(hms[i].hmrb)); + } + } xfree(local_mark_prev_fname); FOR_ALL_BUFFERS(buf) { fmarks_check_names(buf); @@ -491,7 +645,9 @@ static void shada_pack_entry(msgpack_packer *const packer, break; } case kSDItemHistoryEntry: { - const size_t arr_size = 2 + ( + const bool is_hist_search = + entry.data.history_item.histtype == HIST_SEARCH; + const size_t arr_size = 2 + (size_t) is_hist_search + ( entry.data.history_item.additional_elements == NULL ? 0 : entry.data.history_item.additional_elements->size); @@ -499,7 +655,10 @@ static void shada_pack_entry(msgpack_packer *const packer, msgpack_pack_uint8(spacker, entry.data.history_item.histtype); msgpack_rpc_from_string(cstr_as_string(entry.data.history_item.string), spacker); - for (size_t i = 0; i < arr_size - 2; i++) { + if (is_hist_search) { + msgpack_pack_uint8(spacker, (uint8_t) entry.data.history_item.sep); + } + for (size_t i = 0; i < arr_size - 2 - (size_t) is_hist_search; i++) { msgpack_rpc_from_object( entry.data.history_item.additional_elements->items[i], spacker); } @@ -798,26 +957,35 @@ static void shada_write(FILE *const newfp, FILE *const oldfp) // FIXME No merging currently // 4. History - const void *hist_iters[HIST_COUNT] = {NULL, NULL, NULL, NULL, NULL}; + HistoryMergerState hms[HIST_COUNT]; for (uint8_t i = 0; i < HIST_COUNT; i++) { - do { - histentry_T cur_hist; - hist_iters[i] = hist_iter(hist_iters[i], i, false, &cur_hist); - if (cur_hist.hisstr == NULL) { - break; - } - shada_pack_entry(packer, (ShadaEntry) { - .type = kSDItemHistoryEntry, - .timestamp = cur_hist.timestamp, - .data = { - .history_item = { - .histtype = i, - .string = (char *) cur_hist.hisstr, - .additional_elements = cur_hist.additional_elements, + long num_saved = get_viminfo_parameter(hist_type2char(i)); + if (num_saved == -1) { + num_saved = p_hi; + } + if (num_saved > 0) { + HistoryMergerState *hms_p = &(hms[i]); + hms_p->hmrb = hm_rb_new((size_t) num_saved); + hms_p->do_merge = false; + hms_p->iter = shada_hist_iter(NULL, i, false, &(hms[i].last_hist_entry)); + hms_p->history_type = i; + if (hms_p->last_hist_entry.type != kSDItemMissing) { + hm_rb_push(&(hms_p->hmrb), hms_p->last_hist_entry); + while (hms_p->iter != NULL) { + hms_p->iter = shada_hist_iter(hms_p->iter, hms_p->history_type, false, + &(hms_p->last_hist_entry)); + if (hms_p->last_hist_entry.type != kSDItemMissing) { + hm_rb_push(&(hms_p->hmrb), hms_p->last_hist_entry); + } else { + break; } } - }, max_kbyte); - } while (hist_iters[i] != NULL); + } + RINGBUF_FORALL(&(hms_p->hmrb), ShadaEntry, cur_entry) { + shada_pack_entry(packer, *cur_entry, max_kbyte); + } + hm_rb_dealloc(&hms_p->hmrb); + } } // 5. Search patterns @@ -1042,11 +1210,13 @@ static void shada_free_shada_entry(ShadaEntry *const entry) break; } case kSDItemHistoryEntry: { - if (entry->data.history_item.additional_elements != NULL) { - api_free_array(*entry->data.history_item.additional_elements); - xfree(entry->data.history_item.additional_elements); + if (entry->data.history_item.canfree) { + if (entry->data.history_item.additional_elements != NULL) { + api_free_array(*entry->data.history_item.additional_elements); + xfree(entry->data.history_item.additional_elements); + } + xfree(entry->data.history_item.string); } - xfree(entry->data.history_item.string); break; } case kSDItemVariable: { @@ -1656,7 +1826,8 @@ shada_read_next_item_start: entry->data.history_item = (struct history_item) { .histtype = 0, .string = NULL, - .additional_elements = NULL + .sep = 0, + .additional_elements = NULL, }; if (unpacked.data.via.array.size < 2) { emsgn("Error while reading ShaDa file: " @@ -1683,16 +1854,47 @@ shada_read_next_item_start: } entry->data.history_item.histtype = (uint8_t) unpacked.data.via.array.ptr[0].via.u64; - entry->data.history_item.string = - xmemdupz(unpacked.data.via.array.ptr[1].via.bin.ptr, - unpacked.data.via.array.ptr[1].via.bin.size); - if (unpacked.data.via.array.size > 2) { + const bool is_hist_search = + entry->data.history_item.histtype == HIST_SEARCH; + if (is_hist_search) { + if (unpacked.data.via.array.size < 3) { + emsgn("Error while reading ShaDa file: " + "search history entry at position %" PRId64 " " + "does not have separator character", + (int64_t) initial_fpos); + goto shada_read_next_item_error; + } + if (unpacked.data.via.array.ptr[2].type + != MSGPACK_OBJECT_POSITIVE_INTEGER) { + emsgn("Error while reading ShaDa file: " + "search history entry at position %" PRId64 " " + "has wrong history separator type", + (int64_t) initial_fpos); + goto shada_read_next_item_error; + } + entry->data.history_item.sep = + (char) unpacked.data.via.array.ptr[2].via.u64; + } + const size_t 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); + entry->data.history_item.string[strsize - 2] = 0; + entry->data.history_item.string[strsize - 1] = + entry->data.history_item.sep; + if (unpacked.data.via.array.size > (size_t) (2 + is_hist_search)) { msgpack_object obj = { .type = MSGPACK_OBJECT_ARRAY, .via = { .array = { - .size = unpacked.data.via.array.size - 2, - .ptr = unpacked.data.via.array.ptr + 2, + .size = (unpacked.data.via.array.size + - (uint32_t) (2 + is_hist_search)), + .ptr = unpacked.data.via.array.ptr + (2 + is_hist_search), } } }; -- cgit From bc5252261684001d39f2d3a491f8e9295d1f76ed Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 28 Jun 2015 05:18:51 +0300 Subject: functests: Add history tests --- test/functional/shada/history_spec.lua | 105 +++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 test/functional/shada/history_spec.lua diff --git a/test/functional/shada/history_spec.lua b/test/functional/shada/history_spec.lua new file mode 100644 index 0000000000..b73a39f116 --- /dev/null +++ b/test/functional/shada/history_spec.lua @@ -0,0 +1,105 @@ +-- ShaDa history saving/reading support +local helpers = require('test.functional.helpers') +local nvim, nvim_command, nvim_eval, nvim_feed, eq = + helpers.nvim, helpers.command, helpers.eval, helpers.feed, helpers.eq + +local shada_helpers = require('test.functional.shada.helpers') +local reset, set_additional_cmd, clear = + shada_helpers.reset, shada_helpers.set_additional_cmd, + shada_helpers.clear + +describe('ShaDa support code', function() + before_each(reset) + after_each(clear) + + it('is able to dump and read back command-line history', function() + nvim_command('set viminfo=\'0') + nvim_feed(':" Test\n') + nvim_command('wviminfo') + reset() + nvim_command('set viminfo=\'0') + nvim_command('rviminfo') + eq('" Test', nvim_eval('histget(":", -1)')) + end) + + it('is able to dump and read back 2 items in command-line history', function() + nvim_command('set viminfo=\'0 history=2') + nvim_feed(':" Test\n') + nvim_feed(':" Test 2\n') + nvim_command('qall') + reset() + nvim_command('set viminfo=\'0 history=2') + nvim_command('rviminfo') + eq('" Test 2', nvim_eval('histget(":", -1)')) + eq('" Test', nvim_eval('histget(":", -2)')) + nvim_command('qall') + end) + + it('respects &history when dumping', + function() + nvim_command('set viminfo=\'0 history=1') + nvim_feed(':" Test\n') + nvim_feed(':" Test 2\n') + nvim_command('wviminfo') + reset() + nvim_command('set viminfo=\'0 history=2') + nvim_command('rviminfo') + eq('" Test 2', nvim_eval('histget(":", -1)')) + eq('', nvim_eval('histget(":", -2)')) + end) + + it('respects &history when loading', + function() + nvim_command('set viminfo=\'0 history=2') + nvim_feed(':" Test\n') + nvim_feed(':" Test 2\n') + nvim_command('wviminfo') + reset() + nvim_command('set viminfo=\'0 history=1') + nvim_command('rviminfo') + eq('" Test 2', nvim_eval('histget(":", -1)')) + eq('', nvim_eval('histget(":", -2)')) + end) + + it('dumps only requested amount of command-line history items', function() + nvim_command('set viminfo=\'0,:1') + nvim_feed(':" Test\n') + nvim_feed(':" Test 2\n') + nvim_command('wviminfo') + reset() + nvim_command('set viminfo=\'0') + nvim_command('rviminfo') + eq('" Test 2', nvim_eval('histget(":", -1)')) + eq('', nvim_eval('histget(":", -2)')) + end) + + it('does not respect number in &viminfo when loading history', function() + nvim_command('set viminfo=\'0') + nvim_feed(':" Test\n') + nvim_feed(':" Test 2\n') + nvim_command('wviminfo') + reset() + nvim_command('set viminfo=\'0,:1') + nvim_command('rviminfo') + eq('" Test 2', nvim_eval('histget(":", -1)')) + eq('" Test', nvim_eval('histget(":", -2)')) + end) + + it('dumps and loads all kinds of histories', function() + nvim_command('debuggreedy') + nvim_feed(':debug echo "Test"\n" Test 2\nc\n') -- Debug history. + nvim_feed(':call input("")\nTest 2\n') -- Input history. + nvim_feed('"="Test"\nyy') -- Expression history. + nvim_feed('/Test\n') -- Search history + nvim_feed(':" Test\n') -- Command-line history + nvim_command('0debuggreedy') + nvim_command('wviminfo') + reset() + nvim_command('rviminfo') + eq('" Test', nvim_eval('histget(":", -1)')) + eq('Test', nvim_eval('histget("/", -1)')) + eq('"Test"', nvim_eval('histget("=", -1)')) + eq('Test 2', nvim_eval('histget("@", -1)')) + eq('c', nvim_eval('histget(">", -1)')) + end) +end) -- cgit From 9ab08c82569d57fad29da95dc776ae288300903e Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 28 Jun 2015 07:36:51 +0300 Subject: functests: Test loading last search/substitute pattern/replacement --- test/functional/shada/history_spec.lua | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/functional/shada/history_spec.lua b/test/functional/shada/history_spec.lua index b73a39f116..811afd268f 100644 --- a/test/functional/shada/history_spec.lua +++ b/test/functional/shada/history_spec.lua @@ -102,4 +102,26 @@ describe('ShaDa support code', function() eq('Test 2', nvim_eval('histget("@", -1)')) eq('c', nvim_eval('histget(">", -1)')) end) + + it('dumps and loads last search pattern with offset', function() + nvim_eval('setline(".", ["foo", "bar"])') + nvim_feed('gg0/a/e+1\n') + eq({0, 2, 3, 0}, nvim_eval('getpos(".")')) + nvim_command('wviminfo') + reset() + nvim_eval('setline(".", ["foo", "bar"])') + nvim_feed('gg0n') + eq({0, 2, 3, 0}, nvim_eval('getpos(".")')) + end) + + it('dumps and loads last substitute pattern and replacement string', function() + nvim_eval('setline(".", ["foo", "bar"])') + nvim_command('%s/f/g/g') + eq('goo', nvim_eval('getline(1)')) + nvim_command('wviminfo') + reset() + nvim_eval('setline(".", ["foo", "bar"])') + nvim_command('&') + eq('goo', nvim_eval('getline(1)')) + end) end) -- cgit From 200e62efebe8e87fc612218b675b74def57519f1 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 28 Jun 2015 08:06:16 +0300 Subject: shada: Add support for dumping/restoring bufs changes and win jumps --- scripts/shadacat.py | 1 + src/nvim/buffer.c | 3 + src/nvim/buffer_defs.h | 2 +- src/nvim/lib/khash.h | 13 ++ src/nvim/mark.c | 35 ++-- src/nvim/mark.h | 31 +++- src/nvim/misc1.c | 6 +- src/nvim/shada.c | 303 ++++++++++++++++++++++++++++------- test/functional/shada/marks_spec.lua | 80 ++++++++- 9 files changed, 388 insertions(+), 86 deletions(-) diff --git a/scripts/shadacat.py b/scripts/shadacat.py index 8f5ed276f8..c710d0ca95 100755 --- a/scripts/shadacat.py +++ b/scripts/shadacat.py @@ -23,6 +23,7 @@ class EntryTypes(Enum): Jump = 8 BufferList = 9 LocalMark = 10 + Change = 11 def strtrans_errors(e): diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 5ebeec1b70..5f7120e47c 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -562,6 +562,9 @@ static void free_buffer(buf_T *buf) 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. diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 6fbdea2c62..f217cba2af 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -520,7 +520,7 @@ struct file_buffer { /* * 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() */ diff --git a/src/nvim/lib/khash.h b/src/nvim/lib/khash.h index 96e7ea6df0..fd4f910dac 100644 --- a/src/nvim/lib/khash.h +++ b/src/nvim/lib/khash.h @@ -577,6 +577,19 @@ 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 diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 7fdf4f2bcb..b7746f192f 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -59,14 +59,6 @@ /// Global marks (marks with file number or name) static xfmark_T namedfm[NGLOBALMARKS]; -#define SET_XFMARK(xfmarkp_, mark_, fnum_, fname_) \ - do { \ - xfmark_T *const xfmarkp__ = xfmarkp_; \ - xfmarkp__->fname = fname_; \ - RESET_FMARK(&(xfmarkp__->fmark), mark_, fnum_); \ - } while (0) - - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "mark.c.generated.h" #endif @@ -85,11 +77,12 @@ void free_fmark(fmark_T fm) if (fm.additional_data != NULL) { api_free_dictionary(*fm.additional_data); free(fm.additional_data); + fm.additional_data = NULL; } } /// Free xfmark_T item -static inline void free_xfmark(xfmark_T fm) +void free_xfmark(xfmark_T fm) { xfree(fm.fname); fm.fname = NULL; @@ -157,8 +150,7 @@ int setmark_pos(int c, pos_T *pos, int fnum) if (isupper(c)) { assert(c >= 'A' && c <= 'Z'); i = c - 'A'; - free_xfmark(namedfm[i]); - SET_XFMARK(namedfm + i, *pos, fnum, NULL); + RESET_XFMARK(namedfm + i, *pos, fnum, NULL); return OK; } return FAIL; @@ -170,7 +162,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 */ @@ -184,8 +175,8 @@ void setpcmark(void) if (++curwin->w_jumplistlen > JUMPLISTSIZE) { curwin->w_jumplistlen = JUMPLISTSIZE; free_xfmark(curwin->w_jumplist[0]); - for (i = 1; i < JUMPLISTSIZE; ++i) - curwin->w_jumplist[i - 1] = curwin->w_jumplist[i]; + 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]; @@ -284,7 +275,7 @@ pos_T *movechangelist(int count) } else n += count; curwin->w_changelistidx = n; - return curbuf->b_changelist + n; + return &(curbuf->b_changelist[n].mark); } /* @@ -829,7 +820,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; @@ -837,10 +828,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(); @@ -926,7 +917,7 @@ void mark_adjust(linenr_T line1, linenr_T line2, long amount, long amount_after) /* 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)); @@ -1073,7 +1064,7 @@ void mark_col_adjust(linenr_T lnum, colnr_T mincol, long lnum_amount, long col_a /* 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)); @@ -1119,7 +1110,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; diff --git a/src/nvim/mark.h b/src/nvim/mark.h index a8aaf56648..3f63e274bb 100644 --- a/src/nvim/mark.h +++ b/src/nvim/mark.h @@ -3,24 +3,49 @@ #include "nvim/buffer_defs.h" #include "nvim/mark_defs.h" +#include "nvim/memory.h" #include "nvim/pos.h" #include "nvim/os/time.h" -/// Free and set fmark using given value -#define RESET_FMARK(fmarkp_, mark_, fnum_) \ +/// Set fmark using given value +#define SET_FMARK(fmarkp_, mark_, fnum_) \ do { \ fmark_T *const fmarkp__ = fmarkp_; \ - free_fmark(*fmarkp__); \ fmarkp__->mark = mark_; \ fmarkp__->fnum = fnum_; \ fmarkp__->timestamp = os_time(); \ fmarkp__->additional_data = NULL; \ } while (0) +/// 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) + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "mark.h.generated.h" #endif diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index a4ebdca091..1f1b5c2aa9 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -2053,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 { @@ -2073,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. */ @@ -2094,7 +2094,7 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra } } curbuf->b_changelist[curbuf->b_changelistlen - 1] = - curbuf->b_last_change.mark; + curbuf->b_last_change; /* The current window is always after the last change, so that "g," * takes you back to it. */ curwin->w_changelistidx = curbuf->b_changelistlen; diff --git a/src/nvim/shada.c b/src/nvim/shada.c index de2e89736f..9ccd1f2e83 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -43,8 +43,20 @@ #include "nvim/version.h" #include "nvim/path.h" #include "nvim/lib/ringbuf.h" +#include "nvim/lib/khash.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 *) -#define buflist_nr2name(...) ((char *) buflist_nr2name(__VA_ARGS__)) #define copy_option_part(src, dest, ...) \ ((char *) copy_option_part((char_u **) src, (char_u *) dest, __VA_ARGS__)) #define find_viminfo_parameter(...) \ @@ -112,7 +124,8 @@ typedef enum { kSDItemJump = 8, ///< Item from jump list. kSDItemBufferList = 9, ///< Buffer list. kSDItemLocalMark = 10, ///< Buffer-local mark. -#define SHADA_LAST_ENTRY ((uint64_t) kSDItemLocalMark) + kSDItemChange = 11, ///< Item from buffer change list. +#define SHADA_LAST_ENTRY ((uint64_t) kSDItemChange) } ShadaEntryType; /// Structure defining a single ShaDa file entry @@ -329,6 +342,40 @@ static void insert_history_entry(HistoryMergerState *const hms_p, hm_rb_insert(rb, (size_t) (hm_rb_find_idx(rb, insert_after) + 1), 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); +} + /// Read data from ShaDa file /// /// @param[in] fp File to read from. @@ -346,9 +393,8 @@ static void shada_read(FILE *const fp, const int flags) } } ShadaEntry cur_entry; - buf_T *local_mark_prev_buf = NULL; - char *local_mark_prev_fname = NULL; - size_t local_mark_prev_fname_len = 0; + khash_t(bufset) *cl_bufs = kh_init(bufset); + khash_t(fnamebufs) *fname_bufs = kh_init(fnamebufs); while (shada_read_next_item(fp, &cur_entry, flags) == NOTDONE) { switch (cur_entry.type) { case kSDItemMissing: { @@ -455,71 +501,182 @@ static void shada_read(FILE *const fp, const int flags) shada_free_shada_entry(&cur_entry); break; } + case kSDItemJump: case kSDItemGlobalMark: { - if (!(flags & kShaDaWantMarks) || get_viminfo_parameter('f') == 0) { + if (!(flags & kShaDaWantMarks) + || (cur_entry.type == kSDItemGlobalMark + && get_viminfo_parameter('f') == 0)) { shada_free_shada_entry(&cur_entry); break; } - mark_set_global(cur_entry.data.filemark.name, (xfmark_T) { - .fname = (char_u *) cur_entry.data.filemark.fname, + 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 = 0, + .fnum = (buf == NULL ? 0 : buf->b_fnum), .timestamp = cur_entry.timestamp, .additional_data = cur_entry.data.filemark.additional_data, }, - }, !(flags & kShaDaForceit)); + }; + if (cur_entry.type == kSDItemGlobalMark) { + mark_set_global(cur_entry.data.filemark.name, fm, + !(flags & kShaDaForceit)); + } else { + if (flags & kShaDaForceit) { + if (curwin->w_jumplistlen == JUMPLISTSIZE) { + // Jump list items are ignored in this case. + free_xfmark(fm); + } else { + memmove(&curwin->w_jumplist[1], &curwin->w_jumplist[0], + sizeof(curwin->w_jumplist[0]) + * (size_t) curwin->w_jumplistlen); + curwin->w_jumplistidx++; + curwin->w_jumplistlen++; + curwin->w_jumplist[0] = fm; + } + } else { + const int jl_len = curwin->w_jumplistlen; + int i; + for (i = 0; i < jl_len; i++) { + const xfmark_T jl_fm = curwin->w_jumplist[i]; + if (jl_fm.fmark.timestamp >= cur_entry.timestamp) { + if (marks_equal(fm.fmark.mark, jl_fm.fmark.mark) + && (buf == NULL + ? (jl_fm.fname != NULL + && STRCMP(fm.fname, jl_fm.fname) == 0) + : fm.fmark.fnum == jl_fm.fmark.fnum)) { + i = -1; + } + break; + } + } + if (i != -1) { + if (i < jl_len) { + if (jl_len == JUMPLISTSIZE) { + free_xfmark(curwin->w_jumplist[0]); + memmove(&curwin->w_jumplist[0], &curwin->w_jumplist[1], + sizeof(curwin->w_jumplist[0]) * (size_t) i); + } else { + memmove(&curwin->w_jumplist[i + 1], &curwin->w_jumplist[i], + sizeof(curwin->w_jumplist[0]) + * (size_t) (jl_len - i)); + } + } else if (i == jl_len) { + if (jl_len == JUMPLISTSIZE) { + i = -1; + } else if (jl_len > 0) { + memmove(&curwin->w_jumplist[1], &curwin->w_jumplist[0], + sizeof(curwin->w_jumplist[0]) + * (size_t) jl_len); + } + } + } + if (i != -1) { + curwin->w_jumplist[i] = fm; + if (jl_len < JUMPLISTSIZE) { + curwin->w_jumplistlen++; + } + if (curwin->w_jumplistidx > i + && curwin->w_jumplistidx + 1 < curwin->w_jumplistlen) { + curwin->w_jumplistidx++; + } + } else { + shada_free_shada_entry(&cur_entry); + } + } + } // Do not free shada entry: its allocated memory was saved above. break; } - case kSDItemJump: { - // FIXME - shada_free_shada_entry(&cur_entry); - break; - } case kSDItemBufferList: { // FIXME shada_free_shada_entry(&cur_entry); break; } + case kSDItemChange: case kSDItemLocalMark: { if (!(flags & kShaDaWantMarks)) { shada_free_shada_entry(&cur_entry); break; } - buf_T *buf = NULL; - if (local_mark_prev_fname != NULL - && strncmp(local_mark_prev_fname, - cur_entry.data.filemark.fname, - local_mark_prev_fname_len) == 0) { - buf = local_mark_prev_buf; + 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) { + mark_set_local(cur_entry.data.filemark.name, buf, fm, + !(flags & kShaDaForceit)); } else { - FOR_ALL_BUFFERS(bp) { - if (bp->b_ffname != NULL && bp != local_mark_prev_buf) { - if (fnamecmp(cur_entry.data.filemark.fname, bp->b_ffname) == 0) { - buf = bp; + int kh_ret; + (void) kh_put(bufset, cl_bufs, (uintptr_t) buf, &kh_ret); + if (flags & kShaDaForceit) { + if (buf->b_changelistlen == JUMPLISTSIZE) { + free_fmark(buf->b_changelist[0]); + memmove(buf->b_changelist, buf->b_changelist + 1, + sizeof(buf->b_changelist[0]) * (JUMPLISTSIZE - 1)); + } else { + buf->b_changelistlen++; + } + buf->b_changelist[buf->b_changelistlen - 1] = fm; + } else { + const int cl_len = buf->b_changelistlen; + int i; + for (i = cl_len; i > 0; i--) { + const fmark_T cl_fm = buf->b_changelist[i - 1]; + if (cl_fm.timestamp <= cur_entry.timestamp) { + if (marks_equal(fm.mark, cl_fm.mark)) { + i = -1; + } break; } } + if (i > 0) { + if (cl_len == JUMPLISTSIZE) { + free_fmark(buf->b_changelist[0]); + memmove(&buf->b_changelist[0], &buf->b_changelist[1], + sizeof(buf->b_changelist[0]) * (size_t) i); + } else { + memmove(&buf->b_changelist[i + 1], &buf->b_changelist[i], + sizeof(buf->b_changelist[0]) + * (size_t) (cl_len - i)); + } + } else if (i == 0) { + if (cl_len == JUMPLISTSIZE) { + i = -1; + } else if (cl_len > 0) { + memmove(&buf->b_changelist[1], &buf->b_changelist[0], + sizeof(buf->b_changelist[0]) + * (size_t) cl_len); + } + } + if (i != -1) { + buf->b_changelist[i] = fm; + if (cl_len < JUMPLISTSIZE) { + buf->b_changelistlen++; + } + } else { + shada_free_shada_entry(&cur_entry); + cur_entry.data.filemark.fname = NULL; + } } - xfree(local_mark_prev_fname); - local_mark_prev_buf = buf; - local_mark_prev_fname_len = strlen(cur_entry.data.filemark.fname); - local_mark_prev_fname = xmemdupz(cur_entry.data.filemark.fname, - local_mark_prev_fname_len); } - if (buf == NULL) { - break; - } - mark_set_local( - cur_entry.data.filemark.name, buf, - (fmark_T) { - .mark = cur_entry.data.filemark.mark, - .fnum = 0, - .timestamp = cur_entry.timestamp, - .additional_data = cur_entry.data.filemark.additional_data, - }, !(flags & kShaDaForceit)); - free(cur_entry.data.filemark.fname); + // 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; } } @@ -561,10 +718,18 @@ static void shada_read(FILE *const fp, const int flags) hm_rb_dealloc(&(hms[i].hmrb)); } } - xfree(local_mark_prev_fname); - FOR_ALL_BUFFERS(buf) { - fmarks_check_names(buf); + FOR_ALL_TAB_WINDOWS(tp, wp) { + (void) tp; + if (kh_get(bufset, cl_bufs, (uintptr_t) wp->w_buffer) != kh_end(cl_bufs)) { + wp->w_changelistidx = wp->w_buffer->b_changelistlen; + } } + kh_destroy(bufset, cl_bufs); + const char *key; + kh_foreach_key(fname_bufs, key, { + xfree((void *) key); + }) + kh_destroy(fnamebufs, fname_bufs); } /// Get the ShaDa file name to use @@ -745,6 +910,7 @@ static void shada_pack_entry(msgpack_packer *const packer, } break; } + case kSDItemChange: case kSDItemGlobalMark: case kSDItemLocalMark: case kSDItemJump: { @@ -756,6 +922,7 @@ static void shada_pack_entry(msgpack_packer *const packer, + (entry.data.filemark.mark.col != 0) // Mark name: defaults to '"' + (entry.type != kSDItemJump + && entry.type != kSDItemChange && entry.data.filemark.name != '"') // Additional entries, if any: + (entry.data.filemark.additional_data == NULL @@ -774,7 +941,8 @@ static void shada_pack_entry(msgpack_packer *const packer, PACK_STATIC_STR("col"); msgpack_pack_long(spacker, entry.data.filemark.mark.col); } - if (entry.data.filemark.name != '"' && entry.type != kSDItemJump) { + if (entry.data.filemark.name != '"' && entry.type != kSDItemJump + && entry.type != kSDItemChange) { PACK_STATIC_STR("name"); msgpack_pack_uint8(spacker, (uint8_t) entry.data.filemark.name); } @@ -933,12 +1101,21 @@ static void shada_write(FILE *const newfp, FILE *const oldfp) const void *jump_iter = NULL; do { xfmark_T fm; + cleanup_jumplist(); jump_iter = mark_jumplist_iter(jump_iter, curwin, &fm); - char *fname = (fm.fmark.fnum == 0 - ? (fm.fname == NULL - ? NULL - : xstrdup((char *) fm.fname)) - : buflist_nr2name(fm.fmark.fnum, true, false)); + const buf_T *const buf = (fm.fmark.fnum == 0 + ? NULL + : buflist_findnr(fm.fmark.fnum)); + if (buf != NULL + ? shada_removable(buf->b_ffname) + : fm.fmark.fnum != 0) { + continue; + } + const char *const fname = (char *) (fm.fmark.fnum == 0 + ? (fm.fname == NULL + ? NULL + : fm.fname) + : buf->b_ffname); shada_pack_entry(packer, (ShadaEntry) { .type = kSDItemJump, .timestamp = fm.fmark.timestamp, @@ -946,12 +1123,11 @@ static void shada_write(FILE *const newfp, FILE *const oldfp) .filemark = { .name = NUL, .mark = fm.fmark.mark, - .fname = fname, + .fname = (char *) fname, .additional_data = fm.fmark.additional_data, } } }, max_kbyte); - xfree(fname); } while (jump_iter != NULL); // FIXME No merging currently @@ -1050,7 +1226,7 @@ static void shada_write(FILE *const newfp, FILE *const oldfp) xfree(global_marks); } - // 8. Buffer marks + // 8. Buffer marks and buffer change list FOR_ALL_BUFFERS(buf) { if (buf->b_ffname == NULL) { continue; @@ -1061,6 +1237,21 @@ static void shada_write(FILE *const newfp, FILE *const oldfp) shada_pack_entry(packer, *mark, max_kbyte); } xfree(buffer_marks); + + for (int i = 0; i < buf->b_changelistlen; i++) { + const fmark_T fm = buf->b_changelist[i]; + shada_pack_entry(packer, (ShadaEntry) { + .type = kSDItemChange, + .timestamp = fm.timestamp, + .data = { + .filemark = { + .mark = fm.mark, + .fname = (char *) buf->b_ffname, + .additional_data = fm.additional_data, + } + } + }, max_kbyte); + } } // FIXME: Copy previous marks, up to num_marked_files // size_t num_marked_files = get_viminfo_parameter('\''); @@ -1180,6 +1371,7 @@ static void shada_free_shada_entry(ShadaEntry *const entry) api_free_dictionary(entry->data.header); break; } + case kSDItemChange: case kSDItemJump: case kSDItemGlobalMark: case kSDItemLocalMark: { @@ -1433,6 +1625,7 @@ shada_read_next_item_start: break; } case kSDItemBufferList: + case kSDItemChange: case kSDItemJump: case kSDItemVariable: case kSDItemRegister: @@ -1656,6 +1849,7 @@ shada_read_next_item_start: ga_clear(&ad_ga); break; } + case kSDItemChange: case kSDItemJump: case kSDItemGlobalMark: case kSDItemLocalMark: { @@ -1680,6 +1874,7 @@ shada_read_next_item_start: "mark", "name", " which is not an unsigned integer", entry->data.filemark.name, (type_u64 != kSDItemJump + && type_u64 != kSDItemChange && unpacked.data.via.map.ptr[i].val.type == MSGPACK_OBJECT_POSITIVE_INTEGER), u64, TOCHAR) diff --git a/test/functional/shada/marks_spec.lua b/test/functional/shada/marks_spec.lua index 04a49a6d44..b853513317 100644 --- a/test/functional/shada/marks_spec.lua +++ b/test/functional/shada/marks_spec.lua @@ -1,8 +1,8 @@ -- ShaDa marks saving/reading support local helpers = require('test.functional.helpers') -local nvim, nvim_window, nvim_curwin, nvim_command, nvim_eval, eq = - helpers.nvim, helpers.window, helpers.curwin, helpers.command, helpers.eval, - helpers.eq +local nvim, nvim_window, nvim_curwin, nvim_command, nvim_feed, nvim_eval, eq = + helpers.nvim, helpers.window, helpers.curwin, helpers.command, helpers.feed, + helpers.eval, helpers.eq local shada_helpers = require('test.functional.shada.helpers') local reset, set_additional_cmd, clear = @@ -15,16 +15,22 @@ end describe('ShaDa support code', function() testfilename = 'Xtestfile-functional-shada-marks' + testfilename_2 = 'Xtestfile-functional-shada-marks-2' before_each(function() reset() local fd = io.open(testfilename, 'w') fd:write('test\n') fd:write('test2\n') fd:close() + local fd = io.open(testfilename_2, 'w') + fd:write('test3\n') + fd:write('test4\n') + fd:close() end) after_each(function() clear() os.remove(testfilename) + os.remove(testfilename_2) end) it('is able to dump and read back global mark', function() @@ -74,4 +80,72 @@ describe('ShaDa support code', function() nvim_command('normal! `b') eq(2, nvim_current_line()) end) + + it('is able to dump and restore jump list', function() + nvim_command('edit ' .. testfilename_2) + nvim_feed('G') + nvim_feed('gg') + nvim_command('edit ' .. testfilename) + nvim_feed('G') + nvim_feed('gg') + -- nvim_command('redir! >/tmp/jumps.last | jumps | redir END') + -- nvim_command('wviminfo /tmp/foo') + nvim_command('qall') + reset() + nvim_command('redraw') + -- nvim_command('redir! >/tmp/jumps.init | jumps | redir END') + nvim_command('edit ' .. testfilename) + -- nvim_command('redir! >/tmp/jumps | jumps | redir END') + eq(testfilename, nvim_eval('bufname("%")')) + eq(1, nvim_current_line()) + nvim_command('execute "normal! \\"') + eq(testfilename, nvim_eval('bufname("%")')) + eq(1, nvim_current_line()) + nvim_command('execute "normal! \\"') + eq(testfilename, nvim_eval('bufname("%")')) + eq(2, nvim_current_line()) + nvim_command('execute "normal! \\"') + eq(testfilename_2, nvim_eval('bufname("%")')) + eq(1, nvim_current_line()) + nvim_command('execute "normal! \\"') + eq(testfilename_2, nvim_eval('bufname("%")')) + eq(2, nvim_current_line()) + end) + + it('is able to dump and restore jump list with different times (slow!)', + function() + nvim_command('edit ' .. testfilename_2) + nvim_command('sleep 2') + nvim_feed('G') + nvim_command('sleep 2') + nvim_feed('gg') + nvim_command('sleep 2') + nvim_command('edit ' .. testfilename) + nvim_command('sleep 2') + nvim_feed('G') + nvim_command('sleep 2') + nvim_feed('gg') + -- nvim_command('redir! >/tmp/jumps.last | jumps | redir END') + -- nvim_command('wviminfo /tmp/foo') + nvim_command('qall') + reset() + nvim_command('redraw') + -- nvim_command('redir! >/tmp/jumps.init | jumps | redir END') + nvim_command('edit ' .. testfilename) + -- nvim_command('redir! >/tmp/jumps | jumps | redir END') + eq(testfilename, nvim_eval('bufname("%")')) + eq(1, nvim_current_line()) + nvim_command('execute "normal! \\"') + eq(testfilename, nvim_eval('bufname("%")')) + eq(1, nvim_current_line()) + nvim_command('execute "normal! \\"') + eq(testfilename, nvim_eval('bufname("%")')) + eq(2, nvim_current_line()) + nvim_command('execute "normal! \\"') + eq(testfilename_2, nvim_eval('bufname("%")')) + eq(1, nvim_current_line()) + nvim_command('execute "normal! \\"') + eq(testfilename_2, nvim_eval('bufname("%")')) + eq(2, nvim_current_line()) + end) end) -- cgit From 75c9a7e65bf71444ca1a43a9d88909b41c2909ca Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 4 Jul 2015 17:39:31 +0300 Subject: shada: Do not record removable buffers and marks --- src/nvim/shada.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 9ccd1f2e83..4c44624b76 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1074,13 +1074,13 @@ static void shada_write(FILE *const newfp, FILE *const oldfp) size_t buf_count = 0; FOR_ALL_BUFFERS(buf) { - if (buf->b_ffname != NULL) { + if (buf->b_ffname != NULL && !shada_removable(buf->b_ffname)) { buf_count++; } } msgpack_pack_array(packer, buf_count); FOR_ALL_BUFFERS(buf) { - if (buf->b_ffname == NULL) { + if (buf->b_ffname == NULL || shada_removable(buf->b_ffname)) { continue; } msgpack_pack_map(packer, 3); @@ -1228,7 +1228,7 @@ static void shada_write(FILE *const newfp, FILE *const oldfp) // 8. Buffer marks and buffer change list FOR_ALL_BUFFERS(buf) { - if (buf->b_ffname == NULL) { + if (buf->b_ffname == NULL || shada_removable(buf->b_ffname)) { continue; } ShadaEntry *const buffer_marks = list_buffer_marks(buf); -- cgit From 4eeafe7f72de2e63c68977ff929a4a0d49c711f9 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 4 Jul 2015 17:55:58 +0300 Subject: shada: Use pre-populated set of buffers on removable media Should be faster then constantly running shada_removable for each buffer many times. --- src/nvim/shada.c | 67 ++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 48 insertions(+), 19 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 4c44624b76..aaa9f1d9c1 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -200,6 +200,27 @@ typedef struct { RINGBUF_INIT(HM, hm, ShadaEntry, shada_free_shada_entry) +/// 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 buffer is on removable media +/// +/// Uses pre-populated set with buffers on removable media named removable_bufs. +/// +/// @param[in] buf Buffer to check. +/// +/// @return true or false. +#define SHADA_REMOVABLE(buf) in_bufset(removable_bufs, buf) + /// Msgpack callback for writing to FILE* static int msgpack_fbuffer_write(void *data, const char *buf, size_t len) { @@ -720,7 +741,7 @@ static void shada_read(FILE *const fp, const int flags) } FOR_ALL_TAB_WINDOWS(tp, wp) { (void) tp; - if (kh_get(bufset, cl_bufs, (uintptr_t) wp->w_buffer) != kh_end(cl_bufs)) { + if (in_bufset(cl_bufs, wp->w_buffer)) { wp->w_changelistidx = wp->w_buffer->b_changelistlen; } } @@ -1031,6 +1052,7 @@ static void shada_pack_entry(msgpack_packer *const packer, static void shada_write(FILE *const newfp, FILE *const oldfp) FUNC_ATTR_NONNULL_ARG(1) { + khash_t(bufset) *const removable_bufs = kh_init(bufset); int max_kbyte_i = get_viminfo_parameter('s'); if (max_kbyte_i < 0) { max_kbyte_i = 10; @@ -1042,6 +1064,13 @@ static void shada_write(FILE *const newfp, FILE *const oldfp) msgpack_packer *packer = msgpack_packer_new(newfp, &msgpack_fbuffer_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); + } + } + // First write values that do not require merging // 1. Header shada_pack_entry(packer, (ShadaEntry) { @@ -1074,13 +1103,13 @@ static void shada_write(FILE *const newfp, FILE *const oldfp) size_t buf_count = 0; FOR_ALL_BUFFERS(buf) { - if (buf->b_ffname != NULL && !shada_removable(buf->b_ffname)) { + if (buf->b_ffname != NULL && !SHADA_REMOVABLE(buf)) { buf_count++; } } msgpack_pack_array(packer, buf_count); FOR_ALL_BUFFERS(buf) { - if (buf->b_ffname == NULL || shada_removable(buf->b_ffname)) { + if (buf->b_ffname == NULL || SHADA_REMOVABLE(buf)) { continue; } msgpack_pack_map(packer, 3); @@ -1107,7 +1136,7 @@ static void shada_write(FILE *const newfp, FILE *const oldfp) ? NULL : buflist_findnr(fm.fmark.fnum)); if (buf != NULL - ? shada_removable(buf->b_ffname) + ? SHADA_REMOVABLE(buf) : fm.fmark.fnum != 0) { continue; } @@ -1219,8 +1248,9 @@ static void shada_write(FILE *const newfp, FILE *const oldfp) // 7. Global marks if (get_viminfo_parameter('f') != 0) { - ShadaEntry *const global_marks = list_global_marks(); - for (ShadaEntry *mark = global_marks; mark->type != kSDItemMissing; mark++) { + ShadaEntry *const global_marks = list_global_marks(removable_bufs); + for (ShadaEntry *mark = global_marks; mark->type != kSDItemMissing; + mark++) { shada_pack_entry(packer, *mark, max_kbyte); } xfree(global_marks); @@ -1228,7 +1258,7 @@ static void shada_write(FILE *const newfp, FILE *const oldfp) // 8. Buffer marks and buffer change list FOR_ALL_BUFFERS(buf) { - if (buf->b_ffname == NULL || shada_removable(buf->b_ffname)) { + if (buf->b_ffname == NULL || SHADA_REMOVABLE(buf)) { continue; } ShadaEntry *const buffer_marks = list_buffer_marks(buf); @@ -1300,6 +1330,7 @@ static void shada_write(FILE *const newfp, FILE *const oldfp) } while (var_iter != NULL); } + kh_destroy(bufset, removable_bufs); msgpack_packer_free(packer); } @@ -2280,11 +2311,14 @@ shada_read_next_item_end: /// @warning Listed marks must be used before any buffer- or mark-editing /// function is run. /// +/// @param[in] removable_bufs Set of buffers on removable media. +/// /// @return Array of ShadaEntry values, last one has type kSDItemMissing. /// /// @warning Resulting ShadaEntry values must not be freed. Returned /// array must be freed with `xfree()`. -static ShadaEntry *list_global_marks(void) +static ShadaEntry *list_global_marks( + const khash_t(bufset) *const removable_bufs) { const void *iter = NULL; const size_t nummarks = mark_global_amount(); @@ -2299,24 +2333,19 @@ static ShadaEntry *list_global_marks(void) cur->data.filemark.additional_data = cur_fm.fmark.additional_data; cur->timestamp = cur_fm.fmark.timestamp; if (cur_fm.fmark.fnum == 0) { - cur->data.filemark.fname = (char *) (cur_fm.fname == NULL - ? NULL - : cur_fm.fname); + if (cur_fm.fname == NULL) { + continue; + } + cur->data.filemark.fname = (char *) cur_fm.fname; } else { const buf_T *const buf = buflist_findnr(cur_fm.fmark.fnum); - if (buf == NULL) { + if (buf == NULL || buf->b_ffname == NULL || SHADA_REMOVABLE(buf)) { continue; } else { cur->data.filemark.fname = (char *) buf->b_ffname; } } - if (cur->data.filemark.fname != NULL) { - if (shada_removable(cur->data.filemark.fname)) { - continue; - } else { - cur++; - } - } + cur++; } while(iter != NULL); } cur->type = kSDItemMissing; -- cgit From 8c93877e1c3205f6b42b3e4c94d9be6262557dfb Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 4 Jul 2015 22:36:06 +0300 Subject: shada: Add support for dumping and restoring buffer list --- src/nvim/buffer.c | 23 +++- src/nvim/buffer_defs.h | 4 +- src/nvim/shada.c | 208 +++++++++++++++++++++++++++++---- test/functional/shada/buffers_spec.lua | 65 +++++++++++ 4 files changed, 268 insertions(+), 32 deletions(-) create mode 100644 test/functional/shada/buffers_spec.lua diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 5f7120e47c..53612977ee 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -30,6 +30,7 @@ #include #include "nvim/api/private/handle.h" +#include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/vim.h" #include "nvim/buffer.h" @@ -556,6 +557,10 @@ static void free_buffer(buf_T *buf) free_buffer_stuff(buf, TRUE); unref_var_dict(buf->b_vars); aubuflocal_remove(buf); + if (buf->additional_data != NULL) { + api_free_dictionary(*buf->additional_data); + xfree(buf->additional_data); + } free_fmark(buf->b_last_cursor); free_fmark(buf->b_last_insert); free_fmark(buf->b_last_change); @@ -1988,12 +1993,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; diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index f217cba2af..2be5386de9 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -26,7 +26,7 @@ typedef struct file_buffer buf_T; // Forward declaration #include "nvim/eval_defs.h" // for proftime_T #include "nvim/profile.h" -// for String +// for String and Dictionary #include "nvim/api/private/defs.h" #define MODIFIABLE(buf) (!buf->terminal && buf->b_p_ma) @@ -748,6 +748,8 @@ struct file_buffer { signlist_T *b_signlist; /* list of signs to draw */ Terminal *terminal; // Terminal instance associated with the buffer + + Dictionary *additional_data; // Additional data from shada file if any. }; /* diff --git a/src/nvim/shada.c b/src/nvim/shada.c index aaa9f1d9c1..2d555faded 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -35,6 +35,7 @@ #include "nvim/api/private/helpers.h" #include "nvim/globals.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/misc2.h" #include "nvim/ex_getln.h" #include "nvim/search.h" @@ -66,6 +67,10 @@ KHASH_MAP_INIT_STR(fnamebufs, buf_T *) #define emsgn(a, ...) emsgn((char_u *) a, __VA_ARGS__) #define home_replace_save(a, b) \ ((char *)home_replace_save(a, (char_u *)b)) +#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__)) // From http://www.boost.org/doc/libs/1_43_0/boost/detail/endian.hpp + some // additional checks done after examining `{compiler} -dM -E - < /dev/null` @@ -180,7 +185,14 @@ typedef struct { char *sub; Array *additional_elements; } sub_string; - Array buffer_list; + struct buffer_list { + size_t size; + struct buffer_list_buffer { + pos_T pos; + char *fname; + Dictionary *additional_data; + } *buffers; + } buffer_list; } data; } ShadaEntry; @@ -617,7 +629,24 @@ static void shada_read(FILE *const fp, const int flags) break; } case kSDItemBufferList: { - // FIXME + if (!(flags & kShaDaWantInfo) || find_viminfo_parameter('%') == NULL + || ARGCOUNT != 0) { + shada_free_shada_entry(&cur_entry); + break; + } + 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); + } + } shada_free_shada_entry(&cur_entry); break; } @@ -1019,7 +1048,46 @@ static void shada_pack_entry(msgpack_packer *const packer, break; } case kSDItemBufferList: { - msgpack_rpc_from_array(entry.data.buffer_list, spacker); + 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 + // Line number: defaults to 1 + + (entry.data.buffer_list.buffers[i].pos.lnum != 1) + // Column number: defaults to 0 + + (entry.data.buffer_list.buffers[i].pos.col != 0) + // Additional entries, if any: + + (entry.data.buffer_list.buffers[i].additional_data == NULL + ? 0 + : entry.data.buffer_list.buffers[i].additional_data->size) + ); + msgpack_pack_map(spacker, map_size); + PACK_STATIC_STR("file"); + msgpack_rpc_from_string( + cstr_as_string(entry.data.buffer_list.buffers[i].fname), spacker); + if (entry.data.buffer_list.buffers[i].pos.lnum != 1) { + PACK_STATIC_STR("line"); + 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("col"); + msgpack_pack_uint64( + spacker, (uint64_t) entry.data.buffer_list.buffers[i].pos.col); + } + if (entry.data.buffer_list.buffers[i].additional_data != NULL) { + for (size_t j = 0; + j < entry.data.buffer_list.buffers[i].additional_data->size; + j++) { + msgpack_rpc_from_string( + entry.data.buffer_list.buffers[i].additional_data->items[j].key, + spacker); + msgpack_rpc_from_object( + entry.data.buffer_list.buffers[i].additional_data->items[j].value, + spacker); + } + } + } break; } case kSDItemHeader: { @@ -1095,35 +1163,38 @@ static void shada_write(FILE *const newfp, FILE *const oldfp) // 2. Buffer list if (find_viminfo_parameter('%') != NULL) { - msgpack_pack_uint64(packer, (uint64_t) kSDItemBufferList); - msgpack_pack_uint64(packer, (uint64_t) os_time()); - msgpack_sbuffer sbuf; - msgpack_sbuffer_init(&sbuf); - msgpack_packer *spacker = msgpack_packer_new(&sbuf, &msgpack_sbuffer_write); - size_t buf_count = 0; FOR_ALL_BUFFERS(buf) { if (buf->b_ffname != NULL && !SHADA_REMOVABLE(buf)) { buf_count++; } } - msgpack_pack_array(packer, 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 || SHADA_REMOVABLE(buf)) { continue; } - msgpack_pack_map(packer, 3); - PACK_STATIC_STR("fname"); - msgpack_rpc_from_string(cstr_as_string((char *) buf->b_ffname), packer); - PACK_STATIC_STR("lnum"); - msgpack_pack_uint64(packer, (uint64_t) buf->b_last_cursor.mark.lnum); - PACK_STATIC_STR("col"); - msgpack_pack_uint64(packer, (uint64_t) buf->b_last_cursor.mark.col); + 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++; } - msgpack_pack_uint64(packer, (uint64_t) sbuf.size); - packer->callback(packer->data, sbuf.data, (unsigned) sbuf.size); - msgpack_packer_free(spacker); - msgpack_sbuffer_destroy(&sbuf); + shada_pack_entry(packer, buflist_entry, 0); + xfree(buflist_entry.data.buffer_list.buffers); } // 3. Jump list @@ -1460,7 +1531,15 @@ static void shada_free_shada_entry(ShadaEntry *const entry) break; } case kSDItemBufferList: { - api_free_array(entry->data.buffer_list); + for (size_t i = 0; i < entry->data.buffer_list.size; i++) { + xfree(entry->data.buffer_list.buffers[i].fname); + if (entry->data.buffer_list.buffers[i].additional_data != NULL) { + api_free_dictionary( + *entry->data.buffer_list.buffers[i].additional_data); + xfree(entry->data.buffer_list.buffers[i].additional_data); + } + } + xfree(entry->data.buffer_list.buffers); break; } } @@ -1609,7 +1688,7 @@ shada_read_next_item_start: // 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 type_u64 = (uint64_t) kSDItemMissing; uint64_t timestamp_u64; uint64_t length_u64; @@ -2258,12 +2337,88 @@ shada_read_next_item_start: break; } case kSDItemBufferList: { - if (!msgpack_rpc_to_array(&(unpacked.data), &(entry->data.buffer_list))) { + if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) { emsgn("Error while reading ShaDa file: " - "buffer list entry at position %" PRId64 " is not an array", + "buffer list entry at position %" PRId64 " " + "is not an array", (int64_t) initial_fpos); goto shada_read_next_item_error; } + entry->data.buffer_list = (struct buffer_list) { + .size = 0, + .buffers = NULL, + }; + 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) { + emsgn("Error while reading ShaDa file: " + "buffer list at position %" PRId64 " " + "contains entry that is not a dictionary", + (int64_t) initial_fpos); + goto shada_read_next_item_error; + } + entry->data.buffer_list.buffers[i].pos.lnum = 1; + 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", "line", + entry->data.buffer_list.buffers[j].pos.lnum) + else INTEGER_KEY("buffer list entry", "col", + entry->data.buffer_list.buffers[j].pos.col) + else STRING_KEY("buffer list entry", "file", + entry->data.buffer_list.buffers[j].fname) + else ADDITIONAL_KEY + } + } + } + if (entry->data.buffer_list.buffers[i].fname == NULL) { + emsgn("Error while reading ShaDa file: " + "buffer list at position %" PRId64 " " + "contains entry that does not have a file name", + (int64_t) initial_fpos); + ga_clear(&ad_ga); + goto shada_read_next_item_error; + } + if (ad_ga.ga_len) { + msgpack_object obj = { + .type = MSGPACK_OBJECT_MAP, + .via = { + .map = { + .size = (uint32_t) ad_ga.ga_len, + .ptr = ad_ga.ga_data, + } + } + }; + entry->data.buffer_list.buffers[i].additional_data = + xmalloc(sizeof(Dictionary)); + if (!msgpack_rpc_to_dictionary( + &obj, entry->data.buffer_list.buffers[i].additional_data)) { + emsgu("Error while reading ShaDa file: " + "buffer list at position %" PRIu64 " " + "contains entry that cannot be converted to a Dictionary", + (uint64_t) initial_fpos); + ga_clear(&ad_ga); + goto shada_read_next_item_error; + } + } + ga_clear(&ad_ga); + } + } break; } case kSDItemMissing: { @@ -2297,6 +2452,9 @@ shada_read_next_item_start: shada_read_next_item_error: msgpack_unpacked_destroy(&unpacked); msgpack_unpacker_free(unpacker); + entry->type = (ShadaEntryType) type_u64; + shada_free_shada_entry(entry); + entry->type = kSDItemMissing; return FAIL; shada_read_next_item_end: msgpack_unpacked_destroy(&unpacked); diff --git a/test/functional/shada/buffers_spec.lua b/test/functional/shada/buffers_spec.lua new file mode 100644 index 0000000000..97dafa533b --- /dev/null +++ b/test/functional/shada/buffers_spec.lua @@ -0,0 +1,65 @@ +-- ShaDa buffer list saving/reading support +local helpers = require('test.functional.helpers') +local nvim, nvim_window, nvim_curwin, nvim_command, nvim_feed, nvim_eval, eq = + helpers.nvim, helpers.window, helpers.curwin, helpers.command, helpers.feed, + helpers.eval, helpers.eq + +local shada_helpers = require('test.functional.shada.helpers') +local reset, set_additional_cmd, clear = + shada_helpers.reset, shada_helpers.set_additional_cmd, + shada_helpers.clear + +local nvim_current_line = function() + return nvim_window('get_cursor', nvim_curwin())[1] +end + +describe('ShaDa support code', function() + testfilename = 'Xtestfile-functional-shada-buffers' + testfilename_2 = 'Xtestfile-functional-shada-buffers-2' + before_each(reset) + after_each(clear) + + it('is able to dump and restore buffer list', function() + set_additional_cmd('set viminfo+=%') + reset() + nvim_command('edit ' .. testfilename) + nvim_command('edit ' .. testfilename_2) + -- nvim_command('redir! > /tmp/vistr | verbose set viminfo? | redir END') + -- nvim_command('wviminfo /tmp/foo') + nvim_command('qall') + reset() + -- nvim_command('call writefile([&viminfo], "/tmp/vistr")') + eq(3, nvim_eval('bufnr("$")')) + eq('', nvim_eval('bufname(1)')) + eq(testfilename, nvim_eval('bufname(2)')) + eq(testfilename_2, nvim_eval('bufname(3)')) + end) + + it('does not restore buffer list without % in &viminfo', function() + set_additional_cmd('set viminfo+=%') + reset() + nvim_command('edit ' .. testfilename) + nvim_command('edit ' .. testfilename_2) + -- nvim_command('redir! > /tmp/vistr | verbose set viminfo? | redir END') + -- nvim_command('wviminfo /tmp/foo') + set_additional_cmd('') + nvim_command('qall') + reset() + -- nvim_command('call writefile([&viminfo], "/tmp/vistr")') + eq(1, nvim_eval('bufnr("$")')) + eq('', nvim_eval('bufname(1)')) + end) + + it('does not dump buffer list without % in &viminfo', function() + nvim_command('edit ' .. testfilename) + nvim_command('edit ' .. testfilename_2) + -- nvim_command('redir! > /tmp/vistr | verbose set viminfo? | redir END') + -- nvim_command('wviminfo /tmp/foo') + set_additional_cmd('set viminfo+=%') + nvim_command('qall') + reset() + -- nvim_command('call writefile([&viminfo], "/tmp/vistr")') + eq(1, nvim_eval('bufnr("$")')) + eq('', nvim_eval('bufname(1)')) + end) +end) -- cgit From 42536abd33a31234fcff9c736002b2e83c2183db Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 5 Jul 2015 02:01:55 +0300 Subject: functests: Add tests for dumping/restoring registers --- test/functional/shada/registers_spec.lua | 141 +++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 test/functional/shada/registers_spec.lua diff --git a/test/functional/shada/registers_spec.lua b/test/functional/shada/registers_spec.lua new file mode 100644 index 0000000000..75c86628a6 --- /dev/null +++ b/test/functional/shada/registers_spec.lua @@ -0,0 +1,141 @@ +-- ShaDa registers saving/reading support +local helpers = require('test.functional.helpers') +local nvim, nvim_window, nvim_curwin, nvim_command, nvim_feed, nvim_eval, eq = + helpers.nvim, helpers.window, helpers.curwin, helpers.command, helpers.feed, + helpers.eval, helpers.eq + +local shada_helpers = require('test.functional.shada.helpers') +local reset, set_additional_cmd, clear = + shada_helpers.reset, shada_helpers.set_additional_cmd, + shada_helpers.clear + +local nvim_current_line = function() + return nvim_window('get_cursor', nvim_curwin())[1] +end + +local setreg = function(name, contents, typ) + local expr = 'setreg("' .. name .. '", [' + if type(contents) == 'string' then + contents = {contents} + end + for _, line in ipairs(contents) do + expr = expr .. '"' .. line:gsub('[\\"]', '\\\\\\0') .. '", ' + end + expr = expr .. '], "' .. typ .. '")' + nvim_eval(expr) +end + +local getreg = function(name) + return { + nvim_eval(('getreg("%s", 1, 1)'):format(name)), + nvim_eval(('getregtype("%s")'):format(name)), + } +end + +describe('ShaDa support code', function() + before_each(reset) + after_each(clear) + + it('is able to dump and restore registers and their type', function() + setreg('c', {'d', 'e', ''}, 'c') + setreg('l', {'a', 'b', 'cde'}, 'l') + setreg('b', {'bca', 'abc', 'cba'}, 'b3') + nvim_command('qa') + reset() + eq({{'d', 'e', ''}, 'v'}, getreg('c')) + eq({{'a', 'b', 'cde'}, 'V'}, getreg('l')) + eq({{'bca', 'abc', 'cba'}, '\0223'}, getreg('b')) + end) + + it('does not dump registers with zero <', function() + nvim_command('set viminfo=\'0,<0') + setreg('c', {'d', 'e', ''}, 'c') + setreg('l', {'a', 'b', 'cde'}, 'l') + setreg('b', {'bca', 'abc', 'cba'}, 'b3') + nvim_command('qa') + reset() + eq({nil, ''}, getreg('c')) + eq({nil, ''}, getreg('l')) + eq({nil, ''}, getreg('b')) + end) + + it('does restore registers with zero <', function() + setreg('c', {'d', 'e', ''}, 'c') + setreg('l', {'a', 'b', 'cde'}, 'l') + setreg('b', {'bca', 'abc', 'cba'}, 'b3') + set_additional_cmd('set viminfo=\'0,<0') + nvim_command('qa') + reset() + eq({{'d', 'e', ''}, 'v'}, getreg('c')) + eq({{'a', 'b', 'cde'}, 'V'}, getreg('l')) + eq({{'bca', 'abc', 'cba'}, '\0223'}, getreg('b')) + end) + + it('does not dump registers with zero "', function() + nvim_command('set viminfo=\'0,\\"0') + setreg('c', {'d', 'e', ''}, 'c') + setreg('l', {'a', 'b', 'cde'}, 'l') + setreg('b', {'bca', 'abc', 'cba'}, 'b3') + nvim_command('qa') + reset() + eq({nil, ''}, getreg('c')) + eq({nil, ''}, getreg('l')) + eq({nil, ''}, getreg('b')) + end) + + it('does restore registers with zero "', function() + setreg('c', {'d', 'e', ''}, 'c') + setreg('l', {'a', 'b', 'cde'}, 'l') + setreg('b', {'bca', 'abc', 'cba'}, 'b3') + set_additional_cmd('set viminfo=\'0,\\"0') + nvim_command('qa') + reset() + eq({{'d', 'e', ''}, 'v'}, getreg('c')) + eq({{'a', 'b', 'cde'}, 'V'}, getreg('l')) + eq({{'bca', 'abc', 'cba'}, '\0223'}, getreg('b')) + end) + + it('does dump registers with zero ", but non-zero <', function() + nvim_command('set viminfo=\'0,\\"0,<50') + setreg('c', {'d', 'e', ''}, 'c') + setreg('l', {'a', 'b', 'cde'}, 'l') + setreg('b', {'bca', 'abc', 'cba'}, 'b3') + nvim_command('qa') + reset() + eq({{'d', 'e', ''}, 'v'}, getreg('c')) + eq({{'a', 'b', 'cde'}, 'V'}, getreg('l')) + eq({{'bca', 'abc', 'cba'}, '\0223'}, getreg('b')) + end) + + it('does limit number of lines according to <', function() + nvim_command('set viminfo=\'0,<2') + setreg('o', {'d'}, 'c') + setreg('t', {'a', 'b', 'cde'}, 'l') + nvim_command('qa') + reset() + eq({{'d'}, 'v'}, getreg('o')) + eq({nil, ''}, getreg('t')) + end) + + it('does limit number of lines according to "', function() + nvim_command('set viminfo=\'0,\\"2') + setreg('o', {'d'}, 'c') + setreg('t', {'a', 'b', 'cde'}, 'l') + nvim_command('qa') + reset() + eq({{'d'}, 'v'}, getreg('o')) + eq({nil, ''}, getreg('t')) + end) + + it('does limit number of lines according to < rather then "', function() + nvim_command('set viminfo=\'0,\\"2,<3') + setreg('o', {'d'}, 'c') + setreg('t', {'a', 'b', 'cde'}, 'l') + setreg('h', {'abc', 'acb', 'bac', 'bca', 'cab', 'cba'}, 'b3') + nvim_command('qa') + reset() + eq({{'d'}, 'v'}, getreg('o')) + eq({{'a', 'b', 'cde'}, 'V'}, getreg('t')) + eq({nil, ''}, getreg('h')) + end) +end) -- cgit From 941ec54aa1183be41df9bee426bd97ecd4d1a0d7 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 5 Jul 2015 02:37:37 +0300 Subject: shada: Reassign some entry types to WantInfo and WantMarks kShaDaWantMarks now loads only local file change list and file-local marks. kShaDaWantInfo now loads global marks and jump list. --- src/nvim/shada.c | 8 ++++---- src/nvim/shada.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 2d555faded..352ca19e01 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -536,7 +536,7 @@ static void shada_read(FILE *const fp, const int flags) } case kSDItemJump: case kSDItemGlobalMark: { - if (!(flags & kShaDaWantMarks) + if (!(flags & kShaDaWantInfo) || (cur_entry.type == kSDItemGlobalMark && get_viminfo_parameter('f') == 0)) { shada_free_shada_entry(&cur_entry); @@ -1734,9 +1734,9 @@ shada_read_next_item_start: } break; } - case kSDItemBufferList: - case kSDItemChange: + case kSDItemGlobalMark: case kSDItemJump: + case kSDItemBufferList: case kSDItemVariable: case kSDItemRegister: case kSDItemHistoryEntry: @@ -1747,7 +1747,7 @@ shada_read_next_item_start: } break; } - case kSDItemGlobalMark: + case kSDItemChange: case kSDItemLocalMark: { if (!(flags & kShaDaWantMarks)) { SKIP; diff --git a/src/nvim/shada.h b/src/nvim/shada.h index ef7527677e..8033cbe116 100644 --- a/src/nvim/shada.h +++ b/src/nvim/shada.h @@ -6,7 +6,7 @@ typedef long ShadaPosition; /// Flags for shada_read_file and children enum { kShaDaWantInfo = 1, ///< Load non-mark information - kShaDaWantMarks = 2, ///< Load file marks + kShaDaWantMarks = 2, ///< Load local file marks and change list kShaDaForceit = 4, ///< Overwrite info already read kShaDaGetOldfiles = 8, ///< Load v:oldfiles. kShaDaWantHeader = 16, ///< Do not skip header (shada_read_next_item). -- cgit From cf004c0d41833349eca0d7f7947e80dd8fc7aea7 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 5 Jul 2015 02:38:41 +0300 Subject: functests: Test dumping and loading change list --- test/functional/shada/marks_spec.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/functional/shada/marks_spec.lua b/test/functional/shada/marks_spec.lua index b853513317..f1a112bfef 100644 --- a/test/functional/shada/marks_spec.lua +++ b/test/functional/shada/marks_spec.lua @@ -148,4 +148,18 @@ describe('ShaDa support code', function() eq(testfilename_2, nvim_eval('bufname("%")')) eq(2, nvim_current_line()) end) + + it('is able to dump and restore change list', function() + nvim_command('edit ' .. testfilename) + nvim_feed('Gra') + nvim_feed('ggrb') + nvim_command('qall!') + reset() + nvim_command('edit ' .. testfilename) + -- nvim_command('rviminfo') + nvim_feed('Gg;') + eq(1, nvim_current_line()) + nvim_feed('g;') + eq(2, nvim_current_line()) + end) end) -- cgit From d1ae27ceec467605d02e6388d5b8007bc9359d52 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 5 Jul 2015 03:13:00 +0300 Subject: shada,functests: Refactor shada items skipping --- src/nvim/shada.c | 174 +++++++++++++++-------------------- test/functional/shada/marks_spec.lua | 18 +++- 2 files changed, 90 insertions(+), 102 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 352ca19e01..b59b448ca7 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -133,6 +133,43 @@ typedef enum { #define SHADA_LAST_ENTRY ((uint64_t) kSDItemChange) } ShadaEntryType; +/// 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 << kSDItemGlobalMark) + | (1 << kSDItemJump) + ), ///< Data reading which cannot be disabled by &viminfo 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 &viminfo). + kSDReadBufferList = (1 << kSDItemBufferList), ///< Determines whether buffer + ///< list should be read + ///< (disabled by removing + ///< % entry from viminfo). + kSDReadUnknown = (1 << (SHADA_LAST_ENTRY + 1)), ///< Determines whether + ///< unknown items should be + ///< read (usually disabled). + kSDReadLocalMarks = ( + (1 << kSDItemLocalMark) + | (1 << kSDItemChange) + ), ///< Determines whether local marks and change list should be read. Can + ///< only be disabled by disabling &viminfo 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; @@ -416,8 +453,31 @@ static inline bool marks_equal(const pos_T a, const pos_T b) static void shada_read(FILE *const fp, const int flags) FUNC_ATTR_NONNULL_ALL { + unsigned srni_flags = 0; + if (flags & kShaDaWantInfo) { + srni_flags |= kSDReadUndisableableData | kSDReadRegisters; + if (p_hi) { + srni_flags |= kSDReadHistory; + } + if (find_viminfo_parameter('!') != NULL) { + srni_flags |= kSDReadVariables; + } + if (find_viminfo_parameter('%') != NULL && ARGCOUNT == 0) { + srni_flags |= kSDReadBufferList; + } + } + if (flags & kShaDaWantMarks) { + if (get_viminfo_parameter('\'') > 0) { + srni_flags |= kSDReadLocalMarks; + } + } + if (srni_flags == 0) { + // Nothing to do. + return; + } + const bool force = flags & kShaDaForceit; HistoryMergerState hms[HIST_COUNT]; - if (flags & kShaDaWantInfo && p_hi) { + if (srni_flags & kSDReadHistory) { for (uint8_t i = 0; i < HIST_COUNT; i++) { hms[i].hmrb = hm_rb_new((size_t) p_hi); hms[i].do_merge = true; @@ -428,7 +488,7 @@ static void shada_read(FILE *const fp, const int flags) ShadaEntry cur_entry; khash_t(bufset) *cl_bufs = kh_init(bufset); khash_t(fnamebufs) *fname_bufs = kh_init(fnamebufs); - while (shada_read_next_item(fp, &cur_entry, flags) == NOTDONE) { + while (shada_read_next_item(fp, &cur_entry, srni_flags) == NOTDONE) { switch (cur_entry.type) { case kSDItemMissing: { assert(false); @@ -441,10 +501,6 @@ static void shada_read(FILE *const fp, const int flags) break; } case kSDItemSearchPattern: { - if (!(flags & kShaDaWantInfo)) { - shada_free_shada_entry(&cur_entry); - break; - } (cur_entry.data.search_pattern.is_substitute_pattern ? &set_substitute_pattern : &set_search_pattern)((SearchPattern) { @@ -467,10 +523,6 @@ static void shada_read(FILE *const fp, const int flags) break; } case kSDItemSubString: { - if (!(flags & kShaDaWantInfo)) { - shada_free_shada_entry(&cur_entry); - break; - } sub_set_replacement((SubReplacementString) { .sub = cur_entry.data.sub_string.sub, .timestamp = cur_entry.timestamp, @@ -480,10 +532,6 @@ static void shada_read(FILE *const fp, const int flags) break; } case kSDItemHistoryEntry: { - if (!(flags & kShaDaWantInfo && p_hi)) { - shada_free_shada_entry(&cur_entry); - break; - } if (cur_entry.data.history_item.histtype >= HIST_COUNT) { shada_free_shada_entry(&cur_entry); break; @@ -494,10 +542,6 @@ static void shada_read(FILE *const fp, const int flags) break; } case kSDItemRegister: { - if (!(flags & kShaDaWantInfo)) { - shada_free_shada_entry(&cur_entry); - break; - } if (cur_entry.data.reg.type != MCHAR && cur_entry.data.reg.type != MLINE && cur_entry.data.reg.type != MBLOCK) { @@ -516,10 +560,6 @@ static void shada_read(FILE *const fp, const int flags) break; } case kSDItemVariable: { - if (!(flags & kShaDaWantInfo) || find_viminfo_parameter('!') == NULL) { - shada_free_shada_entry(&cur_entry); - break; - } typval_T vartv; Error err; if (!object_to_vim(cur_entry.data.global_var.value, &vartv, &err)) { @@ -536,12 +576,6 @@ static void shada_read(FILE *const fp, const int flags) } case kSDItemJump: case kSDItemGlobalMark: { - if (!(flags & kShaDaWantInfo) - || (cur_entry.type == kSDItemGlobalMark - && get_viminfo_parameter('f') == 0)) { - shada_free_shada_entry(&cur_entry); - break; - } buf_T *buf = find_buffer(fname_bufs, cur_entry.data.filemark.fname); if (buf != NULL) { xfree(cur_entry.data.filemark.fname); @@ -559,10 +593,9 @@ static void shada_read(FILE *const fp, const int flags) }, }; if (cur_entry.type == kSDItemGlobalMark) { - mark_set_global(cur_entry.data.filemark.name, fm, - !(flags & kShaDaForceit)); + mark_set_global(cur_entry.data.filemark.name, fm, !force); } else { - if (flags & kShaDaForceit) { + if (force) { if (curwin->w_jumplistlen == JUMPLISTSIZE) { // Jump list items are ignored in this case. free_xfmark(fm); @@ -629,11 +662,6 @@ static void shada_read(FILE *const fp, const int flags) break; } case kSDItemBufferList: { - if (!(flags & kShaDaWantInfo) || find_viminfo_parameter('%') == NULL - || ARGCOUNT != 0) { - shada_free_shada_entry(&cur_entry); - break; - } 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); @@ -652,10 +680,6 @@ static void shada_read(FILE *const fp, const int flags) } case kSDItemChange: case kSDItemLocalMark: { - if (!(flags & kShaDaWantMarks)) { - 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); @@ -668,12 +692,11 @@ static void shada_read(FILE *const fp, const int flags) .additional_data = cur_entry.data.filemark.additional_data, }; if (cur_entry.type == kSDItemLocalMark) { - mark_set_local(cur_entry.data.filemark.name, buf, fm, - !(flags & kShaDaForceit)); + mark_set_local(cur_entry.data.filemark.name, buf, fm, !force); } else { int kh_ret; (void) kh_put(bufset, cl_bufs, (uintptr_t) buf, &kh_ret); - if (flags & kShaDaForceit) { + if (force) { if (buf->b_changelistlen == JUMPLISTSIZE) { free_fmark(buf->b_changelist[0]); memmove(buf->b_changelist, buf->b_changelist + 1, @@ -737,7 +760,7 @@ static void shada_read(FILE *const fp, const int flags) // 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 (flags & kShaDaWantInfo && p_hi) { + if (srni_flags & kSDReadHistory) { for (uint8_t i = 0; i < HIST_COUNT; i++) { if (hms[i].last_hist_entry.type != kSDItemMissing) { insert_history_entry(&(hms[i]), hms[i].last_hist_entry, false); @@ -1672,12 +1695,12 @@ static int msgpack_read_uint64(FILE *const fp, const int first_char, /// @param[in] fp Pointer to the opened ShaDa file. /// @param[out] entry Address where next entry contents will be saved. /// @param[in] flags Flags, determining whether and which items should be -/// skipped. +/// skipped (see SRNIFlags enum). /// /// @return NOTDONE if entry was read correctly, FAIL if there were errors and /// OK at EOF. static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry, - const int flags) + const unsigned flags) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { shada_read_next_item_start: @@ -1710,63 +1733,14 @@ shada_read_next_item_start: const size_t length = (size_t) length_u64; entry->timestamp = (Timestamp) timestamp_u64; -#define SKIP \ - do { \ - if (fread_len(fp, NULL, length) != OK) { \ - return FAIL; \ - } \ - goto shada_read_next_item_start; \ - } while (0) - // TODO(ZyX-I): More precise skipping: skip reading some things depending on - // 'viminfo': e.g. variables if viminfo does not contain `!`. - // - // Option value should probably be checked elsewhere. - switch (type_u64) { - case kSDItemMissing: { - emsgn("Error while reading ShaDa file: " - "entry at position %" PRId64 "has invalid zero type", - (int64_t) initial_fpos); + if ((type_u64 > SHADA_LAST_ENTRY + ? !(flags & kSDReadUnknown) + : !((unsigned) (1 << type_u64) & flags))) { + if (fread_len(fp, NULL, length) != OK) { return FAIL; } - case kSDItemHeader: { - if (!(flags & kShaDaWantHeader)) { - SKIP; - } - break; - } - case kSDItemGlobalMark: - case kSDItemJump: - case kSDItemBufferList: - case kSDItemVariable: - case kSDItemRegister: - case kSDItemHistoryEntry: - case kSDItemSubString: - case kSDItemSearchPattern: { - if (!(flags & kShaDaWantInfo)) { - SKIP; - } - break; - } - case kSDItemChange: - case kSDItemLocalMark: { - if (!(flags & kShaDaWantMarks)) { - SKIP; - } - break; - } - default: { - entry->data.unknown_item.size = length; - char *contents = xmalloc(length); - entry->data.unknown_item.contents = contents; - entry->data.unknown_item.type = type_u64; - if (fread_len(fp, contents, length) != OK) { - return FAIL; - } - entry->type = kSDItemUnknown; - return NOTDONE; - } + goto shada_read_next_item_start; } -#undef SKIP if (type_u64 > SHADA_LAST_ENTRY) { entry->type = kSDItemUnknown; diff --git a/test/functional/shada/marks_spec.lua b/test/functional/shada/marks_spec.lua index f1a112bfef..a6518ccbf9 100644 --- a/test/functional/shada/marks_spec.lua +++ b/test/functional/shada/marks_spec.lua @@ -48,13 +48,13 @@ describe('ShaDa support code', function() eq(2, nvim_current_line()) end) - it('does not read back global mark without `f0` in viminfo', function() + it('does not dump global mark with `f0` in viminfo', function() + nvim_command('set viminfo+=f0') nvim_command('edit ' .. testfilename) nvim_command('mark A') nvim_command('2') nvim_command('kB') nvim_command('wviminfo') - set_additional_cmd('set viminfo+=f0') reset() nvim_command('language C') nvim_command([[ @@ -66,6 +66,20 @@ describe('ShaDa support code', function() eq('Vim(normal):E20: Mark not set', nvim('get_var', 'exception')) end) + it('does read back global mark even with `\'0` and `f0` in viminfo', function() + nvim_command('edit ' .. testfilename) + nvim_command('mark A') + nvim_command('2') + nvim_command('kB') + nvim_command('wviminfo') + set_additional_cmd('set viminfo=\'0,f0') + reset() + nvim_command('language C') + nvim_command('normal! `A') + eq(testfilename, nvim_eval('fnamemodify(@%, ":t")')) + eq(1, nvim_current_line()) + end) + it('is able to dump and read back local mark', function() nvim_command('edit ' .. testfilename) nvim_command('mark a') -- cgit From 98e8c1f37cee11b440a04257fe45f7d1a80ce6c1 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 5 Jul 2015 14:08:13 +0300 Subject: shada: Refactor file reading/writing to use os_open --- src/nvim/memory.c | 2 +- src/nvim/shada.c | 551 ++++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 388 insertions(+), 165 deletions(-) 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/shada.c b/src/nvim/shada.c index b59b448ca7..355bb5d7e9 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #if defined (__GLIBC__) # ifndef _BSD_SOURCE @@ -24,7 +25,6 @@ #include "nvim/shada.h" #include "nvim/message.h" #include "nvim/globals.h" -#include "nvim/macros.h" #include "nvim/memory.h" #include "nvim/mark.h" #include "nvim/ops.h" @@ -64,13 +64,15 @@ KHASH_MAP_INIT_STR(fnamebufs, buf_T *) ((const char *) find_viminfo_parameter(__VA_ARGS__)) #define emsg2(a, b) emsg2((char_u *) a, (char_u *) b) #define emsg3(a, b, c) emsg3((char_u *) a, (char_u *) b, (char_u *) c) -#define emsgn(a, ...) emsgn((char_u *) a, __VA_ARGS__) +#define emsgu(a, ...) emsgu((char_u *) a, __VA_ARGS__) #define home_replace_save(a, b) \ ((char *)home_replace_save(a, (char_u *)b)) #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)) // From http://www.boost.org/doc/libs/1_43_0/boost/detail/endian.hpp + some // additional checks done after examining `{compiler} -dM -E - < /dev/null` @@ -243,12 +245,207 @@ typedef struct { uint8_t history_type; } HistoryMergerState; +struct sd_read_def; + +/// 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; + +/// Structure containing necessary pointers for reading ShaDa files +typedef struct sd_read_def { + ShaDaFileReader read; ///< Reader function. + void *cookie; ///< Reader function last argument. + 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. +} ShaDaReadDef; + +struct sd_write_def; + +/// 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. + void *cookie; ///< Writer function last argument. + char *error; ///< Error message in case of error. +} ShaDaWriteDef; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "shada.c.generated.h" #endif RINGBUF_INIT(HM, hm, ShadaEntry, shada_free_shada_entry) +/// Wrapper for reading from file descriptors +/// +/// @return true if read was successfull, false otherwise. +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; + while (read_bytes != size) { + const ptrdiff_t cur_read_bytes = read((int)(intptr_t) sd_reader->cookie, + ((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 (errno) { + 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 true if read was successfull, false otherwise. +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; + while (written_bytes != size) { + const ptrdiff_t cur_written_bytes = write((int)(intptr_t) sd_writer->cookie, + (char *) dest + written_bytes, + size - written_bytes); + if (cur_written_bytes > 0) { + written_bytes += (size_t) cur_written_bytes; + } + if (errno) { + 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 opening file descriptors +/// +/// All arguments are passed to os_open(). +/// +/// @return file descriptor or -1 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 -1; + } + if (-fd == ENOMEM && !did_try_to_free) { + try_to_free_memory(); + did_try_to_free = true; + goto open_file_start; + } + if (-fd == EINTR) { + goto open_file_start; + } + emsg3("System error while opening ShaDa file %s: %s", + fname, strerror(-fd)); + return -1; + } + 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 OK in case of success, FAIL 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 == -1) { + return FAIL; + } + + *sd_reader = (ShaDaReadDef) { + .read = &read_file, + .error = NULL, + .eof = false, + .fpos = 0, + .cookie = (void *) fd, + }; + + return OK; +} + +/// 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("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. @@ -270,10 +467,16 @@ static inline bool in_bufset(const khash_t(bufset) *const set, const buf_T *buf) /// @return true or false. #define SHADA_REMOVABLE(buf) in_bufset(removable_bufs, buf) -/// Msgpack callback for writing to FILE* -static int msgpack_fbuffer_write(void *data, const char *buf, size_t len) +/// Msgpack callback for writing to ShaDaWriteDef* +static int msgpack_sd_writer_write(void *data, const char *buf, size_t len) { - return (fwrite(buf, len, 1, (FILE *) data) == 1) ? 0 : -1; + ShaDaWriteDef *const sd_writer = (ShaDaWriteDef *) data; + ptrdiff_t written_bytes = sd_writer->write(sd_writer, buf, len); + if (written_bytes == -1) { + emsg2("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 @@ -294,14 +497,14 @@ static bool shada_disabled(void) int shada_read_file(const char *const file, const int flags) FUNC_ATTR_WARN_UNUSED_RESULT { - FILE *fp; - if (shada_disabled()) { return FAIL; } char *const fname = shada_filename(file); - fp = mch_fopen(fname, READBIN); + + ShaDaReadDef sd_reader; + const int of_ret = open_shada_file_for_reading(fname, &sd_reader); if (p_verbose > 0) { verbose_enter(); @@ -310,18 +513,18 @@ int shada_read_file(const char *const file, const int flags) (flags & kShaDaWantInfo) ? _(" info") : "", (flags & kShaDaWantMarks) ? _(" marks") : "", (flags & kShaDaGetOldfiles) ? _(" oldfiles") : "", - fp == NULL ? _(" FAILED") : ""); + of_ret != OK ? _(" FAILED") : ""); verbose_leave(); } xfree(fname); - if (fp == NULL) { - return FAIL; + if (of_ret != OK) { + return of_ret; } - shada_read(fp, flags); + shada_read(&sd_reader, flags); - fclose(fp); + close_file((int)(intptr_t) sd_reader.cookie); return OK; } @@ -448,9 +651,9 @@ static inline bool marks_equal(const pos_T a, const pos_T b) /// Read data from ShaDa file /// -/// @param[in] fp File to read from. -/// @param[in] flags What to read. -static void shada_read(FILE *const fp, const int flags) +/// @param[in] sd_reader Structure containing file reader definition. +/// @param[in] flags What to read. +static void shada_read(ShaDaReadDef *const sd_reader, const int flags) FUNC_ATTR_NONNULL_ALL { unsigned srni_flags = 0; @@ -488,7 +691,7 @@ static void shada_read(FILE *const fp, const int flags) ShadaEntry cur_entry; khash_t(bufset) *cl_bufs = kh_init(bufset); khash_t(fnamebufs) *fname_bufs = kh_init(fnamebufs); - while (shada_read_next_item(fp, &cur_entry, srni_flags) == NOTDONE) { + while (shada_read_next_item(sd_reader, &cur_entry, srni_flags) == NOTDONE) { switch (cur_entry.type) { case kSDItemMissing: { assert(false); @@ -1136,11 +1339,12 @@ static void shada_pack_entry(msgpack_packer *const packer, /// Write ShaDa file /// -/// @param[in] newfp File pointer to write to. Must not be NULL. -/// @param[in] oldfp Pointer to the previous ShaDa file. If it is not NULL -/// then contents of this file will be merged with current -/// NeoVim runtime. -static void shada_write(FILE *const newfp, FILE *const oldfp) +/// @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 void shada_write(ShaDaWriteDef *const sd_writer, + ShaDaReadDef *const sd_reader) FUNC_ATTR_NONNULL_ARG(1) { khash_t(bufset) *const removable_bufs = kh_init(bufset); @@ -1153,7 +1357,8 @@ static void shada_write(FILE *const newfp, FILE *const oldfp) } const size_t max_kbyte = (size_t) max_kbyte_i; - msgpack_packer *packer = msgpack_packer_new(newfp, &msgpack_fbuffer_write); + msgpack_packer *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)) { @@ -1162,6 +1367,8 @@ static void shada_write(FILE *const newfp, FILE *const oldfp) } } + // TODO(ZyX-I): Iterate over sd_reader, keeping “replaced” values in a set. + // First write values that do not require merging // 1. Header shada_pack_entry(packer, (ShadaEntry) { @@ -1182,7 +1389,6 @@ static void shada_write(FILE *const newfp, FILE *const oldfp) } } }, 0); - fflush(newfp); // 2. Buffer list if (find_viminfo_parameter('%') != NULL) { @@ -1440,7 +1646,14 @@ static void shada_write(FILE *const newfp, FILE *const oldfp) int shada_write_file(const char *const file, const bool nomerge) { char *const fname = shada_filename(file); - FILE *wfp = mch_fopen(fname, WRITEBIN); + ShaDaWriteDef sd_writer = (ShaDaWriteDef) { + .write = &write_file, + .error = NULL, + }; + + const intptr_t fd = (intptr_t) open_file(fname, + O_CREAT|O_WRONLY|O_NOFOLLOW, + 0600); if (p_verbose > 0) { verbose_enter(); @@ -1449,13 +1662,15 @@ int shada_write_file(const char *const file, const bool nomerge) } xfree(fname); - if (wfp == NULL) { + if (fd == -1) { return FAIL; } - shada_write(wfp, NULL); + sd_writer.cookie = (void *) fd; + + shada_write(&sd_writer, NULL); - fclose(wfp); + close_file((int)(intptr_t) sd_writer.cookie); return OK; } @@ -1586,34 +1801,42 @@ static inline uint64_t be64toh(uint64_t big_endian_64_bits) /// Read given number of bytes into given buffer, display error if needed /// -/// @param[in] fp File to read from. -/// @param[out] buffer Where to save the results. May be NULL. -/// @param[in] length How many bytes should be read. +/// @param[in] sd_reader Structure containing file reader definition. +/// @param[out] buffer Where to save the results. May be NULL. +/// @param[in] length How many bytes should be read. /// /// @return FAIL if reading was not successfull, OK otherwise. -static int fread_len(FILE *const fp, char *const buffer, const size_t length) +static int fread_len(ShaDaReadDef *const sd_reader, char *const buffer, + const size_t length) FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT { - size_t read_bytes = 0; + ptrdiff_t read_bytes = 0; if (buffer == NULL) { do { - read_bytes += fread(IObuff, 1, length > IOSIZE ? IOSIZE : length, fp); - } while (read_bytes < length && !ferror(fp) && !feof(fp)); + ptrdiff_t new_read_bytes = sd_reader->read( + sd_reader, IObuff, (size_t) (length - (size_t) read_bytes > IOSIZE + ? IOSIZE + : length - (size_t) read_bytes)); + if (new_read_bytes == -1) { + break; + } + read_bytes += new_read_bytes; + } while ((size_t) read_bytes < length && !sd_reader->eof); } else { - read_bytes = fread(buffer, 1, length, fp); + read_bytes = sd_reader->read(sd_reader, buffer, length); } - if (ferror(fp)) { - emsg2("System error while reading ShaDa file: %s", - strerror(errno)); + if (sd_reader->error != NULL) { + emsg2("System error while reading ShaDa file: %s", sd_reader->error); return FAIL; - } else if (read_bytes != length) { - emsgn("Error while reading ShaDa file: " - "last entry specified that it occupies %" PRId64 " bytes, " + } else if (sd_reader->eof) { + emsgu("Error while reading ShaDa file: " + "last entry specified that it occupies %" PRIu64 " bytes, " "but file ended earlier", - (int64_t) length); + (uint64_t) length); return FAIL; } + assert(read_bytes >= 0 && (size_t) read_bytes == length); return OK; } @@ -1627,27 +1850,26 @@ static int fread_len(FILE *const fp, char *const buffer, const size_t length) /// /// One byte from file stream is always consumed, even if it is not correct. /// -/// @param[in] fp File to read from. -/// @param[out] result Location where result is saved. +/// @param[in] sd_reader Structure containing file reader definition. +/// @param[out] result Location where result is saved. /// /// @return OK if read was successfull, FAIL if it was not. -static int msgpack_read_uint64(FILE *const fp, const int first_char, +static int 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 long fpos = ftell(fp) - 1; - if (fpos == -2) { - clearerr(fp); - } + const uintmax_t fpos = sd_reader->fpos - 1; if (first_char == EOF) { - if (ferror(fp)) { + if (sd_reader->error) { emsg2("System error while reading ShaDa file: %s", - strerror(errno)); - } else if (feof(fp)) { - emsgn("Error while reading ShaDa file: " - "expected positive integer at position %" PRId64, - (int64_t) fpos); + sd_reader->error); + } else if (sd_reader->eof) { + emsgu("Error while reading ShaDa file: " + "expected positive integer at position %" PRIu64 + ", but got nothing", + (uint64_t) fpos); } return FAIL; } @@ -1675,14 +1897,15 @@ static int msgpack_read_uint64(FILE *const fp, const int first_char, break; } default: { - emsgn("Error while reading ShaDa file: " - "expected positive integer at position %" PRId64, - (int64_t) fpos); + emsgu("Error while reading ShaDa file: " + "expected positive integer at position %" PRIu64, + (uint64_t) fpos); return FAIL; } } uint8_t buf[sizeof(uint64_t)] = {0, 0, 0, 0, 0, 0, 0, 0}; - if (fread_len(fp, (char *) &(buf[sizeof(uint64_t)-length]), length) != OK) { + if (fread_len(sd_reader, (char *) &(buf[sizeof(uint64_t)-length]), length) + != OK) { return FAIL; } *result = be64toh(*((uint64_t *) &(buf[0]))); @@ -1692,20 +1915,21 @@ static int msgpack_read_uint64(FILE *const fp, const int first_char, /// Iterate over shada file contents /// -/// @param[in] fp Pointer to the opened ShaDa file. -/// @param[out] entry Address where next entry contents will be saved. -/// @param[in] flags Flags, determining whether and which items should be -/// skipped (see SRNIFlags enum). +/// @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). /// /// @return NOTDONE if entry was read correctly, FAIL if there were errors and /// OK at EOF. -static int shada_read_next_item(FILE *const fp, ShadaEntry *const entry, +static int shada_read_next_item(ShaDaReadDef *const sd_reader, + ShadaEntry *const entry, const unsigned flags) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { shada_read_next_item_start: entry->type = kSDItemMissing; - if (feof(fp)) { + if (sd_reader->eof) { return OK; } @@ -1715,18 +1939,17 @@ shada_read_next_item_start: uint64_t timestamp_u64; uint64_t length_u64; - const long initial_fpos = ftell(fp); - if (initial_fpos == -1) { - clearerr(fp); - } - const int first_char = fgetc(fp); - if (first_char == EOF && feof(fp)) { + const uintmax_t initial_fpos = sd_reader->fpos; + const int first_char = read_char(sd_reader); + if (first_char == EOF && sd_reader->eof) { return OK; } - if (msgpack_read_uint64(fp, first_char, &type_u64) != OK - || msgpack_read_uint64(fp, fgetc(fp), ×tamp_u64) != OK - || msgpack_read_uint64(fp, fgetc(fp), &length_u64) != OK) { + if (msgpack_read_uint64(sd_reader, first_char, &type_u64) != OK + || (msgpack_read_uint64(sd_reader, read_char(sd_reader), ×tamp_u64) + != OK) + || (msgpack_read_uint64(sd_reader, read_char(sd_reader), &length_u64) + != OK)) { return FAIL; } @@ -1736,7 +1959,7 @@ shada_read_next_item_start: if ((type_u64 > SHADA_LAST_ENTRY ? !(flags & kSDReadUnknown) : !((unsigned) (1 << type_u64) & flags))) { - if (fread_len(fp, NULL, length) != OK) { + if (fread_len(sd_reader, NULL, length) != OK) { return FAIL; } goto shada_read_next_item_start; @@ -1747,7 +1970,7 @@ shada_read_next_item_start: entry->data.unknown_item.size = length; entry->data.unknown_item.type = type_u64; entry->data.unknown_item.contents = xmalloc(length); - return fread_len(fp, entry->data.unknown_item.contents, length); + return fread_len(sd_reader, entry->data.unknown_item.contents, length); } msgpack_unpacker *const unpacker = msgpack_unpacker_new(length); @@ -1757,7 +1980,7 @@ shada_read_next_item_start: goto shada_read_next_item_error; } - if (fread_len(fp, msgpack_unpacker_buffer(unpacker), length) != OK) { + if (fread_len(sd_reader, msgpack_unpacker_buffer(unpacker), length) != OK) { msgpack_unpacker_free(unpacker); return FAIL; } @@ -1792,10 +2015,10 @@ shada_read_next_item_start: proc) \ do { \ if (!(condition)) { \ - emsgn("Error while reading ShaDa file: " \ - entry_name " entry at position %" PRId64 " " \ + emsgu("Error while reading ShaDa file: " \ + entry_name " entry at position %" PRIu64 " " \ error_desc, \ - (int64_t) initial_fpos); \ + (uint64_t) initial_fpos); \ ga_clear(&ad_ga); \ goto shada_read_next_item_error; \ } \ @@ -1804,12 +2027,12 @@ shada_read_next_item_start: #define CHECK_KEY_IS_STR(entry_name) \ do { \ if (unpacked.data.via.map.ptr[i].key.type != MSGPACK_OBJECT_STR) { \ - emsgn("Error while reading ShaDa file: " \ - entry_name " entry at position %" PRId64 " " \ + emsgu("Error while reading ShaDa file: " \ + entry_name " entry at position %" PRIu64 " " \ "has key which is not a string", \ - (int64_t) initial_fpos); \ - emsgn("It is %" PRId64 " instead", \ - unpacked.data.via.map.ptr[i].key.type ); \ + (uint64_t) initial_fpos); \ + emsgu("It is %" PRIu64 " instead", \ + (uint64_t) unpacked.data.via.map.ptr[i].key.type); \ ga_clear(&ad_ga); \ goto shada_read_next_item_error; \ } \ @@ -1854,19 +2077,19 @@ shada_read_next_item_start: switch ((ShadaEntryType) type_u64) { case kSDItemHeader: { if (!msgpack_rpc_to_dictionary(&(unpacked.data), &(entry->data.header))) { - emsgn("Error while reading ShaDa file: " - "header entry at position %" PRId64 " is not a dictionary", - (int64_t) initial_fpos); + emsgu("Error while reading ShaDa file: " + "header entry at position %" PRIu64 " is not a dictionary", + (uint64_t) initial_fpos); goto shada_read_next_item_error; } break; } case kSDItemSearchPattern: { if (unpacked.data.type != MSGPACK_OBJECT_MAP) { - emsgn("Error while reading ShaDa file: " - "search pattern entry at position %" PRId64 " " + emsgu("Error while reading ShaDa file: " + "search pattern entry at position %" PRIu64 " " "is not a dictionary", - (int64_t) initial_fpos); + (uint64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.search_pattern = (struct search_pattern) { @@ -1901,10 +2124,10 @@ shada_read_next_item_start: else ADDITIONAL_KEY } if (entry->data.search_pattern.pat == NULL) { - emsgn("Error while reading ShaDa file: " - "search pattern entry at position %" PRId64 " " + emsgu("Error while reading ShaDa file: " + "search pattern entry at position %" PRIu64 " " "has no pattern", - (int64_t) initial_fpos); + (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; } @@ -1938,10 +2161,10 @@ shada_read_next_item_start: case kSDItemGlobalMark: case kSDItemLocalMark: { if (unpacked.data.type != MSGPACK_OBJECT_MAP) { - emsgn("Error while reading ShaDa file: " - "mark entry at position %" PRId64 " " + emsgu("Error while reading ShaDa file: " + "mark entry at position %" PRIu64 " " "is not a dictionary", - (int64_t) initial_fpos); + (uint64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.filemark = (struct shada_filemark) { @@ -1968,10 +2191,10 @@ shada_read_next_item_start: else ADDITIONAL_KEY } if (entry->data.filemark.mark.lnum == 0) { - emsgn("Error while reading ShaDa file: " - "mark entry at position %" PRId64 " " + emsgu("Error while reading ShaDa file: " + "mark entry at position %" PRIu64 " " "is missing line number", - (int64_t) initial_fpos); + (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; } @@ -2001,10 +2224,10 @@ shada_read_next_item_start: } case kSDItemRegister: { if (unpacked.data.type != MSGPACK_OBJECT_MAP) { - emsgn("Error while reading ShaDa file: " - "register entry at position %" PRId64 " " + emsgu("Error while reading ShaDa file: " + "register entry at position %" PRIu64 " " "is not a dictionary", - (int64_t) initial_fpos); + (uint64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.reg = (struct reg) { @@ -2027,18 +2250,18 @@ shada_read_next_item_start: entry->data.reg.width, POSITIVE_INTEGER, u64, TOSIZE) else if (CHECK_KEY(unpacked.data.via.map.ptr[i].key, "contents")) { if (unpacked.data.via.map.ptr[i].val.type != MSGPACK_OBJECT_ARRAY) { - emsgn("Error while reading ShaDa file: " - "register entry at position %" PRId64 " " + emsgu("Error while reading ShaDa file: " + "register entry at position %" PRIu64 " " "has contents key with non-array value", - (int64_t) initial_fpos); + (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; } if (unpacked.data.via.map.ptr[i].val.via.array.size == 0) { - emsgn("Error while reading ShaDa file: " - "register entry at position %" PRId64 " " + emsgu("Error while reading ShaDa file: " + "register entry at position %" PRIu64 " " "has contents key with empty array", - (int64_t) initial_fpos); + (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; } @@ -2046,10 +2269,10 @@ shada_read_next_item_start: unpacked.data.via.map.ptr[i].val.via.array; for (size_t i = 0; i < arr.size; i++) { if (arr.ptr[i].type != MSGPACK_OBJECT_BIN) { - emsgn("Error while reading ShaDa file: " - "register entry at position %" PRId64 " " + emsgu("Error while reading ShaDa file: " + "register entry at position %" PRIu64 " " "has contents array with non-string value", - (int64_t) initial_fpos); + (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; } @@ -2063,10 +2286,10 @@ shada_read_next_item_start: } else ADDITIONAL_KEY } if (entry->data.reg.contents == NULL) { - emsgn("Error while reading ShaDa file: " - "register entry at position %" PRId64 " " + emsgu("Error while reading ShaDa file: " + "register entry at position %" PRIu64 " " "has missing contents array", - (int64_t) initial_fpos); + (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; } @@ -2096,10 +2319,10 @@ shada_read_next_item_start: } case kSDItemHistoryEntry: { if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) { - emsgn("Error while reading ShaDa file: " - "history entry at position %" PRId64 " " + emsgu("Error while reading ShaDa file: " + "history entry at position %" PRIu64 " " "is not an array", - (int64_t) initial_fpos); + (uint64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.history_item = (struct history_item) { @@ -2109,26 +2332,26 @@ shada_read_next_item_start: .additional_elements = NULL, }; if (unpacked.data.via.array.size < 2) { - emsgn("Error while reading ShaDa file: " - "history entry at position %" PRId64 " " + emsgu("Error while reading ShaDa file: " + "history entry at position %" PRIu64 " " "does not have enough elements", - (int64_t) initial_fpos); + (uint64_t) initial_fpos); goto shada_read_next_item_error; } if (unpacked.data.via.array.ptr[0].type != MSGPACK_OBJECT_POSITIVE_INTEGER) { - emsgn("Error while reading ShaDa file: " - "history entry at position %" PRId64 " " + emsgu("Error while reading ShaDa file: " + "history entry at position %" PRIu64 " " "has wrong history type type", - (int64_t) initial_fpos); + (uint64_t) initial_fpos); goto shada_read_next_item_error; } if (unpacked.data.via.array.ptr[1].type != MSGPACK_OBJECT_BIN) { - emsgn("Error while reading ShaDa file: " - "history entry at position %" PRId64 " " + emsgu("Error while reading ShaDa file: " + "history entry at position %" PRIu64 " " "has wrong history string type", - (int64_t) initial_fpos); + (uint64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.history_item.histtype = @@ -2137,18 +2360,18 @@ shada_read_next_item_start: entry->data.history_item.histtype == HIST_SEARCH; if (is_hist_search) { if (unpacked.data.via.array.size < 3) { - emsgn("Error while reading ShaDa file: " - "search history entry at position %" PRId64 " " + emsgu("Error while reading ShaDa file: " + "search history entry at position %" PRIu64 " " "does not have separator character", - (int64_t) initial_fpos); + (uint64_t) initial_fpos); goto shada_read_next_item_error; } if (unpacked.data.via.array.ptr[2].type != MSGPACK_OBJECT_POSITIVE_INTEGER) { - emsgn("Error while reading ShaDa file: " - "search history entry at position %" PRId64 " " + emsgu("Error while reading ShaDa file: " + "search history entry at position %" PRIu64 " " "has wrong history separator type", - (int64_t) initial_fpos); + (uint64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.history_item.sep = @@ -2191,10 +2414,10 @@ shada_read_next_item_start: } case kSDItemVariable: { if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) { - emsgn("Error while reading ShaDa file: " - "variable entry at position %" PRId64 " " + emsgu("Error while reading ShaDa file: " + "variable entry at position %" PRIu64 " " "is not an array", - (int64_t) initial_fpos); + (uint64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.global_var = (struct global_var) { @@ -2205,25 +2428,25 @@ shada_read_next_item_start: .additional_elements = NULL }; if (unpacked.data.via.array.size < 2) { - emsgn("Error while reading ShaDa file: " - "variable entry at position %" PRId64 " " + emsgu("Error while reading ShaDa file: " + "variable entry at position %" PRIu64 " " "does not have enough elements", - (int64_t) initial_fpos); + (uint64_t) initial_fpos); goto shada_read_next_item_error; } if (unpacked.data.via.array.ptr[0].type != MSGPACK_OBJECT_BIN) { - emsgn("Error while reading ShaDa file: " - "variable entry at position %" PRId64 " " + emsgu("Error while reading ShaDa file: " + "variable entry at position %" PRIu64 " " "has wrong variable name type", - (int64_t) initial_fpos); + (uint64_t) 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) { - emsgn("Error while reading ShaDa file: " - "variable entry at position %" PRId64 " " + emsgu("Error while reading ShaDa file: " + "variable entry at position %" PRIu64 " " "has wrong variable value type", - (int64_t) initial_fpos); + (uint64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.global_var.name = @@ -2261,10 +2484,10 @@ shada_read_next_item_start: } case kSDItemSubString: { if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) { - emsgn("Error while reading ShaDa file: " - "sub string entry at position %" PRId64 " " + emsgu("Error while reading ShaDa file: " + "sub string entry at position %" PRIu64 " " "is not an array", - (int64_t) initial_fpos); + (uint64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.sub_string = (struct sub_string) { @@ -2272,17 +2495,17 @@ shada_read_next_item_start: .additional_elements = NULL }; if (unpacked.data.via.array.size < 1) { - emsgn("Error while reading ShaDa file: " - "sub string entry at position %" PRId64 " " + emsgu("Error while reading ShaDa file: " + "sub string entry at position %" PRIu64 " " "does not have enough elements", - (int64_t) initial_fpos); + (uint64_t) initial_fpos); goto shada_read_next_item_error; } if (unpacked.data.via.array.ptr[0].type != MSGPACK_OBJECT_BIN) { - emsgn("Error while reading ShaDa file: " - "sub string entry at position %" PRId64 " " + emsgu("Error while reading ShaDa file: " + "sub string entry at position %" PRIu64 " " "has wrong sub string type", - (int64_t) initial_fpos); + (uint64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.sub_string.sub = @@ -2312,10 +2535,10 @@ shada_read_next_item_start: } case kSDItemBufferList: { if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) { - emsgn("Error while reading ShaDa file: " - "buffer list entry at position %" PRId64 " " + emsgu("Error while reading ShaDa file: " + "buffer list entry at position %" PRIu64 " " "is not an array", - (int64_t) initial_fpos); + (uint64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.buffer_list = (struct buffer_list) { @@ -2336,10 +2559,10 @@ shada_read_next_item_start: { msgpack_unpacked unpacked = unpacked_2; if (unpacked.data.type != MSGPACK_OBJECT_MAP) { - emsgn("Error while reading ShaDa file: " - "buffer list at position %" PRId64 " " + emsgu("Error while reading ShaDa file: " + "buffer list at position %" PRIu64 " " "contains entry that is not a dictionary", - (int64_t) initial_fpos); + (uint64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.buffer_list.buffers[i].pos.lnum = 1; @@ -2361,10 +2584,10 @@ shada_read_next_item_start: } } if (entry->data.buffer_list.buffers[i].fname == NULL) { - emsgn("Error while reading ShaDa file: " - "buffer list at position %" PRId64 " " + emsgu("Error while reading ShaDa file: " + "buffer list at position %" PRIu64 " " "contains entry that does not have a file name", - (int64_t) initial_fpos); + (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; } -- cgit From b7ebd16af099defc76264d3e525c83d799ad875c Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 5 Jul 2015 14:08:50 +0300 Subject: documentation: Update documentation --- runtime/doc/autocmd.txt | 8 +-- runtime/doc/cmdline.txt | 2 +- runtime/doc/eval.txt | 14 ++-- runtime/doc/index.txt | 6 +- runtime/doc/mbyte.txt | 2 +- runtime/doc/motion.txt | 10 +-- runtime/doc/options.txt | 69 +++++++++--------- runtime/doc/quickref.txt | 12 ++-- runtime/doc/starting.txt | 177 +++++++++++++++++++++++------------------------ runtime/doc/usr_21.txt | 38 +++++----- runtime/doc/usr_45.txt | 2 +- runtime/doc/usr_toc.txt | 2 +- runtime/doc/various.txt | 2 +- runtime/doc/vim_diff.txt | 11 ++- runtime/doc/windows.txt | 2 +- 15 files changed, 183 insertions(+), 174 deletions(-) diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index af9676272f..f63c319c2f 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -265,8 +265,8 @@ Name triggered by ~ |TermResponse| after the terminal response to |t_RV| is received |QuitPre| when using `:quit`, before deciding whether to quit -|VimLeavePre| before exiting Vim, before writing the viminfo file -|VimLeave| before exiting Vim, after writing the viminfo file +|VimLeavePre| before exiting Vim, before writing the shada file +|VimLeave| before exiting Vim, after writing the shada file Various |FileChangedShell| Vim notices that a file changed since editing started @@ -912,14 +912,14 @@ VimEnter After doing all the startup stuff, including the buffers in them. *VimLeave* VimLeave Before exiting Vim, just after writing the - .viminfo file. Executed only once, like + .shada file. Executed only once, like VimLeavePre. To detect an abnormal exit use |v:dying|. When v:dying is 2 or more this event is not triggered. *VimLeavePre* VimLeavePre Before exiting Vim, just before writing the - .viminfo file. This is executed only once, + .shada file. This is executed only once, if there is a match with the name of what happens to be the current buffer when exiting. Mostly useful with a "*" pattern. > diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt index b208bba9a8..627a7000ec 100644 --- a/runtime/doc/cmdline.txt +++ b/runtime/doc/cmdline.txt @@ -729,7 +729,7 @@ function expand() |expand()|. # 0) is replaced with old *:_#<* *c_#<* file name n. See |:oldfiles| or |v:oldfiles| to get the number. *E809* - {only when compiled with the |+eval| and |+viminfo| features} + {only when compiled with the |+eval| and |+shada| features} Note that these, except "# set charconvert=CharConvert() fun CharConvert() @@ -2165,7 +2165,7 @@ A jump table for the options with a short description can be found at |Q_op|. feature} Sets the character encoding used inside Vim. It applies to text in the buffers, registers, Strings in expressions, text stored in the - viminfo file, etc. It sets the kind of characters which Vim can work + shada file, etc. It sets the kind of characters which Vim can work with. See |encoding-names| for the possible values. 'encoding' cannot be changed after startup, because (1) it causes @@ -2197,7 +2197,7 @@ A jump table for the options with a short description can be found at |Q_op|. When "unicode", "ucs-2" or "ucs-4" is used, Vim internally uses utf-8. You don't notice this while editing, but it does matter for the - |viminfo-file|. And Vim expects the terminal to use utf-8 too. Thus + |shada-file|. And Vim expects the terminal to use utf-8 too. Thus setting 'encoding' to one of these values instead of utf-8 only has effect for encoding used for files when 'fileencoding' is empty. @@ -3409,7 +3409,7 @@ A jump table for the options with a short description can be found at |Q_op|. line below a closed fold. A match in a previous line which is not drawn may not continue in a newly drawn line. You can specify whether the highlight status is restored on startup - with the 'h' flag in 'viminfo' |viminfo-h|. + with the 'h' flag in 'viminfo' |shada-h|. *'history'* *'hi'* 'history' 'hi' number (Vim default: 10000, Vi default: 0) @@ -3795,7 +3795,8 @@ A jump table for the options with a short description can be found at |Q_op|. option. Careful: If you change this option, it might break expanding environment variables. E.g., when '/' is included and Vim tries to - expand "$HOME/.viminfo". Maybe you should change 'iskeyword' instead. + expand "$HOME/.nvim/shada/main.shada". Maybe you should change + 'iskeyword' instead. *'iskeyword'* *'isk'* 'iskeyword' 'isk' string (Vim default for @@ -6622,7 +6623,7 @@ A jump table for the options with a short description can be found at |Q_op|. global When bigger than zero, Vim will give messages about what it is doing. Currently, these messages are given: - >= 1 When the viminfo file is read or written. + >= 1 When the shada file is read or written. >= 2 When a file is ":source"'ed. >= 5 Every searched tags file and include file. >= 8 Files for which a group of autocommands is executed. @@ -6688,10 +6689,10 @@ A jump table for the options with a short description can be found at |Q_op|. others: '!,100,<50,s10,h Vi default: "") global - {not available when compiled without the |+viminfo| + {not available when compiled without the |+shada| feature} - When non-empty, the viminfo file is read upon startup and written - when exiting Vim (see |viminfo-file|). The string should be a comma + When non-empty, the shada file is read upon startup and written + when exiting Vim (see |shada-file|). The string should be a comma separated list of parameters, each consisting of a single character identifying the particular parameter, followed by a number or string which specifies the value of that parameter. If a particular @@ -6699,69 +6700,69 @@ A jump table for the options with a short description can be found at |Q_op|. parameter. The following is a list of the identifying characters and the effect of their value. CHAR VALUE ~ - *viminfo-!* + *shada-!* ! When included, save and restore global variables that start with an uppercase letter, and don't contain a lowercase letter. Thus "KEEPTHIS and "K_L_M" are stored, but "KeepThis" and "_K_L_M" are not. Nested List and Dict items may not be read back correctly, you end up with an empty item. - *viminfo-quote* + *shada-quote* " Maximum number of lines saved for each register. Old name of the '<' item, with the disadvantage that you need to put a backslash before the ", otherwise it will be recognized as the start of a comment! - *viminfo-%* + *shada-%* % When included, save and restore the buffer list. If Vim is started with a file name argument, the buffer list is not restored. If Vim is started without a file name argument, the - buffer list is restored from the viminfo file. Buffers + buffer list is restored from the shada file. Buffers without a file name and buffers for help files are not written - to the viminfo file. + to the shada file. When followed by a number, the number specifies the maximum number of buffers that are stored. Without a number all buffers are stored. - *viminfo-'* + *shada-'* ' Maximum number of previously edited files for which the marks are remembered. This parameter must always be included when 'viminfo' is non-empty. Including this item also means that the |jumplist| and the - |changelist| are stored in the viminfo file. - *viminfo-/* + |changelist| are stored in the shada file. + *shada-/* / Maximum number of items in the search pattern history to be saved. If non-zero, then the previous search and substitute patterns are also saved. When not included, the value of 'history' is used. - *viminfo-:* + *shada-:* : Maximum number of items in the command-line history to be saved. When not included, the value of 'history' is used. - *viminfo-<* + *shada-<* < Maximum number of lines saved for each register. If zero then registers are not saved. When not included, all lines are saved. '"' is the old name for this item. Also see the 's' item below: limit specified in Kbyte. - *viminfo-@* + *shada-@* @ Maximum number of items in the input-line history to be saved. When not included, the value of 'history' is used. - *viminfo-c* - c When included, convert the text in the viminfo file from the - 'encoding' used when writing the file to the current - 'encoding'. See |viminfo-encoding|. - *viminfo-f* + *shada-c* + c Dumb option, kept for compatibility reasons. Has no actual + effect. Current encoding state is described in + |shada-encoding|. + *shada-f* f Whether file marks need to be stored. If zero, file marks ('0 to '9, 'A to 'Z) are not stored. When not present or when non-zero, they are all stored. '0 is used for the current cursor position (when exiting or when doing ":wviminfo"). - *viminfo-h* - h Disable the effect of 'hlsearch' when loading the viminfo + *shada-h* + h Disable the effect of 'hlsearch' when loading the shada file. When not included, it depends on whether ":nohlsearch" has been used since the last search command. - *viminfo-n* - n Name of the viminfo file. The name must immediately follow + *shada-n* + n Name of the shada file. The name must immediately follow the 'n'. Must be the last one! If the "-i" argument was given when starting Vim, that file name overrides the one given here with 'viminfo'. Environment variables are expanded when opening the file, not when setting the option. - *viminfo-r* + *shada-r* r Removable media. The argument is a string (up to the next ','). This parameter can be given several times. Each specifies the start of a path for which no marks will be @@ -6769,7 +6770,7 @@ A jump table for the options with a short description can be found at |Q_op|. could use "ra:,rb:". You can also use it for temp files, e.g., for Unix: "r/tmp". Case is ignored. Maximum length of each 'r' argument is 50 characters. - *viminfo-s* + *shada-s* s Maximum size of an item contents in KiB. If zero then nothing is saved. Unlike Vim this applies to all items, except for the buffer list and header. Full item size is off by three @@ -6780,7 +6781,7 @@ A jump table for the options with a short description can be found at |Q_op|. contents size) = 10253 bytes. Example: > - :set viminfo='50,<1000,s100,:0,n~/vim/viminfo + :set viminfo='50,<1000,s100,:0,n~/nvim/shada < '50 Marks will be remembered for the last 50 files you edited. @@ -6789,7 +6790,7 @@ A jump table for the options with a short description can be found at |Q_op|. s100 Items with contents occupying more then 100 KiB are skipped. :0 Command-line history will not be saved. - n~/vim/viminfo The name of the file to use is "~/vim/viminfo". + n~/nvim/shada The name of the file to use is "~/nvim/shada". no / Since '/' is not specified, the default will be used, that is, save all of the search history, and also the previous search and substitute patterns. diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt index c42d98ba90..503ab07079 100644 --- a/runtime/doc/quickref.txt +++ b/runtime/doc/quickref.txt @@ -911,7 +911,7 @@ Short explanation of each option: *option-list* 'verbosefile' 'vfile' file to write messages in 'viewdir' 'vdir' directory where to store files with :mkview 'viewoptions' 'vop' specifies what to save for :mkview -'viminfo' 'vi' use .viminfo file upon startup and exiting +'viminfo' 'vi' use .shada file upon startup and exiting 'virtualedit' 've' when to use virtual editing 'visualbell' 'vb' use visual bell instead of beeping 'warn' warn for shell command when buffer was changed @@ -1131,7 +1131,7 @@ Context-sensitive completion on the command-line: |-w| -w {scriptout} write typed chars to file {scriptout} (append) |-W| -W {scriptout} write typed chars to file {scriptout} (overwrite) |-u| -u {vimrc} read inits from {vimrc} instead of other inits -|-i| -i {viminfo} read info from {viminfo} instead of other files +|-i| -i {shada} read info from {shada} instead of other files |---| -- end of options, other arguments are file names |--help| --help show list of arguments and exit |--version| --version show version info and exit @@ -1215,12 +1215,12 @@ Context-sensitive completion on the command-line: ------------------------------------------------------------------------------ *Q_ac* Automatic Commands -|viminfo-file| read registers, marks, history at startup, save when exiting. +|shada-file| read registers, marks, history at startup, save when exiting. -|:rviminfo| :rv[iminfo] [file] read info from viminfo file [file] +|:rviminfo| :rv[iminfo] [file] read info from ShaDa file [file] |:rviminfo| :rv[iminfo]! [file] idem, overwrite existing info -|:wviminfo| :wv[iminfo] [file] add info to viminfo file [file] -|:wviminfo| :wv[iminfo]! [file] write info to viminfo file [file] +|:wviminfo| :wv[iminfo] [file] add info to ShaDa file [file] +|:wviminfo| :wv[iminfo]! [file] write info to ShaDa file [file] |modeline| Automatic option setting when editing a file diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index aa37913779..83351c1002 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -12,7 +12,7 @@ Starting Vim *starting* 4. Suspending |suspend| 5. Saving settings |save-settings| 6. Views and Sessions |views-sessions| -7. The viminfo file |viminfo-file| +7. The ShaDa file |shada-file| ============================================================================== 1. Vim arguments *vim-arguments* @@ -128,7 +128,7 @@ argument. "pat" in the first file being edited (see |pattern| for the available search patterns). The search starts at the cursor position, which can be the first line or the cursor position - last used from |viminfo|. To force a search from the first + last used from |shada|. To force a search from the first line use "+1 +/pat". +{command} *-+c* *-c* @@ -251,7 +251,7 @@ argument. *-V* *verbose* -V[N] Verbose. Sets the 'verbose' option to [N] (default: 10). Messages will be given for each file that is ":source"d and - for reading or writing a viminfo file. Can be used to find + for reading or writing a ShaDa file. Can be used to find out what is happening upon startup and exit. Example: > vim -V8 foobar @@ -322,10 +322,10 @@ argument. same effect as "NONE", but loading plugins is not skipped. *-i* --i {viminfo} The file "viminfo" is used instead of the default viminfo - file. If the name "NONE" is used (all uppercase), no viminfo +-i {shada} The file {shada} is used instead of the default ShaDa + file. If the name "NONE" is used (all uppercase), no ShaDa file is read or written, even if 'viminfo' is set or when - ":rv" or ":wv" are used. See also |viminfo-file|. + ":rv" or ":wv" are used. See also |shada-file|. *-s* -s {scriptin} The script file "scriptin" is read. The characters in the @@ -475,9 +475,9 @@ accordingly. Vim proceeds in this order: Only when starting "gvim", the GUI initializations will be done. See |gui-init|. -9. Read the viminfo file +9. Read the ShaDa file If the 'viminfo' option is not empty, the viminfo file is read. See - |viminfo-file|. + |shada-file|. 10. Read the quickfix file If the "-q" flag was given to Vim, the quickfix file is read. If this @@ -564,10 +564,10 @@ just like executing a command from a vimrc/exrc in the current directory. If Vim takes a long time to start up, use the |--startuptime| argument to find out what happens. -If you have "viminfo" enabled, the loading of the viminfo file may take a +If you have "viminfo" enabled, the loading of the ShaDa file may take a while. You can find out if this is the problem by disabling viminfo for a moment (use the Vim argument "-i NONE", |-i|). Try reducing the number of -lines stored in a register with ":set viminfo='20,<50,s10". |viminfo-file|. +lines stored in a register with ":set viminfo='20,<50,s10". |shada-file|. *:intro* When Vim starts without a file name, an introductory message is displayed (for @@ -768,8 +768,8 @@ save a Session and when you restore it later the window layout looks the same. You can use a Session to quickly switch between different projects, automatically loading the files you were last working on in that project. -Views and Sessions are a nice addition to viminfo-files, which are used to -remember information for all Views and Sessions together |viminfo-file|. +Views and Sessions are a nice addition to ShaDa files, which are used to +remember information for all Views and Sessions together |shada-file|. You can quickly start editing with a previously saved View or Session with the |-S| argument: > @@ -865,7 +865,7 @@ The output of ":mkview" contains these items: Note that Views and Sessions are not perfect: - They don't restore everything. For example, defined functions, autocommands and ":syntax on" are not included. Things like register contents and - command line history are in viminfo, not in Sessions or Views. + command line history are in ShaDa, not in Sessions or Views. - Global option values are only set when they differ from the default value. When the current value is not the default value, loading a Session will not set it back to the default value. Local options will be set back to the @@ -896,15 +896,15 @@ To automatically save and restore views for *.c files: > au BufWinEnter *.c silent loadview ============================================================================== -8. The viminfo file *viminfo* *viminfo-file* *E136* +8. The ShaDa file *shada* *shada-file* *E136* *E575* *E576* *E577* If you exit Vim and later start it again, you would normally lose a lot of -information. The viminfo file can be used to remember that information, which +information. The ShaDa file can be used to remember that information, which enables you to continue where you left off. This is introduced in section |21.3| of the user manual. -The viminfo file is used to store: +The ShaDa file is used to store: - The command line history. - The search string history. - The input-line history. @@ -915,46 +915,43 @@ The viminfo file is used to store: - The buffer list. - Global variables. -The viminfo file is not supported when the |+viminfo| feature has been -disabled at compile time. - -You could also use a Session file. The difference is that the viminfo file +You could also use a Session file. The difference is that the ShaDa file does not depend on what you are working on. There normally is only one -viminfo file. Session files are used to save the state of a specific editing +ShaDa file. Session files are used to save the state of a specific editing Session. You could have several Session files, one for each project you are -working on. Viminfo and Session files together can be used to effectively +working on. ShaDa and Session files together can be used to effectively enter Vim and directly start working in your desired setup. |session-file| - *viminfo-read* + *shada-read* When Vim is started and the 'viminfo' option is non-empty, the contents of -the viminfo file are read and the info can be used in the appropriate places. +the ShaDa file are read and the info can be used in the appropriate places. The |v:oldfiles| variable is filled. The marks are not read in at startup (but file marks are). See |initialization| for how to set the 'viminfo' option upon startup. - *viminfo-write* -When Vim exits and 'viminfo' is non-empty, the info is stored in the viminfo + *shada-write* +When Vim exits and 'viminfo' is non-empty, the info is stored in the ShaDa file (it's actually merged with the existing one, if one exists). The 'viminfo' option is a string containing information about what info should be stored, and contains limits on how much should be stored (see 'viminfo'). Notes for Unix: -- The file protection for the viminfo file will be set to prevent other users +- The file protection for the ShaDa file will be set to prevent other users from being able to read it, because it may contain any text or commands that you have worked with. -- If you want to share the viminfo file with other users (e.g. when you "su" +- If you want to share the ShaDa file with other users (e.g. when you "su" to another user), you can make the file writable for the group or everybody. - Vim will preserve this when writing new viminfo files. Be careful, don't - allow just anybody to read and write your viminfo file! -- Vim will not overwrite a viminfo file that is not writable by the current + Vim will preserve this when writing new ShaDa files. Be careful, don't + allow just anybody to read and write your ShaDa file! +- Vim will not overwrite a ShaDa file that is not writable by the current "real" user. This helps for when you did "su" to become root, but your $HOME is still set to a normal user's home directory. Otherwise Vim would - create a viminfo file owned by root that nobody else can read. -- The viminfo file cannot be a symbolic link. This is to avoid security + create a ShaDa file owned by root that nobody else can read. +- The ShaDa file cannot be a symbolic link. This is to avoid security issues. Marks are stored for each file separately. When a file is read and 'viminfo' -is non-empty, the marks for that file are read from the viminfo file. NOTE: +is non-empty, the marks for that file are read from the ShaDa file. NOTE: The marks are only written when exiting Vim, which is fine because marks are remembered for all the files you have opened in the current editing session, unless ":bdel" is used. If you want to save the marks for a file that you are @@ -964,12 +961,12 @@ cursor position when the file was last exited. No marks are saved for files that start with any string given with the "r" flag in 'viminfo'. This can be used to avoid saving marks for files on removable media (for MS-DOS you would use "ra:,rb:". -The |v:oldfiles| variable is filled with the file names that the viminfo file +The |v:oldfiles| variable is filled with the file names that the ShaDa file has marks for. - *viminfo-file-marks* -Uppercase marks ('A to 'Z) are stored when writing the viminfo file. The -numbered marks ('0 to '9) are a bit special. When the viminfo file is written + *shada-file-marks* +Uppercase marks ('A to 'Z) are stored when writing the ShaDa file. The +numbered marks ('0 to '9) are a bit special. When the ShaDa file is written (when exiting or with the ":wviminfo" command), '0 is set to the current cursor position and file. The old '0 is moved to '1, '1 to '2, etc. This resembles what happens with the "1 to "9 delete registers. If the current @@ -992,87 +989,89 @@ Use the "r" flag in 'viminfo' to specify for which files no marks should be remembered. -VIMINFO FILE NAME *viminfo-file-name* +SHADA FILE NAME *shada-file-name* -- The default name of the viminfo file is "$HOME/.viminfo" for Unix, - "$HOME\_viminfo" for MS-DOS and Win32. For the last two, when $HOME is not - set, "$VIM\_viminfo" is used. When $VIM is also not set, "c:\_viminfo" is - used. -- The 'n' flag in the 'viminfo' option can be used to specify another viminfo +- The default name of the ShaDa file is "$HOME/.nvim/shada/main.shada" for + Unix, "$HOME\_nvim\shada\main.shada" for MS-DOS and Win32. For the last + two, when $HOME is not set, "$VIM\_nvim\shada\main.shada" is used. When + $VIM is also not set, "c:\_nvim\shada\main.shada" is used. +- The 'n' flag in the 'viminfo' option can be used to specify another ShaDa file name |'viminfo'|. - The "-i" Vim argument can be used to set another file name, |-i|. When the - file name given is "NONE" (all uppercase), no viminfo file is ever read or + file name given is "NONE" (all uppercase), no ShaDa file is ever read or written. Also not for the commands below! - For the commands below, another file name can be given, overriding the default and the name given with 'viminfo' or "-i" (unless it's NONE). -CHARACTER ENCODING *viminfo-encoding* +CHARACTER ENCODING *shada-encoding* + +The text in the ShaDa file is UTF-8-encoded. Normally you will always work +with the same 'encoding' value, and this works just fine. However, if you +read the ShaDa file with value for 'encoding' different from utf-8 and +'encoding' used when writing ShaDa file, some of the text (non-ASCII +characters) may be invalid as NeoVim always attempts to convert the text in +the ShaDa file from the UTF-8 to the current 'encoding' value. Filenames are +never converted, affected elements are: -The text in the viminfo file is encoded as specified with the 'encoding' -option. Normally you will always work with the same 'encoding' value, and -this works just fine. However, if you read the viminfo file with another -value for 'encoding' than what it was written with, some of the text -(non-ASCII characters) may be invalid. If this is unacceptable, add the 'c' -flag to the 'viminfo' option: > - :set viminfo+=c -Vim will then attempt to convert the text in the viminfo file from the -'encoding' value it was written with to the current 'encoding' value. This -requires Vim to be compiled with the |+iconv| feature. Filenames are not -converted. +- history strings; +- variable values; +- register values; +- last used search and substitute patterns; +- last used substitute replacement string. -MANUALLY READING AND WRITING *viminfo-read-write* +MANUALLY READING AND WRITING *shada-read-write* -Two commands can be used to read and write the viminfo file manually. This +Two commands can be used to read and write the ShaDa file manually. This can be used to exchange registers between two running Vim programs: First type ":wv" in one and then ":rv" in the other. Note that if the register already contained something, then ":rv!" would be required. Also note however that this means everything will be overwritten with information from the first Vim, including the command line history, etc. -The viminfo file itself can be edited by hand too, although we suggest you -start with an existing one to get the format right. It is reasonably -self-explanatory once you're in there. This can be useful in order to -create a second file, say "~/.my_viminfo" which could contain certain -settings that you always want when you first start Vim. For example, you -can preload registers with particular data, or put certain commands in the -command line history. A line in your .vimrc file like > - :rviminfo! ~/.my_viminfo -can be used to load this information. You could even have different viminfos -for different types of files (e.g., C code) and load them based on the file -name, using the ":autocmd" command (see |:autocmd|). - - *viminfo-errors* -When Vim detects an error while reading a viminfo file, it will not overwrite -that file. If there are more than 10 errors, Vim stops reading the viminfo -file. This was done to avoid accidentally destroying a file when the file -name of the viminfo file is wrong. This could happen when accidentally typing -"vim -i file" when you wanted "vim -R file" (yes, somebody accidentally did -that!). If you want to overwrite a viminfo file with an error in it, you will -either have to fix the error, or delete the file (while Vim is running, so -most of the information will be restored). +The ShaDa file itself can be edited by hand too, although we suggest you +start with an existing one to get the format right. You need to understand +MessagePack (or, more likely, find software that is able to use it) format to +do this. This can be useful in order to create a second file, say +"~/.my.shada" which could contain certain settings that you always want when +you first start NeoVim. For example, you can preload registers with +particular data, or put certain commands in the command line history. A line +in your .nvimrc file like > + :rviminfo! ~/.my.shada +can be used to load this information. You could even have different ShaDa +files for different types of files (e.g., C code) and load them based on the +file name, using the ":autocmd" command (see |:autocmd|). + + *shada-errors* +When Vim detects an error while reading a ShaDa file, it will not overwrite +that file. This was done to avoid accidentally destroying a file when the +file name of the ShaDa file is wrong. This could happen when accidentally +typing "nvim -i file" when you wanted "nvim -R file" (yes, somebody +accidentally did that!). If you want to overwrite a ShaDa file with an error +in it, you will either have to fix the error, or delete the file (while NeoVim +is running, so most of the information will be restored). *:rv* *:rviminfo* *E195* -:rv[iminfo][!] [file] Read from viminfo file [file] (default: see above). +:rv[iminfo][!] [file] Read from ShaDa file [file] (default: see above). If [!] is given, then any information that is already set (registers, marks, |v:oldfiles|, etc.) will be overwritten. *:wv* *:wviminfo* *E137* *E138* *E574* *E886* -:wv[iminfo][!] [file] Write to viminfo file [file] (default: see above). +:wv[iminfo][!] [file] Write to ShaDa file [file] (default: see above). The information in the file is first read in to make a merge between old and new info. When [!] is used, the old information is not read first, only the - internal info is written. If 'viminfo' is empty, marks - for up to 100 files will be written. - When you get error "E138: Can't write viminfo file" - check that no old temp files were left behind (e.g. - ~/.viminf*) and that you can write in the directory of - the .viminfo file. + internal info is written (also disables safety checks + described in |shada-errors|). If 'viminfo' is empty, + marks for up to 100 files will be written. + When you get error "E138: All .tmp.X files exist, + cannot write ShaDa file!" check that no old temp files + were left behind (e.g. ~/.nvim/shada/main.shada.tmp*). *:ol* *:oldfiles* -:ol[dfiles] List the files that have marks stored in the viminfo +:ol[dfiles] List the files that have marks stored in the ShaDa file. This list is read on startup and only changes afterwards with ":rviminfo!". Also see |v:oldfiles|. The number can be used with |c_#<|. diff --git a/runtime/doc/usr_21.txt b/runtime/doc/usr_21.txt index 2ce23f0abf..d2d67339ed 100644 --- a/runtime/doc/usr_21.txt +++ b/runtime/doc/usr_21.txt @@ -12,7 +12,7 @@ it later. |21.1| Suspend and resume |21.2| Executing shell commands -|21.3| Remembering information; viminfo +|21.3| Remembering information; ShaDa |21.4| Sessions |21.5| Views |21.6| Modelines @@ -78,13 +78,13 @@ This is similar to using CTRL-Z to suspend Vim. The difference is that a new shell is started. ============================================================================== -*21.3* Remembering information; viminfo +*21.3* Remembering information; ShaDa After editing for a while you will have text in registers, marks in various files, a command line history filled with carefully crafted commands. When you exit Vim all of this is lost. But you can get it back! -The viminfo file is designed to store status information: +The ShaDa file is designed to store status information: Command-line and Search pattern history Text in registers @@ -92,8 +92,8 @@ The viminfo file is designed to store status information: The buffer list Global variables -Each time you exit Vim it will store this information in a file, the viminfo -file. When Vim starts again, the viminfo file is read and the information +Each time you exit Vim it will store this information in a file, the ShaDa +file. When Vim starts again, the ShaDa file is read and the information restored. The 'viminfo' option is set by default to restore a limited number of items. @@ -120,7 +120,7 @@ the marks are stored. You want this feature, so now you have this: > The < option controls how many lines are saved for each of the registers. By default, all the lines are saved. If 0, nothing is saved. To avoid adding -thousands of lines to your viminfo file (which might never get used and makes +thousands of lines to your ShaDa file (which might never get used and makes starting Vim slower) you use a maximum of 500 lines: > :set viminfo='1000,f1,<500 @@ -137,9 +137,9 @@ Other options you might want to use: % the buffer list (only restored when starting Vim without file arguments) c convert the text using 'encoding' - n name used for the viminfo file (must be the last option) + n name used for the ShaDa file (must be the last option) -See the 'viminfo' option and |viminfo-file| for more information. +See the 'viminfo' option and |shada-file| for more information. When you run Vim multiple times, the last one exiting will store its information. This may cause information that previously exiting Vims stored @@ -209,21 +209,21 @@ You can use the ":wviminfo" and ":rviminfo" commands to save and restore the information while still running Vim. This is useful for exchanging register contents between two instances of Vim, for example. In the first Vim do: > - :wviminfo! ~/tmp/viminfo + :wviminfo! ~/tmp/shada And in the second Vim do: > - :rviminfo! ~/tmp/viminfo + :rviminfo! ~/tmp/shada Obviously, the "w" stands for "write" and the "r" for "read". The ! character is used by ":wviminfo" to forcefully overwrite an existing file. When it is omitted, and the file exists, the information is merged into the file. - The ! character used for ":rviminfo" means that all the information is -used, this may overwrite existing information. Without the ! only information -that wasn't set is used. + The ! character used for ":rviminfo" means that all the information in +ShaDa file has priority over existing information, this may overwrite it. +Without the ! only information that wasn't set is used. These commands can also be used to store info and use it again later. You -could make a directory full of viminfo files, each containing info for a +could make a directory full of ShaDa files, each containing info for a different purpose. ============================================================================== @@ -355,12 +355,12 @@ Similarly, MS-Windows Vim understands file names with / to separate names, but Unix Vim doesn't understand \. -SESSIONS AND VIMINFO +SESSIONS AND SHADA Sessions store many things, but not the position of marks, contents of -registers and the command line history. You need to use the viminfo feature +registers and the command line history. You need to use the shada feature for these things. - In most situations you will want to use sessions separately from viminfo. + In most situations you will want to use sessions separately from shada. This can be used to switch to another session, but keep the command line history. And yank text into registers in one session, and paste it back in another session. @@ -368,12 +368,12 @@ another session. this yourself then. Example: > :mksession! ~/.vim/secret.vim - :wviminfo! ~/.vim/secret.viminfo + :wviminfo! ~/.vim/secret.shada And to restore this again: > :source ~/.vim/secret.vim - :rviminfo! ~/.vim/secret.viminfo + :rviminfo! ~/.vim/secret.shada ============================================================================== *21.5* Views diff --git a/runtime/doc/usr_45.txt b/runtime/doc/usr_45.txt index ac697e4bd5..808a9fc725 100644 --- a/runtime/doc/usr_45.txt +++ b/runtime/doc/usr_45.txt @@ -235,7 +235,7 @@ actually view a file that way, if you have lots of time at hand. Note: Since 'encoding' is used for all text inside Vim, changing it makes all non-ASCII text invalid. You will notice this when using registers - and the 'viminfo' file (e.g., a remembered search pattern). It's + and the |shada-file| (e.g., a remembered search pattern). It's recommended to set 'encoding' in your vimrc file, and leave it alone. ============================================================================== diff --git a/runtime/doc/usr_toc.txt b/runtime/doc/usr_toc.txt index be9f0f1a77..77ea462a23 100644 --- a/runtime/doc/usr_toc.txt +++ b/runtime/doc/usr_toc.txt @@ -181,7 +181,7 @@ Subjects that can be read independently. |usr_21.txt| Go away and come back |21.1| Suspend and resume |21.2| Executing shell commands - |21.3| Remembering information; viminfo + |21.3| Remembering information; ShaDa |21.4| Sessions |21.5| Views |21.6| Modelines diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt index 3cc0e7c8ab..4c44d45436 100644 --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -370,6 +370,7 @@ N *+reltime* |reltime()| function, 'hlsearch'/'incsearch' timeout, 'redrawtime' option B *+rightleft* Right to left typing |'rightleft'| N *+scrollbind* |'scrollbind'| +N *+shada* |'viminfo'| B *+signs* |:sign| N *+smartindent* |'smartindent'| N *+startuptime* |--startuptime| argument @@ -387,7 +388,6 @@ N *+textobjects* |text-objects| selection N *+title* Setting the window 'title' and 'icon' N *+toolbar* |gui-toolbar| N *+user_commands* User-defined commands. |user-commands| -N *+viminfo* |'viminfo'| N *+vertsplit* Vertically split windows |:vsplit| N *+virtualedit* |'virtualedit'| S *+visual* Visual mode |Visual-mode| Always enabled since 7.4.200. diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index de061d3828..5e4182fdae 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -23,7 +23,8 @@ these differences. - Use `.nvimrc` instead of `.vimrc` for storing configuration. - Use `.nvim` instead of `.vim` to store configuration files. -- Use `.nviminfo` instead of `.viminfo` for persistent session information. +- Use `.nvim/shada/main.shada` instead of `.viminfo` for persistent session + information. ============================================================================== 2. Option defaults *nvim-option-defaults* @@ -93,6 +94,14 @@ are always available and may be used simultaneously in separate plugins. The "{E724@level}"), but this is not reliable because |string()| continues to error out. +Viminfo text files were replaced with binary (messagepack) ShaDa files. +Additional differences: + +- |shada-c| has no effect. +- |shada-s| now limits size of every item and not just registers. +- When reading ShaDa files history, jump list and change list items are merged + with those in currently running NeoVim instance according to the timestamp. + ============================================================================== 4. New Features *nvim-features-new* diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt index b649e8a319..95be125c33 100644 --- a/runtime/doc/windows.txt +++ b/runtime/doc/windows.txt @@ -53,7 +53,7 @@ hidden: The buffer is not displayed. If there is a file for this buffer, it *inactive-buffer* inactive: The buffer is not displayed and does not contain anything. Options for the buffer are remembered if the file was once loaded. It can - contain marks from the |viminfo| file. But the buffer doesn't + contain marks from the |shada| file. But the buffer doesn't contain text. In a table: -- cgit From ee282cf49ea1a61c92626679a1a7ab636b45a6b7 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 5 Jul 2015 16:02:04 +0300 Subject: shada: Add support for converting strings that are read or written --- src/nvim/shada.c | 324 ++++++++++++++++++++++++++++++++++++++++++----------- src/nvim/strings.c | 15 +++ 2 files changed, 274 insertions(+), 65 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 355bb5d7e9..053257e746 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -44,7 +44,9 @@ #include "nvim/version.h" #include "nvim/path.h" #include "nvim/lib/ringbuf.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 @@ -67,6 +69,9 @@ KHASH_MAP_INIT_STR(fnamebufs, buf_T *) #define emsgu(a, ...) emsgu((char_u *) a, __VA_ARGS__) #define home_replace_save(a, b) \ ((char *)home_replace_save(a, (char_u *)b)) +#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, ...) \ @@ -261,6 +266,8 @@ typedef struct sd_read_def { 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; @@ -276,6 +283,8 @@ typedef struct sd_write_def { ShaDaFileWriter write; ///< Writer function. void *cookie; ///< Writer function last argument. 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 @@ -522,6 +531,8 @@ int shada_read_file(const char *const file, const int flags) return of_ret; } + convert_setup(&sd_reader.sd_conv, "utf-8", p_enc); + shada_read(&sd_reader, flags); close_file((int)(intptr_t) sd_reader.cookie); @@ -1376,8 +1387,8 @@ static void shada_write(ShaDaWriteDef *const sd_writer, .timestamp = os_time(), .data = { .header = { - .size = 3, - .capacity = 3, + .size = 4, + .capacity = 4, .items = ((KeyValuePair []) { { STATIC_CSTR_AS_STRING("version"), STRING_OBJ(cstr_as_string(longVersion)) }, @@ -1385,6 +1396,8 @@ static void shada_write(ShaDaWriteDef *const sd_writer, 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)) }, }), } } @@ -1461,6 +1474,22 @@ static void shada_write(ShaDaWriteDef *const sd_writer, // FIXME No merging currently +#define RUN_WITH_CONVERTED_STRING(cstr, code) \ + do { \ + bool did_convert = false; \ + if (sd_writer->sd_conv.vc_type != CONV_NONE && has_non_ascii((cstr))) { \ + char *const converted_string = string_convert(&sd_writer->sd_conv, \ + (cstr), NULL); \ + if (converted_string != NULL) { \ + (cstr) = converted_string; \ + did_convert = true; \ + } \ + } \ + code \ + if (did_convert) { \ + xfree((cstr)); \ + } \ + } while (0) // 4. History HistoryMergerState hms[HIST_COUNT]; for (uint8_t i = 0; i < HIST_COUNT; i++) { @@ -1487,64 +1516,82 @@ static void shada_write(ShaDaWriteDef *const sd_writer, } } RINGBUF_FORALL(&(hms_p->hmrb), ShadaEntry, cur_entry) { - shada_pack_entry(packer, *cur_entry, max_kbyte); + RUN_WITH_CONVERTED_STRING(cur_entry->data.history_item.string, { + shada_pack_entry(packer, *cur_entry, max_kbyte); + }); } hm_rb_dealloc(&hms_p->hmrb); } } // 5. Search patterns - SearchPattern pat; - get_search_pattern(&pat); - shada_pack_entry(packer, (ShadaEntry) { - .type = kSDItemSearchPattern, - .timestamp = pat.timestamp, - .data = { - .search_pattern = { - .magic = pat.magic, - .smartcase = !pat.no_scs, - .has_line_offset = pat.off.line, - .place_cursor_at_end = pat.off.end, - .offset = pat.off.off, - .is_last_used = search_was_last_used(), - .is_substitute_pattern = false, - .pat = (char *) pat.pat, - .additional_data = pat.additional_data, + { + SearchPattern pat; + get_search_pattern(&pat); + ShadaEntry sp_entry = (ShadaEntry) { + .type = kSDItemSearchPattern, + .timestamp = pat.timestamp, + .data = { + .search_pattern = { + .magic = pat.magic, + .smartcase = !pat.no_scs, + .has_line_offset = pat.off.line, + .place_cursor_at_end = pat.off.end, + .offset = pat.off.off, + .is_last_used = search_was_last_used(), + .is_substitute_pattern = false, + .pat = (char *) pat.pat, + .additional_data = pat.additional_data, + } } - } - }, max_kbyte); - get_substitute_pattern(&pat); - shada_pack_entry(packer, (ShadaEntry) { - .type = kSDItemSearchPattern, - .timestamp = pat.timestamp, - .data = { - .search_pattern = { - .magic = pat.magic, - .smartcase = !pat.no_scs, - .has_line_offset = false, - .place_cursor_at_end = false, - .offset = 0, - .is_last_used = !search_was_last_used(), - .is_substitute_pattern = true, - .pat = (char *) pat.pat, - .additional_data = pat.additional_data, + }; + RUN_WITH_CONVERTED_STRING(sp_entry.data.search_pattern.pat, { + shada_pack_entry(packer, sp_entry, max_kbyte); + }); + } + { + SearchPattern pat; + get_substitute_pattern(&pat); + ShadaEntry sp_entry = (ShadaEntry) { + .type = kSDItemSearchPattern, + .timestamp = pat.timestamp, + .data = { + .search_pattern = { + .magic = pat.magic, + .smartcase = !pat.no_scs, + .has_line_offset = false, + .place_cursor_at_end = false, + .offset = 0, + .is_last_used = !search_was_last_used(), + .is_substitute_pattern = true, + .pat = (char *) pat.pat, + .additional_data = pat.additional_data, + } } - } - }, max_kbyte); + }; + RUN_WITH_CONVERTED_STRING(sp_entry.data.search_pattern.pat, { + shada_pack_entry(packer, sp_entry, max_kbyte); + }); + } // 6. Substitute string - SubReplacementString sub; - sub_get_replacement(&sub); - shada_pack_entry(packer, (ShadaEntry) { - .type = kSDItemSubString, - .timestamp = sub.timestamp, - .data = { - .sub_string = { - .sub = (char *) sub.sub, - .additional_elements = sub.additional_elements, + { + SubReplacementString sub; + sub_get_replacement(&sub); + ShadaEntry sub_entry = (ShadaEntry) { + .type = kSDItemSubString, + .timestamp = sub.timestamp, + .data = { + .sub_string = { + .sub = (char *) sub.sub, + .additional_elements = sub.additional_elements, + } } - } - }, max_kbyte); + }; + RUN_WITH_CONVERTED_STRING(sub_entry.data.sub_string.sub, { + shada_pack_entry(packer, sub_entry, max_kbyte); + }); + } // 7. Global marks if (get_viminfo_parameter('f') != 0) { @@ -1597,7 +1644,25 @@ static void shada_write(ShaDaWriteDef *const sd_writer, : (size_t) max_num_lines_i); ShadaEntry *const registers = list_registers(max_num_lines); for (ShadaEntry *reg = registers; reg->type != kSDItemMissing; reg++) { + bool did_convert = false; + if (sd_writer->sd_conv.vc_type != CONV_NONE) { + did_convert = true; + reg->data.reg.contents = xmemdup(reg->data.reg.contents, + (reg->data.reg.contents_size + * sizeof(reg->data.reg.contents))); + for (size_t i = 0; i < reg->data.reg.contents_size; i++) { + reg->data.reg.contents[i] = get_converted_string( + &sd_writer->sd_conv, + reg->data.reg.contents[i], strlen(reg->data.reg.contents[i])); + } + } shada_pack_entry(packer, *reg, max_kbyte); + if (did_convert) { + for (size_t i = 0; i < reg->data.reg.contents_size; i++) { + xfree(reg->data.reg.contents[i]); + } + xfree(reg->data.reg.contents); + } } xfree(registers); } @@ -1614,6 +1679,9 @@ static void shada_write(ShaDaWriteDef *const sd_writer, break; } Object obj = vim_to_object(&vartv); + if (sd_writer->sd_conv.vc_type != CONV_NONE) { + convert_object(&sd_writer->sd_conv, &obj); + } shada_pack_entry(packer, (ShadaEntry) { .type = kSDItemVariable, .timestamp = cur_timestamp, @@ -1629,6 +1697,7 @@ static void shada_write(ShaDaWriteDef *const sd_writer, clear_tv(&vartv); } while (var_iter != NULL); } +#undef RUN_WITH_CONVERTED_STRING kh_destroy(bufset, removable_bufs); msgpack_packer_free(packer); @@ -1652,7 +1721,7 @@ int shada_write_file(const char *const file, const bool nomerge) }; const intptr_t fd = (intptr_t) open_file(fname, - O_CREAT|O_WRONLY|O_NOFOLLOW, + O_CREAT|O_WRONLY|O_NOFOLLOW|O_TRUNC, 0600); if (p_verbose > 0) { @@ -1668,6 +1737,8 @@ int shada_write_file(const char *const file, const bool nomerge) sd_writer.cookie = (void *) fd; + convert_setup(&sd_writer.sd_conv, p_enc, "utf-8"); + shada_write(&sd_writer, NULL); close_file((int)(intptr_t) sd_writer.cookie); @@ -1863,7 +1934,7 @@ static int msgpack_read_uint64(ShaDaReadDef *const sd_reader, if (first_char == EOF) { if (sd_reader->error) { - emsg2("System error while reading ShaDa file: %s", + emsg2("System error while reading integer from ShaDa file: %s", sd_reader->error); } else if (sd_reader->eof) { emsgu("Error while reading ShaDa file: " @@ -1913,6 +1984,99 @@ static int msgpack_read_uint64(ShaDaReadDef *const sd_reader, return OK; } +/// Convert all strings in one Object instance +/// +/// @param[in] sd_conv Conversion definition. +/// @param[in,out] obj Object to convert. +static void convert_object(const vimconv_T *const sd_conv, Object *const obj) + FUNC_ATTR_NONNULL_ALL +{ + kvec_t(Object *) toconv; + kv_init(toconv); + kv_push(Object *, toconv, obj); + while (kv_size(toconv)) { + Object *cur_obj = kv_pop(toconv); +#define CONVERT_STRING(str) \ + do { \ + if (!has_non_ascii((str).data)) { \ + break; \ + } \ + size_t len = (str).size; \ + char *const converted_string = string_convert(sd_conv, (str).data, \ + &len); \ + if (converted_string != NULL) { \ + xfree((str).data); \ + (str).data = converted_string; \ + (str).size = len; \ + } \ + } while (0) + switch (cur_obj->type) { + case kObjectTypeNil: + case kObjectTypeInteger: + case kObjectTypeBoolean: + case kObjectTypeFloat: { + break; + } + case kObjectTypeString: { + CONVERT_STRING(cur_obj->data.string); + break; + } + case kObjectTypeArray: { + for (size_t i = 0; i < cur_obj->data.array.size; i++) { + Object *element = &cur_obj->data.array.items[i]; + if (element->type == kObjectTypeDictionary + || element->type == kObjectTypeArray) { + kv_push(Object *, toconv, element); + } else if (element->type == kObjectTypeString) { + CONVERT_STRING(element->data.string); + } + } + break; + } + case kObjectTypeDictionary: { + for (size_t i = 0; i < cur_obj->data.dictionary.size; i++) { + CONVERT_STRING(cur_obj->data.dictionary.items[i].key); + Object *value = &cur_obj->data.dictionary.items[i].value; + if (value->type == kObjectTypeDictionary + || value->type == kObjectTypeArray) { + kv_push(Object *, toconv, value); + } else if (value->type == kObjectTypeString) { + CONVERT_STRING(value->data.string); + } + } + break; + } + default: { + assert(false); + } + } +#undef CONVERT_STRING + } + kv_destroy(toconv); +} + +/// 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; +} + /// Iterate over shada file contents /// /// @param[in] sd_reader Structure containing file reader definition. @@ -2053,6 +2217,8 @@ shada_read_next_item_start: 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, \ @@ -2074,6 +2240,11 @@ shada_read_next_item_start: 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) switch ((ShadaEntryType) type_u64) { case kSDItemHeader: { if (!msgpack_rpc_to_dictionary(&(unpacked.data), &(entry->data.header))) { @@ -2120,7 +2291,8 @@ shada_read_next_item_start: entry->data.search_pattern.is_substitute_pattern) else INTEGER_KEY("search pattern", "off", entry->data.search_pattern.offset) - else STRING_KEY("search pattern", "pat", entry->data.search_pattern.pat) + else CONVERTED_STRING_KEY("search pattern", "pat", + entry->data.search_pattern.pat) else ADDITIONAL_KEY } if (entry->data.search_pattern.pat == NULL) { @@ -2280,8 +2452,7 @@ shada_read_next_item_start: entry->data.reg.contents_size = arr.size; entry->data.reg.contents = xmalloc(arr.size * sizeof(char *)); for (size_t i = 0; i < arr.size; i++) { - entry->data.reg.contents[i] = xmemdupz(arr.ptr[i].via.bin.ptr, - arr.ptr[i].via.bin.size); + entry->data.reg.contents[i] = BIN_CONVERTED(arr.ptr[i].via.bin); } } else ADDITIONAL_KEY } @@ -2377,15 +2548,32 @@ shada_read_next_item_start: entry->data.history_item.sep = (char) unpacked.data.via.array.ptr[2].via.u64; } - const size_t 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); + 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; @@ -2460,6 +2648,9 @@ shada_read_next_item_start: (uint64_t) initial_fpos); goto shada_read_next_item_error; } + if (sd_reader->sd_conv.vc_type != CONV_NONE) { + convert_object(&sd_reader->sd_conv, &entry->data.global_var.value); + } if (unpacked.data.via.array.size > 2) { msgpack_object obj = { .type = MSGPACK_OBJECT_ARRAY, @@ -2509,8 +2700,7 @@ shada_read_next_item_start: goto shada_read_next_item_error; } entry->data.sub_string.sub = - xmemdupz(unpacked.data.via.array.ptr[0].via.bin.ptr, - unpacked.data.via.array.ptr[0].via.bin.size); + BIN_CONVERTED(unpacked.data.via.array.ptr[0].via.bin); if (unpacked.data.via.array.size > 1) { msgpack_object obj = { .type = MSGPACK_OBJECT_ARRAY, @@ -2632,8 +2822,12 @@ shada_read_next_item_start: } entry->type = (ShadaEntryType) type_u64; 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 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. */ -- cgit From c8c5da875c0b7d37c4aedc6b2301441fdc004ac4 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 5 Jul 2015 17:24:02 +0300 Subject: functests: Test &encoding support --- test/functional/shada/history_spec.lua | 155 +++++++++++++++++++++++++++++++ test/functional/shada/registers_spec.lua | 33 +++++++ test/functional/shada/variables_spec.lua | 55 +++++++++++ 3 files changed, 243 insertions(+) diff --git a/test/functional/shada/history_spec.lua b/test/functional/shada/history_spec.lua index 811afd268f..5a7db2bfa6 100644 --- a/test/functional/shada/history_spec.lua +++ b/test/functional/shada/history_spec.lua @@ -124,4 +124,159 @@ describe('ShaDa support code', function() nvim_command('&') eq('goo', nvim_eval('getline(1)')) end) + + it('dumps and loads history correctly when &encoding is not UTF-8', function() + set_additional_cmd('set encoding=latin1') + reset() + -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1 + nvim_feed(':echo "\171"\n') + nvim_command('qall') + reset() + eq('echo "\171"', nvim_eval('histget(":", -1)')) + end) + + it('dumps and loads history correctly when &encoding /= UTF-8 when dumping', + function() + set_additional_cmd('set encoding=latin1') + reset() + -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1 + nvim_feed(':echo "\171"\n') + set_additional_cmd('') + nvim_command('qall') + reset() + eq('echo "«"', nvim_eval('histget(":", -1)')) + end) + + it('dumps and loads history correctly when &encoding /= UTF-8 when loading', + function() + -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1 + nvim_feed(':echo "«"\n') + set_additional_cmd('set encoding=latin1') + nvim_command('qall') + reset() + eq('echo "\171"', nvim_eval('histget(":", -1)')) + end) + + it('dumps and loads replacement correctly when &encoding is not UTF-8', + function() + set_additional_cmd('set encoding=latin1') + reset() + -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1 + nvim_command('substitute/./\171/ge') + nvim_command('qall!') + reset() + nvim_eval('setline(".", ["."])') + nvim_command('&') + eq('\171', nvim_eval('getline(".")')) + end) + + it('dumps&loads replacement correctly when &encoding /= UTF-8 when dumping', + function() + set_additional_cmd('set encoding=latin1') + reset() + -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1 + nvim_command('substitute/./\171/ge') + set_additional_cmd('') + nvim_command('qall') + reset() + nvim_eval('setline(".", ["."])') + nvim_command('&') + eq('«', nvim_eval('getline(".")')) + end) + + it('dumps&loads replacement correctly when &encoding /= UTF-8 when loading', + function() + -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1 + nvim_command('substitute/./«/ge') + set_additional_cmd('set encoding=latin1') + nvim_command('qall') + reset() + nvim_eval('setline(".", ["."])') + nvim_command('&') + eq('\171', nvim_eval('getline(".")')) + end) + + it('dumps and loads substitute pattern correctly when &encoding is not UTF-8', + function() + set_additional_cmd('set encoding=latin1') + reset() + -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1 + nvim_command('substitute/\171/./ge') + nvim_command('qall!') + reset() + nvim_eval('setline(".", ["\171«"])') + nvim_command('&') + eq('.«', nvim_eval('getline(".")')) + end) + + it('dumps&loads s/pattern correctly when &encoding /= UTF-8 when dumping', + function() + set_additional_cmd('set encoding=latin1') + reset() + -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1 + nvim_command('substitute/\171/./ge') + set_additional_cmd('') + nvim_command('qall') + reset() + nvim_eval('setline(".", ["«\171"])') + nvim_command('&') + eq('.\171', nvim_eval('getline(".")')) + end) + + it('dumps&loads s/pattern correctly when &encoding /= UTF-8 when loading', + function() + -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1 + nvim_command('substitute/«/./ge') + set_additional_cmd('set encoding=latin1') + nvim_command('qall') + reset() + nvim_eval('setline(".", ["\171«"])') + nvim_command('&') + eq('.«', nvim_eval('getline(".")')) + end) + + it('dumps and loads search pattern correctly when &encoding is not UTF-8', + function() + set_additional_cmd('set encoding=latin1') + reset() + -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1 + nvim_command('silent! /\171/') + nvim_command('set viminfo+=/0') + nvim_command('qall!') + reset() + nvim_eval('setline(".", ["\171«"])') + nvim_command('~&') + eq('«', nvim_eval('getline(".")')) + eq('', nvim_eval('histget("/", -1)')) + end) + + it('dumps&loads /pattern correctly when &encoding /= UTF-8 when dumping', + function() + set_additional_cmd('set encoding=latin1') + reset() + -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1 + nvim_command('silent! /\171/') + nvim_command('set viminfo+=/0') + set_additional_cmd('') + nvim_command('qall') + reset() + nvim_eval('setline(".", ["«\171"])') + nvim_command('~&') + eq('\171', nvim_eval('getline(".")')) + eq('', nvim_eval('histget("/", -1)')) + end) + + it('dumps&loads /pattern correctly when &encoding /= UTF-8 when loading', + function() + -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1 + nvim_command('silent! /«/') + nvim_command('set viminfo+=/0') + set_additional_cmd('set encoding=latin1') + nvim_command('qall') + reset() + nvim_eval('setline(".", ["\171«"])') + nvim_command('~&') + eq('«', nvim_eval('getline(".")')) + eq('', nvim_eval('histget("/", -1)')) + end) end) diff --git a/test/functional/shada/registers_spec.lua b/test/functional/shada/registers_spec.lua index 75c86628a6..7efb100cd6 100644 --- a/test/functional/shada/registers_spec.lua +++ b/test/functional/shada/registers_spec.lua @@ -138,4 +138,37 @@ describe('ShaDa support code', function() eq({{'a', 'b', 'cde'}, 'V'}, getreg('t')) eq({nil, ''}, getreg('h')) end) + + it('dumps and loads register correctly when &encoding is not UTF-8', + function() + set_additional_cmd('set encoding=latin1') + reset() + -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1 + setreg('e', {'\171'}, 'c') + nvim_command('qall') + reset() + eq({{'\171'}, 'v'}, getreg('e')) + end) + + it('dumps and loads history correctly when &encoding /= UTF-8 when dumping', + function() + set_additional_cmd('set encoding=latin1') + reset() + -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1 + setreg('e', {'\171'}, 'c') + set_additional_cmd('') + nvim_command('qall') + reset() + eq({{'«'}, 'v'}, getreg('e')) + end) + + it('dumps and loads history correctly when &encoding /= UTF-8 when loading', + function() + -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1 + setreg('e', {'«'}, 'c') + set_additional_cmd('set encoding=latin1') + nvim_command('qall') + reset() + eq({{'\171'}, 'v'}, getreg('e')) + end) end) diff --git a/test/functional/shada/variables_spec.lua b/test/functional/shada/variables_spec.lua index c0b94b102e..0ffa524238 100644 --- a/test/functional/shada/variables_spec.lua +++ b/test/functional/shada/variables_spec.lua @@ -81,4 +81,59 @@ describe('ShaDa support code', function() nvim_command('rviminfo') eq(0, nvim_eval('exists("g:str_var")')) end) + + it('dumps and loads variables correctly when &encoding is not UTF-8', + function() + set_additional_cmd('set encoding=latin1') + reset() + -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1 + nvim('set_var', 'STRVAR', '\171') + nvim('set_var', 'LSTVAR', {'\171'}) + nvim('set_var', 'DCTVAR', {['\171']='\171'}) + nvim('set_var', 'NESTEDVAR', {['\171']={{'\171'}, {['\171']='\171'}, + {a='Test'}}}) + nvim_command('qall') + reset() + eq('\171', nvim('get_var', 'STRVAR')) + eq({'\171'}, nvim('get_var', 'LSTVAR')) + eq({['\171']='\171'}, nvim('get_var', 'DCTVAR')) + eq({['\171']={{'\171'}, {['\171']='\171'}, {a='Test'}}}, + nvim('get_var', 'NESTEDVAR')) + end) + + it('dumps and loads variables correctly when &encoding /= UTF-8 when dumping', + function() + set_additional_cmd('set encoding=latin1') + reset() + -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1 + nvim('set_var', 'STRVAR', '\171') + nvim('set_var', 'LSTVAR', {'\171'}) + nvim('set_var', 'DCTVAR', {['\171']='\171'}) + nvim('set_var', 'NESTEDVAR', {['\171']={{'\171'}, {['\171']='\171'}, + {a='Test'}}}) + set_additional_cmd('') + nvim_command('qall') + reset() + eq('«', nvim('get_var', 'STRVAR')) + eq({'«'}, nvim('get_var', 'LSTVAR')) + eq({['«']='«'}, nvim('get_var', 'DCTVAR')) + eq({['«']={{'«'}, {['«']='«'}, {a='Test'}}}, nvim('get_var', 'NESTEDVAR')) + end) + + it('dumps and loads variables correctly when &encoding /= UTF-8 when loading', + function() + -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1 + nvim('set_var', 'STRVAR', '«') + nvim('set_var', 'LSTVAR', {'«'}) + nvim('set_var', 'DCTVAR', {['«']='«'}) + nvim('set_var', 'NESTEDVAR', {['«']={{'«'}, {['«']='«'}, {a='Test'}}}) + set_additional_cmd('set encoding=latin1') + nvim_command('qall') + reset() + eq('\171', nvim('get_var', 'STRVAR')) + eq({'\171'}, nvim('get_var', 'LSTVAR')) + eq({['\171']='\171'}, nvim('get_var', 'DCTVAR')) + eq({['\171']={{'\171'}, {['\171']='\171'}, {a='Test'}}}, + nvim('get_var', 'NESTEDVAR')) + end) end) -- cgit From 55712dcdb43cf8ace896e955d41c22156cd79cc4 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 5 Jul 2015 18:20:06 +0300 Subject: shada: Test that history string does not contain zero byte This test can safely be ignored for other kinds of strings, but history string contains separator after the last NUL byte and string containing NUL byte inside thus means that separator will be placed at one position, but seeked at another. --- src/nvim/shada.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 053257e746..72933df9a4 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -2525,6 +2525,14 @@ shada_read_next_item_start: (uint64_t) 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("Error while reading ShaDa file: " + "history entry at position %" PRIu64 " " + "contains string with zero byte inside", + (uint64_t) 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 = -- cgit From 602efe856a20df22a69c2bfc5de211064248034c Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 6 Jul 2015 01:14:17 +0300 Subject: shada: Use “write then rename” variant by default for writing shada MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/nvim/shada.c | 86 +++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 73 insertions(+), 13 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 72933df9a4..8a383b08a6 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -44,6 +44,7 @@ #include "nvim/version.h" #include "nvim/path.h" #include "nvim/lib/ringbuf.h" +#include "nvim/fileio.h" #include "nvim/strings.h" #include "nvim/lib/khash.h" #include "nvim/lib/kvec.h" @@ -69,6 +70,8 @@ KHASH_MAP_INIT_STR(fnamebufs, buf_T *) #define emsgu(a, ...) emsgu((char_u *) a, __VA_ARGS__) #define home_replace_save(a, b) \ ((char *)home_replace_save(a, (char_u *)b)) +#define vim_rename(a, b) \ + (vim_rename((char_u *)a, (char_u *)b)) #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)) @@ -395,7 +398,7 @@ open_file_start: if (fd < 0) { if (-fd == ENOENT) { - return -1; + return fd; } if (-fd == ENOMEM && !did_try_to_free) { try_to_free_memory(); @@ -405,9 +408,11 @@ open_file_start: if (-fd == EINTR) { goto open_file_start; } - emsg3("System error while opening ShaDa file %s: %s", - fname, strerror(-fd)); - return -1; + if (-fd != EEXIST) { + emsg3("System error while opening ShaDa file %s: %s", + fname, strerror(-fd)); + } + return fd; } return fd; } @@ -424,7 +429,7 @@ static int open_shada_file_for_reading(const char *const fname, { const intptr_t fd = (intptr_t) open_file(fname, O_RDONLY, 0); - if (fd == -1) { + if (fd < 0) { return FAIL; } @@ -436,6 +441,8 @@ static int open_shada_file_for_reading(const char *const fname, .cookie = (void *) fd, }; + convert_setup(&sd_reader->sd_conv, "utf-8", p_enc); + return OK; } @@ -531,8 +538,6 @@ int shada_read_file(const char *const file, const int flags) return of_ret; } - convert_setup(&sd_reader.sd_conv, "utf-8", p_enc); - shada_read(&sd_reader, flags); close_file((int)(intptr_t) sd_reader.cookie); @@ -1712,17 +1717,58 @@ static void shada_write(ShaDaWriteDef *const sd_writer, /// @param[in] nomerge If true then old file is ignored. /// /// @return OK if writing was successfull, FAIL otherwise. -int shada_write_file(const char *const file, const bool nomerge) +int shada_write_file(const char *const file, bool nomerge) { char *const fname = shada_filename(file); + char *tempname = NULL; ShaDaWriteDef sd_writer = (ShaDaWriteDef) { .write = &write_file, .error = NULL, }; + ShaDaReadDef sd_reader; - const intptr_t fd = (intptr_t) open_file(fname, - O_CREAT|O_WRONLY|O_NOFOLLOW|O_TRUNC, - 0600); + intptr_t fd; + + if (!nomerge) { + if (open_shada_file_for_reading(fname, &sd_reader) != OK) { + nomerge = true; + goto shada_write_file_nomerge; + } + tempname = modname(fname, ".tmp.a", false); + if (tempname == NULL) { + nomerge = true; + goto shada_write_file_nomerge; + } + +shada_write_file_open: + fd = (intptr_t) open_file(tempname, O_CREAT|O_WRONLY|O_NOFOLLOW|O_EXCL, + 0600); + 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. + xfree(fname); + xfree(tempname); + return FAIL; + } else { + (*wp)++; + goto shada_write_file_open; + } + } + } + } + if (nomerge) { +shada_write_file_nomerge: + fd = (intptr_t) open_file(fname, O_CREAT|O_WRONLY|O_TRUNC, + 0600); + } if (p_verbose > 0) { verbose_enter(); @@ -1730,8 +1776,9 @@ int shada_write_file(const char *const file, const bool nomerge) verbose_leave(); } - xfree(fname); - if (fd == -1) { + if (fd < 0) { + xfree(fname); + xfree(tempname); return FAIL; } @@ -1742,6 +1789,19 @@ int shada_write_file(const char *const file, const bool nomerge) shada_write(&sd_writer, NULL); close_file((int)(intptr_t) sd_writer.cookie); + + if (!nomerge) { + close_file((int)(intptr_t) sd_reader.cookie); + if (vim_rename(tempname, fname) == -1) { + EMSG3(_("E886: Can't rename viminfo file from %s to %s!"), + tempname, fname); + } else { + os_remove(tempname); + } + xfree(tempname); + } + + xfree(fname); return OK; } -- cgit From 749cae866278469d11e6ec467dd0e98ab6233439 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 6 Jul 2015 01:20:43 +0300 Subject: functests: Fix change list ShaDa test failures --- test/functional/shada/marks_spec.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/functional/shada/marks_spec.lua b/test/functional/shada/marks_spec.lua index a6518ccbf9..25362b406b 100644 --- a/test/functional/shada/marks_spec.lua +++ b/test/functional/shada/marks_spec.lua @@ -171,9 +171,15 @@ describe('ShaDa support code', function() reset() nvim_command('edit ' .. testfilename) -- nvim_command('rviminfo') + -- nvim_command('redir! >/tmp/changes | changes | redir END') nvim_feed('Gg;') + -- Note: without “sync” “commands” test has good changes to fail for unknown + -- reason (in first eq expected 1 is compared with 2). Any command inserted + -- causes this to work properly. + nvim_command('" sync') eq(1, nvim_current_line()) nvim_feed('g;') + nvim_command('" sync 2') eq(2, nvim_current_line()) end) end) -- cgit From 9cf9c4a5860212cc0f9b71d349849811fdaf61a1 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 6 Jul 2015 02:16:05 +0300 Subject: Replace references to viminfo in various places --- man/nvim.1 | 14 +++---- runtime/doc/motion.txt | 2 +- runtime/doc/starting.txt | 6 +-- runtime/doc/usr_21.txt | 4 +- runtime/optwin.vim | 4 +- src/nvim/ex_docmd.c | 10 ++--- src/nvim/fileio.c | 2 +- src/nvim/main.c | 6 +-- src/nvim/ops.c | 2 +- src/nvim/option.c | 14 +++---- src/nvim/option_defs.h | 2 +- src/nvim/options.lua | 2 +- src/nvim/shada.c | 44 ++++++++++++---------- test/functional/ex_cmds/wviminfo_spec.lua | 33 ++++++++-------- .../legacy/074_global_var_in_viminfo_spec.lua | 22 ++++++----- 15 files changed, 89 insertions(+), 78 deletions(-) diff --git a/man/nvim.1 b/man/nvim.1 index 08c4a6db05..83544523e0 100644 --- a/man/nvim.1 +++ b/man/nvim.1 @@ -138,7 +138,7 @@ Sets the options 'hkmap' and 'rightleft'. .It Fl V Ns Oo Ar N Oc Ns Op Ar file Verbose mode. Print messages about which files are being sourced and for reading and -writing an nviminfo file. +writing a ShaDa file. .Ar N is the value for the 'verbose' option; defaults to .Cm 10 @@ -191,18 +191,18 @@ is loading plugins is also skipped. See .Ic :help initialization . -.It Fl i Ar nviminfo +.It Fl i Ar shada Use -.Ar nviminfo +.Ar shada instead of the default of -.Pa ~/.nviminfo . +.Pa ~/.nvim/shada/main.shada . If -.Ar nviminfo +.Ar shada is .Cm NONE , -do not read or write an nviminfo file. +do not read or write a ShaDa file. See -.Ic :help viminfo . +.Ic :help shada . .It Fl -noplugin Skip loading plugins. Implied by diff --git a/runtime/doc/motion.txt b/runtime/doc/motion.txt index ff15de7d0e..7a29f8669c 100644 --- a/runtime/doc/motion.txt +++ b/runtime/doc/motion.txt @@ -1075,7 +1075,7 @@ if you stop editing a file without writing, like with ":n!". When you split a window, the jumplist will be copied to the new window. If you have included the ' item in the 'viminfo' option the jumplist will be -stored in the shada file and restored when starting Vim. +stored in the ShaDa file and restored when starting Vim. CHANGE LIST JUMPS *changelist* *change-list-jumps* *E664* diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index 83351c1002..ee3b0220eb 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -476,7 +476,7 @@ accordingly. Vim proceeds in this order: |gui-init|. 9. Read the ShaDa file - If the 'viminfo' option is not empty, the viminfo file is read. See + If the 'viminfo' option is not empty, the ShaDa file is read. See |shada-file|. 10. Read the quickfix file @@ -564,8 +564,8 @@ just like executing a command from a vimrc/exrc in the current directory. If Vim takes a long time to start up, use the |--startuptime| argument to find out what happens. -If you have "viminfo" enabled, the loading of the ShaDa file may take a -while. You can find out if this is the problem by disabling viminfo for a +If you have 'viminfo' enabled, the loading of the ShaDa file may take a +while. You can find out if this is the problem by disabling ShaDa for a moment (use the Vim argument "-i NONE", |-i|). Try reducing the number of lines stored in a register with ":set viminfo='20,<50,s10". |shada-file|. diff --git a/runtime/doc/usr_21.txt b/runtime/doc/usr_21.txt index d2d67339ed..d57381f5cd 100644 --- a/runtime/doc/usr_21.txt +++ b/runtime/doc/usr_21.txt @@ -168,7 +168,7 @@ exiting Vim, there is a slightly more complicated way. You can see a list of files by typing the command: > :oldfiles -< 1: ~/.viminfo ~ +< 1: ~/.vimrc ~ 2: ~/text/resume.txt ~ 3: /tmp/draft ~ @@ -188,7 +188,7 @@ That #<123 thing is a bit complicated when you just want to edit a file. Fortunately there is a simpler way: > :browse oldfiles -< 1: ~/.viminfo ~ +< 1: ~/.vimrc ~ 2: ~/text/resume.txt ~ 3: /tmp/draft ~ -- More -- diff --git a/runtime/optwin.vim b/runtime/optwin.vim index 636fa4b328..d1ab204180 100644 --- a/runtime/optwin.vim +++ b/runtime/optwin.vim @@ -1226,8 +1226,8 @@ if has("mksession") call append("$", "viewdir\tdirectory where to store files with :mkview") call OptionG("vdir", &vdir) endif -if has("viminfo") - call append("$", "viminfo\tlist that specifies what to write in the viminfo file") +if has("shada") + call append("$", "viminfo\tlist that specifies what to write in the ShaDa file") call OptionG("vi", &vi) endif if has("quickfix") diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index c45e641706..3e0a2c1076 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -9144,18 +9144,18 @@ int put_line(FILE *fd, char *s) */ static void ex_viminfo(exarg_T *eap) { - char_u *save_viminfo; + char_u *save_shada; - save_viminfo = p_viminfo; - if (*p_viminfo == NUL) - p_viminfo = (char_u *)"'100"; + save_shada = p_shada; + if (*p_shada == NUL) + p_shada = (char_u *)"'100"; if (eap->cmdidx == CMD_rviminfo) { if (shada_read_everything((char *) eap->arg, eap->forceit) == FAIL) EMSG(_("E195: Cannot open ShaDa file for reading")); } else { shada_write_file((char *) eap->arg, eap->forceit); } - p_viminfo = save_viminfo; + p_shada = save_shada; } /* diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index e4b59b7316..38442e3ab3 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -2172,7 +2172,7 @@ readfile_charconvert ( */ static void check_marks_read(void) { - if (!curbuf->b_marks_read && get_viminfo_parameter('\'') > 0 + if (!curbuf->b_marks_read && get_shada_parameter('\'') > 0 && curbuf->b_ffname != NULL) { shada_read_marks(); } diff --git a/src/nvim/main.c b/src/nvim/main.c index ae89d341be..bc95980afe 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -382,7 +382,7 @@ int main(int argc, char **argv) * Read in registers, history etc, from the ShaDa file. * This is where v:oldfiles gets filled. */ - if (*p_viminfo != NUL) { + if (*p_shada != NUL) { (void) shada_read_file(NULL, (kShaDaWantInfo | kShaDaGetOldfiles | kShaDaWantMarks)); @@ -807,8 +807,8 @@ 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 + if (p_shada && *p_shada != NUL) { + // Write out the registers, history, marks etc, to the ShaDa file shada_write_file(NULL, false); } diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 55c7aa3364..52064fae06 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -5238,7 +5238,7 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet) reg->additional_data = NULL; reg->timestamp = 0; // Timestamp is not saved for clipboard registers because clipboard registers - // are not saved in the viminfo. + // are not saved in the ShaDa file. int i = 0; for (listitem_T *li = lines->lv_first; li != NULL; li = li->li_next) { diff --git a/src/nvim/option.c b/src/nvim/option.c index c47616620c..87bae0179c 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -1743,11 +1743,11 @@ set_options_bin ( * 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; @@ -1758,11 +1758,11 @@ int get_viminfo_parameter(int type) * '/') in the 'viminfo' 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 */ @@ -2443,8 +2443,8 @@ did_set_string_option ( errmsg = e_invarg; } /* 'viminfo' */ - else if (varp == &p_viminfo) { - for (s = p_viminfo; *s; ) { + else if (varp == &p_shada) { + 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 +2486,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..a4cb390523 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; /* 'viminfo' */ 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..e66c46e08a 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2584,7 +2584,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/shada.c b/src/nvim/shada.c index 8a383b08a6..c853b00bac 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -63,8 +63,8 @@ KHASH_MAP_INIT_STR(fnamebufs, buf_T *) #define copy_option_part(src, dest, ...) \ ((char *) copy_option_part((char_u **) src, (char_u *) dest, __VA_ARGS__)) -#define find_viminfo_parameter(...) \ - ((const char *) find_viminfo_parameter(__VA_ARGS__)) +#define 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__) @@ -167,7 +167,7 @@ enum SRNIFlags { kSDReadBufferList = (1 << kSDItemBufferList), ///< Determines whether buffer ///< list should be read ///< (disabled by removing - ///< % entry from viminfo). + ///< % entry from &viminfo). kSDReadUnknown = (1 << (SHADA_LAST_ENTRY + 1)), ///< Determines whether ///< unknown items should be ///< read (usually disabled). @@ -200,6 +200,7 @@ typedef struct { int64_t offset; bool is_last_used; bool is_substitute_pattern; + // TODO(ZyX-I): Also store v:hlsearch, see :h shada-h char *pat; Dictionary *additional_data; } search_pattern; @@ -524,7 +525,7 @@ int shada_read_file(const char *const file, const int flags) if (p_verbose > 0) { verbose_enter(); - smsg(_("Reading viminfo file \"%s\"%s%s%s"), + smsg(_("Reading ShaDa file \"%s\"%s%s%s"), fname, (flags & kShaDaWantInfo) ? _(" info") : "", (flags & kShaDaWantMarks) ? _(" marks") : "", @@ -672,21 +673,22 @@ static inline bool marks_equal(const pos_T a, const pos_T b) static void shada_read(ShaDaReadDef *const sd_reader, const int flags) FUNC_ATTR_NONNULL_ALL { + // TODO(ZyX-I): Also load v:oldfiles. unsigned srni_flags = 0; if (flags & kShaDaWantInfo) { srni_flags |= kSDReadUndisableableData | kSDReadRegisters; if (p_hi) { srni_flags |= kSDReadHistory; } - if (find_viminfo_parameter('!') != NULL) { + if (find_shada_parameter('!') != NULL) { srni_flags |= kSDReadVariables; } - if (find_viminfo_parameter('%') != NULL && ARGCOUNT == 0) { + if (find_shada_parameter('%') != NULL && ARGCOUNT == 0) { srni_flags |= kSDReadBufferList; } } if (flags & kShaDaWantMarks) { - if (get_viminfo_parameter('\'') > 0) { + if (get_shada_parameter('\'') > 0) { srni_flags |= kSDReadLocalMarks; } } @@ -1039,7 +1041,7 @@ static char *shada_filename(const char *file) if (file == NULL || *file == NUL) { if (used_shada_file != NULL) { file = used_shada_file; - } else if ((file = find_viminfo_parameter('n')) == NULL || *file == NUL) { + } 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) { @@ -1364,7 +1366,7 @@ static void shada_write(ShaDaWriteDef *const sd_writer, FUNC_ATTR_NONNULL_ARG(1) { khash_t(bufset) *const removable_bufs = kh_init(bufset); - int max_kbyte_i = get_viminfo_parameter('s'); + int max_kbyte_i = get_shada_parameter('s'); if (max_kbyte_i < 0) { max_kbyte_i = 10; } @@ -1409,7 +1411,7 @@ static void shada_write(ShaDaWriteDef *const sd_writer, }, 0); // 2. Buffer list - if (find_viminfo_parameter('%') != NULL) { + if (find_shada_parameter('%') != NULL) { size_t buf_count = 0; FOR_ALL_BUFFERS(buf) { if (buf->b_ffname != NULL && !SHADA_REMOVABLE(buf)) { @@ -1498,7 +1500,7 @@ static void shada_write(ShaDaWriteDef *const sd_writer, // 4. History HistoryMergerState hms[HIST_COUNT]; for (uint8_t i = 0; i < HIST_COUNT; i++) { - long num_saved = get_viminfo_parameter(hist_type2char(i)); + long num_saved = get_shada_parameter(hist_type2char(i)); if (num_saved == -1) { num_saved = p_hi; } @@ -1599,7 +1601,7 @@ static void shada_write(ShaDaWriteDef *const sd_writer, } // 7. Global marks - if (get_viminfo_parameter('f') != 0) { + if (get_shada_parameter('f') != 0) { ShadaEntry *const global_marks = list_global_marks(removable_bufs); for (ShadaEntry *mark = global_marks; mark->type != kSDItemMissing; mark++) { @@ -1636,12 +1638,12 @@ static void shada_write(ShaDaWriteDef *const sd_writer, } } // FIXME: Copy previous marks, up to num_marked_files - // size_t num_marked_files = get_viminfo_parameter('\''); + // size_t num_marked_files = get_shada_parameter('\''); // 9. Registers - int max_num_lines_i = get_viminfo_parameter('<'); + int max_num_lines_i = get_shada_parameter('<'); if (max_num_lines_i < 0) { - max_num_lines_i = get_viminfo_parameter('"'); + max_num_lines_i = get_shada_parameter('"'); } if (max_num_lines_i != 0) { const size_t max_num_lines = (max_num_lines_i < 0 @@ -1673,7 +1675,7 @@ static void shada_write(ShaDaWriteDef *const sd_writer, } // 10. Variables - if (find_viminfo_parameter('!') != NULL) { + if (find_shada_parameter('!') != NULL) { const void *var_iter = NULL; const Timestamp cur_timestamp = os_time(); do { @@ -1730,6 +1732,7 @@ int shada_write_file(const char *const file, bool nomerge) intptr_t fd; if (!nomerge) { + // TODO(ZyX-I): Fail on read error. if (open_shada_file_for_reading(fname, &sd_reader) != OK) { nomerge = true; goto shada_write_file_nomerge; @@ -1741,6 +1744,7 @@ int shada_write_file(const char *const file, bool nomerge) } shada_write_file_open: + // TODO(ZyX-I): Preserve existing permissions fd = (intptr_t) open_file(tempname, O_CREAT|O_WRONLY|O_NOFOLLOW|O_EXCL, 0600); if (fd < 0) { @@ -1754,6 +1758,8 @@ shada_write_file_open: 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; @@ -1772,7 +1778,7 @@ shada_write_file_nomerge: if (p_verbose > 0) { verbose_enter(); - smsg(_("Writing viminfo file \"%s\""), fname); + smsg(_("Writing ShaDa file \"%s\""), fname); verbose_leave(); } @@ -1793,7 +1799,7 @@ shada_write_file_nomerge: if (!nomerge) { close_file((int)(intptr_t) sd_reader.cookie); if (vim_rename(tempname, fname) == -1) { - EMSG3(_("E886: Can't rename viminfo file from %s to %s!"), + EMSG3(_("E886: Can't rename ShaDa file from %s to %s!"), tempname, fname); } else { os_remove(tempname); @@ -3021,7 +3027,7 @@ bool shada_removable(const char *name) size_t n; char *new_name = home_replace_save(NULL, name); - for (p = (char *) p_viminfo; *p; ) { + for (p = (char *) p_shada; *p; ) { (void) copy_option_part(&p, part, 51, ", "); if (part[0] == 'r') { n = STRLEN(part + 1); diff --git a/test/functional/ex_cmds/wviminfo_spec.lua b/test/functional/ex_cmds/wviminfo_spec.lua index f4911cd3e8..053555c267 100644 --- a/test/functional/ex_cmds/wviminfo_spec.lua +++ b/test/functional/ex_cmds/wviminfo_spec.lua @@ -4,7 +4,7 @@ local clear, execute, eq, neq, spawn, nvim_prog, set_session, wait, write_file helpers.nvim_prog, helpers.set_session, helpers.wait, helpers.write_file describe(':wviminfo', function() - local viminfo_file = 'wviminfo_test' + local shada_file = 'wviminfo_test' local session before_each(function() @@ -17,38 +17,41 @@ describe(':wviminfo', function() '--cmd', 'set swapfile'}) set_session(session) - os.remove(viminfo_file) + os.remove(shada_file) end) - it('creates a viminfo file', function() + it('creates a shada file', function() -- file should _not_ exist - eq(nil, lfs.attributes(viminfo_file)) - execute('wv! '..viminfo_file) + eq(nil, lfs.attributes(shada_file)) + execute('wv! '..shada_file) wait() -- file _should_ exist - neq(nil, lfs.attributes(viminfo_file)) + neq(nil, lfs.attributes(shada_file)) end) it('overwrites existing files', function() local text = 'wviminfo test' -- Create a dummy file - write_file(viminfo_file, text) + write_file(shada_file, text) -- sanity check - eq(text, io.open(viminfo_file):read()) - neq(nil, lfs.attributes(viminfo_file)) + eq(text, io.open(shada_file):read()) + neq(nil, lfs.attributes(shada_file)) - execute('wv! '..viminfo_file) + execute('wv! '..shada_file) wait() - -- File should have been overwritten with a viminfo file. - local line1 = io.lines(viminfo_file)() - assert(nil ~= string.find(line1, 'This viminfo file was generated by Nvim'), - viminfo_file..' should be a viminfo-formatted file') + -- File should have been overwritten with a shada file. + local fp = io.open(shada_file, 'r') + local char1 = fp:read(1) + fp:close() + -- ShaDa file starts with a “header” entry + assert(char1:byte() == 0x01, + shada_file..' should be a shada file') end) teardown(function() - os.remove(viminfo_file) + os.remove(shada_file) end) end) diff --git a/test/functional/legacy/074_global_var_in_viminfo_spec.lua b/test/functional/legacy/074_global_var_in_viminfo_spec.lua index a89a4181cd..f017ed80a8 100644 --- a/test/functional/legacy/074_global_var_in_viminfo_spec.lua +++ b/test/functional/legacy/074_global_var_in_viminfo_spec.lua @@ -1,14 +1,15 @@ --- Tests for storing global variables in the .viminfo file +-- Tests for storing global variables in the .shada file local helpers, lfs = require('test.functional.helpers'), require('lfs') local clear, execute, eq, neq, eval, wait, spawn = helpers.clear, helpers.execute, helpers.eq, helpers.neq, helpers.eval, helpers.wait, helpers.spawn -describe('storing global variables in viminfo files', function() +describe('storing global variables in ShaDa files', function() + local tempname = 'Xtest-functional-legacy-074' setup(function() clear() - os.remove("Xviminfo") + os.remove(tempname) end) it('is working', function() @@ -29,31 +30,32 @@ describe('storing global variables in viminfo files', function() 'set visualbell', 'set viminfo+=!', "let MY_GLOBAL_DICT={'foo': 1, 'bar': 0, 'longvarible': 1000}", - -- Store a really long list, so line wrapping will occur in viminfo - -- file. + -- Store a really long list. Initially this was testing line wrapping in + -- viminfo, but shada files has no line wrapping, no matter how long the + -- list is. 'let MY_GLOBAL_LIST=range(1,100)' ) eq(test_dict, eval('MY_GLOBAL_DICT')) eq(test_list, eval('MY_GLOBAL_LIST')) - execute('wv! Xviminfo') + execute('wv! ' .. tempname) wait() - -- Assert that the viminfo file exists. - neq(nil, lfs.attributes('Xviminfo')) + -- Assert that the shada file exists. + neq(nil, lfs.attributes(tempname)) execute('unlet MY_GLOBAL_DICT', 'unlet MY_GLOBAL_LIST') -- Assert that the variables where deleted. eq(0, eval('exists("MY_GLOBAL_DICT")')) eq(0, eval('exists("MY_GLOBAL_LIST")')) - execute('rv! Xviminfo') + execute('rv! ' .. tempname) eq(test_list, eval('MY_GLOBAL_LIST')) eq(test_dict, eval('MY_GLOBAL_DICT')) end) teardown(function() - os.remove('Xviminfo') + os.remove(tempname) end) end) -- cgit From 8663983cc4c2f66eb0ba58ad8e247ac0686cc79b Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 6 Jul 2015 02:26:44 +0300 Subject: Deprecate &viminfo and :[rw]v, add &shada and :[rw]sh --- runtime/doc/autocmd.txt | 2 +- runtime/doc/eval.txt | 4 +- runtime/doc/index.txt | 4 +- runtime/doc/motion.txt | 6 +- runtime/doc/options.txt | 242 +++++++++++---------- runtime/doc/quickref.txt | 10 +- runtime/doc/starting.txt | 60 ++--- runtime/doc/usr_21.txt | 32 +-- runtime/doc/various.txt | 2 +- runtime/doc/vim_diff.txt | 4 + runtime/macros/less.vim | 2 +- src/nvim/ex_cmds.lua | 16 +- src/nvim/ex_docmd.c | 6 +- src/nvim/fileio.c | 2 +- src/nvim/globals.h | 2 +- src/nvim/option.c | 6 +- src/nvim/option_defs.h | 2 +- src/nvim/options.lua | 8 + src/nvim/shada.c | 14 +- src/nvim/testdir/test8.in | 2 +- test/functional/ex_cmds/wviminfo_spec.lua | 10 +- .../legacy/074_global_var_in_viminfo_spec.lua | 6 +- test/functional/shada/buffers_spec.lua | 28 +-- test/functional/shada/history_spec.lua | 62 +++--- test/functional/shada/marks_spec.lua | 22 +- test/functional/shada/registers_spec.lua | 16 +- test/functional/shada/variables_spec.lua | 48 ++-- 27 files changed, 326 insertions(+), 292 deletions(-) diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index f63c319c2f..e922d5b601 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -1375,7 +1375,7 @@ use search patterns normally, e.g., with the "n" command. If you want an autocommand to set the search pattern, such that it is used after the autocommand finishes, use the ":let @/ =" command. The search-highlighting cannot be switched off with ":nohlsearch" in an -autocommand. Use the 'h' flag in the 'viminfo' option to disable search- +autocommand. Use the 'h' flag in the 'shada' option to disable search- highlighting when starting Vim. *Cmd-event* diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 0475af6706..0a17521c08 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -557,7 +557,7 @@ Functions that can be used with a Dictionary: > If you need to know the type of a variable or expression, use the |type()| function. -When the '!' flag is included in the 'viminfo' option, global variables that +When the '!' flag is included in the 'shada' option, global variables that start with an uppercase letter, and don't contain a lowercase letter, are stored in the shada file |shada-file|. @@ -1527,7 +1527,7 @@ v:msgpack_types Dictionary containing msgpack types used by |msgpackparse()| v:oldfiles List of file names that is loaded from the |shada| file on startup. These are the files that Vim remembers marks for. The length of the List is limited by the ' argument of the - 'viminfo' option (default is 100). + 'shada' option (default is 100). When the |shada| file is not used the List is empty. Also see |:oldfiles| and |c_#<|. The List can be modified, but this has no effect on what is diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt index 3cb69a11c5..5c1744c816 100644 --- a/runtime/doc/index.txt +++ b/runtime/doc/index.txt @@ -1418,9 +1418,9 @@ tag command action ~ |:rewind| :rew[ind] go to the first file in the argument list |:right| :ri[ght] right align text |:rightbelow| :rightb[elow] make split window appear right or below +|:rshada| :rsh[ada] read from ShaDa file |:rundo| :rund[o] read undo information from a file |:runtime| :ru[ntime] source vim scripts in 'runtimepath' -|:rviminfo| :rv[iminfo] read from ShaDa file |:substitute| :s[ubstitute] find and replace text |:sNext| :sN[ext] split window and go to previous file in argument list @@ -1579,8 +1579,8 @@ tag command action ~ argument list |:wq| :wq write to a file and quit window or Vim |:wqall| :wqa[ll] write all changed buffers and quit Vim +|:wshada| :wsh[ada] write to ShaDa file |:wundo| :wu[ndo] write undo information to a file -|:wviminfo| :wv[iminfo] write to ShaDa file |:xit| :x[it] write if buffer changed and quit window or Vim |:xall| :xa[ll] same as ":wqall" |:xmapclear| :xmapc[lear] remove all mappings for Visual mode diff --git a/runtime/doc/motion.txt b/runtime/doc/motion.txt index 7a29f8669c..2e8f0801dc 100644 --- a/runtime/doc/motion.txt +++ b/runtime/doc/motion.txt @@ -820,13 +820,13 @@ Uppercase marks 'A to 'Z include the file name. You can use them to jump from file to file. You can only use an uppercase mark with an operator if the mark is in the current file. The line number of the mark remains correct, even if you insert/delete lines or edit another file -for a moment. When the 'viminfo' option is not empty, uppercase marks are +for a moment. When the 'shada' option is not empty, uppercase marks are kept in the .shada file. See |shada-file-marks|. Numbered marks '0 to '9 are quite different. They can not be set directly. They are only present when using a shada file |shada-file|. Basically '0 is the location of the cursor when you last exited Vim, '1 the last but one -time, etc. Use the "r" flag in 'viminfo' to specify files for which no +time, etc. Use the "r" flag in 'shada' to specify files for which no Numbered mark should be stored. See |shada-file-marks|. @@ -1074,7 +1074,7 @@ if you stop editing a file without writing, like with ":n!". When you split a window, the jumplist will be copied to the new window. -If you have included the ' item in the 'viminfo' option the jumplist will be +If you have included the ' item in the 'shada' option the jumplist will be stored in the ShaDa file and restored when starting Vim. diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index c7a508ed3f..d65a8b9454 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -3409,7 +3409,7 @@ A jump table for the options with a short description can be found at |Q_op|. line below a closed fold. A match in a previous line which is not drawn may not continue in a newly drawn line. You can specify whether the highlight status is restored on startup - with the 'h' flag in 'viminfo' |shada-h|. + with the 'h' flag in 'shada' |shada-h|. *'history'* *'hi'* 'history' 'hi' number (Vim default: 10000, Vi default: 0) @@ -5291,6 +5291,124 @@ A jump table for the options with a short description can be found at |Q_op|. with Unix. The Unix version of Vim cannot source dos format scripts, but the Windows version of Vim can source unix format scripts. + *'shada'* *'sd'* *E526* *E527* *E528* +'shada' 'sd' string (Vim default for + Win32: '!,100,<50,s10,h,rA:,rB: + others: '!,100,<50,s10,h + Vi default: "") + global + When non-empty, the shada file is read upon startup and written + when exiting Vim (see |shada-file|). The string should be a comma + separated list of parameters, each consisting of a single character + identifying the particular parameter, followed by a number or string + which specifies the value of that parameter. If a particular + character is left out, then the default value is used for that + parameter. The following is a list of the identifying characters and + the effect of their value. + CHAR VALUE ~ + *shada-!* + ! When included, save and restore global variables that start + with an uppercase letter, and don't contain a lowercase + letter. Thus "KEEPTHIS and "K_L_M" are stored, but "KeepThis" + and "_K_L_M" are not. Nested List and Dict items may not be + read back correctly, you end up with an empty item. + *shada-quote* + " Maximum number of lines saved for each register. Old name of + the '<' item, with the disadvantage that you need to put a + backslash before the ", otherwise it will be recognized as the + start of a comment! + *shada-%* + % When included, save and restore the buffer list. If Vim is + started with a file name argument, the buffer list is not + restored. If Vim is started without a file name argument, the + buffer list is restored from the shada file. Buffers + without a file name and buffers for help files are not written + to the shada file. + When followed by a number, the number specifies the maximum + number of buffers that are stored. Without a number all + buffers are stored. + *shada-'* + ' Maximum number of previously edited files for which the marks + are remembered. This parameter must always be included when + 'shada' is non-empty. + Including this item also means that the |jumplist| and the + |changelist| are stored in the shada file. + *shada-/* + / Maximum number of items in the search pattern history to be + saved. If non-zero, then the previous search and substitute + patterns are also saved. When not included, the value of + 'history' is used. + *shada-:* + : Maximum number of items in the command-line history to be + saved. When not included, the value of 'history' is used. + *shada-<* + < Maximum number of lines saved for each register. If zero then + registers are not saved. When not included, all lines are + saved. '"' is the old name for this item. + Also see the 's' item below: limit specified in KiB. + *shada-@* + @ Maximum number of items in the input-line history to be + saved. When not included, the value of 'history' is used. + *shada-c* + c Dummy option, kept for compatibility reasons. Has no actual + effect. Current encoding state is described in + |shada-encoding|. + *shada-f* + f Whether file marks need to be stored. If zero, file marks ('0 + to '9, 'A to 'Z) are not stored. When not present or when + non-zero, they are all stored. '0 is used for the current + cursor position (when exiting or when doing |:wshada|). + *shada-h* + h Disable the effect of 'hlsearch' when loading the shada + file. When not included, it depends on whether ":nohlsearch" + has been used since the last search command. + *shada-n* + n Name of the shada file. The name must immediately follow + the 'n'. Must be the last one! If the "-i" argument was + given when starting Vim, that file name overrides the one + given here with 'shada'. Environment variables are expanded + when opening the file, not when setting the option. + *shada-r* + r Removable media. The argument is a string (up to the next + ','). This parameter can be given several times. Each + specifies the start of a path for which no marks will be + stored. This is to avoid removable media. For MS-DOS you + could use "ra:,rb:". You can also use it for temp files, + e.g., for Unix: "r/tmp". Case is ignored. Maximum length of + each 'r' argument is 50 characters. + *shada-s* + s Maximum size of an item contents in KiB. If zero then nothing + is saved. Unlike Vim this applies to all items, except for + the buffer list and header. Full item size is off by three + unsigned integers: with `s10` maximum item size may be 1 byte + (type: 7-bit integer) + 9 bytes (timestamp: up to 64-bit + integer) + 3 bytes (item size: up to 16-bit integer because + 2^8 < 10240 < 2^16) + 10240 bytes (requested maximum item + contents size) = 10253 bytes. + + Example: > + :set shada='50,<1000,s100,:0,n~/nvim/shada +< + '50 Marks will be remembered for the last 50 files you + edited. + <1000 Contents of registers (up to 1000 lines each) will be + remembered. + s100 Items with contents occupying more then 100 KiB are + skipped. + :0 Command-line history will not be saved. + n~/nvim/shada The name of the file to use is "~/nvim/shada". + no / Since '/' is not specified, the default will be used, + that is, save all of the search history, and also the + previous search and substitute patterns. + no % The buffer list will not be saved nor read back. + no h 'hlsearch' highlighting will be restored. + + When setting 'shada' from an empty value you can use |:rshada| to + load the contents of the file, this is not done automatically. + + This option cannot be set from a |modeline| or in the |sandbox|, for + security reasons. + *'shell'* *'sh'* *E91* 'shell' 'sh' string (default $SHELL or "sh", MS-DOS and Win32: "command.com" or @@ -6683,125 +6801,11 @@ A jump table for the options with a short description can be found at |Q_op|. with Unix. The Unix version of Vim cannot source dos format scripts, but the Windows version of Vim can source unix format scripts. - *'viminfo'* *'vi'* *E526* *E527* *E528* -'viminfo' 'vi' string (Vim default for - Win32: '!,100,<50,s10,h,rA:,rB: - others: '!,100,<50,s10,h - Vi default: "") + *'viminfo'* *'vi'* +'viminfo' 'vi' string global - {not available when compiled without the |+shada| - feature} - When non-empty, the shada file is read upon startup and written - when exiting Vim (see |shada-file|). The string should be a comma - separated list of parameters, each consisting of a single character - identifying the particular parameter, followed by a number or string - which specifies the value of that parameter. If a particular - character is left out, then the default value is used for that - parameter. The following is a list of the identifying characters and - the effect of their value. - CHAR VALUE ~ - *shada-!* - ! When included, save and restore global variables that start - with an uppercase letter, and don't contain a lowercase - letter. Thus "KEEPTHIS and "K_L_M" are stored, but "KeepThis" - and "_K_L_M" are not. Nested List and Dict items may not be - read back correctly, you end up with an empty item. - *shada-quote* - " Maximum number of lines saved for each register. Old name of - the '<' item, with the disadvantage that you need to put a - backslash before the ", otherwise it will be recognized as the - start of a comment! - *shada-%* - % When included, save and restore the buffer list. If Vim is - started with a file name argument, the buffer list is not - restored. If Vim is started without a file name argument, the - buffer list is restored from the shada file. Buffers - without a file name and buffers for help files are not written - to the shada file. - When followed by a number, the number specifies the maximum - number of buffers that are stored. Without a number all - buffers are stored. - *shada-'* - ' Maximum number of previously edited files for which the marks - are remembered. This parameter must always be included when - 'viminfo' is non-empty. - Including this item also means that the |jumplist| and the - |changelist| are stored in the shada file. - *shada-/* - / Maximum number of items in the search pattern history to be - saved. If non-zero, then the previous search and substitute - patterns are also saved. When not included, the value of - 'history' is used. - *shada-:* - : Maximum number of items in the command-line history to be - saved. When not included, the value of 'history' is used. - *shada-<* - < Maximum number of lines saved for each register. If zero then - registers are not saved. When not included, all lines are - saved. '"' is the old name for this item. - Also see the 's' item below: limit specified in Kbyte. - *shada-@* - @ Maximum number of items in the input-line history to be - saved. When not included, the value of 'history' is used. - *shada-c* - c Dumb option, kept for compatibility reasons. Has no actual - effect. Current encoding state is described in - |shada-encoding|. - *shada-f* - f Whether file marks need to be stored. If zero, file marks ('0 - to '9, 'A to 'Z) are not stored. When not present or when - non-zero, they are all stored. '0 is used for the current - cursor position (when exiting or when doing ":wviminfo"). - *shada-h* - h Disable the effect of 'hlsearch' when loading the shada - file. When not included, it depends on whether ":nohlsearch" - has been used since the last search command. - *shada-n* - n Name of the shada file. The name must immediately follow - the 'n'. Must be the last one! If the "-i" argument was - given when starting Vim, that file name overrides the one - given here with 'viminfo'. Environment variables are expanded - when opening the file, not when setting the option. - *shada-r* - r Removable media. The argument is a string (up to the next - ','). This parameter can be given several times. Each - specifies the start of a path for which no marks will be - stored. This is to avoid removable media. For MS-DOS you - could use "ra:,rb:". You can also use it for temp files, - e.g., for Unix: "r/tmp". Case is ignored. Maximum length of - each 'r' argument is 50 characters. - *shada-s* - s Maximum size of an item contents in KiB. If zero then nothing - is saved. Unlike Vim this applies to all items, except for - the buffer list and header. Full item size is off by three - unsigned integers: with `s10` maximum item size may be 1 byte - (type: 7-bit integer) + 9 bytes (timestamp: up to 64-bit - integer) + 3 bytes (item size: up to 16-bit integer because - 2^8 < 10240 < 2^16) + 10240 bytes (requested maximum item - contents size) = 10253 bytes. - - Example: > - :set viminfo='50,<1000,s100,:0,n~/nvim/shada -< - '50 Marks will be remembered for the last 50 files you - edited. - <1000 Contents of registers (up to 1000 lines each) will be - remembered. - s100 Items with contents occupying more then 100 KiB are - skipped. - :0 Command-line history will not be saved. - n~/nvim/shada The name of the file to use is "~/nvim/shada". - no / Since '/' is not specified, the default will be used, - that is, save all of the search history, and also the - previous search and substitute patterns. - no % The buffer list will not be saved nor read back. - no h 'hlsearch' highlighting will be restored. - - When setting 'viminfo' from an empty value you can use |:rviminfo| to - load the contents of the file, this is not done automatically. - - This option cannot be set from a |modeline| or in the |sandbox|, for - security reasons. + Deprecated alias for 'shada' option. Is kept for compatibility + reasons. *'virtualedit'* *'ve'* 'virtualedit' 've' string (default "") diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt index 503ab07079..1b24efc4f9 100644 --- a/runtime/doc/quickref.txt +++ b/runtime/doc/quickref.txt @@ -838,6 +838,7 @@ Short explanation of each option: *option-list* 'selection' 'sel' what type of selection to use 'selectmode' 'slm' when to use Select mode instead of Visual mode 'sessionoptions' 'ssop' options for |:mksession| +'shada' 'sd' use .shada file upon startup and exiting 'shell' 'sh' name of shell to use for external commands 'shellcmdflag' 'shcf' flag to shell to execute one command 'shellpipe' 'sp' string to put output of ":make" in error file @@ -911,7 +912,6 @@ Short explanation of each option: *option-list* 'verbosefile' 'vfile' file to write messages in 'viewdir' 'vdir' directory where to store files with :mkview 'viewoptions' 'vop' specifies what to save for :mkview -'viminfo' 'vi' use .shada file upon startup and exiting 'virtualedit' 've' when to use virtual editing 'visualbell' 'vb' use visual bell instead of beeping 'warn' warn for shell command when buffer was changed @@ -1217,10 +1217,10 @@ Context-sensitive completion on the command-line: |shada-file| read registers, marks, history at startup, save when exiting. -|:rviminfo| :rv[iminfo] [file] read info from ShaDa file [file] -|:rviminfo| :rv[iminfo]! [file] idem, overwrite existing info -|:wviminfo| :wv[iminfo] [file] add info to ShaDa file [file] -|:wviminfo| :wv[iminfo]! [file] write info to ShaDa file [file] +|:rshada| :rsh[ada] [file] read info from ShaDa file [file] +|:rshada| :rsh[ada]! [file] idem, overwrite existing info +|:wshada| :wsh[ada] [file] add info to ShaDa file [file] +|:wshada| :wsh[ada]! [file] write info to ShaDa file [file] |modeline| Automatic option setting when editing a file diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index ee3b0220eb..c5a0be3fff 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -324,8 +324,8 @@ argument. *-i* -i {shada} The file {shada} is used instead of the default ShaDa file. If the name "NONE" is used (all uppercase), no ShaDa - file is read or written, even if 'viminfo' is set or when - ":rv" or ":wv" are used. See also |shada-file|. + file is read or written, even if 'shada' is set or when + ":rsh" or ":wsh" are used. See also |shada-file|. *-s* -s {scriptin} The script file "scriptin" is read. The characters in the @@ -476,7 +476,7 @@ accordingly. Vim proceeds in this order: |gui-init|. 9. Read the ShaDa file - If the 'viminfo' option is not empty, the ShaDa file is read. See + If the 'shada' option is not empty, the ShaDa file is read. See |shada-file|. 10. Read the quickfix file @@ -564,10 +564,10 @@ just like executing a command from a vimrc/exrc in the current directory. If Vim takes a long time to start up, use the |--startuptime| argument to find out what happens. -If you have 'viminfo' enabled, the loading of the ShaDa file may take a +If you have 'shada' enabled, the loading of the ShaDa file may take a while. You can find out if this is the problem by disabling ShaDa for a moment (use the Vim argument "-i NONE", |-i|). Try reducing the number of -lines stored in a register with ":set viminfo='20,<50,s10". |shada-file|. +lines stored in a register with ":set shada='20,<50,s10". |shada-file|. *:intro* When Vim starts without a file name, an introductory message is displayed (for @@ -923,17 +923,17 @@ working on. ShaDa and Session files together can be used to effectively enter Vim and directly start working in your desired setup. |session-file| *shada-read* -When Vim is started and the 'viminfo' option is non-empty, the contents of +When Vim is started and the 'shada' option is non-empty, the contents of the ShaDa file are read and the info can be used in the appropriate places. The |v:oldfiles| variable is filled. The marks are not read in at startup -(but file marks are). See |initialization| for how to set the 'viminfo' +(but file marks are). See |initialization| for how to set the 'shada' option upon startup. *shada-write* -When Vim exits and 'viminfo' is non-empty, the info is stored in the ShaDa +When Vim exits and 'shada' is non-empty, the info is stored in the ShaDa file (it's actually merged with the existing one, if one exists). The -'viminfo' option is a string containing information about what info should be -stored, and contains limits on how much should be stored (see 'viminfo'). +'shada' option is a string containing information about what info should be +stored, and contains limits on how much should be stored (see 'shada'). Notes for Unix: - The file protection for the ShaDa file will be set to prevent other users @@ -950,15 +950,15 @@ Notes for Unix: - The ShaDa file cannot be a symbolic link. This is to avoid security issues. -Marks are stored for each file separately. When a file is read and 'viminfo' +Marks are stored for each file separately. When a file is read and 'shada' is non-empty, the marks for that file are read from the ShaDa file. NOTE: The marks are only written when exiting Vim, which is fine because marks are remembered for all the files you have opened in the current editing session, unless ":bdel" is used. If you want to save the marks for a file that you are -about to abandon with ":bdel", use ":wv". The '[' and ']' marks are not +about to abandon with ":bdel", use ":wsh". The '[' and ']' marks are not stored, but the '"' mark is. The '"' mark is very useful for jumping to the cursor position when the file was last exited. No marks are saved for files -that start with any string given with the "r" flag in 'viminfo'. This can be +that start with any string given with the "r" flag in 'shada'. This can be used to avoid saving marks for files on removable media (for MS-DOS you would use "ra:,rb:". The |v:oldfiles| variable is filled with the file names that the ShaDa file @@ -967,7 +967,7 @@ has marks for. *shada-file-marks* Uppercase marks ('A to 'Z) are stored when writing the ShaDa file. The numbered marks ('0 to '9) are a bit special. When the ShaDa file is written -(when exiting or with the ":wviminfo" command), '0 is set to the current cursor +(when exiting or with the |:wshada| command), '0 is set to the current cursor position and file. The old '0 is moved to '1, '1 to '2, etc. This resembles what happens with the "1 to "9 delete registers. If the current cursor position is already present in '0 to '9, it is moved to '0, to avoid @@ -985,7 +985,7 @@ For a bash-like shell: > alias lvim='vim -c "normal '\''0"' -Use the "r" flag in 'viminfo' to specify for which files no marks should be +Use the "r" flag in 'shada' to specify for which files no marks should be remembered. @@ -995,13 +995,13 @@ SHADA FILE NAME *shada-file-name* Unix, "$HOME\_nvim\shada\main.shada" for MS-DOS and Win32. For the last two, when $HOME is not set, "$VIM\_nvim\shada\main.shada" is used. When $VIM is also not set, "c:\_nvim\shada\main.shada" is used. -- The 'n' flag in the 'viminfo' option can be used to specify another ShaDa - file name |'viminfo'|. +- The 'n' flag in the 'shada' option can be used to specify another ShaDa + file name |'shada'|. - The "-i" Vim argument can be used to set another file name, |-i|. When the file name given is "NONE" (all uppercase), no ShaDa file is ever read or written. Also not for the commands below! - For the commands below, another file name can be given, overriding the - default and the name given with 'viminfo' or "-i" (unless it's NONE). + default and the name given with 'shada' or "-i" (unless it's NONE). CHARACTER ENCODING *shada-encoding* @@ -1025,8 +1025,8 @@ MANUALLY READING AND WRITING *shada-read-write* Two commands can be used to read and write the ShaDa file manually. This can be used to exchange registers between two running Vim programs: First -type ":wv" in one and then ":rv" in the other. Note that if the register -already contained something, then ":rv!" would be required. Also note +type ":wsh" in one and then ":rsh" in the other. Note that if the register +already contained something, then ":rsh!" would be required. Also note however that this means everything will be overwritten with information from the first Vim, including the command line history, etc. @@ -1038,7 +1038,7 @@ do this. This can be useful in order to create a second file, say you first start NeoVim. For example, you can preload registers with particular data, or put certain commands in the command line history. A line in your .nvimrc file like > - :rviminfo! ~/.my.shada + :rshada! ~/.my.shada can be used to load this information. You could even have different ShaDa files for different types of files (e.g., C code) and load them based on the file name, using the ":autocmd" command (see |:autocmd|). @@ -1052,28 +1052,34 @@ accidentally did that!). If you want to overwrite a ShaDa file with an error in it, you will either have to fix the error, or delete the file (while NeoVim is running, so most of the information will be restored). - *:rv* *:rviminfo* *E195* -:rv[iminfo][!] [file] Read from ShaDa file [file] (default: see above). + *:rsh* *:rshada* *E195* +:rsh[ada][!] [file] Read from ShaDa file [file] (default: see above). If [!] is given, then any information that is already set (registers, marks, |v:oldfiles|, etc.) will be overwritten. - *:wv* *:wviminfo* *E137* *E138* *E574* *E886* -:wv[iminfo][!] [file] Write to ShaDa file [file] (default: see above). + *:rv* *:rviminfo* +:rv[iminfo][!] [file] Deprecated alias to |:rshada| command. + + *:wsh* *:wshada* *E137* *E138* *E574* *E886* +:wsh[ada][!] [file] Write to ShaDa file [file] (default: see above). The information in the file is first read in to make a merge between old and new info. When [!] is used, the old information is not read first, only the internal info is written (also disables safety checks - described in |shada-errors|). If 'viminfo' is empty, + described in |shada-errors|). If 'shada' is empty, marks for up to 100 files will be written. When you get error "E138: All .tmp.X files exist, cannot write ShaDa file!" check that no old temp files were left behind (e.g. ~/.nvim/shada/main.shada.tmp*). + *:wv* *:wviminfo* +:wv[iminfo][!] [file] Deprecated alias to |:wshada| command. + *:ol* *:oldfiles* :ol[dfiles] List the files that have marks stored in the ShaDa file. This list is read on startup and only changes - afterwards with ":rviminfo!". Also see |v:oldfiles|. + afterwards with ":rshada!". Also see |v:oldfiles|. The number can be used with |c_#<|. :bro[wse] ol[dfiles][!] diff --git a/runtime/doc/usr_21.txt b/runtime/doc/usr_21.txt index d57381f5cd..96797a745c 100644 --- a/runtime/doc/usr_21.txt +++ b/runtime/doc/usr_21.txt @@ -96,34 +96,34 @@ Each time you exit Vim it will store this information in a file, the ShaDa file. When Vim starts again, the ShaDa file is read and the information restored. -The 'viminfo' option is set by default to restore a limited number of items. +The 'shada' option is set by default to restore a limited number of items. You might want to set it to remember more information. This is done through the following command: > - :set viminfo=string + :set shada=string The string specifies what to save. The syntax of this string is an option character followed by an argument. The option/argument pairs are separated by commas. - Take a look at how you can build up your own viminfo string. First, the ' + Take a look at how you can build up your own shada string. First, the ' option is used to specify how many files for which you save marks (a-z). Pick a nice even number for this option (1000, for instance). Your command now looks like this: > - :set viminfo='1000 + :set shada='1000 The f option controls whether global marks (A-Z and 0-9) are stored. If this option is 0, none are stored. If it is 1 or you do not specify an f option, the marks are stored. You want this feature, so now you have this: > - :set viminfo='1000,f1 + :set shada='1000,f1 The < option controls how many lines are saved for each of the registers. By default, all the lines are saved. If 0, nothing is saved. To avoid adding thousands of lines to your ShaDa file (which might never get used and makes starting Vim slower) you use a maximum of 500 lines: > - :set viminfo='1000,f1,<500 + :set shada='1000,f1,<500 < Other options you might want to use: : number of lines to save from the command line history @@ -139,7 +139,7 @@ Other options you might want to use: c convert the text using 'encoding' n name used for the ShaDa file (must be the last option) -See the 'viminfo' option and |shada-file| for more information. +See the 'shada' option and |shada-file| for more information. When you run Vim multiple times, the last one exiting will store its information. This may cause information that previously exiting Vims stored @@ -205,23 +205,23 @@ More info at |:oldfiles|, |v:oldfiles| and |c_#<|. MOVE INFO FROM ONE VIM TO ANOTHER -You can use the ":wviminfo" and ":rviminfo" commands to save and restore the +You can use the ":wshada" and ":rshada" commands to save and restore the information while still running Vim. This is useful for exchanging register contents between two instances of Vim, for example. In the first Vim do: > - :wviminfo! ~/tmp/shada + :wshada! ~/tmp/shada And in the second Vim do: > - :rviminfo! ~/tmp/shada + :rshada! ~/tmp/shada Obviously, the "w" stands for "write" and the "r" for "read". - The ! character is used by ":wviminfo" to forcefully overwrite an existing + The ! character is used by ":wshada" to forcefully overwrite an existing file. When it is omitted, and the file exists, the information is merged into the file. - The ! character used for ":rviminfo" means that all the information in -ShaDa file has priority over existing information, this may overwrite it. -Without the ! only information that wasn't set is used. + The ! character used for ":rshada" means that all the information in ShaDa +file has priority over existing information, this may overwrite it. Without +the ! only information that wasn't set is used. These commands can also be used to store info and use it again later. You could make a directory full of ShaDa files, each containing info for a different purpose. @@ -368,12 +368,12 @@ another session. this yourself then. Example: > :mksession! ~/.vim/secret.vim - :wviminfo! ~/.vim/secret.shada + :wshada! ~/.vim/secret.shada And to restore this again: > :source ~/.vim/secret.vim - :rviminfo! ~/.vim/secret.shada + :rshada! ~/.vim/secret.shada ============================================================================== *21.5* Views diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt index 4c44d45436..df5d367469 100644 --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -370,7 +370,7 @@ N *+reltime* |reltime()| function, 'hlsearch'/'incsearch' timeout, 'redrawtime' option B *+rightleft* Right to left typing |'rightleft'| N *+scrollbind* |'scrollbind'| -N *+shada* |'viminfo'| +N *+shada* |'shada'| B *+signs* |:sign| N *+smartindent* |'smartindent'| N *+startuptime* |--startuptime| argument diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 5e4182fdae..f93175cccf 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -101,6 +101,10 @@ Additional differences: - |shada-s| now limits size of every item and not just registers. - When reading ShaDa files history, jump list and change list items are merged with those in currently running NeoVim instance according to the timestamp. +- 'viminfo' option got renamed to 'shada'. Old option is kept as an alias for + compatibility reasons. +- |:wviminfo| was renamed to |:wshada|, |:rviminfo| to |:rshada|. Old commands + are still kept. ============================================================================== 4. New Features *nvim-features-new* diff --git a/runtime/macros/less.vim b/runtime/macros/less.vim index 9042e849ca..d38bd3781d 100644 --- a/runtime/macros/less.vim +++ b/runtime/macros/less.vim @@ -42,7 +42,7 @@ set hlsearch set incsearch nohlsearch " Don't remember file names and positions -set viminfo= +set shada= set nows " Inhibit screen updates while searching let s:lz = &lz 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 @@ -2119,6 +2119,12 @@ return { addr_type=ADDR_LINES, 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), @@ -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', @@ -3031,6 +3037,12 @@ return { addr_type=ADDR_LINES, 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), @@ -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 3e0a2c1076..666431438e 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -9140,16 +9140,16 @@ 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_shada; save_shada = p_shada; if (*p_shada == NUL) p_shada = (char_u *)"'100"; - if (eap->cmdidx == CMD_rviminfo) { + if (eap->cmdidx == CMD_rviminfo || eap->cmdidx == CMD_rshada) { if (shada_read_everything((char *) eap->arg, eap->forceit) == FAIL) EMSG(_("E195: Cannot open ShaDa file for reading")); } else { diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 38442e3ab3..a7472b40e2 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -2177,7 +2177,7 @@ static void check_marks_read(void) 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 4ad0d2ec90..146109a414 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -892,7 +892,7 @@ EXTERN int skip_redraw INIT(= FALSE); /* skip redraw once */ EXTERN int do_redraw INIT(= FALSE); /* extra redraw once */ EXTERN int need_highlight_changed INIT(= TRUE); -EXTERN char *used_shada_file INIT(= NULL); /* name of viminfo file to use */ +EXTERN char *used_shada_file INIT(= NULL); /* name of ShaDa file to use */ #define NSCRIPT 15 EXTERN FILE *scriptin[NSCRIPT]; /* streams to read script from */ diff --git a/src/nvim/option.c b/src/nvim/option.c index 87bae0179c..3f90293e19 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -1738,7 +1738,7 @@ 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. @@ -1755,7 +1755,7 @@ int get_shada_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_shada_parameter(int type) @@ -2442,7 +2442,7 @@ did_set_string_option ( if (*p_vfile != NUL && verbose_open() == FAIL) errmsg = e_invarg; } - /* 'viminfo' */ + /* 'shada' */ else if (varp == &p_shada) { for (s = p_shada; *s; ) { /* Check it's a valid character */ diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index a4cb390523..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_shada; /* '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 e66c46e08a..5973e4b938 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1991,6 +1991,14 @@ return { vim="blank,buffers,curdir,folds,help,tabpages,winsize" }} }, + { + 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'}, diff --git a/src/nvim/shada.c b/src/nvim/shada.c index c853b00bac..23a69f5bed 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -152,7 +152,7 @@ enum SRNIFlags { | (1 << kSDItemSubString) | (1 << kSDItemGlobalMark) | (1 << kSDItemJump) - ), ///< Data reading which cannot be disabled by &viminfo or other options + ), ///< 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 @@ -163,11 +163,11 @@ enum SRNIFlags { ///< disabled by &history). kSDReadVariables = (1 << kSDItemVariable), ///< Determines whether variables ///< should be read (disabled by - ///< removing ! from &viminfo). + ///< removing ! from &shada). kSDReadBufferList = (1 << kSDItemBufferList), ///< Determines whether buffer ///< list should be read ///< (disabled by removing - ///< % entry from &viminfo). + ///< % entry from &shada). kSDReadUnknown = (1 << (SHADA_LAST_ENTRY + 1)), ///< Determines whether ///< unknown items should be ///< read (usually disabled). @@ -175,7 +175,7 @@ enum SRNIFlags { (1 << kSDItemLocalMark) | (1 << kSDItemChange) ), ///< Determines whether local marks and change list should be read. Can - ///< only be disabled by disabling &viminfo or putting '0 there. + ///< 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. @@ -1029,8 +1029,8 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) /// Get the ShaDa file name to use /// /// If "file" is given and not empty, use it (has already been expanded by -/// cmdline functions). Otherwise use "-i file_name", value from 'viminfo' or -/// the default, and expand environment variables. +/// 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. /// @@ -3013,7 +3013,7 @@ static ShadaEntry *list_buffer_marks(const buf_T *const buf) return ret; } -/// Check whether "name" is on removable media (according to 'viminfo') +/// Check whether "name" is on removable media (according to 'shada') /// /// @param[in] name Checked name. /// 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/test/functional/ex_cmds/wviminfo_spec.lua b/test/functional/ex_cmds/wviminfo_spec.lua index 053555c267..0c85157a0f 100644 --- a/test/functional/ex_cmds/wviminfo_spec.lua +++ b/test/functional/ex_cmds/wviminfo_spec.lua @@ -3,8 +3,8 @@ local clear, execute, eq, neq, spawn, nvim_prog, set_session, wait, write_file = helpers.clear, helpers.execute, helpers.eq, helpers.neq, helpers.spawn, helpers.nvim_prog, helpers.set_session, helpers.wait, helpers.write_file -describe(':wviminfo', function() - local shada_file = 'wviminfo_test' +describe(':wshada', function() + local shada_file = 'wshada_test' local session before_each(function() @@ -23,14 +23,14 @@ describe(':wviminfo', function() it('creates a shada file', function() -- file should _not_ exist eq(nil, lfs.attributes(shada_file)) - execute('wv! '..shada_file) + execute('wsh! '..shada_file) wait() -- file _should_ exist neq(nil, lfs.attributes(shada_file)) end) it('overwrites existing files', function() - local text = 'wviminfo test' + local text = 'wshada test' -- Create a dummy file write_file(shada_file, text) @@ -39,7 +39,7 @@ describe(':wviminfo', function() eq(text, io.open(shada_file):read()) neq(nil, lfs.attributes(shada_file)) - execute('wv! '..shada_file) + execute('wsh! '..shada_file) wait() -- File should have been overwritten with a shada file. diff --git a/test/functional/legacy/074_global_var_in_viminfo_spec.lua b/test/functional/legacy/074_global_var_in_viminfo_spec.lua index f017ed80a8..2428b7f74d 100644 --- a/test/functional/legacy/074_global_var_in_viminfo_spec.lua +++ b/test/functional/legacy/074_global_var_in_viminfo_spec.lua @@ -28,7 +28,7 @@ describe('storing global variables in ShaDa files', function() execute( -- This will cause a few errors, do it silently. 'set visualbell', - 'set viminfo+=!', + 'set shada+=!', "let MY_GLOBAL_DICT={'foo': 1, 'bar': 0, 'longvarible': 1000}", -- Store a really long list. Initially this was testing line wrapping in -- viminfo, but shada files has no line wrapping, no matter how long the @@ -38,7 +38,7 @@ describe('storing global variables in ShaDa files', function() eq(test_dict, eval('MY_GLOBAL_DICT')) eq(test_list, eval('MY_GLOBAL_LIST')) - execute('wv! ' .. tempname) + execute('wsh! ' .. tempname) wait() -- Assert that the shada file exists. @@ -49,7 +49,7 @@ describe('storing global variables in ShaDa files', function() eq(0, eval('exists("MY_GLOBAL_DICT")')) eq(0, eval('exists("MY_GLOBAL_LIST")')) - execute('rv! ' .. tempname) + execute('rsh! ' .. tempname) eq(test_list, eval('MY_GLOBAL_LIST')) eq(test_dict, eval('MY_GLOBAL_DICT')) diff --git a/test/functional/shada/buffers_spec.lua b/test/functional/shada/buffers_spec.lua index 97dafa533b..38a8cad84d 100644 --- a/test/functional/shada/buffers_spec.lua +++ b/test/functional/shada/buffers_spec.lua @@ -20,45 +20,45 @@ describe('ShaDa support code', function() after_each(clear) it('is able to dump and restore buffer list', function() - set_additional_cmd('set viminfo+=%') + set_additional_cmd('set shada+=%') reset() nvim_command('edit ' .. testfilename) nvim_command('edit ' .. testfilename_2) - -- nvim_command('redir! > /tmp/vistr | verbose set viminfo? | redir END') - -- nvim_command('wviminfo /tmp/foo') + -- nvim_command('redir! > /tmp/vistr | verbose set shada? | redir END') + -- nvim_command('wshada /tmp/foo') nvim_command('qall') reset() - -- nvim_command('call writefile([&viminfo], "/tmp/vistr")') + -- nvim_command('call writefile([&shada], "/tmp/vistr")') eq(3, nvim_eval('bufnr("$")')) eq('', nvim_eval('bufname(1)')) eq(testfilename, nvim_eval('bufname(2)')) eq(testfilename_2, nvim_eval('bufname(3)')) end) - it('does not restore buffer list without % in &viminfo', function() - set_additional_cmd('set viminfo+=%') + it('does not restore buffer list without % in &shada', function() + set_additional_cmd('set shada+=%') reset() nvim_command('edit ' .. testfilename) nvim_command('edit ' .. testfilename_2) - -- nvim_command('redir! > /tmp/vistr | verbose set viminfo? | redir END') - -- nvim_command('wviminfo /tmp/foo') + -- nvim_command('redir! > /tmp/vistr | verbose set shada? | redir END') + -- nvim_command('wshada /tmp/foo') set_additional_cmd('') nvim_command('qall') reset() - -- nvim_command('call writefile([&viminfo], "/tmp/vistr")') + -- nvim_command('call writefile([&shada], "/tmp/vistr")') eq(1, nvim_eval('bufnr("$")')) eq('', nvim_eval('bufname(1)')) end) - it('does not dump buffer list without % in &viminfo', function() + it('does not dump buffer list without % in &shada', function() nvim_command('edit ' .. testfilename) nvim_command('edit ' .. testfilename_2) - -- nvim_command('redir! > /tmp/vistr | verbose set viminfo? | redir END') - -- nvim_command('wviminfo /tmp/foo') - set_additional_cmd('set viminfo+=%') + -- nvim_command('redir! > /tmp/vistr | verbose set shada? | redir END') + -- nvim_command('wshada /tmp/foo') + set_additional_cmd('set shada+=%') nvim_command('qall') reset() - -- nvim_command('call writefile([&viminfo], "/tmp/vistr")') + -- nvim_command('call writefile([&shada], "/tmp/vistr")') eq(1, nvim_eval('bufnr("$")')) eq('', nvim_eval('bufname(1)')) end) diff --git a/test/functional/shada/history_spec.lua b/test/functional/shada/history_spec.lua index 5a7db2bfa6..595938bfb2 100644 --- a/test/functional/shada/history_spec.lua +++ b/test/functional/shada/history_spec.lua @@ -13,23 +13,23 @@ describe('ShaDa support code', function() after_each(clear) it('is able to dump and read back command-line history', function() - nvim_command('set viminfo=\'0') + nvim_command('set shada=\'0') nvim_feed(':" Test\n') - nvim_command('wviminfo') + nvim_command('wshada') reset() - nvim_command('set viminfo=\'0') - nvim_command('rviminfo') + nvim_command('set shada=\'0') + nvim_command('rshada') eq('" Test', nvim_eval('histget(":", -1)')) end) it('is able to dump and read back 2 items in command-line history', function() - nvim_command('set viminfo=\'0 history=2') + nvim_command('set shada=\'0 history=2') nvim_feed(':" Test\n') nvim_feed(':" Test 2\n') nvim_command('qall') reset() - nvim_command('set viminfo=\'0 history=2') - nvim_command('rviminfo') + nvim_command('set shada=\'0 history=2') + nvim_command('rshada') eq('" Test 2', nvim_eval('histget(":", -1)')) eq('" Test', nvim_eval('histget(":", -2)')) nvim_command('qall') @@ -37,50 +37,50 @@ describe('ShaDa support code', function() it('respects &history when dumping', function() - nvim_command('set viminfo=\'0 history=1') + nvim_command('set shada=\'0 history=1') nvim_feed(':" Test\n') nvim_feed(':" Test 2\n') - nvim_command('wviminfo') + nvim_command('wshada') reset() - nvim_command('set viminfo=\'0 history=2') - nvim_command('rviminfo') + nvim_command('set shada=\'0 history=2') + nvim_command('rshada') eq('" Test 2', nvim_eval('histget(":", -1)')) eq('', nvim_eval('histget(":", -2)')) end) it('respects &history when loading', function() - nvim_command('set viminfo=\'0 history=2') + nvim_command('set shada=\'0 history=2') nvim_feed(':" Test\n') nvim_feed(':" Test 2\n') - nvim_command('wviminfo') + nvim_command('wshada') reset() - nvim_command('set viminfo=\'0 history=1') - nvim_command('rviminfo') + nvim_command('set shada=\'0 history=1') + nvim_command('rshada') eq('" Test 2', nvim_eval('histget(":", -1)')) eq('', nvim_eval('histget(":", -2)')) end) it('dumps only requested amount of command-line history items', function() - nvim_command('set viminfo=\'0,:1') + nvim_command('set shada=\'0,:1') nvim_feed(':" Test\n') nvim_feed(':" Test 2\n') - nvim_command('wviminfo') + nvim_command('wshada') reset() - nvim_command('set viminfo=\'0') - nvim_command('rviminfo') + nvim_command('set shada=\'0') + nvim_command('rshada') eq('" Test 2', nvim_eval('histget(":", -1)')) eq('', nvim_eval('histget(":", -2)')) end) - it('does not respect number in &viminfo when loading history', function() - nvim_command('set viminfo=\'0') + it('does not respect number in &shada when loading history', function() + nvim_command('set shada=\'0') nvim_feed(':" Test\n') nvim_feed(':" Test 2\n') - nvim_command('wviminfo') + nvim_command('wshada') reset() - nvim_command('set viminfo=\'0,:1') - nvim_command('rviminfo') + nvim_command('set shada=\'0,:1') + nvim_command('rshada') eq('" Test 2', nvim_eval('histget(":", -1)')) eq('" Test', nvim_eval('histget(":", -2)')) end) @@ -93,9 +93,9 @@ describe('ShaDa support code', function() nvim_feed('/Test\n') -- Search history nvim_feed(':" Test\n') -- Command-line history nvim_command('0debuggreedy') - nvim_command('wviminfo') + nvim_command('wshada') reset() - nvim_command('rviminfo') + nvim_command('rshada') eq('" Test', nvim_eval('histget(":", -1)')) eq('Test', nvim_eval('histget("/", -1)')) eq('"Test"', nvim_eval('histget("=", -1)')) @@ -107,7 +107,7 @@ describe('ShaDa support code', function() nvim_eval('setline(".", ["foo", "bar"])') nvim_feed('gg0/a/e+1\n') eq({0, 2, 3, 0}, nvim_eval('getpos(".")')) - nvim_command('wviminfo') + nvim_command('wshada') reset() nvim_eval('setline(".", ["foo", "bar"])') nvim_feed('gg0n') @@ -118,7 +118,7 @@ describe('ShaDa support code', function() nvim_eval('setline(".", ["foo", "bar"])') nvim_command('%s/f/g/g') eq('goo', nvim_eval('getline(1)')) - nvim_command('wviminfo') + nvim_command('wshada') reset() nvim_eval('setline(".", ["foo", "bar"])') nvim_command('&') @@ -241,7 +241,7 @@ describe('ShaDa support code', function() reset() -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1 nvim_command('silent! /\171/') - nvim_command('set viminfo+=/0') + nvim_command('set shada+=/0') nvim_command('qall!') reset() nvim_eval('setline(".", ["\171«"])') @@ -256,7 +256,7 @@ describe('ShaDa support code', function() reset() -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1 nvim_command('silent! /\171/') - nvim_command('set viminfo+=/0') + nvim_command('set shada+=/0') set_additional_cmd('') nvim_command('qall') reset() @@ -270,7 +270,7 @@ describe('ShaDa support code', function() function() -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1 nvim_command('silent! /«/') - nvim_command('set viminfo+=/0') + nvim_command('set shada+=/0') set_additional_cmd('set encoding=latin1') nvim_command('qall') reset() diff --git a/test/functional/shada/marks_spec.lua b/test/functional/shada/marks_spec.lua index 25362b406b..b69477b6ed 100644 --- a/test/functional/shada/marks_spec.lua +++ b/test/functional/shada/marks_spec.lua @@ -38,9 +38,9 @@ describe('ShaDa support code', function() nvim_command('mark A') nvim_command('2') nvim_command('kB') - nvim_command('wviminfo') + nvim_command('wshada') reset() - nvim_command('rviminfo') + nvim_command('rshada') nvim_command('normal! `A') eq(testfilename, nvim_eval('fnamemodify(@%, ":t")')) eq(1, nvim_current_line()) @@ -48,13 +48,13 @@ describe('ShaDa support code', function() eq(2, nvim_current_line()) end) - it('does not dump global mark with `f0` in viminfo', function() - nvim_command('set viminfo+=f0') + it('does not dump global mark with `f0` in shada', function() + nvim_command('set shada+=f0') nvim_command('edit ' .. testfilename) nvim_command('mark A') nvim_command('2') nvim_command('kB') - nvim_command('wviminfo') + nvim_command('wshada') reset() nvim_command('language C') nvim_command([[ @@ -66,13 +66,13 @@ describe('ShaDa support code', function() eq('Vim(normal):E20: Mark not set', nvim('get_var', 'exception')) end) - it('does read back global mark even with `\'0` and `f0` in viminfo', function() + it('does read back global mark even with `\'0` and `f0` in shada', function() nvim_command('edit ' .. testfilename) nvim_command('mark A') nvim_command('2') nvim_command('kB') - nvim_command('wviminfo') - set_additional_cmd('set viminfo=\'0,f0') + nvim_command('wshada') + set_additional_cmd('set shada=\'0,f0') reset() nvim_command('language C') nvim_command('normal! `A') @@ -103,7 +103,7 @@ describe('ShaDa support code', function() nvim_feed('G') nvim_feed('gg') -- nvim_command('redir! >/tmp/jumps.last | jumps | redir END') - -- nvim_command('wviminfo /tmp/foo') + -- nvim_command('wshada /tmp/foo') nvim_command('qall') reset() nvim_command('redraw') @@ -140,7 +140,7 @@ describe('ShaDa support code', function() nvim_command('sleep 2') nvim_feed('gg') -- nvim_command('redir! >/tmp/jumps.last | jumps | redir END') - -- nvim_command('wviminfo /tmp/foo') + -- nvim_command('wshada /tmp/foo') nvim_command('qall') reset() nvim_command('redraw') @@ -170,7 +170,7 @@ describe('ShaDa support code', function() nvim_command('qall!') reset() nvim_command('edit ' .. testfilename) - -- nvim_command('rviminfo') + -- nvim_command('rshada') -- nvim_command('redir! >/tmp/changes | changes | redir END') nvim_feed('Gg;') -- Note: without “sync” “commands” test has good changes to fail for unknown diff --git a/test/functional/shada/registers_spec.lua b/test/functional/shada/registers_spec.lua index 7efb100cd6..67b0661151 100644 --- a/test/functional/shada/registers_spec.lua +++ b/test/functional/shada/registers_spec.lua @@ -48,7 +48,7 @@ describe('ShaDa support code', function() end) it('does not dump registers with zero <', function() - nvim_command('set viminfo=\'0,<0') + nvim_command('set shada=\'0,<0') setreg('c', {'d', 'e', ''}, 'c') setreg('l', {'a', 'b', 'cde'}, 'l') setreg('b', {'bca', 'abc', 'cba'}, 'b3') @@ -63,7 +63,7 @@ describe('ShaDa support code', function() setreg('c', {'d', 'e', ''}, 'c') setreg('l', {'a', 'b', 'cde'}, 'l') setreg('b', {'bca', 'abc', 'cba'}, 'b3') - set_additional_cmd('set viminfo=\'0,<0') + set_additional_cmd('set shada=\'0,<0') nvim_command('qa') reset() eq({{'d', 'e', ''}, 'v'}, getreg('c')) @@ -72,7 +72,7 @@ describe('ShaDa support code', function() end) it('does not dump registers with zero "', function() - nvim_command('set viminfo=\'0,\\"0') + nvim_command('set shada=\'0,\\"0') setreg('c', {'d', 'e', ''}, 'c') setreg('l', {'a', 'b', 'cde'}, 'l') setreg('b', {'bca', 'abc', 'cba'}, 'b3') @@ -87,7 +87,7 @@ describe('ShaDa support code', function() setreg('c', {'d', 'e', ''}, 'c') setreg('l', {'a', 'b', 'cde'}, 'l') setreg('b', {'bca', 'abc', 'cba'}, 'b3') - set_additional_cmd('set viminfo=\'0,\\"0') + set_additional_cmd('set shada=\'0,\\"0') nvim_command('qa') reset() eq({{'d', 'e', ''}, 'v'}, getreg('c')) @@ -96,7 +96,7 @@ describe('ShaDa support code', function() end) it('does dump registers with zero ", but non-zero <', function() - nvim_command('set viminfo=\'0,\\"0,<50') + nvim_command('set shada=\'0,\\"0,<50') setreg('c', {'d', 'e', ''}, 'c') setreg('l', {'a', 'b', 'cde'}, 'l') setreg('b', {'bca', 'abc', 'cba'}, 'b3') @@ -108,7 +108,7 @@ describe('ShaDa support code', function() end) it('does limit number of lines according to <', function() - nvim_command('set viminfo=\'0,<2') + nvim_command('set shada=\'0,<2') setreg('o', {'d'}, 'c') setreg('t', {'a', 'b', 'cde'}, 'l') nvim_command('qa') @@ -118,7 +118,7 @@ describe('ShaDa support code', function() end) it('does limit number of lines according to "', function() - nvim_command('set viminfo=\'0,\\"2') + nvim_command('set shada=\'0,\\"2') setreg('o', {'d'}, 'c') setreg('t', {'a', 'b', 'cde'}, 'l') nvim_command('qa') @@ -128,7 +128,7 @@ describe('ShaDa support code', function() end) it('does limit number of lines according to < rather then "', function() - nvim_command('set viminfo=\'0,\\"2,<3') + nvim_command('set shada=\'0,\\"2,<3') setreg('o', {'d'}, 'c') setreg('t', {'a', 'b', 'cde'}, 'l') setreg('h', {'abc', 'acb', 'bac', 'bca', 'cab', 'cba'}, 'b3') diff --git a/test/functional/shada/variables_spec.lua b/test/functional/shada/variables_spec.lua index 0ffa524238..c2fa25bcb1 100644 --- a/test/functional/shada/variables_spec.lua +++ b/test/functional/shada/variables_spec.lua @@ -14,21 +14,21 @@ describe('ShaDa support code', function() it('is able to dump and read back string variable', function() nvim('set_var', 'STRVAR', 'foo') - nvim_command('set viminfo+=!') - nvim_command('wviminfo') + nvim_command('set shada+=!') + nvim_command('wshada') reset() - nvim_command('set viminfo+=!') - nvim_command('rviminfo') + nvim_command('set shada+=!') + nvim_command('rshada') eq('foo', nvim('get_var', 'STRVAR')) end) local autotest = function(tname, varname, varval) it('is able to dump and read back ' .. tname .. ' variable automatically', function() - set_additional_cmd('set viminfo+=!') + set_additional_cmd('set shada+=!') reset() nvim('set_var', varname, varval) - -- Exit during `reset` is not a regular exit: it does not write viminfo + -- Exit during `reset` is not a regular exit: it does not write shada -- automatically nvim_command('qall') reset() @@ -42,43 +42,43 @@ describe('ShaDa support code', function() autotest('dictionary', 'DCTVAR', {a=10}) autotest('list', 'LSTVAR', {{a=10}, {b=10.5}, {c='str'}}) - it('does not read back variables without `!` in &viminfo', function() + it('does not read back variables without `!` in &shada', function() nvim('set_var', 'STRVAR', 'foo') - nvim_command('set viminfo+=!') - nvim_command('wviminfo') - set_additional_cmd('set viminfo-=!') + nvim_command('set shada+=!') + nvim_command('wshada') + set_additional_cmd('set shada-=!') reset() - nvim_command('rviminfo') + nvim_command('rshada') eq(0, nvim_eval('exists("g:STRVAR")')) end) - it('does not dump variables without `!` in &viminfo', function() - nvim_command('set viminfo-=!') + it('does not dump variables without `!` in &shada', function() + nvim_command('set shada-=!') nvim('set_var', 'STRVAR', 'foo') - nvim_command('wviminfo') + nvim_command('wshada') reset() - nvim_command('set viminfo+=!') - nvim_command('rviminfo') + nvim_command('set shada+=!') + nvim_command('rshada') eq(0, nvim_eval('exists("g:STRVAR")')) end) it('does not dump session variables', function() - nvim_command('set viminfo+=!') + nvim_command('set shada+=!') nvim('set_var', 'StrVar', 'foo') - nvim_command('wviminfo') + nvim_command('wshada') reset() - nvim_command('set viminfo+=!') - nvim_command('rviminfo') + nvim_command('set shada+=!') + nvim_command('rshada') eq(0, nvim_eval('exists("g:StrVar")')) end) it('does not dump regular variables', function() - nvim_command('set viminfo+=!') + nvim_command('set shada+=!') nvim('set_var', 'str_var', 'foo') - nvim_command('wviminfo') + nvim_command('wshada') reset() - nvim_command('set viminfo+=!') - nvim_command('rviminfo') + nvim_command('set shada+=!') + nvim_command('rshada') eq(0, nvim_eval('exists("g:str_var")')) end) -- cgit From 45ad8103413988227f8c18922a44ff02bd069ecf Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 6 Jul 2015 20:12:18 +0300 Subject: shada: Add more size_t casts As usual, GCC is overparanoid with -Wconversion. --- src/nvim/shada.c | 57 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 23a69f5bed..6bcf857444 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1156,19 +1156,19 @@ static void shada_pack_entry(msgpack_packer *const packer, const size_t map_size = (size_t) ( 1 // Search pattern is always present // Following items default to true: - + !entry.data.search_pattern.magic - + !entry.data.search_pattern.is_last_used + + (size_t) !entry.data.search_pattern.magic + + (size_t) !entry.data.search_pattern.is_last_used // Following items default to false: - + entry.data.search_pattern.smartcase - + entry.data.search_pattern.has_line_offset - + entry.data.search_pattern.place_cursor_at_end - + entry.data.search_pattern.is_substitute_pattern + + (size_t) entry.data.search_pattern.smartcase + + (size_t) entry.data.search_pattern.has_line_offset + + (size_t) entry.data.search_pattern.place_cursor_at_end + + (size_t) entry.data.search_pattern.is_substitute_pattern // offset defaults to zero: - + (entry.data.search_pattern.offset != 0) + + (size_t) (entry.data.search_pattern.offset != 0) // finally, additional data: - + (entry.data.search_pattern.additional_data - ? entry.data.search_pattern.additional_data->size - : 0) + + (size_t) (entry.data.search_pattern.additional_data + ? entry.data.search_pattern.additional_data->size + : 0) ); msgpack_pack_map(spacker, map_size); PACK_STATIC_STR("pat"); @@ -1211,17 +1211,17 @@ static void shada_pack_entry(msgpack_packer *const packer, const size_t map_size = (size_t) ( 1 // File name // Line: defaults to 1 - + (entry.data.filemark.mark.lnum != 1) + + (size_t) (entry.data.filemark.mark.lnum != 1) // Column: defaults to zero: - + (entry.data.filemark.mark.col != 0) + + (size_t) (entry.data.filemark.mark.col != 0) // Mark name: defaults to '"' - + (entry.type != kSDItemJump - && entry.type != kSDItemChange - && entry.data.filemark.name != '"') + + (size_t) (entry.type != kSDItemJump + && entry.type != kSDItemChange + && entry.data.filemark.name != '"') // Additional entries, if any: - + (entry.data.filemark.additional_data == NULL - ? 0 - : entry.data.filemark.additional_data->size) + + (size_t) (entry.data.filemark.additional_data == NULL + ? 0 + : entry.data.filemark.additional_data->size) ); msgpack_pack_map(spacker, map_size); PACK_STATIC_STR("file"); @@ -1255,13 +1255,13 @@ static void shada_pack_entry(msgpack_packer *const packer, const size_t map_size = (size_t) ( 2 // Register contents and name // Register type: defaults to MCHAR - + (entry.data.reg.type != MCHAR) + + (size_t) (entry.data.reg.type != MCHAR) // Register width: defaults to zero - + (entry.data.reg.width != 0) + + (size_t) (entry.data.reg.width != 0) // Additional entries, if any: - + (entry.data.reg.additional_data == NULL - ? 0 - : entry.data.reg.additional_data->size) + + (size_t) (entry.data.reg.additional_data == NULL + ? 0 + : entry.data.reg.additional_data->size) ); msgpack_pack_map(spacker, map_size); PACK_STATIC_STR("contents"); @@ -1297,13 +1297,14 @@ static void shada_pack_entry(msgpack_packer *const packer, const size_t map_size = (size_t) ( 1 // Buffer name // Line number: defaults to 1 - + (entry.data.buffer_list.buffers[i].pos.lnum != 1) + + (size_t) (entry.data.buffer_list.buffers[i].pos.lnum != 1) // Column number: defaults to 0 - + (entry.data.buffer_list.buffers[i].pos.col != 0) + + (size_t) (entry.data.buffer_list.buffers[i].pos.col != 0) // Additional entries, if any: - + (entry.data.buffer_list.buffers[i].additional_data == NULL - ? 0 - : entry.data.buffer_list.buffers[i].additional_data->size) + + (size_t) ( + entry.data.buffer_list.buffers[i].additional_data == NULL + ? 0 + : entry.data.buffer_list.buffers[i].additional_data->size) ); msgpack_pack_map(spacker, map_size); PACK_STATIC_STR("file"); -- cgit From 7abe20f3d31b38bd21f7f4546303c199c4574830 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 6 Jul 2015 20:16:07 +0300 Subject: main: Silence -Wunused-result in main.c for shada_read_file --- src/nvim/main.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/nvim/main.c b/src/nvim/main.c index bc95980afe..e8af356654 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -383,9 +383,7 @@ int main(int argc, char **argv) * This is where v:oldfiles gets filled. */ if (*p_shada != NUL) { - (void) shada_read_file(NULL, (kShaDaWantInfo - | kShaDaGetOldfiles - | kShaDaWantMarks)); + shada_read_everything(NULL, false); TIME_MSG("reading ShaDa"); } /* It's better to make v:oldfiles an empty list than NULL. */ -- cgit From f43a5e692647de81a692e537f1e748667ff84fb4 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 7 Jul 2015 23:05:01 +0300 Subject: shada: Save v:hlsearch value --- src/nvim/shada.c | 12 +++++++++++- test/functional/shada/history_spec.lua | 25 +++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 6bcf857444..6cbaf110c6 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -200,7 +200,7 @@ typedef struct { int64_t offset; bool is_last_used; bool is_substitute_pattern; - // TODO(ZyX-I): Also store v:hlsearch, see :h shada-h + bool highlighted; char *pat; Dictionary *additional_data; } search_pattern; @@ -740,6 +740,9 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) set_last_used_pattern( cur_entry.data.search_pattern.is_substitute_pattern); } + if (!cur_entry.data.search_pattern.is_substitute_pattern) { + SET_NO_HLSEARCH(!cur_entry.data.search_pattern.highlighted); + } // Do not free shada entry: its allocated memory was saved above. break; } @@ -1163,6 +1166,7 @@ static void shada_pack_entry(msgpack_packer *const packer, + (size_t) entry.data.search_pattern.has_line_offset + (size_t) entry.data.search_pattern.place_cursor_at_end + (size_t) entry.data.search_pattern.is_substitute_pattern + + (size_t) entry.data.search_pattern.highlighted // offset defaults to zero: + (size_t) (entry.data.search_pattern.offset != 0) // finally, additional data: @@ -1187,6 +1191,7 @@ static void shada_pack_entry(msgpack_packer *const packer, PACK_BOOL("lineoff", has_line_offset, true); PACK_BOOL("curatend", place_cursor_at_end, true); PACK_BOOL("sub", is_substitute_pattern, true); + PACK_BOOL("hlsearch", highlighted, true); if (entry.data.search_pattern.offset) { PACK_STATIC_STR("off"); msgpack_pack_int64(spacker, entry.data.search_pattern.offset); @@ -1548,6 +1553,7 @@ static void shada_write(ShaDaWriteDef *const sd_writer, .offset = pat.off.off, .is_last_used = search_was_last_used(), .is_substitute_pattern = false, + .highlighted = (!no_hlsearch && find_shada_parameter('h') != NULL), .pat = (char *) pat.pat, .additional_data = pat.additional_data, } @@ -1572,6 +1578,7 @@ static void shada_write(ShaDaWriteDef *const sd_writer, .offset = 0, .is_last_used = !search_was_last_used(), .is_substitute_pattern = true, + .highlighted = false, .pat = (char *) pat.pat, .additional_data = pat.additional_data, } @@ -2338,6 +2345,7 @@ shada_read_next_item_start: .offset = 0, .is_last_used = true, .is_substitute_pattern = false, + .highlighted = false, .pat = NULL, .additional_data = NULL, }; @@ -2356,6 +2364,8 @@ shada_read_next_item_start: entry->data.search_pattern.is_last_used) else BOOLEAN_KEY("search pattern", "sub", entry->data.search_pattern.is_substitute_pattern) + else BOOLEAN_KEY("search pattern", "hlsearch", + entry->data.search_pattern.highlighted) else INTEGER_KEY("search pattern", "off", entry->data.search_pattern.offset) else CONVERTED_STRING_KEY("search pattern", "pat", diff --git a/test/functional/shada/history_spec.lua b/test/functional/shada/history_spec.lua index 595938bfb2..c4d39bd547 100644 --- a/test/functional/shada/history_spec.lua +++ b/test/functional/shada/history_spec.lua @@ -114,6 +114,31 @@ describe('ShaDa support code', function() eq({0, 2, 3, 0}, nvim_eval('getpos(".")')) end) + it('saves v:hlsearch=1', function() + nvim_command('set hlsearch') + nvim_feed('/test\n') + nvim_command('qall') + reset() + eq(1, nvim_eval('v:hlsearch')) + end) + + it('saves v:hlsearch=0 with :nohl', function() + nvim_command('set hlsearch') + nvim_feed('/test\n') + nvim_command('nohlsearch') + nvim_command('qall') + reset() + eq(0, nvim_eval('v:hlsearch')) + end) + + it('saves v:hlsearch=0 with :set viminfo-=h', function() + nvim_command('set hlsearch viminfo-=h') + nvim_feed('/test\n') + nvim_command('qall') + reset() + eq(0, nvim_eval('v:hlsearch')) + end) + it('dumps and loads last substitute pattern and replacement string', function() nvim_eval('setline(".", ["foo", "bar"])') nvim_command('%s/f/g/g') -- cgit From 1d3823a5c9c7b7494966d4e0851e2c1decbed76c Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 7 Jul 2015 23:49:22 +0300 Subject: shada: Populate v:oldfiles --- src/nvim/shada.c | 107 ++++++++++++++++++++++++++++------- test/functional/shada/marks_spec.lua | 15 +++++ 2 files changed, 101 insertions(+), 21 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 6cbaf110c6..3dbd24b7ea 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -60,6 +60,7 @@ KHASH_SET_INIT_INT64(bufset) # 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__)) @@ -171,11 +172,16 @@ enum SRNIFlags { kSDReadUnknown = (1 << (SHADA_LAST_ENTRY + 1)), ///< Determines whether ///< unknown items should be ///< read (usually disabled). - kSDReadLocalMarks = ( - (1 << kSDItemLocalMark) - | (1 << kSDItemChange) - ), ///< Determines whether local marks and change list should be read. Can - ///< only be disabled by disabling &shada or putting '0 there. + 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. @@ -475,6 +481,18 @@ static inline bool in_bufset(const khash_t(bufset) *const set, const buf_T *buf) 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); +} + /// Check whether buffer is on removable media /// /// Uses pre-populated set with buffers on removable media named removable_bufs. @@ -673,8 +691,10 @@ static inline bool marks_equal(const pos_T a, const pos_T b) static void shada_read(ShaDaReadDef *const sd_reader, const int flags) FUNC_ATTR_NONNULL_ALL { - // TODO(ZyX-I): Also load v:oldfiles. unsigned srni_flags = 0; + const bool force = flags & kShaDaForceit; + const bool get_old_files = flags & (kShaDaGetOldfiles | kShaDaForceit); + const bool want_marks = flags & kShaDaWantMarks; if (flags & kShaDaWantInfo) { srni_flags |= kSDReadUndisableableData | kSDReadRegisters; if (p_hi) { @@ -687,16 +707,18 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) srni_flags |= kSDReadBufferList; } } - if (flags & kShaDaWantMarks) { + if (want_marks) { if (get_shada_parameter('\'') > 0) { - srni_flags |= kSDReadLocalMarks; + srni_flags |= kSDReadLocalMarks | kSDReadChanges; } } + if (get_old_files) { + srni_flags |= kSDReadLocalMarks; + } if (srni_flags == 0) { // Nothing to do. return; } - const bool force = flags & kShaDaForceit; HistoryMergerState hms[HIST_COUNT]; if (srni_flags & kSDReadHistory) { for (uint8_t i = 0; i < HIST_COUNT; i++) { @@ -707,8 +729,23 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) } } ShadaEntry cur_entry; - khash_t(bufset) *cl_bufs = kh_init(bufset); - khash_t(fnamebufs) *fname_bufs = kh_init(fnamebufs); + khash_t(bufset) *cl_bufs = NULL; + if (srni_flags & kSDReadChanges) { + cl_bufs = kh_init(bufset); + } + khash_t(fnamebufs) *fname_bufs = NULL; + if (srni_flags & (kSDReadUndisableableData + | kSDReadChanges + | kSDReadLocalMarks)) { + fname_bufs = kh_init(fnamebufs); + } + khash_t(strset) *oldfiles_set = NULL; + list_T *oldfiles_list = NULL; + if (get_old_files) { + oldfiles_set = kh_init(strset); + oldfiles_list = list_alloc(); + set_vim_var_list(VV_OLDFILES, oldfiles_list); + } while (shada_read_next_item(sd_reader, &cur_entry, srni_flags) == NOTDONE) { switch (cur_entry.type) { case kSDItemMissing: { @@ -904,6 +941,27 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) } case kSDItemChange: case kSDItemLocalMark: { + if (oldfiles_set != NULL + && !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); @@ -1015,18 +1073,25 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) hm_rb_dealloc(&(hms[i].hmrb)); } } - FOR_ALL_TAB_WINDOWS(tp, wp) { - (void) tp; - if (in_bufset(cl_bufs, wp->w_buffer)) { - wp->w_changelistidx = wp->w_buffer->b_changelistlen; + if (cl_bufs != NULL) { + 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_destroy(bufset, cl_bufs); + } + if (fname_bufs != NULL) { + const char *key; + kh_foreach_key(fname_bufs, key, { + xfree((void *) key); + }) + kh_destroy(fnamebufs, fname_bufs); + } + if (oldfiles_set != NULL) { + kh_destroy(strset, oldfiles_set); } - kh_destroy(bufset, cl_bufs); - const char *key; - kh_foreach_key(fname_bufs, key, { - xfree((void *) key); - }) - kh_destroy(fnamebufs, fname_bufs); } /// Get the ShaDa file name to use diff --git a/test/functional/shada/marks_spec.lua b/test/functional/shada/marks_spec.lua index b69477b6ed..4909ae608e 100644 --- a/test/functional/shada/marks_spec.lua +++ b/test/functional/shada/marks_spec.lua @@ -95,6 +95,21 @@ describe('ShaDa support code', function() eq(2, nvim_current_line()) end) + it('is able to populate v:oldfiles', function() + nvim_command('edit ' .. testfilename) + local tf_full = nvim_eval('fnamemodify(bufname("%"), ":p")') + nvim_command('edit ' .. testfilename_2) + local tf_full_2 = nvim_eval('fnamemodify(bufname("%"), ":p")') + nvim_command('qall') + reset() + local oldfiles = nvim('get_vvar', 'oldfiles') + eq(2, #oldfiles) + eq(testfilename, oldfiles[1]:sub(-#testfilename)) + eq(testfilename_2, oldfiles[2]:sub(-#testfilename_2)) + eq(tf_full, oldfiles[1]) + eq(tf_full_2, oldfiles[2]) + end) + it('is able to dump and restore jump list', function() nvim_command('edit ' .. testfilename_2) nvim_feed('G') -- cgit From b905c8a942a74aa350c6c50def02d1f6e7b56091 Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 8 Jul 2015 00:10:07 +0300 Subject: shada: Preserve existing file permissions --- src/nvim/shada.c | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 3dbd24b7ea..743adfb20f 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -82,6 +82,8 @@ KHASH_SET_INIT_STR(strset) (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)) // From http://www.boost.org/doc/libs/1_43_0/boost/detail/endian.hpp + some // additional checks done after examining `{compiler} -dM -E - < /dev/null` @@ -1810,16 +1812,41 @@ int shada_write_file(const char *const file, bool nomerge) nomerge = true; goto shada_write_file_nomerge; } +#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; + 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)))) { + EMSG2(_("E137: ShaDa file is not writable: %s"), fname); + close_file((int)(intptr_t) sd_reader.cookie); + xfree(fname); + return FAIL; + } +#endif 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: - // TODO(ZyX-I): Preserve existing permissions fd = (intptr_t) open_file(tempname, O_CREAT|O_WRONLY|O_NOFOLLOW|O_EXCL, - 0600); + perm); if (fd < 0) { if (-fd == EEXIST #ifdef ELOOP -- cgit From 6f6497f5e00292adb4df12fea9cf13647e0edda3 Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 8 Jul 2015 21:55:12 +0300 Subject: shada: Refactor history merging support to separate functions --- src/nvim/shada.c | 145 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 94 insertions(+), 51 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 743adfb20f..2a4708f14a 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -611,9 +611,8 @@ static const void *shada_hist_iter(const void *const iter, /// @param[in] entry Inserted entry. /// @param[in] do_iter Determines whether NeoVim own history should be /// used. -static void insert_history_entry(HistoryMergerState *const hms_p, - const ShadaEntry entry, - const bool no_iter) +static void hms_insert(HistoryMergerState *const hms_p, const ShadaEntry entry, + const bool no_iter) { HMRingBuffer *const rb = &(hms_p->hmrb); RINGBUF_FORALL(rb, ShadaEntry, cur_entry) { @@ -630,14 +629,14 @@ static void insert_history_entry(HistoryMergerState *const hms_p, if (hms_p->iter == NULL) { if (hms_p->last_hist_entry.type != kSDItemMissing && hms_p->last_hist_entry.timestamp < entry.timestamp) { - insert_history_entry(hms_p, hms_p->last_hist_entry, false); + hms_insert(hms_p, hms_p->last_hist_entry, false); hms_p->last_hist_entry.type = kSDItemMissing; } } else { while (hms_p->iter != NULL && hms_p->last_hist_entry.type != kSDItemMissing && hms_p->last_hist_entry.timestamp < entry.timestamp) { - insert_history_entry(hms_p, hms_p->last_hist_entry, false); + hms_insert(hms_p, hms_p->last_hist_entry, false); hms_p->iter = shada_hist_iter(hms_p->iter, hms_p->history_type, true, &(hms_p->last_hist_entry)); } @@ -652,6 +651,87 @@ static void insert_history_entry(HistoryMergerState *const hms_p, hm_rb_insert(rb, (size_t) (hm_rb_find_idx(rb, insert_after) + 1), 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. +static inline void hms_init(HistoryMergerState *const hms_p, + const uint8_t history_type, + const size_t num_elements, + const bool do_merge) + FUNC_ATTR_NONNULL_ALL +{ + hms_p->hmrb = hm_rb_new(num_elements); + hms_p->do_merge = do_merge; + hms_p->iter = shada_hist_iter(NULL, history_type, true, + &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 +{ + if (hms_p->last_hist_entry.type != kSDItemMissing) { + hms_insert(hms_p, hms_p->last_hist_entry, false); + } + while (hms_p->iter != NULL + && hms_p->last_hist_entry.type != kSDItemMissing) { + hms_p->iter = shada_hist_iter(hms_p->iter, hms_p->history_type, true, + &(hms_p->last_hist_entry)); + hms_insert(hms_p, hms_p->last_hist_entry, false); + } +} + +/// 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; + RINGBUF_FORALL(&hms_p->hmrb, ShadaEntry, cur_entry) { + hist->timestamp = cur_entry->timestamp; + hist->hisnum = (int) (hist - hist_array) + 1; + hist->hisstr = (char_u *) cur_entry->data.history_item.string; + hist->additional_elements = + cur_entry->data.history_item.additional_elements; + hist++; + } + *new_hisnum = (int) hm_rb_length(&hms_p->hmrb); + *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 +{ + hm_rb_dealloc(&hms_p->hmrb); +} + +/// 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) \ + RINGBUF_FORALL(&((hms_p)->hmrb), ShadaEntry, cur_entry) + /// Find buffer for given buffer name (cached) /// /// @param[in,out] fname_bufs Cache containing fname to buffer mapping. @@ -724,10 +804,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) HistoryMergerState hms[HIST_COUNT]; if (srni_flags & kSDReadHistory) { for (uint8_t i = 0; i < HIST_COUNT; i++) { - hms[i].hmrb = hm_rb_new((size_t) p_hi); - hms[i].do_merge = true; - hms[i].iter = shada_hist_iter(NULL, i, true, &(hms[i].last_hist_entry)); - hms[i].history_type = i; + hms_init(&hms[i], i, (size_t) p_hi, true); } } ShadaEntry cur_entry; @@ -799,8 +876,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) shada_free_shada_entry(&cur_entry); break; } - insert_history_entry(hms + cur_entry.data.history_item.histtype, - cur_entry, true); + hms_insert(hms + cur_entry.data.history_item.histtype, cur_entry, true); // Do not free shada entry: its allocated memory was saved above. break; } @@ -1046,33 +1122,15 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) // may be assigned right away. if (srni_flags & kSDReadHistory) { for (uint8_t i = 0; i < HIST_COUNT; i++) { - if (hms[i].last_hist_entry.type != kSDItemMissing) { - insert_history_entry(&(hms[i]), hms[i].last_hist_entry, false); - } - while (hms[i].iter != NULL - && hms[i].last_hist_entry.type != kSDItemMissing) { - hms[i].iter = shada_hist_iter(hms[i].iter, hms[i].history_type, true, - &(hms[i].last_hist_entry)); - insert_history_entry(&(hms[i]), hms[i].last_hist_entry, false); - } + 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) { - histentry_T *const hist_init = hist; - RINGBUF_FORALL(&(hms[i].hmrb), ShadaEntry, cur_entry) { - hist->timestamp = cur_entry->timestamp; - hist->hisnum = (int) (hist - hist_init) + 1; - hist->hisstr = (char_u *) cur_entry->data.history_item.string; - hist->additional_elements = - cur_entry->data.history_item.additional_elements; - hist++; - } - *new_hisnum = (int) hm_rb_length(&(hms[i].hmrb)); - *new_hisidx = *new_hisnum - 1; + hms_to_he_array(&hms[i], hist, new_hisidx, new_hisnum); } - hm_rb_dealloc(&(hms[i].hmrb)); + hms_dealloc(&hms[i]); } } if (cl_bufs != NULL) { @@ -1578,29 +1636,14 @@ static void shada_write(ShaDaWriteDef *const sd_writer, num_saved = p_hi; } if (num_saved > 0) { - HistoryMergerState *hms_p = &(hms[i]); - hms_p->hmrb = hm_rb_new((size_t) num_saved); - hms_p->do_merge = false; - hms_p->iter = shada_hist_iter(NULL, i, false, &(hms[i].last_hist_entry)); - hms_p->history_type = i; - if (hms_p->last_hist_entry.type != kSDItemMissing) { - hm_rb_push(&(hms_p->hmrb), hms_p->last_hist_entry); - while (hms_p->iter != NULL) { - hms_p->iter = shada_hist_iter(hms_p->iter, hms_p->history_type, false, - &(hms_p->last_hist_entry)); - if (hms_p->last_hist_entry.type != kSDItemMissing) { - hm_rb_push(&(hms_p->hmrb), hms_p->last_hist_entry); - } else { - break; - } - } - } - RINGBUF_FORALL(&(hms_p->hmrb), ShadaEntry, cur_entry) { + hms_init(&hms[i], i, (size_t) num_saved, false); + hms_insert_whole_neovim_history(&hms[i]); + HMS_ITER(&hms[i], cur_entry) { RUN_WITH_CONVERTED_STRING(cur_entry->data.history_item.string, { shada_pack_entry(packer, *cur_entry, max_kbyte); }); } - hm_rb_dealloc(&hms_p->hmrb); + hms_dealloc(&hms[i]); } } -- cgit From 875d287d4bacffd41bd0d553163da5579ba5e55f Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 10 Jul 2015 20:53:34 +0300 Subject: functests: Test that history is still accessible after :wshada --- test/functional/shada/history_spec.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/functional/shada/history_spec.lua b/test/functional/shada/history_spec.lua index c4d39bd547..0c39b65ee1 100644 --- a/test/functional/shada/history_spec.lua +++ b/test/functional/shada/history_spec.lua @@ -66,6 +66,9 @@ describe('ShaDa support code', function() nvim_feed(':" Test\n') nvim_feed(':" Test 2\n') nvim_command('wshada') + -- Regression test: :wshada should not alter or free history. + eq('" Test 2', nvim_eval('histget(":", -1)')) + eq('" Test', nvim_eval('histget(":", -2)')) reset() nvim_command('set shada=\'0') nvim_command('rshada') -- cgit From 0018b4f5790ec3659698dd47a06c94ce4d20467e Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 10 Jul 2015 21:07:49 +0300 Subject: shada: Refactor history merger to use statically sized 2linked list Also fixes the error: when writing shada files existing history items may be freed. Warning: valgrind reports some memory leaks. --- src/nvim/api/private/helpers.c | 2 +- src/nvim/shada.c | 239 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 211 insertions(+), 30 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/shada.c b/src/nvim/shada.c index 2a4708f14a..f2c3b47baa 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -43,7 +43,6 @@ #include "nvim/eval_defs.h" #include "nvim/version.h" #include "nvim/path.h" -#include "nvim/lib/ringbuf.h" #include "nvim/fileio.h" #include "nvim/strings.h" #include "nvim/lib/khash.h" @@ -252,11 +251,33 @@ typedef struct { } data; } ShadaEntry; -RINGBUF_TYPEDEF(HM, ShadaEntry) +struct hm_llist_entry; + +/// One entry in sized linked list +typedef struct hm_llist_entry { + ShadaEntry data; ///< Entry data. + struct hm_llist_entry *next; ///< Pointer to next entry or NULL. + struct hm_llist_entry *prev; ///< Pointer to previous entry or NULL. +} 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_entries; ///< Free array entries. + HMLListEntry *last_free_element; ///< Last free array element. + size_t size; ///< Number of allocated entries. + size_t free_entries_size; ///< Number of non-NULL entries in free_entries. + size_t num_entries; ///< Number of entries already used. +} HMLList; typedef struct { - HMRingBuffer hmrb; + HMLList hmll; bool do_merge; + bool reading; const void *iter; ShadaEntry last_hist_entry; uint8_t history_type; @@ -303,7 +324,142 @@ typedef struct sd_write_def { # include "shada.c.generated.h" #endif -RINGBUF_INIT(HM, hm, ShadaEntry, shada_free_shada_entry) +/// 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_entries = NULL, + .size = size, + .free_entries_size = 0, + .num_entries = 0, + }; + hmll->last_free_element = 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->free_entries == NULL) { + if (hmll_entry == hmll->last_free_element) { + hmll->last_free_element--; + } else { + hmll->free_entries = xcalloc(hmll->size, sizeof(hmll->free_entries[0])); + hmll->free_entries[hmll->free_entries_size++] = hmll_entry; + } + } else { + hmll->free_entries[hmll->free_entries_size++] = hmll_entry; + } + 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--; + 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. +static inline void hmll_insert(HMLList *const hmll, + HMLListEntry *hmll_entry, + const ShadaEntry data) + 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_entries == NULL) { + assert((size_t) (hmll->last_free_element - hmll->entries) + == hmll->num_entries); + target_entry = hmll->last_free_element++; + } else { + target_entry = hmll->free_entries[--hmll->free_entries_size]; + } + target_entry->data = data; + 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 +{ + xfree(hmll->entries); + xfree(hmll->free_entries); +} + +/// Free linked list and all entries +/// +/// @param[in] hmll List to free. +static inline void hmll_free(HMLList *const hmll) + FUNC_ATTR_NONNULL_ALL +{ + HMLL_FORALL(hmll, cur_entry) { + shada_free_shada_entry(&cur_entry->data); + } + hmll_dealloc(hmll); +} /// Wrapper for reading from file descriptors /// @@ -594,6 +750,18 @@ static const void *shada_hist_iter(const void *const iter, } } }; + if (!zero) { + hist->data.history_item.string = xstrdup(hist->data.history_item.string); + if (hist->data.history_item.additional_elements != NULL) { + Object new_array = copy_object( + ARRAY_OBJ(*hist->data.history_item.additional_elements)); + hist->data.history_item.additional_elements = xmalloc( + sizeof(*hist->data.history_item.additional_elements)); + memcpy(hist->data.history_item.additional_elements, + &new_array.data.array, + sizeof(*hist->data.history_item.additional_elements)); + } + } } return ret; } @@ -614,12 +782,12 @@ static const void *shada_hist_iter(const void *const iter, static void hms_insert(HistoryMergerState *const hms_p, const ShadaEntry entry, const bool no_iter) { - HMRingBuffer *const rb = &(hms_p->hmrb); - RINGBUF_FORALL(rb, ShadaEntry, cur_entry) { - if (STRCMP(cur_entry->data.history_item.string, + HMLList *const hmll = &hms_p->hmll; + HMLL_FORALL(hmll, cur_entry) { + if (STRCMP(cur_entry->data.data.history_item.string, entry.data.history_item.string) == 0) { - if (entry.timestamp > cur_entry->timestamp) { - hm_rb_remove(rb, (size_t) hm_rb_find_idx(rb, cur_entry)); + if (entry.timestamp > cur_entry->data.timestamp) { + hmll_remove(hmll, cur_entry); } else { return; } @@ -637,18 +805,19 @@ static void hms_insert(HistoryMergerState *const hms_p, const ShadaEntry entry, && 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->iter = shada_hist_iter(hms_p->iter, hms_p->history_type, true, + hms_p->iter = shada_hist_iter(hms_p->iter, hms_p->history_type, + hms_p->reading, &(hms_p->last_hist_entry)); } } } - ShadaEntry *insert_after; - RINGBUF_ITER_BACK(rb, ShadaEntry, insert_after) { - if (insert_after->timestamp <= entry.timestamp) { + HMLListEntry *insert_after; + HMLL_ITER_BACK(hmll, insert_after) { + if (insert_after->data.timestamp <= entry.timestamp) { break; } } - hm_rb_insert(rb, (size_t) (hm_rb_find_idx(rb, insert_after) + 1), entry); + hmll_insert(hmll, insert_after, entry); } /// Initialize the history merger @@ -657,15 +826,19 @@ static void hms_insert(HistoryMergerState *const hms_p, const ShadaEntry entry, /// @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 do_merge, + const bool reading) FUNC_ATTR_NONNULL_ALL { - hms_p->hmrb = hm_rb_new(num_elements); + hmll_init(&hms_p->hmll, num_elements); hms_p->do_merge = do_merge; - hms_p->iter = shada_hist_iter(NULL, history_type, true, + 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; } @@ -683,7 +856,8 @@ static inline void hms_insert_whole_neovim_history( } while (hms_p->iter != NULL && hms_p->last_hist_entry.type != kSDItemMissing) { - hms_p->iter = shada_hist_iter(hms_p->iter, hms_p->history_type, true, + hms_p->iter = shada_hist_iter(hms_p->iter, hms_p->history_type, + hms_p->reading, &(hms_p->last_hist_entry)); hms_insert(hms_p, hms_p->last_hist_entry, false); } @@ -702,15 +876,15 @@ static inline void hms_to_he_array(const HistoryMergerState *const hms_p, FUNC_ATTR_NONNULL_ALL { histentry_T *hist = hist_array; - RINGBUF_FORALL(&hms_p->hmrb, ShadaEntry, cur_entry) { - hist->timestamp = cur_entry->timestamp; + 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.history_item.string; + hist->hisstr = (char_u *) cur_entry->data.data.history_item.string; hist->additional_elements = - cur_entry->data.history_item.additional_elements; + cur_entry->data.data.history_item.additional_elements; hist++; } - *new_hisnum = (int) hm_rb_length(&hms_p->hmrb); + *new_hisnum = (int) (hist - hist_array); *new_hisidx = *new_hisnum - 1; } @@ -720,7 +894,14 @@ static inline void hms_to_he_array(const HistoryMergerState *const hms_p, static inline void hms_dealloc(HistoryMergerState *const hms_p) FUNC_ATTR_NONNULL_ALL { - hm_rb_dealloc(&hms_p->hmrb); + if (hms_p->reading) { + // Free only the linked list if reading because all of the allocated memory + // was either already freed or saved in internal NeoVim history. + hmll_dealloc(&hms_p->hmll); + } else { + // Free everything because when writing data is only used once to write it. + hmll_free(&hms_p->hmll); + } } /// Iterate over all history entries in history merger, in order @@ -730,7 +911,7 @@ static inline void hms_dealloc(HistoryMergerState *const hms_p) /// /// @return for cycle header. Use `HMS_ITER(hms_p, cur_entry) {body}`. #define HMS_ITER(hms_p, cur_entry) \ - RINGBUF_FORALL(&((hms_p)->hmrb), ShadaEntry, cur_entry) + HMLL_FORALL(&((hms_p)->hmll), cur_entry) /// Find buffer for given buffer name (cached) /// @@ -804,7 +985,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) 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); + hms_init(&hms[i], i, (size_t) p_hi, true, true); } } ShadaEntry cur_entry; @@ -1636,11 +1817,11 @@ static void shada_write(ShaDaWriteDef *const sd_writer, num_saved = p_hi; } if (num_saved > 0) { - hms_init(&hms[i], i, (size_t) num_saved, false); + hms_init(&hms[i], i, (size_t) num_saved, false, false); hms_insert_whole_neovim_history(&hms[i]); HMS_ITER(&hms[i], cur_entry) { - RUN_WITH_CONVERTED_STRING(cur_entry->data.history_item.string, { - shada_pack_entry(packer, *cur_entry, max_kbyte); + RUN_WITH_CONVERTED_STRING(cur_entry->data.data.history_item.string, { + shada_pack_entry(packer, cur_entry->data, max_kbyte); }); } hms_dealloc(&hms[i]); -- cgit From 09cbec6476f191c683fa48b2e13a12ac2ab69c78 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 12 Jul 2015 14:11:41 +0300 Subject: shada: Use os_mkdir_recurse to create directory for ShaDa file --- src/nvim/shada.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index f2c3b47baa..17595cde14 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -83,6 +83,8 @@ KHASH_SET_INIT_STR(strset) (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 path_tail_with_sep(f) ((char *) path_tail_with_sep((char_u *)f)) // From http://www.boost.org/doc/libs/1_43_0/boost/detail/endian.hpp + some // additional checks done after examining `{compiler} -dM -E - < /dev/null` @@ -2095,7 +2097,24 @@ shada_write_file_open: } } if (nomerge) { -shada_write_file_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("Failed to create directory %s for writing ShaDa file: %s", + failed_dir, 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); } -- cgit From 9d2615df86dadee9f441b280b067897b29a12878 Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 17 Jul 2015 17:54:44 +0300 Subject: shada: Use os_strerror in place of strerror in some places --- src/nvim/shada.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 17595cde14..af01fad744 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -577,7 +577,7 @@ open_file_start: } if (-fd != EEXIST) { emsg3("System error while opening ShaDa file %s: %s", - fname, strerror(-fd)); + fname, os_strerror(fd)); } return fd; } @@ -2107,7 +2107,7 @@ shada_write_file_nomerge: {} char *failed_dir; if ((ret = os_mkdir_recurse(fname, 0700, &failed_dir)) != 0) { EMSG3("Failed to create directory %s for writing ShaDa file: %s", - failed_dir, strerror(-ret)); + failed_dir, os_strerror(ret)); xfree(fname); xfree(failed_dir); return FAIL; -- cgit From f10960869f2c33812f00083e9584dc8ca16738f2 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 18 Jul 2015 21:52:17 +0300 Subject: shada: Do not save search pattern/sub string when it is older --- src/nvim/shada.c | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index af01fad744..44ee47f87f 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1021,6 +1021,16 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) 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) { @@ -1046,6 +1056,14 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) 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, @@ -1679,6 +1697,10 @@ static void shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef *const sd_reader) FUNC_ATTR_NONNULL_ARG(1) { + // TODO(ZyX-I): Write only one search pattern, substitute search pattern and + // substitute replacement string, which has the greatest timestamp. Make sure + // that this also applies if ShaDa file contains more then one replacement + // string. khash_t(bufset) *const removable_bufs = kh_init(bufset); int max_kbyte_i = get_shada_parameter('s'); if (max_kbyte_i < 0) { @@ -1886,13 +1908,16 @@ static void shada_write(ShaDaWriteDef *const sd_writer, { SubReplacementString sub; sub_get_replacement(&sub); - ShadaEntry sub_entry = (ShadaEntry) { - .type = kSDItemSubString, - .timestamp = sub.timestamp, + wms->replacement = (PossiblyFreedShadaEntry) { + .can_free_entry = false, .data = { - .sub_string = { - .sub = (char *) sub.sub, - .additional_elements = sub.additional_elements, + .type = kSDItemSubString, + .timestamp = sub.timestamp, + .data = { + .sub_string = { + .sub = (char *) sub.sub, + .additional_elements = sub.additional_elements, + } } } }; -- cgit From 5e7a7fc2da682f62648034920020c9da6366ef4d Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 18 Jul 2015 21:58:44 +0300 Subject: documentation: Document how ShaDa entries should be merged --- runtime/doc/starting.txt | 37 +++++++++++++++++++++++++++++++++---- runtime/doc/vim_diff.txt | 2 ++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index c5a0be3fff..1679ff170d 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -930,10 +930,10 @@ The |v:oldfiles| variable is filled. The marks are not read in at startup option upon startup. *shada-write* -When Vim exits and 'shada' is non-empty, the info is stored in the ShaDa -file (it's actually merged with the existing one, if one exists). The -'shada' option is a string containing information about what info should be -stored, and contains limits on how much should be stored (see 'shada'). +When Vim exits and 'shada' is non-empty, the info is stored in the ShaDa file +(it's actually merged with the existing one, if one exists |shada-merging|). +The 'shada' option is a string containing information about what info should +be stored, and contains limits on how much should be stored (see 'shada'). Notes for Unix: - The file protection for the ShaDa file will be set to prevent other users @@ -988,6 +988,35 @@ For a bash-like shell: > Use the "r" flag in 'shada' to specify for which files no marks should be remembered. + *shada-merging* +When writing ShaDa files with |:wshada| without bang or at regular exit +information in the existing ShaDa file is merged with information from current +NeoVim instance. For this purpose ShaDa files store timestamps associated +with ShaDa entries. Specifically the following is being done: + +1. History lines are merged, ordered by timestamp. Maximum amount of items in + ShaDa file is defined by 'shada' option (|shada-/|, |shada-:|, |shada-@|, + etc: one suboption for each character that represents history name + (|:history|)). +2. Local marks and changes for files that were not opened by NeoVim are copied + to new ShaDa file. Marks for files that were opened by NeoVim are merged, + changes to files opened by NeoVim are ignored. |shada-'| +3. Jump list is merged: jumps are ordered by timestamp, identical jumps + (identical position AND timestamp) are squashed. +4. Search patterns and substitute strings are not merged: search pattern or + substitute string which has greatest timestamp will be the only one copied + to ShaDa file. +5. For each register entity with greatest timestamp is the only saved. + |shada-<| +6. All saved variables are saved from current NeoVim instance. Additionally + existing variable values are copied, meaning that the only way to remove + variable from a ShaDa file is either removing it by hand or disabling + writing variables completely. |shada-!| +7. For each global mark entity with greatest timestamp is the only saved. +8. Buffer list and header are the only entries which are not merged in any + fashion: the only header and buffer list present are the ones from the + NeoVim instance which was last writing the file. |shada-%| + SHADA FILE NAME *shada-file-name* diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index f93175cccf..94996477ab 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -105,6 +105,8 @@ Additional differences: compatibility reasons. - |:wviminfo| was renamed to |:wshada|, |:rviminfo| to |:rshada|. Old commands are still kept. +- When writing (|:wshada| without bang or at exit) it merges much more data. + Vim merges only marks. ============================================================================== 4. New Features *nvim-features-new* -- cgit From 4bc053facda4aee6cec2c6cbc9fbbe978e66503d Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 19 Jul 2015 16:08:19 +0300 Subject: shada: Do not copy history entries when writing --- src/nvim/shada.c | 62 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 44ee47f87f..6920115735 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -258,6 +258,7 @@ 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; @@ -384,19 +385,23 @@ static inline void hmll_remove(HMLList *const hmll, hmll_entry->prev->next = hmll_entry->next; } hmll->num_entries--; - shada_free_shada_entry(&hmll_entry->data); + 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[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 ShadaEntry data, + const bool can_free_entry) FUNC_ATTR_NONNULL_ARG(1) { if (hmll->num_entries == hmll->size) { @@ -414,6 +419,7 @@ static inline void hmll_insert(HMLList *const hmll, target_entry = hmll->free_entries[--hmll->free_entries_size]; } target_entry->data = data; + target_entry->can_free_entry = can_free_entry; hmll->num_entries++; target_entry->prev = hmll_entry; if (hmll_entry == NULL) { @@ -725,7 +731,13 @@ int shada_read_file(const char *const file, const int flags) /// Wrapper for hist_iter() function which produces ShadaEntry values /// -/// @warning Zeroes original items in process. +/// @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, @@ -752,18 +764,6 @@ static const void *shada_hist_iter(const void *const iter, } } }; - if (!zero) { - hist->data.history_item.string = xstrdup(hist->data.history_item.string); - if (hist->data.history_item.additional_elements != NULL) { - Object new_array = copy_object( - ARRAY_OBJ(*hist->data.history_item.additional_elements)); - hist->data.history_item.additional_elements = xmalloc( - sizeof(*hist->data.history_item.additional_elements)); - memcpy(hist->data.history_item.additional_elements, - &new_array.data.array, - sizeof(*hist->data.history_item.additional_elements)); - } - } } return ret; } @@ -777,12 +777,13 @@ static const void *shada_hist_iter(const void *const iter, /// 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. +/// @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. +/// @param[in] can_free_entry True if entry can be freed. static void hms_insert(HistoryMergerState *const hms_p, const ShadaEntry entry, - const bool no_iter) + const bool no_iter, const bool can_free_entry) { HMLList *const hmll = &hms_p->hmll; HMLL_FORALL(hmll, cur_entry) { @@ -799,14 +800,14 @@ static void hms_insert(HistoryMergerState *const hms_p, const ShadaEntry entry, if (hms_p->iter == NULL) { if (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_insert(hms_p, hms_p->last_hist_entry, false, hms_p->reading); hms_p->last_hist_entry.type = kSDItemMissing; } } else { while (hms_p->iter != NULL && 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_insert(hms_p, hms_p->last_hist_entry, false, hms_p->reading); hms_p->iter = shada_hist_iter(hms_p->iter, hms_p->history_type, hms_p->reading, &(hms_p->last_hist_entry)); @@ -819,7 +820,7 @@ static void hms_insert(HistoryMergerState *const hms_p, const ShadaEntry entry, break; } } - hmll_insert(hmll, insert_after, entry); + hmll_insert(hmll, insert_after, entry, can_free_entry); } /// Initialize the history merger @@ -854,14 +855,14 @@ static inline void hms_insert_whole_neovim_history( FUNC_ATTR_NONNULL_ALL { if (hms_p->last_hist_entry.type != kSDItemMissing) { - hms_insert(hms_p, hms_p->last_hist_entry, false); + hms_insert(hms_p, hms_p->last_hist_entry, false, hms_p->reading); } while (hms_p->iter != NULL && hms_p->last_hist_entry.type != kSDItemMissing) { hms_p->iter = shada_hist_iter(hms_p->iter, hms_p->history_type, hms_p->reading, &(hms_p->last_hist_entry)); - hms_insert(hms_p, hms_p->last_hist_entry, false); + hms_insert(hms_p, hms_p->last_hist_entry, false, hms_p->reading); } } @@ -1077,7 +1078,8 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) shada_free_shada_entry(&cur_entry); break; } - hms_insert(hms + cur_entry.data.history_item.histtype, cur_entry, true); + 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; } -- cgit From 43fe98c9fb350b428d05021995c8892e080054b2 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 19 Jul 2015 21:23:15 +0300 Subject: shada: Add support for merging everything like described in the doc --- src/nvim/lib/khash.h | 20 +- src/nvim/mark.c | 8 +- src/nvim/mark.h | 28 ++ src/nvim/mark_defs.h | 6 + src/nvim/ops.c | 45 +- src/nvim/ops.h | 43 ++ src/nvim/shada.c | 1141 +++++++++++++++++++++++++++++++++----------------- 7 files changed, 862 insertions(+), 429 deletions(-) diff --git a/src/nvim/lib/khash.h b/src/nvim/lib/khash.h index fd4f910dac..17f653f45e 100644 --- a/src/nvim/lib/khash.h +++ b/src/nvim/lib/khash.h @@ -196,6 +196,7 @@ static const double __ac_HASH_UPPER = 0.77; #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); \ @@ -204,16 +205,24 @@ static const double __ac_HASH_UPPER = 0.77; 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((void *)h->keys); kfree(h->flags); \ + kfree((void *)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) { \ - kfree((void *)h->keys); kfree(h->flags); \ - kfree((void *)h->vals); \ + kh_dealloc_##name(h); \ kfree(h); \ } \ } \ @@ -446,6 +455,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] diff --git a/src/nvim/mark.c b/src/nvim/mark.c index b7746f192f..2072483e1d 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -1350,12 +1350,8 @@ size_t mark_buffer_amount(const buf_T *const buf) /// later then existing one. void mark_set_global(const char name, const xfmark_T fm, const bool update) { - xfmark_T *fm_tgt = NULL; - if (ASCII_ISUPPER(name)) { - fm_tgt = &(namedfm[name - 'A']); - } else if (ascii_isdigit(name)) { - fm_tgt = &(namedfm[NMARKS + (name - '0')]); - } else { + xfmark_T *fm_tgt = &(namedfm[mark_global_index(name)]); + if (fm_tgt == &namedfm[-1]) { return; } if (update && fm.fmark.timestamp < fm_tgt->fmark.timestamp) { diff --git a/src/nvim/mark.h b/src/nvim/mark.h index 3f63e274bb..aff6e7273a 100644 --- a/src/nvim/mark.h +++ b/src/nvim/mark.h @@ -1,6 +1,8 @@ #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" @@ -46,6 +48,32 @@ 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" #endif diff --git a/src/nvim/mark_defs.h b/src/nvim/mark_defs.h index 2f7c9b4ed4..af4727d634 100644 --- a/src/nvim/mark_defs.h +++ b/src/nvim/mark_defs.h @@ -19,6 +19,12 @@ /// 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 diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 52064fae06..0d7c319fba 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -54,22 +54,6 @@ #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.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 ShaDa file: -#define STAR_REGISTER 37 -#define PLUS_REGISTER 38 -#define NUM_REGISTERS 39 - static yankreg_T y_regs[NUM_REGISTERS]; static yankreg_T *y_previous = NULL; /* ptr to last written yankreg */ @@ -749,31 +733,6 @@ typedef enum { YREG_PUT, } yreg_mode_t; -/// Convert register name into register index -/// -/// @param[in] regname Register name. -/// -/// @return Index in y_regs array or -1 if register name was not recognized. -static inline int reg_index(const int regname) - FUNC_ATTR_CONST -{ - if (ascii_isdigit(regname)) { - return regname - '0'; - } else if (ASCII_ISLOWER(regname)) { - return CharOrdLow(regname) + 10; - } else if (ASCII_ISUPPER(regname)) { - return CharOrdUp(regname) + 10; - } else if (regname == '-') { - return DELETION_REGISTER; - } else if (regname == '*') { - return STAR_REGISTER; - } else if (regname == '+') { - return PLUS_REGISTER; - } else { - return -1; - } -} - /// Return yankreg_T to use, according to the value of `regname`. /// Cannot handle the '_' (black hole) register. /// Must only be called with a valid register name! @@ -806,7 +765,7 @@ yankreg_T *get_yank_register(int regname, int mode) return y_previous; } - int i = reg_index(regname); + int i = op_reg_index(regname); // when not 0-9, a-z, A-Z or '-'/'+'/'*': use register 0 if (i == -1) { i = 0; @@ -5417,7 +5376,7 @@ size_t op_register_amount(void) /// Set register to a given value void register_set(const char name, const yankreg_T reg) { - int i = reg_index(name); + int i = op_reg_index(name); if (i == -1) { return; } diff --git a/src/nvim/ops.h b/src/nvim/ops.h index 4da5cfc93d..5565f1631f 100644 --- a/src/nvim/ops.h +++ b/src/nvim/ops.h @@ -3,6 +3,8 @@ #include +#include "nvim/macros.h" +#include "nvim/ascii.h" #include "nvim/types.h" #include "nvim/api/private/defs.h" #include "nvim/os/time.h" @@ -17,6 +19,22 @@ typedef int (*Indenter)(void); #define PUT_LINE_SPLIT 16 /* split line for linewise register */ #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! */ @@ -66,6 +84,31 @@ typedef struct yankreg { Dictionary *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/shada.c b/src/nvim/shada.c index 6920115735..a7bde92032 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -154,7 +155,6 @@ enum SRNIFlags { kSDReadUndisableableData = ( (1 << kSDItemSearchPattern) | (1 << kSDItemSubString) - | (1 << kSDItemGlobalMark) | (1 << kSDItemJump) ), ///< Data reading which cannot be disabled by &shada or other options ///< except for disabling reading ShaDa as a whole. @@ -175,6 +175,11 @@ enum SRNIFlags { 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 @@ -286,6 +291,41 @@ typedef struct { 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. + bool is_local_entry; ///< True if structure comes from the current session. +} 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 read ShaDa files @@ -457,18 +497,6 @@ static inline void hmll_dealloc(HMLList *const hmll) xfree(hmll->free_entries); } -/// Free linked list and all entries -/// -/// @param[in] hmll List to free. -static inline void hmll_free(HMLList *const hmll) - FUNC_ATTR_NONNULL_ALL -{ - HMLL_FORALL(hmll, cur_entry) { - shada_free_shada_entry(&cur_entry->data); - } - hmll_dealloc(hmll); -} - /// Wrapper for reading from file descriptors /// /// @return true if read was successfull, false otherwise. @@ -897,14 +925,7 @@ static inline void hms_to_he_array(const HistoryMergerState *const hms_p, static inline void hms_dealloc(HistoryMergerState *const hms_p) FUNC_ATTR_NONNULL_ALL { - if (hms_p->reading) { - // Free only the linked list if reading because all of the allocated memory - // was either already freed or saved in internal NeoVim history. - hmll_dealloc(&hms_p->hmll); - } else { - // Free everything because when writing data is only used once to write it. - hmll_free(&hms_p->hmll); - } + hmll_dealloc(&hms_p->hmll); } /// Iterate over all history entries in history merger, in order @@ -957,30 +978,28 @@ static inline bool marks_equal(const pos_T a, const pos_T b) static void shada_read(ShaDaReadDef *const sd_reader, const int flags) FUNC_ATTR_NONNULL_ALL { - unsigned srni_flags = 0; const bool force = flags & kShaDaForceit; const bool get_old_files = flags & (kShaDaGetOldfiles | kShaDaForceit); const bool want_marks = flags & kShaDaWantMarks; - if (flags & kShaDaWantInfo) { - srni_flags |= kSDReadUndisableableData | kSDReadRegisters; - if (p_hi) { - srni_flags |= kSDReadHistory; - } - if (find_shada_parameter('!') != NULL) { - srni_flags |= kSDReadVariables; - } - if (find_shada_parameter('%') != NULL && ARGCOUNT == 0) { - srni_flags |= kSDReadBufferList; - } - } - if (want_marks) { - if (get_shada_parameter('\'') > 0) { - srni_flags |= kSDReadLocalMarks | kSDReadChanges; - } - } - if (get_old_files) { - srni_flags |= kSDReadLocalMarks; - } + const unsigned srni_flags = ((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; @@ -1009,7 +1028,8 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) oldfiles_list = list_alloc(); set_vim_var_list(VV_OLDFILES, oldfiles_list); } - while (shada_read_next_item(sd_reader, &cur_entry, srni_flags) == NOTDONE) { + while (shada_read_next_item(sd_reader, &cur_entry, srni_flags, 0) + == NOTDONE) { switch (cur_entry.type) { case kSDItemMissing: { assert(false); @@ -1689,6 +1709,140 @@ static void shada_pack_entry(msgpack_packer *const packer, msgpack_sbuffer_destroy(&sbuf); } +/// 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 void 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 +{ + if (entry.can_free_entry) { + shada_pack_entry(packer, entry.data, max_kbyte); + shada_free_shada_entry(&entry.data); + return; + } +#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, { + shada_pack_entry(packer, entry.data, max_kbyte); + }); + break; + } + case kSDItemHistoryEntry: { + RUN_WITH_CONVERTED_STRING(entry.data.data.history_item.string, { + shada_pack_entry(packer, entry.data, max_kbyte); + }); + break; + } + case kSDItemSubString: { + RUN_WITH_CONVERTED_STRING(entry.data.data.sub_string.sub, { + shada_pack_entry(packer, entry.data, max_kbyte); + }); + break; + } + case kSDItemVariable: { + if (sd_conv->vc_type != CONV_NONE) { + convert_object(sd_conv, &entry.data.data.global_var.value); + } + 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]); + } + } + } + } + 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: { + shada_pack_entry(packer, entry.data, max_kbyte); + break; + } + } +#undef RUN_WITH_CONVERTED_STRING +} + +/// 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)); +} + /// Write ShaDa file /// /// @param[in] sd_writer Structure containing file writer definition. @@ -1699,11 +1853,6 @@ static void shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef *const sd_reader) FUNC_ATTR_NONNULL_ARG(1) { - // TODO(ZyX-I): Write only one search pattern, substitute search pattern and - // substitute replacement string, which has the greatest timestamp. Make sure - // that this also applies if ShaDa file contains more then one replacement - // string. - khash_t(bufset) *const removable_bufs = kh_init(bufset); int max_kbyte_i = get_shada_parameter('s'); if (max_kbyte_i < 0) { max_kbyte_i = 10; @@ -1711,10 +1860,49 @@ static void shada_write(ShaDaWriteDef *const sd_writer, if (max_kbyte_i == 0) { return; } + + 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) *const removable_bufs = kh_init(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; - msgpack_packer *packer = msgpack_packer_new(sd_writer, - &msgpack_sd_writer_write); + // 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 = ( + 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)) { @@ -1723,10 +1911,7 @@ static void shada_write(ShaDaWriteDef *const sd_writer, } } - // TODO(ZyX-I): Iterate over sd_reader, keeping “replaced” values in a set. - - // First write values that do not require merging - // 1. Header + // Write header shada_pack_entry(packer, (ShadaEntry) { .type = kSDItemHeader, .timestamp = os_time(), @@ -1748,7 +1933,7 @@ static void shada_write(ShaDaWriteDef *const sd_writer, } }, 0); - // 2. Buffer list + // Write buffer list if (find_shada_parameter('%') != NULL) { size_t buf_count = 0; FOR_ALL_BUFFERS(buf) { @@ -1784,129 +1969,94 @@ static void shada_write(ShaDaWriteDef *const sd_writer, xfree(buflist_entry.data.buffer_list.buffers); } - // 3. 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 - ? SHADA_REMOVABLE(buf) - : fm.fmark.fnum != 0) { - continue; - } - const char *const fname = (char *) (fm.fmark.fnum == 0 - ? (fm.fname == NULL - ? NULL - : fm.fname) - : buf->b_ffname); - shada_pack_entry(packer, (ShadaEntry) { - .type = kSDItemJump, - .timestamp = fm.fmark.timestamp, - .data = { - .filemark = { - .name = NUL, - .mark = fm.fmark.mark, - .fname = (char *) fname, - .additional_data = fm.fmark.additional_data, - } + // 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; + var_iter = var_shada_iter(var_iter, &name, &vartv); + if (var_iter == NULL && vartv.v_type == VAR_UNKNOWN) { + break; } - }, max_kbyte); - } while (jump_iter != NULL); - - // FIXME No merging currently - -#define RUN_WITH_CONVERTED_STRING(cstr, code) \ - do { \ - bool did_convert = false; \ - if (sd_writer->sd_conv.vc_type != CONV_NONE && has_non_ascii((cstr))) { \ - char *const converted_string = string_convert(&sd_writer->sd_conv, \ - (cstr), NULL); \ - if (converted_string != NULL) { \ - (cstr) = converted_string; \ - did_convert = true; \ - } \ - } \ - code \ - if (did_convert) { \ - xfree((cstr)); \ - } \ - } while (0) - // 4. History - HistoryMergerState hms[HIST_COUNT]; - 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) { - hms_init(&hms[i], i, (size_t) num_saved, false, false); - hms_insert_whole_neovim_history(&hms[i]); - HMS_ITER(&hms[i], cur_entry) { - RUN_WITH_CONVERTED_STRING(cur_entry->data.data.history_item.string, { - shada_pack_entry(packer, cur_entry->data, max_kbyte); - }); + Object obj = vim_to_object(&vartv); + if (sd_writer->sd_conv.vc_type != CONV_NONE) { + convert_object(&sd_writer->sd_conv, &obj); } - hms_dealloc(&hms[i]); - } + shada_pack_entry(packer, (ShadaEntry) { + .type = kSDItemVariable, + .timestamp = cur_timestamp, + .data = { + .global_var = { + .name = (char *) name, + .value = obj, + .additional_elements = NULL, + } + } + }, max_kbyte); + api_free_object(obj); + clear_tv(&vartv); + int kh_ret; + (void) kh_put(strset, &wms->dumped_variables, name, &kh_ret); + } while (var_iter != NULL); } - // 5. Search patterns + // Initialize search pattern { SearchPattern pat; get_search_pattern(&pat); - ShadaEntry sp_entry = (ShadaEntry) { - .type = kSDItemSearchPattern, - .timestamp = pat.timestamp, + wms->search_pattern = (PossiblyFreedShadaEntry) { + .can_free_entry = false, .data = { - .search_pattern = { - .magic = pat.magic, - .smartcase = !pat.no_scs, - .has_line_offset = pat.off.line, - .place_cursor_at_end = pat.off.end, - .offset = pat.off.off, - .is_last_used = search_was_last_used(), - .is_substitute_pattern = false, - .highlighted = (!no_hlsearch && find_shada_parameter('h') != NULL), - .pat = (char *) pat.pat, - .additional_data = pat.additional_data, + .type = kSDItemSearchPattern, + .timestamp = pat.timestamp, + .data = { + .search_pattern = { + .magic = pat.magic, + .smartcase = !pat.no_scs, + .has_line_offset = pat.off.line, + .place_cursor_at_end = pat.off.end, + .offset = pat.off.off, + .is_last_used = search_was_last_used(), + .is_substitute_pattern = false, + .highlighted = (!no_hlsearch && find_shada_parameter('h') != NULL), + .pat = (char *) pat.pat, + .additional_data = pat.additional_data, + } } } }; - RUN_WITH_CONVERTED_STRING(sp_entry.data.search_pattern.pat, { - shada_pack_entry(packer, sp_entry, max_kbyte); - }); } + + // Initialize substitute search pattern { SearchPattern pat; get_substitute_pattern(&pat); - ShadaEntry sp_entry = (ShadaEntry) { - .type = kSDItemSearchPattern, - .timestamp = pat.timestamp, + wms->sub_search_pattern = (PossiblyFreedShadaEntry) { + .can_free_entry = false, .data = { - .search_pattern = { - .magic = pat.magic, - .smartcase = !pat.no_scs, - .has_line_offset = false, - .place_cursor_at_end = false, - .offset = 0, - .is_last_used = !search_was_last_used(), - .is_substitute_pattern = true, - .highlighted = false, - .pat = (char *) pat.pat, - .additional_data = pat.additional_data, + .type = kSDItemSearchPattern, + .timestamp = pat.timestamp, + .data = { + .search_pattern = { + .magic = pat.magic, + .smartcase = !pat.no_scs, + .has_line_offset = false, + .place_cursor_at_end = false, + .offset = 0, + .is_last_used = !search_was_last_used(), + .is_substitute_pattern = true, + .highlighted = false, + .pat = (char *) pat.pat, + .additional_data = pat.additional_data, + } } } }; - RUN_WITH_CONVERTED_STRING(sp_entry.data.search_pattern.pat, { - shada_pack_entry(packer, sp_entry, max_kbyte); - }); } - // 6. Substitute string + // Initialize substitute replacement string { SubReplacementString sub; sub_get_replacement(&sub); @@ -1923,119 +2073,482 @@ static void shada_write(ShaDaWriteDef *const sd_writer, } } }; - RUN_WITH_CONVERTED_STRING(sub_entry.data.sub_string.sub, { - shada_pack_entry(packer, sub_entry, max_kbyte); - }); } - // 7. Global marks - if (get_shada_parameter('f') != 0) { - ShadaEntry *const global_marks = list_global_marks(removable_bufs); - for (ShadaEntry *mark = global_marks; mark->type != kSDItemMissing; - mark++) { - shada_pack_entry(packer, *mark, max_kbyte); - } - xfree(global_marks); - } - - // 8. Buffer marks and buffer change list - FOR_ALL_BUFFERS(buf) { - if (buf->b_ffname == NULL || SHADA_REMOVABLE(buf)) { + // 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 + ? SHADA_REMOVABLE(buf) + : fm.fmark.fnum != 0) { continue; } - ShadaEntry *const buffer_marks = list_buffer_marks(buf); - for (ShadaEntry *mark = buffer_marks; mark->type != kSDItemMissing; - mark++) { - shada_pack_entry(packer, *mark, max_kbyte); + const char *const fname = (char *) (fm.fmark.fnum == 0 + ? (fm.fname == NULL + ? NULL + : fm.fname) + : buf->b_ffname); + if (fname == NULL) { + continue; } - xfree(buffer_marks); - - for (int i = 0; i < buf->b_changelistlen; i++) { - const fmark_T fm = buf->b_changelist[i]; - shada_pack_entry(packer, (ShadaEntry) { - .type = kSDItemChange, - .timestamp = fm.timestamp, + wms->jumps[wms->jumps_size++] = (PossiblyFreedShadaEntry) { + .can_free_entry = false, + .data = { + .type = kSDItemJump, + .timestamp = fm.fmark.timestamp, .data = { .filemark = { - .mark = fm.mark, - .fname = (char *) buf->b_ffname, - .additional_data = fm.additional_data, + .name = NUL, + .mark = fm.fmark.mark, + .fname = (char *) fname, + .additional_data = fm.fmark.additional_data, } } - }, max_kbyte); - } - } - // FIXME: Copy previous marks, up to num_marked_files - // size_t num_marked_files = get_shada_parameter('\''); + } + }; + } while (jump_iter != NULL); - // 9. Registers - int max_num_lines_i = get_shada_parameter('<'); - if (max_num_lines_i < 0) { - max_num_lines_i = get_shada_parameter('"'); + // Initialize global marks + if (dump_global_marks) { + const void *global_mark_iter = NULL; + do { + char name; + xfmark_T fm; + global_mark_iter = mark_global_iter(global_mark_iter, &name, &fm); + if (fm.fmark.mark.lnum == 0) { + 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 || SHADA_REMOVABLE(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); } - if (max_num_lines_i != 0) { - const size_t max_num_lines = (max_num_lines_i < 0 - ? 0 - : (size_t) max_num_lines_i); - ShadaEntry *const registers = list_registers(max_num_lines); - for (ShadaEntry *reg = registers; reg->type != kSDItemMissing; reg++) { - bool did_convert = false; - if (sd_writer->sd_conv.vc_type != CONV_NONE) { - did_convert = true; - reg->data.reg.contents = xmemdup(reg->data.reg.contents, - (reg->data.reg.contents_size - * sizeof(reg->data.reg.contents))); - for (size_t i = 0; i < reg->data.reg.contents_size; i++) { - reg->data.reg.contents[i] = get_converted_string( - &sd_writer->sd_conv, - reg->data.reg.contents[i], strlen(reg->data.reg.contents[i])); + + // Initialize registers + if (dump_registers) { + const void *reg_iter = NULL; + do { + yankreg_T reg; + char name; + reg_iter = op_register_iter(reg_iter, &name, ®); + if (reg.y_array == NULL) { + 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 || SHADA_REMOVABLE(buf)) { + continue; } - shada_pack_entry(packer, *reg, max_kbyte); - if (did_convert) { - for (size_t i = 0; i < reg->data.reg.contents_size; i++) { - xfree(reg->data.reg.contents[i]); + 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) { + kh_key(&wms->file_marks, k) = xstrdup(fname); + memset(filemarks, 0, sizeof(*filemarks)); + } + filemarks->is_local_entry = true; + do { + fmark_T fm; + char name; + local_marks_iter = mark_buffer_iter(local_marks_iter, buf, &name, &fm); + if (fm.mark.lnum == 0) { + 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; } - xfree(reg->data.reg.contents); } + filemarks->changes_size = (size_t) buf->b_changelistlen; } - xfree(registers); } - // 10. Variables - if (find_shada_parameter('!') != NULL) { - const void *var_iter = NULL; - const Timestamp cur_timestamp = os_time(); - do { - typval_T vartv; - const char *name; - var_iter = var_shada_iter(var_iter, &name, &vartv); - if (var_iter == NULL && vartv.v_type == VAR_UNKNOWN) { + if (sd_reader == NULL) { + goto shada_write_remaining; + } + + ShadaEntry entry; + while (shada_read_next_item(sd_reader, &entry, srni_flags, max_kbyte) + == NOTDONE) { +#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; } - Object obj = vim_to_object(&vartv); - if (sd_writer->sd_conv.vc_type != CONV_NONE) { - convert_object(&sd_writer->sd_conv, &obj); + case kSDItemHeader: + case kSDItemBufferList: { + assert(false); } - shada_pack_entry(packer, (ShadaEntry) { - .type = kSDItemVariable, - .timestamp = cur_timestamp, - .data = { - .global_var = { - .name = (char *) name, - .value = obj, - .additional_elements = NULL, + case kSDItemUnknown: { + shada_pack_entry(packer, entry, 0); + 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) { + shada_pack_entry(packer, entry, 0); + 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) { + shada_pack_entry(packer, entry, 0); + 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)) { + shada_pack_entry(packer, entry, 0); + } + shada_free_shada_entry(&entry); + break; + } + case kSDItemGlobalMark: { + const int idx = mark_global_index(entry.data.filemark.name); + if (idx < 0) { + shada_pack_entry(packer, entry, 0); + 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) { + kh_key(&wms->file_marks, k) = xstrdup(fname); + 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 { + COMPARE_WITH_ENTRY(&filemarks->marks[idx], entry); + } + } else { + if (filemarks->is_local_entry) { + shada_free_shada_entry(&entry); + } else { + const int cl_len = (int) filemarks->changes_size; + int i; + for (i = cl_len; i > 0; i--) { + const ShadaEntry old_entry = filemarks->changes[i - 1].data; + if (old_entry.timestamp <= entry.timestamp) { + if (marks_equal(old_entry.data.filemark.mark, + entry.data.filemark.mark)) { + i = -1; + } + break; + } + } + if (i > 0) { + if (cl_len == JUMPLISTSIZE) { + if (filemarks->changes[0].can_free_entry) { + shada_free_shada_entry(&filemarks->changes[0].data); + } + memmove(&filemarks->changes[0], &filemarks->changes[1], + sizeof(filemarks->changes[0]) * (size_t) i); + } else if (i == 0) { + if (cl_len == JUMPLISTSIZE) { + i = -1; + } else { + memmove(&filemarks->changes[1], &filemarks->changes[0], + sizeof(filemarks->changes[0]) * (size_t) cl_len); + } + } + } + if (i != -1) { + filemarks->changes[i] = (PossiblyFreedShadaEntry) { + .can_free_entry = true, + .data = entry + }; + if (cl_len < JUMPLISTSIZE) { + filemarks->changes_size++; + } + } else { + shada_free_shada_entry(&entry); + } } } - }, max_kbyte); - api_free_object(obj); - clear_tv(&vartv); - } while (var_iter != NULL); + break; + } + case kSDItemJump: { + const int jl_len = (int) wms->jumps_size; + int i; + for (i = 0; i < jl_len; i++) { + const ShadaEntry old_entry = wms->jumps[i].data; + if (old_entry.timestamp >= entry.timestamp) { + if (marks_equal(old_entry.data.filemark.mark, + entry.data.filemark.mark) + && strcmp(old_entry.data.filemark.fname, + entry.data.filemark.fname) == 0) { + i = -1; + } + break; + } + } + if (i != -1) { + if (i < jl_len) { + if (jl_len == JUMPLISTSIZE) { + if (wms->jumps[0].can_free_entry) { + shada_free_shada_entry(&wms->jumps[0].data); + } + memmove(&wms->jumps[0], &wms->jumps[1], + sizeof(wms->jumps[0]) * (size_t) i); + } else { + memmove(&wms->jumps[i + 1], &wms->jumps[i], + sizeof(wms->jumps[0]) * (size_t) (jl_len - i)); + } + } else if (i == jl_len) { + if (jl_len == JUMPLISTSIZE) { + i = -1; + } else if (jl_len > 0) { + memmove(&wms->jumps[1], &wms->jumps[0], + sizeof(wms->jumps[0]) * (size_t) jl_len); + } + } + } + if (i != -1) { + wms->jumps[i] = (PossiblyFreedShadaEntry) { + .can_free_entry = true, + .data = entry, + }; + if (jl_len < JUMPLISTSIZE) { + wms->jumps_size++; + } + } else { + shada_free_shada_entry(&entry); + } + break; + } + } + } +#undef COMPARE_WITH_ENTRY + + // Write the rest +shada_write_remaining: +#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) { \ + shada_pack_encoded_entry(packer, &sd_writer->sd_conv, wms_array[i_], \ + max_kbyte); \ + } \ + } \ + } while (0) + PACK_WMS_ARRAY(wms->global_marks); + PACK_WMS_ARRAY(wms->registers); + for (size_t i = 0; i < wms->jumps_size; i++) { + shada_pack_encoded_entry(packer, &sd_writer->sd_conv, wms->jumps[i], + max_kbyte); + } +#define PACK_WMS_ENTRY(wms_entry) \ + do { \ + if (wms_entry.data.type != kSDItemMissing) { \ + shada_pack_encoded_entry(packer, &sd_writer->sd_conv, wms_entry, \ + max_kbyte); \ + } \ + } 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); + xfree((void *) kh_key(&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++) { + shada_pack_encoded_entry(packer, &sd_writer->sd_conv, + all_file_markss[i]->changes[j], max_kbyte); + } + for (size_t j = 0; j < all_file_markss[i]->additional_marks_size; j++) { + shada_pack_entry(packer, all_file_markss[i]->additional_marks[j], 0); + 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) { + shada_pack_encoded_entry(packer, &sd_writer->sd_conv, + (PossiblyFreedShadaEntry) { + .data = cur_entry->data, + .can_free_entry = + cur_entry->can_free_entry, + }, max_kbyte); + } + hms_dealloc(&wms->hms[i]); + } + } } -#undef RUN_WITH_CONVERTED_STRING + kh_dealloc(file_marks, &wms->file_marks); kh_destroy(bufset, removable_bufs); msgpack_packer_free(packer); + kh_dealloc(strset, &wms->dumped_variables); + xfree(wms); } #undef PACK_STATIC_STR @@ -2162,7 +2675,7 @@ shada_write_file_nomerge: {} convert_setup(&sd_writer.sd_conv, p_enc, "utf-8"); - shada_write(&sd_writer, NULL); + shada_write(&sd_writer, (nomerge ? NULL : &sd_reader)); close_file((int)(intptr_t) sd_writer.cookie); @@ -2519,12 +3032,15 @@ static inline char *get_converted_string(const vimconv_T *const sd_conv, /// @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 NOTDONE if entry was read correctly, FAIL if there were errors and /// OK at EOF. static int shada_read_next_item(ShaDaReadDef *const sd_reader, ShadaEntry *const entry, - const unsigned flags) + const unsigned flags, + const size_t max_kbyte) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { shada_read_next_item_start: @@ -2558,7 +3074,8 @@ shada_read_next_item_start: if ((type_u64 > SHADA_LAST_ENTRY ? !(flags & kSDReadUnknown) - : !((unsigned) (1 << type_u64) & flags))) { + : !((unsigned) (1 << type_u64) & flags)) + || (max_kbyte && length > max_kbyte * 1024)) { if (fread_len(sd_reader, NULL, length) != OK) { return FAIL; } @@ -3300,92 +3817,6 @@ shada_read_next_item_end: return NOTDONE; } -/// Return a list with all global marks set in current NeoVim instance -/// -/// List is not sorted. -/// -/// @warning Listed marks must be used before any buffer- or mark-editing -/// function is run. -/// -/// @param[in] removable_bufs Set of buffers on removable media. -/// -/// @return Array of ShadaEntry values, last one has type kSDItemMissing. -/// -/// @warning Resulting ShadaEntry values must not be freed. Returned -/// array must be freed with `xfree()`. -static ShadaEntry *list_global_marks( - const khash_t(bufset) *const removable_bufs) -{ - const void *iter = NULL; - const size_t nummarks = mark_global_amount(); - ShadaEntry *const ret = xmalloc(sizeof(ShadaEntry) * (nummarks + 1)); - ShadaEntry *cur = ret; - if (nummarks) { - do { - cur->type = kSDItemGlobalMark; - xfmark_T cur_fm; - iter = mark_global_iter(iter, &(cur->data.filemark.name), &cur_fm); - cur->data.filemark.mark = cur_fm.fmark.mark; - cur->data.filemark.additional_data = cur_fm.fmark.additional_data; - cur->timestamp = cur_fm.fmark.timestamp; - if (cur_fm.fmark.fnum == 0) { - if (cur_fm.fname == NULL) { - continue; - } - cur->data.filemark.fname = (char *) cur_fm.fname; - } else { - const buf_T *const buf = buflist_findnr(cur_fm.fmark.fnum); - if (buf == NULL || buf->b_ffname == NULL || SHADA_REMOVABLE(buf)) { - continue; - } else { - cur->data.filemark.fname = (char *) buf->b_ffname; - } - } - cur++; - } while(iter != NULL); - } - cur->type = kSDItemMissing; - return ret; -} - -/// Return a list with all buffer marks set in some buffer -/// -/// List is not sorted. -/// -/// @warning Listed marks must be used before any buffer- or mark-editing -/// function is run. -/// -/// @param[in] buf Buffer for which marks are listed. -/// -/// @return Array of ShadaEntry values, last one has type kSDItemMissing. -/// -/// @warning Resulting ShadaEntry values must not be freed. Returned -/// array must be freed with `xfree()`. -static ShadaEntry *list_buffer_marks(const buf_T *const buf) -{ - const char *const fname = (char *) buf->b_ffname; - const void *iter = NULL; - const size_t nummarks = mark_buffer_amount(buf); - ShadaEntry *const ret = xmalloc(sizeof(ShadaEntry) * (nummarks + 1)); - ShadaEntry *cur = ret; - if (nummarks) { - do { - cur->type = kSDItemLocalMark; - fmark_T cur_fm; - iter = mark_buffer_iter(iter, buf, &(cur->data.filemark.name), &cur_fm); - cur->data.filemark.mark = cur_fm.mark; - cur->data.filemark.fname = (char *) fname; - cur->data.filemark.additional_data = cur_fm.additional_data; - cur->timestamp = cur_fm.timestamp; - if (cur->data.filemark.mark.lnum != 0) { - cur++; - } - } while(iter != NULL); - } - cur->type = kSDItemMissing; - return ret; -} - /// Check whether "name" is on removable media (according to 'shada') /// /// @param[in] name Checked name. @@ -3414,52 +3845,6 @@ bool shada_removable(const char *name) return retval; } -/// Return a list with all registers and their contents -/// -/// List is not sorted. -/// -/// @warning Listed registers must be used before any register-editing function -/// is run. -/// -/// @param[in] max_num_lines Maximum number of lines in the register. If it is -/// zero then all registers are listed. -/// -/// @return Array of ShadaEntry values, last one has type kSDItemMissing. -/// -/// @warning Resulting ShadaEntry values must not be freed. Returned -/// array must be freed with `xfree()`. -static ShadaEntry *list_registers(const size_t max_num_lines) -{ - const void *iter = NULL; - const size_t numregs = op_register_amount(); - ShadaEntry *const ret = xmalloc(sizeof(ShadaEntry) * (numregs + 1)); - ShadaEntry *cur = ret; - if (numregs) { - do { - cur->type = kSDItemRegister; - yankreg_T cur_reg; - iter = op_register_iter(iter, &(cur->data.reg.name), &cur_reg); - if (max_num_lines && (size_t) cur_reg.y_size > max_num_lines) { - continue; - } - cur->data.reg.contents = (char **) cur_reg.y_array; - cur->data.reg.type = (uint8_t) cur_reg.y_type; - cur->data.reg.contents_size = (size_t) cur_reg.y_size; - if (cur_reg.y_type == MBLOCK) { - cur->data.reg.width = (size_t) cur_reg.y_width; - } else { - cur->data.reg.width = 0; - } - cur->data.reg.additional_data = cur_reg.additional_data; - cur->timestamp = cur_reg.timestamp; - cur++; - } while(iter != NULL); - } - cur->type = kSDItemMissing; - return ret; -} - - #if 0 static int viminfo_errcnt; -- cgit From 83785ef98b90901414f367f592c6132cf5c8a6f3 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 19 Jul 2015 21:35:13 +0300 Subject: viminfo: Remove old code --- src/nvim/mark.c | 2 +- src/nvim/shada.c | 1538 ------------------------------------------------------ 2 files changed, 1 insertion(+), 1539 deletions(-) diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 2072483e1d..e88cc11c39 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -1351,7 +1351,7 @@ size_t mark_buffer_amount(const buf_T *const buf) void mark_set_global(const char name, const xfmark_T fm, const bool update) { xfmark_T *fm_tgt = &(namedfm[mark_global_index(name)]); - if (fm_tgt == &namedfm[-1]) { + if (fm_tgt == &namedfm[0] - 1) { return; } if (update && fm.fmark.timestamp < fm_tgt->fmark.timestamp) { diff --git a/src/nvim/shada.c b/src/nvim/shada.c index a7bde92032..a6054db9d0 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -3844,1541 +3844,3 @@ bool shada_removable(const char *name) xfree(new_name); return retval; } - -#if 0 - -static int viminfo_errcnt; - -static int no_viminfo(void) -{ - /* "vim -i NONE" does not read or write a viminfo file */ - return use_viminfo != NULL && STRCMP(use_viminfo, "NONE") == 0; -} - -/* - * Report an error for reading a viminfo file. - * Count the number of errors. When there are more than 10, return TRUE. - */ -int viminfo_error(char *errnum, char *message, char_u *line) -{ - vim_snprintf((char *)IObuff, IOSIZE, _("%sviminfo: %s in line: "), - errnum, message); - STRNCAT(IObuff, line, IOSIZE - STRLEN(IObuff) - 1); - if (IObuff[STRLEN(IObuff) - 1] == '\n') - IObuff[STRLEN(IObuff) - 1] = NUL; - emsg(IObuff); - if (++viminfo_errcnt >= 10) { - EMSG(_("E136: viminfo: Too many errors, skipping rest of file")); - return TRUE; - } - return FALSE; -} - -/* - * read_viminfo() -- Read the viminfo file. Registers etc. which are already - * set are not over-written unless "flags" includes VIF_FORCEIT. -- webb - */ -int -read_viminfo ( - char_u *file, /* file name or NULL to use default name */ - int flags /* VIF_WANT_INFO et al. */ -) -{ - FILE *fp; - char_u *fname; - - if (no_viminfo()) - return FAIL; - - fname = viminfo_filename(file); /* get file name in allocated buffer */ - fp = mch_fopen((char *)fname, READBIN); - - if (p_verbose > 0) { - verbose_enter(); - smsg(_("Reading viminfo file \"%s\"%s%s%s"), - fname, - (flags & VIF_WANT_INFO) ? _(" info") : "", - (flags & VIF_WANT_MARKS) ? _(" marks") : "", - (flags & VIF_GET_OLDFILES) ? _(" oldfiles") : "", - fp == NULL ? _(" FAILED") : ""); - verbose_leave(); - } - - xfree(fname); - if (fp == NULL) - return FAIL; - - viminfo_errcnt = 0; - do_viminfo(fp, NULL, flags); - - fclose(fp); - return OK; -} - -/* - * Write the viminfo file. The old one is read in first so that effectively a - * merge of current info and old info is done. This allows multiple vims to - * run simultaneously, without losing any marks etc. - * If "forceit" is TRUE, then the old file is not read in, and only internal - * info is written to the file. - */ -void write_viminfo(char_u *file, int forceit) -{ - char_u *fname; - FILE *fp_in = NULL; /* input viminfo file, if any */ - FILE *fp_out = NULL; /* output viminfo file */ - char_u *tempname = NULL; /* name of temp viminfo file */ - char_u *wp; -#if defined(UNIX) - mode_t umask_save; -#endif - - if (no_viminfo()) - return; - - fname = viminfo_filename(file); /* may set to default if NULL */ - - fp_in = mch_fopen((char *)fname, READBIN); - if (fp_in == NULL) { - /* if it does exist, but we can't read it, don't try writing */ - if (os_file_exists(fname)) - goto end; -#if defined(UNIX) - /* - * For Unix we create the .viminfo non-accessible for others, - * because it may contain text from non-accessible documents. - */ - umask_save = umask(077); -#endif - fp_out = mch_fopen((char *)fname, WRITEBIN); -#if defined(UNIX) - (void)umask(umask_save); -#endif - } else { - /* - * There is an existing viminfo file. Create a temporary file to - * write the new viminfo into, in the same directory as the - * existing viminfo file, which will be renamed later. - */ -#ifdef UNIX - /* - * For Unix we check the owner of the file. It's not very nice to - * overwrite a user's viminfo file after a "su root", with a - * viminfo file that the user can't read. - */ - - FileInfo old_info; // FileInfo of existing viminfo file - if (os_fileinfo((char *)fname, &old_info) - && getuid() != ROOT_UID - && !(old_info.stat.st_uid == getuid() - ? (old_info.stat.st_mode & 0200) - : (old_info.stat.st_gid == getgid() - ? (old_info.stat.st_mode & 0020) - : (old_info.stat.st_mode & 0002)))) { - int tt = msg_didany; - - /* avoid a wait_return for this message, it's annoying */ - EMSG2(_("E137: Viminfo file is not writable: %s"), fname); - msg_didany = tt; - fclose(fp_in); - goto end; - } -#endif - - // Make tempname - tempname = (char_u *)modname((char *)fname, ".tmp", FALSE); - if (tempname != NULL) { - /* - * Check if tempfile already exists. Never overwrite an - * existing file! - */ - if (os_file_exists(tempname)) { - /* - * Try another name. Change one character, just before - * the extension. - */ - wp = tempname + STRLEN(tempname) - 5; - if (wp < path_tail(tempname)) /* empty file name? */ - wp = path_tail(tempname); - for (*wp = 'z'; os_file_exists(tempname); --*wp) { - /* - * They all exist? Must be something wrong! Don't - * write the viminfo file then. - */ - if (*wp == 'a') { - xfree(tempname); - tempname = NULL; - break; - } - } - } - } - - if (tempname != NULL) { - int fd; - - /* Use os_open() to be able to use O_NOFOLLOW and set file - * protection: - * Unix: same as original file, but strip s-bit. Reset umask to - * avoid it getting in the way. - * Others: r&w for user only. */ -# ifdef UNIX - umask_save = umask(0); - fd = os_open((char *)tempname, - O_CREAT|O_EXCL|O_WRONLY|O_NOFOLLOW, - (int)((old_info.stat.st_mode & 0777) | 0600)); - (void)umask(umask_save); -# else - fd = os_open((char *)tempname, - O_CREAT|O_EXCL|O_WRONLY|O_NOFOLLOW, 0600); -# endif - if (fd < 0) - fp_out = NULL; - else - fp_out = fdopen(fd, WRITEBIN); - - /* - * If we can't create in the same directory, try creating a - * "normal" temp file. - */ - if (fp_out == NULL) { - xfree(tempname); - if ((tempname = vim_tempname()) != NULL) - fp_out = mch_fopen((char *)tempname, WRITEBIN); - } - -#ifdef UNIX - /* - * Make sure the owner can read/write it. This only works for - * root. - */ - if (fp_out != NULL) { - os_fchown(fileno(fp_out), old_info.stat.st_uid, old_info.stat.st_gid); - } -#endif - } - } - - /* - * Check if the new viminfo file can be written to. - */ - if (fp_out == NULL) { - EMSG2(_("E138: Can't write viminfo file %s!"), - (fp_in == NULL || tempname == NULL) ? fname : tempname); - if (fp_in != NULL) - fclose(fp_in); - goto end; - } - - if (p_verbose > 0) { - verbose_enter(); - smsg(_("Writing viminfo file \"%s\""), fname); - verbose_leave(); - } - - viminfo_errcnt = 0; - do_viminfo(fp_in, fp_out, forceit ? 0 : (VIF_WANT_INFO | VIF_WANT_MARKS)); - - fclose(fp_out); /* errors are ignored !? */ - if (fp_in != NULL) { - fclose(fp_in); - - /* In case of an error keep the original viminfo file. Otherwise - * rename the newly written file. Give an error if that fails. */ - if (viminfo_errcnt == 0 && vim_rename(tempname, fname) == -1) { - viminfo_errcnt++; - EMSG2(_("E886: Can't rename viminfo file to %s!"), fname); - } - if (viminfo_errcnt > 0) { - os_remove((char *)tempname); - } - } - -end: - xfree(fname); - xfree(tempname); -} - -/* - * Get the viminfo file name to use. - * If "file" is given and not empty, use it (has already been expanded by - * cmdline functions). - * Otherwise use "-i file_name", value from 'viminfo' or the default, and - * expand environment variables. - * Returns an allocated string. - */ -static char_u *viminfo_filename(char_u *file) -{ - if (file == NULL || *file == NUL) { - if (use_viminfo != NULL) - file = use_viminfo; - else if ((file = find_viminfo_parameter('n')) == NULL || *file == NUL) { -#ifdef VIMINFO_FILE2 - // don't use $HOME when not defined (turned into "c:/"!). - if (!os_env_exists("HOME")) { - // don't use $VIM when not available. - expand_env((char_u *)"$VIM", NameBuff, MAXPATHL); - if (STRCMP("$VIM", NameBuff) != 0) /* $VIM was expanded */ - file = (char_u *)VIMINFO_FILE2; - else - file = (char_u *)VIMINFO_FILE; - } else -#endif - file = (char_u *)VIMINFO_FILE; - } - expand_env(file, NameBuff, MAXPATHL); - file = NameBuff; - } - return vim_strsave(file); -} - -/* - * do_viminfo() -- Should only be called from read_viminfo() & write_viminfo(). - */ -static void do_viminfo(FILE *fp_in, FILE *fp_out, int flags) -{ - int count = 0; - int eof = FALSE; - vir_T vir; - int merge = FALSE; - - vir.vir_line = xmalloc(LSIZE); - vir.vir_fd = fp_in; - vir.vir_conv.vc_type = CONV_NONE; - - if (fp_in != NULL) { - if (flags & VIF_WANT_INFO) { - eof = read_viminfo_up_to_marks(&vir, - flags & VIF_FORCEIT, fp_out != NULL); - merge = TRUE; - } else if (flags != 0) - /* Skip info, find start of marks */ - while (!(eof = viminfo_readline(&vir)) - && vir.vir_line[0] != '>') - ; - } - if (fp_out != NULL) { - /* Write the info: */ - fprintf(fp_out, _("# This viminfo file was generated by Nvim %s.\n"), - mediumVersion); - fputs(_("# You may edit it if you're careful!\n\n"), fp_out); - fputs(_("# Value of 'encoding' when this file was written\n"), fp_out); - fprintf(fp_out, "*encoding=%s\n\n", p_enc); - write_viminfo_search_pattern(fp_out); - write_viminfo_sub_string(fp_out); - write_viminfo_history(fp_out, merge); - write_viminfo_registers(fp_out); - write_viminfo_varlist(fp_out); - write_viminfo_filemarks(fp_out); - write_viminfo_bufferlist(fp_out); - count = write_viminfo_marks(fp_out); - } - if (fp_in != NULL - && (flags & (VIF_WANT_MARKS | VIF_GET_OLDFILES | VIF_FORCEIT))) - copy_viminfo_marks(&vir, fp_out, count, eof, flags); - - xfree(vir.vir_line); - if (vir.vir_conv.vc_type != CONV_NONE) - convert_setup(&vir.vir_conv, NULL, NULL); -} - -/* - * read_viminfo_up_to_marks() -- Only called from do_viminfo(). Reads in the - * first part of the viminfo file which contains everything but the marks that - * are local to a file. Returns TRUE when end-of-file is reached. -- webb - */ -static int read_viminfo_up_to_marks(vir_T *virp, int forceit, int writing) -{ - int eof; - - prepare_viminfo_history(forceit ? 9999 : 0, writing); - eof = viminfo_readline(virp); - while (!eof && virp->vir_line[0] != '>') { - switch (virp->vir_line[0]) { - /* Characters reserved for future expansion, ignored now */ - case '+': /* "+40 /path/dir file", for running vim without args */ - case '|': /* to be defined */ - case '^': /* to be defined */ - case '<': /* long line - ignored */ - /* A comment or empty line. */ - case NUL: - case '\r': - case '\n': - case '#': - eof = viminfo_readline(virp); - break; - case '*': /* "*encoding=value" */ - eof = viminfo_encoding(virp); - break; - case '!': /* global variable */ - eof = read_viminfo_varlist(virp, writing); - break; - case '%': /* entry for buffer list */ - eof = read_viminfo_bufferlist(virp, writing); - break; - case '"': - eof = read_viminfo_register(virp, forceit); - break; - case '/': /* Search string */ - case '&': /* Substitute search string */ - case '~': /* Last search string, followed by '/' or '&' */ - eof = read_viminfo_search_pattern(virp, forceit); - break; - case '$': - eof = read_viminfo_sub_string(virp, forceit); - break; - case ':': - case '?': - case '=': - case '@': - eof = read_viminfo_history(virp, writing); - break; - case '-': - case '\'': - eof = read_viminfo_filemark(virp, forceit); - break; - default: - if (viminfo_error("E575: ", _("Illegal starting char"), - virp->vir_line)) - eof = TRUE; - else - eof = viminfo_readline(virp); - break; - } - } - - /* Finish reading history items. */ - if (!writing) - finish_viminfo_history(); - - /* Change file names to buffer numbers for fmarks. */ - FOR_ALL_BUFFERS(buf) { - fmarks_check_names(buf); - } - - return eof; -} - -/* - * Compare the 'encoding' value in the viminfo file with the current value of - * 'encoding'. If different and the 'c' flag is in 'viminfo', setup for - * conversion of text with iconv() in viminfo_readstring(). - */ -static int viminfo_encoding(vir_T *virp) -{ - char_u *p; - int i; - - if (get_viminfo_parameter('c') != 0) { - p = vim_strchr(virp->vir_line, '='); - if (p != NULL) { - /* remove trailing newline */ - ++p; - for (i = 0; vim_isprintc(p[i]); ++i) - ; - p[i] = NUL; - - convert_setup(&virp->vir_conv, p, p_enc); - } - } - return viminfo_readline(virp); -} - -/* - * Read a line from the viminfo file. - * Returns TRUE for end-of-file; - */ -int viminfo_readline(vir_T *virp) -{ - return vim_fgets(virp->vir_line, LSIZE, virp->vir_fd); -} - -/* - * check string read from viminfo file - * remove '\n' at the end of the line - * - replace CTRL-V CTRL-V with CTRL-V - * - replace CTRL-V 'n' with '\n' - * - * Check for a long line as written by viminfo_writestring(). - * - * Return the string in allocated memory. - */ -char_u * -viminfo_readstring ( - vir_T *virp, - int off, /* offset for virp->vir_line */ - int convert /* convert the string */ -) - FUNC_ATTR_NONNULL_RET -{ - char_u *retval; - char_u *s, *d; - - if (virp->vir_line[off] == Ctrl_V && ascii_isdigit(virp->vir_line[off + 1])) { - ssize_t len = atol((char *)virp->vir_line + off + 1); - retval = xmalloc(len); - // TODO(philix): change type of vim_fgets() size argument to size_t - (void)vim_fgets(retval, (int)len, virp->vir_fd); - s = retval + 1; /* Skip the leading '<' */ - } else { - retval = vim_strsave(virp->vir_line + off); - s = retval; - } - - /* Change CTRL-V CTRL-V to CTRL-V and CTRL-V n to \n in-place. */ - d = retval; - while (*s != NUL && *s != '\n') { - if (s[0] == Ctrl_V && s[1] != NUL) { - if (s[1] == 'n') - *d++ = '\n'; - else - *d++ = Ctrl_V; - s += 2; - } else - *d++ = *s++; - } - *d = NUL; - - if (convert && virp->vir_conv.vc_type != CONV_NONE && *retval != NUL) { - d = string_convert(&virp->vir_conv, retval, NULL); - if (d != NULL) { - xfree(retval); - retval = d; - } - } - - return retval; -} - -/* - * write string to viminfo file - * - replace CTRL-V with CTRL-V CTRL-V - * - replace '\n' with CTRL-V 'n' - * - add a '\n' at the end - * - * For a long line: - * - write " CTRL-V \n " in first line - * - write " < \n " in second line - */ -void viminfo_writestring(FILE *fd, char_u *p) -{ - int c; - char_u *s; - int len = 0; - - for (s = p; *s != NUL; ++s) { - if (*s == Ctrl_V || *s == '\n') - ++len; - ++len; - } - - /* If the string will be too long, write its length and put it in the next - * line. Take into account that some room is needed for what comes before - * the string (e.g., variable name). Add something to the length for the - * '<', NL and trailing NUL. */ - if (len > LSIZE / 2) - fprintf(fd, "\026%d\n<", len + 3); - - while ((c = *p++) != NUL) { - if (c == Ctrl_V || c == '\n') { - putc(Ctrl_V, fd); - if (c == '\n') - c = 'n'; - } - putc(c, fd); - } - putc('\n', fd); -} - -/* - * Write all the named marks for all buffers. - * Return the number of buffers for which marks have been written. - */ -int write_viminfo_marks(FILE *fp_out) -{ - /* - * Set b_last_cursor for the all buffers that have a window. - */ - FOR_ALL_TAB_WINDOWS(tp, win) { - set_last_cursor(win); - } - - fputs(_("\n# History of marks within files (newest to oldest):\n"), fp_out); - int count = 0; - FOR_ALL_BUFFERS(buf) { - /* - * Only write something if buffer has been loaded and at least one - * mark is set. - */ - if (buf->b_marks_read) { - bool is_mark_set = true; - if (buf->b_last_cursor.lnum == 0) { - is_mark_set = false; - for (int i = 0; i < NMARKS; i++) { - if (buf->b_namedm[i].lnum != 0) { - is_mark_set = true; - break; - } - } - } - if (is_mark_set && buf->b_ffname != NULL - && buf->b_ffname[0] != NUL && !removable(buf->b_ffname)) { - home_replace(NULL, buf->b_ffname, IObuff, IOSIZE, TRUE); - fprintf(fp_out, "\n> "); - viminfo_writestring(fp_out, IObuff); - write_one_mark(fp_out, '"', &buf->b_last_cursor); - write_one_mark(fp_out, '^', &buf->b_last_insert); - write_one_mark(fp_out, '.', &buf->b_last_change); - /* changelist positions are stored oldest first */ - for (int i = 0; i < buf->b_changelistlen; ++i) { - write_one_mark(fp_out, '+', &buf->b_changelist[i]); - } - for (int i = 0; i < NMARKS; i++) { - write_one_mark(fp_out, 'a' + i, &buf->b_namedm[i]); - } - count++; - } - } - } - - return count; -} - -static void write_one_mark(FILE *fp_out, int c, pos_T *pos) -{ - if (pos->lnum != 0) - fprintf(fp_out, "\t%c\t%" PRId64 "\t%d\n", c, - (int64_t)pos->lnum, (int)pos->col); -} - -/* - * Handle marks in the viminfo file: - * fp_out != NULL: copy marks for buffers not in buffer list - * fp_out == NULL && (flags & VIF_WANT_MARKS): read marks for curbuf only - * fp_out == NULL && (flags & VIF_GET_OLDFILES | VIF_FORCEIT): fill v:oldfiles - */ -void copy_viminfo_marks(vir_T *virp, FILE *fp_out, int count, int eof, int flags) -{ - char_u *line = virp->vir_line; - buf_T *buf; - int num_marked_files; - int load_marks; - int copy_marks_out; - char_u *str; - int i; - char_u *p; - char_u *name_buf; - pos_T pos; - list_T *list = NULL; - - name_buf = xmalloc(LSIZE); - *name_buf = NUL; - - if (fp_out == NULL && (flags & (VIF_GET_OLDFILES | VIF_FORCEIT))) { - list = list_alloc(); - set_vim_var_list(VV_OLDFILES, list); - } - - num_marked_files = get_viminfo_parameter('\''); - while (!eof && (count < num_marked_files || fp_out == NULL)) { - if (line[0] != '>') { - if (line[0] != '\n' && line[0] != '\r' && line[0] != '#') { - if (viminfo_error("E576: ", _("Missing '>'"), line)) - break; /* too many errors, return now */ - } - eof = vim_fgets(line, LSIZE, virp->vir_fd); - continue; /* Skip this dud line */ - } - - /* - * Handle long line and translate escaped characters. - * Find file name, set str to start. - * Ignore leading and trailing white space. - */ - str = skipwhite(line + 1); - str = viminfo_readstring(virp, (int)(str - virp->vir_line), FALSE); - p = str + STRLEN(str); - while (p != str && (*p == NUL || ascii_isspace(*p))) - p--; - if (*p) - p++; - *p = NUL; - - if (list != NULL) - list_append_string(list, str, -1); - - /* - * If fp_out == NULL, load marks for current buffer. - * If fp_out != NULL, copy marks for buffers not in buflist. - */ - load_marks = copy_marks_out = FALSE; - if (fp_out == NULL) { - if ((flags & VIF_WANT_MARKS) && curbuf->b_ffname != NULL) { - if (*name_buf == NUL) /* only need to do this once */ - home_replace(NULL, curbuf->b_ffname, name_buf, LSIZE, TRUE); - if (fnamecmp(str, name_buf) == 0) - load_marks = TRUE; - } - } else { /* fp_out != NULL */ - /* This is slow if there are many buffers!! */ - buf = NULL; - FOR_ALL_BUFFERS(bp) { - if (bp->b_ffname != NULL) { - home_replace(NULL, bp->b_ffname, name_buf, LSIZE, TRUE); - if (fnamecmp(str, name_buf) == 0) { - buf = bp; - break; - } - } - } - - /* - * copy marks if the buffer has not been loaded - */ - if (buf == NULL || !buf->b_marks_read) { - copy_marks_out = TRUE; - fputs("\n> ", fp_out); - viminfo_writestring(fp_out, str); - count++; - } - } - free(str); - - pos.coladd = 0; - while (!(eof = viminfo_readline(virp)) && line[0] == TAB) { - if (load_marks) { - if (line[1] != NUL) { - int64_t lnum_64; - unsigned int u; - sscanf((char *)line + 2, "%" SCNd64 "%u", &lnum_64, &u); - // safely downcast to linenr_T (long); remove when linenr_T refactored - assert(lnum_64 <= LONG_MAX); - pos.lnum = (linenr_T)lnum_64; - assert(u <= INT_MAX); - pos.col = (colnr_T)u; - switch (line[1]) { - case '"': curbuf->b_last_cursor = pos; break; - case '^': curbuf->b_last_insert = pos; break; - case '.': curbuf->b_last_change = pos; break; - case '+': - /* changelist positions are stored oldest - * first */ - if (curbuf->b_changelistlen == JUMPLISTSIZE) - /* list is full, remove oldest entry */ - memmove(curbuf->b_changelist, - curbuf->b_changelist + 1, - sizeof(pos_T) * (JUMPLISTSIZE - 1)); - else - ++curbuf->b_changelistlen; - curbuf->b_changelist[ - curbuf->b_changelistlen - 1] = pos; - break; - default: if ((i = line[1] - 'a') >= 0 && i < NMARKS) - curbuf->b_namedm[i] = pos; - } - } - } else if (copy_marks_out) - fputs((char *)line, fp_out); - } - if (load_marks) { - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer == curbuf) - wp->w_changelistidx = curbuf->b_changelistlen; - } - break; - } - } - free(name_buf); -} - -int read_viminfo_filemark(vir_T *virp, int force) -{ - char_u *str; - xfmark_T *fm; - int i; - - /* We only get here if line[0] == '\'' or '-'. - * Illegal mark names are ignored (for future expansion). */ - str = virp->vir_line + 1; - if ( - *str <= 127 && - ((*virp->vir_line == '\'' && (ascii_isdigit(*str) || isupper(*str))) - || (*virp->vir_line == '-' && *str == '\''))) { - if (*str == '\'') { - /* If the jumplist isn't full insert fmark as oldest entry */ - if (curwin->w_jumplistlen == JUMPLISTSIZE) - fm = NULL; - else { - for (i = curwin->w_jumplistlen; i > 0; --i) - curwin->w_jumplist[i] = curwin->w_jumplist[i - 1]; - ++curwin->w_jumplistidx; - ++curwin->w_jumplistlen; - fm = &curwin->w_jumplist[0]; - fm->fmark.mark.lnum = 0; - fm->fname = NULL; - } - } else if (ascii_isdigit(*str)) - fm = &namedfm[*str - '0' + NMARKS]; - else { // is uppercase - assert(*str >= 'A' && *str <= 'Z'); - fm = &namedfm[*str - 'A']; - } - if (fm != NULL && (fm->fmark.mark.lnum == 0 || force)) { - str = skipwhite(str + 1); - fm->fmark.mark.lnum = getdigits_long(&str); - str = skipwhite(str); - fm->fmark.mark.col = getdigits_int(&str); - fm->fmark.mark.coladd = 0; - fm->fmark.fnum = 0; - str = skipwhite(str); - free(fm->fname); - fm->fname = viminfo_readstring(virp, (int)(str - virp->vir_line), - FALSE); - } - } - return vim_fgets(virp->vir_line, LSIZE, virp->vir_fd); -} - -void write_viminfo_filemarks(FILE *fp) -{ - int i; - char_u *name; - buf_T *buf; - xfmark_T *fm; - - if (get_viminfo_parameter('f') == 0) - return; - - fputs(_("\n# File marks:\n"), fp); - - /* - * Find a mark that is the same file and position as the cursor. - * That one, or else the last one is deleted. - * Move '0 to '1, '1 to '2, etc. until the matching one or '9 - * Set '0 mark to current cursor position. - */ - if (curbuf->b_ffname != NULL && !removable(curbuf->b_ffname)) { - name = buflist_nr2name(curbuf->b_fnum, TRUE, FALSE); - for (i = NMARKS; i < NMARKS + EXTRA_MARKS - 1; ++i) - if (namedfm[i].fmark.mark.lnum == curwin->w_cursor.lnum - && (namedfm[i].fname == NULL - ? namedfm[i].fmark.fnum == curbuf->b_fnum - : (name != NULL - && STRCMP(name, namedfm[i].fname) == 0))) - break; - free(name); - - free(namedfm[i].fname); - for (; i > NMARKS; --i) - namedfm[i] = namedfm[i - 1]; - namedfm[NMARKS].fmark.mark = curwin->w_cursor; - namedfm[NMARKS].fmark.fnum = curbuf->b_fnum; - namedfm[NMARKS].fname = NULL; - } - - /* Write the filemarks '0 - '9 and 'A - 'Z */ - for (i = 0; i < NMARKS + EXTRA_MARKS; i++) - write_one_filemark(fp, &namedfm[i], '\'', - i < NMARKS ? i + 'A' : i - NMARKS + '0'); - - /* Write the jumplist with -' */ - fputs(_("\n# Jumplist (newest first):\n"), fp); - setpcmark(); /* add current cursor position */ - cleanup_jumplist(); - for (fm = &curwin->w_jumplist[curwin->w_jumplistlen - 1]; - fm >= &curwin->w_jumplist[0]; --fm) { - if (fm->fmark.fnum == 0 - || ((buf = buflist_findnr(fm->fmark.fnum)) != NULL - && !removable(buf->b_ffname))) - write_one_filemark(fp, fm, '-', '\''); - } -} - -int read_viminfo_search_pattern(vir_T *virp, int force) -{ - char_u *lp; - int idx = -1; - int magic = FALSE; - int no_scs = FALSE; - int off_line = FALSE; - int off_end = 0; - long off = 0; - int setlast = FALSE; - static int hlsearch_on = FALSE; - char_u *val; - - /* - * Old line types: - * "/pat", "&pat": search/subst. pat - * "~/pat", "~&pat": last used search/subst. pat - * New line types: - * "~h", "~H": hlsearch highlighting off/on - * "~pat" - * : 'm' off, 'M' on - * : 's' off, 'S' on - * : 'L' line offset, 'l' char offset - * : 'E' from end, 'e' from start - * : decimal, offset - * : '~' last used pattern - * : '/' search pat, '&' subst. pat - */ - lp = virp->vir_line; - if (lp[0] == '~' && (lp[1] == 'm' || lp[1] == 'M')) { /* new line type */ - if (lp[1] == 'M') /* magic on */ - magic = TRUE; - if (lp[2] == 's') - no_scs = TRUE; - if (lp[3] == 'L') - off_line = TRUE; - if (lp[4] == 'E') - off_end = SEARCH_END; - lp += 5; - off = getdigits_long(&lp); - } - if (lp[0] == '~') { /* use this pattern for last-used pattern */ - setlast = TRUE; - lp++; - } - if (lp[0] == '/') - idx = RE_SEARCH; - else if (lp[0] == '&') - idx = RE_SUBST; - else if (lp[0] == 'h') /* ~h: 'hlsearch' highlighting off */ - hlsearch_on = FALSE; - else if (lp[0] == 'H') /* ~H: 'hlsearch' highlighting on */ - hlsearch_on = TRUE; - if (idx >= 0) { - if (force || spats[idx].pat == NULL) { - val = viminfo_readstring(virp, (int)(lp - virp->vir_line + 1), TRUE); - set_last_search_pat(val, idx, magic, setlast); - xfree(val); - spats[idx].no_scs = no_scs; - spats[idx].off.line = off_line; - spats[idx].off.end = off_end; - spats[idx].off.off = off; - if (setlast) { - SET_NO_HLSEARCH(!hlsearch_on); - } - } - } - return viminfo_readline(virp); -} - -void write_viminfo_search_pattern(FILE *fp) -{ - if (get_viminfo_parameter('/') != 0) { - fprintf(fp, "\n# hlsearch on (H) or off (h):\n~%c", - (no_hlsearch || find_viminfo_parameter('h') != NULL) ? 'h' : 'H'); - wvsp_one(fp, RE_SEARCH, "", '/'); - wvsp_one(fp, RE_SUBST, _("Substitute "), '&'); - } -} - -static void -wvsp_one ( - FILE *fp, /* file to write to */ - int idx, /* spats[] index */ - char *s, /* search pat */ - int sc /* dir char */ -) -{ - if (spats[idx].pat != NULL) { - fprintf(fp, _("\n# Last %sSearch Pattern:\n~"), s); - /* off.dir is not stored, it's reset to forward */ - fprintf(fp, "%c%c%c%c%" PRId64 "%s%c", - spats[idx].magic ? 'M' : 'm', /* magic */ - spats[idx].no_scs ? 's' : 'S', /* smartcase */ - spats[idx].off.line ? 'L' : 'l', /* line offset */ - spats[idx].off.end ? 'E' : 'e', /* offset from end */ - (int64_t)spats[idx].off.off, /* offset */ - last_idx == idx ? "~" : "", /* last used pat */ - sc); - viminfo_writestring(fp, spats[idx].pat); - } -} - -/* - * Prepare for reading the history from the viminfo file. - * This allocates history arrays to store the read history lines. - */ -void prepare_viminfo_history(int asklen, int writing) -{ - int i; - int num; - - init_history(); - viminfo_add_at_front = (asklen != 0 && !writing); - if (asklen > hislen) - asklen = hislen; - - for (int type = 0; type < HIST_COUNT; ++type) { - /* Count the number of empty spaces in the history list. Entries read - * from viminfo previously are also considered empty. If there are - * more spaces available than we request, then fill them up. */ - for (i = 0, num = 0; i < hislen; i++) - if (history[type][i].hisstr == NULL || history[type][i].viminfo) - num++; - int len = asklen; - if (num > len) - len = num; - if (len <= 0) - viminfo_history[type] = NULL; - else - viminfo_history[type] = xmalloc(len * sizeof(char_u *)); - if (viminfo_history[type] == NULL) - len = 0; - viminfo_hislen[type] = len; - viminfo_hisidx[type] = 0; - } -} - -/* - * Accept a line from the viminfo, store it in the history array when it's - * new. - */ -int read_viminfo_history(vir_T *virp, int writing) -{ - int type; - char_u *val; - - type = hist_char2type(virp->vir_line[0]); - if (viminfo_hisidx[type] < viminfo_hislen[type]) { - val = viminfo_readstring(virp, 1, TRUE); - if (val != NULL && *val != NUL) { - int sep = (*val == ' ' ? NUL : *val); - - if (!in_history(type, val + (type == HIST_SEARCH), - viminfo_add_at_front, sep, writing)) { - /* Need to re-allocate to append the separator byte. */ - size_t len = STRLEN(val); - char_u *p = xmalloc(len + 2); - if (type == HIST_SEARCH) { - /* Search entry: Move the separator from the first - * column to after the NUL. */ - memmove(p, val + 1, len); - p[len] = sep; - } else { - /* Not a search entry: No separator in the viminfo - * file, add a NUL separator. */ - memmove(p, val, len + 1); - p[len + 1] = NUL; - } - viminfo_history[type][viminfo_hisidx[type]++] = p; - } - } - xfree(val); - } - return viminfo_readline(virp); -} - -/* - * Finish reading history lines from viminfo. Not used when writing viminfo. - */ -void finish_viminfo_history(void) -{ - int idx; - int i; - int type; - - for (type = 0; type < HIST_COUNT; ++type) { - if (history[type] == NULL) - continue; - idx = hisidx[type] + viminfo_hisidx[type]; - if (idx >= hislen) - idx -= hislen; - else if (idx < 0) - idx = hislen - 1; - if (viminfo_add_at_front) - hisidx[type] = idx; - else { - if (hisidx[type] == -1) - hisidx[type] = hislen - 1; - do { - if (history[type][idx].hisstr != NULL - || history[type][idx].viminfo) - break; - if (++idx == hislen) - idx = 0; - } while (idx != hisidx[type]); - if (idx != hisidx[type] && --idx < 0) - idx = hislen - 1; - } - for (i = 0; i < viminfo_hisidx[type]; i++) { - xfree(history[type][idx].hisstr); - history[type][idx].hisstr = viminfo_history[type][i]; - history[type][idx].viminfo = TRUE; - if (--idx < 0) - idx = hislen - 1; - } - idx += 1; - idx %= hislen; - for (i = 0; i < viminfo_hisidx[type]; i++) { - history[type][idx++].hisnum = ++hisnum[type]; - idx %= hislen; - } - xfree(viminfo_history[type]); - viminfo_history[type] = NULL; - viminfo_hisidx[type] = 0; - } -} - -/* - * Write history to viminfo file in "fp". - * When "merge" is TRUE merge history lines with a previously read viminfo - * file, data is in viminfo_history[]. - * When "merge" is FALSE just write all history lines. Used for ":wviminfo!". - */ -void write_viminfo_history(FILE *fp, int merge) -{ - int i; - int type; - int num_saved; - char_u *p; - int c; - int round; - - init_history(); - if (hislen == 0) - return; - for (type = 0; type < HIST_COUNT; ++type) { - num_saved = get_viminfo_parameter(hist_type2char(type, FALSE)); - if (num_saved == 0) - continue; - if (num_saved < 0) /* Use default */ - num_saved = hislen; - fprintf(fp, _("\n# %s History (newest to oldest):\n"), - type == HIST_CMD ? _("Command Line") : - type == HIST_SEARCH ? _("Search String") : - type == HIST_EXPR ? _("Expression") : - _("Input Line")); - if (num_saved > hislen) - num_saved = hislen; - - /* - * Merge typed and viminfo history: - * round 1: history of typed commands. - * round 2: history from recently read viminfo. - */ - for (round = 1; round <= 2; ++round) { - if (round == 1) - /* start at newest entry, somewhere in the list */ - i = hisidx[type]; - else if (viminfo_hisidx[type] > 0) - /* start at newest entry, first in the list */ - i = 0; - else - /* empty list */ - i = -1; - if (i >= 0) - while (num_saved > 0 - && !(round == 2 && i >= viminfo_hisidx[type])) { - p = round == 1 ? history[type][i].hisstr - : viminfo_history[type] == NULL ? NULL - : viminfo_history[type][i]; - if (p != NULL && (round == 2 - || !merge - || !history[type][i].viminfo)) { - --num_saved; - fputc(hist_type2char(type, TRUE), fp); - /* For the search history: put the separator in the - * second column; use a space if there isn't one. */ - if (type == HIST_SEARCH) { - c = p[STRLEN(p) + 1]; - putc(c == NUL ? ' ' : c, fp); - } - viminfo_writestring(fp, p); - } - if (round == 1) { - /* Decrement index, loop around and stop when back at - * the start. */ - if (--i < 0) - i = hislen - 1; - if (i == hisidx[type]) - break; - } else { - /* Increment index. Stop at the end in the while. */ - ++i; - } - } - } - for (i = 0; i < viminfo_hisidx[type]; ++i) - if (viminfo_history[type] != NULL) - xfree(viminfo_history[type][i]); - xfree(viminfo_history[type]); - viminfo_history[type] = NULL; - viminfo_hisidx[type] = 0; - } -} - -int read_viminfo_register(vir_T *virp, int force) -{ - int eof; - int do_it = TRUE; - int size; - int limit; - int set_prev = FALSE; - char_u *str; - char_u **array = NULL; - - /* We only get here (hopefully) if line[0] == '"' */ - str = virp->vir_line + 1; - - /* If the line starts with "" this is the y_previous register. */ - if (*str == '"') { - set_prev = TRUE; - str++; - } - - if (!ASCII_ISALNUM(*str) && *str != '-') { - if (viminfo_error("E577: ", _("Illegal register name"), virp->vir_line)) - return TRUE; /* too many errors, pretend end-of-file */ - do_it = FALSE; - } - yankreg_T *reg = get_yank_register(*str++, YREG_PUT); - if (!force && reg->y_array != NULL) - do_it = FALSE; - - if (*str == '@') { - /* "x@: register x used for @@ */ - if (force || execreg_lastc == NUL) - execreg_lastc = str[-1]; - } - - size = 0; - limit = 100; /* Optimized for registers containing <= 100 lines */ - if (do_it) { - if (set_prev) { - y_previous = reg; - } - - free_register(reg); - array = xmalloc(limit * sizeof(char_u *)); - - str = skipwhite(skiptowhite(str)); - if (STRNCMP(str, "CHAR", 4) == 0) { - reg->y_type = MCHAR; - } else if (STRNCMP(str, "BLOCK", 5) == 0) { - reg->y_type = MBLOCK; - } else { - reg->y_type = MLINE; - } - /* get the block width; if it's missing we get a zero, which is OK */ - str = skipwhite(skiptowhite(str)); - reg->y_width = getdigits_int(&str); - } - - while (!(eof = viminfo_readline(virp)) - && (virp->vir_line[0] == TAB || virp->vir_line[0] == '<')) { - if (do_it) { - if (size >= limit) { - limit *= 2; - array = xrealloc(array, limit * sizeof(char_u *)); - } - array[size++] = viminfo_readstring(virp, 1, TRUE); - } - } - - if (do_it) { - if (size == 0) { - xfree(array); - } else if (size < limit) { - reg->y_array = xrealloc(array, size * sizeof(char_u *)); - } else { - reg->y_array = array; - } - reg->y_size = size; - } - return eof; -} - -void write_viminfo_registers(FILE *fp) -{ - int i, j; - char_u *type; - char_u c; - int num_lines; - int max_num_lines; - int max_kbyte; - long len; - - fputs(_("\n# Registers:\n"), fp); - - /* Get '<' value, use old '"' value if '<' is not found. */ - max_num_lines = get_viminfo_parameter('<'); - if (max_num_lines < 0) - max_num_lines = get_viminfo_parameter('"'); - if (max_num_lines == 0) - return; - max_kbyte = get_viminfo_parameter('s'); - if (max_kbyte == 0) - return; - - // don't include clipboard registers '*'/'+' - for (i = 0; i < NUM_SAVED_REGISTERS; i++) { - if (y_regs[i].y_array == NULL) - continue; - - /* Skip empty registers. */ - num_lines = y_regs[i].y_size; - if (num_lines == 0 - || (num_lines == 1 && y_regs[i].y_type == MCHAR - && *y_regs[i].y_array[0] == NUL)) - continue; - - if (max_kbyte > 0) { - /* Skip register if there is more text than the maximum size. */ - len = 0; - for (j = 0; j < num_lines; j++) - len += (long)STRLEN(y_regs[i].y_array[j]) + 1L; - if (len > (long)max_kbyte * 1024L) - continue; - } - - switch (y_regs[i].y_type) { - case MLINE: - type = (char_u *)"LINE"; - break; - case MCHAR: - type = (char_u *)"CHAR"; - break; - case MBLOCK: - type = (char_u *)"BLOCK"; - break; - default: - sprintf((char *)IObuff, _("E574: Unknown register type %d"), - y_regs[i].y_type); - emsg(IObuff); - type = (char_u *)"LINE"; - break; - } - if (y_previous == &y_regs[i]) - fprintf(fp, "\""); - c = get_register_name(i); - fprintf(fp, "\"%c", c); - if (c == execreg_lastc) - fprintf(fp, "@"); - fprintf(fp, "\t%s\t%d\n", type, - (int)y_regs[i].y_width - ); - - /* If max_num_lines < 0, then we save ALL the lines in the register */ - if (max_num_lines > 0 && num_lines > max_num_lines) - num_lines = max_num_lines; - for (j = 0; j < num_lines; j++) { - putc('\t', fp); - viminfo_writestring(fp, y_regs[i].y_array[j]); - } - } -} - -/* - * Restore global vars that start with a capital from the viminfo file - */ -int read_viminfo_varlist(vir_T *virp, int writing) -{ - char_u *tab; - int type = VAR_NUMBER; - typval_T tv; - - if (!writing && (find_viminfo_parameter('!') != NULL)) { - tab = vim_strchr(virp->vir_line + 1, '\t'); - if (tab != NULL) { - *tab++ = NUL; /* isolate the variable name */ - switch (*tab) { - case 'S': type = VAR_STRING; break; - case 'F': type = VAR_FLOAT; break; - case 'D': type = VAR_DICT; break; - case 'L': type = VAR_LIST; break; - } - - tab = vim_strchr(tab, '\t'); - if (tab != NULL) { - tv.v_type = type; - if (type == VAR_STRING || type == VAR_DICT || type == VAR_LIST) - tv.vval.v_string = viminfo_readstring(virp, - (int)(tab - virp->vir_line + 1), TRUE); - else if (type == VAR_FLOAT) - (void)string2float(tab + 1, &tv.vval.v_float); - else - tv.vval.v_number = atol((char *)tab + 1); - if (type == VAR_DICT || type == VAR_LIST) { - typval_T *etv = eval_expr(tv.vval.v_string, NULL); - - if (etv == NULL) - /* Failed to parse back the dict or list, use it as a - * string. */ - tv.v_type = VAR_STRING; - else { - free(tv.vval.v_string); - tv = *etv; - free(etv); - } - } - - set_var(virp->vir_line + 1, &tv, FALSE); - - if (tv.v_type == VAR_STRING) - free(tv.vval.v_string); - else if (tv.v_type == VAR_DICT || tv.v_type == VAR_LIST) - clear_tv(&tv); - } - } - } - - return viminfo_readline(virp); -} - -/* - * Write global vars that start with a capital to the viminfo file - */ -void write_viminfo_varlist(FILE *fp) -{ - hashitem_T *hi; - dictitem_T *this_var; - int todo; - char *s; - char_u *p; - char_u *tofree; - char_u numbuf[NUMBUFLEN]; - - if (find_viminfo_parameter('!') == NULL) - return; - - fputs(_("\n# global variables:\n"), fp); - - todo = (int)globvarht.ht_used; - for (hi = globvarht.ht_array; todo > 0; ++hi) { - if (!HASHITEM_EMPTY(hi)) { - --todo; - this_var = HI2DI(hi); - if (var_flavour(this_var->di_key) == VAR_FLAVOUR_VIMINFO) { - switch (this_var->di_tv.v_type) { - case VAR_STRING: s = "STR"; break; - case VAR_NUMBER: s = "NUM"; break; - case VAR_FLOAT: s = "FLO"; break; - case VAR_DICT: s = "DIC"; break; - case VAR_LIST: s = "LIS"; break; - default: continue; - } - fprintf(fp, "!%s\t%s\t", this_var->di_key, s); - p = echo_string(&this_var->di_tv, &tofree, numbuf, 0); - if (p != NULL) - viminfo_writestring(fp, p); - free(tofree); - } - } - } -} - -int read_viminfo_bufferlist(vir_T *virp, int writing) -{ - char_u *tab; - linenr_T lnum; - colnr_T col; - buf_T *buf; - char_u *sfname; - char_u *xline; - - /* Handle long line and escaped characters. */ - xline = viminfo_readstring(virp, 1, FALSE); - - /* don't read in if there are files on the command-line or if writing: */ - if (xline != NULL && !writing && ARGCOUNT == 0 - && find_viminfo_parameter('%') != NULL) { - /* Format is: Tab Tab . - * Watch out for a Tab in the file name, work from the end. */ - lnum = 0; - col = 0; - tab = vim_strrchr(xline, '\t'); - if (tab != NULL) { - *tab++ = '\0'; - col = (colnr_T)atoi((char *)tab); - tab = vim_strrchr(xline, '\t'); - if (tab != NULL) { - *tab++ = '\0'; - lnum = atol((char *)tab); - } - } - - /* Expand "~/" in the file name at "line + 1" to a full path. - * Then try shortening it by comparing with the current directory */ - expand_env(xline, NameBuff, MAXPATHL); - sfname = path_shorten_fname_if_possible(NameBuff); - - buf = buflist_new(NameBuff, sfname, (linenr_T)0, BLN_LISTED); - if (buf != NULL) { /* just in case... */ - buf->b_last_cursor.lnum = lnum; - buf->b_last_cursor.col = col; - buflist_setfpos(buf, curwin, lnum, col, FALSE); - } - } - xfree(xline); - - return viminfo_readline(virp); -} - -void write_viminfo_bufferlist(FILE *fp) -{ - char_u *line; - int max_buffers; - - if (find_viminfo_parameter('%') == NULL) - return; - - /* Without a number -1 is returned: do all buffers. */ - max_buffers = get_viminfo_parameter('%'); - - /* Allocate room for the file name, lnum and col. */ -#define LINE_BUF_LEN (MAXPATHL + 40) - line = xmalloc(LINE_BUF_LEN); - - FOR_ALL_TAB_WINDOWS(tp, win) { - set_last_cursor(win); - } - - fputs(_("\n# Buffer list:\n"), fp); - FOR_ALL_BUFFERS(buf) { - if (buf->b_fname == NULL - || !buf->b_p_bl - || bt_quickfix(buf) - || removable(buf->b_ffname)) - continue; - - if (max_buffers-- == 0) - break; - putc('%', fp); - home_replace(NULL, buf->b_ffname, line, MAXPATHL, TRUE); - vim_snprintf_add((char *)line, LINE_BUF_LEN, "\t%" PRId64 "\t%d", - (int64_t)buf->b_last_cursor.lnum, - buf->b_last_cursor.col); - viminfo_writestring(fp, line); - } - xfree(line); -} - -int read_viminfo_sub_string(vir_T *virp, int force) -{ - if (force) - xfree(old_sub); - if (force || old_sub == NULL) - old_sub = viminfo_readstring(virp, 1, TRUE); - return viminfo_readline(virp); -} - -void write_viminfo_sub_string(FILE *fp) -{ - if (get_viminfo_parameter('/') != 0 && old_sub != NULL) { - fputs(_("\n# Last Substitute String:\n$"), fp); - viminfo_writestring(fp, old_sub); - } -} - -/* - * Structure used for reading from the viminfo file. - */ -typedef struct { - char_u *vir_line; /* text of the current line */ - FILE *vir_fd; /* file descriptor */ - vimconv_T vir_conv; /* encoding conversion */ -} vir_T; -#endif -- cgit From ebf3c86a552696df136b94f63c06b6db00155f40 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 25 Jul 2015 12:43:03 +0300 Subject: shada: Handle all possible msgpack_unpacker_next returns --- src/nvim/shada.c | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index a6054db9d0..274296eee4 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -3106,15 +3106,38 @@ shada_read_next_item_start: msgpack_unpacked unpacked; msgpack_unpacked_init(&unpacked); + bool did_try_to_free = false; +shada_read_next_item_read_next: {} const msgpack_unpack_return result = msgpack_unpacker_next(unpacker, &unpacked); - if (result != MSGPACK_UNPACK_SUCCESS) { - if (result == MSGPACK_UNPACK_NOMEM_ERROR) { + switch (result) { + case MSGPACK_UNPACK_SUCCESS: { + break; + } + case MSGPACK_UNPACK_PARSE_ERROR: { + emsgu("Failed to parse ShaDa file due to an error at position %" PRIu64, + (uint64_t) initial_fpos); + goto shada_read_next_item_error; + } + case MSGPACK_UNPACK_NOMEM_ERROR: { + if (!did_try_to_free) { + did_try_to_free = true; + try_to_free_memory(); + goto shada_read_next_item_read_next; + } EMSG(e_outofmem); goto shada_read_next_item_error; } - if (result == MSGPACK_UNPACK_PARSE_ERROR) { - EMSG("Failed to parse ShaDa file"); + case MSGPACK_UNPACK_CONTINUE: { + emsgu("Failed to parse ShaDa file: incomplete msgpack string " + "at position %" PRIu64, + (uint64_t) initial_fpos); + goto shada_read_next_item_error; + } + case MSGPACK_UNPACK_EXTRA_BYTES: { + emsgu("Failed to parse ShaDa file: extra bytes in msgpack string " + "at position %" PRIu64, + (uint64_t) initial_fpos); goto shada_read_next_item_error; } } -- cgit From 40bbaa757e5a0c9c3989871be4ad81fd2a71b39b Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 25 Jul 2015 12:56:47 +0300 Subject: shada: Put pointer to the close function into reader/writer structure --- src/nvim/shada.c | 69 ++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 47 insertions(+), 22 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 274296eee4..da107ef38b 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -328,6 +328,10 @@ typedef struct { 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, @@ -336,18 +340,23 @@ typedef ptrdiff_t (*ShaDaFileReader)(struct sd_read_def *const sd_reader, /// Structure containing necessary pointers for reading ShaDa files typedef struct sd_read_def { - ShaDaFileReader read; ///< Reader function. - void *cookie; ///< Reader function last argument. - 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. + ShaDaFileReader read; ///< Reader function. + ShaDaReadCloser close; ///< Close function. + void *cookie; ///< Reader function last argument. + 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, @@ -356,11 +365,12 @@ typedef ptrdiff_t (*ShaDaFileWriter)(struct sd_write_def *const sd_writer, /// Structure containing necessary pointers for writing ShaDa files typedef struct sd_write_def { - ShaDaFileWriter write; ///< Writer function. - void *cookie; ///< Writer function last argument. - char *error; ///< Error message in case of error. - vimconv_T sd_conv; ///< Structure used for converting encodings of some - ///< items. + ShaDaFileWriter write; ///< Writer function. + ShaDaWriteCloser close; ///< Close function. + void *cookie; ///< Writer function last argument. + 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 @@ -506,9 +516,9 @@ static ptrdiff_t read_file(ShaDaReadDef *const sd_reader, void *const dest, { 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((int)(intptr_t) sd_reader->cookie, - ((char *) dest) + read_bytes, + 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; @@ -559,9 +569,9 @@ static ptrdiff_t write_file(ShaDaWriteDef *const sd_writer, 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((int)(intptr_t) sd_writer->cookie, - (char *) dest + written_bytes, + 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; @@ -584,6 +594,20 @@ static ptrdiff_t write_file(ShaDaWriteDef *const sd_writer, 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 +{ + close_file((int)(intptr_t) sd_writer->cookie); +} + /// Wrapper for opening file descriptors /// /// All arguments are passed to os_open(). @@ -636,6 +660,7 @@ static int open_shada_file_for_reading(const char *const fname, *sd_reader = (ShaDaReadDef) { .read = &read_file, + .close = &close_sd_reader, .error = NULL, .eof = false, .fpos = 0, @@ -752,8 +777,8 @@ int shada_read_file(const char *const file, const int flags) } shada_read(&sd_reader, flags); + sd_reader.close(&sd_reader); - close_file((int)(intptr_t) sd_reader.cookie); return OK; } @@ -2566,6 +2591,7 @@ int shada_write_file(const char *const file, bool nomerge) char *tempname = NULL; ShaDaWriteDef sd_writer = (ShaDaWriteDef) { .write = &write_file, + .close = &close_sd_writer, .error = NULL, }; ShaDaReadDef sd_reader; @@ -2591,7 +2617,7 @@ int shada_write_file(const char *const file, bool nomerge) ? (old_info.stat.st_mode & 0020) : (old_info.stat.st_mode & 0002)))) { EMSG2(_("E137: ShaDa file is not writable: %s"), fname); - close_file((int)(intptr_t) sd_reader.cookie); + sd_reader.close(&sd_reader); xfree(fname); return FAIL; } @@ -2676,11 +2702,10 @@ shada_write_file_nomerge: {} convert_setup(&sd_writer.sd_conv, p_enc, "utf-8"); shada_write(&sd_writer, (nomerge ? NULL : &sd_reader)); - - close_file((int)(intptr_t) sd_writer.cookie); + sd_writer.close(&sd_writer); if (!nomerge) { - close_file((int)(intptr_t) sd_reader.cookie); + sd_reader.close(&sd_reader); if (vim_rename(tempname, fname) == -1) { EMSG3(_("E886: Can't rename ShaDa file from %s to %s!"), tempname, fname); -- cgit From fa8e3f3f20f2f2dc5d160fb70b747568a7b4f1cf Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 25 Jul 2015 13:34:57 +0300 Subject: shada: Only check errno if read/write returned -1 According to the manual (POSIX) this is the only case when errno is set by these functions. This is needed because some functions (e.g. buflist_new) leave errno set to non-zero value under some conditions (e.g. when opening non-existing files). --- src/nvim/shada.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index da107ef38b..d7b3e27550 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -525,7 +525,7 @@ static ptrdiff_t read_file(ShaDaReadDef *const sd_reader, void *const dest, sd_reader->fpos += (uintmax_t) cur_read_bytes; assert(read_bytes <= size); } - if (errno) { + if (cur_read_bytes < 0) { if (errno == EINTR || errno == EAGAIN) { errno = 0; continue; @@ -576,7 +576,7 @@ static ptrdiff_t write_file(ShaDaWriteDef *const sd_writer, if (cur_written_bytes > 0) { written_bytes += (size_t) cur_written_bytes; } - if (errno) { + if (cur_written_bytes < 0) { if (errno == EINTR || errno == EAGAIN) { errno = 0; continue; -- cgit From 8dafa533dbd505a6a2cf2e009bf440e2bde342f4 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 25 Jul 2015 19:42:04 +0300 Subject: shada: Translate errors and add error codes Notes: - E136 code greatly changed its meaning: now it is write error and not read error. - E195 was removed because shada_read_everything will already do all the necessary error reporting. - E886 can be reported by both :rshada and :wshada, but :rshada comes first and AFAIR it is the only error which is not E575 and can be reported by :rshada. --- runtime/doc/starting.txt | 7 +- src/nvim/ex_docmd.c | 3 +- src/nvim/main.c | 2 +- src/nvim/shada.c | 340 ++++++++++++++++++++++++++--------------------- src/nvim/shada.h | 10 +- 5 files changed, 200 insertions(+), 162 deletions(-) diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index 1679ff170d..68f4ff2e64 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -896,8 +896,7 @@ To automatically save and restore views for *.c files: > au BufWinEnter *.c silent loadview ============================================================================== -8. The ShaDa file *shada* *shada-file* *E136* - *E575* *E576* *E577* +8. The ShaDa file *shada* *shada-file* *E575* If you exit Vim and later start it again, you would normally lose a lot of information. The ShaDa file can be used to remember that information, which enables you to continue where you left off. @@ -1081,7 +1080,7 @@ accidentally did that!). If you want to overwrite a ShaDa file with an error in it, you will either have to fix the error, or delete the file (while NeoVim is running, so most of the information will be restored). - *:rsh* *:rshada* *E195* + *:rsh* *:rshada* *E886* :rsh[ada][!] [file] Read from ShaDa file [file] (default: see above). If [!] is given, then any information that is already set (registers, marks, |v:oldfiles|, etc.) @@ -1090,7 +1089,7 @@ is running, so most of the information will be restored). *:rv* *:rviminfo* :rv[iminfo][!] [file] Deprecated alias to |:rshada| command. - *:wsh* *:wshada* *E137* *E138* *E574* *E886* + *:wsh* *:wshada* *E136* *E137* *E138* :wsh[ada][!] [file] Write to ShaDa file [file] (default: see above). The information in the file is first read in to make a merge between old and new info. When [!] is used, diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 666431438e..a9262ca6ea 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -9150,8 +9150,7 @@ static void ex_shada(exarg_T *eap) if (*p_shada == NUL) p_shada = (char_u *)"'100"; if (eap->cmdidx == CMD_rviminfo || eap->cmdidx == CMD_rshada) { - if (shada_read_everything((char *) eap->arg, eap->forceit) == FAIL) - EMSG(_("E195: Cannot open ShaDa file for reading")); + (void) shada_read_everything((char *) eap->arg, eap->forceit, false); } else { shada_write_file((char *) eap->arg, eap->forceit); } diff --git a/src/nvim/main.c b/src/nvim/main.c index e8af356654..c4387c0b7f 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -383,7 +383,7 @@ int main(int argc, char **argv) * This is where v:oldfiles gets filled. */ if (*p_shada != NUL) { - shada_read_everything(NULL, false); + shada_read_everything(NULL, false, true); TIME_MSG("reading ShaDa"); } /* It's better to make v:oldfiles an empty list than NULL. */ diff --git a/src/nvim/shada.c b/src/nvim/shada.c index d7b3e27550..d64b8c489d 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -124,6 +124,33 @@ KHASH_SET_INIT_STR(strset) // Define nothing #endif +// 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! +// E136: Can't rename ShaDa file from %s to %s! +// 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 all “system” errors +#define SERR "E886: " + /// Possible ShaDa entry types /// /// @warning Enum values are part of the API and must not be altered. @@ -634,7 +661,7 @@ open_file_start: goto open_file_start; } if (-fd != EEXIST) { - emsg3("System error while opening ShaDa file %s: %s", + emsg3(_(SERR "System error while opening ShaDa file %s: %s"), fname, os_strerror(fd)); } return fd; @@ -647,7 +674,7 @@ open_file_start: /// @param[in] fname File name to open. /// @param[out] sd_reader Location where reader structure will be saved. /// -/// @return OK in case of success, FAIL otherwise. +/// @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 @@ -655,7 +682,7 @@ static int open_shada_file_for_reading(const char *const fname, const intptr_t fd = (intptr_t) open_file(fname, O_RDONLY, 0); if (fd < 0) { - return FAIL; + return (int) fd; } *sd_reader = (ShaDaReadDef) { @@ -669,7 +696,7 @@ static int open_shada_file_for_reading(const char *const fname, convert_setup(&sd_reader->sd_conv, "utf-8", p_enc); - return OK; + return 0; } /// Wrapper for closing file descriptors @@ -681,7 +708,7 @@ close_file_start: errno = 0; goto close_file_start; } else { - emsg2("System error while closing ShaDa file: %s", + emsg2(_(SERR "System error while closing ShaDa file: %s"), strerror(errno)); errno = 0; } @@ -727,7 +754,8 @@ 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("System error while writing ShaDa file: %s", sd_writer->error); + emsg2(_(SERR "System error while writing ShaDa file: %s"), + sd_writer->error); return -1; } return 0; @@ -767,14 +795,19 @@ int shada_read_file(const char *const file, const int flags) (flags & kShaDaWantInfo) ? _(" info") : "", (flags & kShaDaWantMarks) ? _(" marks") : "", (flags & kShaDaGetOldfiles) ? _(" oldfiles") : "", - of_ret != OK ? _(" FAILED") : ""); + of_ret != 0 ? _(" FAILED") : ""); verbose_leave(); } - xfree(fname); - if (of_ret != OK) { - return of_ret; + 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); @@ -1151,8 +1184,8 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) Error err; if (!object_to_vim(cur_entry.data.global_var.value, &vartv, &err)) { if (err.set) { - emsg3("Error while reading ShaDa file: " - "failed to read value for variable %s: %s", + emsg3(_(RERR "Error while reading ShaDa file: " + "failed to read value for variable %s: %s"), cur_entry.data.global_var.name, err.msg); } break; @@ -2600,7 +2633,7 @@ int shada_write_file(const char *const file, bool nomerge) if (!nomerge) { // TODO(ZyX-I): Fail on read error. - if (open_shada_file_for_reading(fname, &sd_reader) != OK) { + if (open_shada_file_for_reading(fname, &sd_reader) != 0) { nomerge = true; goto shada_write_file_nomerge; } @@ -2672,7 +2705,8 @@ shada_write_file_nomerge: {} int ret; char *failed_dir; if ((ret = os_mkdir_recurse(fname, 0700, &failed_dir)) != 0) { - EMSG3("Failed to create directory %s for writing ShaDa file: %s", + EMSG3(_(SERR "Failed to create directory %s " + "for writing ShaDa file: %s"), failed_dir, os_strerror(ret)); xfree(fname); xfree(failed_dir); @@ -2707,7 +2741,7 @@ shada_write_file_nomerge: {} if (!nomerge) { sd_reader.close(&sd_reader); if (vim_rename(tempname, fname) == -1) { - EMSG3(_("E886: Can't rename ShaDa file from %s to %s!"), + EMSG3(_("E136: Can't rename ShaDa file from %s to %s!"), tempname, fname); } else { os_remove(tempname); @@ -2730,13 +2764,18 @@ int shada_read_marks(void) /// 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) +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)); + |(forceit?kShaDaForceit:0) + |(missing_ok?0:kShaDaMissingError)); } static void shada_free_shada_entry(ShadaEntry *const entry) @@ -2872,12 +2911,13 @@ static int fread_len(ShaDaReadDef *const sd_reader, char *const buffer, } if (sd_reader->error != NULL) { - emsg2("System error while reading ShaDa file: %s", sd_reader->error); + emsg2(_(SERR "System error while reading ShaDa file: %s"), + sd_reader->error); return FAIL; } else if (sd_reader->eof) { - emsgu("Error while reading ShaDa file: " - "last entry specified that it occupies %" PRIu64 " bytes, " - "but file ended earlier", + emsgu(_(RERR "Error while reading ShaDa file: " + "last entry specified that it occupies %" PRIu64 " bytes, " + "but file ended earlier"), (uint64_t) length); return FAIL; } @@ -2908,12 +2948,12 @@ static int msgpack_read_uint64(ShaDaReadDef *const sd_reader, if (first_char == EOF) { if (sd_reader->error) { - emsg2("System error while reading integer from ShaDa file: %s", + emsg2(_(SERR "System error while reading integer from ShaDa file: %s"), sd_reader->error); } else if (sd_reader->eof) { - emsgu("Error while reading ShaDa file: " - "expected positive integer at position %" PRIu64 - ", but got nothing", + emsgu(_(RERR "Error while reading ShaDa file: " + "expected positive integer at position %" PRIu64 + ", but got nothing"), (uint64_t) fpos); } return FAIL; @@ -2942,8 +2982,8 @@ static int msgpack_read_uint64(ShaDaReadDef *const sd_reader, break; } default: { - emsgu("Error while reading ShaDa file: " - "expected positive integer at position %" PRIu64, + emsgu(_(RERR "Error while reading ShaDa file: " + "expected positive integer at position %" PRIu64), (uint64_t) fpos); return FAIL; } @@ -3118,7 +3158,7 @@ shada_read_next_item_start: msgpack_unpacker *const unpacker = msgpack_unpacker_new(length); if (unpacker == NULL || !msgpack_unpacker_reserve_buffer(unpacker, length)) { - EMSG(e_outofmem); + EMSG(_(e_outofmem)); goto shada_read_next_item_error; } @@ -3140,7 +3180,8 @@ shada_read_next_item_read_next: {} break; } case MSGPACK_UNPACK_PARSE_ERROR: { - emsgu("Failed to parse ShaDa file due to an error at position %" PRIu64, + emsgu(_(RERR "Failed to parse ShaDa file due to a msgpack parser error " + "at position %" PRIu64), (uint64_t) initial_fpos); goto shada_read_next_item_error; } @@ -3150,18 +3191,18 @@ shada_read_next_item_read_next: {} try_to_free_memory(); goto shada_read_next_item_read_next; } - EMSG(e_outofmem); + EMSG(_(e_outofmem)); goto shada_read_next_item_error; } case MSGPACK_UNPACK_CONTINUE: { - emsgu("Failed to parse ShaDa file: incomplete msgpack string " - "at position %" PRIu64, + emsgu(_(RERR "Failed to parse ShaDa file: incomplete msgpack string " + "at position %" PRIu64), (uint64_t) initial_fpos); goto shada_read_next_item_error; } case MSGPACK_UNPACK_EXTRA_BYTES: { - emsgu("Failed to parse ShaDa file: extra bytes in msgpack string " - "at position %" PRIu64, + emsgu(_(RERR "Failed to parse ShaDa file: extra bytes in msgpack string " + "at position %" PRIu64), (uint64_t) initial_fpos); goto shada_read_next_item_error; } @@ -3180,9 +3221,9 @@ shada_read_next_item_read_next: {} proc) \ do { \ if (!(condition)) { \ - emsgu("Error while reading ShaDa file: " \ - entry_name " entry at position %" PRIu64 " " \ - error_desc, \ + emsgu(_(RERR "Error while reading ShaDa file: " \ + entry_name " entry at position %" PRIu64 " " \ + error_desc), \ (uint64_t) initial_fpos); \ ga_clear(&ad_ga); \ goto shada_read_next_item_error; \ @@ -3192,12 +3233,10 @@ shada_read_next_item_read_next: {} #define CHECK_KEY_IS_STR(entry_name) \ do { \ if (unpacked.data.via.map.ptr[i].key.type != MSGPACK_OBJECT_STR) { \ - emsgu("Error while reading ShaDa file: " \ - entry_name " entry at position %" PRIu64 " " \ - "has key which is not a string", \ + emsgu(_(RERR "Error while reading ShaDa file: " \ + entry_name " entry at position %" PRIu64 " " \ + "has key which is not a string"), \ (uint64_t) initial_fpos); \ - emsgu("It is %" PRIu64 " instead", \ - (uint64_t) unpacked.data.via.map.ptr[i].key.type); \ ga_clear(&ad_ga); \ goto shada_read_next_item_error; \ } \ @@ -3249,8 +3288,8 @@ shada_read_next_item_read_next: {} switch ((ShadaEntryType) type_u64) { case kSDItemHeader: { if (!msgpack_rpc_to_dictionary(&(unpacked.data), &(entry->data.header))) { - emsgu("Error while reading ShaDa file: " - "header entry at position %" PRIu64 " is not a dictionary", + emsgu(_(RERR "Error while reading ShaDa file: " + "header entry at position %" PRIu64 " is not a dictionary"), (uint64_t) initial_fpos); goto shada_read_next_item_error; } @@ -3258,9 +3297,9 @@ shada_read_next_item_read_next: {} } case kSDItemSearchPattern: { if (unpacked.data.type != MSGPACK_OBJECT_MAP) { - emsgu("Error while reading ShaDa file: " - "search pattern entry at position %" PRIu64 " " - "is not a dictionary", + emsgu(_(RERR "Error while reading ShaDa file: " + "search pattern entry at position %" PRIu64 " " + "is not a dictionary"), (uint64_t) initial_fpos); goto shada_read_next_item_error; } @@ -3300,9 +3339,9 @@ shada_read_next_item_read_next: {} else ADDITIONAL_KEY } if (entry->data.search_pattern.pat == NULL) { - emsgu("Error while reading ShaDa file: " - "search pattern entry at position %" PRIu64 " " - "has no pattern", + emsgu(_(RERR "Error while reading ShaDa file: " + "search pattern entry at position %" PRIu64 " " + "has no pattern"), (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; @@ -3321,9 +3360,9 @@ shada_read_next_item_read_next: {} xmalloc(sizeof(Dictionary)); if (!msgpack_rpc_to_dictionary( &obj, entry->data.search_pattern.additional_data)) { - emsgu("Error while reading ShaDa file: " - "search pattern entry at position %" PRIu64 " " - "cannot be converted to a Dictionary", + emsgu(_(RERR "Error while reading ShaDa file: " + "search pattern entry at position %" PRIu64 " " + "cannot be converted to a Dictionary"), (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; @@ -3337,9 +3376,9 @@ shada_read_next_item_read_next: {} case kSDItemGlobalMark: case kSDItemLocalMark: { if (unpacked.data.type != MSGPACK_OBJECT_MAP) { - emsgu("Error while reading ShaDa file: " - "mark entry at position %" PRIu64 " " - "is not a dictionary", + emsgu(_(RERR "Error while reading ShaDa file: " + "mark entry at position %" PRIu64 " " + "is not a dictionary"), (uint64_t) initial_fpos); goto shada_read_next_item_error; } @@ -3367,9 +3406,9 @@ shada_read_next_item_read_next: {} else ADDITIONAL_KEY } if (entry->data.filemark.mark.lnum == 0) { - emsgu("Error while reading ShaDa file: " - "mark entry at position %" PRIu64 " " - "is missing line number", + emsgu(_(RERR "Error while reading ShaDa file: " + "mark entry at position %" PRIu64 " " + "is missing line number"), (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; @@ -3387,9 +3426,9 @@ shada_read_next_item_read_next: {} entry->data.filemark.additional_data = xmalloc(sizeof(Dictionary)); if (!msgpack_rpc_to_dictionary( &obj, entry->data.filemark.additional_data)) { - emsgu("Error while reading ShaDa file: " - "mark entry at position %" PRIu64 " " - "cannot be converted to a Dictionary", + emsgu(_(RERR "Error while reading ShaDa file: " + "mark entry at position %" PRIu64 " " + "cannot be converted to a Dictionary"), (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; @@ -3400,9 +3439,9 @@ shada_read_next_item_read_next: {} } case kSDItemRegister: { if (unpacked.data.type != MSGPACK_OBJECT_MAP) { - emsgu("Error while reading ShaDa file: " - "register entry at position %" PRIu64 " " - "is not a dictionary", + emsgu(_(RERR "Error while reading ShaDa file: " + "register entry at position %" PRIu64 " " + "is not a dictionary"), (uint64_t) initial_fpos); goto shada_read_next_item_error; } @@ -3426,17 +3465,17 @@ shada_read_next_item_read_next: {} entry->data.reg.width, POSITIVE_INTEGER, u64, TOSIZE) else if (CHECK_KEY(unpacked.data.via.map.ptr[i].key, "contents")) { if (unpacked.data.via.map.ptr[i].val.type != MSGPACK_OBJECT_ARRAY) { - emsgu("Error while reading ShaDa file: " - "register entry at position %" PRIu64 " " - "has contents key with non-array value", + emsgu(_(RERR "Error while reading ShaDa file: " + "register entry at position %" PRIu64 " " + "has contents key with non-array value"), (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; } if (unpacked.data.via.map.ptr[i].val.via.array.size == 0) { - emsgu("Error while reading ShaDa file: " - "register entry at position %" PRIu64 " " - "has contents key with empty array", + emsgu(_(RERR "Error while reading ShaDa file: " + "register entry at position %" PRIu64 " " + "has contents key with empty array"), (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; @@ -3445,9 +3484,9 @@ shada_read_next_item_read_next: {} 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("Error while reading ShaDa file: " - "register entry at position %" PRIu64 " " - "has contents array with non-string value", + emsgu(_(RERR "Error while reading ShaDa file: " + "register entry at position %" PRIu64 " " + "has contents array with non-string value"), (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; @@ -3461,9 +3500,9 @@ shada_read_next_item_read_next: {} } else ADDITIONAL_KEY } if (entry->data.reg.contents == NULL) { - emsgu("Error while reading ShaDa file: " - "register entry at position %" PRIu64 " " - "has missing contents array", + emsgu(_(RERR "Error while reading ShaDa file: " + "register entry at position %" PRIu64 " " + "has missing contents array"), (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; @@ -3481,9 +3520,9 @@ shada_read_next_item_read_next: {} entry->data.reg.additional_data = xmalloc(sizeof(Dictionary)); if (!msgpack_rpc_to_dictionary( &obj, entry->data.reg.additional_data)) { - emsgu("Error while reading ShaDa file: " - "register entry at position %" PRIu64 " " - "cannot be converted to a Dictionary", + emsgu(_(RERR "Error while reading ShaDa file: " + "register entry at position %" PRIu64 " " + "cannot be converted to a Dictionary"), (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; @@ -3494,9 +3533,9 @@ shada_read_next_item_read_next: {} } case kSDItemHistoryEntry: { if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) { - emsgu("Error while reading ShaDa file: " - "history entry at position %" PRIu64 " " - "is not an array", + emsgu(_(RERR "Error while reading ShaDa file: " + "history entry at position %" PRIu64 " " + "is not an array"), (uint64_t) initial_fpos); goto shada_read_next_item_error; } @@ -3507,33 +3546,33 @@ shada_read_next_item_read_next: {} .additional_elements = NULL, }; if (unpacked.data.via.array.size < 2) { - emsgu("Error while reading ShaDa file: " - "history entry at position %" PRIu64 " " - "does not have enough elements", + emsgu(_(RERR "Error while reading ShaDa file: " + "history entry at position %" PRIu64 " " + "does not have enough elements"), (uint64_t) initial_fpos); goto shada_read_next_item_error; } if (unpacked.data.via.array.ptr[0].type != MSGPACK_OBJECT_POSITIVE_INTEGER) { - emsgu("Error while reading ShaDa file: " - "history entry at position %" PRIu64 " " - "has wrong history type type", + emsgu(_(RERR "Error while reading ShaDa file: " + "history entry at position %" PRIu64 " " + "has wrong history type type"), (uint64_t) initial_fpos); goto shada_read_next_item_error; } if (unpacked.data.via.array.ptr[1].type != MSGPACK_OBJECT_BIN) { - emsgu("Error while reading ShaDa file: " - "history entry at position %" PRIu64 " " - "has wrong history string type", + emsgu(_(RERR "Error while reading ShaDa file: " + "history entry at position %" PRIu64 " " + "has wrong history string type"), (uint64_t) 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("Error while reading ShaDa file: " - "history entry at position %" PRIu64 " " - "contains string with zero byte inside", + emsgu(_(RERR "Error while reading ShaDa file: " + "history entry at position %" PRIu64 " " + "contains string with zero byte inside"), (uint64_t) initial_fpos); goto shada_read_next_item_error; } @@ -3543,17 +3582,17 @@ shada_read_next_item_read_next: {} entry->data.history_item.histtype == HIST_SEARCH; if (is_hist_search) { if (unpacked.data.via.array.size < 3) { - emsgu("Error while reading ShaDa file: " - "search history entry at position %" PRIu64 " " - "does not have separator character", + emsgu(_(RERR "Error while reading ShaDa file: " + "search history entry at position %" PRIu64 " " + "does not have separator character"), (uint64_t) initial_fpos); goto shada_read_next_item_error; } if (unpacked.data.via.array.ptr[2].type != MSGPACK_OBJECT_POSITIVE_INTEGER) { - emsgu("Error while reading ShaDa file: " - "search history entry at position %" PRIu64 " " - "has wrong history separator type", + emsgu(_(RERR "Error while reading ShaDa file: " + "search history entry at position %" PRIu64 " " + "has wrong history separator type"), (uint64_t) initial_fpos); goto shada_read_next_item_error; } @@ -3603,9 +3642,9 @@ shada_read_next_item_hist_no_conv: entry->data.history_item.additional_elements = xmalloc(sizeof(Array)); if (!msgpack_rpc_to_array( &obj, entry->data.history_item.additional_elements)) { - emsgu("Error while reading ShaDa file: " - "history entry at position %" PRIu64 " " - "cannot be converted to an Array", + emsgu(_(RERR "Error while reading ShaDa file: " + "history entry at position %" PRIu64 " " + "cannot be converted to an Array"), (uint64_t) initial_fpos); goto shada_read_next_item_error; } @@ -3614,9 +3653,9 @@ shada_read_next_item_hist_no_conv: } case kSDItemVariable: { if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) { - emsgu("Error while reading ShaDa file: " - "variable entry at position %" PRIu64 " " - "is not an array", + emsgu(_(RERR "Error while reading ShaDa file: " + "variable entry at position %" PRIu64 " " + "is not an array"), (uint64_t) initial_fpos); goto shada_read_next_item_error; } @@ -3628,24 +3667,24 @@ shada_read_next_item_hist_no_conv: .additional_elements = NULL }; if (unpacked.data.via.array.size < 2) { - emsgu("Error while reading ShaDa file: " - "variable entry at position %" PRIu64 " " - "does not have enough elements", + emsgu(_(RERR "Error while reading ShaDa file: " + "variable entry at position %" PRIu64 " " + "does not have enough elements"), (uint64_t) initial_fpos); goto shada_read_next_item_error; } if (unpacked.data.via.array.ptr[0].type != MSGPACK_OBJECT_BIN) { - emsgu("Error while reading ShaDa file: " - "variable entry at position %" PRIu64 " " - "has wrong variable name type", + emsgu(_(RERR "Error while reading ShaDa file: " + "variable entry at position %" PRIu64 " " + "has wrong variable name type"), (uint64_t) 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("Error while reading ShaDa file: " - "variable entry at position %" PRIu64 " " - "has wrong variable value type", + emsgu(_(RERR "Error while reading ShaDa file: " + "variable entry at position %" PRIu64 " " + "has wrong variable value type"), (uint64_t) initial_fpos); goto shada_read_next_item_error; } @@ -3654,9 +3693,9 @@ shada_read_next_item_hist_no_conv: unpacked.data.via.array.ptr[0].via.bin.size); if (!msgpack_rpc_to_object(&(unpacked.data.via.array.ptr[1]), &(entry->data.global_var.value))) { - emsgu("Error while reading ShaDa file: " - "variable entry at position %" PRIu64 " " - "has value that cannot be converted to the object", + emsgu(_(RERR "Error while reading ShaDa file: " + "variable entry at position %" PRIu64 " " + "has value that cannot be converted to the object"), (uint64_t) initial_fpos); goto shada_read_next_item_error; } @@ -3676,9 +3715,9 @@ shada_read_next_item_hist_no_conv: entry->data.global_var.additional_elements = xmalloc(sizeof(Array)); if (!msgpack_rpc_to_array( &obj, entry->data.global_var.additional_elements)) { - emsgu("Error while reading ShaDa file: " - "variable entry at position %" PRIu64 " " - "cannot be converted to an Array", + emsgu(_(RERR "Error while reading ShaDa file: " + "variable entry at position %" PRIu64 " " + "cannot be converted to an Array"), (uint64_t) initial_fpos); goto shada_read_next_item_error; } @@ -3687,9 +3726,9 @@ shada_read_next_item_hist_no_conv: } case kSDItemSubString: { if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) { - emsgu("Error while reading ShaDa file: " - "sub string entry at position %" PRIu64 " " - "is not an array", + emsgu(_(RERR "Error while reading ShaDa file: " + "sub string entry at position %" PRIu64 " " + "is not an array"), (uint64_t) initial_fpos); goto shada_read_next_item_error; } @@ -3698,16 +3737,16 @@ shada_read_next_item_hist_no_conv: .additional_elements = NULL }; if (unpacked.data.via.array.size < 1) { - emsgu("Error while reading ShaDa file: " - "sub string entry at position %" PRIu64 " " - "does not have enough elements", + emsgu(_(RERR "Error while reading ShaDa file: " + "sub string entry at position %" PRIu64 " " + "does not have enough elements"), (uint64_t) initial_fpos); goto shada_read_next_item_error; } if (unpacked.data.via.array.ptr[0].type != MSGPACK_OBJECT_BIN) { - emsgu("Error while reading ShaDa file: " - "sub string entry at position %" PRIu64 " " - "has wrong sub string type", + emsgu(_(RERR "Error while reading ShaDa file: " + "sub string entry at position %" PRIu64 " " + "has wrong sub string type"), (uint64_t) initial_fpos); goto shada_read_next_item_error; } @@ -3726,9 +3765,9 @@ shada_read_next_item_hist_no_conv: entry->data.sub_string.additional_elements = xmalloc(sizeof(Array)); if (!msgpack_rpc_to_array( &obj, entry->data.sub_string.additional_elements)) { - emsgu("Error while reading ShaDa file: " - "sub string entry at position %" PRIu64 " " - "cannot be converted to an Array", + emsgu(_(RERR "Error while reading ShaDa file: " + "sub string entry at position %" PRIu64 " " + "cannot be converted to an Array"), (uint64_t) initial_fpos); goto shada_read_next_item_error; } @@ -3737,9 +3776,9 @@ shada_read_next_item_hist_no_conv: } case kSDItemBufferList: { if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) { - emsgu("Error while reading ShaDa file: " - "buffer list entry at position %" PRIu64 " " - "is not an array", + emsgu(_(RERR "Error while reading ShaDa file: " + "buffer list entry at position %" PRIu64 " " + "is not an array"), (uint64_t) initial_fpos); goto shada_read_next_item_error; } @@ -3761,9 +3800,9 @@ shada_read_next_item_hist_no_conv: { msgpack_unpacked unpacked = unpacked_2; if (unpacked.data.type != MSGPACK_OBJECT_MAP) { - emsgu("Error while reading ShaDa file: " - "buffer list at position %" PRIu64 " " - "contains entry that is not a dictionary", + emsgu(_(RERR "Error while reading ShaDa file: " + "buffer list at position %" PRIu64 " " + "contains entry that is not a dictionary"), (uint64_t) initial_fpos); goto shada_read_next_item_error; } @@ -3786,9 +3825,9 @@ shada_read_next_item_hist_no_conv: } } if (entry->data.buffer_list.buffers[i].fname == NULL) { - emsgu("Error while reading ShaDa file: " - "buffer list at position %" PRIu64 " " - "contains entry that does not have a file name", + emsgu(_(RERR "Error while reading ShaDa file: " + "buffer list at position %" PRIu64 " " + "contains entry that does not have a file name"), (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; @@ -3807,9 +3846,10 @@ shada_read_next_item_hist_no_conv: xmalloc(sizeof(Dictionary)); if (!msgpack_rpc_to_dictionary( &obj, entry->data.buffer_list.buffers[i].additional_data)) { - emsgu("Error while reading ShaDa file: " - "buffer list at position %" PRIu64 " " - "contains entry that cannot be converted to a Dictionary", + emsgu(_(RERR "Error while reading ShaDa file: " + "buffer list at position %" PRIu64 " " + "contains entry that cannot be converted " + "to a Dictionary"), (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; @@ -3821,10 +3861,10 @@ shada_read_next_item_hist_no_conv: break; } case kSDItemMissing: { - emsgu("Error while reading ShaDa file: " - "there is an item at position %" PRIu64 " " - "that must not be there: Missing items are " - "for internal uses only", + emsgu(_(RERR "Error while reading ShaDa file: " + "there is an item at position %" PRIu64 " " + "that must not be there: Missing items are " + "for internal uses only"), (uint64_t) initial_fpos); goto shada_read_next_item_error; } diff --git a/src/nvim/shada.h b/src/nvim/shada.h index 8033cbe116..438eb9c07f 100644 --- a/src/nvim/shada.h +++ b/src/nvim/shada.h @@ -5,11 +5,11 @@ typedef long ShadaPosition; /// Flags for shada_read_file and children 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. - kShaDaWantHeader = 16, ///< Do not skip header (shada_read_next_item). + 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. }; #ifdef INCLUDE_GENERATED_DECLARATIONS -- cgit From 931539d108d397b88da618585861dce854018af2 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 26 Jul 2015 01:20:13 +0300 Subject: shada: Do not export shada_read_file Unlike some wrapper functions it is not used actually, so no need to export it. Also removed ShadaPosition typedef which was not used. --- src/nvim/shada.c | 11 ++++++++++- src/nvim/shada.h | 11 ----------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index d64b8c489d..7ca4edc783 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -151,6 +151,15 @@ KHASH_SET_INIT_STR(strset) /// Common prefix for all “system” errors #define SERR "E886: " +/// Flags for shada_read_file and children +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. +}; + /// Possible ShaDa entry types /// /// @warning Enum values are part of the API and must not be altered. @@ -776,7 +785,7 @@ static bool shada_disabled(void) /// @param[in] flags Flags, see kShaDa enum values in shada.h. /// /// @return FAIL if reading failed for some reason and OK otherwise. -int shada_read_file(const char *const file, const int flags) +static int shada_read_file(const char *const file, const int flags) FUNC_ATTR_WARN_UNUSED_RESULT { if (shada_disabled()) { diff --git a/src/nvim/shada.h b/src/nvim/shada.h index 438eb9c07f..2b0a6ff6be 100644 --- a/src/nvim/shada.h +++ b/src/nvim/shada.h @@ -1,17 +1,6 @@ #ifndef NVIM_SHADA_H #define NVIM_SHADA_H -typedef long ShadaPosition; - -/// Flags for shada_read_file and children -enum { - kShaDaWantInfo = 1, ///< Load non-mark information - kShaDaWantMarks = 2, ///< Load 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. -}; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "shada.h.generated.h" #endif -- cgit From 21c12cf4e7a1ac355c288171f0da0cb2f5f978fd Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 26 Jul 2015 02:19:49 +0300 Subject: shada: Do not handle EINTR in open_file It is already handled by libuv which is used by os_open. --- src/nvim/shada.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 7ca4edc783..b4002a3b77 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -666,9 +666,6 @@ open_file_start: did_try_to_free = true; goto open_file_start; } - if (-fd == EINTR) { - goto open_file_start; - } if (-fd != EEXIST) { emsg3(_(SERR "System error while opening ShaDa file %s: %s"), fname, os_strerror(fd)); -- cgit From b7ca976f730b4648d4f1cf48ea894ee0a8ae8e72 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 26 Jul 2015 19:52:29 +0300 Subject: shada: Make all mapping keys have at most 2 byte length Now all keys are defined in a way that makes it easy to redefine if needed. --- src/nvim/shada.c | 113 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 67 insertions(+), 46 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index b4002a3b77..92a1a11d27 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -124,6 +124,25 @@ KHASH_SET_INIT_STR(strset) // Define nothing #endif +#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 @@ -1587,7 +1606,7 @@ static void shada_pack_entry(msgpack_packer *const packer, : 0) ); msgpack_pack_map(spacker, map_size); - PACK_STATIC_STR("pat"); + PACK_STATIC_STR(SEARCH_KEY_PAT); msgpack_rpc_from_string(cstr_as_string(entry.data.search_pattern.pat), spacker); #define PACK_BOOL(name, attr, nondef_value) \ @@ -1597,15 +1616,15 @@ static void shada_pack_entry(msgpack_packer *const packer, msgpack_pack_##nondef_value(spacker); \ } \ } while (0) - PACK_BOOL("magic", magic, false); - PACK_BOOL("islast", is_last_used, false); - PACK_BOOL("smartcase", smartcase, true); - PACK_BOOL("lineoff", has_line_offset, true); - PACK_BOOL("curatend", place_cursor_at_end, true); - PACK_BOOL("sub", is_substitute_pattern, true); - PACK_BOOL("hlsearch", highlighted, true); + PACK_BOOL(SEARCH_KEY_MAGIC, magic, false); + PACK_BOOL(SEARCH_KEY_IS_LAST_USED, is_last_used, false); + PACK_BOOL(SEARCH_KEY_SMARTCASE, smartcase, true); + PACK_BOOL(SEARCH_KEY_HAS_LINE_OFFSET, has_line_offset, true); + PACK_BOOL(SEARCH_KEY_PLACE_CURSOR_AT_END, place_cursor_at_end, true); + PACK_BOOL(SEARCH_KEY_IS_SUBSTITUTE_PATTERN, is_substitute_pattern, true); + PACK_BOOL(SEARCH_KEY_HIGHLIGHTED, highlighted, true); if (entry.data.search_pattern.offset) { - PACK_STATIC_STR("off"); + PACK_STATIC_STR(SEARCH_KEY_OFFSET); msgpack_pack_int64(spacker, entry.data.search_pattern.offset); } #undef PACK_BOOL @@ -1641,20 +1660,20 @@ static void shada_pack_entry(msgpack_packer *const packer, : entry.data.filemark.additional_data->size) ); msgpack_pack_map(spacker, map_size); - PACK_STATIC_STR("file"); + PACK_STATIC_STR(KEY_FILE); msgpack_rpc_from_string(cstr_as_string(entry.data.filemark.fname), spacker); if (entry.data.filemark.mark.lnum != 1) { - PACK_STATIC_STR("line"); + PACK_STATIC_STR(KEY_LNUM); msgpack_pack_long(spacker, entry.data.filemark.mark.lnum); } if (entry.data.filemark.mark.col != 0) { - PACK_STATIC_STR("col"); + PACK_STATIC_STR(KEY_COL); msgpack_pack_long(spacker, entry.data.filemark.mark.col); } if (entry.data.filemark.name != '"' && entry.type != kSDItemJump && entry.type != kSDItemChange) { - PACK_STATIC_STR("name"); + PACK_STATIC_STR(KEY_NAME_CHAR); msgpack_pack_uint8(spacker, (uint8_t) entry.data.filemark.name); } if (entry.data.filemark.additional_data != NULL) { @@ -1681,20 +1700,20 @@ static void shada_pack_entry(msgpack_packer *const packer, : entry.data.reg.additional_data->size) ); msgpack_pack_map(spacker, map_size); - PACK_STATIC_STR("contents"); + 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++) { msgpack_rpc_from_string(cstr_as_string(entry.data.reg.contents[i]), spacker); } - PACK_STATIC_STR("name"); + PACK_STATIC_STR(KEY_NAME_CHAR); msgpack_pack_char(spacker, entry.data.reg.name); if (entry.data.reg.type != MCHAR) { - PACK_STATIC_STR("type"); + PACK_STATIC_STR(REG_KEY_TYPE); msgpack_pack_uint8(spacker, entry.data.reg.type); } if (entry.data.reg.width != 0) { - PACK_STATIC_STR("width"); + PACK_STATIC_STR(REG_KEY_WIDTH); msgpack_pack_uint64(spacker, (uint64_t) entry.data.reg.width); } if (entry.data.reg.additional_data != NULL) { @@ -1724,16 +1743,16 @@ static void shada_pack_entry(msgpack_packer *const packer, : entry.data.buffer_list.buffers[i].additional_data->size) ); msgpack_pack_map(spacker, map_size); - PACK_STATIC_STR("file"); + PACK_STATIC_STR(KEY_FILE); msgpack_rpc_from_string( cstr_as_string(entry.data.buffer_list.buffers[i].fname), spacker); if (entry.data.buffer_list.buffers[i].pos.lnum != 1) { - PACK_STATIC_STR("line"); + 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("col"); + PACK_STATIC_STR(KEY_COL); msgpack_pack_uint64( spacker, (uint64_t) entry.data.buffer_list.buffers[i].pos.col); } @@ -3256,7 +3275,7 @@ shada_read_next_item_read_next: {} } #define TYPED_KEY(entry_name, name, type_name, tgt, objtype, attr, proc) \ CHECKED_KEY( \ - entry_name, name, " which is not " type_name, tgt, \ + 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) \ @@ -3267,7 +3286,7 @@ shada_read_next_item_read_next: {} 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, \ + 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 \ @@ -3325,22 +3344,23 @@ shada_read_next_item_read_next: {} ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1); for (size_t i = 0; i < unpacked.data.via.map.size; i++) { CHECK_KEY_IS_STR("search pattern"); - BOOLEAN_KEY("search pattern", "magic", entry->data.search_pattern.magic) - else BOOLEAN_KEY("search pattern", "smartcase", + 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", "lineoff", + else BOOLEAN_KEY("search pattern", SEARCH_KEY_HAS_LINE_OFFSET, entry->data.search_pattern.has_line_offset) - else BOOLEAN_KEY("search pattern", "curatend", + else BOOLEAN_KEY("search pattern", SEARCH_KEY_PLACE_CURSOR_AT_END, entry->data.search_pattern.place_cursor_at_end) - else BOOLEAN_KEY("search pattern", "islast", + else BOOLEAN_KEY("search pattern", SEARCH_KEY_IS_LAST_USED, entry->data.search_pattern.is_last_used) - else BOOLEAN_KEY("search pattern", "sub", + else BOOLEAN_KEY("search pattern", SEARCH_KEY_IS_SUBSTITUTE_PATTERN, entry->data.search_pattern.is_substitute_pattern) - else BOOLEAN_KEY("search pattern", "hlsearch", + else BOOLEAN_KEY("search pattern", SEARCH_KEY_HIGHLIGHTED, entry->data.search_pattern.highlighted) - else INTEGER_KEY("search pattern", "off", + else INTEGER_KEY("search pattern", SEARCH_KEY_OFFSET, entry->data.search_pattern.offset) - else CONVERTED_STRING_KEY("search pattern", "pat", + else CONVERTED_STRING_KEY("search pattern", SEARCH_KEY_PAT, entry->data.search_pattern.pat) else ADDITIONAL_KEY } @@ -3399,16 +3419,16 @@ shada_read_next_item_read_next: {} for (size_t i = 0; i < unpacked.data.via.map.size; i++) { CHECK_KEY_IS_STR("mark"); CHECKED_KEY( - "mark", "name", " which is not an unsigned integer", + "mark", KEY_NAME_CHAR, " which is not an unsigned integer", entry->data.filemark.name, (type_u64 != kSDItemJump && type_u64 != kSDItemChange && unpacked.data.via.map.ptr[i].val.type == MSGPACK_OBJECT_POSITIVE_INTEGER), u64, TOCHAR) - else LONG_KEY("mark", "line", entry->data.filemark.mark.lnum) - else INTEGER_KEY("mark", "col", entry->data.filemark.mark.col) - else STRING_KEY("mark", "file", entry->data.filemark.fname) + else 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.mark.lnum == 0) { @@ -3463,17 +3483,18 @@ shada_read_next_item_read_next: {} ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1); for (size_t i = 0; i < unpacked.data.via.map.size; i++) { CHECK_KEY_IS_STR("register"); - TYPED_KEY("register", "type", "an unsigned integer", + TYPED_KEY("register", REG_KEY_TYPE, "an unsigned integer", entry->data.reg.type, POSITIVE_INTEGER, u64, TOU8) - else TYPED_KEY("register", "name", "an unsigned integer", + else TYPED_KEY("register", KEY_NAME_CHAR, "an unsigned integer", entry->data.reg.name, POSITIVE_INTEGER, u64, TOCHAR) - else TYPED_KEY("register", "width", "an unsigned integer", + 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, "contents")) { + 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(_(RERR "Error while reading ShaDa file: " "register entry at position %" PRIu64 " " - "has contents key with non-array value"), + "has " REG_KEY_CONTENTS " key with non-array value"), (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; @@ -3481,7 +3502,7 @@ shada_read_next_item_read_next: {} if (unpacked.data.via.map.ptr[i].val.via.array.size == 0) { emsgu(_(RERR "Error while reading ShaDa file: " "register entry at position %" PRIu64 " " - "has contents key with empty array"), + "has " REG_KEY_CONTENTS " key with empty array"), (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; @@ -3492,7 +3513,7 @@ shada_read_next_item_read_next: {} if (arr.ptr[i].type != MSGPACK_OBJECT_BIN) { emsgu(_(RERR "Error while reading ShaDa file: " "register entry at position %" PRIu64 " " - "has contents array with non-string value"), + "has " REG_KEY_CONTENTS " array with non-string value"), (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; @@ -3508,7 +3529,7 @@ shada_read_next_item_read_next: {} if (entry->data.reg.contents == NULL) { emsgu(_(RERR "Error while reading ShaDa file: " "register entry at position %" PRIu64 " " - "has missing contents array"), + "has missing " REG_KEY_CONTENTS " array"), (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; @@ -3820,11 +3841,11 @@ shada_read_next_item_hist_no_conv: { for (size_t i = 0; i < unpacked.data.via.map.size; i++) { CHECK_KEY_IS_STR("buffer list entry"); - LONG_KEY("buffer list entry", "line", + LONG_KEY("buffer list entry", KEY_LNUM, entry->data.buffer_list.buffers[j].pos.lnum) - else INTEGER_KEY("buffer list entry", "col", + else INTEGER_KEY("buffer list entry", KEY_COL, entry->data.buffer_list.buffers[j].pos.col) - else STRING_KEY("buffer list entry", "file", + else STRING_KEY("buffer list entry", KEY_FILE, entry->data.buffer_list.buffers[j].fname) else ADDITIONAL_KEY } -- cgit From e2994a3c62265a26a632ed7cd4d11ce4fb711586 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 26 Jul 2015 01:52:50 +0300 Subject: shada,functests: Test how ShaDa support code reacts on errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some notes: - Replaced msgpack_unpacker usage with regular xmalloc’ed buffer. Also since msgpack_unpack_next (as well as msgpack_unpacker_next) is not ever going to return MSGPACK_UNPACK_EXTRA_BYTES this condition was checked manually. Function that does return this status is msgpack_unpack, but it is marked as obsolete. - Zero type is checked prior to main switch in shada_read_next_item because otherwise check would be skipped. - Zeroing entry at the start of shada_read_next_item makes it safer. - dedent('') does not work. - v:oldfiles list is only replaced with bang, if it is NULL or empty. --- src/nvim/shada.c | 126 +++++++---- test/functional/helpers.lua | 7 +- test/functional/shada/errors_spec.lua | 399 ++++++++++++++++++++++++++++++++++ test/functional/shada/helpers.lua | 19 +- test/functional/shada/marks_spec.lua | 12 +- 5 files changed, 513 insertions(+), 50 deletions(-) create mode 100644 test/functional/shada/errors_spec.lua diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 92a1a11d27..3672e2db25 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1061,8 +1061,11 @@ static inline bool marks_equal(const pos_T a, const pos_T b) 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); + 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 = ((flags & kShaDaWantInfo ? (kSDReadUndisableableData @@ -1105,11 +1108,12 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) fname_bufs = kh_init(fnamebufs); } khash_t(strset) *oldfiles_set = NULL; - list_T *oldfiles_list = NULL; if (get_old_files) { oldfiles_set = kh_init(strset); - oldfiles_list = list_alloc(); - set_vim_var_list(VV_OLDFILES, oldfiles_list); + if (oldfiles_list == NULL) { + oldfiles_list = list_alloc(); + set_vim_var_list(VV_OLDFILES, oldfiles_list); + } } while (shada_read_next_item(sd_reader, &cur_entry, srni_flags, 0) == NOTDONE) { @@ -3134,7 +3138,11 @@ static int shada_read_next_item(ShaDaReadDef *const sd_reader, FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { shada_read_next_item_start: - entry->type = kSDItemMissing; + // 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 OK; } @@ -3162,6 +3170,18 @@ shada_read_next_item_start: 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(_(RERR "Error while reading ShaDa file: " + "there is an item at position %" PRIu64 " " + "that must not be there: Missing items are " + "for internal uses only"), + (uint64_t) initial_fpos); + return FAIL; + } + if ((type_u64 > SHADA_LAST_ENTRY ? !(flags & kSDReadUnknown) : !((unsigned) (1 << type_u64) & flags)) @@ -3180,28 +3200,26 @@ shada_read_next_item_start: return fread_len(sd_reader, entry->data.unknown_item.contents, length); } - msgpack_unpacker *const unpacker = msgpack_unpacker_new(length); - if (unpacker == NULL || - !msgpack_unpacker_reserve_buffer(unpacker, length)) { - EMSG(_(e_outofmem)); - goto shada_read_next_item_error; - } + char *const buf = xmalloc(length); - if (fread_len(sd_reader, msgpack_unpacker_buffer(unpacker), length) != OK) { - msgpack_unpacker_free(unpacker); + if (fread_len(sd_reader, buf, length) != OK) { + xfree(buf); return FAIL; } - msgpack_unpacker_buffer_consumed(unpacker, length); msgpack_unpacked unpacked; msgpack_unpacked_init(&unpacked); bool did_try_to_free = false; shada_read_next_item_read_next: {} + size_t off = 0; const msgpack_unpack_return result = - msgpack_unpacker_next(unpacker, &unpacked); + msgpack_unpack_next(&unpacked, buf, length, &off); switch (result) { case MSGPACK_UNPACK_SUCCESS: { + if (off < length) { + goto shada_read_next_item_extra_bytes; + } break; } case MSGPACK_UNPACK_PARSE_ERROR: { @@ -3226,6 +3244,7 @@ shada_read_next_item_read_next: {} goto shada_read_next_item_error; } case MSGPACK_UNPACK_EXTRA_BYTES: { +shada_read_next_item_extra_bytes: emsgu(_(RERR "Failed to parse ShaDa file: extra bytes in msgpack string " "at position %" PRIu64), (uint64_t) initial_fpos); @@ -3418,23 +3437,47 @@ shada_read_next_item_read_next: {} ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1); for (size_t i = 0; i < unpacked.data.via.map.size; i++) { CHECK_KEY_IS_STR("mark"); - CHECKED_KEY( - "mark", KEY_NAME_CHAR, " which is not an unsigned integer", - entry->data.filemark.name, - (type_u64 != kSDItemJump - && type_u64 != kSDItemChange - && unpacked.data.via.map.ptr[i].val.type - == MSGPACK_OBJECT_POSITIVE_INTEGER), - u64, TOCHAR) - else LONG_KEY("mark", KEY_LNUM, entry->data.filemark.mark.lnum) + if (CHECK_KEY(unpacked.data.via.map.ptr[i].key, KEY_NAME_CHAR)) { + if (type_u64 == kSDItemJump || type_u64 == kSDItemChange) { + emsgu(_(RERR "Error while reading ShaDa file: " + "mark entry at position %" PRIu64 " " + "has n key which is only valid " + "for local and global mark entries"), + (uint64_t) initial_fpos); + ga_clear(&ad_ga); + goto shada_read_next_item_error; + } + 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.mark.lnum == 0) { + if (entry->data.filemark.fname == NULL) { + emsgu(_(RERR "Error while reading ShaDa file: " + "mark entry at position %" PRIu64 " " + "is missing file name"), + (uint64_t) initial_fpos); + ga_clear(&ad_ga); + goto shada_read_next_item_error; + } + if (entry->data.filemark.mark.lnum <= 0) { + emsgu(_(RERR "Error while reading ShaDa file: " + "mark entry at position %" PRIu64 " " + "has invalid line number"), + (uint64_t) initial_fpos); + ga_clear(&ad_ga); + goto shada_read_next_item_error; + } + if (entry->data.filemark.mark.col < 0) { emsgu(_(RERR "Error while reading ShaDa file: " "mark entry at position %" PRIu64 " " - "is missing line number"), + "has invalid column number"), (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; @@ -3513,7 +3556,7 @@ shada_read_next_item_read_next: {} if (arr.ptr[i].type != MSGPACK_OBJECT_BIN) { emsgu(_(RERR "Error while reading ShaDa file: " "register entry at position %" PRIu64 " " - "has " REG_KEY_CONTENTS " array with non-string value"), + "has " REG_KEY_CONTENTS " array with non-binary value"), (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; @@ -3851,6 +3894,22 @@ shada_read_next_item_hist_no_conv: } } } + 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"), + (uint64_t) initial_fpos); + ga_clear(&ad_ga); + goto shada_read_next_item_error; + } + 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"), + (uint64_t) initial_fpos); + ga_clear(&ad_ga); + goto shada_read_next_item_error; + } if (entry->data.buffer_list.buffers[i].fname == NULL) { emsgu(_(RERR "Error while reading ShaDa file: " "buffer list at position %" PRIu64 " " @@ -3887,14 +3946,7 @@ shada_read_next_item_hist_no_conv: } break; } - case kSDItemMissing: { - emsgu(_(RERR "Error while reading ShaDa file: " - "there is an item at position %" PRIu64 " " - "that must not be there: Missing items are " - "for internal uses only"), - (uint64_t) initial_fpos); - goto shada_read_next_item_error; - } + case kSDItemMissing: case kSDItemUnknown: { assert(false); } @@ -3921,14 +3973,14 @@ shada_read_next_item_hist_no_conv: #undef TOSIZE shada_read_next_item_error: msgpack_unpacked_destroy(&unpacked); - msgpack_unpacker_free(unpacker); + xfree(buf); entry->type = (ShadaEntryType) type_u64; shada_free_shada_entry(entry); entry->type = kSDItemMissing; return FAIL; shada_read_next_item_end: msgpack_unpacked_destroy(&unpacked); - msgpack_unpacker_free(unpacker); + xfree(buf); return NOTDONE; } diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index a6f4f7c2e5..23581ba4d2 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -220,9 +220,12 @@ local function execute(...) end -- Dedent the given text and write it to the file name. -local function write_file(name, text) +local function write_file(name, text, dont_dedent) local file = io.open(name, 'w') - file:write(dedent(text)) + if not dont_dedent then + text = dedent(text) + end + file:write(text) file:flush() file:close() end diff --git a/test/functional/shada/errors_spec.lua b/test/functional/shada/errors_spec.lua new file mode 100644 index 0000000000..5107273115 --- /dev/null +++ b/test/functional/shada/errors_spec.lua @@ -0,0 +1,399 @@ +-- ShaDa errors handling support +local helpers = require('test.functional.helpers') +local nvim, nvim_window, nvim_curwin, nvim_command, nvim_feed, nvim_eval, eq = + helpers.nvim, helpers.window, helpers.curwin, helpers.command, helpers.feed, + helpers.eval, helpers.eq +local write_file = helpers.write_file + +local shada_helpers = require('test.functional.shada.helpers') +local reset, set_additional_cmd, clear, exc_exec = + shada_helpers.reset, shada_helpers.set_additional_cmd, + shada_helpers.clear, shada_helpers.exc_exec + +local shada_fname = 'Xtest-functional-shada-errors.shada' +local wshada = function(text) + write_file(shada_fname, text, true) +end +local sdrcmd = function(bang) + return 'rshada' .. (bang and '!' or '') .. ' ' .. shada_fname +end + +describe('ShaDa error handling', function() + before_each(reset) + after_each(function() + clear() + os.remove(shada_fname) + local i = ('a'):byte() + while i < ('z'):byte() do + if not os.remove(shada_fname .. ('.tmp.%c'):format(i)) then + break + end + i = i + 1 + end + end) + + -- Note: most of tests have additional items like sX, mX, rX. These are for + -- valgrind tests, to check for memory leaks (i.e. whether error handling code + -- does (not) forget to call ga_clear). Not needed for array-based items like + -- history because they are not using ad_ga. + + it('does not fail on empty file', function() + wshada('') + eq(0, exc_exec(sdrcmd())) + end) + + it('fails on zero', function() + wshada('\000') + eq('Vim(rshada):E575: Error while reading ShaDa file: expected positive integer at position 0, but got nothing', exc_exec(sdrcmd())) + end) + + it('fails on missing item', function() + wshada('\000\000\000') + eq('Vim(rshada):E575: Error while reading ShaDa file: there is an item at position 0 that must not be there: Missing items are for internal uses only', exc_exec(sdrcmd())) + end) + + it('fails on -2 type', function() + wshada('\254\000\000') + eq('Vim(rshada):E575: Error while reading ShaDa file: expected positive integer at position 0', exc_exec(sdrcmd())) + end) + + it('does not fail on header with zero length', function() + -- Header items are skipped when reading. + wshada('\001\000\000') + eq(0, exc_exec(sdrcmd())) + end) + + it('fails on search pattern item with zero length', function() + wshada('\002\000\000') + eq('Vim(rshada):E575: Failed to parse ShaDa file: incomplete msgpack string at position 0', exc_exec(sdrcmd())) + end) + + it('fails on search pattern item with -2 timestamp', function() + wshada('\002\254\000') + eq('Vim(rshada):E575: Error while reading ShaDa file: expected positive integer at position 1', exc_exec(sdrcmd())) + end) + + it('fails on search pattern item with -2 length', function() + wshada('\002\000\254') + eq('Vim(rshada):E575: Error while reading ShaDa file: expected positive integer at position 2', exc_exec(sdrcmd())) + end) + + it('fails on search pattern item with length greater then file length', function() + wshada('\002\000\002\000') + eq('Vim(rshada):E575: Error while reading ShaDa file: last entry specified that it occupies 2 bytes, but file ended earlier', exc_exec(sdrcmd())) + end) + + it('fails on search pattern item with invalid byte', function() + -- 195 (== 0xC1) cannot start any valid messagepack entry (the only byte + -- that cannot do this). Specifically unpack_template.h contains + -- + -- //case 0xc1: // string + -- // again_terminal_trail(NEXT_CS(p), p+1); + -- + -- (literally: commented out code) which means that in place of this code + -- `goto _failed` is used from default: case. I do not know any other way to + -- get MSGPACK_UNPACK_PARSE_ERROR and not MSGPACK_UNPACK_CONTINUE or + -- MSGPACK_UNPACK_EXTRA_BYTES. + wshada('\002\000\001\193') + eq('Vim(rshada):E575: Failed to parse ShaDa file due to a msgpack parser error at position 0', exc_exec(sdrcmd())) + end) + + it('fails on search pattern item with incomplete map', function() + wshada('\002\000\001\129') + eq('Vim(rshada):E575: Failed to parse ShaDa file: incomplete msgpack string at position 0', exc_exec(sdrcmd())) + end) + + it('fails on search pattern item without a pattern', function() + wshada('\002\000\005\129\162sX\192') + eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has no pattern', exc_exec(sdrcmd())) + end) + + it('fails on search pattern with extra bytes', function() + wshada('\002\000\002\128\000') + eq('Vim(rshada):E575: Failed to parse ShaDa file: extra bytes in msgpack string at position 0', exc_exec(sdrcmd())) + end) + + it('fails on search pattern item with NIL value', function() + wshada('\002\000\001\192') + eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 is not a dictionary', exc_exec(sdrcmd())) + end) + + -- sp entry is here because it causes an allocation. + it('fails on search pattern item with BIN key', function() + wshada('\002\000\014\131\162sp\196\001a\162sX\192\196\000\000') + eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has key which is not a string', exc_exec(sdrcmd())) + end) + + it('fails on search pattern item with NIL magic key value', function() + wshada('\002\000\009\130\162sX\192\162sm\192') + eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has sm key value which is not a boolean', exc_exec(sdrcmd())) + end) + + it('fails on search pattern item with NIL smartcase key value', function() + wshada('\002\000\009\130\162sX\192\162sc\192') + eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has sc key value which is not a boolean', exc_exec(sdrcmd())) + end) + + it('fails on search pattern item with NIL has_line_offset key value', function() + wshada('\002\000\009\130\162sX\192\162sl\192') + eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has sl key value which is not a boolean', exc_exec(sdrcmd())) + end) + + it('fails on search pattern item with NIL place_cursor_at_end key value', function() + wshada('\002\000\009\130\162sX\192\162se\192') + eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has se key value which is not a boolean', exc_exec(sdrcmd())) + end) + + it('fails on search pattern item with NIL is_last_used key value', function() + wshada('\002\000\009\130\162sX\192\162su\192') + eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has su key value which is not a boolean', exc_exec(sdrcmd())) + end) + + it('fails on search pattern item with NIL is_substitute_pattern key value', function() + wshada('\002\000\009\130\162sX\192\162ss\192') + eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has ss key value which is not a boolean', exc_exec(sdrcmd())) + end) + + it('fails on search pattern item with NIL highlighted key value', function() + wshada('\002\000\009\130\162sX\192\162sh\192') + eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has sh key value which is not a boolean', exc_exec(sdrcmd())) + end) + + it('fails on search pattern item with NIL offset key value', function() + wshada('\002\000\009\130\162sX\192\162so\192') + eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has so key value which is not an integer', exc_exec(sdrcmd())) + end) + + it('fails on search pattern item with NIL pat key value', function() + wshada('\002\000\009\130\162sX\192\162sp\192') + eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has sp key value which is not a binary', exc_exec(sdrcmd())) + end) + + it('fails on search pattern item with STR pat key value', function() + wshada('\002\000\011\130\162sX\192\162sp\162sp') + eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has sp key value which is not a binary', exc_exec(sdrcmd())) + end) + + for _, v in ipairs({{name='global mark', mpack='\007'}, + {name='jump', mpack='\008'}, + {name='local mark', mpack='\010'}, + {name='change', mpack='\011'}, + }) do + local is_mark_test = ({['global mark']=true, ['local mark']=true})[v.name] + + it('fails on ' .. v.name .. ' item with NIL value', function() + wshada(v.mpack .. '\000\001\192') + eq('Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 is not a dictionary', exc_exec(sdrcmd())) + end) + + -- f entry is here because it causes an allocation. + it('fails on ' .. v.name .. ' item with BIN key', function() + wshada(v.mpack .. '\000\013\131\161f\196\001/\162mX\192\196\000\000') + eq('Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 has key which is not a string', exc_exec(sdrcmd())) + end) + + it('fails on ' .. v.name .. ' item without f key', function() + wshada(v.mpack .. '\000\008\130\162mX\192\161l\001') + eq('Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 is missing file name', exc_exec(sdrcmd())) + end) + + it('fails on ' .. v.name .. ' item with zero l key', function() + wshada(v.mpack .. '\000\013\131\162mX\192\161f\196\001/\161l\000') + eq('Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 has invalid line number', exc_exec(sdrcmd())) + end) + + it('fails on ' .. v.name .. ' item with negative l key', function() + wshada(v.mpack .. '\000\013\131\162mX\192\161f\196\001/\161l\255') + eq('Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 has invalid line number', exc_exec(sdrcmd())) + end) + + it('fails on ' .. v.name .. ' item with negative c key', function() + wshada(v.mpack .. '\000\013\131\162mX\192\161f\196\001/\161c\255') + eq('Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 has invalid column number', exc_exec(sdrcmd())) + end) + + it('fails on ' .. v.name .. ' item with STR n key value', function() + wshada(v.mpack .. '\000\011\130\162mX\192\161n\163spa') + eq(is_mark_test and 'Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 has n key value which is not an unsigned integer' or 'Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 has n key which is only valid for local and global mark entries', exc_exec(sdrcmd())) + end) + + it('fails on ' .. v.name .. ' item with STR l key value', function() + wshada(v.mpack .. '\000\010\130\162mX\192\161l\162sp') + eq('Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 has l key value which is not an integer', exc_exec(sdrcmd())) + end) + + it('fails on ' .. v.name .. ' item with STR c key value', function() + wshada(v.mpack .. '\000\010\130\162mX\192\161c\162sp') + eq('Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 has c key value which is not an integer', exc_exec(sdrcmd())) + end) + + it('fails on ' .. v.name .. ' item with STR f key value', function() + wshada(v.mpack .. '\000\010\130\162mX\192\161f\162sp') + eq('Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 has f key value which is not a binary', exc_exec(sdrcmd())) + end) + end + + it('fails on register item with NIL value', function() + wshada('\005\000\001\192') + eq('Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 is not a dictionary', exc_exec(sdrcmd())) + end) + + -- rc entry is here because it causes an allocation + it('fails on register item with BIN key', function() + wshada('\005\000\015\131\162rc\145\196\001a\162rX\192\196\000\000') + eq('Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 has key which is not a string', exc_exec(sdrcmd())) + end) + + it('fails on register item with NIL rt key value', function() + wshada('\005\000\009\130\162rX\192\162rt\192') + eq('Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 has rt key value which is not an unsigned integer', exc_exec(sdrcmd())) + end) + + it('fails on register item with NIL rw key value', function() + wshada('\005\000\009\130\162rX\192\162rw\192') + eq('Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 has rw key value which is not an unsigned integer', exc_exec(sdrcmd())) + end) + + it('fails on register item with NIL rc key value', function() + wshada('\005\000\009\130\162rX\192\162rc\192') + eq('Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 has rc key with non-array value', exc_exec(sdrcmd())) + end) + + it('fails on register item with empty rc key value', function() + wshada('\005\000\009\130\162rX\192\162rc\144') + eq('Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 has rc key with empty array', exc_exec(sdrcmd())) + end) + + it('fails on register item with NIL in rc array', function() + wshada('\005\000\013\130\162rX\192\162rc\146\196\001a\192') + eq('Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 has rc array with non-binary value', exc_exec(sdrcmd())) + end) + + it('fails on register item without rc array', function() + wshada('\005\000\009\129\162rX\146\196\001a\192') + eq('Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 has missing rc array', exc_exec(sdrcmd())) + end) + + it('fails on history item with NIL value', function() + wshada('\004\000\001\192') + eq('Vim(rshada):E575: Error while reading ShaDa file: history entry at position 0 is not an array', exc_exec(sdrcmd())) + end) + + it('fails on history item with empty value', function() + wshada('\004\000\001\144') + eq('Vim(rshada):E575: Error while reading ShaDa file: history entry at position 0 does not have enough elements', exc_exec(sdrcmd())) + end) + + it('fails on history item with single element value', function() + wshada('\004\000\002\145\000') + eq('Vim(rshada):E575: Error while reading ShaDa file: history entry at position 0 does not have enough elements', exc_exec(sdrcmd())) + end) + + it('fails on history item with NIL first item', function() + wshada('\004\000\003\146\192\000') + eq('Vim(rshada):E575: Error while reading ShaDa file: history entry at position 0 has wrong history type type', exc_exec(sdrcmd())) + end) + + it('fails on history item with FIXUINT second item', function() + wshada('\004\000\003\146\000\000') + eq('Vim(rshada):E575: Error while reading ShaDa file: history entry at position 0 has wrong history string type', exc_exec(sdrcmd())) + end) + + it('fails on history item with second item with zero byte', function() + wshada('\004\000\007\146\000\196\003ab\000') + eq('Vim(rshada):E575: Error while reading ShaDa file: history entry at position 0 contains string with zero byte inside', exc_exec(sdrcmd())) + end) + + it('fails on search history item without third item', function() + wshada('\004\000\007\146\001\196\003abc') + eq('Vim(rshada):E575: Error while reading ShaDa file: search history entry at position 0 does not have separator character', exc_exec(sdrcmd())) + end) + + it('fails on search history item with NIL third item', function() + wshada('\004\000\007\147\001\196\002ab\192') + eq('Vim(rshada):E575: Error while reading ShaDa file: search history entry at position 0 has wrong history separator type', exc_exec(sdrcmd())) + end) + + it('fails on variable item with NIL value', function() + wshada('\006\000\001\192') + eq('Vim(rshada):E575: Error while reading ShaDa file: variable entry at position 0 is not an array', exc_exec(sdrcmd())) + end) + + it('fails on variable item with empty value', function() + wshada('\006\000\001\144') + eq('Vim(rshada):E575: Error while reading ShaDa file: variable entry at position 0 does not have enough elements', exc_exec(sdrcmd())) + end) + + it('fails on variable item with single element value', function() + wshada('\006\000\002\145\000') + eq('Vim(rshada):E575: Error while reading ShaDa file: variable entry at position 0 does not have enough elements', exc_exec(sdrcmd())) + end) + + it('fails on variable item with NIL first item', function() + wshada('\006\000\003\146\192\000') + eq('Vim(rshada):E575: Error while reading ShaDa file: variable entry at position 0 has wrong variable name type', exc_exec(sdrcmd())) + end) + + it('fails on replacement item with NIL value', function() + wshada('\003\000\001\192') + eq('Vim(rshada):E575: Error while reading ShaDa file: sub string entry at position 0 is not an array', exc_exec(sdrcmd())) + end) + + it('fails on replacement item with empty value', function() + wshada('\003\000\001\144') + eq('Vim(rshada):E575: Error while reading ShaDa file: sub string entry at position 0 does not have enough elements', exc_exec(sdrcmd())) + end) + + it('fails on replacement item with NIL first item', function() + wshada('\003\000\002\145\192') + eq('Vim(rshada):E575: Error while reading ShaDa file: sub string entry at position 0 has wrong sub string type', exc_exec(sdrcmd())) + end) + + it('fails on buffer list item with NIL value', function() + nvim_command('set shada+=%') + wshada('\009\000\001\192') + eq('Vim(rshada):E575: Error while reading ShaDa file: buffer list entry at position 0 is not an array', exc_exec(sdrcmd())) + end) + + it('fails on buffer list item with NIL item in the array', function() + nvim_command('set shada+=%') + wshada('\009\000\008\146\129\161f\196\001/\192') + eq('Vim(rshada):E575: Error while reading ShaDa file: buffer list at position 0 contains entry that is not a dictionary', exc_exec(sdrcmd())) + end) + + it('fails on buffer list item with empty item', function() + nvim_command('set shada+=%') + wshada('\009\000\008\146\129\161f\196\001/\128') + eq('Vim(rshada):E575: Error while reading ShaDa file: buffer list at position 0 contains entry that does not have a file name', exc_exec(sdrcmd())) + end) + + it('fails on buffer list item with NIL l key', function() + nvim_command('set shada+=%') + wshada('\009\000\017\146\129\161f\196\001/\130\161f\196\002/a\161l\192') + eq('Vim(rshada):E575: Error while reading ShaDa file: buffer list entry entry at position 0 has l key value which is not an integer', exc_exec(sdrcmd())) + end) + + it('fails on buffer list item with zero l key', function() + nvim_command('set shada+=%') + wshada('\009\000\017\146\129\161f\196\001/\130\161f\196\002/a\161l\000') + eq('Vim(rshada):E575: Error while reading ShaDa file: buffer list at position 0 contains entry with invalid line number', exc_exec(sdrcmd())) + end) + + it('fails on buffer list item with negative l key', function() + nvim_command('set shada+=%') + wshada('\009\000\017\146\129\161f\196\001/\130\161f\196\002/a\161l\255') + eq('Vim(rshada):E575: Error while reading ShaDa file: buffer list at position 0 contains entry with invalid line number', exc_exec(sdrcmd())) + end) + + it('fails on buffer list item with negative c key', function() + nvim_command('set shada+=%') + wshada('\009\000\017\146\129\161f\196\001/\130\161f\196\002/a\161c\255') + eq('Vim(rshada):E575: Error while reading ShaDa file: buffer list at position 0 contains entry with invalid column number', exc_exec(sdrcmd())) + end) + + it('fails on buffer list item with NIL c key', function() + nvim_command('set shada+=%') + wshada('\009\000\017\146\129\161f\196\001/\130\161f\196\002/a\161c\192') + eq('Vim(rshada):E575: Error while reading ShaDa file: buffer list entry entry at position 0 has c key value which is not an integer', exc_exec(sdrcmd())) + end) +end) diff --git a/test/functional/shada/helpers.lua b/test/functional/shada/helpers.lua index 7e93fcb915..909fcd62e6 100644 --- a/test/functional/shada/helpers.lua +++ b/test/functional/shada/helpers.lua @@ -1,6 +1,7 @@ local helpers = require('test.functional.helpers') -local spawn, set_session, nvim, nvim_prog = - helpers.spawn, helpers.set_session, helpers.nvim, helpers.nvim_prog +local spawn, set_session, nvim, nvim_prog, nvim_command, nvim_eval = + helpers.spawn, helpers.set_session, helpers.nvim, helpers.nvim_prog, + helpers.command, helpers.eval local tmpname = os.tmpname() local additional_cmd = '' @@ -46,8 +47,22 @@ local clear = function() set_additional_cmd('') end +local exc_exec = function(cmd) + nvim_command(([[ + try + execute "%s" + catch + let g:__exception = v:exception + endtry + ]]):format(cmd:gsub('\n', '\\n'):gsub('[\\"]', '\\%0'))) + local ret = nvim_eval('get(g:, "__exception", 0)') + nvim_command('unlet! g:__exception') + return ret +end + return { reset=reset, set_additional_cmd=set_additional_cmd, clear=clear, + exc_exec=exc_exec, } diff --git a/test/functional/shada/marks_spec.lua b/test/functional/shada/marks_spec.lua index 4909ae608e..341ca2d647 100644 --- a/test/functional/shada/marks_spec.lua +++ b/test/functional/shada/marks_spec.lua @@ -5,9 +5,9 @@ local nvim, nvim_window, nvim_curwin, nvim_command, nvim_feed, nvim_eval, eq = helpers.eval, helpers.eq local shada_helpers = require('test.functional.shada.helpers') -local reset, set_additional_cmd, clear = +local reset, set_additional_cmd, clear, exc_exec = shada_helpers.reset, shada_helpers.set_additional_cmd, - shada_helpers.clear + shada_helpers.clear, shada_helpers.exc_exec local nvim_current_line = function() return nvim_window('get_cursor', nvim_curwin())[1] @@ -57,13 +57,7 @@ describe('ShaDa support code', function() nvim_command('wshada') reset() nvim_command('language C') - nvim_command([[ - try - execute "normal! `A" - catch - let exception = v:exception - endtry]]) - eq('Vim(normal):E20: Mark not set', nvim('get_var', 'exception')) + eq('Vim(normal):E20: Mark not set', exc_exec('normal! `A')) end) it('does read back global mark even with `\'0` and `f0` in shada', function() -- cgit From 38b8eb35610f154ef6d90b0e9be693f832dda0a9 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 26 Jul 2015 19:29:49 +0300 Subject: scripts: Also print entry offset in shadacat.py --- scripts/shadacat.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/shadacat.py b/scripts/shadacat.py index c710d0ca95..d29000a5d9 100755 --- a/scripts/shadacat.py +++ b/scripts/shadacat.py @@ -59,9 +59,10 @@ def mnormalize(o): with open(sys.argv[1], 'rb') as fp: - unpacker = msgpack.Unpacker(file_like=fp) + unpacker = msgpack.Unpacker(file_like=fp, read_size=1) while True: try: + pos = fp.tell() typ = EntryTypes(unpacker.unpack()) except msgpack.OutOfData: break @@ -70,5 +71,5 @@ with open(sys.argv[1], 'rb') as fp: time = datetime.fromtimestamp(timestamp) length = unpacker.unpack() entry = unpacker.unpack() - print('{0:13} {1} {2:5} {3!r}'.format( - typ.name, time.isoformat(), length, mnormalize(entry))) + print('{0:4} {1:13} {2} {3:5} {4!r}'.format( + pos, typ.name, time.isoformat(), length, mnormalize(entry))) -- cgit From f8169ff24db20d9420eae0c6044da86fa09378a9 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 26 Jul 2015 21:02:56 +0300 Subject: documentation: Document ShaDa compatibility features --- runtime/doc/starting.txt | 35 ++++++++++++++++++++++++++++++++++- runtime/doc/vim_diff.txt | 2 ++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index 68f4ff2e64..7d38fb68a4 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -987,7 +987,8 @@ For a bash-like shell: > Use the "r" flag in 'shada' to specify for which files no marks should be remembered. - *shada-merging* +MERGING *shada-merging* + {Nvim} When writing ShaDa files with |:wshada| without bang or at regular exit information in the existing ShaDa file is merged with information from current NeoVim instance. For this purpose ShaDa files store timestamps associated @@ -1016,6 +1017,38 @@ with ShaDa entries. Specifically the following is being done: fashion: the only header and buffer list present are the ones from the NeoVim instance which was last writing the file. |shada-%| +COMPATIBILITY *shada-compatibility* + {Nvim} +ShaDa files are forward and backward compatible. This means that + +1. Entries which have unknown type (i.e. that hold unidentified data) are + ignored when reading and blindly copied when writing. +2. Register entries with unknown register name are ignored when reading and + blindly copied when writing. |registers| +3. Register entries with unknown register type are ignored when reading and + merged as usual when writing. |getregtype()| +4. Local and global mark entries with unknown mark names are ignored when + reading. When writing global mark entries are blindly copied and local mark + entries are also blindly copied, but only if file they are attached to fits + in the |shada-'| limit. Unknown local mark entry's timestamp is also taken + into account when calculating which files exactly should fit into this + limit. |mark-motions| +5. History entries with unknown history type are ignored when reading and + blindly copied when writing. |history| +6. Unknown keys found in register, local mark, global mark, change, jump and + search pattern entries are saved internally and dumped when writing. + Entries created during NeoVim session never have such additions. +7. Additional elements found in replacement string and history entries are + saved internally and dumped. Entries created during NeoVim session never + have such additions. +8. Additional elements found in variable entries are simply ignored when + reading. When writing new variables they will be preserved during merging, + but that's all. Variable values dumped from current NeoVim session never + have additional elements, even if variables themselves were obtained by + reading ShaDa files. + +"Blindly" here means that there will be no attempts to somehow merge them, +even if other entries (with known name/type/etc) are merged. |shada-merging| SHADA FILE NAME *shada-file-name* diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 94996477ab..d5ff27d8c5 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -107,6 +107,8 @@ Additional differences: are still kept. - When writing (|:wshada| without bang or at exit) it merges much more data. Vim merges only marks. +- ShaDa file format was designed with forward and backward compatibility in + mind. |shada-compatibility| ============================================================================== 4. New Features *nvim-features-new* -- cgit From 56174572bc70947ab1e9e6ef48112272682ad6d8 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 2 Aug 2015 02:49:44 +0300 Subject: shada,documentation: Extend read error handling, handle write errors Modifications: - If file was not written due to write error then writing stops and temporary file will not be renamed. - If NeoVim detects that target file is not a ShaDa file then temporary file will not be renamed. --- runtime/doc/starting.txt | 19 +- src/nvim/shada.c | 375 ++++++++++++++++++++++++---------- test/functional/shada/errors_spec.lua | 20 +- 3 files changed, 293 insertions(+), 121 deletions(-) diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index 7d38fb68a4..a3af862ad6 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -896,7 +896,7 @@ To automatically save and restore views for *.c files: > au BufWinEnter *.c silent loadview ============================================================================== -8. The ShaDa file *shada* *shada-file* *E575* +8. The ShaDa file *shada* *shada-file* *E575* *E576* If you exit Vim and later start it again, you would normally lose a lot of information. The ShaDa file can be used to remember that information, which enables you to continue where you left off. @@ -1110,8 +1110,19 @@ that file. This was done to avoid accidentally destroying a file when the file name of the ShaDa file is wrong. This could happen when accidentally typing "nvim -i file" when you wanted "nvim -R file" (yes, somebody accidentally did that!). If you want to overwrite a ShaDa file with an error -in it, you will either have to fix the error, or delete the file (while NeoVim -is running, so most of the information will be restored). +in it, you will either have to fix the error, delete the file (while NeoVim is +running, so most of the information will be restored) or write it explicitly +with |:wshada| and a bang. + *E136* *E138* +Note: when NeoVim finds out that it failed to write part of the ShaDa file +(e.g. because there is no space left to write the file) or when it appears +that already present ShaDa file contains errors that indicate that this file +is likely not a ShaDa file then ShaDa file with `.tmp.X` suffix is left on the +file system (where X is any latin small letter: from U+0061 to U+007A). You +may use such file to recover the data if you want, but in any case it needs to +be cleaned up after you resolve the issue that prevented old ShaDa file from +being overwritten. If NeoVim fails to find unexisting `.tmp.X` file it will +not write ShaDa file at all. *:rsh* *:rshada* *E886* :rsh[ada][!] [file] Read from ShaDa file [file] (default: see above). @@ -1122,7 +1133,7 @@ is running, so most of the information will be restored). *:rv* *:rviminfo* :rv[iminfo][!] [file] Deprecated alias to |:rshada| command. - *:wsh* *:wshada* *E136* *E137* *E138* + *:wsh* *:wshada* *E137* :wsh[ada][!] [file] Write to ShaDa file [file] (default: see above). The information in the file is first read in to make a merge between old and new info. When [!] is used, diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 3672e2db25..8830653f48 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -156,7 +156,8 @@ KHASH_SET_INIT_STR(strset) // 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! -// E136: Can't rename ShaDa file from %s to %s! +// 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) @@ -167,9 +168,17 @@ KHASH_SET_INIT_STR(strset) /// 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 enum { kShaDaWantInfo = 1, ///< Load non-mark information @@ -203,6 +212,25 @@ typedef enum { #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 @@ -1115,8 +1143,25 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) set_vim_var_list(VV_OLDFILES, oldfiles_list); } } - while (shada_read_next_item(sd_reader, &cur_entry, srni_flags, 0) - == NOTDONE) { + 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); @@ -1424,6 +1469,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) } } } +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 @@ -1520,14 +1566,11 @@ static char *shada_filename(const char *file) /// @param[in] entry Entry written. /// @param[in] max_kbyte Maximum size of an item in KiB. Zero means no /// restrictions. -static void shada_pack_entry(msgpack_packer *const packer, +static bool shada_pack_entry(msgpack_packer *const packer, const ShadaEntry entry, const size_t max_kbyte) FUNC_ATTR_NONNULL_ALL { - if (entry.type == kSDItemMissing) { - return; - } msgpack_sbuffer sbuf; msgpack_sbuffer_init(&sbuf); msgpack_packer *spacker = msgpack_packer_new(&sbuf, &msgpack_sbuffer_write); @@ -1536,9 +1579,13 @@ static void shada_pack_entry(msgpack_packer *const packer, assert(false); } case kSDItemUnknown: { - msgpack_pack_uint64(packer, (uint64_t) entry.data.unknown_item.size); - packer->callback(packer->data, entry.data.unknown_item.contents, - (unsigned) entry.data.unknown_item.size); + if ((msgpack_pack_uint64(packer, (uint64_t) entry.data.unknown_item.size) + == -1) + || (packer->callback(packer->data, entry.data.unknown_item.contents, + (unsigned) entry.data.unknown_item.size) + == -1)) { + return false; + } break; } case kSDItemHistoryEntry: { @@ -1782,18 +1829,29 @@ static void shada_pack_entry(msgpack_packer *const packer, } if (!max_kbyte || sbuf.size <= max_kbyte * 1024) { if (entry.type == kSDItemUnknown) { - msgpack_pack_uint64(packer, (uint64_t) entry.data.unknown_item.type); + if (msgpack_pack_uint64(packer, (uint64_t) entry.data.unknown_item.type) + == -1) { + return false; + } } else { - msgpack_pack_uint64(packer, (uint64_t) entry.type); + if (msgpack_pack_uint64(packer, (uint64_t) entry.type) == -1) { + return false; + } + } + if (msgpack_pack_uint64(packer, (uint64_t) entry.timestamp) == -1) { + return false; } - msgpack_pack_uint64(packer, (uint64_t) entry.timestamp); if (sbuf.size > 0) { - msgpack_pack_uint64(packer, (uint64_t) sbuf.size); - packer->callback(packer->data, sbuf.data, (unsigned) sbuf.size); + if ((msgpack_pack_uint64(packer, (uint64_t) sbuf.size) == -1) + || (packer->callback(packer->data, sbuf.data, + (unsigned) sbuf.size) == -1)) { + return false; + } } } msgpack_packer_free(spacker); msgpack_sbuffer_destroy(&sbuf); + return true; } /// Write single ShaDa entry, converting it if needed @@ -1807,16 +1865,17 @@ static void shada_pack_entry(msgpack_packer *const packer, /// is assumed that entry was already converted. /// @param[in] max_kbyte Maximum size of an item in KiB. Zero means no /// restrictions. -static void shada_pack_encoded_entry(msgpack_packer *const packer, +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) { - shada_pack_entry(packer, entry.data, max_kbyte); + ret = shada_pack_entry(packer, entry.data, max_kbyte); shada_free_shada_entry(&entry.data); - return; + return ret; } #define RUN_WITH_CONVERTED_STRING(cstr, code) \ do { \ @@ -1840,19 +1899,19 @@ static void shada_pack_encoded_entry(msgpack_packer *const packer, } case kSDItemSearchPattern: { RUN_WITH_CONVERTED_STRING(entry.data.data.search_pattern.pat, { - shada_pack_entry(packer, entry.data, max_kbyte); + ret = shada_pack_entry(packer, entry.data, max_kbyte); }); break; } case kSDItemHistoryEntry: { RUN_WITH_CONVERTED_STRING(entry.data.data.history_item.string, { - shada_pack_entry(packer, entry.data, max_kbyte); + ret = shada_pack_entry(packer, entry.data, max_kbyte); }); break; } case kSDItemSubString: { RUN_WITH_CONVERTED_STRING(entry.data.data.sub_string.sub, { - shada_pack_entry(packer, entry.data, max_kbyte); + ret = shada_pack_entry(packer, entry.data, max_kbyte); }); break; } @@ -1860,7 +1919,7 @@ static void shada_pack_encoded_entry(msgpack_packer *const packer, if (sd_conv->vc_type != CONV_NONE) { convert_object(sd_conv, &entry.data.data.global_var.value); } - shada_pack_entry(packer, entry.data, max_kbyte); + ret = shada_pack_entry(packer, entry.data, max_kbyte); break; } case kSDItemRegister: { @@ -1892,7 +1951,7 @@ static void shada_pack_encoded_entry(msgpack_packer *const packer, } } } - shada_pack_entry(packer, entry.data, max_kbyte); + 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]); @@ -1907,11 +1966,12 @@ static void shada_pack_encoded_entry(msgpack_packer *const packer, case kSDItemBufferList: case kSDItemLocalMark: case kSDItemChange: { - shada_pack_entry(packer, entry.data, max_kbyte); + 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 @@ -1936,16 +1996,17 @@ static int compare_file_marks(const void *a, const void *b) /// @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 void shada_write(ShaDaWriteDef *const sd_writer, - ShaDaReadDef *const sd_reader) +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; + return ret; } WriteMergerState *const wms = xcalloc(1, sizeof(*wms)); @@ -1999,7 +2060,7 @@ static void shada_write(ShaDaWriteDef *const sd_writer, } // Write header - shada_pack_entry(packer, (ShadaEntry) { + if (!shada_pack_entry(packer, (ShadaEntry) { .type = kSDItemHeader, .timestamp = os_time(), .data = { @@ -2018,7 +2079,10 @@ static void shada_write(ShaDaWriteDef *const sd_writer, }), } } - }, 0); + }, 0)) { + ret = kSDWriteFailed; + goto shada_write_exit; + } // Write buffer list if (find_shada_parameter('%') != NULL) { @@ -2052,7 +2116,11 @@ static void shada_write(ShaDaWriteDef *const sd_writer, }; i++; } - shada_pack_entry(packer, buflist_entry, 0); + 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); } @@ -2071,7 +2139,7 @@ static void shada_write(ShaDaWriteDef *const sd_writer, if (sd_writer->sd_conv.vc_type != CONV_NONE) { convert_object(&sd_writer->sd_conv, &obj); } - shada_pack_entry(packer, (ShadaEntry) { + if (!shada_pack_entry(packer, (ShadaEntry) { .type = kSDItemVariable, .timestamp = cur_timestamp, .data = { @@ -2081,7 +2149,12 @@ static void shada_write(ShaDaWriteDef *const sd_writer, .additional_elements = NULL, } } - }, max_kbyte); + }, max_kbyte)) { + api_free_object(obj); + clear_tv(&vartv); + ret = kSDWriteFailed; + goto shada_write_exit; + } api_free_object(obj); clear_tv(&vartv); int kh_ret; @@ -2344,12 +2417,33 @@ static void shada_write(ShaDaWriteDef *const sd_writer, } if (sd_reader == NULL) { - goto shada_write_remaining; + goto shada_write_main_cycle_end; } ShadaEntry entry; - while (shada_read_next_item(sd_reader, &entry, srni_flags, max_kbyte) - == NOTDONE) { + 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: { + goto shada_write_main_cycle_end; + } + case kSDReadStatusMalformed: { + continue; + } + } #define COMPARE_WITH_ENTRY(wms_entry_, entry) \ do { \ PossiblyFreedShadaEntry *const wms_entry = (wms_entry_); \ @@ -2374,7 +2468,9 @@ static void shada_write(ShaDaWriteDef *const sd_writer, assert(false); } case kSDItemUnknown: { - shada_pack_entry(packer, entry, 0); + if (!shada_pack_entry(packer, entry, 0)) { + ret = kSDWriteFailed; + } shada_free_shada_entry(&entry); break; } @@ -2390,7 +2486,9 @@ static void shada_write(ShaDaWriteDef *const sd_writer, } case kSDItemHistoryEntry: { if (entry.data.history_item.histtype >= HIST_COUNT) { - shada_pack_entry(packer, entry, 0); + if (!shada_pack_entry(packer, entry, 0)) { + ret = kSDWriteFailed; + } shada_free_shada_entry(&entry); break; } @@ -2401,7 +2499,9 @@ static void shada_write(ShaDaWriteDef *const sd_writer, case kSDItemRegister: { const int idx = op_reg_index(entry.data.reg.name); if (idx < 0) { - shada_pack_entry(packer, entry, 0); + if (!shada_pack_entry(packer, entry, 0)) { + ret = kSDWriteFailed; + } shada_free_shada_entry(&entry); break; } @@ -2410,7 +2510,9 @@ static void shada_write(ShaDaWriteDef *const sd_writer, } case kSDItemVariable: { if (!in_strset(&wms->dumped_variables, entry.data.global_var.name)) { - shada_pack_entry(packer, entry, 0); + if (!shada_pack_entry(packer, entry, 0)) { + ret = kSDWriteFailed; + } } shada_free_shada_entry(&entry); break; @@ -2418,7 +2520,9 @@ static void shada_write(ShaDaWriteDef *const sd_writer, case kSDItemGlobalMark: { const int idx = mark_global_index(entry.data.filemark.name); if (idx < 0) { - shada_pack_entry(packer, entry, 0); + if (!shada_pack_entry(packer, entry, 0)) { + ret = kSDWriteFailed; + } shada_free_shada_entry(&entry); break; } @@ -2556,27 +2660,37 @@ static void shada_write(ShaDaWriteDef *const sd_writer, #undef COMPARE_WITH_ENTRY // Write the rest -shada_write_remaining: +shada_write_main_cycle_end: #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) { \ - shada_pack_encoded_entry(packer, &sd_writer->sd_conv, wms_array[i_], \ - max_kbyte); \ + 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++) { - shada_pack_encoded_entry(packer, &sd_writer->sd_conv, wms->jumps[i], - max_kbyte); + 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) { \ - shada_pack_encoded_entry(packer, &sd_writer->sd_conv, wms_entry, \ - max_kbyte); \ + 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); @@ -2602,11 +2716,20 @@ shada_write_remaining: 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++) { - shada_pack_encoded_entry(packer, &sd_writer->sd_conv, - all_file_markss[i]->changes[j], max_kbyte); + 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++) { - shada_pack_entry(packer, all_file_markss[i]->additional_marks[j], 0); + 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); @@ -2619,23 +2742,31 @@ shada_write_remaining: if (dump_one_history[i]) { hms_insert_whole_neovim_history(&wms->hms[i]); HMS_ITER(&wms->hms[i], cur_entry) { - shada_pack_encoded_entry(packer, &sd_writer->sd_conv, - (PossiblyFreedShadaEntry) { - .data = cur_entry->data, - .can_free_entry = - cur_entry->can_free_entry, - }, max_kbyte); + 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_destroy(bufset, removable_bufs); msgpack_packer_free(packer); kh_dealloc(strset, &wms->dumped_variables); xfree(wms); + return ret; } #undef PACK_STATIC_STR @@ -2661,7 +2792,6 @@ int shada_write_file(const char *const file, bool nomerge) intptr_t fd; if (!nomerge) { - // TODO(ZyX-I): Fail on read error. if (open_shada_file_for_reading(fname, &sd_reader) != 0) { nomerge = true; goto shada_write_file_nomerge; @@ -2764,16 +2894,28 @@ shada_write_file_nomerge: {} convert_setup(&sd_writer.sd_conv, p_enc, "utf-8"); - shada_write(&sd_writer, (nomerge ? NULL : &sd_reader)); + const ShaDaWriteResult sw_ret = shada_write(&sd_writer, (nomerge + ? NULL + : &sd_reader)); sd_writer.close(&sd_writer); if (!nomerge) { sd_reader.close(&sd_reader); - if (vim_rename(tempname, fname) == -1) { - EMSG3(_("E136: Can't rename ShaDa file from %s to %s!"), - tempname, fname); + if (sw_ret == kSDWriteSuccessfull) { + if (vim_rename(tempname, fname) == -1) { + EMSG3(_(RNERR "Can't rename ShaDa file from %s to %s!"), + tempname, fname); + } else { + os_remove(tempname); + } } else { - os_remove(tempname); + 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); + } } xfree(tempname); } @@ -2918,9 +3060,12 @@ static inline uint64_t be64toh(uint64_t big_endian_64_bits) /// @param[out] buffer Where to save the results. May be NULL. /// @param[in] length How many bytes should be read. /// -/// @return FAIL if reading was not successfull, OK otherwise. -static int fread_len(ShaDaReadDef *const sd_reader, char *const buffer, - const size_t length) +/// @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_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT { ptrdiff_t read_bytes = 0; @@ -2942,16 +3087,16 @@ static int fread_len(ShaDaReadDef *const sd_reader, char *const buffer, if (sd_reader->error != NULL) { emsg2(_(SERR "System error while reading ShaDa file: %s"), sd_reader->error); - return FAIL; + return kSDReadStatusReadError; } else if (sd_reader->eof) { - emsgu(_(RERR "Error while reading ShaDa file: " + emsgu(_(RCERR "Error while reading ShaDa file: " "last entry specified that it occupies %" PRIu64 " bytes, " "but file ended earlier"), (uint64_t) length); - return FAIL; + return kSDReadStatusNotShaDa; } assert(read_bytes >= 0 && (size_t) read_bytes == length); - return OK; + return kSDReadStatusSuccess; } /// Read next unsigned integer from file @@ -2967,10 +3112,12 @@ static int fread_len(ShaDaReadDef *const sd_reader, char *const buffer, /// @param[in] sd_reader Structure containing file reader definition. /// @param[out] result Location where result is saved. /// -/// @return OK if read was successfull, FAIL if it was not. -static int msgpack_read_uint64(ShaDaReadDef *const sd_reader, - const int first_char, - uint64_t *const result) +/// @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; @@ -2979,13 +3126,14 @@ static int msgpack_read_uint64(ShaDaReadDef *const sd_reader, 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(_(RERR "Error while reading ShaDa file: " + emsgu(_(RCERR "Error while reading ShaDa file: " "expected positive integer at position %" PRIu64 ", but got nothing"), (uint64_t) fpos); + return kSDReadStatusNotShaDa; } - return FAIL; } if (~first_char & 0x80) { @@ -3011,20 +3159,22 @@ static int msgpack_read_uint64(ShaDaReadDef *const sd_reader, break; } default: { - emsgu(_(RERR "Error while reading ShaDa file: " + emsgu(_(RCERR "Error while reading ShaDa file: " "expected positive integer at position %" PRIu64), (uint64_t) fpos); - return FAIL; + return kSDReadStatusNotShaDa; } } uint8_t buf[sizeof(uint64_t)] = {0, 0, 0, 0, 0, 0, 0, 0}; - if (fread_len(sd_reader, (char *) &(buf[sizeof(uint64_t)-length]), length) - != OK) { - return FAIL; + ShaDaReadResult fl_ret; + if ((fl_ret = fread_len(sd_reader, (char *) &(buf[sizeof(uint64_t)-length]), + length)) + != kSDReadStatusSuccess) { + return fl_ret; } *result = be64toh(*((uint64_t *) &(buf[0]))); } - return OK; + return kSDReadStatusSuccess; } /// Convert all strings in one Object instance @@ -3129,14 +3279,14 @@ static inline char *get_converted_string(const vimconv_T *const sd_conv, /// @param[in] max_kbyte If non-zero, skip reading entries which have length /// greater then given. /// -/// @return NOTDONE if entry was read correctly, FAIL if there were errors and -/// OK at EOF. -static int shada_read_next_item(ShaDaReadDef *const sd_reader, - ShadaEntry *const entry, - const unsigned flags, - const size_t max_kbyte) +/// @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 @@ -3144,7 +3294,7 @@ shada_read_next_item_start: // the switch. memset(entry, 0, sizeof(*entry)); if (sd_reader->eof) { - return OK; + return kSDReadStatusFinished; } // First: manually unpack type, timestamp and length. @@ -3156,15 +3306,19 @@ shada_read_next_item_start: const uintmax_t initial_fpos = sd_reader->fpos; const int first_char = read_char(sd_reader); if (first_char == EOF && sd_reader->eof) { - return OK; + return kSDReadStatusFinished; } - if (msgpack_read_uint64(sd_reader, first_char, &type_u64) != OK - || (msgpack_read_uint64(sd_reader, read_char(sd_reader), ×tamp_u64) - != OK) - || (msgpack_read_uint64(sd_reader, read_char(sd_reader), &length_u64) - != OK)) { - return FAIL; + 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), + ×tamp_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; @@ -3174,20 +3328,21 @@ shada_read_next_item_start: // 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(_(RERR "Error while reading ShaDa file: " + 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"), (uint64_t) initial_fpos); - return FAIL; + return kSDReadStatusNotShaDa; } if ((type_u64 > SHADA_LAST_ENTRY ? !(flags & kSDReadUnknown) : !((unsigned) (1 << type_u64) & flags)) || (max_kbyte && length > max_kbyte * 1024)) { - if (fread_len(sd_reader, NULL, length) != OK) { - return FAIL; + const ShaDaReadResult fl_ret = fread_len(sd_reader, NULL, length); + if (fl_ret != kSDReadStatusSuccess) { + return fl_ret; } goto shada_read_next_item_start; } @@ -3202,9 +3357,12 @@ shada_read_next_item_start: char *const buf = xmalloc(length); - if (fread_len(sd_reader, buf, length) != OK) { - xfree(buf); - return FAIL; + { + const ShaDaReadResult fl_ret = fread_len(sd_reader, buf, length); + if (fl_ret != kSDReadStatusSuccess) { + xfree(buf); + return fl_ret; + } } msgpack_unpacked unpacked; @@ -3215,6 +3373,7 @@ shada_read_next_item_read_next: {} size_t off = 0; const msgpack_unpack_return result = msgpack_unpack_next(&unpacked, buf, length, &off); + ret = kSDReadStatusNotShaDa; switch (result) { case MSGPACK_UNPACK_SUCCESS: { if (off < length) { @@ -3223,7 +3382,7 @@ shada_read_next_item_read_next: {} break; } case MSGPACK_UNPACK_PARSE_ERROR: { - emsgu(_(RERR "Failed to parse ShaDa file due to a msgpack parser error " + emsgu(_(RCERR "Failed to parse ShaDa file due to a msgpack parser error " "at position %" PRIu64), (uint64_t) initial_fpos); goto shada_read_next_item_error; @@ -3235,22 +3394,24 @@ shada_read_next_item_read_next: {} goto shada_read_next_item_read_next; } EMSG(_(e_outofmem)); + ret = kSDReadStatusReadError; goto shada_read_next_item_error; } case MSGPACK_UNPACK_CONTINUE: { - emsgu(_(RERR "Failed to parse ShaDa file: incomplete msgpack string " + emsgu(_(RCERR "Failed to parse ShaDa file: incomplete msgpack string " "at position %" PRIu64), (uint64_t) initial_fpos); goto shada_read_next_item_error; } case MSGPACK_UNPACK_EXTRA_BYTES: { shada_read_next_item_extra_bytes: - emsgu(_(RERR "Failed to parse ShaDa file: extra bytes in msgpack string " + emsgu(_(RCERR "Failed to parse ShaDa file: extra bytes in msgpack string " "at position %" PRIu64), (uint64_t) initial_fpos); goto shada_read_next_item_error; } } + ret = kSDReadStatusMalformed; #define CHECK_KEY(key, expected) \ (key.via.str.size == sizeof(expected) - 1 \ && STRNCMP(key.via.str.ptr, expected, sizeof(expected) - 1) == 0) @@ -3977,11 +4138,11 @@ shada_read_next_item_error: entry->type = (ShadaEntryType) type_u64; shada_free_shada_entry(entry); entry->type = kSDItemMissing; - return FAIL; + return ret; shada_read_next_item_end: msgpack_unpacked_destroy(&unpacked); xfree(buf); - return NOTDONE; + return kSDReadStatusSuccess; } /// Check whether "name" is on removable media (according to 'shada') diff --git a/test/functional/shada/errors_spec.lua b/test/functional/shada/errors_spec.lua index 5107273115..4ae7847cc7 100644 --- a/test/functional/shada/errors_spec.lua +++ b/test/functional/shada/errors_spec.lua @@ -44,17 +44,17 @@ describe('ShaDa error handling', function() it('fails on zero', function() wshada('\000') - eq('Vim(rshada):E575: Error while reading ShaDa file: expected positive integer at position 0, but got nothing', exc_exec(sdrcmd())) + eq('Vim(rshada):E576: Error while reading ShaDa file: expected positive integer at position 0, but got nothing', exc_exec(sdrcmd())) end) it('fails on missing item', function() wshada('\000\000\000') - eq('Vim(rshada):E575: Error while reading ShaDa file: there is an item at position 0 that must not be there: Missing items are for internal uses only', exc_exec(sdrcmd())) + eq('Vim(rshada):E576: Error while reading ShaDa file: there is an item at position 0 that must not be there: Missing items are for internal uses only', exc_exec(sdrcmd())) end) it('fails on -2 type', function() wshada('\254\000\000') - eq('Vim(rshada):E575: Error while reading ShaDa file: expected positive integer at position 0', exc_exec(sdrcmd())) + eq('Vim(rshada):E576: Error while reading ShaDa file: expected positive integer at position 0', exc_exec(sdrcmd())) end) it('does not fail on header with zero length', function() @@ -65,22 +65,22 @@ describe('ShaDa error handling', function() it('fails on search pattern item with zero length', function() wshada('\002\000\000') - eq('Vim(rshada):E575: Failed to parse ShaDa file: incomplete msgpack string at position 0', exc_exec(sdrcmd())) + eq('Vim(rshada):E576: Failed to parse ShaDa file: incomplete msgpack string at position 0', exc_exec(sdrcmd())) end) it('fails on search pattern item with -2 timestamp', function() wshada('\002\254\000') - eq('Vim(rshada):E575: Error while reading ShaDa file: expected positive integer at position 1', exc_exec(sdrcmd())) + eq('Vim(rshada):E576: Error while reading ShaDa file: expected positive integer at position 1', exc_exec(sdrcmd())) end) it('fails on search pattern item with -2 length', function() wshada('\002\000\254') - eq('Vim(rshada):E575: Error while reading ShaDa file: expected positive integer at position 2', exc_exec(sdrcmd())) + eq('Vim(rshada):E576: Error while reading ShaDa file: expected positive integer at position 2', exc_exec(sdrcmd())) end) it('fails on search pattern item with length greater then file length', function() wshada('\002\000\002\000') - eq('Vim(rshada):E575: Error while reading ShaDa file: last entry specified that it occupies 2 bytes, but file ended earlier', exc_exec(sdrcmd())) + eq('Vim(rshada):E576: Error while reading ShaDa file: last entry specified that it occupies 2 bytes, but file ended earlier', exc_exec(sdrcmd())) end) it('fails on search pattern item with invalid byte', function() @@ -95,12 +95,12 @@ describe('ShaDa error handling', function() -- get MSGPACK_UNPACK_PARSE_ERROR and not MSGPACK_UNPACK_CONTINUE or -- MSGPACK_UNPACK_EXTRA_BYTES. wshada('\002\000\001\193') - eq('Vim(rshada):E575: Failed to parse ShaDa file due to a msgpack parser error at position 0', exc_exec(sdrcmd())) + eq('Vim(rshada):E576: Failed to parse ShaDa file due to a msgpack parser error at position 0', exc_exec(sdrcmd())) end) it('fails on search pattern item with incomplete map', function() wshada('\002\000\001\129') - eq('Vim(rshada):E575: Failed to parse ShaDa file: incomplete msgpack string at position 0', exc_exec(sdrcmd())) + eq('Vim(rshada):E576: Failed to parse ShaDa file: incomplete msgpack string at position 0', exc_exec(sdrcmd())) end) it('fails on search pattern item without a pattern', function() @@ -110,7 +110,7 @@ describe('ShaDa error handling', function() it('fails on search pattern with extra bytes', function() wshada('\002\000\002\128\000') - eq('Vim(rshada):E575: Failed to parse ShaDa file: extra bytes in msgpack string at position 0', exc_exec(sdrcmd())) + eq('Vim(rshada):E576: Failed to parse ShaDa file: extra bytes in msgpack string at position 0', exc_exec(sdrcmd())) end) it('fails on search pattern item with NIL value', function() -- cgit From 07d9ab26c6526c0d9af0435f1adeb614a1b88743 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 3 Aug 2015 23:06:06 +0300 Subject: *: Make ShaDa code use VimL values for additional_\* data --- src/nvim/buffer.c | 6 +- src/nvim/buffer_defs.h | 4 +- src/nvim/eval.c | 144 +++++++++----- src/nvim/eval.h | 4 + src/nvim/eval_defs.h | 13 ++ src/nvim/ex_cmds.c | 7 +- src/nvim/ex_cmds.h | 4 +- src/nvim/ex_getln.c | 7 +- src/nvim/ex_getln.h | 3 +- src/nvim/mark.c | 9 +- src/nvim/mark_defs.h | 4 +- src/nvim/ops.c | 10 +- src/nvim/ops.h | 4 +- src/nvim/search.c | 6 +- src/nvim/search.h | 2 +- src/nvim/shada.c | 530 ++++++++++++++++--------------------------------- src/nvim/undo.c | 8 +- 17 files changed, 305 insertions(+), 460 deletions(-) diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 53612977ee..a25a51c6ce 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -30,7 +30,6 @@ #include #include "nvim/api/private/handle.h" -#include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/vim.h" #include "nvim/buffer.h" @@ -557,10 +556,7 @@ static void free_buffer(buf_T *buf) free_buffer_stuff(buf, TRUE); unref_var_dict(buf->b_vars); aubuflocal_remove(buf); - if (buf->additional_data != NULL) { - api_free_dictionary(*buf->additional_data); - xfree(buf->additional_data); - } + dict_unref(buf->additional_data); free_fmark(buf->b_last_cursor); free_fmark(buf->b_last_insert); free_fmark(buf->b_last_change); diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 2be5386de9..f71575deaf 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -26,7 +26,7 @@ typedef struct file_buffer buf_T; // Forward declaration #include "nvim/eval_defs.h" // for proftime_T #include "nvim/profile.h" -// for String and Dictionary +// for String #include "nvim/api/private/defs.h" #define MODIFIABLE(buf) (!buf->terminal && buf->b_p_ma) @@ -749,7 +749,7 @@ struct file_buffer { Terminal *terminal; // Terminal instance associated with the buffer - Dictionary *additional_data; // Additional data from shada file if any. + dict_T *additional_data; // Additional data from shada file if any. }; /* diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 5a7d4702d2..ad66c4c924 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -107,18 +107,6 @@ #define AUTOLOAD_CHAR '#' /* Character used as separator in autoload 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: @@ -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; } @@ -5964,13 +5959,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 +5992,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 +6318,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 +6556,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 +6752,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 +6782,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 +8357,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 +8526,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)); } } @@ -12486,7 +12501,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 +12607,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) \ @@ -18446,14 +18461,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 +18496,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; @@ -18481,7 +18523,7 @@ static int item_copy(typval_T *from, typval_T *to, int deep, int copyID) 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); + to->vval.v_list = list_copy(conv, from->vval.v_list, deep, copyID); if (to->vval.v_list == NULL) ret = FAIL; break; @@ -18495,12 +18537,12 @@ static int item_copy(typval_T *from, typval_T *to, int deep, int copyID) 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); + 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; 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 + #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 +#include #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 262150a8c0..5db3880026 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -68,8 +68,6 @@ #include "nvim/os/shell.h" #include "nvim/os/input.h" #include "nvim/os/time.h" -#include "nvim/api/private/defs.h" -#include "nvim/api/private/helpers.h" /* * Struct to hold the sign properties. @@ -2846,10 +2844,7 @@ void sub_set_replacement(SubReplacementString sub) { xfree(old_sub.sub); if (sub.additional_elements != old_sub.additional_elements) { - if (old_sub.additional_elements != NULL) { - api_free_array(*old_sub.additional_elements); - xfree(old_sub.additional_elements); - } + list_unref(old_sub.additional_elements); } old_sub = sub; } diff --git a/src/nvim/ex_cmds.h b/src/nvim/ex_cmds.h index 71fc100e41..721145efd8 100644 --- a/src/nvim/ex_cmds.h +++ b/src/nvim/ex_cmds.h @@ -4,7 +4,7 @@ #include #include "nvim/os/time.h" -#include "nvim/api/private/defs.h" +#include "nvim/eval_defs.h" /* flags for do_ecmd() */ #define ECMD_HIDE 0x01 /* don't free the current buffer */ @@ -23,7 +23,7 @@ typedef struct { char *sub; ///< Previous replacement string. Timestamp timestamp; ///< Time when it was last set. - Array *additional_elements; ///< Additional data left from ShaDa file. + list_T *additional_elements; ///< Additional data left from ShaDa file. } SubReplacementString; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 38f84f2e7f..7be8d722b8 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -66,8 +66,6 @@ #include "nvim/os/os.h" #include "nvim/event/loop.h" #include "nvim/os/time.h" -#include "nvim/api/private/defs.h" -#include "nvim/api/private/helpers.h" /* * Variables shared between getcmdline(), redrawcmdline() and others. @@ -4252,10 +4250,7 @@ static inline void hist_free_entry(histentry_T *hisptr) FUNC_ATTR_NONNULL_ALL { xfree(hisptr->hisstr); - if (hisptr->additional_elements != NULL) { - api_free_array(*hisptr->additional_elements); - xfree(hisptr->additional_elements); - } + list_unref(hisptr->additional_elements); clear_hist_entry(hisptr); } diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h index 738e515f21..3bfd6a8aac 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. */ @@ -40,7 +41,7 @@ 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. - Array *additional_elements; ///< Additional entries from ShaDa file. + list_T *additional_elements; ///< Additional entries from ShaDa file. } histentry_T; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/mark.c b/src/nvim/mark.c index e88cc11c39..beb8dd6679 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -41,8 +41,6 @@ #include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/os/input.h" -#include "nvim/api/private/defs.h" -#include "nvim/api/private/helpers.h" /* * This file contains routines to maintain and manipulate marks. @@ -74,11 +72,8 @@ int setmark(int c) /// Free fmark_T item void free_fmark(fmark_T fm) { - if (fm.additional_data != NULL) { - api_free_dictionary(*fm.additional_data); - free(fm.additional_data); - fm.additional_data = NULL; - } + dict_unref(fm.additional_data); + fm.additional_data = NULL; } /// Free xfmark_T item diff --git a/src/nvim/mark_defs.h b/src/nvim/mark_defs.h index af4727d634..8f8425f0a6 100644 --- a/src/nvim/mark_defs.h +++ b/src/nvim/mark_defs.h @@ -3,7 +3,7 @@ #include "nvim/pos.h" #include "nvim/os/time.h" -#include "nvim/api/private/defs.h" +#include "nvim/eval_defs.h" /* * marks: positions in a file @@ -36,7 +36,7 @@ typedef struct filemark { pos_T mark; ///< Cursor position. int fnum; ///< File number. Timestamp timestamp; ///< Time when this mark was last set. - Dictionary *additional_data; ///< Additional data from ShaDa file. + dict_T *additional_data; ///< Additional data from ShaDa file. } fmark_T; /// Structure defining extended mark (mark with file name attached) diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 0d7c319fba..9af37df8a0 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -51,8 +51,6 @@ #include "nvim/window.h" #include "nvim/os/input.h" #include "nvim/os/time.h" -#include "nvim/api/private/defs.h" -#include "nvim/api/private/helpers.h" static yankreg_T y_regs[NUM_REGISTERS]; @@ -869,17 +867,13 @@ int do_record(int c) return retval; } -static void set_yreg_additional_data(yankreg_T *reg, - Dictionary *additional_data) +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; } - if (reg->additional_data != NULL) { - api_free_dictionary(*reg->additional_data); - free(reg->additional_data); - } + dict_unref(reg->additional_data); reg->additional_data = additional_data; } diff --git a/src/nvim/ops.h b/src/nvim/ops.h index 5565f1631f..507f933acf 100644 --- a/src/nvim/ops.h +++ b/src/nvim/ops.h @@ -6,7 +6,7 @@ #include "nvim/macros.h" #include "nvim/ascii.h" #include "nvim/types.h" -#include "nvim/api/private/defs.h" +#include "nvim/eval_defs.h" #include "nvim/os/time.h" typedef int (*Indenter)(void); @@ -81,7 +81,7 @@ typedef struct yankreg { char_u y_type; ///< Register type: MLINE, MCHAR or MBLOCK. colnr_T y_width; ///< Register width (only valid for y_type == MBLOCK). Timestamp timestamp; ///< Time when register was last modified. - Dictionary *additional_data; ///< Additional data from ShaDa file. + dict_T *additional_data; ///< Additional data from ShaDa file. } yankreg_T; /// Convert register name into register index diff --git a/src/nvim/search.c b/src/nvim/search.c index f8dd7bd482..285f18e58f 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -51,7 +51,6 @@ #include "nvim/ui.h" #include "nvim/window.h" #include "nvim/os/time.h" -#include "nvim/api/private/helpers.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -292,10 +291,7 @@ void restore_search_patterns(void) static inline void free_spat(struct spat *const spat) { xfree(spat->pat); - if (spat->additional_data != NULL) { - api_free_dictionary(*spat->additional_data); - xfree(spat->additional_data); - } + dict_unref(spat->additional_data); } #if defined(EXITFREE) diff --git a/src/nvim/search.h b/src/nvim/search.h index 691782e41c..1fc2d6710e 100644 --- a/src/nvim/search.h +++ b/src/nvim/search.h @@ -60,7 +60,7 @@ typedef struct spat { bool no_scs; ///< No smartcase for this pattern. Timestamp timestamp; ///< Time of the last change. SearchOffset off; ///< Pattern offset. - Dictionary *additional_data; ///< Additional data from ShaDa file. + dict_T *additional_data; ///< Additional data from ShaDa file. } SearchPattern; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 8830653f48..684a6ea62d 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -287,7 +287,7 @@ typedef struct { char name; pos_T mark; char *fname; - Dictionary *additional_data; + dict_T *additional_data; } filemark; struct search_pattern { bool magic; @@ -299,14 +299,13 @@ typedef struct { bool is_substitute_pattern; bool highlighted; char *pat; - Dictionary *additional_data; + dict_T *additional_data; } search_pattern; struct history_item { uint8_t histtype; char *string; char sep; - bool canfree; - Array *additional_elements; + list_T *additional_elements; } history_item; struct reg { char name; @@ -314,12 +313,12 @@ typedef struct { char **contents; size_t contents_size; size_t width; - Dictionary *additional_data; + dict_T *additional_data; } reg; struct global_var { char *name; - Object value; - Array *additional_elements; + typval_T value; + list_T *additional_elements; } global_var; struct { uint64_t type; @@ -328,14 +327,14 @@ typedef struct { } unknown_item; struct sub_string { char *sub; - Array *additional_elements; + list_T *additional_elements; } sub_string; struct buffer_list { size_t size; struct buffer_list_buffer { pos_T pos; char *fname; - Dictionary *additional_data; + dict_T *additional_data; } *buffers; } buffer_list; } data; @@ -899,7 +898,6 @@ static const void *shada_hist_iter(const void *const iter, ? (char) hist_he.hisstr[STRLEN(hist_he.hisstr) + 1] : 0), .additional_elements = hist_he.additional_elements, - .canfree = zero, } } }; @@ -1254,17 +1252,9 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) break; } case kSDItemVariable: { - typval_T vartv; - Error err; - if (!object_to_vim(cur_entry.data.global_var.value, &vartv, &err)) { - if (err.set) { - emsg3(_(RERR "Error while reading ShaDa file: " - "failed to read value for variable %s: %s"), - cur_entry.data.global_var.name, err.msg); - } - break; - } - var_set_global(cur_entry.data.global_var.name, vartv); + 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; } @@ -1367,6 +1357,9 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) 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); @@ -1567,13 +1560,42 @@ static char *shada_filename(const char *file) /// @param[in] max_kbyte Maximum size of an item in KiB. Zero means no /// restrictions. static bool shada_pack_entry(msgpack_packer *const packer, - const ShadaEntry entry, + 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) { \ + return false; \ + } \ + } \ + } \ + } 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) { \ + return false; \ + } \ + } \ + } \ + } \ + } while (0) switch (entry.type) { case kSDItemMissing: { assert(false); @@ -1591,10 +1613,10 @@ static bool shada_pack_entry(msgpack_packer *const packer, 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 + ( + 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->size); + : entry.data.history_item.additional_elements->lv_len); msgpack_pack_array(spacker, arr_size); msgpack_pack_uint8(spacker, entry.data.history_item.histtype); msgpack_rpc_from_string(cstr_as_string(entry.data.history_item.string), @@ -1602,39 +1624,32 @@ static bool shada_pack_entry(msgpack_packer *const packer, if (is_hist_search) { msgpack_pack_uint8(spacker, (uint8_t) entry.data.history_item.sep); } - for (size_t i = 0; i < arr_size - 2 - (size_t) is_hist_search; i++) { - msgpack_rpc_from_object( - entry.data.history_item.additional_elements->items[i], spacker); - } + DUMP_ADDITIONAL_ELEMENTS(entry.data.history_item.additional_elements); break; } case kSDItemVariable: { - const size_t arr_size = 2 + ( + const size_t arr_size = 2 + (size_t) ( entry.data.global_var.additional_elements == NULL ? 0 - : entry.data.global_var.additional_elements->size); + : entry.data.global_var.additional_elements->lv_len); msgpack_pack_array(spacker, arr_size); msgpack_rpc_from_string(cstr_as_string(entry.data.global_var.name), spacker); - msgpack_rpc_from_object(entry.data.global_var.value, spacker); - for (size_t i = 0; i < arr_size - 2; i++) { - msgpack_rpc_from_object( - entry.data.global_var.additional_elements->items[i], spacker); + if (vim_to_msgpack(spacker, &entry.data.global_var.value) == FAIL) { + return false; } + DUMP_ADDITIONAL_ELEMENTS(entry.data.global_var.additional_elements); break; } case kSDItemSubString: { - const size_t arr_size = 1 + ( + const size_t arr_size = 1 + (size_t) ( entry.data.sub_string.additional_elements == NULL ? 0 - : entry.data.sub_string.additional_elements->size); + : entry.data.sub_string.additional_elements->lv_len); msgpack_pack_array(spacker, arr_size); msgpack_rpc_from_string(cstr_as_string(entry.data.sub_string.sub), spacker); - for (size_t i = 0; i < arr_size - 1; i++) { - msgpack_rpc_from_object( - entry.data.sub_string.additional_elements->items[i], spacker); - } + DUMP_ADDITIONAL_ELEMENTS(entry.data.sub_string.additional_elements); break; } case kSDItemSearchPattern: { @@ -1652,9 +1667,10 @@ static bool shada_pack_entry(msgpack_packer *const packer, // offset defaults to zero: + (size_t) (entry.data.search_pattern.offset != 0) // finally, additional data: - + (size_t) (entry.data.search_pattern.additional_data - ? entry.data.search_pattern.additional_data->size - : 0) + + (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); @@ -1679,16 +1695,7 @@ static bool shada_pack_entry(msgpack_packer *const packer, msgpack_pack_int64(spacker, entry.data.search_pattern.offset); } #undef PACK_BOOL - if (entry.data.search_pattern.additional_data != NULL) { - for (size_t i = 0; i < entry.data.search_pattern.additional_data->size; - i++) { - msgpack_rpc_from_string( - entry.data.search_pattern.additional_data->items[i].key, spacker); - msgpack_rpc_from_object( - entry.data.search_pattern.additional_data->items[i].value, - spacker); - } - } + DUMP_ADDITIONAL_DATA(entry.data.search_pattern.additional_data); break; } case kSDItemChange: @@ -1706,9 +1713,10 @@ static bool shada_pack_entry(msgpack_packer *const packer, && entry.type != kSDItemChange && entry.data.filemark.name != '"') // Additional entries, if any: - + (size_t) (entry.data.filemark.additional_data == NULL - ? 0 - : entry.data.filemark.additional_data->size) + + (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); @@ -1727,15 +1735,7 @@ static bool shada_pack_entry(msgpack_packer *const packer, PACK_STATIC_STR(KEY_NAME_CHAR); msgpack_pack_uint8(spacker, (uint8_t) entry.data.filemark.name); } - if (entry.data.filemark.additional_data != NULL) { - for (size_t i = 0; i < entry.data.filemark.additional_data->size; - i++) { - msgpack_rpc_from_string( - entry.data.filemark.additional_data->items[i].key, spacker); - msgpack_rpc_from_object( - entry.data.filemark.additional_data->items[i].value, spacker); - } - } + DUMP_ADDITIONAL_DATA(entry.data.filemark.additional_data); break; } case kSDItemRegister: { @@ -1748,7 +1748,7 @@ static bool shada_pack_entry(msgpack_packer *const packer, // Additional entries, if any: + (size_t) (entry.data.reg.additional_data == NULL ? 0 - : entry.data.reg.additional_data->size) + : entry.data.reg.additional_data->dv_hashtab.ht_used) ); msgpack_pack_map(spacker, map_size); PACK_STATIC_STR(REG_KEY_CONTENTS); @@ -1767,15 +1767,7 @@ static bool shada_pack_entry(msgpack_packer *const packer, PACK_STATIC_STR(REG_KEY_WIDTH); msgpack_pack_uint64(spacker, (uint64_t) entry.data.reg.width); } - if (entry.data.reg.additional_data != NULL) { - for (size_t i = 0; i < entry.data.reg.additional_data->size; - i++) { - msgpack_rpc_from_string(entry.data.reg.additional_data->items[i].key, - spacker); - msgpack_rpc_from_object( - entry.data.reg.additional_data->items[i].value, spacker); - } - } + DUMP_ADDITIONAL_DATA(entry.data.reg.additional_data); break; } case kSDItemBufferList: { @@ -1791,7 +1783,8 @@ static bool shada_pack_entry(msgpack_packer *const packer, + (size_t) ( entry.data.buffer_list.buffers[i].additional_data == NULL ? 0 - : entry.data.buffer_list.buffers[i].additional_data->size) + : (entry.data.buffer_list.buffers[i].additional_data + ->dv_hashtab.ht_used)) ); msgpack_pack_map(spacker, map_size); PACK_STATIC_STR(KEY_FILE); @@ -1807,18 +1800,7 @@ static bool shada_pack_entry(msgpack_packer *const packer, msgpack_pack_uint64( spacker, (uint64_t) entry.data.buffer_list.buffers[i].pos.col); } - if (entry.data.buffer_list.buffers[i].additional_data != NULL) { - for (size_t j = 0; - j < entry.data.buffer_list.buffers[i].additional_data->size; - j++) { - msgpack_rpc_from_string( - entry.data.buffer_list.buffers[i].additional_data->items[j].key, - spacker); - msgpack_rpc_from_object( - entry.data.buffer_list.buffers[i].additional_data->items[j].value, - spacker); - } - } + DUMP_ADDITIONAL_DATA(entry.data.buffer_list.buffers[i].additional_data); } break; } @@ -1917,7 +1899,11 @@ static bool shada_pack_encoded_entry(msgpack_packer *const packer, } case kSDItemVariable: { if (sd_conv->vc_type != CONV_NONE) { - convert_object(sd_conv, &entry.data.data.global_var.value); + 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; @@ -2135,9 +2121,11 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, if (var_iter == NULL && vartv.v_type == VAR_UNKNOWN) { break; } - Object obj = vim_to_object(&vartv); + typval_T tgttv; if (sd_writer->sd_conv.vc_type != CONV_NONE) { - convert_object(&sd_writer->sd_conv, &obj); + var_item_copy(&sd_writer->sd_conv, &vartv, &tgttv, true, 0); + } else { + copy_tv(&vartv, &tgttv); } if (!shada_pack_entry(packer, (ShadaEntry) { .type = kSDItemVariable, @@ -2145,18 +2133,18 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, .data = { .global_var = { .name = (char *) name, - .value = obj, + .value = tgttv, .additional_elements = NULL, } } }, max_kbyte)) { - api_free_object(obj); clear_tv(&vartv); + clear_tv(&tgttv); ret = kSDWriteFailed; goto shada_write_exit; } - api_free_object(obj); clear_tv(&vartv); + clear_tv(&tgttv); int kh_ret; (void) kh_put(strset, &wms->dumped_variables, name, &kh_ret); } while (var_iter != NULL); @@ -2970,26 +2958,17 @@ static void shada_free_shada_entry(ShadaEntry *const entry) case kSDItemJump: case kSDItemGlobalMark: case kSDItemLocalMark: { - if (entry->data.filemark.additional_data != NULL) { - api_free_dictionary(*entry->data.filemark.additional_data); - xfree(entry->data.filemark.additional_data); - } + dict_unref(entry->data.filemark.additional_data); xfree(entry->data.filemark.fname); break; } case kSDItemSearchPattern: { - if (entry->data.search_pattern.additional_data != NULL) { - api_free_dictionary(*entry->data.search_pattern.additional_data); - xfree(entry->data.search_pattern.additional_data); - } + dict_unref(entry->data.search_pattern.additional_data); xfree(entry->data.search_pattern.pat); break; } case kSDItemRegister: { - if (entry->data.reg.additional_data != NULL) { - api_free_dictionary(*entry->data.reg.additional_data); - xfree(entry->data.reg.additional_data); - } + 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]); } @@ -2997,40 +2976,25 @@ static void shada_free_shada_entry(ShadaEntry *const entry) break; } case kSDItemHistoryEntry: { - if (entry->data.history_item.canfree) { - if (entry->data.history_item.additional_elements != NULL) { - api_free_array(*entry->data.history_item.additional_elements); - xfree(entry->data.history_item.additional_elements); - } - xfree(entry->data.history_item.string); - } + list_unref(entry->data.history_item.additional_elements); + xfree(entry->data.history_item.string); break; } case kSDItemVariable: { - if (entry->data.global_var.additional_elements != NULL) { - api_free_array(*entry->data.global_var.additional_elements); - xfree(entry->data.global_var.additional_elements); - } + list_unref(entry->data.global_var.additional_elements); xfree(entry->data.global_var.name); - api_free_object(entry->data.global_var.value); + clear_tv(&entry->data.global_var.value); break; } case kSDItemSubString: { - if (entry->data.sub_string.additional_elements != NULL) { - api_free_array(*entry->data.sub_string.additional_elements); - xfree(entry->data.sub_string.additional_elements); - } + 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); - if (entry->data.buffer_list.buffers[i].additional_data != NULL) { - api_free_dictionary( - *entry->data.buffer_list.buffers[i].additional_data); - xfree(entry->data.buffer_list.buffers[i].additional_data); - } + dict_unref(entry->data.buffer_list.buffers[i].additional_data); } xfree(entry->data.buffer_list.buffers); break; @@ -3177,77 +3141,6 @@ static ShaDaReadResult msgpack_read_uint64(ShaDaReadDef *const sd_reader, return kSDReadStatusSuccess; } -/// Convert all strings in one Object instance -/// -/// @param[in] sd_conv Conversion definition. -/// @param[in,out] obj Object to convert. -static void convert_object(const vimconv_T *const sd_conv, Object *const obj) - FUNC_ATTR_NONNULL_ALL -{ - kvec_t(Object *) toconv; - kv_init(toconv); - kv_push(Object *, toconv, obj); - while (kv_size(toconv)) { - Object *cur_obj = kv_pop(toconv); -#define CONVERT_STRING(str) \ - do { \ - if (!has_non_ascii((str).data)) { \ - break; \ - } \ - size_t len = (str).size; \ - char *const converted_string = string_convert(sd_conv, (str).data, \ - &len); \ - if (converted_string != NULL) { \ - xfree((str).data); \ - (str).data = converted_string; \ - (str).size = len; \ - } \ - } while (0) - switch (cur_obj->type) { - case kObjectTypeNil: - case kObjectTypeInteger: - case kObjectTypeBoolean: - case kObjectTypeFloat: { - break; - } - case kObjectTypeString: { - CONVERT_STRING(cur_obj->data.string); - break; - } - case kObjectTypeArray: { - for (size_t i = 0; i < cur_obj->data.array.size; i++) { - Object *element = &cur_obj->data.array.items[i]; - if (element->type == kObjectTypeDictionary - || element->type == kObjectTypeArray) { - kv_push(Object *, toconv, element); - } else if (element->type == kObjectTypeString) { - CONVERT_STRING(element->data.string); - } - } - break; - } - case kObjectTypeDictionary: { - for (size_t i = 0; i < cur_obj->data.dictionary.size; i++) { - CONVERT_STRING(cur_obj->data.dictionary.items[i].key); - Object *value = &cur_obj->data.dictionary.items[i].value; - if (value->type == kObjectTypeDictionary - || value->type == kObjectTypeArray) { - kv_push(Object *, toconv, value); - } else if (value->type == kObjectTypeString) { - CONVERT_STRING(value->data.string); - } - } - break; - } - default: { - assert(false); - } - } -#undef CONVERT_STRING - } - kv_destroy(toconv); -} - /// Convert or copy and return a string /// /// @param[in] sd_conv Conversion definition. @@ -3490,6 +3383,58 @@ shada_read_next_item_extra_bytes: ? 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(_(RERR "Error while reading ShaDa file: " \ + name " entry at position %" PRIu64 " " \ + "cannot be converted to a VimL dictionary"), \ + (uint64_t) 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(_(RERR "Error while reading ShaDa file: " \ + name " entry at position %" PRIu64 " " \ + "cannot be converted to a VimL list"), \ + (uint64_t) initial_fpos); \ + clear_tv(&aetv); \ + goto shada_read_next_item_error; \ + } \ + assert(aetv.v_type == VAR_LIST); \ + (tgt) = aetv.vval.v_list; \ + } \ + } while (0) switch ((ShadaEntryType) type_u64) { case kSDItemHeader: { if (!msgpack_rpc_to_dictionary(&(unpacked.data), &(entry->data.header))) { @@ -3552,29 +3497,8 @@ shada_read_next_item_extra_bytes: ga_clear(&ad_ga); goto shada_read_next_item_error; } - if (ad_ga.ga_len) { - msgpack_object obj = { - .type = MSGPACK_OBJECT_MAP, - .via = { - .map = { - .size = (uint32_t) ad_ga.ga_len, - .ptr = ad_ga.ga_data, - } - } - }; - entry->data.search_pattern.additional_data = - xmalloc(sizeof(Dictionary)); - if (!msgpack_rpc_to_dictionary( - &obj, entry->data.search_pattern.additional_data)) { - emsgu(_(RERR "Error while reading ShaDa file: " - "search pattern entry at position %" PRIu64 " " - "cannot be converted to a Dictionary"), - (uint64_t) initial_fpos); - ga_clear(&ad_ga); - goto shada_read_next_item_error; - } - } - ga_clear(&ad_ga); + SET_ADDITIONAL_DATA(entry->data.search_pattern.additional_data, + "search pattern"); break; } case kSDItemChange: @@ -3643,28 +3567,7 @@ shada_read_next_item_extra_bytes: ga_clear(&ad_ga); goto shada_read_next_item_error; } - if (ad_ga.ga_len) { - msgpack_object obj = { - .type = MSGPACK_OBJECT_MAP, - .via = { - .map = { - .size = (uint32_t) ad_ga.ga_len, - .ptr = ad_ga.ga_data, - } - } - }; - entry->data.filemark.additional_data = xmalloc(sizeof(Dictionary)); - if (!msgpack_rpc_to_dictionary( - &obj, entry->data.filemark.additional_data)) { - emsgu(_(RERR "Error while reading ShaDa file: " - "mark entry at position %" PRIu64 " " - "cannot be converted to a Dictionary"), - (uint64_t) initial_fpos); - ga_clear(&ad_ga); - goto shada_read_next_item_error; - } - } - ga_clear(&ad_ga); + SET_ADDITIONAL_DATA(entry->data.filemark.additional_data, "mark"); break; } case kSDItemRegister: { @@ -3738,28 +3641,7 @@ shada_read_next_item_extra_bytes: ga_clear(&ad_ga); goto shada_read_next_item_error; } - if (ad_ga.ga_len) { - msgpack_object obj = { - .type = MSGPACK_OBJECT_MAP, - .via = { - .map = { - .size = (uint32_t) ad_ga.ga_len, - .ptr = ad_ga.ga_data, - } - } - }; - entry->data.reg.additional_data = xmalloc(sizeof(Dictionary)); - if (!msgpack_rpc_to_dictionary( - &obj, entry->data.reg.additional_data)) { - emsgu(_(RERR "Error while reading ShaDa file: " - "register entry at position %" PRIu64 " " - "cannot be converted to a Dictionary"), - (uint64_t) initial_fpos); - ga_clear(&ad_ga); - goto shada_read_next_item_error; - } - } - ga_clear(&ad_ga); + SET_ADDITIONAL_DATA(entry->data.reg.additional_data, "register"); break; } case kSDItemHistoryEntry: { @@ -3859,27 +3741,9 @@ 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; - if (unpacked.data.via.array.size > (size_t) (2 + is_hist_search)) { - msgpack_object obj = { - .type = MSGPACK_OBJECT_ARRAY, - .via = { - .array = { - .size = (unpacked.data.via.array.size - - (uint32_t) (2 + is_hist_search)), - .ptr = unpacked.data.via.array.ptr + (2 + is_hist_search), - } - } - }; - entry->data.history_item.additional_elements = xmalloc(sizeof(Array)); - if (!msgpack_rpc_to_array( - &obj, entry->data.history_item.additional_elements)) { - emsgu(_(RERR "Error while reading ShaDa file: " - "history entry at position %" PRIu64 " " - "cannot be converted to an Array"), - (uint64_t) initial_fpos); - goto shada_read_next_item_error; - } - } + SET_ADDITIONAL_ELEMENTS(unpacked.data.via.array, (2 + is_hist_search), + entry->data.history_item.additional_elements, + "history"); break; } case kSDItemVariable: { @@ -3893,7 +3757,7 @@ shada_read_next_item_hist_no_conv: entry->data.global_var = (struct global_var) { .name = NULL, .value = { - .type = kObjectTypeNil, + .v_type = VAR_UNKNOWN, }, .additional_elements = NULL }; @@ -3922,37 +3786,27 @@ shada_read_next_item_hist_no_conv: entry->data.global_var.name = xmemdupz(unpacked.data.via.array.ptr[0].via.bin.ptr, unpacked.data.via.array.ptr[0].via.bin.size); - if (!msgpack_rpc_to_object(&(unpacked.data.via.array.ptr[1]), - &(entry->data.global_var.value))) { + if (msgpack_to_vim(unpacked.data.via.array.ptr[1], + &(entry->data.global_var.value)) == FAIL) { emsgu(_(RERR "Error while reading ShaDa file: " "variable entry at position %" PRIu64 " " - "has value that cannot be converted to the object"), + "has value that cannot be converted to the VimL value"), (uint64_t) initial_fpos); goto shada_read_next_item_error; } if (sd_reader->sd_conv.vc_type != CONV_NONE) { - convert_object(&sd_reader->sd_conv, &entry->data.global_var.value); - } - if (unpacked.data.via.array.size > 2) { - msgpack_object obj = { - .type = MSGPACK_OBJECT_ARRAY, - .via = { - .array = { - .size = unpacked.data.via.array.size - 2, - .ptr = unpacked.data.via.array.ptr + 2, - } - } - }; - entry->data.global_var.additional_elements = xmalloc(sizeof(Array)); - if (!msgpack_rpc_to_array( - &obj, entry->data.global_var.additional_elements)) { - emsgu(_(RERR "Error while reading ShaDa file: " - "variable entry at position %" PRIu64 " " - "cannot be converted to an Array"), - (uint64_t) initial_fpos); - goto shada_read_next_item_error; - } - } + 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: { @@ -3983,26 +3837,9 @@ shada_read_next_item_hist_no_conv: } entry->data.sub_string.sub = BIN_CONVERTED(unpacked.data.via.array.ptr[0].via.bin); - if (unpacked.data.via.array.size > 1) { - msgpack_object obj = { - .type = MSGPACK_OBJECT_ARRAY, - .via = { - .array = { - .size = unpacked.data.via.array.size - 1, - .ptr = unpacked.data.via.array.ptr + 1, - } - } - }; - entry->data.sub_string.additional_elements = xmalloc(sizeof(Array)); - if (!msgpack_rpc_to_array( - &obj, entry->data.sub_string.additional_elements)) { - emsgu(_(RERR "Error while reading ShaDa file: " - "sub string entry at position %" PRIu64 " " - "cannot be converted to an Array"), - (uint64_t) initial_fpos); - goto shada_read_next_item_error; - } - } + SET_ADDITIONAL_ELEMENTS(unpacked.data.via.array, 1, + entry->data.sub_string.additional_elements, + "sub string"); break; } case kSDItemBufferList: { @@ -4079,30 +3916,9 @@ shada_read_next_item_hist_no_conv: ga_clear(&ad_ga); goto shada_read_next_item_error; } - if (ad_ga.ga_len) { - msgpack_object obj = { - .type = MSGPACK_OBJECT_MAP, - .via = { - .map = { - .size = (uint32_t) ad_ga.ga_len, - .ptr = ad_ga.ga_data, - } - } - }; - entry->data.buffer_list.buffers[i].additional_data = - xmalloc(sizeof(Dictionary)); - if (!msgpack_rpc_to_dictionary( - &obj, entry->data.buffer_list.buffers[i].additional_data)) { - emsgu(_(RERR "Error while reading ShaDa file: " - "buffer list at position %" PRIu64 " " - "contains entry that cannot be converted " - "to a Dictionary"), - (uint64_t) initial_fpos); - ga_clear(&ad_ga); - goto shada_read_next_item_error; - } - } - ga_clear(&ad_ga); + SET_ADDITIONAL_DATA( + entry->data.buffer_list.buffers[i].additional_data, + "buffer list entry"); } } break; @@ -4132,6 +3948,8 @@ shada_read_next_item_hist_no_conv: #undef LONG_KEY #undef TOU8 #undef TOSIZE +#undef SET_ADDITIONAL_DATA +#undef SET_ADDITIONAL_ELEMENTS shada_read_next_item_error: msgpack_unpacked_destroy(&unpacked); xfree(buf); diff --git a/src/nvim/undo.c b/src/nvim/undo.c index a04e7c2763..b2f71432dc 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -111,7 +111,6 @@ #include "nvim/types.h" #include "nvim/os/os.h" #include "nvim/os/time.h" -#include "nvim/api/private/helpers.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "undo.c.generated.h" @@ -329,11 +328,8 @@ static long get_undolevel(void) static inline void zero_fmark_additional_data(fmark_T *fmarks) { for (size_t i = 0; i < NMARKS; i++) { - if (fmarks[i].additional_data != NULL) { - api_free_dictionary(*fmarks[i].additional_data); - free(fmarks[i].additional_data); - fmarks[i].additional_data = NULL; - } + dict_unref(fmarks[i].additional_data); + fmarks[i].additional_data = NULL; } } -- cgit From 5b3e668f3e03e6b0142be5afdc72cb6dadd58498 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 4 Aug 2015 08:14:08 +0300 Subject: shada: When reading marks or registers, free items when set fails --- src/nvim/mark.c | 18 ++++++++++++------ src/nvim/ops.c | 10 ++++++++-- src/nvim/shada.c | 16 ++++++++++++---- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/nvim/mark.c b/src/nvim/mark.c index beb8dd6679..5afa24e621 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -1343,19 +1343,22 @@ size_t mark_buffer_amount(const buf_T *const buf) /// @param[in] fm Mark to be set. /// @param[in] update If true then only set global mark if it was created /// later then existing one. -void mark_set_global(const char name, const xfmark_T fm, const bool update) +/// +/// @return true on success, false on failure. +bool mark_set_global(const char name, const xfmark_T fm, const bool update) { xfmark_T *fm_tgt = &(namedfm[mark_global_index(name)]); if (fm_tgt == &namedfm[0] - 1) { - return; + return false; } if (update && fm.fmark.timestamp < fm_tgt->fmark.timestamp) { - return; + return false; } if (fm_tgt->fmark.mark.lnum != 0) { free_xfmark(*fm_tgt); } *fm_tgt = fm; + return true; } /// Set local mark @@ -1365,7 +1368,9 @@ void mark_set_global(const char name, const xfmark_T fm, const bool update) /// @param[in] fm Mark to be set. /// @param[in] update If true then only set global mark if it was created /// later then existing one. -void mark_set_local(const char name, buf_T *const buf, +/// +/// @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 { @@ -1379,15 +1384,16 @@ void mark_set_local(const char name, buf_T *const buf, } else if (name == '.') { fm_tgt = &(buf->b_last_change); } else { - return; + return false; } if (update && fm.timestamp < fm_tgt->timestamp) { - return; + return false; } if (fm_tgt->mark.lnum != 0) { free_fmark(*fm_tgt); } *fm_tgt = fm; + return true; } /* diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 9af37df8a0..04c53d30bb 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -5368,11 +5368,17 @@ size_t op_register_amount(void) } /// Set register to a given value -void register_set(const char name, const yankreg_T reg) +/// +/// @param[in] name Register name. +/// @param[in] reg Register value. +/// +/// @return true on success, false on failure. +bool register_set(const char name, const yankreg_T reg) { int i = op_reg_index(name); if (i == -1) { - return; + return false; } y_regs[i] = reg; + return true; } diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 684a6ea62d..c91005ab9c 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1240,14 +1240,16 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) shada_free_shada_entry(&cur_entry); break; } - register_set(cur_entry.data.reg.name, (yankreg_T) { + if (!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; } @@ -1277,7 +1279,10 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) }, }; if (cur_entry.type == kSDItemGlobalMark) { - mark_set_global(cur_entry.data.filemark.name, fm, !force); + if (!mark_set_global(cur_entry.data.filemark.name, fm, !force)) { + shada_free_shada_entry(&cur_entry); + break; + } } else { if (force) { if (curwin->w_jumplistlen == JUMPLISTSIZE) { @@ -1400,7 +1405,10 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) .additional_data = cur_entry.data.filemark.additional_data, }; if (cur_entry.type == kSDItemLocalMark) { - mark_set_local(cur_entry.data.filemark.name, buf, fm, !force); + 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); -- cgit From 2244db67aa0dc1100dc5814c2bf070d76cf5f313 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 4 Aug 2015 08:19:01 +0300 Subject: shada: Do not allow empty keys --- src/nvim/shada.c | 7 +++++++ test/functional/shada/errors_spec.lua | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index c91005ab9c..3e9f9298ab 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -3345,6 +3345,13 @@ shada_read_next_item_extra_bytes: (uint64_t) initial_fpos); \ ga_clear(&ad_ga); \ goto shada_read_next_item_error; \ + } else if (unpacked.data.via.map.ptr[i].key.via.str.size == 0) { \ + emsgu(_(RERR "Error while reading ShaDa file: " \ + entry_name " entry at position %" PRIu64 " " \ + "has empty key"), \ + (uint64_t) initial_fpos); \ + ga_clear(&ad_ga); \ + goto shada_read_next_item_error; \ } \ } while (0) #define CHECKED_KEY(entry_name, name, error_desc, tgt, condition, attr, proc) \ diff --git a/test/functional/shada/errors_spec.lua b/test/functional/shada/errors_spec.lua index 4ae7847cc7..e3a1dcdbb9 100644 --- a/test/functional/shada/errors_spec.lua +++ b/test/functional/shada/errors_spec.lua @@ -124,6 +124,12 @@ describe('ShaDa error handling', function() eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has key which is not a string', exc_exec(sdrcmd())) end) + -- sp entry is here because it causes an allocation. + it('fails on search pattern item with empty key', function() + wshada('\002\000\013\131\162sp\196\001a\162sX\192\160\000') + eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has empty key', exc_exec(sdrcmd())) + end) + it('fails on search pattern item with NIL magic key value', function() wshada('\002\000\009\130\162sX\192\162sm\192') eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has sm key value which is not a boolean', exc_exec(sdrcmd())) @@ -192,6 +198,12 @@ describe('ShaDa error handling', function() eq('Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 has key which is not a string', exc_exec(sdrcmd())) end) + -- f entry is here because it causes an allocation. + it('fails on ' .. v.name .. ' item with empty key', function() + wshada(v.mpack .. '\000\012\131\161f\196\001/\162mX\192\160\000') + eq('Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 has empty key', exc_exec(sdrcmd())) + end) + it('fails on ' .. v.name .. ' item without f key', function() wshada(v.mpack .. '\000\008\130\162mX\192\161l\001') eq('Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 is missing file name', exc_exec(sdrcmd())) @@ -244,6 +256,12 @@ describe('ShaDa error handling', function() eq('Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 has key which is not a string', exc_exec(sdrcmd())) end) + -- rc entry is here because it causes an allocation + it('fails on register item with BIN key', function() + wshada('\005\000\014\131\162rc\145\196\001a\162rX\192\160\000') + eq('Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 has empty key', exc_exec(sdrcmd())) + end) + it('fails on register item with NIL rt key value', function() wshada('\005\000\009\130\162rX\192\162rt\192') eq('Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 has rt key value which is not an unsigned integer', exc_exec(sdrcmd())) -- cgit From 21056bad57bc239d27639c087ed113f8e512cc76 Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 5 Aug 2015 07:17:48 +0300 Subject: documentation: Describe forward compatibility limitations --- runtime/doc/starting.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index a3af862ad6..c75970db56 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -1024,7 +1024,8 @@ ShaDa files are forward and backward compatible. This means that 1. Entries which have unknown type (i.e. that hold unidentified data) are ignored when reading and blindly copied when writing. 2. Register entries with unknown register name are ignored when reading and - blindly copied when writing. |registers| + blindly copied when writing. Limitation: only registers that use name with + code in interval [1, 255] are supported. |registers| 3. Register entries with unknown register type are ignored when reading and merged as usual when writing. |getregtype()| 4. Local and global mark entries with unknown mark names are ignored when @@ -1032,9 +1033,11 @@ ShaDa files are forward and backward compatible. This means that entries are also blindly copied, but only if file they are attached to fits in the |shada-'| limit. Unknown local mark entry's timestamp is also taken into account when calculating which files exactly should fit into this - limit. |mark-motions| + limit. Limitation: only marks that use name with code in interval [1, 255] + are supported. |mark-motions| 5. History entries with unknown history type are ignored when reading and - blindly copied when writing. |history| + blindly copied when writing. Limitation: there can be only up to 256 + history types. |history| 6. Unknown keys found in register, local mark, global mark, change, jump and search pattern entries are saved internally and dumped when writing. Entries created during NeoVim session never have such additions. -- cgit From 278de872f76cb617c969037468a832245615090f Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 6 Aug 2015 22:38:30 +0300 Subject: shada: Synchronize sd_writer file before closing --- src/nvim/shada.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 3e9f9298ab..2ba0398541 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -687,7 +687,13 @@ static void close_sd_reader(ShaDaReadDef *const sd_reader) static void close_sd_writer(ShaDaWriteDef *const sd_writer) FUNC_ATTR_NONNULL_ALL { - close_file((int)(intptr_t) sd_writer->cookie); + 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 opening file descriptors -- cgit From 82934e8797651b934569ba77bd9fd6d8f75e87e6 Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 6 Aug 2015 23:01:28 +0300 Subject: shada: Fix GCC -Wconversion warnings in srni_flags definitions --- src/nvim/shada.c | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 2ba0398541..be049fbb20 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1099,25 +1099,26 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) && (force || oldfiles_list == NULL || oldfiles_list->lv_len == 0)); const bool want_marks = flags & kShaDaWantMarks; - const unsigned srni_flags = ((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)); + 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; @@ -2039,7 +2040,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, } } - const unsigned srni_flags = ( + const unsigned srni_flags = (unsigned) ( kSDReadUndisableableData | kSDReadUnknown | (dump_history ? kSDReadHistory : 0) -- cgit From 12a31c70c1beb3d106c4450bbd2ab33a1f8c2316 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 26 Jul 2015 20:46:40 +0300 Subject: shada,functests: Test compatibility support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For compatibility the following things are done: 1. Items with type greater then greatest type are ignored when reading and copied when writing. 2. Registers with unknown name are ignored when reading and blindly copied when writing. 3. Registers with unknown type are ignored when reading and merged as usual when writing. 4. Local and global marks with unknown names are ignored when reading. When writing global marks are blindly copied and local marks are also blindly copied, but only if file they are attached to fits in the `'N` limit defined in &shada. Unknown local mark’s timestamp is also taken into account when calculating which files exactly should fit into this limit. 5. History items with unknown type are ignored when reading and blindly copied when writing. 6. Unknown keys found in register, local marks, global marks, changes, jumps and search pattern entries are read to additional_data Dictionary and dumped (of course, unless any of these elements were not overwritten later). It obviously works only for values conversible to Object type. 7. Additional elements found in replacement string and history entries are read to additional_elements Array and dumped (same: only if they were not overwritten later). Again this works only for elements conversible to Object type. 8. Additional elements found in variable entries are simply ignored when reading. When writing *new* variables they will be preserved during merging, but that’s all. Variable values dumped from current NeoVim session never have additional elements. --- src/nvim/mark.c | 4 +- src/nvim/shada.c | 45 ++- test/functional/shada/compatibility_spec.lua | 420 +++++++++++++++++++++++++++ 3 files changed, 451 insertions(+), 18 deletions(-) create mode 100644 test/functional/shada/compatibility_spec.lua diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 5afa24e621..7e1ae42c47 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -1351,7 +1351,7 @@ bool mark_set_global(const char name, const xfmark_T fm, const bool update) if (fm_tgt == &namedfm[0] - 1) { return false; } - if (update && fm.fmark.timestamp < fm_tgt->fmark.timestamp) { + if (update && fm.fmark.timestamp <= fm_tgt->fmark.timestamp) { return false; } if (fm_tgt->fmark.mark.lnum != 0) { @@ -1386,7 +1386,7 @@ bool mark_set_local(const char name, buf_T *const buf, } else { return false; } - if (update && fm.timestamp < fm_tgt->timestamp) { + if (update && fm.timestamp <= fm_tgt->timestamp) { return false; } if (fm_tgt->mark.lnum != 0) { diff --git a/src/nvim/shada.c b/src/nvim/shada.c index be049fbb20..770a5a89f4 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1567,6 +1567,11 @@ static char *shada_filename(const char *file) msgpack_pack_str(spacker, sizeof(s) - 1); \ msgpack_pack_str_body(spacker, s, sizeof(s) - 1); \ } while (0) +#define PACK_STRING(s) \ + do { \ + msgpack_pack_str(spacker, s.size); \ + msgpack_pack_str_body(spacker, s.data, s.size); \ + } while (0) /// Write single ShaDa entry /// @@ -1587,7 +1592,7 @@ static bool shada_pack_entry(msgpack_packer *const packer, 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) { \ - return false; \ + goto shada_pack_entry_error; \ } \ } \ } \ @@ -1605,7 +1610,7 @@ static bool shada_pack_entry(msgpack_packer *const packer, 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) { \ - return false; \ + goto shada_pack_entry_error; \ } \ } \ } \ @@ -1616,12 +1621,9 @@ static bool shada_pack_entry(msgpack_packer *const packer, assert(false); } case kSDItemUnknown: { - if ((msgpack_pack_uint64(packer, (uint64_t) entry.data.unknown_item.size) - == -1) - || (packer->callback(packer->data, entry.data.unknown_item.contents, - (unsigned) entry.data.unknown_item.size) - == -1)) { - return false; + if (spacker->callback(spacker->data, entry.data.unknown_item.contents, + (unsigned) entry.data.unknown_item.size) == -1) { + goto shada_pack_entry_error; } break; } @@ -1651,7 +1653,7 @@ static bool shada_pack_entry(msgpack_packer *const packer, msgpack_rpc_from_string(cstr_as_string(entry.data.global_var.name), spacker); if (vim_to_msgpack(spacker, &entry.data.global_var.value) == FAIL) { - return false; + goto shada_pack_entry_error; } DUMP_ADDITIONAL_ELEMENTS(entry.data.global_var.additional_elements); break; @@ -1826,30 +1828,34 @@ static bool shada_pack_entry(msgpack_packer *const packer, } if (!max_kbyte || sbuf.size <= max_kbyte * 1024) { if (entry.type == kSDItemUnknown) { - if (msgpack_pack_uint64(packer, (uint64_t) entry.data.unknown_item.type) - == -1) { - return false; + 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) { - return false; + goto shada_pack_entry_error; } } if (msgpack_pack_uint64(packer, (uint64_t) entry.timestamp) == -1) { - return false; + 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)) { - return false; + 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 /// @@ -3260,7 +3266,14 @@ shada_read_next_item_start: entry->data.unknown_item.size = length; entry->data.unknown_item.type = type_u64; entry->data.unknown_item.contents = xmalloc(length); - return fread_len(sd_reader, entry->data.unknown_item.contents, 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; } char *const buf = xmalloc(length); diff --git a/test/functional/shada/compatibility_spec.lua b/test/functional/shada/compatibility_spec.lua new file mode 100644 index 0000000000..5c48d82f4a --- /dev/null +++ b/test/functional/shada/compatibility_spec.lua @@ -0,0 +1,420 @@ +-- ShaDa compatibility support +local helpers = require('test.functional.helpers') +local nvim, nvim_window, nvim_curwin, nvim_command, nvim_feed, nvim_eval, eq = + helpers.nvim, helpers.window, helpers.curwin, helpers.command, helpers.feed, + helpers.eval, helpers.eq +local write_file = helpers.write_file + +local shada_helpers = require('test.functional.shada.helpers') +local reset, set_additional_cmd, clear, exc_exec = + shada_helpers.reset, shada_helpers.set_additional_cmd, + shada_helpers.clear, shada_helpers.exc_exec + +local shada_fname = 'Xtest-functional-shada-additional.shada' +local wshada = function(text) + write_file(shada_fname, text, true) +end +local sdrcmd = function(bang) + return 'rshada' .. (bang and '!' or '') .. ' ' .. shada_fname +end + +local msgpack = require('MessagePack') +local mpack_keys = {'type', 'timestamp', 'length', 'value'} +local read_shada_file = function(fname) + local fd = io.open(fname, 'r') + local mstring = fd:read('*a') + fd:close() + local unpacker = msgpack.unpacker(mstring) + local ret = {} + local cur + local i = 0 + while true do + local off, val = unpacker() + if not off then break end + if i % 4 == 0 then + cur = {} + ret[#ret + 1] = cur + end + cur[mpack_keys[(i % 4) + 1]] = val + i = i + 1 + end + return ret +end + +describe('ShaDa forward compatibility support code', function() + before_each(reset) + after_each(function() + clear() + os.remove(shada_fname) + end) + + it('works with search pattern item with BOOL unknown (sX) key value', function() + wshada('\002\001\011\130\162sX\194\162sp\196\001-') + eq(0, exc_exec(sdrcmd())) + os.remove(shada_fname) + nvim_command('wshada ' .. shada_fname) + local found = false + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 2 and not v.value.ss then + eq(false, v.value.sX) + found = true + end + end + eq(true, found) + eq(0, exc_exec(sdrcmd())) + os.remove(shada_fname) + nvim_command('silent! /---/') + nvim_command('wshada ' .. shada_fname) + found = false + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 2 and v.value.ss then + eq(nil, v.value.sX) + found = true + end + end + eq(true, found) + end) + + it('works with s/search pattern item with BOOL unknown (sX) key value', function() + wshada('\002\001\015\131\162sX\194\162ss\195\162sp\196\001-') + eq(0, exc_exec(sdrcmd())) + os.remove(shada_fname) + nvim_command('wshada ' .. shada_fname) + local found = false + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 2 and v.value.ss then + eq(false, v.value.sX) + found = true + end + end + eq(true, found) + eq(0, exc_exec(sdrcmd())) + os.remove(shada_fname) + nvim_command('silent! s/--/---/ge') + nvim_command('wshada ' .. shada_fname) + found = false + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 2 and v.value.ss then + eq(nil, v.value.sX) + found = true + end + end + eq(true, found) + end) + + it('works with replacement item with BOOL additional value in list', function() + wshada('\003\000\005\146\196\001-\194') + eq(0, exc_exec(sdrcmd())) + os.remove(shada_fname) + nvim_command('wshada ' .. shada_fname) + local found = false + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 3 then + eq(2, #v.value) + eq(false, v.value[2]) + found = true + end + end + eq(true, found) + eq(0, exc_exec(sdrcmd())) + os.remove(shada_fname) + nvim_command('silent! s/--/---/ge') + nvim_command('wshada ' .. shada_fname) + found = false + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 3 then + eq(1, #v.value) + found = true + end + end + eq(true, found) + end) + + for _, v in ipairs({{name='global mark', mpack='\007\001\018\131\162mX\195\161f\196\006/a/b/c\161nA'}, + {name='jump', mpack='\008\001\018\131\162mX\195\161f\196\006/a/b/c\161l\002'}, + {name='local mark', mpack='\010\001\018\131\162mX\195\161f\196\006/a/b/c\161na'}, + {name='change', mpack='\011\001\015\130\162mX\195\161f\196\006/a/b/c'}, + }) do + it('works with ' .. v.name .. ' item with BOOL unknown (mX) key value', function() + nvim_command('silent noautocmd edit /a/b/c') + eq('/a/b/c', nvim_eval('bufname("%")')) + nvim_command('call setline(".", ["1", "2", "3"])') + wshada(v.mpack) + eq(0, exc_exec(sdrcmd(true))) + os.remove(shada_fname) + nvim_command('wshada ' .. shada_fname) + local found = false + for _, subv in ipairs(read_shada_file(shada_fname)) do + if subv.type == v.mpack:byte() then + if subv.value.mX == true then + found = true + end + end + end + eq(true, found) + eq(0, exc_exec(sdrcmd())) + nvim_command('bwipeout!') + nvim_eval('setpos("\'A", [0, 1, 1, 0])') + os.remove(shada_fname) + nvim_command('wshada ' .. shada_fname) + found = false + for _, subv in ipairs(read_shada_file(shada_fname)) do + if subv.type == v.mpack:byte() then + if subv.value.mX == true then + found = true + end + end + end + eq(false, found) + end) + + if v.name == 'global mark' or v.name == 'local mark' then + it('works with ' .. v.name .. ' item with name', function() + nvim_command('silent noautocmd edit /a/b/c') + eq('/a/b/c', nvim_eval('bufname("%")')) + nvim_command('call setline(".", ["1", "2", "3"])') + wshada(v.mpack:gsub('n.$', 'n\001') + .. v.mpack:gsub('n.$', 'n\002') + .. v.mpack:gsub('n.$', 'n\003'):gsub('/a/b/c', '/d/e/f')) + eq(0, exc_exec(sdrcmd(true))) + nvim_command('wshada ' .. shada_fname) + local found = 0 + for i, subv in ipairs(read_shada_file(shada_fname)) do + if i == 1 then + eq(1, subv.type) + end + if subv.type == v.mpack:byte() then + if subv.value.mX == true and subv.value.n <= 3 then + found = found + 1 + end + end + end + eq(3, found) + nvim_command('wshada! ' .. shada_fname) + local found = 0 + for i, subv in ipairs(read_shada_file(shada_fname)) do + if i == 1 then + eq(1, subv.type) + end + if subv.type == v.mpack:byte() then + if subv.value.mX == true and subv.value.n <= 3 then + found = found + 1 + end + end + end + eq(0, found) + end) + end + end + + it('works with register item with BOOL unknown (rX) key', function() + wshada('\005\001\015\131\161na\162rX\194\162rc\145\196\001-') + eq(0, exc_exec(sdrcmd())) + os.remove(shada_fname) + nvim_command('wshada ' .. shada_fname) + local found = false + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 5 and v.value.rX == false then + found = true + end + end + eq(true, found) + eq(0, exc_exec(sdrcmd())) + os.remove(shada_fname) + nvim_command('let @a = "Test"') + nvim_command('wshada ' .. shada_fname) + found = false + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 5 and v.value.rX == false then + found = true + end + end + eq(false, found) + end) + + it('works with register item with name', function() + wshada('\005\001\015\131\161n\001\162rX\194\162rc\145\196\001-') + eq(0, exc_exec(sdrcmd(true))) + nvim_command('wshada ' .. shada_fname) + local found = 0 + for i, v in ipairs(read_shada_file(shada_fname)) do + if i == 1 then + eq(1, v.type) + end + if v.type == 5 then + if v.value.rX == false and v.value.n == 1 then + found = found + 1 + end + end + end + eq(1, found) + nvim_command('wshada! ' .. shada_fname) + local found = 0 + for i, v in ipairs(read_shada_file(shada_fname)) do + if i == 1 then + eq(1, v.type) + end + if v.type == 5 then + if v.value.rX == false and v.value.n == 1 then + found = found + 1 + end + end + end + eq(0, found) + end) + + it('works with register item with type 10', function() + wshada('\005\001\019\132\161na\162rX\194\162rc\145\196\001-\162rt\010') + eq(0, exc_exec(sdrcmd(true))) + eq({{}, ''}, nvim_eval('[getreg("a", 1, 1)[:], getregtype("a")]')) + nvim_command('wshada ' .. shada_fname) + local found = 0 + for i, v in ipairs(read_shada_file(shada_fname)) do + if i == 1 then + eq(1, v.type) + end + if v.type == 5 then + if v.value.rX == false and v.value.rt == 10 then + found = found + 1 + end + end + end + eq(1, found) + nvim_command('wshada! ' .. shada_fname) + local found = 0 + for i, v in ipairs(read_shada_file(shada_fname)) do + if i == 1 then + eq(1, v.type) + end + if v.type == 5 then + if v.value.rX == false and v.value.rt == 10 then + found = found + 1 + end + end + end + eq(0, found) + end) + + it('works with buffer list item with BOOL unknown (bX) key', function() + nvim_command('set shada+=%') + wshada('\009\000\016\145\130\161f\196\006/a/b/c\162bX\195') + eq(0, exc_exec(sdrcmd())) + eq(2, nvim_eval('bufnr("$")')) + eq('/a/b/c', nvim_eval('bufname(2)')) + os.remove(shada_fname) + nvim_command('wshada ' .. shada_fname) + local found = false + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 9 and #v.value == 1 and v.value[1].bX == true then + found = true + end + end + eq(true, found) + eq(0, exc_exec(sdrcmd())) + os.remove(shada_fname) + nvim_command('buffer 2') + nvim_command('edit!') + nvim_command('wshada ' .. shada_fname) + found = false + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 5 and v.value.rX == false then + found = true + end + end + eq(false, found) + nvim_command('bwipeout!') + end) + + it('works with history item with BOOL additional value in list', function() + wshada('\004\000\006\147\000\196\001-\194') + eq(0, exc_exec(sdrcmd())) + os.remove(shada_fname) + nvim_command('wshada ' .. shada_fname) + local found = false + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 4 and v.value[1] == 0 and v.value[2] == '-' then + eq(false, v.value[3]) + eq(3, #v.value) + found = true + end + end + eq(true, found) + eq(0, exc_exec(sdrcmd())) + os.remove(shada_fname) + nvim_eval('histadd(":", "--")') + nvim_eval('histadd(":", "-")') + nvim_command('wshada ' .. shada_fname) + found = false + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 4 and v.value[1] == 0 and v.value[2] == '-' then + eq(2, #v.value) + found = true + end + end + eq(true, found) + end) + + it('works with history item with type 10', function() + wshada('\004\000\006\147\010\196\001-\194') + eq(0, exc_exec(sdrcmd())) + nvim_command('wshada ' .. shada_fname) + eq(0, exc_exec(sdrcmd())) + local found = 0 + for i, v in ipairs(read_shada_file(shada_fname)) do + if i == 1 then + eq(1, v.type) + end + if v.type == 4 then + if v.value[1] == 10 and #v.value == 3 and v.value[3] == false then + found = found + 1 + end + end + end + eq(1, found) + nvim_command('wshada! ' .. shada_fname) + local found = 0 + for i, v in ipairs(read_shada_file(shada_fname)) do + if i == 1 then + eq(1, v.type) + end + if v.type == 4 then + if v.value[1] == 10 and #v.value == 3 and v.value[3] == false then + found = found + 1 + end + end + end + eq(0, found) + end) + + it('works with item with 100 type', function() + wshada('\100\000\006\147\010\196\001-\194') + eq(0, exc_exec(sdrcmd())) + nvim_command('wshada ' .. shada_fname) + eq(0, exc_exec(sdrcmd())) + local found = 0 + for i, v in ipairs(read_shada_file(shada_fname)) do + if i == 1 then + eq(1, v.type) + end + if v.type == 100 then + if v.value[1] == 10 and #v.value == 3 and v.value[3] == false then + found = found + 1 + end + end + end + eq(1, found) + nvim_command('wshada! ' .. shada_fname) + local found = 0 + for i, v in ipairs(read_shada_file(shada_fname)) do + if i == 1 then + eq(1, v.type) + end + if v.type == 100 then + if v.value[1] == 10 and #v.value == 3 and v.value[3] == false then + found = found + 1 + end + end + end + eq(0, found) + end) +end) -- cgit From fce9590cfc128baab97a03baa70af2368c6ed23a Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 8 Aug 2015 03:31:54 +0300 Subject: shada: When packing header, pack mapping keys as strings, not binary --- src/nvim/shada.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 770a5a89f4..03dc10c533 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1822,7 +1822,11 @@ static bool shada_pack_entry(msgpack_packer *const packer, break; } case kSDItemHeader: { - msgpack_rpc_from_dictionary(entry.data.header, spacker); + 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); + msgpack_rpc_from_object(entry.data.header.items[i].value, spacker); + } break; } } -- cgit From aea7f6aa723e1fca3cab1294731f1b828eb59f88 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 8 Aug 2015 03:50:56 +0300 Subject: ex_getln: Refactor HIST_\* list of macros to enum --- src/nvim/ex_getln.h | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h index 3bfd6a8aac..c537d681c6 100644 --- a/src/nvim/ex_getln.h +++ b/src/nvim/ex_getln.h @@ -24,15 +24,17 @@ #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); -- cgit From d8b0cd5c173d6255a100977bd18204ecf989f5d4 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 8 Aug 2015 04:52:42 +0300 Subject: documentation: Add extended ShaDa format description --- runtime/doc/starting.txt | 157 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 154 insertions(+), 3 deletions(-) diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index c75970db56..e96a014041 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -896,7 +896,7 @@ To automatically save and restore views for *.c files: > au BufWinEnter *.c silent loadview ============================================================================== -8. The ShaDa file *shada* *shada-file* *E575* *E576* +8. The ShaDa file *shada* *shada-file* If you exit Vim and later start it again, you would normally lose a lot of information. The ShaDa file can be used to remember that information, which enables you to continue where you left off. @@ -1105,7 +1105,8 @@ in your .nvimrc file like > :rshada! ~/.my.shada can be used to load this information. You could even have different ShaDa files for different types of files (e.g., C code) and load them based on the -file name, using the ":autocmd" command (see |:autocmd|). +file name, using the ":autocmd" command (see |:autocmd|). More information on +ShaDa file format is contained in |shada-format| section. *shada-errors* When Vim detects an error while reading a ShaDa file, it will not overwrite @@ -1116,7 +1117,7 @@ accidentally did that!). If you want to overwrite a ShaDa file with an error in it, you will either have to fix the error, delete the file (while NeoVim is running, so most of the information will be restored) or write it explicitly with |:wshada| and a bang. - *E136* *E138* + *E136* *E138* *shada-error-handling* Note: when NeoVim finds out that it failed to write part of the ShaDa file (e.g. because there is no space left to write the file) or when it appears that already present ShaDa file contains errors that indicate that this file @@ -1165,4 +1166,154 @@ not write ShaDa file at all. and still get the prompt to enter a file number. Use ! to abandon a modified buffer. |abandon| +SHADA FILE FORMAT *shada-format* + +ShaDa files are concats of MessagePack entries. Each entry is a concat of +exactly four MessagePack objects: + +1. First goes type of the entry. Object type must be an unsigned integer. + Object type must not be equal to zero. +2. Second goes entry timestamp. It must also be an unsigned integer. +3. Third goes the length of the fourth entry. Unsigned integer as well, used + for fast skipping without parsing. +4. Fourth is actual entry data. All currently used ShaDa entries use + containers to hold data: either map or array. Exact format depends on the + entry type: + + Entry type (name) Entry data ~ + 1 (Header) Map containing data that describes the NeoVim instance + that written this ShaDa file. It is ignored when + reading ShaDa files. Contains the following data: + Key Data ~ + version Binary, NeoVim version. + encoding Binary, effective 'encoding' value. + max_kbyte Integer, effective |shada-s| limit value. + pid Integer, instance process ID. + 2 (SearchPattern) Map containing data describing last used search or + substitute pattern. Normally ShaDa file contains two + such entries: one with "ss" key set to true (describes + substitute pattern, see |:substitute|), and one set to + false (describes search pattern, see + |search-commands|). "su" key should be true on one of + the entries. If key value is equal to default then it + is normally not present. Keys: + Key Type Default Description ~ + sm Boolean true Effective 'magic' value. + sc Boolean false Effective 'smartcase' value. + sl Boolean true True if search pattern comes + with a line offset. See + |search-offset|. + se Boolean false True if |search-offset| + requested to place cursor at + (relative to) the end of the + pattern. + so Integer 0 Offset value. |search-offset| + su Boolean false True if current entry was the + last used search pattern. + ss Boolean false True if current entry describes + |:substitute| pattern. + sh Boolean false True if |v:hlsearch| is on. + With |shada-h| or 'nohlsearch' + this key is always false. + sp Binary N/A Actual pattern. Required. + * any none Other keys are allowed for + compatibility reasons, see + |shada-compatibility|. + 3 (SubString) Array containing last |:substitute| replacement string. + Contains single entry: binary, replacement string used. + More entries are allowed for compatibility reasons, see + |shada-compatibility|. + 4 (HistoryEntry) Array containing one entry from history. Should have + two or three entries. First one is history type + (unsigned integer), second is history line (binary), + third is the separator character (unsigned integer, + must be in interval [0, 255]). Third item is only + valid for search history. Possible history types are + listed in |hist-names|, here are the corresponding + numbers: 0 - cmd, 1 - search, 2 - expr, 3 - input, + 4 - debug. + 5 (Register) Map describing one register (|registers|). If key + value is equal to default then it is normally not + present. Keys: + Key Type Def Description ~ + rt UInteger 0 Register type: + No Description ~ + 0 |characterwise-register| + 1 |linewise-register| + 2 |blockwise-register| + rw UInteger 0 Register width. Only valid + for |blockwise-register|s. + rc Array of binary N/A Register contents. Each + entry in the array + represents its own line. + NUL characters inside the + line should be represented + as NL according to + |NL-used-for-Nul|. + n UInteger N/A Register name: character + code in range [1, 255]. + Example: |quote0| register + has name 48 (ASCII code for + zero character). + * any none Other keys are allowed + for compatibility reasons, + see |shada-compatibility|. + 6 (Variable) Array containing two items: variable name (binary) and + variable value (any object). Values are converted + using the same code |msgpackparse()| uses when reading, + |msgpackdump()| when writing, so there may appear + |msgpack-special-dict|s. If there are more then two + entries then the rest are ignored + (|shada-compatibility|). + 7 (GlobalMark) + 8 (Jump) + 10 (LocalMark) + 11 (Change) Map containing some position description: + Entry Position ~ + GlobaMark Global mark position. |'A| + LocalMark Local mark position. |'a| + Jump One position from the |jumplist|. + Change One position from the |changelist|. + + Data contained in the map: + Key Type Default Description ~ + l UInteger 1 Position line number. Must be + greater then zero. + c UInteger 0 Position column number. + n UInteger 34 ('"') Mark name. Only valid for + GlobalMark and LocalMark + entries. + f Binary N/A File name. Required. + * any none Other keys are allowed for + compatibility reasons, see + |shada-compatibility|. + 9 (BufferList) Array containing maps. Each map in the array + represents one buffer. Possible keys: + Key Type Default Description ~ + l UInteger 1 Position line number. Must be + greater then zero. + c UInteger 0 Position column number. + f Binary N/A File name. Required. + * any none Other keys are allowed for + compatibility reasons, see + |shada-compatibility|. + * (Unknown) Any other entry type is allowed for compatibility + reasons, see |shada-compatibility|. + + *E575* *E576* +Errors in ShaDa file may have two types: E575 used for all “logical” errors +and E576 used for all “critical” errors. Critical errors trigger behaviour +described in |shada-error-handling| when writing and skipping the rest of the +file when reading and include: + +- Any of first three MessagePack objects being not an unsigned integer. +- Third object requesting amount of bytes greater then bytes left in the ShaDa + file. +- Entry with zero type. I.e. first object being equal to zero. +- MessagePack parser failing to parse the entry data. +- MessagePack parser consuming less or requesting greater bytes then described + in the third object for parsing fourth object. I.e. when fourth object + either contains more then one MessagePack object or it does not contain + complete MessagePack object. + vim:tw=78:ts=8:ft=help:norl: -- cgit From 1e067920994a5dd02df8f71f21afa29f0d4bcd21 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 8 Aug 2015 05:10:19 +0300 Subject: scripts: Improve shadacat to work with unknown items --- scripts/shadacat.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/scripts/shadacat.py b/scripts/shadacat.py index d29000a5d9..4ff493bfbc 100755 --- a/scripts/shadacat.py +++ b/scripts/shadacat.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3.4 +import os import sys import codecs @@ -58,18 +59,28 @@ def mnormalize(o): return ctable.get(type(o), idfunc)(o) -with open(sys.argv[1], 'rb') as fp: +fname = sys.argv[1] +poswidth = len(str(os.stat(fname).st_size or 1000)) + + +with open(fname, 'rb') as fp: unpacker = msgpack.Unpacker(file_like=fp, read_size=1) + max_type = max(typ.value for typ in EntryTypes) while True: try: pos = fp.tell() - typ = EntryTypes(unpacker.unpack()) + typ = unpacker.unpack() except msgpack.OutOfData: break else: timestamp = unpacker.unpack() time = datetime.fromtimestamp(timestamp) length = unpacker.unpack() - entry = unpacker.unpack() - print('{0:4} {1:13} {2} {3:5} {4!r}'.format( - pos, typ.name, time.isoformat(), length, mnormalize(entry))) + if typ > max_type: + entry = fp.read(length) + typ = EntryTypes.Unknown + else: + entry = unpacker.unpack() + typ = EntryTypes(typ) + print('%*u %13s %s %5u %r' % ( + poswidth, pos, typ.name, time.isoformat(), length, mnormalize(entry))) -- cgit From 1542fc221e52f31d5102730a222b5dbbae782dcd Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 8 Aug 2015 13:27:55 +0300 Subject: shada,functests: Improve detection of invalid ShaDa files It appears that large portion of non-ShaDa ASCII text files may be parsed as a ShaDa file because it is mostly recognized as a sequence of unknown entries: all ASCII non-control characters are recognized as FIXUINT shada objects, so text like #!/bin/sh powerline "$@" 2>&1 | tee -a powerline (with trailing newline) will be recognized as a correct ShaDa file containing single unknown entry with type 0x23 (dec 35, '#'), timestamp 0x21 (dec 33, '!') and length 0x2F (dec 47, '/') without this commit. With it parsing this entry will fail. --- src/nvim/shada.c | 200 ++++++++++++++++++++++------------ test/functional/shada/errors_spec.lua | 90 ++++++++++++++- 2 files changed, 216 insertions(+), 74 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 03dc10c533..bd3fd731e3 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -2001,6 +2001,93 @@ static int compare_file_marks(const void *a, const void *b) : 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; +} + /// Write ShaDa file /// /// @param[in] sd_writer Structure containing file writer definition. @@ -3258,9 +3345,23 @@ shada_read_next_item_start: ? !(flags & kSDReadUnknown) : !((unsigned) (1 << type_u64) & flags)) || (max_kbyte && length > max_kbyte * 1024)) { - const ShaDaReadResult fl_ret = fread_len(sd_reader, NULL, length); - if (fl_ret != kSDReadStatusSuccess) { - return fl_ret; + // 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 fl_ret = fread_len(sd_reader, NULL, length); + if (fl_ret != kSDReadStatusSuccess) { + return fl_ret; + } } goto shada_read_next_item_start; } @@ -3269,72 +3370,33 @@ shada_read_next_item_start: entry->type = kSDItemUnknown; entry->data.unknown_item.size = length; entry->data.unknown_item.type = type_u64; - 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; - } - - char *const buf = xmalloc(length); - - { - const ShaDaReadResult fl_ret = fread_len(sd_reader, buf, length); - if (fl_ret != kSDReadStatusSuccess) { - xfree(buf); + 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; - msgpack_unpacked_init(&unpacked); + char *buf = NULL; - bool did_try_to_free = false; -shada_read_next_item_read_next: {} - size_t off = 0; - const msgpack_unpack_return result = - msgpack_unpack_next(&unpacked, buf, length, &off); - ret = kSDReadStatusNotShaDa; - switch (result) { - case MSGPACK_UNPACK_SUCCESS: { - if (off < length) { - goto shada_read_next_item_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); - goto shada_read_next_item_error; - } - case MSGPACK_UNPACK_NOMEM_ERROR: { - if (!did_try_to_free) { - did_try_to_free = true; - try_to_free_memory(); - goto shada_read_next_item_read_next; - } - EMSG(_(e_outofmem)); - ret = kSDReadStatusReadError; - goto shada_read_next_item_error; - } - case MSGPACK_UNPACK_CONTINUE: { - emsgu(_(RCERR "Failed to parse ShaDa file: incomplete msgpack string " - "at position %" PRIu64), - (uint64_t) initial_fpos); - goto shada_read_next_item_error; - } - case MSGPACK_UNPACK_EXTRA_BYTES: { -shada_read_next_item_extra_bytes: - emsgu(_(RCERR "Failed to parse ShaDa file: extra bytes in msgpack string " - "at position %" PRIu64), - (uint64_t) initial_fpos); - goto shada_read_next_item_error; - } + 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; #define CHECK_KEY(key, expected) \ @@ -3968,7 +4030,6 @@ shada_read_next_item_hist_no_conv: } } entry->type = (ShadaEntryType) type_u64; - goto shada_read_next_item_end; #undef BIN_CONVERTED #undef CONVERTED #undef CHECK_KEY @@ -3989,17 +4050,16 @@ shada_read_next_item_hist_no_conv: #undef TOSIZE #undef SET_ADDITIONAL_DATA #undef SET_ADDITIONAL_ELEMENTS -shada_read_next_item_error: + ret = kSDReadStatusSuccess; +shada_read_next_item_end: 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; - return ret; -shada_read_next_item_end: - msgpack_unpacked_destroy(&unpacked); - xfree(buf); - return kSDReadStatusSuccess; + goto shada_read_next_item_end; } /// Check whether "name" is on removable media (according to 'shada') diff --git a/test/functional/shada/errors_spec.lua b/test/functional/shada/errors_spec.lua index e3a1dcdbb9..0dfb204b35 100644 --- a/test/functional/shada/errors_spec.lua +++ b/test/functional/shada/errors_spec.lua @@ -65,7 +65,7 @@ describe('ShaDa error handling', function() it('fails on search pattern item with zero length', function() wshada('\002\000\000') - eq('Vim(rshada):E576: Failed to parse ShaDa file: incomplete msgpack string at position 0', exc_exec(sdrcmd())) + eq('Vim(rshada):E576: Failed to parse ShaDa file: incomplete msgpack string at position 3', exc_exec(sdrcmd())) end) it('fails on search pattern item with -2 timestamp', function() @@ -95,12 +95,12 @@ describe('ShaDa error handling', function() -- get MSGPACK_UNPACK_PARSE_ERROR and not MSGPACK_UNPACK_CONTINUE or -- MSGPACK_UNPACK_EXTRA_BYTES. wshada('\002\000\001\193') - eq('Vim(rshada):E576: Failed to parse ShaDa file due to a msgpack parser error at position 0', exc_exec(sdrcmd())) + eq('Vim(rshada):E576: Failed to parse ShaDa file due to a msgpack parser error at position 3', exc_exec(sdrcmd())) end) it('fails on search pattern item with incomplete map', function() wshada('\002\000\001\129') - eq('Vim(rshada):E576: Failed to parse ShaDa file: incomplete msgpack string at position 0', exc_exec(sdrcmd())) + eq('Vim(rshada):E576: Failed to parse ShaDa file: incomplete msgpack string at position 3', exc_exec(sdrcmd())) end) it('fails on search pattern item without a pattern', function() @@ -110,7 +110,7 @@ describe('ShaDa error handling', function() it('fails on search pattern with extra bytes', function() wshada('\002\000\002\128\000') - eq('Vim(rshada):E576: Failed to parse ShaDa file: extra bytes in msgpack string at position 0', exc_exec(sdrcmd())) + eq('Vim(rshada):E576: Failed to parse ShaDa file: extra bytes in msgpack string at position 3', exc_exec(sdrcmd())) end) it('fails on search pattern item with NIL value', function() @@ -414,4 +414,86 @@ describe('ShaDa error handling', function() wshada('\009\000\017\146\129\161f\196\001/\130\161f\196\002/a\161c\192') eq('Vim(rshada):E575: Error while reading ShaDa file: buffer list entry entry at position 0 has c key value which is not an integer', exc_exec(sdrcmd())) end) + + it('fails on invalid ShaDa file (viminfo file)', function() + wshada([[# This viminfo file was generated by Vim 7.4. +# You may edit it if you're careful! + +# Value of 'encoding' when this file was written +*encoding=utf-8 + + +# hlsearch on (H) or off (h): +~h +# Last Search Pattern: +~MSle0~/buffer=abuf + +# Last Substitute Search Pattern: +~MSle0&^$ + +# Last Substitute String: +$ + +# Command Line History (newest to oldest): +:cq + +# Search String History (newest to oldest): +? \ + +# Expression History (newest to oldest): +=system('echo "\xAB"') + +# Input Line History (newest to oldest): +@i + +# Input Line History (newest to oldest): + +# Registers: +"0 LINE 0 + case FLAG_B: puts("B"); break; +"1 LINE 0 + pick 874a489 shada,functests: Test compatibility support +""- CHAR 0 + . + +# global variables: +!STUF_HISTORY_TRANSLIT LIS [] +!TR3_INPUT_HISTORY LIS [] + +# File marks: +'A 8320 12 ~/a.a/Proj/c/neovim-2076/src/nvim/ex_docmd.c +'0 66 5 ~/a.a/Proj/c/neovim/.git/rebase-merge/git-rebase-todo +'1 7 0 ~/.vam/powerline/.git/MERGE_MSG +'2 64 4 ~/a.a/Proj/c/neovim/.git/rebase-merge/git-rebase-todo +'3 9 0 ~/a.a/Proj/c/neovim/.git/COMMIT_EDITMSG +'4 62 0 ~/a.a/Proj/c/neovim/.git/rebase-merge/git-rebase-todo +'5 57 4 ~/a.a/Proj/c/neovim/.git/rebase-merge/git-rebase-todo +'6 1 0 ~/a.a/Proj/c/neovim/.git/rebase-merge/git-rebase-todo +'7 399 7 /usr/share/vim/vim74/doc/motion.txt +'8 1 0 ~/a.a/Proj/c/zpython/build/CMakeFiles/3.2.2/CMakeCCompiler.cmake +'9 1 0 ~/a.a/Proj/c/vim/README.txt + +# Jumplist (newest first): +-' 66 5 ~/a.a/Proj/c/neovim/.git/rebase-merge/git-rebase-todo + +# History of marks within files (newest to oldest): + +> ~/a.a/Proj/c/neovim/.git/rebase-merge/git-rebase-todo + " 66 5 + ^ 66 6 + . 66 5 + + 65 0 + + 65 0 +]]) + eq('Vim(rshada):E576: Failed to parse ShaDa file: extra bytes in msgpack string at position 3', exc_exec(sdrcmd())) + eq('Vim(wshada):E576: Failed to parse ShaDa file: extra bytes in msgpack string at position 3', exc_exec('wshada ' .. shada_fname)) + eq(0, exc_exec('wshada! ' .. shada_fname)) + end) + + it('fails on invalid ShaDa file (wrapper script)', function() + wshada('#!/bin/sh\n\npowerline "$@" 2>&1 | tee -a powerline\n') + eq('Vim(rshada):E576: Failed to parse ShaDa file: extra bytes in msgpack string at position 3', exc_exec(sdrcmd())) + eq('Vim(wshada):E576: Failed to parse ShaDa file: extra bytes in msgpack string at position 3', exc_exec('wshada ' .. shada_fname)) + eq(0, exc_exec('wshada! ' .. shada_fname)) + end) end) -- cgit From 42fbfd3aee79961ba68d4f45af5e1926d7aa97a0 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 8 Aug 2015 14:26:38 +0300 Subject: shada: Add warning about removing/renaming temporary file --- src/nvim/shada.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index bd3fd731e3..40037026a1 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -3001,11 +3001,13 @@ shada_write_file_nomerge: {} if (!nomerge) { sd_reader.close(&sd_reader); + bool did_remove = false; if (sw_ret == kSDWriteSuccessfull) { 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 { @@ -3017,6 +3019,10 @@ shada_write_file_nomerge: {} "during writing it"), tempname, fname); } } + if (!did_remove) { + EMSG3(_(RNERR "Do not forget to remove %s or rename it manually to %s."), + tempname, fname); + } xfree(tempname); } -- cgit From 17b5d27d85e772907fe3ca48fd8f58117564c72c Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 8 Aug 2015 14:54:47 +0300 Subject: functests: Move wshada and sdrcmd commands to helpers --- test/functional/shada/compatibility_spec.lua | 14 ++++---------- test/functional/shada/errors_spec.lua | 16 +++++----------- test/functional/shada/helpers.lua | 12 ++++++++++++ 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/test/functional/shada/compatibility_spec.lua b/test/functional/shada/compatibility_spec.lua index 5c48d82f4a..fba6f54feb 100644 --- a/test/functional/shada/compatibility_spec.lua +++ b/test/functional/shada/compatibility_spec.lua @@ -3,20 +3,14 @@ local helpers = require('test.functional.helpers') local nvim, nvim_window, nvim_curwin, nvim_command, nvim_feed, nvim_eval, eq = helpers.nvim, helpers.window, helpers.curwin, helpers.command, helpers.feed, helpers.eval, helpers.eq -local write_file = helpers.write_file local shada_helpers = require('test.functional.shada.helpers') -local reset, set_additional_cmd, clear, exc_exec = +local reset, set_additional_cmd, clear, exc_exec, get_shada_rw = shada_helpers.reset, shada_helpers.set_additional_cmd, - shada_helpers.clear, shada_helpers.exc_exec + shada_helpers.clear, shada_helpers.exc_exec, + shada_helpers.get_shada_rw -local shada_fname = 'Xtest-functional-shada-additional.shada' -local wshada = function(text) - write_file(shada_fname, text, true) -end -local sdrcmd = function(bang) - return 'rshada' .. (bang and '!' or '') .. ' ' .. shada_fname -end +local wshada, sdrcmd, shada_fname = get_shada_rw('Xtest-functional-shada-compatibility.shada') local msgpack = require('MessagePack') local mpack_keys = {'type', 'timestamp', 'length', 'value'} diff --git a/test/functional/shada/errors_spec.lua b/test/functional/shada/errors_spec.lua index 0dfb204b35..a53b5cee2b 100644 --- a/test/functional/shada/errors_spec.lua +++ b/test/functional/shada/errors_spec.lua @@ -3,20 +3,14 @@ local helpers = require('test.functional.helpers') local nvim, nvim_window, nvim_curwin, nvim_command, nvim_feed, nvim_eval, eq = helpers.nvim, helpers.window, helpers.curwin, helpers.command, helpers.feed, helpers.eval, helpers.eq -local write_file = helpers.write_file local shada_helpers = require('test.functional.shada.helpers') -local reset, set_additional_cmd, clear, exc_exec = +local reset, set_additional_cmd, clear, exc_exec, get_shada_rw = shada_helpers.reset, shada_helpers.set_additional_cmd, - shada_helpers.clear, shada_helpers.exc_exec - -local shada_fname = 'Xtest-functional-shada-errors.shada' -local wshada = function(text) - write_file(shada_fname, text, true) -end -local sdrcmd = function(bang) - return 'rshada' .. (bang and '!' or '') .. ' ' .. shada_fname -end + shada_helpers.clear, shada_helpers.exc_exec, + shada_helpers.get_shada_rw + +local wshada, sdrcmd, shada_fname = get_shada_rw('Xtest-functional-shada-errors.shada') describe('ShaDa error handling', function() before_each(reset) diff --git a/test/functional/shada/helpers.lua b/test/functional/shada/helpers.lua index 909fcd62e6..180b6f8c44 100644 --- a/test/functional/shada/helpers.lua +++ b/test/functional/shada/helpers.lua @@ -2,6 +2,7 @@ local helpers = require('test.functional.helpers') local spawn, set_session, nvim, nvim_prog, nvim_command, nvim_eval = helpers.spawn, helpers.set_session, helpers.nvim, helpers.nvim_prog, helpers.command, helpers.eval +local write_file = helpers.write_file local tmpname = os.tmpname() local additional_cmd = '' @@ -60,9 +61,20 @@ local exc_exec = function(cmd) return ret end +local get_shada_rw = function(fname) + local wshada = function(text) + write_file(fname, text, true) + end + local sdrcmd = function(bang) + return 'rshada' .. (bang and '!' or '') .. ' ' .. fname + end + return wshada, sdrcmd, fname +end + return { reset=reset, set_additional_cmd=set_additional_cmd, clear=clear, exc_exec=exc_exec, + get_shada_rw=get_shada_rw, } -- cgit From fd4d5521a3445b36fba69046ba6fb0caad8f398d Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 8 Aug 2015 19:47:32 +0300 Subject: shada,functests: Use special sd_reader function for skipping --- src/nvim/shada.c | 91 +++++++++++++++++++++++++++-------- test/functional/shada/errors_spec.lua | 7 +++ 2 files changed, 78 insertions(+), 20 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 40037026a1..98cd614d12 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -420,10 +420,16 @@ typedef ptrdiff_t (*ShaDaFileReader)(struct sd_read_def *const sd_reader, 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; ///< Reader function last argument. bool eof; ///< True if reader reached end of file. char *error; ///< Error message in case of error. @@ -696,6 +702,64 @@ static void close_sd_writer(ShaDaWriteDef *const sd_writer) 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(). @@ -746,6 +810,7 @@ static int open_shada_file_for_reading(const char *const fname, *sd_reader = (ShaDaReadDef) { .read = &read_file, .close = &close_sd_reader, + .skip = &sd_reader_skip_read, .error = NULL, .eof = false, .fpos = 0, @@ -3139,7 +3204,7 @@ static inline uint64_t be64toh(uint64_t big_endian_64_bits) /// 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. May be NULL. +/// @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 @@ -3148,23 +3213,9 @@ static inline uint64_t be64toh(uint64_t big_endian_64_bits) static ShaDaReadResult fread_len(ShaDaReadDef *const sd_reader, char *const buffer, const size_t length) - FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - ptrdiff_t read_bytes = 0; - if (buffer == NULL) { - do { - ptrdiff_t new_read_bytes = sd_reader->read( - sd_reader, IObuff, (size_t) (length - (size_t) read_bytes > IOSIZE - ? IOSIZE - : length - (size_t) read_bytes)); - if (new_read_bytes == -1) { - break; - } - read_bytes += new_read_bytes; - } while ((size_t) read_bytes < length && !sd_reader->eof); - } else { - read_bytes = sd_reader->read(sd_reader, buffer, length); - } + const ptrdiff_t read_bytes = sd_reader->read(sd_reader, buffer, length); if (sd_reader->error != NULL) { emsg2(_(SERR "System error while reading ShaDa file: %s"), @@ -3364,9 +3415,9 @@ shada_read_next_item_start: return spm_ret; } } else { - const ShaDaReadResult fl_ret = fread_len(sd_reader, NULL, length); - if (fl_ret != kSDReadStatusSuccess) { - return fl_ret; + const ShaDaReadResult srs_ret = sd_reader_skip(sd_reader, length); + if (srs_ret != kSDReadStatusSuccess) { + return srs_ret; } } goto shada_read_next_item_start; diff --git a/test/functional/shada/errors_spec.lua b/test/functional/shada/errors_spec.lua index a53b5cee2b..739f2ff148 100644 --- a/test/functional/shada/errors_spec.lua +++ b/test/functional/shada/errors_spec.lua @@ -490,4 +490,11 @@ $ eq('Vim(wshada):E576: Failed to parse ShaDa file: extra bytes in msgpack string at position 3', exc_exec('wshada ' .. shada_fname)) eq(0, exc_exec('wshada! ' .. shada_fname)) end) + + it('fails on invalid ShaDa file (failing skip in second item)', function() + wshada('\001\000\001\128#!/') + eq('Vim(rshada):E576: Error while reading ShaDa file: last entry specified that it occupies 47 bytes, but file ended earlier', exc_exec(sdrcmd())) + eq('Vim(wshada):E576: Error while reading ShaDa file: last entry specified that it occupies 47 bytes, but file ended earlier', exc_exec('wshada ' .. shada_fname)) + eq(0, exc_exec('wshada! ' .. shada_fname)) + end) end) -- cgit From bcdda63e3ab0fcc1e55f51702f78cf602da30a10 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 8 Aug 2015 20:41:39 +0300 Subject: shada: Allocate all hashes on stack --- src/nvim/lib/khash.h | 15 ++++++++++ src/nvim/shada.c | 83 +++++++++++++++++++--------------------------------- 2 files changed, 45 insertions(+), 53 deletions(-) diff --git a/src/nvim/lib/khash.h b/src/nvim/lib/khash.h index 17f653f45e..e45dcf2c80 100644 --- a/src/nvim/lib/khash.h +++ b/src/nvim/lib/khash.h @@ -653,4 +653,19 @@ typedef const char *kh_cstr_t; */ #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 /* __AC_KHASH_H */ diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 98cd614d12..c0f36d7fc8 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -862,15 +862,6 @@ static inline bool in_strset(const khash_t(strset) *const set, char *str) return kh_get(strset, set, str) != kh_end(set); } -/// Check whether buffer is on removable media -/// -/// Uses pre-populated set with buffers on removable media named removable_bufs. -/// -/// @param[in] buf Buffer to check. -/// -/// @return true or false. -#define SHADA_REMOVABLE(buf) in_bufset(removable_bufs, buf) - /// Msgpack callback for writing to ShaDaWriteDef* static int msgpack_sd_writer_write(void *data, const char *buf, size_t len) { @@ -1195,23 +1186,12 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) } } ShadaEntry cur_entry; - khash_t(bufset) *cl_bufs = NULL; - if (srni_flags & kSDReadChanges) { - cl_bufs = kh_init(bufset); - } - khash_t(fnamebufs) *fname_bufs = NULL; - if (srni_flags & (kSDReadUndisableableData - | kSDReadChanges - | kSDReadLocalMarks)) { - fname_bufs = kh_init(fnamebufs); - } - khash_t(strset) *oldfiles_set = NULL; - if (get_old_files) { - oldfiles_set = kh_init(strset); - if (oldfiles_list == NULL) { - oldfiles_list = list_alloc(); - set_vim_var_list(VV_OLDFILES, oldfiles_list); - } + 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) { + 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)) @@ -1334,7 +1314,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) } case kSDItemJump: case kSDItemGlobalMark: { - buf_T *buf = find_buffer(fname_bufs, cur_entry.data.filemark.fname); + 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; @@ -1444,8 +1424,8 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) } case kSDItemChange: case kSDItemLocalMark: { - if (oldfiles_set != NULL - && !in_strset(oldfiles_set, cur_entry.data.filemark.fname)) { + 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 @@ -1454,7 +1434,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) fname = xstrdup(fname); } int kh_ret; - (void) kh_put(strset, oldfiles_set, fname, &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. @@ -1465,7 +1445,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) shada_free_shada_entry(&cur_entry); break; } - buf_T *buf = find_buffer(fname_bufs, cur_entry.data.filemark.fname); + buf_T *buf = find_buffer(&fname_bufs, cur_entry.data.filemark.fname); if (buf == NULL) { shada_free_shada_entry(&cur_entry); break; @@ -1483,7 +1463,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) } } else { int kh_ret; - (void) kh_put(bufset, cl_bufs, (uintptr_t) buf, &kh_ret); + (void) kh_put(bufset, &cl_bufs, (uintptr_t) buf, &kh_ret); if (force) { if (buf->b_changelistlen == JUMPLISTSIZE) { free_fmark(buf->b_changelist[0]); @@ -1562,25 +1542,21 @@ shada_read_main_cycle_end: hms_dealloc(&hms[i]); } } - if (cl_bufs != NULL) { + if (cl_bufs.n_occupied) { FOR_ALL_TAB_WINDOWS(tp, wp) { (void) tp; - if (in_bufset(cl_bufs, wp->w_buffer)) { + if (in_bufset(&cl_bufs, wp->w_buffer)) { wp->w_changelistidx = wp->w_buffer->b_changelistlen; } } - kh_destroy(bufset, cl_bufs); - } - if (fname_bufs != NULL) { - const char *key; - kh_foreach_key(fname_bufs, key, { - xfree((void *) key); - }) - kh_destroy(fnamebufs, fname_bufs); - } - if (oldfiles_set != NULL) { - kh_destroy(strset, oldfiles_set); } + 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 @@ -2181,7 +2157,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, } const bool limit_reg_lines = max_reg_lines >= 0; const bool dump_registers = (max_reg_lines != 0); - khash_t(bufset) *const removable_bufs = kh_init(bufset); + 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; @@ -2218,7 +2194,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, 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); + (void) kh_put(bufset, &removable_bufs, (uintptr_t) buf, &kh_ret); } } @@ -2251,7 +2227,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, if (find_shada_parameter('%') != NULL) { size_t buf_count = 0; FOR_ALL_BUFFERS(buf) { - if (buf->b_ffname != NULL && !SHADA_REMOVABLE(buf)) { + if (buf->b_ffname != NULL && !in_bufset(&removable_bufs, buf)) { buf_count++; } } @@ -2269,7 +2245,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, }; size_t i = 0; FOR_ALL_BUFFERS(buf) { - if (buf->b_ffname == NULL || SHADA_REMOVABLE(buf)) { + if (buf->b_ffname == NULL || in_bufset(&removable_bufs, buf)) { continue; } buflist_entry.data.buffer_list.buffers[i] = (struct buffer_list_buffer) { @@ -2410,7 +2386,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ? NULL : buflist_findnr(fm.fmark.fnum)); if (buf != NULL - ? SHADA_REMOVABLE(buf) + ? in_bufset(&removable_bufs, buf) : fm.fmark.fnum != 0) { continue; } @@ -2458,7 +2434,8 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, fname = (const char *) fm.fname; } else { const buf_T *const buf = buflist_findnr(fm.fmark.fnum); - if (buf == NULL || buf->b_ffname == NULL || SHADA_REMOVABLE(buf)) { + if (buf == NULL || buf->b_ffname == NULL + || in_bufset(&removable_bufs, buf)) { continue; } fname = (const char *) buf->b_ffname; @@ -2517,7 +2494,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, // Initialize buffers if (num_marked_files > 0) { FOR_ALL_BUFFERS(buf) { - if (buf->b_ffname == NULL || SHADA_REMOVABLE(buf)) { + if (buf->b_ffname == NULL || in_bufset(&removable_bufs, buf)) { continue; } const void *local_marks_iter = NULL; @@ -2927,7 +2904,7 @@ shada_write_main_cycle_end: shada_write_exit: kh_dealloc(file_marks, &wms->file_marks); - kh_destroy(bufset, removable_bufs); + kh_dealloc(bufset, &removable_bufs); msgpack_packer_free(packer); kh_dealloc(strset, &wms->dumped_variables); xfree(wms); -- cgit From c5554cbb87de7edcbdb2f2f8eb7d1153dee2ba1d Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 9 Aug 2015 03:18:50 +0300 Subject: shada: Use hash for searching for history entries --- src/nvim/shada.c | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index c0f36d7fc8..8789ab8772 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -350,6 +350,8 @@ typedef struct hm_llist_entry { 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 @@ -362,6 +364,9 @@ typedef struct { size_t size; ///< Number of allocated entries. size_t free_entries_size; ///< Number of non-NULL entries in free_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 { @@ -480,6 +485,7 @@ static inline void hmll_init(HMLList *const hmll, const size_t size) .size = size, .free_entries_size = 0, .num_entries = 0, + .contained_entries = KHASH_EMPTY_TABLE(hmll_entries), }; hmll->last_free_element = hmll->entries; } @@ -512,6 +518,10 @@ static inline void hmll_remove(HMLList *const hmll, } else { hmll->free_entries[hmll->free_entries_size++] = 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 { @@ -558,6 +568,12 @@ static inline void hmll_insert(HMLList *const hmll, } 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) { @@ -591,6 +607,7 @@ static inline void hmll_insert(HMLList *const hmll, static inline void hmll_dealloc(HMLList *const hmll) FUNC_ATTR_NONNULL_ALL { + kh_dealloc(hmll_entries, &hmll->contained_entries); xfree(hmll->entries); xfree(hmll->free_entries); } @@ -985,14 +1002,14 @@ static void hms_insert(HistoryMergerState *const hms_p, const ShadaEntry entry, const bool no_iter, const bool can_free_entry) { HMLList *const hmll = &hms_p->hmll; - HMLL_FORALL(hmll, cur_entry) { - if (STRCMP(cur_entry->data.data.history_item.string, - entry.data.history_item.string) == 0) { - if (entry.timestamp > cur_entry->data.timestamp) { - hmll_remove(hmll, cur_entry); - } else { - return; - } + 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 cur_entry = kh_val(&hmll->contained_entries, k); + if (entry.timestamp > cur_entry->data.timestamp) { + hmll_remove(hmll, cur_entry); + } else { + return; } } if (!no_iter) { -- cgit From 830c8bd23e693a9081ea7ebeb11b415822b1e3b3 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 9 Aug 2015 18:41:25 +0300 Subject: functests: Add a number of “generic” functional tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/functional/shada/compatibility_spec.lua | 24 +---- test/functional/shada/errors_spec.lua | 12 +-- test/functional/shada/helpers.lua | 38 +++++++- test/functional/shada/shada_spec.lua | 130 +++++++++++++++++++++++++++ 4 files changed, 171 insertions(+), 33 deletions(-) create mode 100644 test/functional/shada/shada_spec.lua diff --git a/test/functional/shada/compatibility_spec.lua b/test/functional/shada/compatibility_spec.lua index fba6f54feb..45d747658a 100644 --- a/test/functional/shada/compatibility_spec.lua +++ b/test/functional/shada/compatibility_spec.lua @@ -9,32 +9,10 @@ local reset, set_additional_cmd, clear, exc_exec, get_shada_rw = shada_helpers.reset, shada_helpers.set_additional_cmd, shada_helpers.clear, shada_helpers.exc_exec, shada_helpers.get_shada_rw +local read_shada_file = shada_helpers.read_shada_file local wshada, sdrcmd, shada_fname = get_shada_rw('Xtest-functional-shada-compatibility.shada') -local msgpack = require('MessagePack') -local mpack_keys = {'type', 'timestamp', 'length', 'value'} -local read_shada_file = function(fname) - local fd = io.open(fname, 'r') - local mstring = fd:read('*a') - fd:close() - local unpacker = msgpack.unpacker(mstring) - local ret = {} - local cur - local i = 0 - while true do - local off, val = unpacker() - if not off then break end - if i % 4 == 0 then - cur = {} - ret[#ret + 1] = cur - end - cur[mpack_keys[(i % 4) + 1]] = val - i = i + 1 - end - return ret -end - describe('ShaDa forward compatibility support code', function() before_each(reset) after_each(function() diff --git a/test/functional/shada/errors_spec.lua b/test/functional/shada/errors_spec.lua index 739f2ff148..ce849fdcbd 100644 --- a/test/functional/shada/errors_spec.lua +++ b/test/functional/shada/errors_spec.lua @@ -10,20 +10,14 @@ local reset, set_additional_cmd, clear, exc_exec, get_shada_rw = shada_helpers.clear, shada_helpers.exc_exec, shada_helpers.get_shada_rw -local wshada, sdrcmd, shada_fname = get_shada_rw('Xtest-functional-shada-errors.shada') +local wshada, sdrcmd, shada_fname, clean = + get_shada_rw('Xtest-functional-shada-errors.shada') describe('ShaDa error handling', function() before_each(reset) after_each(function() clear() - os.remove(shada_fname) - local i = ('a'):byte() - while i < ('z'):byte() do - if not os.remove(shada_fname .. ('.tmp.%c'):format(i)) then - break - end - i = i + 1 - end + clean() end) -- Note: most of tests have additional items like sX, mX, rX. These are for diff --git a/test/functional/shada/helpers.lua b/test/functional/shada/helpers.lua index 180b6f8c44..d37e84f156 100644 --- a/test/functional/shada/helpers.lua +++ b/test/functional/shada/helpers.lua @@ -4,6 +4,8 @@ local spawn, set_session, nvim, nvim_prog, nvim_command, nvim_eval = helpers.command, helpers.eval local write_file = helpers.write_file +local msgpack = require('MessagePack') + local tmpname = os.tmpname() local additional_cmd = '' @@ -68,7 +70,40 @@ local get_shada_rw = function(fname) local sdrcmd = function(bang) return 'rshada' .. (bang and '!' or '') .. ' ' .. fname end - return wshada, sdrcmd, fname + local clean = function() + os.remove(fname) + local i = ('a'):byte() + while i <= ('z'):byte() do + if not os.remove(fname .. ('.tmp.%c'):format(i)) then + break + end + i = i + 1 + end + end + return wshada, sdrcmd, fname, clean +end + +local mpack_keys = {'type', 'timestamp', 'length', 'value'} + +local read_shada_file = function(fname) + local fd = io.open(fname, 'r') + local mstring = fd:read('*a') + fd:close() + local unpacker = msgpack.unpacker(mstring) + local ret = {} + local cur + local i = 0 + while true do + local off, val = unpacker() + if not off then break end + if i % 4 == 0 then + cur = {} + ret[#ret + 1] = cur + end + cur[mpack_keys[(i % 4) + 1]] = val + i = i + 1 + end + return ret end return { @@ -77,4 +112,5 @@ return { clear=clear, exc_exec=exc_exec, get_shada_rw=get_shada_rw, + read_shada_file=read_shada_file, } diff --git a/test/functional/shada/shada_spec.lua b/test/functional/shada/shada_spec.lua new file mode 100644 index 0000000000..19b8a47244 --- /dev/null +++ b/test/functional/shada/shada_spec.lua @@ -0,0 +1,130 @@ +-- Other ShaDa tests +local helpers = require('test.functional.helpers') +local nvim, nvim_window, nvim_curwin, nvim_command, nvim_feed, nvim_eval, eq = + helpers.nvim, helpers.window, helpers.curwin, helpers.command, helpers.feed, + helpers.eval, helpers.eq +local write_file = helpers.write_file +local lfs = require('lfs') + +local msgpack = require('MessagePack') + +local shada_helpers = require('test.functional.shada.helpers') +local reset, set_additional_cmd, clear, exc_exec, get_shada_rw = + shada_helpers.reset, shada_helpers.set_additional_cmd, + shada_helpers.clear, shada_helpers.exc_exec, + shada_helpers.get_shada_rw +local read_shada_file = shada_helpers.read_shada_file + +local wshada, sdrcmd, shada_fname, clean = get_shada_rw('Xtest-functional-shada-shada.shada') + +describe('ShaDa support code', function() + before_each(reset) + after_each(function() + clear() + clean() + end) + + it('preserves `s` item size limit with unknown entries', function() + wshada('\100\000\207\000\000\000\000\000\000\004\000\218\003\253' .. ('-'):rep(1024 - 3) + .. '\100\000\207\000\000\000\000\000\000\004\001\218\003\254' .. ('-'):rep(1025 - 3)) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 100 then + found = found + 1 + end + end + eq(2, found) + eq(0, exc_exec('set shada-=s10 shada+=s1')) + eq(0, exc_exec('wshada ' .. shada_fname)) + found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 100 then + found = found + 1 + end + end + eq(1, found) + end) + + it('preserves `s` item size limit with instance history entries', function() + local hist1 = ('-'):rep(1024 - 5) + local hist2 = ('-'):rep(1025 - 5) + nvim_command('set shada-=s10 shada+=s1') + nvim_eval(('histadd(":", "%s")'):format(hist1)) + nvim_eval(('histadd(":", "%s")'):format(hist2)) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 4 then + found = found + 1 + eq(hist1, v.value[2]) + end + end + eq(1, found) + end) + + it('leaves .tmp.a in-place when there is error in original ShaDa', function() + wshada('Some text file') + eq('Vim(wshada):E576: Error while reading ShaDa file: last entry specified that it occupies 109 bytes, but file ended earlier', exc_exec('wshada ' .. shada_fname)) + eq(1, read_shada_file(shada_fname .. '.tmp.a')[1].type) + end) + + it('does not leave .tmp.a in-place when there is error in original ShaDa, but writing with bang', function() + wshada('Some text file') + eq(0, exc_exec('wshada! ' .. shada_fname)) + eq(1, read_shada_file(shada_fname)[1].type) + eq(nil, lfs.attributes(shada_fname .. '.tmp.a')) + end) + + it('leaves .tmp.b in-place when there is error in original ShaDa and it has .tmp.a', function() + wshada('Some text file') + eq('Vim(wshada):E576: Error while reading ShaDa file: last entry specified that it occupies 109 bytes, but file ended earlier', exc_exec('wshada ' .. shada_fname)) + eq('Vim(wshada):E576: Error while reading ShaDa file: last entry specified that it occupies 109 bytes, but file ended earlier', exc_exec('wshada ' .. shada_fname)) + eq(1, read_shada_file(shada_fname .. '.tmp.a')[1].type) + eq(1, read_shada_file(shada_fname .. '.tmp.b')[1].type) + end) + + it('leaves .tmp.z in-place when there is error in original ShaDa and it has .tmp.a … .tmp.x', function() + wshada('Some text file') + local i = ('a'):byte() + while i < ('z'):byte() do + write_file(shada_fname .. ('.tmp.%c'):format(i), 'Some text file', true) + i = i + 1 + end + eq('Vim(wshada):E576: Error while reading ShaDa file: last entry specified that it occupies 109 bytes, but file ended earlier', exc_exec('wshada ' .. shada_fname)) + eq(1, read_shada_file(shada_fname .. '.tmp.z')[1].type) + end) + + it('errors out when there are .tmp.a … .tmp.z ShaDa files', function() + wshada('') + local i = ('a'):byte() + while i <= ('z'):byte() do + write_file(shada_fname .. ('.tmp.%c'):format(i), '', true) + i = i + 1 + end + eq('Vim(wshada):E138: All Xtest-functional-shada-shada.shada.tmp.X files exist, cannot write ShaDa file!', exc_exec('wshada ' .. shada_fname)) + end) + + it('reads correctly various timestamps', function() + local mpack = { + '\100', -- Positive fixnum 100 + '\204\255', -- uint 8 255 + '\205\010\003', -- uint 16 2563 + '\206\255\010\030\004', -- uint 32 4278853124 + '\207\005\100\060\250\255\010\030\004', -- uint 64 388502516579048964 + } + local s = '\100' + local e = '\001\192' + wshada(s .. table.concat(mpack, e .. s) .. e) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + local typ = select(2, msgpack.unpacker(s)()) + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == typ then + found = found + 1 + eq(select(2, msgpack.unpacker(mpack[found])()), v.timestamp) + end + end + eq(#mpack, found) + end) +end) -- cgit From 4371842bcc0e5384d29a25a495c66691dae0cec5 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 9 Aug 2015 21:22:27 +0300 Subject: shada: Call regtilde when reading last replacement string --- src/nvim/shada.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 8789ab8772..2a2fb07b89 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -41,6 +41,7 @@ #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" @@ -85,6 +86,7 @@ KHASH_SET_INIT_STR(strset) #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)) // From http://www.boost.org/doc/libs/1_43_0/boost/detail/endian.hpp + some @@ -1289,6 +1291,11 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) .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; } -- cgit From c9963872ef11f536e9728bf3e709ab39fe5056ec Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 10 Aug 2015 01:28:48 +0300 Subject: shada: Always use merging when reading jumps and changes --- src/nvim/shada.c | 175 ++++++++++++++++++++++++------------------------------- 1 file changed, 75 insertions(+), 100 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 2a2fb07b89..7ec954080b 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1360,68 +1360,54 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) break; } } else { - if (force) { - if (curwin->w_jumplistlen == JUMPLISTSIZE) { - // Jump list items are ignored in this case. - free_xfmark(fm); - } else { - memmove(&curwin->w_jumplist[1], &curwin->w_jumplist[0], - sizeof(curwin->w_jumplist[0]) - * (size_t) curwin->w_jumplistlen); - curwin->w_jumplistidx++; - curwin->w_jumplistlen++; - curwin->w_jumplist[0] = fm; - } - } else { - const int jl_len = curwin->w_jumplistlen; - int i; - for (i = 0; i < jl_len; i++) { - const xfmark_T jl_fm = curwin->w_jumplist[i]; - if (jl_fm.fmark.timestamp >= cur_entry.timestamp) { - if (marks_equal(fm.fmark.mark, jl_fm.fmark.mark) - && (buf == NULL - ? (jl_fm.fname != NULL - && STRCMP(fm.fname, jl_fm.fname) == 0) - : fm.fmark.fnum == jl_fm.fmark.fnum)) { - i = -1; - } - break; - } - } - if (i != -1) { - if (i < jl_len) { - if (jl_len == JUMPLISTSIZE) { - free_xfmark(curwin->w_jumplist[0]); - memmove(&curwin->w_jumplist[0], &curwin->w_jumplist[1], - sizeof(curwin->w_jumplist[0]) * (size_t) i); - } else { - memmove(&curwin->w_jumplist[i + 1], &curwin->w_jumplist[i], - sizeof(curwin->w_jumplist[0]) - * (size_t) (jl_len - i)); - } - } else if (i == jl_len) { - if (jl_len == JUMPLISTSIZE) { - i = -1; - } else if (jl_len > 0) { - memmove(&curwin->w_jumplist[1], &curwin->w_jumplist[0], - sizeof(curwin->w_jumplist[0]) - * (size_t) jl_len); - } + const int jl_len = curwin->w_jumplistlen; + int i; + for (i = 0; i < jl_len; i++) { + const xfmark_T jl_fm = curwin->w_jumplist[i]; + if (jl_fm.fmark.timestamp >= cur_entry.timestamp) { + if (marks_equal(fm.fmark.mark, jl_fm.fmark.mark) + && (buf == NULL + ? (jl_fm.fname != NULL + && STRCMP(fm.fname, jl_fm.fname) == 0) + : fm.fmark.fnum == jl_fm.fmark.fnum)) { + i = -1; } + break; } - if (i != -1) { - curwin->w_jumplist[i] = fm; - if (jl_len < JUMPLISTSIZE) { - curwin->w_jumplistlen++; + } + if (i != -1) { + if (i < jl_len) { + if (jl_len == JUMPLISTSIZE) { + free_xfmark(curwin->w_jumplist[0]); + memmove(&curwin->w_jumplist[0], &curwin->w_jumplist[1], + sizeof(curwin->w_jumplist[0]) * (size_t) i); + } else { + memmove(&curwin->w_jumplist[i + 1], &curwin->w_jumplist[i], + sizeof(curwin->w_jumplist[0]) + * (size_t) (jl_len - i)); } - if (curwin->w_jumplistidx > i - && curwin->w_jumplistidx + 1 < curwin->w_jumplistlen) { - curwin->w_jumplistidx++; + } else if (i == jl_len) { + if (jl_len == JUMPLISTSIZE) { + i = -1; + } else if (jl_len > 0) { + memmove(&curwin->w_jumplist[1], &curwin->w_jumplist[0], + sizeof(curwin->w_jumplist[0]) + * (size_t) jl_len); } - } else { - shada_free_shada_entry(&cur_entry); } } + if (i != -1) { + curwin->w_jumplist[i] = fm; + if (jl_len < JUMPLISTSIZE) { + curwin->w_jumplistlen++; + } + if (curwin->w_jumplistidx > i + && curwin->w_jumplistidx + 1 < curwin->w_jumplistlen) { + curwin->w_jumplistidx++; + } + } else { + shada_free_shada_entry(&cur_entry); + } } // Do not free shada entry: its allocated memory was saved above. break; @@ -1488,55 +1474,44 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) } else { int kh_ret; (void) kh_put(bufset, &cl_bufs, (uintptr_t) buf, &kh_ret); - if (force) { - if (buf->b_changelistlen == JUMPLISTSIZE) { - free_fmark(buf->b_changelist[0]); - memmove(buf->b_changelist, buf->b_changelist + 1, - sizeof(buf->b_changelist[0]) * (JUMPLISTSIZE - 1)); - } else { - buf->b_changelistlen++; - } - buf->b_changelist[buf->b_changelistlen - 1] = fm; - } else { - const int cl_len = buf->b_changelistlen; - int i; - for (i = cl_len; i > 0; i--) { - const fmark_T cl_fm = buf->b_changelist[i - 1]; - if (cl_fm.timestamp <= cur_entry.timestamp) { - if (marks_equal(fm.mark, cl_fm.mark)) { - i = -1; - } - break; - } - } - if (i > 0) { - if (cl_len == JUMPLISTSIZE) { - free_fmark(buf->b_changelist[0]); - memmove(&buf->b_changelist[0], &buf->b_changelist[1], - sizeof(buf->b_changelist[0]) * (size_t) i); - } else { - memmove(&buf->b_changelist[i + 1], &buf->b_changelist[i], - sizeof(buf->b_changelist[0]) - * (size_t) (cl_len - i)); - } - } else if (i == 0) { - if (cl_len == JUMPLISTSIZE) { + const int cl_len = buf->b_changelistlen; + int i; + for (i = cl_len; i > 0; i--) { + const fmark_T cl_fm = buf->b_changelist[i - 1]; + if (cl_fm.timestamp <= cur_entry.timestamp) { + if (marks_equal(fm.mark, cl_fm.mark)) { i = -1; - } else if (cl_len > 0) { - memmove(&buf->b_changelist[1], &buf->b_changelist[0], - sizeof(buf->b_changelist[0]) - * (size_t) cl_len); } + break; } - if (i != -1) { - buf->b_changelist[i] = fm; - if (cl_len < JUMPLISTSIZE) { - buf->b_changelistlen++; - } + } + if (i > 0) { + if (cl_len == JUMPLISTSIZE) { + free_fmark(buf->b_changelist[0]); + memmove(&buf->b_changelist[0], &buf->b_changelist[1], + sizeof(buf->b_changelist[0]) * (size_t) i); } else { - shada_free_shada_entry(&cur_entry); - cur_entry.data.filemark.fname = NULL; + memmove(&buf->b_changelist[i + 1], &buf->b_changelist[i], + sizeof(buf->b_changelist[0]) + * (size_t) (cl_len - i)); + } + } else if (i == 0) { + if (cl_len == JUMPLISTSIZE) { + i = -1; + } else if (cl_len > 0) { + memmove(&buf->b_changelist[1], &buf->b_changelist[0], + sizeof(buf->b_changelist[0]) + * (size_t) cl_len); + } + } + if (i != -1) { + buf->b_changelist[i] = fm; + if (cl_len < JUMPLISTSIZE) { + buf->b_changelistlen++; } + } else { + shada_free_shada_entry(&cur_entry); + cur_entry.data.filemark.fname = NULL; } } // Do not free shada entry: except for fname, its allocated memory (i.e. -- cgit From 4dc3bc8fc1d94f9f97ab06d030b9279e7e3b5934 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 10 Aug 2015 01:39:50 +0300 Subject: shada,functests: Make sure that v:oldfiles list is reset on :rshada! --- src/nvim/shada.c | 2 +- test/functional/shada/marks_spec.lua | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 7ec954080b..8f731e7eda 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1208,7 +1208,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) 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) { + if (get_old_files && (oldfiles_list == NULL || force)) { oldfiles_list = list_alloc(); set_vim_var_list(VV_OLDFILES, oldfiles_list); } diff --git a/test/functional/shada/marks_spec.lua b/test/functional/shada/marks_spec.lua index 341ca2d647..8b9e70045c 100644 --- a/test/functional/shada/marks_spec.lua +++ b/test/functional/shada/marks_spec.lua @@ -102,6 +102,13 @@ describe('ShaDa support code', function() eq(testfilename_2, oldfiles[2]:sub(-#testfilename_2)) eq(tf_full, oldfiles[1]) eq(tf_full_2, oldfiles[2]) + nvim_command('rshada!') + local oldfiles = nvim('get_vvar', 'oldfiles') + eq(2, #oldfiles) + eq(testfilename, oldfiles[1]:sub(-#testfilename)) + eq(testfilename_2, oldfiles[2]:sub(-#testfilename_2)) + eq(tf_full, oldfiles[1]) + eq(tf_full_2, oldfiles[2]) end) it('is able to dump and restore jump list', function() -- cgit From fcb3e96cbdaa49472abde3cbf8792c12a46e2082 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 10 Aug 2015 02:29:16 +0300 Subject: undo: When reading persistent undo file give marks current time Guess this is better then zero time which will give ShaDa marks the priority almost always. --- src/nvim/undo.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/nvim/undo.c b/src/nvim/undo.c index b2f71432dc..2b0ffefa7e 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -841,9 +841,10 @@ 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].mark); - uhp->uh_namedm[i].timestamp = 0; + uhp->uh_namedm[i].timestamp = cur_timestamp; uhp->uh_namedm[i].fnum = 0; } unserialize_visualinfo(bi, &uhp->uh_visual); -- cgit From 0a334f9d33e2b68ce39216207b574d894dd182f4 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 11 Aug 2015 01:28:05 +0300 Subject: shada: Merge changes regardless whether they are from current instance --- src/nvim/shada.c | 75 +++++++++++++++++++++++++++----------------------------- 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 8f731e7eda..6551e319d2 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -394,7 +394,6 @@ typedef struct { 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. - bool is_local_entry; ///< True if structure comes from the current session. } FileMarks; KHASH_MAP_INIT_STR(file_marks, FileMarks) @@ -2506,7 +2505,6 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, kh_key(&wms->file_marks, k) = xstrdup(fname); memset(filemarks, 0, sizeof(*filemarks)); } - filemarks->is_local_entry = true; do { fmark_T fm; char name; @@ -2701,48 +2699,47 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, COMPARE_WITH_ENTRY(&filemarks->marks[idx], entry); } } else { - if (filemarks->is_local_entry) { - shada_free_shada_entry(&entry); - } else { - const int cl_len = (int) filemarks->changes_size; - int i; - for (i = cl_len; i > 0; i--) { - const ShadaEntry old_entry = filemarks->changes[i - 1].data; - if (old_entry.timestamp <= entry.timestamp) { - if (marks_equal(old_entry.data.filemark.mark, - entry.data.filemark.mark)) { - i = -1; - } - break; - } - } - if (i > 0) { - if (cl_len == JUMPLISTSIZE) { - if (filemarks->changes[0].can_free_entry) { - shada_free_shada_entry(&filemarks->changes[0].data); - } - memmove(&filemarks->changes[0], &filemarks->changes[1], - sizeof(filemarks->changes[0]) * (size_t) i); - } else if (i == 0) { - if (cl_len == JUMPLISTSIZE) { - i = -1; - } else { - memmove(&filemarks->changes[1], &filemarks->changes[0], - sizeof(filemarks->changes[0]) * (size_t) cl_len); - } + const int cl_len = (int) filemarks->changes_size; + int i; + for (i = cl_len; i > 0; i--) { + const ShadaEntry old_entry = filemarks->changes[i - 1].data; + if (old_entry.timestamp <= entry.timestamp) { + if (marks_equal(old_entry.data.filemark.mark, + entry.data.filemark.mark)) { + i = -1; } + break; } - if (i != -1) { - filemarks->changes[i] = (PossiblyFreedShadaEntry) { - .can_free_entry = true, - .data = entry - }; - if (cl_len < JUMPLISTSIZE) { - filemarks->changes_size++; + } + if (i > 0) { + if (cl_len == JUMPLISTSIZE) { + if (filemarks->changes[0].can_free_entry) { + shada_free_shada_entry(&filemarks->changes[0].data); } + memmove(&filemarks->changes[0], &filemarks->changes[1], + sizeof(filemarks->changes[0]) * (size_t) i); } else { - shada_free_shada_entry(&entry); + memmove(&filemarks->changes[i + 1], &filemarks->changes[i], + sizeof(filemarks->changes[0]) * (size_t) (cl_len - i)); } + } else if (i == 0) { + if (cl_len == JUMPLISTSIZE) { + i = -1; + } else if (cl_len > 0) { + memmove(&filemarks->changes[1], &filemarks->changes[0], + sizeof(filemarks->changes[0]) * (size_t) cl_len); + } + } + if (i != -1) { + filemarks->changes[i] = (PossiblyFreedShadaEntry) { + .can_free_entry = true, + .data = entry + }; + if (cl_len < JUMPLISTSIZE) { + filemarks->changes_size++; + } + } else { + shada_free_shada_entry(&entry); } } break; -- cgit From 74d5084139423177c88e2c49a74d04afb6c00fb2 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 8 Aug 2015 15:00:52 +0300 Subject: shada,functests: Add tests for merging ShaDa data --- src/nvim/mark.c | 6 +- src/nvim/ops.c | 16 +- src/nvim/shada.c | 56 ++- test/functional/shada/merging_spec.lua | 867 +++++++++++++++++++++++++++++++++ 4 files changed, 921 insertions(+), 24 deletions(-) create mode 100644 test/functional/shada/merging_spec.lua diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 7e1ae42c47..6ab0403e30 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -1175,13 +1175,13 @@ const void *mark_jumplist_iter(const void *const iter, const win_T *const win, } const xfmark_T *const iter_mark = (iter == NULL - ? &(win->w_jumplist[win->w_jumplistlen - 1]) + ? &(win->w_jumplist[0]) : (const xfmark_T *const) iter); *fm = *iter_mark; - if (iter_mark == &(win->w_jumplist[0])) { + if (iter_mark == &(win->w_jumplist[win->w_jumplistlen - 1])) { return NULL; } else { - return iter_mark - 1; + return iter_mark + 1; } } diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 04c53d30bb..da1eb7afcb 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -5373,7 +5373,7 @@ size_t op_register_amount(void) /// @param[in] reg Register value. /// /// @return true on success, false on failure. -bool register_set(const char name, const yankreg_T reg) +bool op_register_set(const char name, const yankreg_T reg) { int i = op_reg_index(name); if (i == -1) { @@ -5382,3 +5382,17 @@ bool register_set(const char name, const yankreg_T reg) 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/shada.c b/src/nvim/shada.c index 6551e319d2..67f299b8f9 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -997,23 +997,13 @@ static const void *shada_hist_iter(const void *const iter, /// @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. +/// 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 no_iter, const bool can_free_entry) + const bool do_iter, const bool can_free_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 cur_entry = kh_val(&hmll->contained_entries, k); - if (entry.timestamp > cur_entry->data.timestamp) { - hmll_remove(hmll, cur_entry); - } else { - return; - } - } - if (!no_iter) { + if (do_iter) { if (hms_p->iter == NULL) { if (hms_p->last_hist_entry.type != kSDItemMissing && hms_p->last_hist_entry.timestamp < entry.timestamp) { @@ -1031,6 +1021,27 @@ static void hms_insert(HistoryMergerState *const hms_p, const ShadaEntry 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) { @@ -1315,7 +1326,14 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) shada_free_shada_entry(&cur_entry); break; } - if (!register_set(cur_entry.data.reg.name, (yankreg_T) { + 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, @@ -1400,7 +1418,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) if (jl_len < JUMPLISTSIZE) { curwin->w_jumplistlen++; } - if (curwin->w_jumplistidx > i + if (curwin->w_jumplistidx >= i && curwin->w_jumplistidx + 1 < curwin->w_jumplistlen) { curwin->w_jumplistidx++; } @@ -1491,16 +1509,14 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) sizeof(buf->b_changelist[0]) * (size_t) i); } else { memmove(&buf->b_changelist[i + 1], &buf->b_changelist[i], - sizeof(buf->b_changelist[0]) - * (size_t) (cl_len - i)); + sizeof(buf->b_changelist[0]) * (size_t) (cl_len - i)); } } else if (i == 0) { if (cl_len == JUMPLISTSIZE) { i = -1; } else if (cl_len > 0) { memmove(&buf->b_changelist[1], &buf->b_changelist[0], - sizeof(buf->b_changelist[0]) - * (size_t) cl_len); + sizeof(buf->b_changelist[0]) * (size_t) cl_len); } } if (i != -1) { diff --git a/test/functional/shada/merging_spec.lua b/test/functional/shada/merging_spec.lua new file mode 100644 index 0000000000..e627f8f76e --- /dev/null +++ b/test/functional/shada/merging_spec.lua @@ -0,0 +1,867 @@ +-- ShaDa merging data support +local helpers = require('test.functional.helpers') +local nvim, nvim_window, nvim_curwin, nvim_command, nvim_feed, nvim_eval, eq = + helpers.nvim, helpers.window, helpers.curwin, helpers.command, helpers.feed, + helpers.eval, helpers.eq + +local shada_helpers = require('test.functional.shada.helpers') +local reset, set_additional_cmd, clear, exc_exec, get_shada_rw = + shada_helpers.reset, shada_helpers.set_additional_cmd, + shada_helpers.clear, shada_helpers.exc_exec, + shada_helpers.get_shada_rw +local read_shada_file = shada_helpers.read_shada_file + +local wshada, sdrcmd, shada_fname = + get_shada_rw('Xtest-functional-shada-merging.shada') + +describe('ShaDa history merging code', function() + before_each(reset) + after_each(function() + clear() + os.remove(shada_fname) + end) + + it('takes item with greater timestamp from NeoVim instance when reading', + function() + wshada('\004\001\009\147\000\196\002ab\196\001a') + eq(0, exc_exec(sdrcmd())) + wshada('\004\000\009\147\000\196\002ab\196\001b') + eq(0, exc_exec(sdrcmd())) + os.remove(shada_fname) + eq(0, exc_exec('wshada! ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 4 and v.value[1] == 0 and v.value[2] == 'ab' then + eq(1, v.timestamp) + eq('a', v.value[3]) + found = found + 1 + end + end + eq(1, found) + end) + + it('takes item with equal timestamp from NeoVim instance when reading', + function() + wshada('\004\000\009\147\000\196\002ab\196\001a') + eq(0, exc_exec(sdrcmd())) + wshada('\004\000\009\147\000\196\002ab\196\001b') + eq(0, exc_exec(sdrcmd())) + os.remove(shada_fname) + eq(0, exc_exec('wshada! ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 4 and v.value[1] == 0 and v.value[2] == 'ab' then + eq(0, v.timestamp) + eq('a', v.value[3]) + found = found + 1 + end + end + eq(1, found) + end) + + it('takes item with greater timestamp from ShaDa when reading', + function() + wshada('\004\000\009\147\000\196\002ab\196\001a') + eq(0, exc_exec(sdrcmd())) + wshada('\004\001\009\147\000\196\002ab\196\001b') + eq(0, exc_exec(sdrcmd())) + os.remove(shada_fname) + eq(0, exc_exec('wshada! ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 4 and v.value[1] == 0 and v.value[2] == 'ab' then + eq(1, v.timestamp) + eq('b', v.value[3]) + found = found + 1 + end + end + eq(1, found) + end) + + it('takes item with greater timestamp from NeoVim instance when writing', + function() + wshada('\004\001\009\147\000\196\002ab\196\001a') + eq(0, exc_exec(sdrcmd())) + wshada('\004\000\009\147\000\196\002ab\196\001b') + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 4 and v.value[1] == 0 and v.value[2] == 'ab' then + eq(1, v.timestamp) + eq('a', v.value[3]) + found = found + 1 + end + end + eq(1, found) + end) + + it('takes item with equal timestamp from NeoVim instance when writing', + function() + wshada('\004\000\009\147\000\196\002ab\196\001a') + eq(0, exc_exec(sdrcmd())) + wshada('\004\000\009\147\000\196\002ab\196\001b') + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 4 and v.value[1] == 0 and v.value[2] == 'ab' then + eq(0, v.timestamp) + eq('a', v.value[3]) + found = found + 1 + end + end + eq(1, found) + end) + + it('takes item with greater timestamp from ShaDa when writing', + function() + wshada('\004\000\009\147\000\196\002ab\196\001a') + eq(0, exc_exec(sdrcmd())) + wshada('\004\001\009\147\000\196\002ab\196\001b') + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 4 and v.value[1] == 0 and v.value[2] == 'ab' then + eq(1, v.timestamp) + eq('b', v.value[3]) + found = found + 1 + end + end + eq(1, found) + end) + + it('correctly reads history items with messed up timestamps', + function() + wshada('\004\010\009\147\000\196\002ab\196\001a' + .. '\004\010\009\147\000\196\002ac\196\001a' + .. '\004\005\009\147\000\196\002ad\196\001a' + .. '\004\100\009\147\000\196\002ae\196\001a' + .. '\004\090\009\147\000\196\002af\196\001a' + ) + eq(0, exc_exec(sdrcmd())) + os.remove(shada_fname) + eq(0, exc_exec('wshada! ' .. shada_fname)) + local items = {'ad', 'ab', 'ac', 'af', 'ae'} + for i, v in ipairs(items) do + eq(v, nvim_eval(('histget(":", %i)'):format(i))) + end + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 4 and v.value[1] == 0 then + found = found + 1 + eq(items[found], v.value[2]) + eq('a', v.value[3]) + end + end + eq(#items, found) + end) + + it('correctly reorders history items with messed up timestamps when writing', + function() + wshada('\004\010\009\147\000\196\002ab\196\001a' + .. '\004\010\009\147\000\196\002ac\196\001a' + .. '\004\005\009\147\000\196\002ad\196\001a' + .. '\004\100\009\147\000\196\002ae\196\001a' + .. '\004\090\009\147\000\196\002af\196\001a' + ) + eq(0, exc_exec('wshada ' .. shada_fname)) + local items = {'ad', 'ab', 'ac', 'af', 'ae'} + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 4 and v.value[1] == 0 then + found = found + 1 + eq(items[found], v.value[2]) + eq('a', v.value[3]) + end + end + eq(#items, found) + end) +end) + +describe('ShaDa search pattern support code', function() + before_each(reset) + after_each(function() + clear() + os.remove(shada_fname) + end) + + it('uses last search pattern with gt timestamp from instance when reading', + function() + wshada('\002\001\011\130\162sX\194\162sp\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\002\000\011\130\162sX\194\162sp\196\001?') + eq(0, exc_exec(sdrcmd())) + eq('-', nvim_eval('@/')) + end) + + it('uses last search pattern with gt tstamp from file when reading with bang', + function() + wshada('\002\001\011\130\162sX\194\162sp\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\002\000\011\130\162sX\194\162sp\196\001?') + eq(0, exc_exec(sdrcmd(true))) + eq('?', nvim_eval('@/')) + end) + + it('uses last search pattern with eq timestamp from instance when reading', + function() + wshada('\002\001\011\130\162sX\194\162sp\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\002\001\011\130\162sX\194\162sp\196\001?') + eq(0, exc_exec(sdrcmd())) + eq('-', nvim_eval('@/')) + end) + + it('uses last search pattern with gt timestamp from file when reading', + function() + wshada('\002\001\011\130\162sX\194\162sp\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\002\002\011\130\162sX\194\162sp\196\001?') + eq(0, exc_exec(sdrcmd())) + eq('?', nvim_eval('@/')) + end) + + it('uses last search pattern with gt timestamp from instance when writing', + function() + wshada('\002\001\011\130\162sX\194\162sp\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\002\000\011\130\162sX\194\162sp\196\001?') + eq('-', nvim_eval('@/')) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 2 and v.value.sp == '-' then + found = found + 1 + end + end + eq(1, found) + end) + + it('uses last search pattern with eq timestamp from instance when writing', + function() + wshada('\002\001\011\130\162sX\194\162sp\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\002\001\011\130\162sX\194\162sp\196\001?') + eq('-', nvim_eval('@/')) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 2 and v.value.sp == '-' then + found = found + 1 + end + end + eq(1, found) + end) + + it('uses last search pattern with gt timestamp from file when writing', + function() + wshada('\002\001\011\130\162sX\194\162sp\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\002\002\011\130\162sX\194\162sp\196\001?') + eq('-', nvim_eval('@/')) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 2 and v.value.sp == '?' then + found = found + 1 + end + end + eq(1, found) + end) + + it('uses last s/ pattern with gt timestamp from instance when reading', + function() + wshada('\002\001\011\130\162ss\195\162sp\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\002\000\011\130\162ss\195\162sp\196\001?') + eq(0, exc_exec(sdrcmd())) + eq('-', nvim_eval('@/')) + end) + + it('uses last s/ pattern with gt timestamp from file when reading with !', + function() + wshada('\002\001\011\130\162ss\195\162sp\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\002\000\011\130\162ss\195\162sp\196\001?') + eq(0, exc_exec(sdrcmd(true))) + eq('?', nvim_eval('@/')) + end) + + it('uses last s/ pattern with eq timestamp from instance when reading', + function() + wshada('\002\001\011\130\162ss\195\162sp\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\002\001\011\130\162ss\195\162sp\196\001?') + eq(0, exc_exec(sdrcmd())) + eq('-', nvim_eval('@/')) + end) + + it('uses last s/ pattern with gt timestamp from file when reading', + function() + wshada('\002\001\011\130\162ss\195\162sp\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\002\002\011\130\162ss\195\162sp\196\001?') + eq(0, exc_exec(sdrcmd())) + eq('?', nvim_eval('@/')) + end) + + it('uses last s/ pattern with gt timestamp from instance when writing', + function() + wshada('\002\001\011\130\162ss\195\162sp\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\002\000\011\130\162ss\195\162sp\196\001?') + eq('-', nvim_eval('@/')) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 2 and v.value.sp == '-' then + found = found + 1 + end + end + eq(1, found) + end) + + it('uses last s/ pattern with eq timestamp from instance when writing', + function() + wshada('\002\001\011\130\162ss\195\162sp\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\002\001\011\130\162ss\195\162sp\196\001?') + eq('-', nvim_eval('@/')) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 2 and v.value.sp == '-' then + found = found + 1 + end + end + eq(1, found) + end) + + it('uses last s/ pattern with gt timestamp from file when writing', + function() + wshada('\002\001\011\130\162ss\195\162sp\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\002\002\011\130\162ss\195\162sp\196\001?') + eq('-', nvim_eval('@/')) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 2 and v.value.sp == '?' then + found = found + 1 + end + end + eq(1, found) + end) +end) + +describe('ShaDa replacement string support code', function() + before_each(reset) + after_each(function() + clear() + os.remove(shada_fname) + end) + + it('uses last replacement with gt timestamp from instance when reading', + function() + wshada('\003\001\004\145\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\003\000\004\145\196\001?') + eq(0, exc_exec(sdrcmd())) + nvim_command('s/.*/~') + eq('-', nvim_eval('getline(".")')) + nvim_command('bwipeout!') + end) + + it('uses last replacement with gt timestamp from file when reading with bang', + function() + wshada('\003\001\004\145\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\003\000\004\145\196\001?') + eq(0, exc_exec(sdrcmd(true))) + nvim_command('s/.*/~') + eq('?', nvim_eval('getline(".")')) + nvim_command('bwipeout!') + end) + + it('uses last replacement with eq timestamp from instance when reading', + function() + wshada('\003\001\004\145\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\003\001\004\145\196\001?') + eq(0, exc_exec(sdrcmd())) + nvim_command('s/.*/~') + eq('-', nvim_eval('getline(".")')) + nvim_command('bwipeout!') + end) + + it('uses last replacement with gt timestamp from file when reading', + function() + wshada('\003\001\004\145\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\003\002\004\145\196\001?') + eq(0, exc_exec(sdrcmd())) + nvim_command('s/.*/~') + eq('?', nvim_eval('getline(".")')) + nvim_command('bwipeout!') + end) + + it('uses last replacement with gt timestamp from instance when writing', + function() + wshada('\003\001\004\145\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\003\000\004\145\196\001?') + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 3 and v.value[1] == '-' then + found = found + 1 + end + end + eq(1, found) + end) + + it('uses last replacement with eq timestamp from instance when writing', + function() + wshada('\003\001\004\145\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\003\001\004\145\196\001?') + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 3 and v.value[1] == '-' then + found = found + 1 + end + end + eq(1, found) + end) + + it('uses last replacement with gt timestamp from file when writing', + function() + wshada('\003\001\004\145\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\003\002\004\145\196\001?') + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 3 and v.value[1] == '?' then + found = found + 1 + end + end + eq(1, found) + end) +end) + +describe('ShaDa marks support code', function() + before_each(reset) + after_each(function() + clear() + os.remove(shada_fname) + end) + + it('uses last A mark with gt timestamp from instance when reading', + function() + wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/-\161nA') + eq(0, exc_exec(sdrcmd())) + wshada('\007\000\018\131\162mX\195\161f\196\006/a/b/?\161nA') + eq(0, exc_exec(sdrcmd())) + nvim_command('normal! `A') + eq('-', nvim_eval('fnamemodify(bufname("%"), ":t")')) + end) + + it('uses last A mark with gt timestamp from file when reading with !', + function() + wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/-\161nA') + eq(0, exc_exec(sdrcmd())) + wshada('\007\000\018\131\162mX\195\161f\196\006/a/b/?\161nA') + eq(0, exc_exec(sdrcmd(true))) + nvim_command('normal! `A') + eq('?', nvim_eval('fnamemodify(bufname("%"), ":t")')) + end) + + it('uses last A mark with eq timestamp from instance when reading', + function() + wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/-\161nA') + eq(0, exc_exec(sdrcmd())) + wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/?\161nA') + eq(0, exc_exec(sdrcmd())) + nvim_command('normal! `A') + eq('-', nvim_eval('fnamemodify(bufname("%"), ":t")')) + end) + + it('uses last A mark with gt timestamp from file when reading', + function() + wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/-\161nA') + eq(0, exc_exec(sdrcmd())) + wshada('\007\002\018\131\162mX\195\161f\196\006/a/b/?\161nA') + eq(0, exc_exec(sdrcmd())) + nvim_command('normal! `A') + eq('?', nvim_eval('fnamemodify(bufname("%"), ":t")')) + end) + + it('uses last A mark with gt timestamp from instance when writing', + function() + wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/-\161nA') + eq(0, exc_exec(sdrcmd())) + wshada('\007\000\018\131\162mX\195\161f\196\006/a/b/?\161nA') + nvim_command('normal! `A') + eq('-', nvim_eval('fnamemodify(bufname("%"), ":t")')) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 7 and v.value.f == '/a/b/-' then + found = found + 1 + end + end + eq(1, found) + end) + + it('uses last A mark with eq timestamp from instance when writing', + function() + wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/-\161nA') + eq(0, exc_exec(sdrcmd())) + wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/?\161nA') + nvim_command('normal! `A') + eq('-', nvim_eval('fnamemodify(bufname("%"), ":t")')) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 7 and v.value.f == '/a/b/-' then + found = found + 1 + end + end + eq(1, found) + end) + + it('uses last A mark with gt timestamp from file when writing', + function() + wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/-\161nA') + eq(0, exc_exec(sdrcmd())) + wshada('\007\002\018\131\162mX\195\161f\196\006/a/b/?\161nA') + nvim_command('normal! `A') + eq('-', nvim_eval('fnamemodify(bufname("%"), ":t")')) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 7 and v.value.f == '/a/b/?' then + found = found + 1 + end + end + eq(1, found) + end) + + it('uses last a mark with gt timestamp from instance when reading', + function() + nvim_command('edit /a/b/-') + nvim_eval('setline(1, ["-", "?"])') + wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na') + eq(0, exc_exec(sdrcmd())) + wshada('\010\000\017\131\161l\002\161f\196\006/a/b/-\161na') + eq(0, exc_exec(sdrcmd())) + nvim_command('normal! `a') + eq('-', nvim_eval('getline(".")')) + end) + + it('uses last a mark with gt timestamp from file when reading with !', + function() + nvim_command('edit /a/b/-') + nvim_eval('setline(1, ["-", "?"])') + wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na') + eq(0, exc_exec(sdrcmd())) + wshada('\010\000\017\131\161l\002\161f\196\006/a/b/-\161na') + eq(0, exc_exec(sdrcmd(true))) + nvim_command('normal! `a') + eq('?', nvim_eval('getline(".")')) + end) + + it('uses last a mark with eq timestamp from instance when reading', + function() + nvim_command('edit /a/b/-') + nvim_eval('setline(1, ["-", "?"])') + wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na') + eq(0, exc_exec(sdrcmd())) + wshada('\010\001\017\131\161l\002\161f\196\006/a/b/-\161na') + eq(0, exc_exec(sdrcmd())) + nvim_command('normal! `a') + eq('-', nvim_eval('getline(".")')) + end) + + it('uses last a mark with gt timestamp from file when reading', + function() + nvim_command('edit /a/b/-') + nvim_eval('setline(1, ["-", "?"])') + wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na') + eq(0, exc_exec(sdrcmd())) + wshada('\010\002\017\131\161l\002\161f\196\006/a/b/-\161na') + eq(0, exc_exec(sdrcmd())) + nvim_command('normal! `a') + eq('?', nvim_eval('getline(".")')) + end) + + it('uses last a mark with gt timestamp from instance when writing', + function() + nvim_command('edit /a/b/-') + nvim_eval('setline(1, ["-", "?"])') + wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na') + eq(0, exc_exec(sdrcmd())) + wshada('\010\000\017\131\161l\002\161f\196\006/a/b/-\161na') + nvim_command('normal! `a') + eq('-', nvim_eval('getline(".")')) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 10 and v.value.f == '/a/b/-' and v.value.n == ('a'):byte() then + eq(true, v.value.l == 1 or v.value.l == nil) + found = found + 1 + end + end + eq(1, found) + end) + + it('uses last a mark with eq timestamp from instance when writing', + function() + nvim_command('edit /a/b/-') + nvim_eval('setline(1, ["-", "?"])') + wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na') + eq(0, exc_exec(sdrcmd())) + wshada('\010\001\017\131\161l\002\161f\196\006/a/b/-\161na') + nvim_command('normal! `a') + eq('-', nvim_eval('getline(".")')) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 10 and v.value.f == '/a/b/-' and v.value.n == ('a'):byte() then + eq(true, v.value.l == 1 or v.value.l == nil) + found = found + 1 + end + end + eq(1, found) + end) + + it('uses last a mark with gt timestamp from file when writing', + function() + nvim_command('edit /a/b/-') + nvim_eval('setline(1, ["-", "?"])') + wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na') + eq(0, exc_exec(sdrcmd())) + wshada('\010\002\017\131\161l\002\161f\196\006/a/b/-\161na') + nvim_command('normal! `a') + eq('-', nvim_eval('fnamemodify(bufname("%"), ":t")')) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 10 and v.value.f == '/a/b/-' and v.value.n == ('a'):byte() then + eq(2, v.value.l) + found = found + 1 + end + end + eq(1, found) + end) +end) + +describe('ShaDa registers support code', function() + before_each(reset) + after_each(function() + clear() + os.remove(shada_fname) + end) + + it('uses last a register with gt timestamp from instance when reading', + function() + wshada('\005\001\015\131\161na\162rX\194\162rc\145\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\005\000\015\131\161na\162rX\194\162rc\145\196\001?') + eq(0, exc_exec(sdrcmd())) + eq('-', nvim_eval('@a')) + end) + + it('uses last a register with gt timestamp from file when reading with !', + function() + wshada('\005\001\015\131\161na\162rX\194\162rc\145\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\005\000\015\131\161na\162rX\194\162rc\145\196\001?') + eq(0, exc_exec(sdrcmd(true))) + eq('?', nvim_eval('@a')) + end) + + it('uses last a register with eq timestamp from instance when reading', + function() + wshada('\005\001\015\131\161na\162rX\194\162rc\145\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\005\001\015\131\161na\162rX\194\162rc\145\196\001?') + eq(0, exc_exec(sdrcmd())) + eq('-', nvim_eval('@a')) + end) + + it('uses last a register with gt timestamp from file when reading', + function() + wshada('\005\001\015\131\161na\162rX\194\162rc\145\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\005\002\015\131\161na\162rX\194\162rc\145\196\001?') + eq(0, exc_exec(sdrcmd())) + eq('?', nvim_eval('@a')) + end) + + it('uses last a register with gt timestamp from instance when writing', + function() + wshada('\005\001\015\131\161na\162rX\194\162rc\145\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\005\000\015\131\161na\162rX\194\162rc\145\196\001?') + eq('-', nvim_eval('@a')) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 5 and v.value.n == ('a'):byte() then + eq({'-'}, v.value.rc) + found = found + 1 + end + end + eq(1, found) + end) + + it('uses last a register with eq timestamp from instance when writing', + function() + wshada('\005\001\015\131\161na\162rX\194\162rc\145\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\005\001\015\131\161na\162rX\194\162rc\145\196\001?') + eq('-', nvim_eval('@a')) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 5 and v.value.n == ('a'):byte() then + eq({'-'}, v.value.rc) + found = found + 1 + end + end + eq(1, found) + end) + + it('uses last a register with gt timestamp from file when writing', + function() + wshada('\005\001\015\131\161na\162rX\194\162rc\145\196\001-') + eq(0, exc_exec(sdrcmd())) + wshada('\005\002\015\131\161na\162rX\194\162rc\145\196\001?') + eq('-', nvim_eval('@a')) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 5 and v.value.n == ('a'):byte() then + eq({'?'}, v.value.rc) + found = found + 1 + end + end + eq(1, found) + end) +end) + +describe('ShaDa jumps support code', function() + before_each(reset) + after_each(function() + clear() + os.remove(shada_fname) + end) + + it('merges jumps when reading', function() + wshada('\008\001\018\131\162mX\195\161f\196\006/a/b/c\161l\002' + .. '\008\004\018\131\162mX\195\161f\196\006/a/b/d\161l\002' + .. '\008\007\018\131\162mX\195\161f\196\006/a/b/e\161l\002') + eq(0, exc_exec(sdrcmd())) + wshada('\008\001\018\131\162mX\195\161f\196\006/a/b/c\161l\002' + .. '\008\004\018\131\162mX\195\161f\196\006/a/b/d\161l\003' + .. '\008\007\018\131\162mX\195\161f\196\006/a/b/f\161l\002') + eq(0, exc_exec(sdrcmd())) + nvim_command('redir => g:jumps | jumps | redir END') + eq('', nvim_eval('bufname("%")')) + eq('\n' + .. ' jump line col file/text\n' + .. ' 5 2 0 /a/b/c\n' + .. ' 4 3 0 /a/b/d\n' + .. ' 3 2 0 /a/b/d\n' + .. ' 2 2 0 /a/b/f\n' + .. ' 1 2 0 /a/b/e\n' + .. '> 0 1 0 ', nvim_eval('g:jumps')) + end) + + it('merges jumps when writing', function() + wshada('\008\001\018\131\162mX\195\161f\196\006/a/b/c\161l\002' + .. '\008\004\018\131\162mX\195\161f\196\006/a/b/d\161l\002' + .. '\008\007\018\131\162mX\195\161f\196\006/a/b/e\161l\002') + eq(0, exc_exec(sdrcmd())) + wshada('\008\001\018\131\162mX\195\161f\196\006/a/b/c\161l\002' + .. '\008\004\018\131\162mX\195\161f\196\006/a/b/d\161l\003' + .. '\008\007\018\131\162mX\195\161f\196\006/a/b/f\161l\002') + eq(0, exc_exec('wshada ' .. shada_fname)) + local jumps = { + {file='/a/b/c', line=2}, + {file='/a/b/d', line=3}, + {file='/a/b/d', line=2}, + {file='/a/b/f', line=2}, + {file='/a/b/e', line=2}, + } + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 8 then + found = found + 1 + eq(jumps[found].file, v.value.f) + eq(jumps[found].line, v.value.l) + end + end + eq(found, #jumps) + end) +end) + +describe('ShaDa changes support code', function() + before_each(reset) + after_each(function() + clear() + os.remove(shada_fname) + end) + + it('merges changes when reading', function() + nvim_command('edit /a/b/c') + nvim_command('keepjumps call setline(1, range(7))') + wshada('\011\001\018\131\162mX\195\161f\196\006/a/b/c\161l\001' + .. '\011\004\018\131\162mX\195\161f\196\006/a/b/c\161l\002' + .. '\011\007\018\131\162mX\195\161f\196\006/a/b/c\161l\003') + eq(0, exc_exec(sdrcmd())) + wshada('\011\001\018\131\162mX\194\161f\196\006/a/b/c\161l\001' + .. '\011\004\018\131\162mX\195\161f\196\006/a/b/c\161l\005' + .. '\011\008\018\131\162mX\195\161f\196\006/a/b/c\161l\004') + eq(0, exc_exec(sdrcmd())) + nvim_command('redir => g:changes | changes | redir END') + eq('\n' + .. 'change line col text\n' + .. ' 5 1 0 0\n' + .. ' 4 2 0 1\n' + .. ' 3 5 0 4\n' + .. ' 2 3 0 2\n' + .. ' 1 4 0 3\n' + .. '>', nvim_eval('g:changes')) + end) + + it('merges changes when writing', function() + nvim_command('edit /a/b/c') + nvim_command('keepjumps call setline(1, range(7))') + wshada('\011\001\018\131\162mX\195\161f\196\006/a/b/c\161l\001' + .. '\011\004\018\131\162mX\195\161f\196\006/a/b/c\161l\002' + .. '\011\007\018\131\162mX\195\161f\196\006/a/b/c\161l\003') + eq(0, exc_exec(sdrcmd())) + wshada('\011\001\018\131\162mX\194\161f\196\006/a/b/c\161l\001' + .. '\011\004\018\131\162mX\195\161f\196\006/a/b/c\161l\005' + .. '\011\008\018\131\162mX\195\161f\196\006/a/b/c\161l\004') + eq(0, exc_exec('wshada ' .. shada_fname)) + local changes = { + {line=1}, + {line=2}, + {line=5}, + {line=3}, + {line=4}, + } + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 11 and v.value.f == '/a/b/c' then + found = found + 1 + eq(changes[found].line, v.value.l or 1) + end + end + eq(found, #changes) + end) +end) -- cgit From 0564b30ef5003c517162a3d081632f6335952c78 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 11 Aug 2015 08:13:25 +0300 Subject: shada: Fix linter errors --- src/nvim/lib/ringbuf.h | 8 +- src/nvim/shada.c | 1411 +++++++++++++++++++++++------------------------- 2 files changed, 685 insertions(+), 734 deletions(-) diff --git a/src/nvim/lib/ringbuf.h b/src/nvim/lib/ringbuf.h index f86d656deb..cb71500bb7 100644 --- a/src/nvim/lib/ringbuf.h +++ b/src/nvim/lib/ringbuf.h @@ -62,7 +62,7 @@ /// Define a ring buffer structure /// -/// @param TypeName Ring buffer type name. Actual type name will be +/// @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) \ @@ -75,12 +75,12 @@ typedef struct { \ /// Initialize a new ring buffer /// -/// @param TypeName Ring buffer type name. Actual type name will be +/// @param TypeName Ring buffer type name. Actual type name will be /// `{TypeName}RingBuffer`. -/// @param funcprefix Prefix for all ring buffer functions. Function name will +/// @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 +/// @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 *)`; diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 67f299b8f9..d36102c52b 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -34,7 +34,6 @@ #include "nvim/msgpack_rpc/helpers.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/globals.h" #include "nvim/buffer.h" #include "nvim/buffer_defs.h" #include "nvim/misc2.h" @@ -50,8 +49,8 @@ #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 +// 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) @@ -89,8 +88,8 @@ KHASH_SET_INIT_STR(strset) #define regtilde(s, m) ((char *) regtilde((char_u *) s, m)) #define path_tail_with_sep(f) ((char *) path_tail_with_sep((char_u *)f)) -// From http://www.boost.org/doc/libs/1_43_0/boost/detail/endian.hpp + some -// additional checks done after examining `{compiler} -dM -E - < /dev/null` +// From http://www.boost.org/doc/libs/1_43_0/boost/detail/endian.hpp + some +// additional checks done after examining `{compiler} -dM -E - < /dev/null` // output. #if defined (__GLIBC__) # if (__BYTE_ORDER == __BIG_ENDIAN) @@ -111,18 +110,18 @@ KHASH_SET_INIT_STR(strset) # define SHADA_BIG_ENDIAN # endif #elif defined(__sparc) || defined(__sparc__) \ - || defined(_POWER) || defined(__powerpc__) \ - || defined(__ppc__) || defined(__hpux) || defined(__hppa) \ - || defined(_MIPSEB) || defined(_POWER) \ - || defined(__s390__) + || defined(_POWER) || defined(__powerpc__) \ + || defined(__ppc__) || defined(__hpux) || defined(__hppa) \ + || defined(_MIPSEB) || defined(_POWER) \ + || defined(__s390__) # define SHADA_BIG_ENDIAN #elif defined(__i386__) || defined(__alpha__) \ - || defined(__ia64) || defined(__ia64__) \ - || defined(_M_IX86) || defined(_M_IA64) \ - || defined(_M_ALPHA) || defined(__amd64) \ - || defined(__amd64__) || defined(_M_AMD64) \ - || defined(__x86_64) || defined(__x86_64__) \ - || defined(_M_X64) || defined(__bfin__) + || defined(__ia64) || defined(__ia64__) \ + || defined(_M_IX86) || defined(_M_IA64) \ + || defined(_M_ALPHA) || defined(__amd64) \ + || defined(__amd64__) || defined(_M_AMD64) \ + || defined(__x86_64) || defined(__x86_64__) \ + || defined(_M_X64) || defined(__bfin__) // Define nothing #endif @@ -161,12 +160,12 @@ KHASH_SET_INIT_STR(strset) // 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 +// 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 +/// I.e. errors occurred while parsing, but not system errors occurred while /// reading. #define RERR "E575: " @@ -200,7 +199,7 @@ typedef enum { 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 + ///< Comes from user searches (e.g. when typing ///< "/pat") or :substitute command calls. kSDItemSubString = 3, ///< Last substitute replacement string. kSDItemHistoryEntry = 4, ///< History item. @@ -226,10 +225,10 @@ typedef enum { /// 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 + 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 + kSDWriteFailed, ///< Writing was not successfull (e.g. because there ///< was no space left on device). } ShaDaWriteResult; @@ -240,43 +239,43 @@ enum SRNIFlags { 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 + | (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 + ///< should be read (can only be ///< disabled by &history). - kSDReadVariables = (1 << kSDItemVariable), ///< Determines whether variables - ///< should be read (disabled by + 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 + 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 + 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 + 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 + 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 + 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 +// 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 @@ -356,9 +355,9 @@ 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 + HMLListEntry *entries; ///< Pointer to the start of the allocated array of ///< entries. - HMLListEntry *first; ///< First entry in the list (is not necessary start + 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_entries; ///< Free array entries. @@ -366,8 +365,8 @@ typedef struct { size_t size; ///< Number of allocated entries. size_t free_entries_size; ///< Number of non-NULL entries in free_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 + khash_t(hmll_entries) contained_entries; ///< Hash mapping all history entry + ///< strings to corresponding entry ///< pointers. } HMLList; @@ -439,7 +438,7 @@ typedef struct sd_read_def { void *cookie; ///< Reader function last argument. 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 + 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. @@ -543,7 +542,7 @@ static inline void hmll_remove(HMLList *const hmll, /// 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 +/// @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. @@ -594,7 +593,7 @@ static inline void hmll_insert(HMLList *const hmll, /// 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 +/// @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}`). @@ -727,7 +726,7 @@ static void close_sd_writer(ShaDaWriteDef *const sd_writer) /// @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 +/// @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) @@ -755,7 +754,7 @@ static int sd_reader_skip_read(ShaDaReadDef *const sd_reader, /// @param[in,out] sd_reader File read. /// @param[in] offset Amount of bytes to skip. /// -/// @return kSDReadStatusReadError, kSDReadStatusNotShaDa or +/// @return kSDReadStatusReadError, kSDReadStatusNotShaDa or /// kSDReadStatusSuccess. static ShaDaReadResult sd_reader_skip(ShaDaReadDef *const sd_reader, const size_t offset) @@ -951,7 +950,7 @@ static int shada_read_file(const char *const file, const int flags) /// /// @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 +/// @param[in] zero If true, then item is removed from instance /// memory upon reading. /// @param[out] hist Location where iteration results should be saved. /// @@ -987,17 +986,17 @@ static const void *shada_hist_iter(const void *const iter, /// 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 +/// 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 +/// 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 +/// @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, @@ -1057,7 +1056,7 @@ static void hms_insert(HistoryMergerState *const hms_p, const ShadaEntry entry, /// @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 +/// @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, @@ -1076,7 +1075,7 @@ static inline void hms_init(HistoryMergerState *const hms_p, /// Merge in all remaining NeoVim own history entries /// -/// @param[in,out] hms_p Merger structure into which history should be +/// @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) @@ -1301,9 +1300,9 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) .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 + // 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. @@ -1455,8 +1454,8 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) 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 + // 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); } @@ -1529,7 +1528,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) cur_entry.data.filemark.fname = NULL; } } - // Do not free shada entry: except for fname, its allocated memory (i.e. + // 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; @@ -1537,11 +1536,11 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) } } 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 + // 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++) { @@ -1575,8 +1574,8 @@ shada_read_main_cycle_end: /// 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 +/// 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. @@ -1605,10 +1604,10 @@ static char *shada_filename(const char *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 + // 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]); @@ -1632,7 +1631,7 @@ static char *shada_filename(const char *file) /// /// @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 +/// @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, @@ -1726,7 +1725,7 @@ static bool shada_pack_entry(msgpack_packer *const packer, } case kSDItemSearchPattern: { const size_t map_size = (size_t) ( - 1 // Search pattern is always present + 1 // Search pattern is always present // Following items default to true: + (size_t) !entry.data.search_pattern.magic + (size_t) !entry.data.search_pattern.is_last_used @@ -1742,8 +1741,7 @@ static bool shada_pack_entry(msgpack_packer *const packer, + (size_t) ( entry.data.search_pattern.additional_data ? entry.data.search_pattern.additional_data->dv_hashtab.ht_used - : 0) - ); + : 0)); msgpack_pack_map(spacker, map_size); PACK_STATIC_STR(SEARCH_KEY_PAT); msgpack_rpc_from_string(cstr_as_string(entry.data.search_pattern.pat), @@ -1788,8 +1786,7 @@ static bool shada_pack_entry(msgpack_packer *const packer, + (size_t) ( entry.data.filemark.additional_data == NULL ? 0 - : entry.data.filemark.additional_data->dv_hashtab.ht_used) - ); + : entry.data.filemark.additional_data->dv_hashtab.ht_used)); msgpack_pack_map(spacker, map_size); PACK_STATIC_STR(KEY_FILE); msgpack_rpc_from_string(cstr_as_string(entry.data.filemark.fname), @@ -1820,8 +1817,7 @@ static bool shada_pack_entry(msgpack_packer *const packer, // Additional entries, if any: + (size_t) (entry.data.reg.additional_data == NULL ? 0 - : entry.data.reg.additional_data->dv_hashtab.ht_used) - ); + : 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); @@ -1856,8 +1852,7 @@ static bool shada_pack_entry(msgpack_packer *const packer, entry.data.buffer_list.buffers[i].additional_data == NULL ? 0 : (entry.data.buffer_list.buffers[i].additional_data - ->dv_hashtab.ht_used)) - ); + ->dv_hashtab.ht_used))); msgpack_pack_map(spacker, map_size); PACK_STATIC_STR(KEY_FILE); msgpack_rpc_from_string( @@ -1922,10 +1917,10 @@ shada_pack_entry_error: /// /// @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 +/// @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 +/// @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, @@ -2042,7 +2037,7 @@ static bool shada_pack_encoded_entry(msgpack_packer *const packer, /// Compare two FileMarks structure to order them by greatest_timestamp /// -/// Order is reversed: structure with greatest greatest_timestamp comes first. +/// 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 @@ -2060,12 +2055,12 @@ static int compare_file_marks(const void *a, const void *b) /// /// @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 +/// @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 +/// @return kSDReadStatusNotShaDa, kSDReadStatusReadError or /// kSDReadStatusSuccess. static inline ShaDaReadResult shada_parse_msgpack( ShaDaReadDef *const sd_reader, const size_t length, @@ -2143,11 +2138,266 @@ shada_parse_msgpack_extra_bytes: 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) { + kh_key(&wms->file_marks, k) = xstrdup(fname); + 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 { + COMPARE_WITH_ENTRY(&filemarks->marks[idx], entry); + } + } else { + const int cl_len = (int) filemarks->changes_size; + int i; + for (i = cl_len; i > 0; i--) { + const ShadaEntry old_entry = filemarks->changes[i - 1].data; + if (old_entry.timestamp <= entry.timestamp) { + if (marks_equal(old_entry.data.filemark.mark, + entry.data.filemark.mark)) { + i = -1; + } + break; + } + } + if (i > 0) { + if (cl_len == JUMPLISTSIZE) { + if (filemarks->changes[0].can_free_entry) { + shada_free_shada_entry(&filemarks->changes[0].data); + } + memmove(&filemarks->changes[0], &filemarks->changes[1], + sizeof(filemarks->changes[0]) * (size_t) i); + } else { + memmove(&filemarks->changes[i + 1], &filemarks->changes[i], + sizeof(filemarks->changes[0]) * (size_t) (cl_len - i)); + } + } else if (i == 0) { + if (cl_len == JUMPLISTSIZE) { + i = -1; + } else if (cl_len > 0) { + memmove(&filemarks->changes[1], &filemarks->changes[0], + sizeof(filemarks->changes[0]) * (size_t) cl_len); + } + } + if (i != -1) { + filemarks->changes[i] = (PossiblyFreedShadaEntry) { + .can_free_entry = true, + .data = entry + }; + if (cl_len < JUMPLISTSIZE) { + filemarks->changes_size++; + } + } else { + shada_free_shada_entry(&entry); + } + } + break; + } + case kSDItemJump: { + const int jl_len = (int) wms->jumps_size; + int i; + for (i = 0; i < jl_len; i++) { + const ShadaEntry old_entry = wms->jumps[i].data; + if (old_entry.timestamp >= entry.timestamp) { + if (marks_equal(old_entry.data.filemark.mark, + entry.data.filemark.mark) + && strcmp(old_entry.data.filemark.fname, + entry.data.filemark.fname) == 0) { + i = -1; + } + break; + } + } + if (i != -1) { + if (i < jl_len) { + if (jl_len == JUMPLISTSIZE) { + if (wms->jumps[0].can_free_entry) { + shada_free_shada_entry(&wms->jumps[0].data); + } + memmove(&wms->jumps[0], &wms->jumps[1], + sizeof(wms->jumps[0]) * (size_t) i); + } else { + memmove(&wms->jumps[i + 1], &wms->jumps[i], + sizeof(wms->jumps[0]) * (size_t) (jl_len - i)); + } + } else if (i == jl_len) { + if (jl_len == JUMPLISTSIZE) { + i = -1; + } else if (jl_len > 0) { + memmove(&wms->jumps[1], &wms->jumps[0], + sizeof(wms->jumps[0]) * (size_t) jl_len); + } + } + } + if (i != -1) { + wms->jumps[i] = (PossiblyFreedShadaEntry) { + .can_free_entry = true, + .data = entry, + }; + if (jl_len < JUMPLISTSIZE) { + wms->jumps_size++; + } + } else { + shada_free_shada_entry(&entry); + } + 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 +/// @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) @@ -2199,8 +2449,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, | (dump_registers ? kSDReadRegisters : 0) | (dump_global_vars ? kSDReadVariables : 0) | (dump_global_marks ? kSDReadGlobalMarks : 0) - | (num_marked_files ? kSDReadLocalMarks | kSDReadChanges : 0) - ); + | (num_marked_files ? kSDReadLocalMarks | kSDReadChanges : 0)); msgpack_packer *const packer = msgpack_packer_new(sd_writer, &msgpack_sd_writer_write); @@ -2220,7 +2469,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, .header = { .size = 4, .capacity = 4, - .items = ((KeyValuePair []) { + .items = ((KeyValuePair[]) { { STATIC_CSTR_AS_STRING("version"), STRING_OBJ(cstr_as_string(longVersion)) }, { STATIC_CSTR_AS_STRING("max_kbyte"), @@ -2469,7 +2718,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, } }, }; - } while(global_mark_iter != NULL); + } while (global_mark_iter != NULL); } // Initialize registers @@ -2502,7 +2751,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, } } }; - } while(reg_iter != NULL); + } while (reg_iter != NULL); } // Initialize buffers @@ -2546,7 +2795,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, if (fm.timestamp > filemarks->greatest_timestamp) { filemarks->greatest_timestamp = fm.timestamp; } - } while(local_marks_iter != NULL); + } 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) { @@ -2571,250 +2820,15 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, } } - if (sd_reader == NULL) { - goto shada_write_main_cycle_end; + 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; + } } - 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: { - goto shada_write_main_cycle_end; - } - 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) { - kh_key(&wms->file_marks, k) = xstrdup(fname); - 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 { - COMPARE_WITH_ENTRY(&filemarks->marks[idx], entry); - } - } else { - const int cl_len = (int) filemarks->changes_size; - int i; - for (i = cl_len; i > 0; i--) { - const ShadaEntry old_entry = filemarks->changes[i - 1].data; - if (old_entry.timestamp <= entry.timestamp) { - if (marks_equal(old_entry.data.filemark.mark, - entry.data.filemark.mark)) { - i = -1; - } - break; - } - } - if (i > 0) { - if (cl_len == JUMPLISTSIZE) { - if (filemarks->changes[0].can_free_entry) { - shada_free_shada_entry(&filemarks->changes[0].data); - } - memmove(&filemarks->changes[0], &filemarks->changes[1], - sizeof(filemarks->changes[0]) * (size_t) i); - } else { - memmove(&filemarks->changes[i + 1], &filemarks->changes[i], - sizeof(filemarks->changes[0]) * (size_t) (cl_len - i)); - } - } else if (i == 0) { - if (cl_len == JUMPLISTSIZE) { - i = -1; - } else if (cl_len > 0) { - memmove(&filemarks->changes[1], &filemarks->changes[0], - sizeof(filemarks->changes[0]) * (size_t) cl_len); - } - } - if (i != -1) { - filemarks->changes[i] = (PossiblyFreedShadaEntry) { - .can_free_entry = true, - .data = entry - }; - if (cl_len < JUMPLISTSIZE) { - filemarks->changes_size++; - } - } else { - shada_free_shada_entry(&entry); - } - } - break; - } - case kSDItemJump: { - const int jl_len = (int) wms->jumps_size; - int i; - for (i = 0; i < jl_len; i++) { - const ShadaEntry old_entry = wms->jumps[i].data; - if (old_entry.timestamp >= entry.timestamp) { - if (marks_equal(old_entry.data.filemark.mark, - entry.data.filemark.mark) - && strcmp(old_entry.data.filemark.fname, - entry.data.filemark.fname) == 0) { - i = -1; - } - break; - } - } - if (i != -1) { - if (i < jl_len) { - if (jl_len == JUMPLISTSIZE) { - if (wms->jumps[0].can_free_entry) { - shada_free_shada_entry(&wms->jumps[0].data); - } - memmove(&wms->jumps[0], &wms->jumps[1], - sizeof(wms->jumps[0]) * (size_t) i); - } else { - memmove(&wms->jumps[i + 1], &wms->jumps[i], - sizeof(wms->jumps[0]) * (size_t) (jl_len - i)); - } - } else if (i == jl_len) { - if (jl_len == JUMPLISTSIZE) { - i = -1; - } else if (jl_len > 0) { - memmove(&wms->jumps[1], &wms->jumps[0], - sizeof(wms->jumps[0]) * (size_t) jl_len); - } - } - } - if (i != -1) { - wms->jumps[i] = (PossiblyFreedShadaEntry) { - .can_free_entry = true, - .data = entry, - }; - if (jl_len < JUMPLISTSIZE) { - wms->jumps_size++; - } - } else { - shada_free_shada_entry(&entry); - } - break; - } - } - } -#undef COMPARE_WITH_ENTRY - // Write the rest -shada_write_main_cycle_end: #define PACK_WMS_ARRAY(wms_array) \ do { \ for (size_t i_ = 0; i_ < ARRAY_SIZE(wms_array); i_++) { \ @@ -2856,8 +2870,7 @@ shada_write_main_cycle_end: 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); + 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); @@ -2896,12 +2909,11 @@ shada_write_main_cycle_end: 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)) { + 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; } @@ -2927,7 +2939,7 @@ shada_write_exit: /// Write ShaDa file to a given location /// -/// @param[in] fname File to write to. If it is NULL or empty then default +/// @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. /// @@ -2980,7 +2992,7 @@ int shada_write_file(const char *const file, bool nomerge) // ^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 + // 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, @@ -2994,7 +3006,7 @@ shada_write_file_open: // 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 + // 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); @@ -3094,7 +3106,7 @@ int shada_read_marks(void) /// Read all information from ShaDa file /// -/// @param[in] fname File to write to. If it is NULL or empty then default +/// @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. @@ -3196,8 +3208,8 @@ static inline uint64_t be64toh(uint64_t big_endian_64_bits) /// @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 +/// @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, @@ -3225,8 +3237,8 @@ static ShaDaReadResult fread_len(ShaDaReadDef *const sd_reader, /// /// 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 +/// 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. @@ -3234,8 +3246,8 @@ static ShaDaReadResult fread_len(ShaDaReadDef *const sd_reader, /// @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 +/// @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, @@ -3264,19 +3276,19 @@ static ShaDaReadResult msgpack_read_uint64(ShaDaReadDef *const sd_reader, } else { size_t length = 0; switch (first_char) { - case 0xCC: { // uint8 + case 0xCC: { // uint8 length = 1; break; } - case 0xCD: { // uint16 + case 0xCD: { // uint16 length = 2; break; } - case 0xCE: { // uint32 + case 0xCE: { // uint32 length = 4; break; } - case 0xCF: { // uint64 + case 0xCF: { // uint64 length = 8; break; } @@ -3307,7 +3319,8 @@ static ShaDaReadResult msgpack_read_uint64(ShaDaReadDef *const sd_reader, /// /// @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) + 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)) { @@ -3321,13 +3334,146 @@ static inline char *get_converted_string(const vimconv_T *const sd_conv, 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 +/// @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 +/// @param[in] max_kbyte If non-zero, skip reading entries which have length /// greater then given. /// /// @return Any value from ShaDaReadResult enum. @@ -3339,9 +3485,9 @@ static ShaDaReadResult shada_read_next_item(ShaDaReadDef *const sd_reader, { 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 + // 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) { @@ -3354,7 +3500,7 @@ shada_read_next_item_start: uint64_t timestamp_u64; uint64_t length_u64; - const uintmax_t initial_fpos = sd_reader->fpos; + 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; @@ -3376,14 +3522,14 @@ shada_read_next_item_start: 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 + // 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"), - (uint64_t) initial_fpos); + initial_fpos); return kSDReadStatusNotShaDa; } @@ -3391,10 +3537,10 @@ shada_read_next_item_start: ? !(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 + // 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)) { @@ -3445,159 +3591,18 @@ shada_read_next_item_start: goto shada_read_next_item_error; } ret = kSDReadStatusMalformed; -#define CHECK_KEY(key, expected) \ - (key.via.str.size == sizeof(expected) - 1 \ - && STRNCMP(key.via.str.ptr, expected, sizeof(expected) - 1) == 0) -#define ID(s) s -#define BINDUP(b) xmemdupz(b.ptr, b.size) -#define TOINT(s) ((int) (s)) -#define TOLONG(s) ((long) (s)) -#define TOCHAR(s) ((char) (s)) -#define TOU8(s) ((uint8_t) (s)) -#define TOSIZE(s) ((size_t) (s)) -#define CHECKED_ENTRY(condition, error_desc, entry_name, obj, tgt, attr, \ - proc) \ - do { \ - if (!(condition)) { \ - emsgu(_(RERR "Error while reading ShaDa file: " \ - entry_name " entry at position %" PRIu64 " " \ - error_desc), \ - (uint64_t) initial_fpos); \ - ga_clear(&ad_ga); \ - goto shada_read_next_item_error; \ - } \ - tgt = proc(obj.via.attr); \ - } while (0) -#define CHECK_KEY_IS_STR(entry_name) \ - do { \ - if (unpacked.data.via.map.ptr[i].key.type != MSGPACK_OBJECT_STR) { \ - emsgu(_(RERR "Error while reading ShaDa file: " \ - entry_name " entry at position %" PRIu64 " " \ - "has key which is not a string"), \ - (uint64_t) initial_fpos); \ - ga_clear(&ad_ga); \ - goto shada_read_next_item_error; \ - } else if (unpacked.data.via.map.ptr[i].key.via.str.size == 0) { \ - emsgu(_(RERR "Error while reading ShaDa file: " \ - entry_name " entry at position %" PRIu64 " " \ - "has empty key"), \ - (uint64_t) initial_fpos); \ - ga_clear(&ad_ga); \ - goto shada_read_next_item_error; \ - } \ - } while (0) -#define CHECKED_KEY(entry_name, name, error_desc, tgt, condition, attr, proc) \ - if (CHECK_KEY(unpacked.data.via.map.ptr[i].key, name)) { \ - CHECKED_ENTRY( \ - condition, "has " name " key value " error_desc, \ - entry_name, unpacked.data.via.map.ptr[i].val, \ - tgt, attr, proc); \ - } -#define TYPED_KEY(entry_name, name, type_name, tgt, objtype, attr, proc) \ - CHECKED_KEY( \ - entry_name, name, "which is not " type_name, tgt, \ - unpacked.data.via.map.ptr[i].val.type == MSGPACK_OBJECT_##objtype, \ - attr, proc) -#define BOOLEAN_KEY(entry_name, name, tgt) \ - TYPED_KEY(entry_name, name, "a boolean", tgt, BOOLEAN, boolean, ID) -#define STRING_KEY(entry_name, name, tgt) \ - TYPED_KEY(entry_name, name, "a binary", tgt, BIN, bin, BINDUP) -#define 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(_(RERR "Error while reading ShaDa file: " \ - name " entry at position %" PRIu64 " " \ - "cannot be converted to a VimL dictionary"), \ - (uint64_t) 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(_(RERR "Error while reading ShaDa file: " \ - name " entry at position %" PRIu64 " " \ - "cannot be converted to a VimL list"), \ - (uint64_t) initial_fpos); \ - clear_tv(&aetv); \ - goto shada_read_next_item_error; \ - } \ - assert(aetv.v_type == VAR_LIST); \ - (tgt) = aetv.vval.v_list; \ - } \ - } while (0) switch ((ShadaEntryType) type_u64) { case kSDItemHeader: { if (!msgpack_rpc_to_dictionary(&(unpacked.data), &(entry->data.header))) { - emsgu(_(RERR "Error while reading ShaDa file: " - "header entry at position %" PRIu64 " is not a dictionary"), - (uint64_t) initial_fpos); + 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(_(RERR "Error while reading ShaDa file: " - "search pattern entry at position %" PRIu64 " " - "is not a dictionary"), - (uint64_t) initial_fpos); + emsgu(_(READERR("search pattern", "is not a dictionary")), + initial_fpos); goto shada_read_next_item_error; } entry->data.search_pattern = (struct search_pattern) { @@ -3618,31 +3623,36 @@ shada_read_next_item_start: 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 + 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(_(RERR "Error while reading ShaDa file: " - "search pattern entry at position %" PRIu64 " " - "has no pattern"), - (uint64_t) initial_fpos); - ga_clear(&ad_ga); - goto shada_read_next_item_error; + 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"); @@ -3653,10 +3663,7 @@ shada_read_next_item_start: case kSDItemGlobalMark: case kSDItemLocalMark: { if (unpacked.data.type != MSGPACK_OBJECT_MAP) { - emsgu(_(RERR "Error while reading ShaDa file: " - "mark entry at position %" PRIu64 " " - "is not a dictionary"), - (uint64_t) initial_fpos); + emsgu(_(READERR("mark", "is not a dictionary")), initial_fpos); goto shada_read_next_item_error; } entry->data.filemark = (struct shada_filemark) { @@ -3671,13 +3678,9 @@ shada_read_next_item_start: 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(_(RERR "Error while reading ShaDa file: " - "mark entry at position %" PRIu64 " " - "has n key which is only valid " - "for local and global mark entries"), - (uint64_t) initial_fpos); - ga_clear(&ad_ga); - goto shada_read_next_item_error; + 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 @@ -3685,44 +3688,34 @@ shada_read_next_item_start: "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 + } 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(_(RERR "Error while reading ShaDa file: " - "mark entry at position %" PRIu64 " " - "is missing file name"), - (uint64_t) initial_fpos); - ga_clear(&ad_ga); - goto shada_read_next_item_error; + emsgu(_(READERR("mark", "is missing file name")), initial_fpos); + CLEAR_GA_AND_ERROR_OUT(ad_ga); } if (entry->data.filemark.mark.lnum <= 0) { - emsgu(_(RERR "Error while reading ShaDa file: " - "mark entry at position %" PRIu64 " " - "has invalid line number"), - (uint64_t) initial_fpos); - ga_clear(&ad_ga); - goto shada_read_next_item_error; + emsgu(_(READERR("mark", "has invalid line number")), initial_fpos); + CLEAR_GA_AND_ERROR_OUT(ad_ga); } if (entry->data.filemark.mark.col < 0) { - emsgu(_(RERR "Error while reading ShaDa file: " - "mark entry at position %" PRIu64 " " - "has invalid column number"), - (uint64_t) initial_fpos); - ga_clear(&ad_ga); - goto shada_read_next_item_error; + 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(_(RERR "Error while reading ShaDa file: " - "register entry at position %" PRIu64 " " - "is not a dictionary"), - (uint64_t) initial_fpos); + emsgu(_(READERR("register", "is not a dictionary")), initial_fpos); goto shada_read_next_item_error; } entry->data.reg = (struct reg) { @@ -3739,64 +3732,57 @@ shada_read_next_item_start: 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(_(RERR "Error while reading ShaDa file: " - "register entry at position %" PRIu64 " " - "has " REG_KEY_CONTENTS " key with non-array value"), - (uint64_t) initial_fpos); - ga_clear(&ad_ga); - goto shada_read_next_item_error; - } - if (unpacked.data.via.map.ptr[i].val.via.array.size == 0) { - emsgu(_(RERR "Error while reading ShaDa file: " - "register entry at position %" PRIu64 " " - "has " REG_KEY_CONTENTS " key with empty array"), - (uint64_t) initial_fpos); - ga_clear(&ad_ga); - goto shada_read_next_item_error; - } - const msgpack_object_array arr = - unpacked.data.via.map.ptr[i].val.via.array; - for (size_t i = 0; i < arr.size; i++) { - if (arr.ptr[i].type != MSGPACK_OBJECT_BIN) { - emsgu(_(RERR "Error while reading ShaDa file: " - "register entry at position %" PRIu64 " " - "has " REG_KEY_CONTENTS " array with non-binary value"), - (uint64_t) initial_fpos); - ga_clear(&ad_ga); - goto shada_read_next_item_error; + 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 } - 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(_(RERR "Error while reading ShaDa file: " - "register entry at position %" PRIu64 " " - "has missing " REG_KEY_CONTENTS " array"), - (uint64_t) initial_fpos); - ga_clear(&ad_ga); - goto shada_read_next_item_error; + 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(_(RERR "Error while reading ShaDa file: " - "history entry at position %" PRIu64 " " - "is not an array"), - (uint64_t) initial_fpos); + emsgu(_(READERR("history", "is not an array")), initial_fpos); goto shada_read_next_item_error; } entry->data.history_item = (struct history_item) { @@ -3806,34 +3792,26 @@ shada_read_next_item_start: .additional_elements = NULL, }; if (unpacked.data.via.array.size < 2) { - emsgu(_(RERR "Error while reading ShaDa file: " - "history entry at position %" PRIu64 " " - "does not have enough elements"), - (uint64_t) initial_fpos); + 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(_(RERR "Error while reading ShaDa file: " - "history entry at position %" PRIu64 " " - "has wrong history type type"), - (uint64_t) initial_fpos); + 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(_(RERR "Error while reading ShaDa file: " - "history entry at position %" PRIu64 " " - "has wrong history string type"), - (uint64_t) initial_fpos); + 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(_(RERR "Error while reading ShaDa file: " - "history entry at position %" PRIu64 " " - "contains string with zero byte inside"), - (uint64_t) initial_fpos); + emsgu(_(READERR("history", "contains string with zero byte inside")), + initial_fpos); goto shada_read_next_item_error; } entry->data.history_item.histtype = @@ -3842,18 +3820,14 @@ shada_read_next_item_start: entry->data.history_item.histtype == HIST_SEARCH; if (is_hist_search) { if (unpacked.data.via.array.size < 3) { - emsgu(_(RERR "Error while reading ShaDa file: " - "search history entry at position %" PRIu64 " " - "does not have separator character"), - (uint64_t) initial_fpos); + 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(_(RERR "Error while reading ShaDa file: " - "search history entry at position %" PRIu64 " " - "has wrong history separator type"), - (uint64_t) initial_fpos); + emsgu(_(READERR("search history", + "has wrong history separator type")), initial_fpos); goto shada_read_next_item_error; } entry->data.history_item.sep = @@ -3866,9 +3840,8 @@ shada_read_next_item_start: shada_read_next_item_hist_no_conv: strsize = ( unpacked.data.via.array.ptr[1].via.bin.size - + 1 // Zero byte - + 1 // Separator character - ); + + 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, @@ -3895,39 +3868,28 @@ shada_read_next_item_hist_no_conv: } case kSDItemVariable: { if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) { - emsgu(_(RERR "Error while reading ShaDa file: " - "variable entry at position %" PRIu64 " " - "is not an array"), - (uint64_t) initial_fpos); + emsgu(_(READERR("variable", "is not an array")), initial_fpos); goto shada_read_next_item_error; } entry->data.global_var = (struct global_var) { .name = NULL, - .value = { - .v_type = VAR_UNKNOWN, - }, + .value = { .v_type = VAR_UNKNOWN }, .additional_elements = NULL }; if (unpacked.data.via.array.size < 2) { - emsgu(_(RERR "Error while reading ShaDa file: " - "variable entry at position %" PRIu64 " " - "does not have enough elements"), - (uint64_t) initial_fpos); + 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(_(RERR "Error while reading ShaDa file: " - "variable entry at position %" PRIu64 " " - "has wrong variable name type"), - (uint64_t) initial_fpos); + 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(_(RERR "Error while reading ShaDa file: " - "variable entry at position %" PRIu64 " " - "has wrong variable value type"), - (uint64_t) initial_fpos); + emsgu(_(READERR("variable", "has wrong variable value type")), + initial_fpos); goto shada_read_next_item_error; } entry->data.global_var.name = @@ -3935,10 +3897,8 @@ shada_read_next_item_hist_no_conv: 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(_(RERR "Error while reading ShaDa file: " - "variable entry at position %" PRIu64 " " - "has value that cannot be converted to the VimL value"), - (uint64_t) initial_fpos); + 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) { @@ -3958,10 +3918,7 @@ shada_read_next_item_hist_no_conv: } case kSDItemSubString: { if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) { - emsgu(_(RERR "Error while reading ShaDa file: " - "sub string entry at position %" PRIu64 " " - "is not an array"), - (uint64_t) initial_fpos); + emsgu(_(READERR("sub string", "is not an array")), initial_fpos); goto shada_read_next_item_error; } entry->data.sub_string = (struct sub_string) { @@ -3969,17 +3926,13 @@ shada_read_next_item_hist_no_conv: .additional_elements = NULL }; if (unpacked.data.via.array.size < 1) { - emsgu(_(RERR "Error while reading ShaDa file: " - "sub string entry at position %" PRIu64 " " - "does not have enough elements"), - (uint64_t) initial_fpos); + 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(_(RERR "Error while reading ShaDa file: " - "sub string entry at position %" PRIu64 " " - "has wrong sub string type"), - (uint64_t) initial_fpos); + emsgu(_(READERR("sub string", "has wrong sub string type")), + initial_fpos); goto shada_read_next_item_error; } entry->data.sub_string.sub = @@ -3991,10 +3944,7 @@ shada_read_next_item_hist_no_conv: } case kSDItemBufferList: { if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) { - emsgu(_(RERR "Error while reading ShaDa file: " - "buffer list entry at position %" PRIu64 " " - "is not an array"), - (uint64_t) initial_fpos); + emsgu(_(READERR("buffer list", "is not an array")), initial_fpos); goto shada_read_next_item_error; } entry->data.buffer_list = (struct buffer_list) { @@ -4018,7 +3968,7 @@ shada_read_next_item_hist_no_conv: emsgu(_(RERR "Error while reading ShaDa file: " "buffer list at position %" PRIu64 " " "contains entry that is not a dictionary"), - (uint64_t) initial_fpos); + initial_fpos); goto shada_read_next_item_error; } entry->data.buffer_list.buffers[i].pos.lnum = 1; @@ -4031,11 +3981,14 @@ shada_read_next_item_hist_no_conv: 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 + 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 } } } @@ -4043,25 +3996,22 @@ shada_read_next_item_hist_no_conv: emsgu(_(RERR "Error while reading ShaDa file: " "buffer list at position %" PRIu64 " " "contains entry with invalid line number"), - (uint64_t) initial_fpos); - ga_clear(&ad_ga); - goto shada_read_next_item_error; + 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"), - (uint64_t) initial_fpos); - ga_clear(&ad_ga); - goto shada_read_next_item_error; + 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"), - (uint64_t) initial_fpos); - ga_clear(&ad_ga); - goto shada_read_next_item_error; + initial_fpos); + CLEAR_GA_AND_ERROR_OUT(ad_ga); } SET_ADDITIONAL_DATA( entry->data.buffer_list.buffers[i].additional_data, @@ -4076,6 +4026,17 @@ shada_read_next_item_hist_no_conv: } } entry->type = (ShadaEntryType) type_u64; + ret = kSDReadStatusSuccess; +shada_read_next_item_end: + 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 @@ -4096,17 +4057,7 @@ shada_read_next_item_hist_no_conv: #undef TOSIZE #undef SET_ADDITIONAL_DATA #undef SET_ADDITIONAL_ELEMENTS - ret = kSDReadStatusSuccess; -shada_read_next_item_end: - 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 CLEAR_GA_AND_ERROR_OUT /// Check whether "name" is on removable media (according to 'shada') /// @@ -4118,7 +4069,7 @@ bool shada_removable(const char *name) { char *p; char part[51]; - int retval = FALSE; + bool retval = false; size_t n; char *new_name = home_replace_save(NULL, name); @@ -4127,7 +4078,7 @@ bool shada_removable(const char *name) if (part[0] == 'r') { n = STRLEN(part + 1); if (STRNICMP(part + 1, new_name, n) == 0) { - retval = TRUE; + retval = true; break; } } -- cgit From a4b2698a1fcab1fcbf073147dad141ed7bc3b613 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 11 Aug 2015 19:21:39 +0300 Subject: shada: Move all default values to an array of ShadaEntry structs --- src/nvim/shada.c | 209 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 119 insertions(+), 90 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index d36102c52b..3e1054f408 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -470,6 +470,77 @@ typedef struct sd_write_def { # 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. @@ -1670,6 +1741,10 @@ static bool shada_pack_entry(msgpack_packer *const packer, } \ } \ } 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); @@ -1726,17 +1801,14 @@ static bool shada_pack_entry(msgpack_packer *const packer, case kSDItemSearchPattern: { const size_t map_size = (size_t) ( 1 // Search pattern is always present - // Following items default to true: - + (size_t) !entry.data.search_pattern.magic - + (size_t) !entry.data.search_pattern.is_last_used - // Following items default to false: - + (size_t) entry.data.search_pattern.smartcase - + (size_t) entry.data.search_pattern.has_line_offset - + (size_t) entry.data.search_pattern.place_cursor_at_end - + (size_t) entry.data.search_pattern.is_substitute_pattern - + (size_t) entry.data.search_pattern.highlighted - // offset defaults to zero: - + (size_t) (entry.data.search_pattern.offset != 0) + + 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 @@ -1746,21 +1818,25 @@ static bool shada_pack_entry(msgpack_packer *const packer, PACK_STATIC_STR(SEARCH_KEY_PAT); msgpack_rpc_from_string(cstr_as_string(entry.data.search_pattern.pat), spacker); -#define PACK_BOOL(name, attr, nondef_value) \ +#define PACK_BOOL(entry, name, attr) \ do { \ - if (entry.data.search_pattern.attr == nondef_value) { \ + if (!CHECK_DEFAULT(entry, search_pattern.attr)) { \ PACK_STATIC_STR(name); \ - msgpack_pack_##nondef_value(spacker); \ + if (sd_default_values[entry.type].data.search_pattern.attr) { \ + msgpack_pack_false(spacker); \ + } else { \ + msgpack_pack_true(spacker); \ + } \ } \ } while (0) - PACK_BOOL(SEARCH_KEY_MAGIC, magic, false); - PACK_BOOL(SEARCH_KEY_IS_LAST_USED, is_last_used, false); - PACK_BOOL(SEARCH_KEY_SMARTCASE, smartcase, true); - PACK_BOOL(SEARCH_KEY_HAS_LINE_OFFSET, has_line_offset, true); - PACK_BOOL(SEARCH_KEY_PLACE_CURSOR_AT_END, place_cursor_at_end, true); - PACK_BOOL(SEARCH_KEY_IS_SUBSTITUTE_PATTERN, is_substitute_pattern, true); - PACK_BOOL(SEARCH_KEY_HIGHLIGHTED, highlighted, true); - if (entry.data.search_pattern.offset) { + 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); } @@ -1774,14 +1850,9 @@ static bool shada_pack_entry(msgpack_packer *const packer, case kSDItemJump: { const size_t map_size = (size_t) ( 1 // File name - // Line: defaults to 1 - + (size_t) (entry.data.filemark.mark.lnum != 1) - // Column: defaults to zero: - + (size_t) (entry.data.filemark.mark.col != 0) - // Mark name: defaults to '"' - + (size_t) (entry.type != kSDItemJump - && entry.type != kSDItemChange - && entry.data.filemark.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 @@ -1791,16 +1862,18 @@ static bool shada_pack_entry(msgpack_packer *const packer, PACK_STATIC_STR(KEY_FILE); msgpack_rpc_from_string(cstr_as_string(entry.data.filemark.fname), spacker); - if (entry.data.filemark.mark.lnum != 1) { + if (!CHECK_DEFAULT(entry, filemark.mark.lnum)) { PACK_STATIC_STR(KEY_LNUM); msgpack_pack_long(spacker, entry.data.filemark.mark.lnum); } - if (entry.data.filemark.mark.col != 0) { + if (!CHECK_DEFAULT(entry, filemark.mark.col)) { PACK_STATIC_STR(KEY_COL); msgpack_pack_long(spacker, entry.data.filemark.mark.col); } - if (entry.data.filemark.name != '"' && entry.type != kSDItemJump - && entry.type != kSDItemChange) { + 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); } @@ -1810,10 +1883,8 @@ static bool shada_pack_entry(msgpack_packer *const packer, case kSDItemRegister: { const size_t map_size = (size_t) ( 2 // Register contents and name - // Register type: defaults to MCHAR - + (size_t) (entry.data.reg.type != MCHAR) - // Register width: defaults to zero - + (size_t) (entry.data.reg.width != 0) + + 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 @@ -1827,11 +1898,11 @@ static bool shada_pack_entry(msgpack_packer *const packer, } PACK_STATIC_STR(KEY_NAME_CHAR); msgpack_pack_char(spacker, entry.data.reg.name); - if (entry.data.reg.type != MCHAR) { + if (!CHECK_DEFAULT(entry, reg.type)) { PACK_STATIC_STR(REG_KEY_TYPE); msgpack_pack_uint8(spacker, entry.data.reg.type); } - if (entry.data.reg.width != 0) { + if (!CHECK_DEFAULT(entry, reg.width)) { PACK_STATIC_STR(REG_KEY_WIDTH); msgpack_pack_uint64(spacker, (uint64_t) entry.data.reg.width); } @@ -1843,10 +1914,10 @@ static bool shada_pack_entry(msgpack_packer *const packer, for (size_t i = 0; i < entry.data.buffer_list.size; i++) { const size_t map_size = (size_t) ( 1 // Buffer name - // Line number: defaults to 1 - + (size_t) (entry.data.buffer_list.buffers[i].pos.lnum != 1) - // Column number: defaults to 0 - + (size_t) (entry.data.buffer_list.buffers[i].pos.col != 0) + + (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 @@ -1880,6 +1951,8 @@ static bool shada_pack_entry(msgpack_packer *const packer, 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) { @@ -3591,6 +3664,7 @@ shada_read_next_item_start: 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))) { @@ -3605,18 +3679,6 @@ shada_read_next_item_start: initial_fpos); goto shada_read_next_item_error; } - entry->data.search_pattern = (struct search_pattern) { - .magic = true, - .smartcase = false, - .has_line_offset = false, - .place_cursor_at_end = false, - .offset = 0, - .is_last_used = true, - .is_substitute_pattern = false, - .highlighted = false, - .pat = NULL, - .additional_data = NULL, - }; garray_T ad_ga; ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1); for (size_t i = 0; i < unpacked.data.via.map.size; i++) { @@ -3666,12 +3728,6 @@ shada_read_next_item_start: emsgu(_(READERR("mark", "is not a dictionary")), initial_fpos); goto shada_read_next_item_error; } - entry->data.filemark = (struct shada_filemark) { - .name = '"', - .mark = (pos_T) {1, 0, 0}, - .fname = NULL, - .additional_data = NULL, - }; garray_T ad_ga; ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1); for (size_t i = 0; i < unpacked.data.via.map.size; i++) { @@ -3718,14 +3774,6 @@ shada_read_next_item_start: emsgu(_(READERR("register", "is not a dictionary")), initial_fpos); goto shada_read_next_item_error; } - entry->data.reg = (struct reg) { - .name = NUL, - .type = MCHAR, - .contents = NULL, - .contents_size = 0, - .width = 0, - .additional_data = NULL, - }; garray_T ad_ga; ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1); for (size_t i = 0; i < unpacked.data.via.map.size; i++) { @@ -3785,12 +3833,6 @@ shada_read_next_item_start: emsgu(_(READERR("history", "is not an array")), initial_fpos); goto shada_read_next_item_error; } - entry->data.history_item = (struct history_item) { - .histtype = 0, - .string = NULL, - .sep = 0, - .additional_elements = NULL, - }; if (unpacked.data.via.array.size < 2) { emsgu(_(READERR("history", "does not have enough elements")), initial_fpos); @@ -3871,11 +3913,6 @@ shada_read_next_item_hist_no_conv: emsgu(_(READERR("variable", "is not an array")), initial_fpos); goto shada_read_next_item_error; } - entry->data.global_var = (struct global_var) { - .name = NULL, - .value = { .v_type = VAR_UNKNOWN }, - .additional_elements = NULL - }; if (unpacked.data.via.array.size < 2) { emsgu(_(READERR("variable", "does not have enough elements")), initial_fpos); @@ -3921,10 +3958,6 @@ shada_read_next_item_hist_no_conv: emsgu(_(READERR("sub string", "is not an array")), initial_fpos); goto shada_read_next_item_error; } - entry->data.sub_string = (struct sub_string) { - .sub = NULL, - .additional_elements = NULL - }; if (unpacked.data.via.array.size < 1) { emsgu(_(READERR("sub string", "does not have enough elements")), initial_fpos); @@ -3947,10 +3980,6 @@ shada_read_next_item_hist_no_conv: emsgu(_(READERR("buffer list", "is not an array")), initial_fpos); goto shada_read_next_item_error; } - entry->data.buffer_list = (struct buffer_list) { - .size = 0, - .buffers = NULL, - }; if (unpacked.data.via.array.size == 0) { break; } @@ -3971,7 +4000,7 @@ shada_read_next_item_hist_no_conv: initial_fpos); goto shada_read_next_item_error; } - entry->data.buffer_list.buffers[i].pos.lnum = 1; + entry->data.buffer_list.buffers[i].pos = default_pos; garray_T ad_ga; ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1); { -- cgit From f9ae1caf7ee4ff2f6247b08dd85b1b8d52b43318 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 11 Aug 2015 21:00:31 +0300 Subject: shada: Fix jump list merger code --- src/nvim/shada.c | 43 ++++++++++++------------------------------- 1 file changed, 12 insertions(+), 31 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 3e1054f408..0c824d0dd3 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1465,22 +1465,13 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) if (i != -1) { if (i < jl_len) { if (jl_len == JUMPLISTSIZE) { - free_xfmark(curwin->w_jumplist[0]); - memmove(&curwin->w_jumplist[0], &curwin->w_jumplist[1], - sizeof(curwin->w_jumplist[0]) * (size_t) i); - } else { - memmove(&curwin->w_jumplist[i + 1], &curwin->w_jumplist[i], - sizeof(curwin->w_jumplist[0]) - * (size_t) (jl_len - i)); - } - } else if (i == jl_len) { - if (jl_len == JUMPLISTSIZE) { - i = -1; - } else if (jl_len > 0) { - memmove(&curwin->w_jumplist[1], &curwin->w_jumplist[0], - sizeof(curwin->w_jumplist[0]) - * (size_t) jl_len); + free_xfmark(curwin->w_jumplist[curwin->w_jumplistlen - 1]); } + memmove(&curwin->w_jumplist[i + 1], &curwin->w_jumplist[i], + sizeof(curwin->w_jumplist[0]) + * (size_t) (jl_len - i)); + } else if (i == jl_len && jl_len == JUMPLISTSIZE) { + i = -1; } } if (i != -1) { @@ -2428,23 +2419,13 @@ static inline ShaDaWriteResult shada_read_when_writing( } if (i != -1) { if (i < jl_len) { - if (jl_len == JUMPLISTSIZE) { - if (wms->jumps[0].can_free_entry) { - shada_free_shada_entry(&wms->jumps[0].data); - } - memmove(&wms->jumps[0], &wms->jumps[1], - sizeof(wms->jumps[0]) * (size_t) i); - } else { - memmove(&wms->jumps[i + 1], &wms->jumps[i], - sizeof(wms->jumps[0]) * (size_t) (jl_len - i)); - } - } else if (i == jl_len) { - if (jl_len == JUMPLISTSIZE) { - i = -1; - } else if (jl_len > 0) { - memmove(&wms->jumps[1], &wms->jumps[0], - sizeof(wms->jumps[0]) * (size_t) jl_len); + if (jl_len == JUMPLISTSIZE && wms->jumps[0].can_free_entry) { + shada_free_shada_entry(&wms->jumps[wms->jumps_size - 1].data); } + memmove(&wms->jumps[i + 1], &wms->jumps[i], + sizeof(wms->jumps[0]) * (size_t) (jl_len - i)); + } else if (i == jl_len && jl_len == JUMPLISTSIZE) { + i = -1; } } if (i != -1) { -- cgit From 9d8184c65a5fdbf9793b759843178db74137b2a7 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 11 Aug 2015 21:56:24 +0300 Subject: shada: Move common jump/change merging code into a macros --- src/nvim/shada.c | 281 +++++++++++++++++++++++++------------------------------ 1 file changed, 128 insertions(+), 153 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 0c824d0dd3..c9a9fffe37 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1241,6 +1241,88 @@ 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, entry, \ + mark_attr, fname_cond, free_func, entry_to_jumps_func, \ + idxadj_func) \ + do { \ + const int jl_len = (int) jumps_size; \ + int i; \ + for (i = 0; i < jl_len; i++) { \ + const jumps_type jl_entry = jumps[i]; \ + if (jl_entry.timestamp_attr.timestamp >= entry.timestamp) { \ + if (marks_equal(jl_entry.mark_attr.mark, \ + entry.data.filemark.mark) \ + && fname_cond) { \ + i = -1; \ + } \ + break; \ + } \ + } \ + if (i != -1) { \ + if (i < jl_len) { \ + if (jl_len == JUMPLISTSIZE) { \ + free_func(jumps[jl_len - 1]); \ + } \ + memmove(&jumps[i + 1], &jumps[i], \ + sizeof(jumps[0]) * (size_t) (jl_len - i)); \ + } else if (i == jl_len && jl_len == JUMPLISTSIZE) { \ + i = -1; \ + } \ + } \ + if (i != -1) { \ + jumps[i] = entry_to_jumps_func(entry); \ + if (jl_len < JUMPLISTSIZE) { \ + jumps_size++; \ + } \ + idxadj_func(i); \ + } else { \ + shada_free_shada_entry(&entry); \ + } \ + } while (0) + +#define MERGE_CHANGES(changes_size, changes_type, changes, timestamp_attr, \ + mark_attr, entry, free_func, fin_func, afterfree_func) \ + do { \ + const int cl_len = (int) changes_size; \ + int i; \ + for (i = cl_len; i > 0; i--) { \ + const changes_type cl_entry = changes[i - 1]; \ + if (cl_entry.timestamp_attr <= entry.timestamp) { \ + if (marks_equal(cl_entry.mark_attr, \ + entry.data.filemark.mark)) { \ + i = -1; \ + } \ + break; \ + } \ + } \ + if (i > 0) { \ + if (cl_len == JUMPLISTSIZE) { \ + free_func(changes[0]); \ + memmove(&changes[0], &changes[1], \ + sizeof(changes[0]) * (size_t) i); \ + } else { \ + memmove(&changes[i + 1], &changes[i], \ + sizeof(changes[0]) * (size_t) (cl_len - i)); \ + } \ + } else if (i == 0) { \ + if (cl_len == JUMPLISTSIZE) { \ + i = -1; \ + } else if (cl_len > 0) { \ + memmove(&changes[1], &changes[0], \ + sizeof(changes[0]) * (size_t) cl_len); \ + } \ + } \ + if (i != -1) { \ + changes[i] = fin_func(entry); \ + if (cl_len < JUMPLISTSIZE) { \ + changes_size++; \ + } \ + } 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. @@ -1447,45 +1529,21 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) break; } } else { - const int jl_len = curwin->w_jumplistlen; - int i; - for (i = 0; i < jl_len; i++) { - const xfmark_T jl_fm = curwin->w_jumplist[i]; - if (jl_fm.fmark.timestamp >= cur_entry.timestamp) { - if (marks_equal(fm.fmark.mark, jl_fm.fmark.mark) - && (buf == NULL - ? (jl_fm.fname != NULL - && STRCMP(fm.fname, jl_fm.fname) == 0) - : fm.fmark.fnum == jl_fm.fmark.fnum)) { - i = -1; - } - break; - } - } - if (i != -1) { - if (i < jl_len) { - if (jl_len == JUMPLISTSIZE) { - free_xfmark(curwin->w_jumplist[curwin->w_jumplistlen - 1]); - } - memmove(&curwin->w_jumplist[i + 1], &curwin->w_jumplist[i], - sizeof(curwin->w_jumplist[0]) - * (size_t) (jl_len - i)); - } else if (i == jl_len && jl_len == JUMPLISTSIZE) { - i = -1; - } - } - if (i != -1) { - curwin->w_jumplist[i] = fm; - if (jl_len < JUMPLISTSIZE) { - curwin->w_jumplistlen++; - } - if (curwin->w_jumplistidx >= i - && curwin->w_jumplistidx + 1 < curwin->w_jumplistlen) { - curwin->w_jumplistidx++; - } - } else { - shada_free_shada_entry(&cur_entry); +#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++; \ } + MERGE_JUMPS(curwin->w_jumplistlen, curwin->w_jumplist, xfmark_T, + fmark, cur_entry, fmark, + (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); +#undef SDE_TO_XFMARK +#undef ADJUST_IDX } // Do not free shada entry: its allocated memory was saved above. break; @@ -1552,43 +1610,13 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) } else { int kh_ret; (void) kh_put(bufset, &cl_bufs, (uintptr_t) buf, &kh_ret); - const int cl_len = buf->b_changelistlen; - int i; - for (i = cl_len; i > 0; i--) { - const fmark_T cl_fm = buf->b_changelist[i - 1]; - if (cl_fm.timestamp <= cur_entry.timestamp) { - if (marks_equal(fm.mark, cl_fm.mark)) { - i = -1; - } - break; - } - } - if (i > 0) { - if (cl_len == JUMPLISTSIZE) { - free_fmark(buf->b_changelist[0]); - memmove(&buf->b_changelist[0], &buf->b_changelist[1], - sizeof(buf->b_changelist[0]) * (size_t) i); - } else { - memmove(&buf->b_changelist[i + 1], &buf->b_changelist[i], - sizeof(buf->b_changelist[0]) * (size_t) (cl_len - i)); - } - } else if (i == 0) { - if (cl_len == JUMPLISTSIZE) { - i = -1; - } else if (cl_len > 0) { - memmove(&buf->b_changelist[1], &buf->b_changelist[0], - sizeof(buf->b_changelist[0]) * (size_t) cl_len); - } - } - if (i != -1) { - buf->b_changelist[i] = fm; - if (cl_len < JUMPLISTSIZE) { - buf->b_changelistlen++; - } - } else { - shada_free_shada_entry(&cur_entry); - cur_entry.data.filemark.fname = NULL; - } +#define SDE_TO_FMARK(entry) fm +#define AFTERFREE(entry) (entry).data.filemark.fname = NULL + MERGE_CHANGES(buf->b_changelistlen, fmark_T, buf->b_changelist, + timestamp, mark, cur_entry, free_fmark, SDE_TO_FMARK, + AFTERFREE); +#undef SDE_TO_FMARK +#undef AFTERFREE } // Do not free shada entry: except for fname, its allocated memory (i.e. // additional_data attribute contents if non-NULL) was saved above. @@ -2357,88 +2385,35 @@ static inline ShaDaWriteResult shada_read_when_writing( COMPARE_WITH_ENTRY(&filemarks->marks[idx], entry); } } else { - const int cl_len = (int) filemarks->changes_size; - int i; - for (i = cl_len; i > 0; i--) { - const ShadaEntry old_entry = filemarks->changes[i - 1].data; - if (old_entry.timestamp <= entry.timestamp) { - if (marks_equal(old_entry.data.filemark.mark, - entry.data.filemark.mark)) { - i = -1; - } - break; - } - } - if (i > 0) { - if (cl_len == JUMPLISTSIZE) { - if (filemarks->changes[0].can_free_entry) { - shada_free_shada_entry(&filemarks->changes[0].data); - } - memmove(&filemarks->changes[0], &filemarks->changes[1], - sizeof(filemarks->changes[0]) * (size_t) i); - } else { - memmove(&filemarks->changes[i + 1], &filemarks->changes[i], - sizeof(filemarks->changes[0]) * (size_t) (cl_len - i)); - } - } else if (i == 0) { - if (cl_len == JUMPLISTSIZE) { - i = -1; - } else if (cl_len > 0) { - memmove(&filemarks->changes[1], &filemarks->changes[0], - sizeof(filemarks->changes[0]) * (size_t) cl_len); - } - } - if (i != -1) { - filemarks->changes[i] = (PossiblyFreedShadaEntry) { - .can_free_entry = true, - .data = entry - }; - if (cl_len < JUMPLISTSIZE) { - filemarks->changes_size++; - } - } else { - shada_free_shada_entry(&entry); - } +#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) + MERGE_CHANGES(filemarks->changes_size, PossiblyFreedShadaEntry, + filemarks->changes, data.timestamp, + data.data.filemark.mark, entry, + FREE_POSSIBLY_FREED_SHADA_ENTRY, SDE_TO_PFSDE, + AFTERFREE_DUMMY); +#undef AFTERFREE_DUMMY } break; } case kSDItemJump: { - const int jl_len = (int) wms->jumps_size; - int i; - for (i = 0; i < jl_len; i++) { - const ShadaEntry old_entry = wms->jumps[i].data; - if (old_entry.timestamp >= entry.timestamp) { - if (marks_equal(old_entry.data.filemark.mark, - entry.data.filemark.mark) - && strcmp(old_entry.data.filemark.fname, - entry.data.filemark.fname) == 0) { - i = -1; - } - break; - } - } - if (i != -1) { - if (i < jl_len) { - if (jl_len == JUMPLISTSIZE && wms->jumps[0].can_free_entry) { - shada_free_shada_entry(&wms->jumps[wms->jumps_size - 1].data); - } - memmove(&wms->jumps[i + 1], &wms->jumps[i], - sizeof(wms->jumps[0]) * (size_t) (jl_len - i)); - } else if (i == jl_len && jl_len == JUMPLISTSIZE) { - i = -1; - } - } - if (i != -1) { - wms->jumps[i] = (PossiblyFreedShadaEntry) { - .can_free_entry = true, - .data = entry, - }; - if (jl_len < JUMPLISTSIZE) { - wms->jumps_size++; - } - } else { - shada_free_shada_entry(&entry); - } +#define ADJUST_IDX_DUMMY(i) + MERGE_JUMPS(wms->jumps_size, wms->jumps, PossiblyFreedShadaEntry, + data, entry, data.data.filemark, + strcmp(jl_entry.data.data.filemark.fname, + entry.data.filemark.fname) == 0, + FREE_POSSIBLY_FREED_SHADA_ENTRY, SDE_TO_PFSDE, + ADJUST_IDX_DUMMY); +#undef FREE_POSSIBLY_FREED_SHADA_ENTRY +#undef SDE_TO_PFSDE +#undef ADJUST_IDX_DUMMY break; } } -- cgit From 17c69258a7128ee31fbf294b46599241f8e226cc Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 12 Aug 2015 21:01:45 +0300 Subject: shada: Use same merging code for jumps and changes --- src/nvim/shada.c | 109 +++++++++++---------------------- test/functional/shada/marks_spec.lua | 26 ++------ test/functional/shada/merging_spec.lua | 13 ++-- 3 files changed, 50 insertions(+), 98 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index c9a9fffe37..a9f5d9bc5b 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1241,82 +1241,43 @@ 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, entry, \ - mark_attr, fname_cond, free_func, entry_to_jumps_func, \ - idxadj_func) \ +#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 = 0; i < jl_len; i++) { \ - const jumps_type jl_entry = jumps[i]; \ - if (jl_entry.timestamp_attr.timestamp >= entry.timestamp) { \ - if (marks_equal(jl_entry.mark_attr.mark, \ - entry.data.filemark.mark) \ + 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 != -1) { \ - if (i < jl_len) { \ - if (jl_len == JUMPLISTSIZE) { \ - free_func(jumps[jl_len - 1]); \ - } \ + if (i > 0) { \ + if (jl_len == JUMPLISTSIZE) { \ + free_func(jumps[0]); \ + memmove(&jumps[0], &jumps[1], sizeof(jumps[1]) * (size_t) i); \ + } else { \ memmove(&jumps[i + 1], &jumps[i], \ sizeof(jumps[0]) * (size_t) (jl_len - i)); \ - } else if (i == jl_len && jl_len == JUMPLISTSIZE) { \ + } \ + } 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] = entry_to_jumps_func(entry); \ + jumps[i] = fin_func(entry); \ if (jl_len < JUMPLISTSIZE) { \ jumps_size++; \ } \ idxadj_func(i); \ - } else { \ - shada_free_shada_entry(&entry); \ - } \ - } while (0) - -#define MERGE_CHANGES(changes_size, changes_type, changes, timestamp_attr, \ - mark_attr, entry, free_func, fin_func, afterfree_func) \ - do { \ - const int cl_len = (int) changes_size; \ - int i; \ - for (i = cl_len; i > 0; i--) { \ - const changes_type cl_entry = changes[i - 1]; \ - if (cl_entry.timestamp_attr <= entry.timestamp) { \ - if (marks_equal(cl_entry.mark_attr, \ - entry.data.filemark.mark)) { \ - i = -1; \ - } \ - break; \ - } \ - } \ - if (i > 0) { \ - if (cl_len == JUMPLISTSIZE) { \ - free_func(changes[0]); \ - memmove(&changes[0], &changes[1], \ - sizeof(changes[0]) * (size_t) i); \ - } else { \ - memmove(&changes[i + 1], &changes[i], \ - sizeof(changes[0]) * (size_t) (cl_len - i)); \ - } \ - } else if (i == 0) { \ - if (cl_len == JUMPLISTSIZE) { \ - i = -1; \ - } else if (cl_len > 0) { \ - memmove(&changes[1], &changes[0], \ - sizeof(changes[0]) * (size_t) cl_len); \ - } \ - } \ - if (i != -1) { \ - changes[i] = fin_func(entry); \ - if (cl_len < JUMPLISTSIZE) { \ - changes_size++; \ - } \ } else { \ shada_free_shada_entry(&entry); \ afterfree_func(entry); \ @@ -1535,15 +1496,17 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) && 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, cur_entry, fmark, + 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); + 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; @@ -1612,11 +1575,13 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) (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 - MERGE_CHANGES(buf->b_changelistlen, fmark_T, buf->b_changelist, - timestamp, mark, cur_entry, free_fmark, SDE_TO_FMARK, - AFTERFREE); +#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. @@ -2394,26 +2359,26 @@ static inline ShaDaWriteResult shada_read_when_writing( #define SDE_TO_PFSDE(entry) \ ((PossiblyFreedShadaEntry) { .can_free_entry = true, .data = entry }) #define AFTERFREE_DUMMY(entry) - MERGE_CHANGES(filemarks->changes_size, PossiblyFreedShadaEntry, - filemarks->changes, data.timestamp, - data.data.filemark.mark, entry, - FREE_POSSIBLY_FREED_SHADA_ENTRY, SDE_TO_PFSDE, - AFTERFREE_DUMMY); -#undef AFTERFREE_DUMMY +#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: { -#define ADJUST_IDX_DUMMY(i) MERGE_JUMPS(wms->jumps_size, wms->jumps, PossiblyFreedShadaEntry, - data, entry, data.data.filemark, + 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, - ADJUST_IDX_DUMMY); + DUMMY_IDX_ADJ, AFTERFREE_DUMMY); #undef FREE_POSSIBLY_FREED_SHADA_ENTRY #undef SDE_TO_PFSDE -#undef ADJUST_IDX_DUMMY +#undef DUMMY_IDX_ADJ +#undef AFTERFREE_DUMMY break; } } diff --git a/test/functional/shada/marks_spec.lua b/test/functional/shada/marks_spec.lua index 8b9e70045c..dc7a710143 100644 --- a/test/functional/shada/marks_spec.lua +++ b/test/functional/shada/marks_spec.lua @@ -118,28 +118,14 @@ describe('ShaDa support code', function() nvim_command('edit ' .. testfilename) nvim_feed('G') nvim_feed('gg') - -- nvim_command('redir! >/tmp/jumps.last | jumps | redir END') - -- nvim_command('wshada /tmp/foo') + nvim_command('enew') + nvim_feed('gg') + nvim_command('redir => g:jumps | jumps | redir END') + local saved = nvim_eval('g:jumps') nvim_command('qall') reset() - nvim_command('redraw') - -- nvim_command('redir! >/tmp/jumps.init | jumps | redir END') - nvim_command('edit ' .. testfilename) - -- nvim_command('redir! >/tmp/jumps | jumps | redir END') - eq(testfilename, nvim_eval('bufname("%")')) - eq(1, nvim_current_line()) - nvim_command('execute "normal! \\"') - eq(testfilename, nvim_eval('bufname("%")')) - eq(1, nvim_current_line()) - nvim_command('execute "normal! \\"') - eq(testfilename, nvim_eval('bufname("%")')) - eq(2, nvim_current_line()) - nvim_command('execute "normal! \\"') - eq(testfilename_2, nvim_eval('bufname("%")')) - eq(1, nvim_current_line()) - nvim_command('execute "normal! \\"') - eq(testfilename_2, nvim_eval('bufname("%")')) - eq(2, nvim_current_line()) + nvim_command('redir => g:jumps | jumps | redir END') + eq(saved, nvim_eval('g:jumps')) end) it('is able to dump and restore jump list with different times (slow!)', diff --git a/test/functional/shada/merging_spec.lua b/test/functional/shada/merging_spec.lua index e627f8f76e..3daa5af7c2 100644 --- a/test/functional/shada/merging_spec.lua +++ b/test/functional/shada/merging_spec.lua @@ -772,12 +772,13 @@ describe('ShaDa jumps support code', function() eq('', nvim_eval('bufname("%")')) eq('\n' .. ' jump line col file/text\n' - .. ' 5 2 0 /a/b/c\n' + .. ' 6 2 0 /a/b/c\n' + .. ' 5 2 0 /a/b/d\n' .. ' 4 3 0 /a/b/d\n' - .. ' 3 2 0 /a/b/d\n' + .. ' 3 2 0 /a/b/e\n' .. ' 2 2 0 /a/b/f\n' - .. ' 1 2 0 /a/b/e\n' - .. '> 0 1 0 ', nvim_eval('g:jumps')) + .. ' 1 1 0 \n' + .. '>', nvim_eval('g:jumps')) end) it('merges jumps when writing', function() @@ -791,10 +792,10 @@ describe('ShaDa jumps support code', function() eq(0, exc_exec('wshada ' .. shada_fname)) local jumps = { {file='/a/b/c', line=2}, - {file='/a/b/d', line=3}, {file='/a/b/d', line=2}, - {file='/a/b/f', line=2}, + {file='/a/b/d', line=3}, {file='/a/b/e', line=2}, + {file='/a/b/f', line=2}, } local found = 0 for _, v in ipairs(read_shada_file(shada_fname)) do -- cgit From 1889ee329f98ba5a1a2988c1e0e82c6c2b19e1ad Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 13 Aug 2015 20:25:54 +0300 Subject: shada: Allow moving jump index past the end idx == len + 1 indicates that no jumplist entry is currently used. --- src/nvim/shada.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index a9f5d9bc5b..e314c702ad 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1493,7 +1493,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) #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 + 1 <= curwin->w_jumplistlen) { \ curwin->w_jumplistidx++; \ } #define DUMMY_AFTERFREE(entry) -- cgit From 9b53acffb520556457391720e88c742688f55df5 Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 13 Aug 2015 21:26:30 +0300 Subject: shada: Avoid “conditional … depends on initialized value” error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Error can be seen in some errors_spec.lua tests. --- src/nvim/shada.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index e314c702ad..aa409c937c 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -3978,8 +3978,10 @@ shada_read_next_item_hist_no_conv: entry->type = (ShadaEntryType) type_u64; ret = kSDReadStatusSuccess; shada_read_next_item_end: - msgpack_unpacked_destroy(&unpacked); - xfree(buf); + if (buf != NULL) { + msgpack_unpacked_destroy(&unpacked); + xfree(buf); + } return ret; shada_read_next_item_error: entry->type = (ShadaEntryType) type_u64; -- cgit From 563f5c26507c8de4db1f4ccac4828b2db37017dc Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 13 Aug 2015 21:41:51 +0300 Subject: shada: Fix memory leak when reading registers from ShaDa file Occures once register from ShaDa file overwrites non-empty register in the current instance. --- src/nvim/ops.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nvim/ops.c b/src/nvim/ops.c index da1eb7afcb..9424e82578 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -5379,6 +5379,7 @@ bool op_register_set(const char name, const yankreg_T reg) if (i == -1) { return false; } + free_register(&y_regs[i]); y_regs[i] = reg; return true; } -- cgit From 597da1977708bcdf8c9dc0c374896fa0e3e7cb39 Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 13 Aug 2015 21:50:11 +0300 Subject: shada: Fix incompatible pointer types error in non-glibc builds --- src/nvim/shada.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index aa409c937c..86714a5eb7 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -3186,7 +3186,7 @@ static inline uint64_t be64toh(uint64_t big_endian_64_bits) #ifdef SHADA_BIG_ENDIAN return big_endian_64_bits; #else - uint8_t *buf = &big_endian_64_bits; + 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); -- cgit From 1a348f8ed82bce1d7fb56c907c7508e10914299f Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 13 Aug 2015 21:56:36 +0300 Subject: ops: Fix “conditional … depends on unitialized …” error in op_yank_reg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/nvim/ops.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 9424e82578..5ba1d8b2f5 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -2356,7 +2356,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) reg->y_type = yanktype; /* set the yank register type */ reg->y_width = 0; reg->y_array = xcalloc(yanklines, sizeof(char_u *)); - set_yreg_additional_data(reg, NULL); + reg->additional_data = NULL; reg->timestamp = os_time(); y_idx = 0; -- cgit From 689390210a03aef00b627327dc8ce8723f2ecb4d Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 13 Aug 2015 23:31:14 +0300 Subject: mark: Fix out-of-bounds array access when iterating over global marks --- src/nvim/mark.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 6ab0403e30..38495079e3 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -1203,12 +1203,14 @@ const void *mark_global_iter(const void *const iter, char *const name, const xfmark_T *iter_mark = (iter == NULL ? &(namedfm[0]) : (const xfmark_T *const) iter); - while (!iter_mark->fmark.mark.lnum - && (size_t) (iter_mark - &(namedfm[0])) < ARRAY_SIZE(namedfm)) { + while ((size_t) (iter_mark - &(namedfm[0])) < ARRAY_SIZE(namedfm) + && !iter_mark->fmark.mark.lnum) { iter_mark++; } - if (!iter_mark->fmark.mark.lnum) { - *fm = (xfmark_T) {.fmark = {.mark = {.lnum = 0}}}; + if ((size_t) (iter_mark - &(namedfm[0])) == ARRAY_SIZE(namedfm) + || !iter_mark->fmark.mark.lnum) { + *fm = (xfmark_T) { .fmark = { .mark = { .lnum = 0 } } }; + return NULL; } size_t iter_off = (size_t) (iter_mark - &(namedfm[0])); *name = (char) (iter_off < NMARKS -- cgit From e4c0741206b7d3e307d5ecf8bdb5d0266d952513 Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 13 Aug 2015 23:34:42 +0300 Subject: ops: Fix access to unsaved register when iterating over registers --- src/nvim/ops.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 5ba1d8b2f5..b3e889c0ec 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -5336,11 +5336,11 @@ const void *op_register_iter(const void *const iter, char *const name, const yankreg_T *iter_reg = (iter == NULL ? &(y_regs[0]) : (const yankreg_T *const) iter); - while (reg_empty(iter_reg) && iter_reg - &(y_regs[0]) < NUM_SAVED_REGISTERS) { + while (iter_reg - &(y_regs[0]) < NUM_SAVED_REGISTERS && reg_empty(iter_reg)) { iter_reg++; } - if (reg_empty(iter_reg)) { - *reg = (yankreg_T) {.y_array = NULL}; + if (iter_reg - &(y_regs[0]) == NUM_SAVED_REGISTERS || reg_empty(iter_reg)) { + *reg = (yankreg_T) { .y_array = NULL }; return NULL; } size_t iter_off = iter_reg - &(y_regs[0]); -- cgit From 5941380f49b4007fd143ed26b9681e2dde87d98d Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 14 Aug 2015 00:02:27 +0300 Subject: mark: Avoid address sanitizer error --- src/nvim/mark.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 38495079e3..7c76bd2162 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -1349,10 +1349,11 @@ size_t mark_buffer_amount(const buf_T *const buf) /// @return true on success, false on failure. bool mark_set_global(const char name, const xfmark_T fm, const bool update) { - xfmark_T *fm_tgt = &(namedfm[mark_global_index(name)]); - if (fm_tgt == &namedfm[0] - 1) { + const int idx = mark_global_index(name); + if (idx == -1) { return false; } + xfmark_T *const fm_tgt = &(namedfm[idx]); if (update && fm.fmark.timestamp <= fm_tgt->fmark.timestamp) { return false; } -- cgit From bcd8789609476c5ad845d186be13f338c951a068 Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 14 Aug 2015 00:18:36 +0300 Subject: ex_getln: Return something from hist_type2char without asserts Otherwise QuickBuild shows a warning that control may reach end of non-void function. --- src/nvim/ex_getln.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 7be8d722b8..2bfe574b1d 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -4783,6 +4783,7 @@ int hist_type2char(int type) assert(false); } } + return NUL; } /* -- cgit From bcb60b0a645582558017e31b75f691a889cf562b Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 14 Aug 2015 00:38:24 +0300 Subject: shada: Fix unused variable warning when using Release build --- src/nvim/shada.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 86714a5eb7..f44594f372 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -3210,7 +3210,8 @@ static ShaDaReadResult fread_len(ShaDaReadDef *const sd_reader, 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); + 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"), -- cgit From 9afa45e6391eda5aeeed13475e65071558b5b59c Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 14 Aug 2015 08:21:30 +0300 Subject: shada: Fix -Wstrict-aliasing gcc error Also removes theoretically possible unaligned memory access when computing be64toh() argument. --- src/nvim/shada.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index f44594f372..b98aa2f258 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -3294,14 +3294,14 @@ static ShaDaReadResult msgpack_read_uint64(ShaDaReadDef *const sd_reader, return kSDReadStatusNotShaDa; } } - uint8_t buf[sizeof(uint64_t)] = {0, 0, 0, 0, 0, 0, 0, 0}; + uint64_t buf = 0; + char *buf_u8 = (char *) &buf; ShaDaReadResult fl_ret; - if ((fl_ret = fread_len(sd_reader, (char *) &(buf[sizeof(uint64_t)-length]), - length)) + if ((fl_ret = fread_len(sd_reader, &(buf_u8[sizeof(buf)-length]), length)) != kSDReadStatusSuccess) { return fl_ret; } - *result = be64toh(*((uint64_t *) &(buf[0]))); + *result = be64toh(buf); } return kSDReadStatusSuccess; } -- cgit From 0960e169086e5f2d63fa24053e7c1c06e7abc912 Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 14 Aug 2015 08:40:49 +0300 Subject: functests: Disable some tests when running with address sanitizer Ref #1350 --- test/functional/shada/history_spec.lua | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/test/functional/shada/history_spec.lua b/test/functional/shada/history_spec.lua index 0c39b65ee1..9ec7f9c256 100644 --- a/test/functional/shada/history_spec.lua +++ b/test/functional/shada/history_spec.lua @@ -12,6 +12,16 @@ describe('ShaDa support code', function() before_each(reset) after_each(clear) + local clang_sanitizer = os.getenv('CLANG_SANITIZER') + local it_noasan + if clang_sanitizer and clang_sanitizer:match('ASAN') then + it_noasan = function(name, test) + pending(name, function() end) + end + else + it_noasan = it + end + it('is able to dump and read back command-line history', function() nvim_command('set shada=\'0') nvim_feed(':" Test\n') @@ -237,7 +247,7 @@ describe('ShaDa support code', function() eq('.«', nvim_eval('getline(".")')) end) - it('dumps&loads s/pattern correctly when &encoding /= UTF-8 when dumping', + it_noasan('dumps&loads s/pattern correctly when &encoding /= UTF-8 when dumping', function() set_additional_cmd('set encoding=latin1') reset() @@ -278,7 +288,7 @@ describe('ShaDa support code', function() eq('', nvim_eval('histget("/", -1)')) end) - it('dumps&loads /pattern correctly when &encoding /= UTF-8 when dumping', + it_noasan('dumps&loads /pattern correctly when &encoding /= UTF-8 when dumping', function() set_additional_cmd('set encoding=latin1') reset() -- cgit From 369081d1c4ccd81c23fa09286f14637ae8136b8e Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 15 Aug 2015 14:33:00 +0300 Subject: shada: Fix crash in hmll_insert This problem made test64 to crash. Description of the bug: when removing entry from history when removed entry is not the last one it puts one element to free_entries list, but ignores free entries starting from last_free_element. Possible solutions: 1. First working: simply populate free_entries list with entries which are still free, starting from last_free_element. 2. Better (wastes less CPU): after free_entries list size goes to zero (which is the initial value) continue using last_free_element. 3. Even better (less memory): note that element from the list is *only* removed before adding another one. So replace free_entries array with one item. Also renamed last_free_element to last_free_entry: in any case most of the lines which mention it were altered. --- src/nvim/shada.c | 34 ++++++++++----------- test/functional/shada/merging_spec.lua | 54 ++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 19 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index b98aa2f258..c069bd56ad 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -360,10 +360,9 @@ typedef struct { 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_entries; ///< Free array entries. - HMLListEntry *last_free_element; ///< Last free array element. + 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 free_entries_size; ///< Number of non-NULL entries in free_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 @@ -552,13 +551,12 @@ static inline void hmll_init(HMLList *const hmll, const size_t size) .entries = xcalloc(size, sizeof(hmll->entries[0])), .first = NULL, .last = NULL, - .free_entries = NULL, + .free_entry = NULL, .size = size, - .free_entries_size = 0, .num_entries = 0, .contained_entries = KHASH_EMPTY_TABLE(hmll_entries), }; - hmll->last_free_element = hmll->entries; + hmll->last_free_entry = hmll->entries; } /// Iterate over HMLList in forward direction @@ -579,15 +577,11 @@ static inline void hmll_remove(HMLList *const hmll, HMLListEntry *const hmll_entry) FUNC_ATTR_NONNULL_ALL { - if (hmll->free_entries == NULL) { - if (hmll_entry == hmll->last_free_element) { - hmll->last_free_element--; - } else { - hmll->free_entries = xcalloc(hmll->size, sizeof(hmll->free_entries[0])); - hmll->free_entries[hmll->free_entries_size++] = hmll_entry; - } + if (hmll_entry == hmll->last_free_entry - 1) { + hmll->last_free_entry--; } else { - hmll->free_entries[hmll->free_entries_size++] = hmll_entry; + 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); @@ -630,12 +624,15 @@ static inline void hmll_insert(HMLList *const hmll, hmll_remove(hmll, hmll->first); } HMLListEntry *target_entry; - if (hmll->free_entries == NULL) { - assert((size_t) (hmll->last_free_element - hmll->entries) + if (hmll->free_entry == NULL) { + assert((size_t) (hmll->last_free_entry - hmll->entries) == hmll->num_entries); - target_entry = hmll->last_free_element++; + target_entry = hmll->last_free_entry++; } else { - target_entry = hmll->free_entries[--hmll->free_entries_size]; + 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; @@ -680,7 +677,6 @@ static inline void hmll_dealloc(HMLList *const hmll) { kh_dealloc(hmll_entries, &hmll->contained_entries); xfree(hmll->entries); - xfree(hmll->free_entries); } /// Wrapper for reading from file descriptors diff --git a/test/functional/shada/merging_spec.lua b/test/functional/shada/merging_spec.lua index 3daa5af7c2..fd0111d30d 100644 --- a/test/functional/shada/merging_spec.lua +++ b/test/functional/shada/merging_spec.lua @@ -175,6 +175,60 @@ describe('ShaDa history merging code', function() end eq(#items, found) end) + + it('correctly merges history items with duplicate mid entry when writing', + function() + -- Regression test: ShaDa code used to crash here. + -- Conditions: + -- 1. Entry which is duplicate to non-last entry. + -- 2. At least one more non-duplicate entry. + wshada('\004\000\009\147\000\196\002ab\196\001a' + .. '\004\001\009\147\000\196\002ac\196\001a' + .. '\004\002\009\147\000\196\002ad\196\001a' + .. '\004\003\009\147\000\196\002ac\196\001a' + .. '\004\004\009\147\000\196\002af\196\001a' + .. '\004\005\009\147\000\196\002ae\196\001a' + .. '\004\006\009\147\000\196\002ag\196\001a' + .. '\004\007\009\147\000\196\002ah\196\001a' + .. '\004\008\009\147\000\196\002ai\196\001a' + ) + eq(0, exc_exec('wshada ' .. shada_fname)) + local items = {'ab', 'ad', 'ac', 'af', 'ae', 'ag', 'ah', 'ai'} + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 4 and v.value[1] == 0 then + found = found + 1 + eq(items[found], v.value[2]) + eq('a', v.value[3]) + end + end + eq(#items, found) + end) + + it('correctly merges history items with duplicate adj entry when writing', + function() + wshada('\004\000\009\147\000\196\002ab\196\001a' + .. '\004\001\009\147\000\196\002ac\196\001a' + .. '\004\002\009\147\000\196\002ad\196\001a' + .. '\004\003\009\147\000\196\002ad\196\001a' + .. '\004\004\009\147\000\196\002af\196\001a' + .. '\004\005\009\147\000\196\002ae\196\001a' + .. '\004\006\009\147\000\196\002ag\196\001a' + .. '\004\007\009\147\000\196\002ah\196\001a' + .. '\004\008\009\147\000\196\002ai\196\001a' + ) + eq(0, exc_exec('wshada ' .. shada_fname)) + local items = {'ab', 'ac', 'ad', 'af', 'ae', 'ag', 'ah', 'ai'} + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 4 and v.value[1] == 0 then + found = found + 1 + eq(items[found], v.value[2]) + eq('a', v.value[3]) + end + end + eq(#items, found) + end) end) describe('ShaDa search pattern support code', function() -- cgit From 8f7ddfb9a4c5ca0889bc78f617628d5c91cbe772 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 15 Aug 2015 14:50:04 +0300 Subject: documentation: Update vim differences list, reference critical errors --- runtime/doc/starting.txt | 9 +++++---- runtime/doc/vim_diff.txt | 18 ++++++++++++------ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index e96a014041..dfb80b7f98 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -1122,11 +1122,12 @@ Note: when NeoVim finds out that it failed to write part of the ShaDa file (e.g. because there is no space left to write the file) or when it appears that already present ShaDa file contains errors that indicate that this file is likely not a ShaDa file then ShaDa file with `.tmp.X` suffix is left on the -file system (where X is any latin small letter: from U+0061 to U+007A). You +file system (where X is any latin small letter: from U+0061 to U+007A). You may use such file to recover the data if you want, but in any case it needs to be cleaned up after you resolve the issue that prevented old ShaDa file from -being overwritten. If NeoVim fails to find unexisting `.tmp.X` file it will -not write ShaDa file at all. +being overwritten. If NeoVim fails to find unexisting `.tmp.X` file it will +not write ShaDa file at all. Errors which trigger this behaviour are listed +at |shada-keeping-tmpfile|. *:rsh* *:rshada* *E886* :rsh[ada][!] [file] Read from ShaDa file [file] (default: see above). @@ -1305,7 +1306,7 @@ Errors in ShaDa file may have two types: E575 used for all “logical” errors and E576 used for all “critical” errors. Critical errors trigger behaviour described in |shada-error-handling| when writing and skipping the rest of the file when reading and include: - + *shada-keeping-tmpfile* - Any of first three MessagePack objects being not an unsigned integer. - Third object requesting amount of bytes greater then bytes left in the ShaDa file. diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index d5ff27d8c5..3b3f257351 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -99,16 +99,22 @@ Additional differences: - |shada-c| has no effect. - |shada-s| now limits size of every item and not just registers. -- When reading ShaDa files history, jump list and change list items are merged - with those in currently running NeoVim instance according to the timestamp. +- When reading ShaDa files items are merged according to the timestamp. + |shada-merging| - 'viminfo' option got renamed to 'shada'. Old option is kept as an alias for compatibility reasons. -- |:wviminfo| was renamed to |:wshada|, |:rviminfo| to |:rshada|. Old commands - are still kept. -- When writing (|:wshada| without bang or at exit) it merges much more data. - Vim merges only marks. +- |:wviminfo| was renamed to |:wshada|, |:rviminfo| to |:rshada|. Old + commands are still kept. +- When writing (|:wshada| without bang or at exit) it merges much more data, + and does this according to the timestamp. Vim merges only marks. + |shada-merging| - ShaDa file format was designed with forward and backward compatibility in mind. |shada-compatibility| +- Some errors make ShaDa code keep temporary file in-place for user to decide + what to do with it. Vim deletes temporary file in these cases. + |shada-error-handling| +- Vim keeps no timestamps at all, neither in viminfo file nor in the instance + itself. ============================================================================== 4. New Features *nvim-features-new* -- cgit From 0d15b35d49fdc9872fb28b31c9ef521ef8ab044d Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 15 Aug 2015 16:16:29 +0300 Subject: shada,config: Use CMake to determine endianess and be64toh existence --- config/CMakeLists.txt | 30 ++++++++++++++++++++++++++++ config/config.h.in | 3 +++ src/nvim/shada.c | 55 +++++++-------------------------------------------- 3 files changed, 40 insertions(+), 48 deletions(-) diff --git a/config/CMakeLists.txt b/config/CMakeLists.txt index e9a872d9be..9334691e2a 100644 --- a/config/CMakeLists.txt +++ b/config/CMakeLists.txt @@ -2,6 +2,7 @@ include(CheckTypeSize) include(CheckSymbolExists) include(CheckFunctionExists) include(CheckIncludeFiles) +include(CheckCSourceRuns) check_type_size("int" SIZEOF_INT) check_type_size("long" SIZEOF_LONG) @@ -71,6 +72,35 @@ if(HAVE_LANGINFO_H) check_symbol_exists(CODESET "langinfo.h" HAVE_NL_LANGINFO_CODESET) endif() +set(SI "#include \n") +set(MS "int main(int argc,char**argv)\n{\n uint64_t i=0x0102030405060708ULL;") +set(ME "}") +check_c_source_runs(" + ${SI} + ${MS} + char *s = (char *) &i; + return ( + s[0] == 0x01 + && s[1] == 0x02 + && s[2] == 0x03 + && s[3] == 0x04 + && s[4] == 0x05 + && s[5] == 0x06 + && s[6] == 0x07 + && s[7] == 0x08) ? 0 : 1; + ${ME}" + ORDER_BIG_ENDIAN) +check_c_source_runs(" + #define _BSD_SOURCE 1 + #define _DEFAULT_SOURCE 1 + ${SI} + #include + ${MS} + uint64_t j = be64toh(i); + return (j == 0); // Must not be zero + ${ME}" + HAVE_BE64TOH) + # generate configuration header and update include directories configure_file ( "${PROJECT_SOURCE_DIR}/config/config.h.in" diff --git a/config/config.h.in b/config/config.h.in index c234501820..f8a795327f 100644 --- a/config/config.h.in +++ b/config/config.h.in @@ -61,4 +61,7 @@ #cmakedefine HAVE_JEMALLOC #endif +#cmakedefine HAVE_BE64TOH +#cmakedefine ORDER_BIG_ENDIAN + #endif // AUTO_CONFIG_H diff --git a/src/nvim/shada.c b/src/nvim/shada.c index c069bd56ad..6de3d8b20e 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1,3 +1,8 @@ +#ifdef HAVE_BE64TOH +# define _BSD_SOURCE 1 +# define _DEFAULT_SOURCE 1 +# include +#endif #include #include #include @@ -7,15 +12,6 @@ #include #include #include -#if defined (__GLIBC__) -# ifndef _BSD_SOURCE -# define _BSD_SOURCE 1 -# endif -# ifndef _DEFAULT_SOURCE -# define _DEFAULT_SOURCE 1 -# endif -# include -#endif #include @@ -88,43 +84,6 @@ KHASH_SET_INIT_STR(strset) #define regtilde(s, m) ((char *) regtilde((char_u *) s, m)) #define path_tail_with_sep(f) ((char *) path_tail_with_sep((char_u *)f)) -// From http://www.boost.org/doc/libs/1_43_0/boost/detail/endian.hpp + some -// additional checks done after examining `{compiler} -dM -E - < /dev/null` -// output. -#if defined (__GLIBC__) -# if (__BYTE_ORDER == __BIG_ENDIAN) -# define SHADA_BIG_ENDIAN -# endif -#elif defined(_BIG_ENDIAN) || defined(_LITTLE_ENDIAN) -# if defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN) -# define SHADA_BIG_ENDIAN -# endif -// clang-specific -#elif defined(__BIG_ENDIAN__) || defined(__LITTLE_ENDIAN__) -# if defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN) -# define SHADA_BIG_ENDIAN -# endif -// pcc-, gcc- and clang-specific -#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) -# if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ -# define SHADA_BIG_ENDIAN -# endif -#elif defined(__sparc) || defined(__sparc__) \ - || defined(_POWER) || defined(__powerpc__) \ - || defined(__ppc__) || defined(__hpux) || defined(__hppa) \ - || defined(_MIPSEB) || defined(_POWER) \ - || defined(__s390__) -# define SHADA_BIG_ENDIAN -#elif defined(__i386__) || defined(__alpha__) \ - || defined(__ia64) || defined(__ia64__) \ - || defined(_M_IX86) || defined(_M_IA64) \ - || defined(_M_ALPHA) || defined(__amd64) \ - || defined(__amd64__) || defined(_M_AMD64) \ - || defined(__x86_64) || defined(__x86_64__) \ - || defined(_M_X64) || defined(__bfin__) -// Define nothing -#endif - #define SEARCH_KEY_MAGIC "sm" #define SEARCH_KEY_SMARTCASE "sc" #define SEARCH_KEY_HAS_LINE_OFFSET "sl" @@ -3176,10 +3135,10 @@ static void shada_free_shada_entry(ShadaEntry *const entry) } } -#ifndef __GLIBC__ +#ifndef HAVE_BE64TOH static inline uint64_t be64toh(uint64_t big_endian_64_bits) { -#ifdef SHADA_BIG_ENDIAN +#ifdef ORDER_BIG_ENDIAN return big_endian_64_bits; #else uint8_t *buf = (uint8_t *) &big_endian_64_bits; -- cgit From 804e0740964bf84891d798d36d82a5c7ffa462f2 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 15 Aug 2015 17:20:07 +0300 Subject: eval,functests: Reference all additional_* items created by ShaDa --- src/nvim/eval.c | 138 ++++++++++++++++++++++++--- src/nvim/ops.c | 2 +- test/functional/shada/compatibility_spec.lua | 60 ++++++++++++ 3 files changed, 185 insertions(+), 15 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index ad66c4c924..84721feade 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5541,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; @@ -5559,45 +5560,104 @@ 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; + reg_iter = op_register_iter(reg_iter, &name, ®); + if (reg.y_array != NULL) { + ABORTING(set_ref_dict)(reg.additional_data, copyID); + } + } while (reg_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) { @@ -5626,6 +5686,7 @@ bool garbage_collect(void) verb_msg((char_u *)_( "Not enough memory to set references, garbage collection aborted!")); } +#undef ABORTING return did_free; } @@ -5685,6 +5746,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; @@ -5727,6 +5789,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; @@ -5767,6 +5830,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; @@ -5816,6 +5880,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. */ diff --git a/src/nvim/ops.c b/src/nvim/ops.c index b3e889c0ec..f7219aba31 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -5330,7 +5330,7 @@ static inline bool reg_empty(const yankreg_T *const reg) /// @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) + yankreg_T *const reg) FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT { const yankreg_T *iter_reg = (iter == NULL diff --git a/test/functional/shada/compatibility_spec.lua b/test/functional/shada/compatibility_spec.lua index 45d747658a..13d4c44657 100644 --- a/test/functional/shada/compatibility_spec.lua +++ b/test/functional/shada/compatibility_spec.lua @@ -45,6 +45,11 @@ describe('ShaDa forward compatibility support code', function() end end eq(true, found) + nvim_eval('garbagecollect(1)') + nvim_eval('garbagecollect(1)') + nvim_command('rshada! ' .. shada_fname) + nvim_eval('garbagecollect(1)') + nvim_eval('garbagecollect(1)') end) it('works with s/search pattern item with BOOL unknown (sX) key value', function() @@ -72,6 +77,11 @@ describe('ShaDa forward compatibility support code', function() end end eq(true, found) + nvim_eval('garbagecollect(1)') + nvim_eval('garbagecollect(1)') + nvim_command('rshada!' .. shada_fname) + nvim_eval('garbagecollect(1)') + nvim_eval('garbagecollect(1)') end) it('works with replacement item with BOOL additional value in list', function() @@ -100,6 +110,11 @@ describe('ShaDa forward compatibility support code', function() end end eq(true, found) + nvim_eval('garbagecollect(1)') + nvim_eval('garbagecollect(1)') + nvim_command('rshada!' .. shada_fname) + nvim_eval('garbagecollect(1)') + nvim_eval('garbagecollect(1)') end) for _, v in ipairs({{name='global mark', mpack='\007\001\018\131\162mX\195\161f\196\006/a/b/c\161nA'}, @@ -138,6 +153,11 @@ describe('ShaDa forward compatibility support code', function() end end eq(false, found) + nvim_eval('garbagecollect(1)') + nvim_eval('garbagecollect(1)') + nvim_command('rshada!' .. shada_fname) + nvim_eval('garbagecollect(1)') + nvim_eval('garbagecollect(1)') end) if v.name == 'global mark' or v.name == 'local mark' then @@ -175,6 +195,11 @@ describe('ShaDa forward compatibility support code', function() end end eq(0, found) + nvim_eval('garbagecollect(1)') + nvim_eval('garbagecollect(1)') + nvim_command('rshada!' .. shada_fname) + nvim_eval('garbagecollect(1)') + nvim_eval('garbagecollect(1)') end) end end @@ -202,6 +227,11 @@ describe('ShaDa forward compatibility support code', function() end end eq(false, found) + nvim_eval('garbagecollect(1)') + nvim_eval('garbagecollect(1)') + nvim_command('rshada!' .. shada_fname) + nvim_eval('garbagecollect(1)') + nvim_eval('garbagecollect(1)') end) it('works with register item with name', function() @@ -233,6 +263,11 @@ describe('ShaDa forward compatibility support code', function() end end eq(0, found) + nvim_eval('garbagecollect(1)') + nvim_eval('garbagecollect(1)') + nvim_command('rshada!' .. shada_fname) + nvim_eval('garbagecollect(1)') + nvim_eval('garbagecollect(1)') end) it('works with register item with type 10', function() @@ -265,6 +300,11 @@ describe('ShaDa forward compatibility support code', function() end end eq(0, found) + nvim_eval('garbagecollect(1)') + nvim_eval('garbagecollect(1)') + nvim_command('rshada!' .. shada_fname) + nvim_eval('garbagecollect(1)') + nvim_eval('garbagecollect(1)') end) it('works with buffer list item with BOOL unknown (bX) key', function() @@ -295,6 +335,11 @@ describe('ShaDa forward compatibility support code', function() end eq(false, found) nvim_command('bwipeout!') + nvim_eval('garbagecollect(1)') + nvim_eval('garbagecollect(1)') + nvim_command('rshada!' .. shada_fname) + nvim_eval('garbagecollect(1)') + nvim_eval('garbagecollect(1)') end) it('works with history item with BOOL additional value in list', function() @@ -324,6 +369,11 @@ describe('ShaDa forward compatibility support code', function() end end eq(true, found) + nvim_eval('garbagecollect(1)') + nvim_eval('garbagecollect(1)') + nvim_command('rshada!' .. shada_fname) + nvim_eval('garbagecollect(1)') + nvim_eval('garbagecollect(1)') end) it('works with history item with type 10', function() @@ -356,6 +406,11 @@ describe('ShaDa forward compatibility support code', function() end end eq(0, found) + nvim_eval('garbagecollect(1)') + nvim_eval('garbagecollect(1)') + nvim_command('rshada!' .. shada_fname) + nvim_eval('garbagecollect(1)') + nvim_eval('garbagecollect(1)') end) it('works with item with 100 type', function() @@ -388,5 +443,10 @@ describe('ShaDa forward compatibility support code', function() end end eq(0, found) + nvim_eval('garbagecollect(1)') + nvim_eval('garbagecollect(1)') + nvim_command('rshada!' .. shada_fname) + nvim_eval('garbagecollect(1)') + nvim_eval('garbagecollect(1)') end) end) -- cgit From b249529676306760b5cd99a4707e6bf246f8b99b Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 15 Aug 2015 17:25:10 +0300 Subject: functests: Make one recover_spec test also use gdb or valgrind --- test/functional/ex_cmds/recover_spec.lua | 3 ++- test/functional/helpers.lua | 20 ++++++++++++++++++-- test/functional/shada/helpers.lua | 14 +++----------- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/test/functional/ex_cmds/recover_spec.lua b/test/functional/ex_cmds/recover_spec.lua index b92739e40e..a24a60af81 100644 --- a/test/functional/ex_cmds/recover_spec.lua +++ b/test/functional/ex_cmds/recover_spec.lua @@ -47,7 +47,8 @@ describe(':preserve', function() --TODO(justinmk): this is an ugly hack to force `helpers` to support --multiple sessions. - local nvim2 = helpers.spawn({helpers.nvim_prog, '-u', 'NONE', '--embed'}) + local nvim2 = helpers.spawn({helpers.nvim_prog, '-u', 'NONE', '--embed'}, + true) helpers.set_session(nvim2) source(init) diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 23581ba4d2..79f1feb7b5 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -178,12 +178,27 @@ local function rawfeed(...) end end -local function spawn(argv) +local function merge_args(...) + local i = 1 + local argv = {} + for anum = 1,select('#', ...) do + local args = select(anum, ...) + if args then + for _, arg in ipairs(args) do + argv[i] = arg + i = i + 1 + end + end + end + return argv +end + +local function spawn(argv, merge) local loop = Loop.new() local msgpack_stream = MsgpackStream.new(loop) local async_session = AsyncSession.new(msgpack_stream) local session = Session.new(async_session) - loop:spawn(argv) + loop:spawn(merge and merge_args(prepend_argv, argv) or argv) return session end @@ -382,4 +397,5 @@ return { rmdir = rmdir, mkdir = lfs.mkdir, exc_exec = exc_exec, + merge_args = merge_args, } diff --git a/test/functional/shada/helpers.lua b/test/functional/shada/helpers.lua index d37e84f156..b3153ca7f1 100644 --- a/test/functional/shada/helpers.lua +++ b/test/functional/shada/helpers.lua @@ -2,7 +2,7 @@ local helpers = require('test.functional.helpers') local spawn, set_session, nvim, nvim_prog, nvim_command, nvim_eval = helpers.spawn, helpers.set_session, helpers.nvim, helpers.nvim_prog, helpers.command, helpers.eval -local write_file = helpers.write_file +local write_file, merge_args = helpers.write_file, helpers.merge_args local msgpack = require('MessagePack') @@ -16,18 +16,10 @@ local function nvim_argv() '--cmd', additional_cmd, '--embed'} if helpers.prepend_argv then - ret = {} - for i, v in ipairs(helpers.prepend_argv) do - ret[i] = v - end - local shift = #ret - for i, v in ipairs(nvim_argv) do - ret[i + shift] = v - end + return merge_args(helpers.prepend_argv, nvim_argv) else - ret = nvim_argv + return nvim_argv end - return ret end local session = nil -- cgit From d1830e143342a1714fa66af727a2cc5c4084b6fe Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 15 Aug 2015 17:41:28 +0300 Subject: config: Check order and endianess even when cross-compiling --- config/CMakeLists.txt | 56 ++++++++++++++++++++++++++++++++++----------------- src/nvim/shada.c | 3 +++ 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/config/CMakeLists.txt b/config/CMakeLists.txt index 9334691e2a..b780291264 100644 --- a/config/CMakeLists.txt +++ b/config/CMakeLists.txt @@ -3,6 +3,7 @@ include(CheckSymbolExists) include(CheckFunctionExists) include(CheckIncludeFiles) include(CheckCSourceRuns) +include(CheckCSourceCompiles) check_type_size("int" SIZEOF_INT) check_type_size("long" SIZEOF_LONG) @@ -75,31 +76,50 @@ endif() set(SI "#include \n") set(MS "int main(int argc,char**argv)\n{\n uint64_t i=0x0102030405060708ULL;") set(ME "}") -check_c_source_runs(" - ${SI} - ${MS} - char *s = (char *) &i; - return ( - s[0] == 0x01 - && s[1] == 0x02 - && s[2] == 0x03 - && s[3] == 0x04 - && s[4] == 0x05 - && s[5] == 0x06 - && s[6] == 0x07 - && s[7] == 0x08) ? 0 : 1; - ${ME}" - ORDER_BIG_ENDIAN) -check_c_source_runs(" +check_c_source_compiles(" #define _BSD_SOURCE 1 #define _DEFAULT_SOURCE 1 ${SI} #include + #ifndef be64toh + # error No be64toh macros + #endif ${MS} uint64_t j = be64toh(i); - return (j == 0); // Must not be zero + return (j == 0); // j must not be zero ${ME}" - HAVE_BE64TOH) + HAVE_BE64TOH_MACROS) +if(NOT "${HAVE_BE64TOH_MACROS}") + check_function_exists(be64toh HAVE_BE64TOH_FUNC) +endif() +if("${HAVE_BE64TOH_MACROS}" OR "${HAVE_BE64TOH_FUNC}") + set(HAVE_BE64TOH 1) +endif() +if (NOT "${HAVE_BE64TOH}") + if (NOT "${CMAKE_CROSSCOMPILING}") + # It is safe to make ORDER_BIG_ENDIAN not defined if + # - HAVE_BE64TOH is true. In this case be64toh will be used unconditionally in + # any case and ORDER_BIG_ENDIAN will not be examined. + # - CMAKE_CROSSCOMPILING *and* HAVE_BE64TOH are both false. In this case + # be64toh function which uses cycle and arithmetic operations is used which + # will work regardless of endianess. Function is sub-optimal though. + check_c_source_runs(" + ${SI} + ${MS} + char *s = (char *) &i; + return ( + s[0] == 0x01 + && s[1] == 0x02 + && s[2] == 0x03 + && s[3] == 0x04 + && s[4] == 0x05 + && s[5] == 0x06 + && s[6] == 0x07 + && s[7] == 0x08) ? 0 : 1; + ${ME}" + ORDER_BIG_ENDIAN) + endif() +endif() # generate configuration header and update include directories configure_file ( diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 6de3d8b20e..1d4b486823 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -3141,6 +3141,9 @@ 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--) { -- cgit From 0caf17d0e8dda4b4abd4ec4c96d4c26f6a25ab86 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 15 Aug 2015 20:06:03 +0300 Subject: search: When freeing search patterns also clear them --- src/nvim/search.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/nvim/search.c b/src/nvim/search.c index 285f18e58f..3fbddc7fc8 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -300,6 +300,8 @@ void free_search_patterns(void) free_spat(&spats[0]); free_spat(&spats[1]); + memset(spats, 0, sizeof(spats)); + if (mr_pattern_alloced) { xfree(mr_pattern); mr_pattern_alloced = FALSE; -- cgit From e2c3ea44451fe7b6c67bee78387073fa033e4c0e Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 15 Aug 2015 23:49:01 +0300 Subject: os/time: Make Timestamp be 64-bit unsigned integer --- src/nvim/os/time.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nvim/os/time.h b/src/nvim/os/time.h index 3a6b665b51..ad4886446a 100644 --- a/src/nvim/os/time.h +++ b/src/nvim/os/time.h @@ -5,7 +5,7 @@ #include #include -typedef time_t Timestamp; +typedef uint64_t Timestamp; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/time.h.generated.h" -- cgit From be45e750267a7cb52b625b2e1e71796be18d8061 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 16 Aug 2015 01:16:24 +0300 Subject: shada: Refuse to write ShaDa file when ShaDa was disabled --- src/nvim/shada.c | 4 ++++ test/functional/shada/shada_spec.lua | 21 ++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 1d4b486823..b5e60d0c08 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -2895,6 +2895,10 @@ shada_write_exit: /// @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) { diff --git a/test/functional/shada/shada_spec.lua b/test/functional/shada/shada_spec.lua index 19b8a47244..e256173611 100644 --- a/test/functional/shada/shada_spec.lua +++ b/test/functional/shada/shada_spec.lua @@ -3,7 +3,8 @@ local helpers = require('test.functional.helpers') local nvim, nvim_window, nvim_curwin, nvim_command, nvim_feed, nvim_eval, eq = helpers.nvim, helpers.window, helpers.curwin, helpers.command, helpers.feed, helpers.eval, helpers.eq -local write_file = helpers.write_file +local write_file, spawn, set_session, nvim_prog = + helpers.write_file, helpers.spawn, helpers.set_session, helpers.nvim_prog local lfs = require('lfs') local msgpack = require('MessagePack') @@ -127,4 +128,22 @@ describe('ShaDa support code', function() end eq(#mpack, found) end) + + it('does not write NONE file', function() + local session = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed', + '--cmd', 'qall'}, true) + session:exit(0) + eq(nil, lfs.attributes('NONE')) + eq(nil, lfs.attributes('NONE.tmp.a')) + end) + + it('does not read NONE file', function() + write_file('NONE', '\005\001\015\131\161na\162rX\194\162rc\145\196\001-') + local session = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed'}, + true) + set_session(session) + eq('', nvim_eval('@a')) + session:exit(0) + os.remove('NONE') + end) end) -- cgit From 22906265a2647aadcbc3ab9ab8a590bffd95002c Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 16 Aug 2015 18:54:41 +0300 Subject: mark: Clear marks in some cases, but do not do useless job in free_\* --- .gitignore | 2 ++ src/nvim/buffer.c | 8 +++++--- src/nvim/mark.c | 12 ++++++++++-- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index d483afdd73..ee4a579f91 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ *.o *.so +tags + /src/nvim/po/vim.pot /src/nvim/po/*.ck diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index a25a51c6ce..d0fad0e002 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -557,9 +557,9 @@ static void free_buffer(buf_T *buf) unref_var_dict(buf->b_vars); aubuflocal_remove(buf); dict_unref(buf->additional_data); - free_fmark(buf->b_last_cursor); - free_fmark(buf->b_last_insert); - free_fmark(buf->b_last_change); + 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]); } @@ -569,6 +569,8 @@ static void free_buffer(buf_T *buf) 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 { diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 7c76bd2162..a134905d98 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -73,17 +73,23 @@ int setmark(int c) void free_fmark(fmark_T fm) { dict_unref(fm.additional_data); - fm.additional_data = NULL; } /// Free xfmark_T item void free_xfmark(xfmark_T fm) { xfree(fm.fname); - fm.fname = NULL; 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". @@ -1409,6 +1415,7 @@ void free_jumplist(win_T *wp) for (i = 0; i < wp->w_jumplistlen; ++i) { free_xfmark(wp->w_jumplist[i]); } + wp->w_jumplistlen = 0; } void set_last_cursor(win_T *win) @@ -1428,5 +1435,6 @@ void free_all_marks(void) free_xfmark(namedfm[i]); } } + memset(&namedfm[0], 0, sizeof(namedfm)); } #endif -- cgit From 2ba138b2f9c060c98af952e6956e48ad1521b0c9 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 16 Aug 2015 20:09:51 +0300 Subject: *: Fix problems with clang+asan Release build --- src/nvim/eval.c | 19 ++++++++++++++++--- src/nvim/mark.c | 40 ++++++---------------------------------- src/nvim/ops.c | 8 ++++---- src/nvim/shada.c | 18 +++++++++--------- 4 files changed, 35 insertions(+), 50 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 84721feade..338ee9dfed 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5604,14 +5604,27 @@ bool garbage_collect(void) const void *reg_iter = NULL; do { yankreg_T reg; - char name; + char name = NUL; reg_iter = op_register_iter(reg_iter, &name, ®); - if (reg.y_array != NULL) { + 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) { ABORTING(set_ref_in_item)(&tp->tp_winvar.di_tv, copyID, NULL, NULL); @@ -20882,7 +20895,7 @@ static var_flavour_T var_flavour(char_u *varname) /// or NULL to indicate that iteration is over. const void *var_shada_iter(const void *const iter, const char **const name, typval_T *rettv) - FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(2, 3) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(2, 3) { const hashitem_T *hi; const hashitem_T *hifirst = globvarht.ht_array; diff --git a/src/nvim/mark.c b/src/nvim/mark.c index a134905d98..db0f18c3a5 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -1173,7 +1173,7 @@ void copy_jumplist(win_T *from, win_T *to) /// NULL if iteration is over. const void *mark_jumplist_iter(const void *const iter, const win_T *const win, xfmark_T *const fm) - FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT { if (iter == NULL && win->w_jumplistlen == 0) { *fm = (xfmark_T) {{{0, 0, 0}, 0, 0, NULL}, NULL}; @@ -1204,8 +1204,9 @@ const void *mark_jumplist_iter(const void *const iter, const win_T *const win, /// NULL if iteration is over. const void *mark_global_iter(const void *const iter, char *const name, xfmark_T *const fm) - FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT { + *name = NUL; const xfmark_T *iter_mark = (iter == NULL ? &(namedfm[0]) : (const xfmark_T *const) iter); @@ -1215,7 +1216,6 @@ const void *mark_global_iter(const void *const iter, char *const name, } if ((size_t) (iter_mark - &(namedfm[0])) == ARRAY_SIZE(namedfm) || !iter_mark->fmark.mark.lnum) { - *fm = (xfmark_T) { .fmark = { .mark = { .lnum = 0 } } }; return NULL; } size_t iter_off = (size_t) (iter_mark - &(namedfm[0])); @@ -1244,7 +1244,7 @@ const void *mark_global_iter(const void *const iter, char *const name, /// @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_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { switch (*mark_name) { case NUL: { @@ -1287,8 +1287,9 @@ static inline const fmark_T *next_buffer_mark(const buf_T *const buf, /// NULL if iteration is over. const void *mark_buffer_iter(const void *const iter, const buf_T *const buf, char *const name, fmark_T *const fm) - FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ARG(2, 3, 4) FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_NONNULL_ARG(2, 3, 4) FUNC_ATTR_WARN_UNUSED_RESULT { + *name = NUL; char mark_name = (char) (iter == NULL ? NUL : (iter == &(buf->b_last_cursor) @@ -1304,7 +1305,6 @@ const void *mark_buffer_iter(const void *const iter, const buf_T *const buf, iter_mark = next_buffer_mark(buf, &mark_name); } if (iter_mark == NULL) { - *fm = (fmark_T) {.mark = {.lnum = 0}}; return NULL; } size_t iter_off = (size_t) (iter_mark - &(buf->b_namedm[0])); @@ -1317,34 +1317,6 @@ const void *mark_buffer_iter(const void *const iter, const buf_T *const buf, return (const void *) iter_mark; } -/// Get a number of valid marks -size_t mark_global_amount(void) - FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT -{ - size_t ret = 0; - for (size_t i = 0; i < NGLOBALMARKS; i++) { - if (namedfm[i].fmark.mark.lnum != 0) { - ret++; - } - } - return ret; -} - -/// Get a number of valid marks -size_t mark_buffer_amount(const buf_T *const buf) - FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL -{ - size_t ret = (size_t) ((buf->b_last_cursor.mark.lnum != 0) - + (buf->b_last_insert.mark.lnum != 0) - + (buf->b_last_change.mark.lnum != 0)); - for (size_t i = 0; i < NMARKS; i++) { - if (buf->b_namedm[i].mark.lnum != 0) { - ret++; - } - } - return ret; -} - /// Set global mark /// /// @param[in] name Mark name. diff --git a/src/nvim/ops.c b/src/nvim/ops.c index f7219aba31..d59630697d 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -5312,7 +5312,7 @@ void end_global_changes(void) /// Check whether register is empty static inline bool reg_empty(const yankreg_T *const reg) - FUNC_ATTR_CONST + FUNC_ATTR_PURE { return (reg->y_array == NULL || reg->y_size == 0 @@ -5331,8 +5331,9 @@ static inline bool reg_empty(const yankreg_T *const reg) /// NULL if iteration is over. const void *op_register_iter(const void *const iter, char *const name, yankreg_T *const reg) - FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT + 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); @@ -5340,7 +5341,6 @@ const void *op_register_iter(const void *const iter, char *const name, iter_reg++; } if (iter_reg - &(y_regs[0]) == NUM_SAVED_REGISTERS || reg_empty(iter_reg)) { - *reg = (yankreg_T) { .y_array = NULL }; return NULL; } size_t iter_off = iter_reg - &(y_regs[0]); @@ -5356,7 +5356,7 @@ const void *op_register_iter(const void *const iter, char *const name, /// Get a number of non-empty registers size_t op_register_amount(void) - FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_WARN_UNUSED_RESULT { size_t ret = 0; for (size_t i = 0; i < NUM_SAVED_REGISTERS; i++) { diff --git a/src/nvim/shada.c b/src/nvim/shada.c index b5e60d0c08..314fc7e9db 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1037,8 +1037,8 @@ static void hms_insert(HistoryMergerState *const hms_p, const ShadaEntry entry, } } else { while (hms_p->iter != NULL - && hms_p->last_hist_entry.type != kSDItemMissing - && hms_p->last_hist_entry.timestamp < entry.timestamp) { + && 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); hms_p->iter = shada_hist_iter(hms_p->iter, hms_p->history_type, hms_p->reading, @@ -1111,7 +1111,7 @@ static inline void hms_insert_whole_neovim_history( hms_insert(hms_p, hms_p->last_hist_entry, false, hms_p->reading); } while (hms_p->iter != NULL - && hms_p->last_hist_entry.type != kSDItemMissing) { + && hms_p->last_hist_entry.type != kSDItemMissing) { hms_p->iter = shada_hist_iter(hms_p->iter, hms_p->history_type, hms_p->reading, &(hms_p->last_hist_entry)); @@ -2631,10 +2631,10 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, if (dump_global_marks) { const void *global_mark_iter = NULL; do { - char name; + char name = NUL; xfmark_T fm; global_mark_iter = mark_global_iter(global_mark_iter, &name, &fm); - if (fm.fmark.mark.lnum == 0) { + if (name == NUL) { break; } const char *fname; @@ -2675,9 +2675,9 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, const void *reg_iter = NULL; do { yankreg_T reg; - char name; + char name = NUL; reg_iter = op_register_iter(reg_iter, &name, ®); - if (reg.y_array == NULL) { + if (name == NUL) { break; } if (limit_reg_lines && reg.y_size > max_reg_lines) { @@ -2721,9 +2721,9 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, } do { fmark_T fm; - char name; + char name = NUL; local_marks_iter = mark_buffer_iter(local_marks_iter, buf, &name, &fm); - if (fm.mark.lnum == 0) { + if (name == NUL) { break; } filemarks->marks[mark_local_index(name)] = (PossiblyFreedShadaEntry) { -- cgit From f59ef120e1d04b87ea36b9453a77a1a6b8532a6f Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 16 Aug 2015 20:44:34 +0300 Subject: eval: Remove incorrect NONNULL_RET attributes --- src/nvim/eval.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 338ee9dfed..8f4db663af 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -17491,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; } -- cgit From 5d47fe124a6a6038975006a306b451d65d267e80 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 17 Aug 2015 01:09:00 +0300 Subject: shada.h: Remove newline after INCLUDE_GENERATED_DECLARATIONS section --- src/nvim/shada.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/nvim/shada.h b/src/nvim/shada.h index 2b0a6ff6be..49986ac1c1 100644 --- a/src/nvim/shada.h +++ b/src/nvim/shada.h @@ -4,5 +4,4 @@ #ifdef INCLUDE_GENERATED_DECLARATIONS # include "shada.h.generated.h" #endif - #endif // NVIM_SHADA_H -- cgit From c57c569000a48ef3f535a325f090607985aa0811 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 17 Aug 2015 20:44:33 +0300 Subject: shada: Also expand file name obtained from &shada option --- src/nvim/shada.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 314fc7e9db..654b6dde41 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1597,23 +1597,25 @@ static char *shada_filename(const char *file) if (file == NULL || *file == NUL) { if (used_shada_file != NULL) { file = used_shada_file; - } else if ((file = find_shada_parameter('n')) == NULL || *file == NUL) { + } 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; + // 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 { - file = SHADA_FILE; - } - } else { #endif - file = SHADA_FILE; + 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. -- cgit From aa4d0bb7289137e625f5f210d73d8b691b78d23c Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 17 Aug 2015 20:53:50 +0300 Subject: eval: Reorder checks in var_shada_iter --- src/nvim/eval.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 8f4db663af..c0a3a385fa 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -20903,9 +20903,9 @@ const void *var_shada_iter(const void *const iter, const char **const name, *name = NULL; if (iter == NULL) { hi = globvarht.ht_array; - while ((HASHITEM_EMPTY(hi) - || var_flavour(HI2DI(hi)->di_key) != VAR_FLAVOUR_SHADA) - && (size_t) (hi - hifirst) < hinum) { + while ((size_t) (hi - hifirst) < hinum + && (HASHITEM_EMPTY(hi) + || var_flavour(HI2DI(hi)->di_key) != VAR_FLAVOUR_SHADA)) { hi++; } if (HASHITEM_EMPTY(hi) -- cgit From b87c65d7b96ef0290764362fb7b1102b978653f9 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 17 Aug 2015 21:25:17 +0300 Subject: eval: Use proper iteration end condition --- src/nvim/eval.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index c0a3a385fa..25ead0c201 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -20908,9 +20908,8 @@ const void *var_shada_iter(const void *const iter, const char **const name, || var_flavour(HI2DI(hi)->di_key) != VAR_FLAVOUR_SHADA)) { hi++; } - if (HASHITEM_EMPTY(hi) - || var_flavour(HI2DI(hi)->di_key) != VAR_FLAVOUR_SHADA) { - *rettv = (typval_T) {.v_type = VAR_UNKNOWN}; + if ((size_t) (hi - hifirst) == hinum) { + *rettv = (typval_T) { .v_type = VAR_UNKNOWN }; return NULL; } } else { -- cgit From 127e63fc17c093298bbb3ae3640d991857a2bd61 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 17 Aug 2015 21:29:13 +0300 Subject: shada: Use same iteration end conditions as with other iterators --- src/nvim/eval.c | 1 - src/nvim/shada.c | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 25ead0c201..d2d195155d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -20909,7 +20909,6 @@ const void *var_shada_iter(const void *const iter, const char **const name, hi++; } if ((size_t) (hi - hifirst) == hinum) { - *rettv = (typval_T) { .v_type = VAR_UNKNOWN }; return NULL; } } else { diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 654b6dde41..76ae82e309 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -2483,9 +2483,9 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, const Timestamp cur_timestamp = os_time(); do { typval_T vartv; - const char *name; + const char *name = NULL; var_iter = var_shada_iter(var_iter, &name, &vartv); - if (var_iter == NULL && vartv.v_type == VAR_UNKNOWN) { + if (name == NULL) { break; } typval_T tgttv; -- cgit From 0fe11fe70a1927356bf3c2edc01741c1cd65e614 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 18 Aug 2015 19:32:31 +0300 Subject: shada: Add generator key to the header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For use in viminfo→shada converters, plugins that allow editing ShaDa files or any other software which generates ShaDa files for whatever purpose. --- runtime/doc/starting.txt | 13 +++++++++---- src/nvim/shada.c | 10 +++++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index dfb80b7f98..2078e9761b 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -1182,14 +1182,19 @@ exactly four MessagePack objects: entry type: Entry type (name) Entry data ~ - 1 (Header) Map containing data that describes the NeoVim instance - that written this ShaDa file. It is ignored when - reading ShaDa files. Contains the following data: + 1 (Header) Map containing data that describes the generator + instance that wrote this ShaDa file. It is ignored + when reading ShaDa files. Contains the following data: Key Data ~ - version Binary, NeoVim version. + generator Binary, software used to generate ShaDa + file. Is equal to "nvim" when ShaDa file was + written by NeoVim. + version Binary, generator version. encoding Binary, effective 'encoding' value. max_kbyte Integer, effective |shada-s| limit value. pid Integer, instance process ID. + * It is allowed to have any number of + additional keys with any data. 2 (SearchPattern) Map containing data describing last used search or substitute pattern. Normally ShaDa file contains two such entries: one with "ss" key set to true (describes diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 76ae82e309..322f485f48 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -2418,9 +2418,11 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, .timestamp = os_time(), .data = { .header = { - .size = 4, - .capacity = 4, + .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"), @@ -2605,9 +2607,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, continue; } const char *const fname = (char *) (fm.fmark.fnum == 0 - ? (fm.fname == NULL - ? NULL - : fm.fname) + ? (fm.fname == NULL ? NULL : fm.fname) : buf->b_ffname); if (fname == NULL) { continue; -- cgit From 48ba2f0109ad2b5bc51b08c55848d526b37fd8d0 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 18 Aug 2015 21:20:48 +0300 Subject: documentation/functests: Replace NeoVim with Neovim --- runtime/doc/starting.txt | 30 +++++++++++++++--------------- src/nvim/shada.c | 20 ++++++++++---------- test/functional/shada/merging_spec.lua | 8 ++++---- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index 2078e9761b..cfd3d2c9d2 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -991,16 +991,16 @@ MERGING *shada-merging* {Nvim} When writing ShaDa files with |:wshada| without bang or at regular exit information in the existing ShaDa file is merged with information from current -NeoVim instance. For this purpose ShaDa files store timestamps associated +Neovim instance. For this purpose ShaDa files store timestamps associated with ShaDa entries. Specifically the following is being done: 1. History lines are merged, ordered by timestamp. Maximum amount of items in ShaDa file is defined by 'shada' option (|shada-/|, |shada-:|, |shada-@|, etc: one suboption for each character that represents history name (|:history|)). -2. Local marks and changes for files that were not opened by NeoVim are copied - to new ShaDa file. Marks for files that were opened by NeoVim are merged, - changes to files opened by NeoVim are ignored. |shada-'| +2. Local marks and changes for files that were not opened by Neovim are copied + to new ShaDa file. Marks for files that were opened by Neovim are merged, + changes to files opened by Neovim are ignored. |shada-'| 3. Jump list is merged: jumps are ordered by timestamp, identical jumps (identical position AND timestamp) are squashed. 4. Search patterns and substitute strings are not merged: search pattern or @@ -1008,14 +1008,14 @@ with ShaDa entries. Specifically the following is being done: to ShaDa file. 5. For each register entity with greatest timestamp is the only saved. |shada-<| -6. All saved variables are saved from current NeoVim instance. Additionally +6. All saved variables are saved from current Neovim instance. Additionally existing variable values are copied, meaning that the only way to remove variable from a ShaDa file is either removing it by hand or disabling writing variables completely. |shada-!| 7. For each global mark entity with greatest timestamp is the only saved. 8. Buffer list and header are the only entries which are not merged in any fashion: the only header and buffer list present are the ones from the - NeoVim instance which was last writing the file. |shada-%| + Neovim instance which was last writing the file. |shada-%| COMPATIBILITY *shada-compatibility* {Nvim} @@ -1040,13 +1040,13 @@ ShaDa files are forward and backward compatible. This means that history types. |history| 6. Unknown keys found in register, local mark, global mark, change, jump and search pattern entries are saved internally and dumped when writing. - Entries created during NeoVim session never have such additions. + Entries created during Neovim session never have such additions. 7. Additional elements found in replacement string and history entries are - saved internally and dumped. Entries created during NeoVim session never + saved internally and dumped. Entries created during Neovim session never have such additions. 8. Additional elements found in variable entries are simply ignored when reading. When writing new variables they will be preserved during merging, - but that's all. Variable values dumped from current NeoVim session never + but that's all. Variable values dumped from current Neovim session never have additional elements, even if variables themselves were obtained by reading ShaDa files. @@ -1074,7 +1074,7 @@ The text in the ShaDa file is UTF-8-encoded. Normally you will always work with the same 'encoding' value, and this works just fine. However, if you read the ShaDa file with value for 'encoding' different from utf-8 and 'encoding' used when writing ShaDa file, some of the text (non-ASCII -characters) may be invalid as NeoVim always attempts to convert the text in +characters) may be invalid as Neovim always attempts to convert the text in the ShaDa file from the UTF-8 to the current 'encoding' value. Filenames are never converted, affected elements are: @@ -1099,7 +1099,7 @@ start with an existing one to get the format right. You need to understand MessagePack (or, more likely, find software that is able to use it) format to do this. This can be useful in order to create a second file, say "~/.my.shada" which could contain certain settings that you always want when -you first start NeoVim. For example, you can preload registers with +you first start Neovim. For example, you can preload registers with particular data, or put certain commands in the command line history. A line in your .nvimrc file like > :rshada! ~/.my.shada @@ -1114,18 +1114,18 @@ that file. This was done to avoid accidentally destroying a file when the file name of the ShaDa file is wrong. This could happen when accidentally typing "nvim -i file" when you wanted "nvim -R file" (yes, somebody accidentally did that!). If you want to overwrite a ShaDa file with an error -in it, you will either have to fix the error, delete the file (while NeoVim is +in it, you will either have to fix the error, delete the file (while Neovim is running, so most of the information will be restored) or write it explicitly with |:wshada| and a bang. *E136* *E138* *shada-error-handling* -Note: when NeoVim finds out that it failed to write part of the ShaDa file +Note: when Neovim finds out that it failed to write part of the ShaDa file (e.g. because there is no space left to write the file) or when it appears that already present ShaDa file contains errors that indicate that this file is likely not a ShaDa file then ShaDa file with `.tmp.X` suffix is left on the file system (where X is any latin small letter: from U+0061 to U+007A). You may use such file to recover the data if you want, but in any case it needs to be cleaned up after you resolve the issue that prevented old ShaDa file from -being overwritten. If NeoVim fails to find unexisting `.tmp.X` file it will +being overwritten. If Neovim fails to find unexisting `.tmp.X` file it will not write ShaDa file at all. Errors which trigger this behaviour are listed at |shada-keeping-tmpfile|. @@ -1188,7 +1188,7 @@ exactly four MessagePack objects: Key Data ~ generator Binary, software used to generate ShaDa file. Is equal to "nvim" when ShaDa file was - written by NeoVim. + written by Neovim. version Binary, generator version. encoding Binary, effective 'encoding' value. max_kbyte Integer, effective |shada-s| limit value. diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 322f485f48..3f8dfe827d 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1016,14 +1016,14 @@ static const void *shada_hist_iter(const void *const iter, /// 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 +/// 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 +/// @param[in] do_iter Determines whether Neovim own history should /// be used. Must be true only if inserting -/// entry from current NeoVim history. +/// 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) @@ -1054,7 +1054,7 @@ static void hms_insert(HistoryMergerState *const hms_p, const ShadaEntry entry, 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. + // Prefer entry from the current Neovim instance. if (existing_entry->can_free_entry) { shada_free_shada_entry(&existing_entry->data); } @@ -1083,7 +1083,7 @@ static void hms_insert(HistoryMergerState *const hms_p, const ShadaEntry entry, /// @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. +/// in Neovim. static inline void hms_init(HistoryMergerState *const hms_p, const uint8_t history_type, const size_t num_elements, @@ -1099,7 +1099,7 @@ static inline void hms_init(HistoryMergerState *const hms_p, hms_p->history_type = history_type; } -/// Merge in all remaining NeoVim own history entries +/// Merge in all remaining Neovim own history entries /// /// @param[in,out] hms_p Merger structure into which history should be /// inserted. @@ -1119,7 +1119,7 @@ static inline void hms_insert_whole_neovim_history( } } -/// Convert merger structure to NeoVim internal structure for history +/// Convert merger structure to Neovim internal structure for history /// /// @param[in] hms_p Converted merger structure. /// @param[out] hist_array Array with the results. @@ -1548,7 +1548,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) 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 + // 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. @@ -2349,7 +2349,7 @@ static inline ShaDaWriteResult shada_read_when_writing( /// @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. +/// with current Neovim runtime. static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef *const sd_reader) FUNC_ATTR_NONNULL_ARG(1) @@ -3063,7 +3063,7 @@ int shada_read_marks(void) /// /// @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). +/// 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. diff --git a/test/functional/shada/merging_spec.lua b/test/functional/shada/merging_spec.lua index fd0111d30d..2c3eb72e03 100644 --- a/test/functional/shada/merging_spec.lua +++ b/test/functional/shada/merging_spec.lua @@ -21,7 +21,7 @@ describe('ShaDa history merging code', function() os.remove(shada_fname) end) - it('takes item with greater timestamp from NeoVim instance when reading', + it('takes item with greater timestamp from Neovim instance when reading', function() wshada('\004\001\009\147\000\196\002ab\196\001a') eq(0, exc_exec(sdrcmd())) @@ -40,7 +40,7 @@ describe('ShaDa history merging code', function() eq(1, found) end) - it('takes item with equal timestamp from NeoVim instance when reading', + it('takes item with equal timestamp from Neovim instance when reading', function() wshada('\004\000\009\147\000\196\002ab\196\001a') eq(0, exc_exec(sdrcmd())) @@ -78,7 +78,7 @@ describe('ShaDa history merging code', function() eq(1, found) end) - it('takes item with greater timestamp from NeoVim instance when writing', + it('takes item with greater timestamp from Neovim instance when writing', function() wshada('\004\001\009\147\000\196\002ab\196\001a') eq(0, exc_exec(sdrcmd())) @@ -95,7 +95,7 @@ describe('ShaDa history merging code', function() eq(1, found) end) - it('takes item with equal timestamp from NeoVim instance when writing', + it('takes item with equal timestamp from Neovim instance when writing', function() wshada('\004\000\009\147\000\196\002ab\196\001a') eq(0, exc_exec(sdrcmd())) -- cgit From 313b947e3fe1f290d91e0b48517145e103d178d2 Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 19 Aug 2015 18:24:42 +0300 Subject: shada: Refactor code that works with hms.last_hist_entry --- src/nvim/shada.c | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 3f8dfe827d..b3559f3451 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1027,23 +1027,18 @@ static const void *shada_hist_iter(const void *const iter, /// @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) { - if (hms_p->iter == NULL) { - if (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); + 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; } - } else { - while (hms_p->iter != NULL - && 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); - hms_p->iter = shada_hist_iter(hms_p->iter, hms_p->history_type, - hms_p->reading, - &(hms_p->last_hist_entry)); - } + 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; @@ -1107,15 +1102,13 @@ static inline void hms_insert_whole_neovim_history( HistoryMergerState *const hms_p) FUNC_ATTR_NONNULL_ALL { - if (hms_p->last_hist_entry.type != kSDItemMissing) { + while (hms_p->last_hist_entry.type != kSDItemMissing) { hms_insert(hms_p, hms_p->last_hist_entry, false, hms_p->reading); - } - while (hms_p->iter != NULL - && hms_p->last_hist_entry.type != kSDItemMissing) { + 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)); - hms_insert(hms_p, hms_p->last_hist_entry, false, hms_p->reading); + hms_p->reading, &hms_p->last_hist_entry); } } -- cgit From ad7b7716bb00ad19c013956861b437ee0ccf0876 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 23 Aug 2015 13:53:47 +0300 Subject: *: Fix lint errors found in new code in previously ignored files --- src/nvim/buffer.c | 2 +- src/nvim/buffer_defs.h | 8 +- src/nvim/eval.c | 40 +++-- src/nvim/ex_getln.c | 36 ++-- src/nvim/globals.h | 4 +- src/nvim/lib/khash.h | 437 ++++++++++++++++++++++++++++--------------------- src/nvim/main.c | 2 +- src/nvim/mark.c | 78 ++++----- src/nvim/mark_defs.h | 2 +- src/nvim/ops.c | 4 +- src/nvim/option.c | 3 +- src/nvim/search.c | 2 +- src/nvim/search.h | 2 +- 13 files changed, 341 insertions(+), 279 deletions(-) diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index d0fad0e002..b3eba4f5f6 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1995,7 +1995,7 @@ buflist_nr2name ( /// /// @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 +/// @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. diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index f71575deaf..3eabb7ee43 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -512,10 +512,10 @@ struct file_buffer { visualinfo_T b_visual; int b_visual_mode_eval; /* b_visual.vi_mode for visualmode() */ - 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 */ + 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 diff --git a/src/nvim/eval.c b/src/nvim/eval.c index d2d195155d..d02348028a 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5340,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(NULL, l1, FALSE, 0); + l = list_copy(NULL, l1, false, 0); if (l == NULL) return FAIL; tv->v_type = VAR_LIST; @@ -5358,7 +5358,7 @@ static int list_concat(list_T *l1, list_T *l2, typval_T *tv) /// @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 +/// @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, @@ -6089,8 +6089,8 @@ void dictitem_free(dictitem_T *item) /// @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 +/// @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, @@ -18591,21 +18591,21 @@ void copy_tv(typval_T *from, typval_T *to) /// @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 +/// @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 +/// @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) + FUNC_ATTR_NONNULL_ARG(2, 3) { static int recurse = 0; int ret = OK; @@ -18645,8 +18645,9 @@ int var_item_copy(const vimconv_T *const conv, /* use the copy made earlier */ to->vval.v_list = from->vval.v_list->lv_copylist; ++to->vval.v_list->lv_refcount; - } else + } else { to->vval.v_list = list_copy(conv, from->vval.v_list, deep, copyID); + } if (to->vval.v_list == NULL) ret = FAIL; break; @@ -18659,8 +18660,9 @@ int var_item_copy(const vimconv_T *const conv, /* use the copy made earlier */ to->vval.v_dict = from->vval.v_dict->dv_copydict; ++to->vval.v_dict->dv_refcount; - } else + } else { to->vval.v_dict = dict_copy(conv, from->vval.v_dict, deep, copyID); + } if (to->vval.v_dict == NULL) ret = FAIL; break; @@ -20875,23 +20877,25 @@ 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_SHADA; - } else + } else { return VAR_FLAVOUR_DEFAULT; + } } /// Iterate over global variables /// -/// @warning No modifications to global variable dictionary must be performed +/// @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 +/// @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) diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 2bfe574b1d..7998d7e823 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -4268,7 +4268,7 @@ static int in_history ( int type, char_u *str, - int move_to_front, /* Move the entry to the front if it exists */ + int move_to_front, // Move the entry to the front if it exists int sep ) { @@ -4309,9 +4309,9 @@ in_history ( history[type][i].hisstr = str; history[type][i].timestamp = os_time(); history[type][i].additional_elements = NULL; - return TRUE; + return true; } - return FALSE; + return false; } /* @@ -4378,7 +4378,7 @@ add_to_history ( } last_maptick = -1; } - if (!in_history(histype, new_entry, TRUE, sep)) { + if (!in_history(histype, new_entry, true, sep)) { if (++hisidx[histype] == hislen) hisidx[histype] = 0; hisptr = &history[histype][hisidx[histype]]; @@ -4548,7 +4548,7 @@ char_u *get_history_entry(int histype, int idx) /// /// @param[in] histype One of the HIST_ values. /// -/// @return OK if there was something to clean and histype was one of HIST_ +/// @return OK if there was something to clean and histype was one of HIST_ /// values, FAIL otherwise. int clr_history(const int histype) { @@ -4575,7 +4575,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 */ @@ -4592,7 +4592,7 @@ int del_history_entry(int histype, char_u *str) if (hisptr->hisstr == NULL) break; if (vim_regexec(®match, hisptr->hisstr, (colnr_T)0)) { - found = TRUE; + found = true; hist_free_entry(hisptr); } else { if (i != last) { @@ -5077,24 +5077,24 @@ char_u *script_get(exarg_T *eap, char_u *cmd) /// Iterate over history items /// -/// @warning No history-editing functions must be run while iteration is in +/// @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 +/// @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 +/// @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 +/// @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) @@ -5143,9 +5143,9 @@ const void *hist_iter(const void *const iter, const uint8_t history_type, /// 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 +/// @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 +/// @param[out] new_hisnum Location where last history number in the new /// history should be saved. /// /// @return Pointer to the array or NULL. diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 146109a414..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 *used_shada_file INIT(= NULL); /* name of ShaDa 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 e45dcf2c80..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,175 +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_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((void *)h->keys); kfree(h->flags); \ - kfree((void *)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; \ - 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) \ @@ -599,12 +653,17 @@ static kh_inline khint_t __ac_Wang_hash(khint_t key) @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; \ - } } +#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 */ @@ -651,21 +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) +#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 /* __AC_KHASH_H */ + ((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/main.c b/src/nvim/main.c index c4387c0b7f..d865260295 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -2042,7 +2042,7 @@ static void usage(void) mch_msg(_(" -r, -L List swap files and exit\n")); mch_msg(_(" -r Recover crashed session\n")); mch_msg(_(" -u Use instead of the default\n")); - mch_msg(_(" -i Use instead of the default " SHADA_FILE "\n")); + mch_msg(_(" -i Use 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 db0f18c3a5..bf6ef33ae0 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -312,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 */ + 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 */ + } else if (c == '^') { // to where Insert mode stopped posp = &(buf->b_last_insert.mark); - else if (c == '.') /* to where last change was made */ + } else if (c == '.') { // to where last change was made posp = &(buf->b_last_change.mark); - else if (c == '[') /* to start of previous operator */ + } 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; @@ -620,9 +619,9 @@ 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].mark, NULL, TRUE); + 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); @@ -636,14 +635,14 @@ void do_marks(exarg_T *eap) xfree(name); } } - 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); + 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 @@ -737,13 +736,14 @@ void ex_delmarks(exarg_T *eap) from = to = *p; for (i = from; i <= to; ++i) { - if (lower) + if (lower) { curbuf->b_namedm[i - 'a'].mark.lnum = 0; - else { - if (digit) + } 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; @@ -1127,10 +1127,10 @@ 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 + 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 + // 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]; } @@ -1162,14 +1162,14 @@ void copy_jumplist(win_T *from, win_T *to) /// Iterate over jumplist items /// -/// @warning No jumplist-editing functions must be run while iteration is in +/// @warning No jumplist-editing functions must be 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 +/// @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) @@ -1193,14 +1193,14 @@ const void *mark_jumplist_iter(const void *const iter, const win_T *const win, /// Iterate over global marks /// -/// @warning No mark-editing functions must be run while iteration is in +/// @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 +/// @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) @@ -1234,11 +1234,11 @@ const void *mark_global_iter(const void *const iter, char *const name, /// 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 +/// @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 +/// 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. @@ -1275,7 +1275,7 @@ static inline const fmark_T *next_buffer_mark(const buf_T *const buf, /// Iterate over buffer marks /// -/// @warning No mark-editing functions must be run while iteration is in +/// @warning No mark-editing functions must be run while iteration is in /// progress. /// /// @param[in] iter Iterator. Pass NULL to start iteration. @@ -1283,7 +1283,7 @@ static inline const fmark_T *next_buffer_mark(const buf_T *const buf, /// @param[out] name Mark name. /// @param[out] fm Mark definition. /// -/// @return Pointer that needs to be passed to next `mark_buffer_iter` call or +/// @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) @@ -1321,7 +1321,7 @@ const void *mark_buffer_iter(const void *const iter, const buf_T *const buf, /// /// @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 +/// @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. @@ -1347,7 +1347,7 @@ bool mark_set_global(const char name, const xfmark_T fm, const bool update) /// @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 +/// @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. diff --git a/src/nvim/mark_defs.h b/src/nvim/mark_defs.h index 8f8425f0a6..720b2475ed 100644 --- a/src/nvim/mark_defs.h +++ b/src/nvim/mark_defs.h @@ -21,7 +21,7 @@ /// Total possible number of local marks /// -/// That are uppercase marks plus '"', '^' and '.'. There are other 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) diff --git a/src/nvim/ops.c b/src/nvim/ops.c index d59630697d..3fd2c0b773 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -5190,7 +5190,7 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet) reg->y_size = lines->lv_len; reg->additional_data = NULL; reg->timestamp = 0; - // Timestamp is not saved for clipboard registers because clipboard registers + // Timestamp is not saved for clipboard registers because clipboard registers // are not saved in the ShaDa file. int i = 0; @@ -5327,7 +5327,7 @@ static inline bool reg_empty(const yankreg_T *const reg) /// @param[out] name Register name. /// @param[out] reg Register contents. /// -/// @return Pointer that needs to be passed to next `op_register_iter` call or +/// @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) diff --git a/src/nvim/option.c b/src/nvim/option.c index 3f90293e19..653b217485 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2441,9 +2441,8 @@ did_set_string_option ( verbose_stop(); if (*p_vfile != NUL && verbose_open() == FAIL) errmsg = e_invarg; - } /* 'shada' */ - else if (varp == &p_shada) { + } else if (varp == &p_shada) { for (s = p_shada; *s; ) { /* Check it's a valid character */ if (vim_strchr((char_u *)"!\"%'/:<@cfhnrs", *s) == NULL) { diff --git a/src/nvim/search.c b/src/nvim/search.c index 3fbddc7fc8..8ba888841c 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -4633,7 +4633,7 @@ void set_substitute_pattern(const SearchPattern pat) /// Set last used search pattern /// -/// @param[in] is_substitute_pattern If true set substitute pattern as last +/// @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) { diff --git a/src/nvim/search.h b/src/nvim/search.h index 1fc2d6710e..6947f79d49 100644 --- a/src/nvim/search.h +++ b/src/nvim/search.h @@ -44,7 +44,7 @@ /// Structure containing offset definition for the last search pattern /// -/// @note Only offset for the last search pattern is used, not for the last +/// @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 ('?') -- cgit From ceb135a498c38d9469c14bf62997486b1af5fce7 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 23 Aug 2015 16:11:19 +0300 Subject: shada: Keep uid and gid of the file when working from root It was the behaviour exhibited by Vim, but it got unintentionally removed. There is a difference though: Vim runs fchown *before* populating the file, I run it just before renaming. --- src/nvim/shada.c | 58 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index b3559f3451..d20f05ee2b 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -14,6 +14,7 @@ #include #include +#include #include "nvim/os/os.h" #include "nvim/os/time.h" @@ -2902,6 +2903,11 @@ int shada_write_file(const char *const file, bool nomerge) .error = NULL, }; ShaDaReadDef sd_reader; +#ifdef UNIX + bool do_fchown = false; + uv_uid_t old_uid = (uv_uid_t) -1; + uv_gid_t old_gid = (uv_gid_t) -1; +#endif intptr_t fd; @@ -2915,17 +2921,24 @@ int shada_write_file(const char *const file, bool nomerge) // 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) - && 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)))) { - EMSG2(_("E137: ShaDa file is not writable: %s"), fname); - sd_reader.close(&sd_reader); - xfree(fname); - return FAIL; + 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()) { + do_fchown = true; + old_uid = (uv_uid_t) old_info.stat.st_uid; + old_gid = (uv_gid_t) old_info.stat.st_gid; + } + } 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_reader.close(&sd_reader); + xfree(fname); + return FAIL; + } } #endif tempname = modname(fname, ".tmp.a", false); @@ -3011,12 +3024,28 @@ shada_write_file_nomerge: {} const ShaDaWriteResult sw_ret = shada_write(&sd_writer, (nomerge ? NULL : &sd_reader)); - sd_writer.close(&sd_writer); - +#ifdef UNIX + if (!do_fchown) { +#endif + sd_writer.close(&sd_writer); +#ifdef UNIX + } +#endif if (!nomerge) { sd_reader.close(&sd_reader); bool did_remove = false; if (sw_ret == kSDWriteSuccessfull) { +#ifdef UNIX + if (do_fchown) { + 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; + } + } +#endif if (vim_rename(tempname, fname) == -1) { EMSG3(_(RNERR "Can't rename ShaDa file from %s to %s!"), tempname, fname); @@ -3034,6 +3063,9 @@ shada_write_file_nomerge: {} } } 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); } -- cgit From 6de5900c50c5fc7d8149ff7114d8114357e4e7f9 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 23 Aug 2015 17:59:18 +0300 Subject: documentation: Extend shada error handling documentation --- runtime/doc/starting.txt | 64 +++++++++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index cfd3d2c9d2..03e64db430 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -1108,26 +1108,46 @@ files for different types of files (e.g., C code) and load them based on the file name, using the ":autocmd" command (see |:autocmd|). More information on ShaDa file format is contained in |shada-format| section. - *shada-errors* -When Vim detects an error while reading a ShaDa file, it will not overwrite -that file. This was done to avoid accidentally destroying a file when the -file name of the ShaDa file is wrong. This could happen when accidentally -typing "nvim -i file" when you wanted "nvim -R file" (yes, somebody -accidentally did that!). If you want to overwrite a ShaDa file with an error -in it, you will either have to fix the error, delete the file (while Neovim is -running, so most of the information will be restored) or write it explicitly -with |:wshada| and a bang. *E136* *E138* *shada-error-handling* -Note: when Neovim finds out that it failed to write part of the ShaDa file -(e.g. because there is no space left to write the file) or when it appears -that already present ShaDa file contains errors that indicate that this file -is likely not a ShaDa file then ShaDa file with `.tmp.X` suffix is left on the -file system (where X is any latin small letter: from U+0061 to U+007A). You -may use such file to recover the data if you want, but in any case it needs to -be cleaned up after you resolve the issue that prevented old ShaDa file from -being overwritten. If Neovim fails to find unexisting `.tmp.X` file it will -not write ShaDa file at all. Errors which trigger this behaviour are listed -at |shada-keeping-tmpfile|. +Some errors make Neovim leave temporary file named `{basename}.tmp.X` (X is +any free letter from `a` to `z`) while normally it will create this file, +write to it and then rename `{basename}.tmp.X` to `{basename}`. Such errors +include: + +- Errors which make Neovim think that read file is not a ShaDa file at all: + non-ShaDa files are not overwritten for safety reasons to avoid accidentally + destroying an unrelated file. This could happen e.g. when typing "nvim -i + file" in place of "nvim -R file" (yes, somebody did that at least with Vim). + Such errors are listed at |shada-critical-contents-errors|. +- If writing to the temporary file failed: e.g. because of the insufficient + space left. +- If renaming file failed: e.g. because of insufficient permissions. On Unix + permissions are checked before trying to create even the temporary file, so + permission error can only happen if permissions were changed after starting + to edit the temporary file and before renaming it. +- If target ShaDa file has different from the Neovim instance's owners (user + and group) and changing them failed. Unix-specific, applies only when + Neovim was launched from root. + +Do not forget to remove the temporary file or replace the target file with +temporary one after getting one of the above errors or all attempts to create +a ShaDa file may fail with |E138|. If you got one of them when using +|:wshada| (and not when exiting Neovim: i.e. when you have Neovim session +running) you have additional options: + +- First thing which you should consider if you got any error, except failure + to write to the temporary file: remove existing file and replace it with the + temporary file. Do it even if you have running Neovim instance. +- Fix the permissions and/or file ownership, free some space and attempt to + write again. Do not remove the existing file. +- Use |:wshada| with bang. Does not help in case of permission error. If + target file was actually the ShaDa file some information may be lost in this + case. To make the matters slightly better use |:rshada| prior to writing, + but this still will loose buffer-local marks and change list entries for any + file which is not opened in the current Neovim instance. +- Remove the target file from shell and use |:wshada|. Consequences are not + different from using |:wshada| with bang, but "rm -f" works in some cases + when you don't have write permissions. *:rsh* *:rshada* *E886* :rsh[ada][!] [file] Read from ShaDa file [file] (default: see above). @@ -1144,8 +1164,8 @@ at |shada-keeping-tmpfile|. a merge between old and new info. When [!] is used, the old information is not read first, only the internal info is written (also disables safety checks - described in |shada-errors|). If 'shada' is empty, - marks for up to 100 files will be written. + described in |shada-error-handling|). If 'shada' is + empty, marks for up to 100 files will be written. When you get error "E138: All .tmp.X files exist, cannot write ShaDa file!" check that no old temp files were left behind (e.g. ~/.nvim/shada/main.shada.tmp*). @@ -1311,7 +1331,7 @@ Errors in ShaDa file may have two types: E575 used for all “logical” errors and E576 used for all “critical” errors. Critical errors trigger behaviour described in |shada-error-handling| when writing and skipping the rest of the file when reading and include: - *shada-keeping-tmpfile* + *shada-critical-contents-errors* - Any of first three MessagePack objects being not an unsigned integer. - Third object requesting amount of bytes greater then bytes left in the ShaDa file. -- cgit From e1dc9ed4641953977028650d8b7300a8680bdf55 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 23 Aug 2015 18:10:40 +0300 Subject: shada: First write temporary file and only then check any permissions It is not logical that on UNIX permissions can prevent even writing temporary file, while on other OS it will first write temporary file and then fail during rename. --- runtime/doc/starting.txt | 5 +--- src/nvim/shada.c | 74 +++++++++++++++++++++--------------------------- 2 files changed, 33 insertions(+), 46 deletions(-) diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index 03e64db430..0712888284 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -1121,10 +1121,7 @@ include: Such errors are listed at |shada-critical-contents-errors|. - If writing to the temporary file failed: e.g. because of the insufficient space left. -- If renaming file failed: e.g. because of insufficient permissions. On Unix - permissions are checked before trying to create even the temporary file, so - permission error can only happen if permissions were changed after starting - to edit the temporary file and before renaming it. +- If renaming file failed: e.g. because of insufficient permissions. - If target ShaDa file has different from the Neovim instance's owners (user and group) and changing them failed. Unix-specific, applies only when Neovim was launched from root. diff --git a/src/nvim/shada.c b/src/nvim/shada.c index d20f05ee2b..931636b030 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -2903,11 +2903,6 @@ int shada_write_file(const char *const file, bool nomerge) .error = NULL, }; ShaDaReadDef sd_reader; -#ifdef UNIX - bool do_fchown = false; - uv_uid_t old_uid = (uv_uid_t) -1; - uv_gid_t old_gid = (uv_gid_t) -1; -#endif intptr_t fd; @@ -2916,31 +2911,6 @@ int shada_write_file(const char *const file, bool nomerge) nomerge = true; goto shada_write_file_nomerge; } -#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; - 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()) { - do_fchown = true; - old_uid = (uv_uid_t) old_info.stat.st_uid; - old_gid = (uv_gid_t) old_info.stat.st_gid; - } - } 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_reader.close(&sd_reader); - xfree(fname); - return FAIL; - } - } -#endif tempname = modname(fname, ".tmp.a", false); if (tempname == NULL) { nomerge = true; @@ -3024,27 +2994,47 @@ shada_write_file_nomerge: {} const ShaDaWriteResult sw_ret = shada_write(&sd_writer, (nomerge ? NULL : &sd_reader)); -#ifdef UNIX - if (!do_fchown) { -#endif - sd_writer.close(&sd_writer); -#ifdef UNIX - } +#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 - if (do_fchown) { - 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)); + 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!"), -- cgit From 915a29822335905b79a2dacc73ce6bd219367eeb Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 23 Aug 2015 19:41:00 +0300 Subject: shada,functests: Fix v:hlsearch saving/restoring handling --- src/nvim/shada.c | 2 +- test/functional/shada/history_spec.lua | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 931636b030..938a76dbd8 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -2531,7 +2531,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, .offset = pat.off.off, .is_last_used = search_was_last_used(), .is_substitute_pattern = false, - .highlighted = (!no_hlsearch && find_shada_parameter('h') != NULL), + .highlighted = !(no_hlsearch || find_shada_parameter('h') != NULL), .pat = (char *) pat.pat, .additional_data = pat.additional_data, } diff --git a/test/functional/shada/history_spec.lua b/test/functional/shada/history_spec.lua index 9ec7f9c256..599330d6eb 100644 --- a/test/functional/shada/history_spec.lua +++ b/test/functional/shada/history_spec.lua @@ -128,15 +128,16 @@ describe('ShaDa support code', function() end) it('saves v:hlsearch=1', function() - nvim_command('set hlsearch') + nvim_command('set hlsearch shada-=h') nvim_feed('/test\n') + eq(1, nvim_eval('v:hlsearch')) nvim_command('qall') reset() eq(1, nvim_eval('v:hlsearch')) end) it('saves v:hlsearch=0 with :nohl', function() - nvim_command('set hlsearch') + nvim_command('set hlsearch shada-=h') nvim_feed('/test\n') nvim_command('nohlsearch') nvim_command('qall') @@ -144,9 +145,10 @@ describe('ShaDa support code', function() eq(0, nvim_eval('v:hlsearch')) end) - it('saves v:hlsearch=0 with :set viminfo-=h', function() - nvim_command('set hlsearch viminfo-=h') + it('saves v:hlsearch=0 with default &shada', function() + nvim_command('set hlsearch') nvim_feed('/test\n') + eq(1, nvim_eval('v:hlsearch')) nvim_command('qall') reset() eq(0, nvim_eval('v:hlsearch')) -- cgit From 29a3e972ded23b2a3f960c572c1c69e98646ae67 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 23 Aug 2015 19:58:56 +0300 Subject: shada: Fix v:hlsearch save/restore and do not write empty patterns --- src/nvim/shada.c | 90 ++++++++++++---------------- test/functional/shada/compatibility_spec.lua | 2 +- 2 files changed, 40 insertions(+), 52 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 938a76dbd8..197c3e4dcd 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1343,7 +1343,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) set_last_used_pattern( cur_entry.data.search_pattern.is_substitute_pattern); } - if (!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. @@ -2513,59 +2513,47 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, } 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 - { - SearchPattern pat; - get_search_pattern(&pat); - wms->search_pattern = (PossiblyFreedShadaEntry) { - .can_free_entry = false, - .data = { - .type = kSDItemSearchPattern, - .timestamp = pat.timestamp, - .data = { - .search_pattern = { - .magic = pat.magic, - .smartcase = !pat.no_scs, - .has_line_offset = pat.off.line, - .place_cursor_at_end = pat.off.end, - .offset = pat.off.off, - .is_last_used = search_was_last_used(), - .is_substitute_pattern = false, - .highlighted = !(no_hlsearch || find_shada_parameter('h') != NULL), - .pat = (char *) pat.pat, - .additional_data = pat.additional_data, - } - } - } - }; - } + ADD_SEARCH_PAT(get_search_pattern, search_pattern, pat.off.line, \ + pat.off.end, pat.off.off, false); // Initialize substitute search pattern - { - SearchPattern pat; - get_substitute_pattern(&pat); - wms->sub_search_pattern = (PossiblyFreedShadaEntry) { - .can_free_entry = false, - .data = { - .type = kSDItemSearchPattern, - .timestamp = pat.timestamp, - .data = { - .search_pattern = { - .magic = pat.magic, - .smartcase = !pat.no_scs, - .has_line_offset = false, - .place_cursor_at_end = false, - .offset = 0, - .is_last_used = !search_was_last_used(), - .is_substitute_pattern = true, - .highlighted = false, - .pat = (char *) pat.pat, - .additional_data = pat.additional_data, - } - } - } - }; - } + ADD_SEARCH_PAT(get_substitute_pattern, sub_search_pattern, false, false, 0, + true); +#undef ADD_SEARCH_PAT // Initialize substitute replacement string { diff --git a/test/functional/shada/compatibility_spec.lua b/test/functional/shada/compatibility_spec.lua index 13d4c44657..485e8a183c 100644 --- a/test/functional/shada/compatibility_spec.lua +++ b/test/functional/shada/compatibility_spec.lua @@ -39,7 +39,7 @@ describe('ShaDa forward compatibility support code', function() nvim_command('wshada ' .. shada_fname) found = false for _, v in ipairs(read_shada_file(shada_fname)) do - if v.type == 2 and v.value.ss then + if v.type == 2 and not v.value.ss then eq(nil, v.value.sX) found = true end -- cgit From 690d280fa8d7fd98969fa3fc5bae09a2cd928da6 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 29 Aug 2015 00:52:34 +0300 Subject: shada: Save an allocation when writing local marks --- src/nvim/shada.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 197c3e4dcd..190e5a6cbe 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -2282,7 +2282,6 @@ static inline ShaDaWriteResult shada_read_when_writing( k = kh_put(file_marks, &wms->file_marks, fname, &kh_ret); FileMarks *const filemarks = &kh_val(&wms->file_marks, k); if (kh_ret > 0) { - kh_key(&wms->file_marks, k) = xstrdup(fname); memset(filemarks, 0, sizeof(*filemarks)); } if (entry.timestamp > filemarks->greatest_timestamp) { @@ -2298,7 +2297,22 @@ static inline ShaDaWriteResult shada_read_when_writing( filemarks->additional_marks[filemarks->additional_marks_size - 1] = entry; } else { - COMPARE_WITH_ENTRY(&filemarks->marks[idx], entry); + 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) \ @@ -2700,7 +2714,6 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, k = kh_put(file_marks, &wms->file_marks, fname, &kh_ret); FileMarks *const filemarks = &kh_val(&wms->file_marks, k); if (kh_ret > 0) { - kh_key(&wms->file_marks, k) = xstrdup(fname); memset(filemarks, 0, sizeof(*filemarks)); } do { @@ -2807,7 +2820,6 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, i++) { if (kh_exist(&wms->file_marks, i)) { *cur_file_marks++ = &kh_val(&wms->file_marks, i); - xfree((void *) kh_key(&wms->file_marks, i)); } } qsort((void *) all_file_markss, file_markss_size, sizeof(*all_file_markss), -- cgit From d283e758ea8646d92a53cebb457f16a0ddf49d75 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 6 Sep 2015 05:31:04 +0300 Subject: shada: Fix out-of-bounds array access It leads to a memory leak as well. May overwrite wms->jumps_size. --- src/nvim/shada.c | 5 ++- test/functional/shada/merging_spec.lua | 71 ++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 190e5a6cbe..d6a507eb50 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1209,8 +1209,11 @@ static inline bool marks_equal(const pos_T a, const pos_T b) if (i > 0) { \ if (jl_len == JUMPLISTSIZE) { \ free_func(jumps[0]); \ + if (i == JUMPLISTSIZE) { \ + i = JUMPLISTSIZE - 1; \ + } \ memmove(&jumps[0], &jumps[1], sizeof(jumps[1]) * (size_t) i); \ - } else { \ + } else if (i != jl_len) { \ memmove(&jumps[i + 1], &jumps[i], \ sizeof(jumps[0]) * (size_t) (jl_len - i)); \ } \ diff --git a/test/functional/shada/merging_spec.lua b/test/functional/shada/merging_spec.lua index 2c3eb72e03..9d0d00f5c6 100644 --- a/test/functional/shada/merging_spec.lua +++ b/test/functional/shada/merging_spec.lua @@ -861,6 +861,41 @@ describe('ShaDa jumps support code', function() end eq(found, #jumps) end) + + it('merges JUMPLISTSIZE jumps when writing', function() + local jumps = {} + local shada = '' + for i = 1,100 do + shada = shada .. ('\008%c\018\131\162mX\195\161f\196\006/a/b/c\161l%c' + ):format(i, i) + jumps[i] = {file='/a/b/c', line=i} + end + wshada(shada) + eq(0, exc_exec(sdrcmd())) + local shada = '' + for i = 1,101 do + local t = i * 2 + shada = shada .. ( + '\008\204%c\019\131\162mX\195\161f\196\006/a/b/c\161l\204%c' + ):format(t, t) + jumps[(t > #jumps + 1) and (#jumps + 1) or t] = {file='/a/b/c', line=t} + end + wshada(shada) + eq(0, exc_exec('wshada ' .. shada_fname)) + local shift = #jumps - 100 + for i = 1,100 do + jumps[i] = jumps[i + shift] + end + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 8 then + found = found + 1 + eq(jumps[found].file, v.value.f) + eq(jumps[found].line, v.value.l) + end + end + eq(found, 100) + end) end) describe('ShaDa changes support code', function() @@ -919,4 +954,40 @@ describe('ShaDa changes support code', function() end eq(found, #changes) end) + + it('merges JUMPLISTSIZE changes when writing', function() + nvim_command('edit /a/b/c') + nvim_command('keepjumps call setline(1, range(202))') + local changes = {} + local shada = '' + for i = 1,100 do + shada = shada .. ('\011%c\018\131\162mX\195\161f\196\006/a/b/c\161l%c' + ):format(i, i) + changes[i] = {line=i} + end + wshada(shada) + eq(0, exc_exec(sdrcmd())) + local shada = '' + for i = 1,101 do + local t = i * 2 + shada = shada .. ( + '\011\204%c\019\131\162mX\195\161f\196\006/a/b/c\161l\204%c' + ):format(t, t) + changes[(t > #changes + 1) and (#changes + 1) or t] = {line=t} + end + wshada(shada) + eq(0, exc_exec('wshada ' .. shada_fname)) + local shift = #changes - 100 + for i = 1,100 do + changes[i] = changes[i + shift] + end + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 11 and v.value.f == '/a/b/c' then + found = found + 1 + eq(changes[found].line, v.value.l) + end + end + eq(found, 100) + end) end) -- cgit From 56a2549ff8bf2d02bdcf0c0946344d090d958e20 Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 18 Sep 2015 23:40:08 +0300 Subject: functests: Do not disable pattern tests Problem that led to this skip was fixed in [#3309][1]. [1]: https://github.com/neovim/neovim/commit/0a116c828debc6192a6bfb6bceb8cf020e867db0 --- test/functional/shada/history_spec.lua | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/test/functional/shada/history_spec.lua b/test/functional/shada/history_spec.lua index 599330d6eb..720ad986ea 100644 --- a/test/functional/shada/history_spec.lua +++ b/test/functional/shada/history_spec.lua @@ -12,16 +12,6 @@ describe('ShaDa support code', function() before_each(reset) after_each(clear) - local clang_sanitizer = os.getenv('CLANG_SANITIZER') - local it_noasan - if clang_sanitizer and clang_sanitizer:match('ASAN') then - it_noasan = function(name, test) - pending(name, function() end) - end - else - it_noasan = it - end - it('is able to dump and read back command-line history', function() nvim_command('set shada=\'0') nvim_feed(':" Test\n') @@ -249,7 +239,7 @@ describe('ShaDa support code', function() eq('.«', nvim_eval('getline(".")')) end) - it_noasan('dumps&loads s/pattern correctly when &encoding /= UTF-8 when dumping', + it('dumps&loads s/pattern correctly when &encoding /= UTF-8 when dumping', function() set_additional_cmd('set encoding=latin1') reset() @@ -290,7 +280,7 @@ describe('ShaDa support code', function() eq('', nvim_eval('histget("/", -1)')) end) - it_noasan('dumps&loads /pattern correctly when &encoding /= UTF-8 when dumping', + it('dumps&loads /pattern correctly when &encoding /= UTF-8 when dumping', function() set_additional_cmd('set encoding=latin1') reset() -- cgit From 7085ea07d8e5f976260476f2e84c38b3a3bf0407 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 19 Sep 2015 20:57:49 +0300 Subject: functests: Move exc_exec to test.functional.helpers --- test/functional/shada/compatibility_spec.lua | 6 +++--- test/functional/shada/errors_spec.lua | 6 +++--- test/functional/shada/helpers.lua | 13 ------------- test/functional/shada/marks_spec.lua | 5 +++-- test/functional/shada/merging_spec.lua | 6 +++--- test/functional/shada/shada_spec.lua | 10 +++++----- 6 files changed, 17 insertions(+), 29 deletions(-) diff --git a/test/functional/shada/compatibility_spec.lua b/test/functional/shada/compatibility_spec.lua index 485e8a183c..295678d8d6 100644 --- a/test/functional/shada/compatibility_spec.lua +++ b/test/functional/shada/compatibility_spec.lua @@ -3,12 +3,12 @@ local helpers = require('test.functional.helpers') local nvim, nvim_window, nvim_curwin, nvim_command, nvim_feed, nvim_eval, eq = helpers.nvim, helpers.window, helpers.curwin, helpers.command, helpers.feed, helpers.eval, helpers.eq +local exc_exec = helpers.exc_exec local shada_helpers = require('test.functional.shada.helpers') -local reset, set_additional_cmd, clear, exc_exec, get_shada_rw = +local reset, set_additional_cmd, clear, get_shada_rw = shada_helpers.reset, shada_helpers.set_additional_cmd, - shada_helpers.clear, shada_helpers.exc_exec, - shada_helpers.get_shada_rw + shada_helpers.clear, shada_helpers.get_shada_rw local read_shada_file = shada_helpers.read_shada_file local wshada, sdrcmd, shada_fname = get_shada_rw('Xtest-functional-shada-compatibility.shada') diff --git a/test/functional/shada/errors_spec.lua b/test/functional/shada/errors_spec.lua index ce849fdcbd..b612ed48c5 100644 --- a/test/functional/shada/errors_spec.lua +++ b/test/functional/shada/errors_spec.lua @@ -3,12 +3,12 @@ local helpers = require('test.functional.helpers') local nvim, nvim_window, nvim_curwin, nvim_command, nvim_feed, nvim_eval, eq = helpers.nvim, helpers.window, helpers.curwin, helpers.command, helpers.feed, helpers.eval, helpers.eq +local exc_exec = helpers.exc_exec local shada_helpers = require('test.functional.shada.helpers') -local reset, set_additional_cmd, clear, exc_exec, get_shada_rw = +local reset, set_additional_cmd, clear, get_shada_rw = shada_helpers.reset, shada_helpers.set_additional_cmd, - shada_helpers.clear, shada_helpers.exc_exec, - shada_helpers.get_shada_rw + shada_helpers.clear, shada_helpers.get_shada_rw local wshada, sdrcmd, shada_fname, clean = get_shada_rw('Xtest-functional-shada-errors.shada') diff --git a/test/functional/shada/helpers.lua b/test/functional/shada/helpers.lua index b3153ca7f1..3af92112cb 100644 --- a/test/functional/shada/helpers.lua +++ b/test/functional/shada/helpers.lua @@ -42,19 +42,6 @@ local clear = function() set_additional_cmd('') end -local exc_exec = function(cmd) - nvim_command(([[ - try - execute "%s" - catch - let g:__exception = v:exception - endtry - ]]):format(cmd:gsub('\n', '\\n'):gsub('[\\"]', '\\%0'))) - local ret = nvim_eval('get(g:, "__exception", 0)') - nvim_command('unlet! g:__exception') - return ret -end - local get_shada_rw = function(fname) local wshada = function(text) write_file(fname, text, true) diff --git a/test/functional/shada/marks_spec.lua b/test/functional/shada/marks_spec.lua index dc7a710143..18ce470cb7 100644 --- a/test/functional/shada/marks_spec.lua +++ b/test/functional/shada/marks_spec.lua @@ -3,11 +3,12 @@ local helpers = require('test.functional.helpers') local nvim, nvim_window, nvim_curwin, nvim_command, nvim_feed, nvim_eval, eq = helpers.nvim, helpers.window, helpers.curwin, helpers.command, helpers.feed, helpers.eval, helpers.eq +local exc_exec = helpers.exc_exec local shada_helpers = require('test.functional.shada.helpers') -local reset, set_additional_cmd, clear, exc_exec = +local reset, set_additional_cmd, clear = shada_helpers.reset, shada_helpers.set_additional_cmd, - shada_helpers.clear, shada_helpers.exc_exec + shada_helpers.clear local nvim_current_line = function() return nvim_window('get_cursor', nvim_curwin())[1] diff --git a/test/functional/shada/merging_spec.lua b/test/functional/shada/merging_spec.lua index 9d0d00f5c6..f7a3c4d9ca 100644 --- a/test/functional/shada/merging_spec.lua +++ b/test/functional/shada/merging_spec.lua @@ -3,12 +3,12 @@ local helpers = require('test.functional.helpers') local nvim, nvim_window, nvim_curwin, nvim_command, nvim_feed, nvim_eval, eq = helpers.nvim, helpers.window, helpers.curwin, helpers.command, helpers.feed, helpers.eval, helpers.eq +local exc_exec = helpers.exc_exec local shada_helpers = require('test.functional.shada.helpers') -local reset, set_additional_cmd, clear, exc_exec, get_shada_rw = +local reset, set_additional_cmd, clear, get_shada_rw = shada_helpers.reset, shada_helpers.set_additional_cmd, - shada_helpers.clear, shada_helpers.exc_exec, - shada_helpers.get_shada_rw + shada_helpers.clear, shada_helpers.get_shada_rw local read_shada_file = shada_helpers.read_shada_file local wshada, sdrcmd, shada_fname = diff --git a/test/functional/shada/shada_spec.lua b/test/functional/shada/shada_spec.lua index e256173611..ed022ea65c 100644 --- a/test/functional/shada/shada_spec.lua +++ b/test/functional/shada/shada_spec.lua @@ -3,17 +3,17 @@ local helpers = require('test.functional.helpers') local nvim, nvim_window, nvim_curwin, nvim_command, nvim_feed, nvim_eval, eq = helpers.nvim, helpers.window, helpers.curwin, helpers.command, helpers.feed, helpers.eval, helpers.eq -local write_file, spawn, set_session, nvim_prog = - helpers.write_file, helpers.spawn, helpers.set_session, helpers.nvim_prog +local write_file, spawn, set_session, nvim_prog, exc_exec = + helpers.write_file, helpers.spawn, helpers.set_session, helpers.nvim_prog, + helpers.exc_exec local lfs = require('lfs') local msgpack = require('MessagePack') local shada_helpers = require('test.functional.shada.helpers') -local reset, set_additional_cmd, clear, exc_exec, get_shada_rw = +local reset, set_additional_cmd, clear, get_shada_rw = shada_helpers.reset, shada_helpers.set_additional_cmd, - shada_helpers.clear, shada_helpers.exc_exec, - shada_helpers.get_shada_rw + shada_helpers.clear, shada_helpers.get_shada_rw local read_shada_file = shada_helpers.read_shada_file local wshada, sdrcmd, shada_fname, clean = get_shada_rw('Xtest-functional-shada-shada.shada') -- cgit From 0966e92cf441bbae1d50c68f56809fbb08fab7b6 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 22 Sep 2015 15:03:27 +0300 Subject: shada: When using shada-r normalize option path --- runtime/doc/options.txt | 3 +-- src/nvim/shada.c | 12 ++++++----- test/config/paths.lua.in | 1 + test/functional/shada/shada_spec.lua | 39 ++++++++++++++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index d65a8b9454..579fced314 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -5374,8 +5374,7 @@ A jump table for the options with a short description can be found at |Q_op|. specifies the start of a path for which no marks will be stored. This is to avoid removable media. For MS-DOS you could use "ra:,rb:". You can also use it for temp files, - e.g., for Unix: "r/tmp". Case is ignored. Maximum length of - each 'r' argument is 50 characters. + e.g., for Unix: "r/tmp". Case is ignored. *shada-s* s Maximum size of an item contents in KiB. If zero then nothing is saved. Unlike Vim this applies to all items, except for diff --git a/src/nvim/shada.c b/src/nvim/shada.c index d6a507eb50..a66b3c598e 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -68,6 +68,8 @@ KHASH_SET_INIT_STR(strset) #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 has_non_ascii(a) (has_non_ascii((char_u *)a)) @@ -4003,16 +4005,16 @@ bool shada_removable(const char *name) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE { char *p; - char part[51]; + char part[MAXPATHL + 1]; bool retval = false; - size_t n; char *new_name = home_replace_save(NULL, name); for (p = (char *) p_shada; *p; ) { - (void) copy_option_part(&p, part, 51, ", "); + (void) copy_option_part(&p, part, ARRAY_SIZE(part), ", "); if (part[0] == 'r') { - n = STRLEN(part + 1); - if (STRNICMP(part + 1, new_name, n) == 0) { + home_replace(NULL, part + 1, NameBuff, MAXPATHL, true); + size_t n = STRLEN(NameBuff); + if (STRNICMP(NameBuff, new_name, n) == 0) { retval = true; break; } diff --git a/test/config/paths.lua.in b/test/config/paths.lua.in index a13230ed28..80cc5629d1 100644 --- a/test/config/paths.lua.in +++ b/test/config/paths.lua.in @@ -7,6 +7,7 @@ end module.test_include_path = "${CMAKE_BINARY_DIR}/test/includes/post" module.test_libnvim_path = "${TEST_LIBNVIM_PATH}" +module.test_source_path = "${CMAKE_SOURCE_DIR}" table.insert(module.include_paths, "${CMAKE_BINARY_DIR}/include") return module diff --git a/test/functional/shada/shada_spec.lua b/test/functional/shada/shada_spec.lua index ed022ea65c..dfd5c436e4 100644 --- a/test/functional/shada/shada_spec.lua +++ b/test/functional/shada/shada_spec.lua @@ -7,6 +7,7 @@ local write_file, spawn, set_session, nvim_prog, exc_exec = helpers.write_file, helpers.spawn, helpers.set_session, helpers.nvim_prog, helpers.exc_exec local lfs = require('lfs') +local paths = require('test.config.paths') local msgpack = require('MessagePack') @@ -146,4 +147,42 @@ describe('ShaDa support code', function() session:exit(0) os.remove('NONE') end) + + it('correctly uses shada-r option', function() + nvim('set_var', '__home', paths.test_source_path) + nvim_command('let $HOME = __home') + nvim_command('unlet __home') + nvim_command('edit ~/README.md') + nvim_command('normal! GmAggmaAabc') + nvim_command('undo') + nvim_command('set shada+=%') + nvim_command('wshada! ' .. shada_fname) + local marklike = {[7]=true, [8]=true, [10]=true, [11]=true} + local readme_fname = paths.test_source_path .. '/README.md' + local find_readme = function() + local found = {} + for _, v in ipairs(read_shada_file(shada_fname)) do + if marklike[v.type] and v.value.f == readme_fname then + found[v.type] = (found[v.type] or 0) + 1 + elseif v.type == 9 then + for _, b in ipairs(v.value) do + if b.f == readme_fname then + found[v.type] = (found[v.type] or 0) + 1 + end + end + end + end + return found + end + eq({[7]=1, [8]=2, [9]=1, [10]=4, [11]=1}, find_readme()) + nvim_command('set shada+=r~') + nvim_command('wshada! ' .. shada_fname) + eq({}, find_readme()) + nvim_command('set shada-=r~') + nvim_command('wshada! ' .. shada_fname) + eq({[7]=1, [8]=2, [9]=1, [10]=4, [11]=1}, find_readme()) + nvim_command('set shada+=r' .. paths.test_source_path) + nvim_command('wshada! ' .. shada_fname) + eq({}, find_readme()) + end) end) -- cgit From b8e791559682e4e4ba435564c70ea038022ce364 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 22 Sep 2015 15:22:22 +0300 Subject: shada: Make sure that shada-r option correctly ignores case --- src/nvim/shada.c | 4 ++- test/functional/shada/shada_spec.lua | 58 +++++++++++++++++++++++------------- 2 files changed, 41 insertions(+), 21 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index a66b3c598e..53c58d081f 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -72,6 +72,8 @@ KHASH_SET_INIT_STR(strset) 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)) @@ -4014,7 +4016,7 @@ bool shada_removable(const char *name) if (part[0] == 'r') { home_replace(NULL, part + 1, NameBuff, MAXPATHL, true); size_t n = STRLEN(NameBuff); - if (STRNICMP(NameBuff, new_name, n) == 0) { + if (mb_strnicmp(NameBuff, new_name, n) == 0) { retval = true; break; } diff --git a/test/functional/shada/shada_spec.lua b/test/functional/shada/shada_spec.lua index dfd5c436e4..6c2694b834 100644 --- a/test/functional/shada/shada_spec.lua +++ b/test/functional/shada/shada_spec.lua @@ -148,6 +148,23 @@ describe('ShaDa support code', function() os.remove('NONE') end) + local marklike = {[7]=true, [8]=true, [10]=true, [11]=true} + local find_file = function(fname) + local found = {} + for _, v in ipairs(read_shada_file(shada_fname)) do + if marklike[v.type] and v.value.f == fname then + found[v.type] = (found[v.type] or 0) + 1 + elseif v.type == 9 then + for _, b in ipairs(v.value) do + if b.f == fname then + found[v.type] = (found[v.type] or 0) + 1 + end + end + end + end + return found + end + it('correctly uses shada-r option', function() nvim('set_var', '__home', paths.test_source_path) nvim_command('let $HOME = __home') @@ -157,32 +174,33 @@ describe('ShaDa support code', function() nvim_command('undo') nvim_command('set shada+=%') nvim_command('wshada! ' .. shada_fname) - local marklike = {[7]=true, [8]=true, [10]=true, [11]=true} local readme_fname = paths.test_source_path .. '/README.md' - local find_readme = function() - local found = {} - for _, v in ipairs(read_shada_file(shada_fname)) do - if marklike[v.type] and v.value.f == readme_fname then - found[v.type] = (found[v.type] or 0) + 1 - elseif v.type == 9 then - for _, b in ipairs(v.value) do - if b.f == readme_fname then - found[v.type] = (found[v.type] or 0) + 1 - end - end - end - end - return found - end - eq({[7]=1, [8]=2, [9]=1, [10]=4, [11]=1}, find_readme()) + eq({[7]=1, [8]=2, [9]=1, [10]=4, [11]=1}, find_file(readme_fname)) nvim_command('set shada+=r~') nvim_command('wshada! ' .. shada_fname) - eq({}, find_readme()) + eq({}, find_file(readme_fname)) nvim_command('set shada-=r~') nvim_command('wshada! ' .. shada_fname) - eq({[7]=1, [8]=2, [9]=1, [10]=4, [11]=1}, find_readme()) + eq({[7]=1, [8]=2, [9]=1, [10]=4, [11]=1}, find_file(readme_fname)) nvim_command('set shada+=r' .. paths.test_source_path) nvim_command('wshada! ' .. shada_fname) - eq({}, find_readme()) + eq({}, find_file(readme_fname)) + end) + + it('correctly ignores case with shada-r option', function() + local pwd = nvim('call_function', 'getcwd', {}) + local relfname = 'абв/test' + local fname = pwd .. '/' .. relfname + nvim('set_var', '__fname', fname) + nvim_command('silent! edit `=__fname`') + nvim('call_function', 'setline', {1, {'a', 'b', 'c', 'd'}}) + nvim_command('normal! GmAggmaAabc') + nvim_command('undo') + nvim_command('set shada+=%') + nvim_command('wshada! ' .. shada_fname) + eq({[7]=1, [8]=2, [9]=1, [10]=4, [11]=2}, find_file(fname)) + nvim_command('set shada+=r' .. pwd .. '/АБВ') + nvim_command('wshada! ' .. shada_fname) + eq({}, find_file(fname)) end) end) -- cgit From 0a44d504c86fbbf300fbff8fe82c93050c9436be Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 23 Sep 2015 23:41:18 +0300 Subject: shada(documentation): Fix outdated comments --- src/nvim/shada.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 53c58d081f..9e745431cb 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -145,13 +145,13 @@ KHASH_SET_INIT_STR(strset) #define RNERR "E136: " /// Flags for shada_read_file and children -enum { +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 /// @@ -398,7 +398,7 @@ typedef struct sd_read_def { ShaDaFileReader read; ///< Reader function. ShaDaReadCloser close; ///< Close function. ShaDaFileSkipper skip; ///< Function used to skip some bytes. - void *cookie; ///< Reader function last argument. + 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 @@ -423,7 +423,7 @@ typedef ptrdiff_t (*ShaDaFileWriter)(struct sd_write_def *const sd_writer, typedef struct sd_write_def { ShaDaFileWriter write; ///< Writer function. ShaDaWriteCloser close; ///< Close function. - void *cookie; ///< Writer function last argument. + 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. @@ -645,7 +645,7 @@ static inline void hmll_dealloc(HMLList *const hmll) /// Wrapper for reading from file descriptors /// -/// @return true if read was successfull, false otherwise. +/// @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 @@ -698,7 +698,7 @@ static int read_char(ShaDaReadDef *const sd_reader) /// Wrapper for writing to file descriptors /// -/// @return true if read was successfull, false otherwise. +/// @return -1 or number of bytes written. static ptrdiff_t write_file(ShaDaWriteDef *const sd_writer, const void *const dest, const size_t size) @@ -812,7 +812,7 @@ static ShaDaReadResult sd_reader_skip(ShaDaReadDef *const sd_reader, /// /// All arguments are passed to os_open(). /// -/// @return file descriptor or -1 on failure. +/// @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 { @@ -935,7 +935,7 @@ static bool shada_disabled(void) /// Read ShaDa file /// /// @param[in] file File to read or NULL to use default name. -/// @param[in] flags Flags, see kShaDa enum values in shada.h. +/// @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) @@ -1243,7 +1243,7 @@ static inline bool marks_equal(const pos_T a, const pos_T b) /// Read data from ShaDa file /// /// @param[in] sd_reader Structure containing file reader definition. -/// @param[in] flags What to read. +/// @param[in] flags What to read, see ShaDaReadFileFlags enum. static void shada_read(ShaDaReadDef *const sd_reader, const int flags) FUNC_ATTR_NONNULL_ALL { -- cgit From 2dd8e05f9fa9caef6fc04882dbdd27f387112266 Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 24 Sep 2015 00:22:58 +0300 Subject: shada: Fix jump/change list merging code Errors happens under following conditions: 1. Jump/change list is full. 2. New jump/change list item should go between some of the old ones. --- src/nvim/shada.c | 6 ++--- test/functional/shada/merging_spec.lua | 42 ++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 9e745431cb..cb5b5087f6 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1213,10 +1213,10 @@ static inline bool marks_equal(const pos_T a, const pos_T b) if (i > 0) { \ if (jl_len == JUMPLISTSIZE) { \ free_func(jumps[0]); \ - if (i == JUMPLISTSIZE) { \ - i = JUMPLISTSIZE - 1; \ + i--; \ + if (i > 0) { \ + memmove(&jumps[0], &jumps[1], sizeof(jumps[1]) * (size_t) i); \ } \ - 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)); \ diff --git a/test/functional/shada/merging_spec.lua b/test/functional/shada/merging_spec.lua index f7a3c4d9ca..b880bdae8a 100644 --- a/test/functional/shada/merging_spec.lua +++ b/test/functional/shada/merging_spec.lua @@ -990,4 +990,46 @@ describe('ShaDa changes support code', function() end eq(found, 100) end) + + it('merges JUMPLISTSIZE changes when writing, with new items between old', + function() + nvim_command('edit /a/b/c') + nvim_command('keepjumps call setline(1, range(202))') + local shada = '' + for i = 1,101 do + local t = i * 2 + shada = shada .. ( + '\011\204%c\019\131\162mX\195\161f\196\006/a/b/c\161l\204%c' + ):format(t, t) + end + wshada(shada) + eq(0, exc_exec(sdrcmd())) + local shada = '' + for i = 1,100 do + shada = shada .. ('\011%c\018\131\162mX\195\161f\196\006/a/b/c\161l%c' + ):format(i, i) + end + local changes = {} + for i = 1, 100 do + changes[i] = {line=i} + end + for i = 1, 101 do + local t = i * 2 + changes[(t > #changes + 1) and (#changes + 1) or t] = {line=t} + end + wshada(shada) + eq(0, exc_exec('wshada ' .. shada_fname)) + local shift = #changes - 100 + for i = 1,100 do + changes[i] = changes[i + shift] + end + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 11 and v.value.f == '/a/b/c' then + found = found + 1 + eq(changes[found].line, v.value.l) + end + end + eq(found, 100) + end) end) -- cgit From 9d72f8ebaa6a30cdf32d538ad5cb7605dd88b1fe Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 24 Sep 2015 20:16:46 +0300 Subject: shada: Fix shada_removable signature --- src/nvim/shada.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index cb5b5087f6..44e2402fec 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -4003,8 +4003,8 @@ shada_read_next_item_error: /// @param[in] name Checked name. /// /// @return True if it is, false otherwise. -bool shada_removable(const char *name) - FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +static bool shada_removable(const char *name) + FUNC_ATTR_WARN_UNUSED_RESULT { char *p; char part[MAXPATHL + 1]; -- cgit From 1162962d8beaab6be78a32954600205686f5d09b Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 27 Sep 2015 02:49:48 +0300 Subject: functests: Refactor tests: - Remove unused variables. - Do not use helpers.nvim_feed in most cases. - Do not use helpers.nvim and helpers.nvim_eval at all. - Add helpers.funcs and helpers.\*meths special tables. Indexing such table creates functions which call helpers.call or helpers.nvim (and similar) with first argument equal to table index. --- test/functional/ex_getln/history_spec.lua | 34 ++++---- test/functional/helpers.lua | 41 +++++++++ test/functional/shada/buffers_spec.lua | 34 +++----- test/functional/shada/compatibility_spec.lua | 123 ++++++++++++++------------- test/functional/shada/errors_spec.lua | 10 +-- test/functional/shada/helpers.lua | 7 +- test/functional/shada/history_spec.lua | 114 ++++++++++++------------- test/functional/shada/marks_spec.lua | 72 +++++++--------- test/functional/shada/merging_spec.lua | 115 ++++++++++++------------- test/functional/shada/registers_spec.lua | 37 +++----- test/functional/shada/shada_spec.lua | 27 +++--- test/functional/shada/variables_spec.lua | 80 ++++++++--------- 12 files changed, 345 insertions(+), 349 deletions(-) diff --git a/test/functional/ex_getln/history_spec.lua b/test/functional/ex_getln/history_spec.lua index c3ef56c8ad..532c81dab9 100644 --- a/test/functional/ex_getln/history_spec.lua +++ b/test/functional/ex_getln/history_spec.lua @@ -1,40 +1,36 @@ local helpers = require('test.functional.helpers') -local clear, nvim, call, eq = - helpers.clear, helpers.nvim, helpers.call, helpers.eq +local clear, meths, funcs, eq = + helpers.clear, helpers.meths, helpers.funcs, helpers.eq describe('history support code', function() before_each(clear) - local histadd = function(...) return call('histadd', ...) end - local histget = function(...) return call('histget', ...) end - local histdel = function(...) return call('histdel', ...) end - it('correctly clears start of the history', function() -- Regression test: check absense of the memory leak when clearing start of -- the history using ex_getln.c/clr_history(). - eq(1, histadd(':', 'foo')) - eq(1, histdel(':')) - eq('', histget(':', -1)) + eq(1, funcs.histadd(':', 'foo')) + eq(1, funcs.histdel(':')) + eq('', funcs.histget(':', -1)) end) it('correctly clears end of the history', function() -- Regression test: check absense of the memory leak when clearing end of -- the history using ex_getln.c/clr_history(). - nvim('set_option', 'history', 1) - eq(1, histadd(':', 'foo')) - eq(1, histdel(':')) - eq('', histget(':', -1)) + meths.set_option('history', 1) + eq(1, funcs.histadd(':', 'foo')) + eq(1, funcs.histdel(':')) + eq('', funcs.histget(':', -1)) end) it('correctly removes item from history', function() -- Regression test: check that ex_getln.c/del_history_idx() correctly clears -- history index after removing history entry. If it does not then deleting -- history will result in a double free. - eq(1, histadd(':', 'foo')) - eq(1, histadd(':', 'bar')) - eq(1, histadd(':', 'baz')) - eq(1, histdel(':', -2)) - eq(1, histdel(':')) - eq('', histget(':', -1)) + eq(1, funcs.histadd(':', 'foo')) + eq(1, funcs.histadd(':', 'bar')) + eq(1, funcs.histadd(':', 'baz')) + eq(1, funcs.histdel(':', -2)) + eq(1, funcs.histdel(':')) + eq('', funcs.histget(':', -1)) end) end) diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 79f1feb7b5..a62f7a3d00 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -359,6 +359,38 @@ local exc_exec = function(cmd) return ret end +local function redir_exec(cmd) + nvim_command(([[ + redir => g:__output + silent! execute "%s" + redir END + ]]):format(cmd:gsub('\n', '\\n'):gsub('[\\"]', '\\%0'))) + local ret = nvim_eval('get(g:, "__output", 0)') + nvim_command('unlet! g:__output') + return ret +end + +local function create_callindex(func) + local tbl = {} + setmetatable(tbl, { + __index = function(tbl, arg1) + ret = function(...) return func(arg1, ...) end + tbl[arg1] = ret + return ret + end, + }) + return tbl +end + +local funcs = create_callindex(nvim_call) +local meths = create_callindex(nvim) +local bufmeths = create_callindex(buffer) +local winmeths = create_callindex(window) +local tabmeths = create_callindex(tabpage) +local curbufmeths = create_callindex(curbuf) +local curwinmeths = create_callindex(curwin) +local curtabmeths = create_callindex(curtab) + return { prepend_argv = prepend_argv, clear = clear, @@ -397,5 +429,14 @@ return { rmdir = rmdir, mkdir = lfs.mkdir, exc_exec = exc_exec, + redir_exec = redir_exec, merge_args = merge_args, + funcs = funcs, + meths = meths, + bufmeths = bufmeths, + winmeths = winmeths, + tabmeths = tabmeths, + curbufmeths = curbufmeths, + curwinmeths = curwinmeths, + curtabmeths = curtabmeths, } diff --git a/test/functional/shada/buffers_spec.lua b/test/functional/shada/buffers_spec.lua index 38a8cad84d..3666b718f0 100644 --- a/test/functional/shada/buffers_spec.lua +++ b/test/functional/shada/buffers_spec.lua @@ -1,18 +1,13 @@ -- ShaDa buffer list saving/reading support local helpers = require('test.functional.helpers') -local nvim, nvim_window, nvim_curwin, nvim_command, nvim_feed, nvim_eval, eq = - helpers.nvim, helpers.window, helpers.curwin, helpers.command, helpers.feed, - helpers.eval, helpers.eq +local nvim_command, funcs, eq = + helpers.command, helpers.funcs, helpers.eq local shada_helpers = require('test.functional.shada.helpers') local reset, set_additional_cmd, clear = shada_helpers.reset, shada_helpers.set_additional_cmd, shada_helpers.clear -local nvim_current_line = function() - return nvim_window('get_cursor', nvim_curwin())[1] -end - describe('ShaDa support code', function() testfilename = 'Xtestfile-functional-shada-buffers' testfilename_2 = 'Xtestfile-functional-shada-buffers-2' @@ -24,15 +19,12 @@ describe('ShaDa support code', function() reset() nvim_command('edit ' .. testfilename) nvim_command('edit ' .. testfilename_2) - -- nvim_command('redir! > /tmp/vistr | verbose set shada? | redir END') - -- nvim_command('wshada /tmp/foo') nvim_command('qall') reset() - -- nvim_command('call writefile([&shada], "/tmp/vistr")') - eq(3, nvim_eval('bufnr("$")')) - eq('', nvim_eval('bufname(1)')) - eq(testfilename, nvim_eval('bufname(2)')) - eq(testfilename_2, nvim_eval('bufname(3)')) + eq(3, funcs.bufnr('$')) + eq('', funcs.bufname(1)) + eq(testfilename, funcs.bufname(2)) + eq(testfilename_2, funcs.bufname(3)) end) it('does not restore buffer list without % in &shada', function() @@ -40,26 +32,20 @@ describe('ShaDa support code', function() reset() nvim_command('edit ' .. testfilename) nvim_command('edit ' .. testfilename_2) - -- nvim_command('redir! > /tmp/vistr | verbose set shada? | redir END') - -- nvim_command('wshada /tmp/foo') set_additional_cmd('') nvim_command('qall') reset() - -- nvim_command('call writefile([&shada], "/tmp/vistr")') - eq(1, nvim_eval('bufnr("$")')) - eq('', nvim_eval('bufname(1)')) + eq(1, funcs.bufnr('$')) + eq('', funcs.bufname(1)) end) it('does not dump buffer list without % in &shada', function() nvim_command('edit ' .. testfilename) nvim_command('edit ' .. testfilename_2) - -- nvim_command('redir! > /tmp/vistr | verbose set shada? | redir END') - -- nvim_command('wshada /tmp/foo') set_additional_cmd('set shada+=%') nvim_command('qall') reset() - -- nvim_command('call writefile([&shada], "/tmp/vistr")') - eq(1, nvim_eval('bufnr("$")')) - eq('', nvim_eval('bufname(1)')) + eq(1, funcs.bufnr('$')) + eq('', funcs.bufname(1)) end) end) diff --git a/test/functional/shada/compatibility_spec.lua b/test/functional/shada/compatibility_spec.lua index 295678d8d6..342dee377b 100644 --- a/test/functional/shada/compatibility_spec.lua +++ b/test/functional/shada/compatibility_spec.lua @@ -1,8 +1,6 @@ -- ShaDa compatibility support local helpers = require('test.functional.helpers') -local nvim, nvim_window, nvim_curwin, nvim_command, nvim_feed, nvim_eval, eq = - helpers.nvim, helpers.window, helpers.curwin, helpers.command, helpers.feed, - helpers.eval, helpers.eq +local nvim_command, funcs, eq = helpers.command, helpers.funcs, helpers.eq local exc_exec = helpers.exc_exec local shada_helpers = require('test.functional.shada.helpers') @@ -45,11 +43,11 @@ describe('ShaDa forward compatibility support code', function() end end eq(true, found) - nvim_eval('garbagecollect(1)') - nvim_eval('garbagecollect(1)') + funcs.garbagecollect(1) + funcs.garbagecollect(1) nvim_command('rshada! ' .. shada_fname) - nvim_eval('garbagecollect(1)') - nvim_eval('garbagecollect(1)') + funcs.garbagecollect(1) + funcs.garbagecollect(1) end) it('works with s/search pattern item with BOOL unknown (sX) key value', function() @@ -77,11 +75,11 @@ describe('ShaDa forward compatibility support code', function() end end eq(true, found) - nvim_eval('garbagecollect(1)') - nvim_eval('garbagecollect(1)') + funcs.garbagecollect(1) + funcs.garbagecollect(1) nvim_command('rshada!' .. shada_fname) - nvim_eval('garbagecollect(1)') - nvim_eval('garbagecollect(1)') + funcs.garbagecollect(1) + funcs.garbagecollect(1) end) it('works with replacement item with BOOL additional value in list', function() @@ -110,11 +108,11 @@ describe('ShaDa forward compatibility support code', function() end end eq(true, found) - nvim_eval('garbagecollect(1)') - nvim_eval('garbagecollect(1)') + funcs.garbagecollect(1) + funcs.garbagecollect(1) nvim_command('rshada!' .. shada_fname) - nvim_eval('garbagecollect(1)') - nvim_eval('garbagecollect(1)') + funcs.garbagecollect(1) + funcs.garbagecollect(1) end) for _, v in ipairs({{name='global mark', mpack='\007\001\018\131\162mX\195\161f\196\006/a/b/c\161nA'}, @@ -124,8 +122,8 @@ describe('ShaDa forward compatibility support code', function() }) do it('works with ' .. v.name .. ' item with BOOL unknown (mX) key value', function() nvim_command('silent noautocmd edit /a/b/c') - eq('/a/b/c', nvim_eval('bufname("%")')) - nvim_command('call setline(".", ["1", "2", "3"])') + eq('/a/b/c', funcs.bufname('%')) + funcs.setline('.', {'1', '2', '3'}) wshada(v.mpack) eq(0, exc_exec(sdrcmd(true))) os.remove(shada_fname) @@ -141,7 +139,7 @@ describe('ShaDa forward compatibility support code', function() eq(true, found) eq(0, exc_exec(sdrcmd())) nvim_command('bwipeout!') - nvim_eval('setpos("\'A", [0, 1, 1, 0])') + funcs.setpos('\'A', {0, 1, 1, 0}) os.remove(shada_fname) nvim_command('wshada ' .. shada_fname) found = false @@ -153,18 +151,18 @@ describe('ShaDa forward compatibility support code', function() end end eq(false, found) - nvim_eval('garbagecollect(1)') - nvim_eval('garbagecollect(1)') + funcs.garbagecollect(1) + funcs.garbagecollect(1) nvim_command('rshada!' .. shada_fname) - nvim_eval('garbagecollect(1)') - nvim_eval('garbagecollect(1)') + funcs.garbagecollect(1) + funcs.garbagecollect(1) end) if v.name == 'global mark' or v.name == 'local mark' then it('works with ' .. v.name .. ' item with name', function() nvim_command('silent noautocmd edit /a/b/c') - eq('/a/b/c', nvim_eval('bufname("%")')) - nvim_command('call setline(".", ["1", "2", "3"])') + eq('/a/b/c', funcs.bufname('%')) + funcs.setline('.', {'1', '2', '3'}) wshada(v.mpack:gsub('n.$', 'n\001') .. v.mpack:gsub('n.$', 'n\002') .. v.mpack:gsub('n.$', 'n\003'):gsub('/a/b/c', '/d/e/f')) @@ -195,11 +193,11 @@ describe('ShaDa forward compatibility support code', function() end end eq(0, found) - nvim_eval('garbagecollect(1)') - nvim_eval('garbagecollect(1)') + funcs.garbagecollect(1) + funcs.garbagecollect(1) nvim_command('rshada!' .. shada_fname) - nvim_eval('garbagecollect(1)') - nvim_eval('garbagecollect(1)') + funcs.garbagecollect(1) + funcs.garbagecollect(1) end) end end @@ -227,11 +225,11 @@ describe('ShaDa forward compatibility support code', function() end end eq(false, found) - nvim_eval('garbagecollect(1)') - nvim_eval('garbagecollect(1)') + funcs.garbagecollect(1) + funcs.garbagecollect(1) nvim_command('rshada!' .. shada_fname) - nvim_eval('garbagecollect(1)') - nvim_eval('garbagecollect(1)') + funcs.garbagecollect(1) + funcs.garbagecollect(1) end) it('works with register item with name', function() @@ -263,17 +261,20 @@ describe('ShaDa forward compatibility support code', function() end end eq(0, found) - nvim_eval('garbagecollect(1)') - nvim_eval('garbagecollect(1)') + funcs.garbagecollect(1) + funcs.garbagecollect(1) nvim_command('rshada!' .. shada_fname) - nvim_eval('garbagecollect(1)') - nvim_eval('garbagecollect(1)') + funcs.garbagecollect(1) + funcs.garbagecollect(1) end) it('works with register item with type 10', function() wshada('\005\001\019\132\161na\162rX\194\162rc\145\196\001-\162rt\010') eq(0, exc_exec(sdrcmd(true))) - eq({{}, ''}, nvim_eval('[getreg("a", 1, 1)[:], getregtype("a")]')) + -- getreg may return empty list as list with NULL pointer which API + -- translates into nil for some reason. + eq({}, funcs.getreg('a', 1, 1) or {}) + eq('', funcs.getregtype('a')) nvim_command('wshada ' .. shada_fname) local found = 0 for i, v in ipairs(read_shada_file(shada_fname)) do @@ -300,19 +301,19 @@ describe('ShaDa forward compatibility support code', function() end end eq(0, found) - nvim_eval('garbagecollect(1)') - nvim_eval('garbagecollect(1)') + funcs.garbagecollect(1) + funcs.garbagecollect(1) nvim_command('rshada!' .. shada_fname) - nvim_eval('garbagecollect(1)') - nvim_eval('garbagecollect(1)') + funcs.garbagecollect(1) + funcs.garbagecollect(1) end) it('works with buffer list item with BOOL unknown (bX) key', function() nvim_command('set shada+=%') wshada('\009\000\016\145\130\161f\196\006/a/b/c\162bX\195') eq(0, exc_exec(sdrcmd())) - eq(2, nvim_eval('bufnr("$")')) - eq('/a/b/c', nvim_eval('bufname(2)')) + eq(2, funcs.bufnr('$')) + eq('/a/b/c', funcs.bufname(2)) os.remove(shada_fname) nvim_command('wshada ' .. shada_fname) local found = false @@ -335,11 +336,11 @@ describe('ShaDa forward compatibility support code', function() end eq(false, found) nvim_command('bwipeout!') - nvim_eval('garbagecollect(1)') - nvim_eval('garbagecollect(1)') + funcs.garbagecollect(1) + funcs.garbagecollect(1) nvim_command('rshada!' .. shada_fname) - nvim_eval('garbagecollect(1)') - nvim_eval('garbagecollect(1)') + funcs.garbagecollect(1) + funcs.garbagecollect(1) end) it('works with history item with BOOL additional value in list', function() @@ -358,8 +359,8 @@ describe('ShaDa forward compatibility support code', function() eq(true, found) eq(0, exc_exec(sdrcmd())) os.remove(shada_fname) - nvim_eval('histadd(":", "--")') - nvim_eval('histadd(":", "-")') + funcs.histadd(':', '--') + funcs.histadd(':', '-') nvim_command('wshada ' .. shada_fname) found = false for _, v in ipairs(read_shada_file(shada_fname)) do @@ -369,11 +370,11 @@ describe('ShaDa forward compatibility support code', function() end end eq(true, found) - nvim_eval('garbagecollect(1)') - nvim_eval('garbagecollect(1)') + funcs.garbagecollect(1) + funcs.garbagecollect(1) nvim_command('rshada!' .. shada_fname) - nvim_eval('garbagecollect(1)') - nvim_eval('garbagecollect(1)') + funcs.garbagecollect(1) + funcs.garbagecollect(1) end) it('works with history item with type 10', function() @@ -406,11 +407,11 @@ describe('ShaDa forward compatibility support code', function() end end eq(0, found) - nvim_eval('garbagecollect(1)') - nvim_eval('garbagecollect(1)') + funcs.garbagecollect(1) + funcs.garbagecollect(1) nvim_command('rshada!' .. shada_fname) - nvim_eval('garbagecollect(1)') - nvim_eval('garbagecollect(1)') + funcs.garbagecollect(1) + funcs.garbagecollect(1) end) it('works with item with 100 type', function() @@ -443,10 +444,10 @@ describe('ShaDa forward compatibility support code', function() end end eq(0, found) - nvim_eval('garbagecollect(1)') - nvim_eval('garbagecollect(1)') + funcs.garbagecollect(1) + funcs.garbagecollect(1) nvim_command('rshada!' .. shada_fname) - nvim_eval('garbagecollect(1)') - nvim_eval('garbagecollect(1)') + funcs.garbagecollect(1) + funcs.garbagecollect(1) end) end) diff --git a/test/functional/shada/errors_spec.lua b/test/functional/shada/errors_spec.lua index b612ed48c5..16ae77af02 100644 --- a/test/functional/shada/errors_spec.lua +++ b/test/functional/shada/errors_spec.lua @@ -1,14 +1,10 @@ -- ShaDa errors handling support local helpers = require('test.functional.helpers') -local nvim, nvim_window, nvim_curwin, nvim_command, nvim_feed, nvim_eval, eq = - helpers.nvim, helpers.window, helpers.curwin, helpers.command, helpers.feed, - helpers.eval, helpers.eq -local exc_exec = helpers.exc_exec +local nvim_command, eq, exc_exec = helpers.command, helpers.eq, helpers.exc_exec local shada_helpers = require('test.functional.shada.helpers') -local reset, set_additional_cmd, clear, get_shada_rw = - shada_helpers.reset, shada_helpers.set_additional_cmd, - shada_helpers.clear, shada_helpers.get_shada_rw +local reset, clear, get_shada_rw = + shada_helpers.reset, shada_helpers.clear, shada_helpers.get_shada_rw local wshada, sdrcmd, shada_fname, clean = get_shada_rw('Xtest-functional-shada-errors.shada') diff --git a/test/functional/shada/helpers.lua b/test/functional/shada/helpers.lua index 3af92112cb..c2ff4cadd1 100644 --- a/test/functional/shada/helpers.lua +++ b/test/functional/shada/helpers.lua @@ -1,7 +1,6 @@ local helpers = require('test.functional.helpers') -local spawn, set_session, nvim, nvim_prog, nvim_command, nvim_eval = - helpers.spawn, helpers.set_session, helpers.nvim, helpers.nvim_prog, - helpers.command, helpers.eval +local spawn, set_session, meths, nvim_prog = + helpers.spawn, helpers.set_session, helpers.meths, helpers.nvim_prog local write_file, merge_args = helpers.write_file, helpers.merge_args local msgpack = require('MessagePack') @@ -30,7 +29,7 @@ local reset = function() end session = spawn(nvim_argv()) set_session(session) - nvim('set_var', 'tmpname', tmpname) + meths.set_var('tmpname', tmpname) end local set_additional_cmd = function(s) diff --git a/test/functional/shada/history_spec.lua b/test/functional/shada/history_spec.lua index 720ad986ea..1123f829d2 100644 --- a/test/functional/shada/history_spec.lua +++ b/test/functional/shada/history_spec.lua @@ -1,7 +1,7 @@ -- ShaDa history saving/reading support local helpers = require('test.functional.helpers') -local nvim, nvim_command, nvim_eval, nvim_feed, eq = - helpers.nvim, helpers.command, helpers.eval, helpers.feed, helpers.eq +local nvim_command, funcs, meths, nvim_feed, eq = + helpers.command, helpers.funcs, helpers.meths, helpers.feed, helpers.eq local shada_helpers = require('test.functional.shada.helpers') local reset, set_additional_cmd, clear = @@ -19,7 +19,7 @@ describe('ShaDa support code', function() reset() nvim_command('set shada=\'0') nvim_command('rshada') - eq('" Test', nvim_eval('histget(":", -1)')) + eq('" Test', funcs.histget(':', -1)) end) it('is able to dump and read back 2 items in command-line history', function() @@ -30,8 +30,8 @@ describe('ShaDa support code', function() reset() nvim_command('set shada=\'0 history=2') nvim_command('rshada') - eq('" Test 2', nvim_eval('histget(":", -1)')) - eq('" Test', nvim_eval('histget(":", -2)')) + eq('" Test 2', funcs.histget(':', -1)) + eq('" Test', funcs.histget(':', -2)) nvim_command('qall') end) @@ -44,8 +44,8 @@ describe('ShaDa support code', function() reset() nvim_command('set shada=\'0 history=2') nvim_command('rshada') - eq('" Test 2', nvim_eval('histget(":", -1)')) - eq('', nvim_eval('histget(":", -2)')) + eq('" Test 2', funcs.histget(':', -1)) + eq('', funcs.histget(':', -2)) end) it('respects &history when loading', @@ -57,8 +57,8 @@ describe('ShaDa support code', function() reset() nvim_command('set shada=\'0 history=1') nvim_command('rshada') - eq('" Test 2', nvim_eval('histget(":", -1)')) - eq('', nvim_eval('histget(":", -2)')) + eq('" Test 2', funcs.histget(':', -1)) + eq('', funcs.histget(':', -2)) end) it('dumps only requested amount of command-line history items', function() @@ -67,13 +67,13 @@ describe('ShaDa support code', function() nvim_feed(':" Test 2\n') nvim_command('wshada') -- Regression test: :wshada should not alter or free history. - eq('" Test 2', nvim_eval('histget(":", -1)')) - eq('" Test', nvim_eval('histget(":", -2)')) + eq('" Test 2', funcs.histget(':', -1)) + eq('" Test', funcs.histget(':', -2)) reset() nvim_command('set shada=\'0') nvim_command('rshada') - eq('" Test 2', nvim_eval('histget(":", -1)')) - eq('', nvim_eval('histget(":", -2)')) + eq('" Test 2', funcs.histget(':', -1)) + eq('', funcs.histget(':', -2)) end) it('does not respect number in &shada when loading history', function() @@ -84,8 +84,8 @@ describe('ShaDa support code', function() reset() nvim_command('set shada=\'0,:1') nvim_command('rshada') - eq('" Test 2', nvim_eval('histget(":", -1)')) - eq('" Test', nvim_eval('histget(":", -2)')) + eq('" Test 2', funcs.histget(':', -1)) + eq('" Test', funcs.histget(':', -2)) end) it('dumps and loads all kinds of histories', function() @@ -99,31 +99,31 @@ describe('ShaDa support code', function() nvim_command('wshada') reset() nvim_command('rshada') - eq('" Test', nvim_eval('histget(":", -1)')) - eq('Test', nvim_eval('histget("/", -1)')) - eq('"Test"', nvim_eval('histget("=", -1)')) - eq('Test 2', nvim_eval('histget("@", -1)')) - eq('c', nvim_eval('histget(">", -1)')) + eq('" Test', funcs.histget(':', -1)) + eq('Test', funcs.histget('/', -1)) + eq('"Test"', funcs.histget('=', -1)) + eq('Test 2', funcs.histget('@', -1)) + eq('c', funcs.histget('>', -1)) end) it('dumps and loads last search pattern with offset', function() - nvim_eval('setline(".", ["foo", "bar"])') + funcs.setline('.', {'foo', 'bar'}) nvim_feed('gg0/a/e+1\n') - eq({0, 2, 3, 0}, nvim_eval('getpos(".")')) + eq({0, 2, 3, 0}, funcs.getpos('.')) nvim_command('wshada') reset() - nvim_eval('setline(".", ["foo", "bar"])') + funcs.setline('.', {'foo', 'bar'}) nvim_feed('gg0n') - eq({0, 2, 3, 0}, nvim_eval('getpos(".")')) + eq({0, 2, 3, 0}, funcs.getpos('.')) end) it('saves v:hlsearch=1', function() nvim_command('set hlsearch shada-=h') nvim_feed('/test\n') - eq(1, nvim_eval('v:hlsearch')) + eq(1, meths.get_vvar('hlsearch')) nvim_command('qall') reset() - eq(1, nvim_eval('v:hlsearch')) + eq(1, meths.get_vvar('hlsearch')) end) it('saves v:hlsearch=0 with :nohl', function() @@ -132,27 +132,27 @@ describe('ShaDa support code', function() nvim_command('nohlsearch') nvim_command('qall') reset() - eq(0, nvim_eval('v:hlsearch')) + eq(0, meths.get_vvar('hlsearch')) end) it('saves v:hlsearch=0 with default &shada', function() nvim_command('set hlsearch') nvim_feed('/test\n') - eq(1, nvim_eval('v:hlsearch')) + eq(1, meths.get_vvar('hlsearch')) nvim_command('qall') reset() - eq(0, nvim_eval('v:hlsearch')) + eq(0, meths.get_vvar('hlsearch')) end) it('dumps and loads last substitute pattern and replacement string', function() - nvim_eval('setline(".", ["foo", "bar"])') + funcs.setline('.', {'foo', 'bar'}) nvim_command('%s/f/g/g') - eq('goo', nvim_eval('getline(1)')) + eq('goo', funcs.getline(1)) nvim_command('wshada') reset() - nvim_eval('setline(".", ["foo", "bar"])') + funcs.setline('.', {'foo', 'bar'}) nvim_command('&') - eq('goo', nvim_eval('getline(1)')) + eq('goo', funcs.getline(1)) end) it('dumps and loads history correctly when &encoding is not UTF-8', function() @@ -162,7 +162,7 @@ describe('ShaDa support code', function() nvim_feed(':echo "\171"\n') nvim_command('qall') reset() - eq('echo "\171"', nvim_eval('histget(":", -1)')) + eq('echo "\171"', funcs.histget(':', -1)) end) it('dumps and loads history correctly when &encoding /= UTF-8 when dumping', @@ -174,7 +174,7 @@ describe('ShaDa support code', function() set_additional_cmd('') nvim_command('qall') reset() - eq('echo "«"', nvim_eval('histget(":", -1)')) + eq('echo "«"', funcs.histget(':', -1)) end) it('dumps and loads history correctly when &encoding /= UTF-8 when loading', @@ -184,7 +184,7 @@ describe('ShaDa support code', function() set_additional_cmd('set encoding=latin1') nvim_command('qall') reset() - eq('echo "\171"', nvim_eval('histget(":", -1)')) + eq('echo "\171"', funcs.histget(':', -1)) end) it('dumps and loads replacement correctly when &encoding is not UTF-8', @@ -195,9 +195,9 @@ describe('ShaDa support code', function() nvim_command('substitute/./\171/ge') nvim_command('qall!') reset() - nvim_eval('setline(".", ["."])') + funcs.setline('.', {'.'}) nvim_command('&') - eq('\171', nvim_eval('getline(".")')) + eq('\171', funcs.getline('.')) end) it('dumps&loads replacement correctly when &encoding /= UTF-8 when dumping', @@ -209,9 +209,9 @@ describe('ShaDa support code', function() set_additional_cmd('') nvim_command('qall') reset() - nvim_eval('setline(".", ["."])') + funcs.setline('.', {'.'}) nvim_command('&') - eq('«', nvim_eval('getline(".")')) + eq('«', funcs.getline('.')) end) it('dumps&loads replacement correctly when &encoding /= UTF-8 when loading', @@ -221,9 +221,9 @@ describe('ShaDa support code', function() set_additional_cmd('set encoding=latin1') nvim_command('qall') reset() - nvim_eval('setline(".", ["."])') + funcs.setline('.', {'.'}) nvim_command('&') - eq('\171', nvim_eval('getline(".")')) + eq('\171', funcs.getline('.')) end) it('dumps and loads substitute pattern correctly when &encoding is not UTF-8', @@ -234,9 +234,9 @@ describe('ShaDa support code', function() nvim_command('substitute/\171/./ge') nvim_command('qall!') reset() - nvim_eval('setline(".", ["\171«"])') + funcs.setline('.', {'\171«'}) nvim_command('&') - eq('.«', nvim_eval('getline(".")')) + eq('.«', funcs.getline('.')) end) it('dumps&loads s/pattern correctly when &encoding /= UTF-8 when dumping', @@ -248,9 +248,9 @@ describe('ShaDa support code', function() set_additional_cmd('') nvim_command('qall') reset() - nvim_eval('setline(".", ["«\171"])') + funcs.setline('.', {'«\171'}) nvim_command('&') - eq('.\171', nvim_eval('getline(".")')) + eq('.\171', funcs.getline('.')) end) it('dumps&loads s/pattern correctly when &encoding /= UTF-8 when loading', @@ -260,9 +260,9 @@ describe('ShaDa support code', function() set_additional_cmd('set encoding=latin1') nvim_command('qall') reset() - nvim_eval('setline(".", ["\171«"])') + funcs.setline('.', {'\171«'}) nvim_command('&') - eq('.«', nvim_eval('getline(".")')) + eq('.«', funcs.getline('.')) end) it('dumps and loads search pattern correctly when &encoding is not UTF-8', @@ -274,10 +274,10 @@ describe('ShaDa support code', function() nvim_command('set shada+=/0') nvim_command('qall!') reset() - nvim_eval('setline(".", ["\171«"])') + funcs.setline('.', {'\171«'}) nvim_command('~&') - eq('«', nvim_eval('getline(".")')) - eq('', nvim_eval('histget("/", -1)')) + eq('«', funcs.getline('.')) + eq('', funcs.histget('/', -1)) end) it('dumps&loads /pattern correctly when &encoding /= UTF-8 when dumping', @@ -290,10 +290,10 @@ describe('ShaDa support code', function() set_additional_cmd('') nvim_command('qall') reset() - nvim_eval('setline(".", ["«\171"])') + funcs.setline('.', {'«\171'}) nvim_command('~&') - eq('\171', nvim_eval('getline(".")')) - eq('', nvim_eval('histget("/", -1)')) + eq('\171', funcs.getline('.')) + eq('', funcs.histget('/', -1)) end) it('dumps&loads /pattern correctly when &encoding /= UTF-8 when loading', @@ -304,9 +304,9 @@ describe('ShaDa support code', function() set_additional_cmd('set encoding=latin1') nvim_command('qall') reset() - nvim_eval('setline(".", ["\171«"])') + funcs.setline('.', {'\171«'}) nvim_command('~&') - eq('«', nvim_eval('getline(".")')) - eq('', nvim_eval('histget("/", -1)')) + eq('«', funcs.getline('.')) + eq('', funcs.histget('/', -1)) end) end) diff --git a/test/functional/shada/marks_spec.lua b/test/functional/shada/marks_spec.lua index 18ce470cb7..b03af39662 100644 --- a/test/functional/shada/marks_spec.lua +++ b/test/functional/shada/marks_spec.lua @@ -1,9 +1,9 @@ -- ShaDa marks saving/reading support local helpers = require('test.functional.helpers') -local nvim, nvim_window, nvim_curwin, nvim_command, nvim_feed, nvim_eval, eq = - helpers.nvim, helpers.window, helpers.curwin, helpers.command, helpers.feed, - helpers.eval, helpers.eq -local exc_exec = helpers.exc_exec +local meths, curwinmeths, curbufmeths, nvim_command, funcs, eq = + helpers.meths, helpers.curwinmeths, helpers.curbufmeths, helpers.command, + helpers.funcs, helpers.eq +local exc_exec, redir_exec = helpers.exc_exec, helpers.redir_exec local shada_helpers = require('test.functional.shada.helpers') local reset, set_additional_cmd, clear = @@ -11,7 +11,7 @@ local reset, set_additional_cmd, clear = shada_helpers.clear local nvim_current_line = function() - return nvim_window('get_cursor', nvim_curwin())[1] + return curwinmeths.get_cursor()[1] end describe('ShaDa support code', function() @@ -43,7 +43,7 @@ describe('ShaDa support code', function() reset() nvim_command('rshada') nvim_command('normal! `A') - eq(testfilename, nvim_eval('fnamemodify(@%, ":t")')) + eq(testfilename, funcs.fnamemodify(curbufmeths.get_name(), ':t')) eq(1, nvim_current_line()) nvim_command('normal! `B') eq(2, nvim_current_line()) @@ -71,7 +71,7 @@ describe('ShaDa support code', function() reset() nvim_command('language C') nvim_command('normal! `A') - eq(testfilename, nvim_eval('fnamemodify(@%, ":t")')) + eq(testfilename, funcs.fnamemodify(curbufmeths.get_name(), ':t')) eq(1, nvim_current_line()) end) @@ -84,7 +84,7 @@ describe('ShaDa support code', function() reset() nvim_command('edit ' .. testfilename) nvim_command('normal! `a') - eq(testfilename, nvim_eval('fnamemodify(@%, ":t")')) + eq(testfilename, funcs.fnamemodify(curbufmeths.get_name(), ':t')) eq(1, nvim_current_line()) nvim_command('normal! `b') eq(2, nvim_current_line()) @@ -92,19 +92,19 @@ describe('ShaDa support code', function() it('is able to populate v:oldfiles', function() nvim_command('edit ' .. testfilename) - local tf_full = nvim_eval('fnamemodify(bufname("%"), ":p")') + local tf_full = curbufmeths.get_name() nvim_command('edit ' .. testfilename_2) - local tf_full_2 = nvim_eval('fnamemodify(bufname("%"), ":p")') + local tf_full_2 = curbufmeths.get_name() nvim_command('qall') reset() - local oldfiles = nvim('get_vvar', 'oldfiles') + local oldfiles = meths.get_vvar('oldfiles') eq(2, #oldfiles) eq(testfilename, oldfiles[1]:sub(-#testfilename)) eq(testfilename_2, oldfiles[2]:sub(-#testfilename_2)) eq(tf_full, oldfiles[1]) eq(tf_full_2, oldfiles[2]) nvim_command('rshada!') - local oldfiles = nvim('get_vvar', 'oldfiles') + local oldfiles = meths.get_vvar('oldfiles') eq(2, #oldfiles) eq(testfilename, oldfiles[1]:sub(-#testfilename)) eq(testfilename_2, oldfiles[2]:sub(-#testfilename_2)) @@ -114,74 +114,66 @@ describe('ShaDa support code', function() it('is able to dump and restore jump list', function() nvim_command('edit ' .. testfilename_2) - nvim_feed('G') - nvim_feed('gg') + nvim_command('normal! G') + nvim_command('normal! gg') nvim_command('edit ' .. testfilename) - nvim_feed('G') - nvim_feed('gg') + nvim_command('normal! G') + nvim_command('normal! gg') nvim_command('enew') - nvim_feed('gg') - nvim_command('redir => g:jumps | jumps | redir END') - local saved = nvim_eval('g:jumps') + nvim_command('normal! gg') + local saved = redir_exec('jumps') nvim_command('qall') reset() - nvim_command('redir => g:jumps | jumps | redir END') - eq(saved, nvim_eval('g:jumps')) + eq(saved, redir_exec('jumps')) end) it('is able to dump and restore jump list with different times (slow!)', function() nvim_command('edit ' .. testfilename_2) nvim_command('sleep 2') - nvim_feed('G') + nvim_command('normal! G') nvim_command('sleep 2') - nvim_feed('gg') + nvim_command('normal! gg') nvim_command('sleep 2') nvim_command('edit ' .. testfilename) nvim_command('sleep 2') - nvim_feed('G') + nvim_command('normal! G') nvim_command('sleep 2') - nvim_feed('gg') - -- nvim_command('redir! >/tmp/jumps.last | jumps | redir END') - -- nvim_command('wshada /tmp/foo') + nvim_command('normal! gg') nvim_command('qall') reset() nvim_command('redraw') - -- nvim_command('redir! >/tmp/jumps.init | jumps | redir END') nvim_command('edit ' .. testfilename) - -- nvim_command('redir! >/tmp/jumps | jumps | redir END') - eq(testfilename, nvim_eval('bufname("%")')) + eq(testfilename, funcs.bufname('%')) eq(1, nvim_current_line()) nvim_command('execute "normal! \\"') - eq(testfilename, nvim_eval('bufname("%")')) + eq(testfilename, funcs.bufname('%')) eq(1, nvim_current_line()) nvim_command('execute "normal! \\"') - eq(testfilename, nvim_eval('bufname("%")')) + eq(testfilename, funcs.bufname('%')) eq(2, nvim_current_line()) nvim_command('execute "normal! \\"') - eq(testfilename_2, nvim_eval('bufname("%")')) + eq(testfilename_2, funcs.bufname('%')) eq(1, nvim_current_line()) nvim_command('execute "normal! \\"') - eq(testfilename_2, nvim_eval('bufname("%")')) + eq(testfilename_2, funcs.bufname('%')) eq(2, nvim_current_line()) end) it('is able to dump and restore change list', function() nvim_command('edit ' .. testfilename) - nvim_feed('Gra') - nvim_feed('ggrb') + nvim_command('normal! Gra') + nvim_command('normal! ggrb') nvim_command('qall!') reset() nvim_command('edit ' .. testfilename) - -- nvim_command('rshada') - -- nvim_command('redir! >/tmp/changes | changes | redir END') - nvim_feed('Gg;') + nvim_command('normal! Gg;') -- Note: without “sync” “commands” test has good changes to fail for unknown -- reason (in first eq expected 1 is compared with 2). Any command inserted -- causes this to work properly. nvim_command('" sync') eq(1, nvim_current_line()) - nvim_feed('g;') + nvim_command('normal! g;') nvim_command('" sync 2') eq(2, nvim_current_line()) end) diff --git a/test/functional/shada/merging_spec.lua b/test/functional/shada/merging_spec.lua index b880bdae8a..7066ca9f54 100644 --- a/test/functional/shada/merging_spec.lua +++ b/test/functional/shada/merging_spec.lua @@ -1,14 +1,13 @@ -- ShaDa merging data support local helpers = require('test.functional.helpers') -local nvim, nvim_window, nvim_curwin, nvim_command, nvim_feed, nvim_eval, eq = - helpers.nvim, helpers.window, helpers.curwin, helpers.command, helpers.feed, - helpers.eval, helpers.eq -local exc_exec = helpers.exc_exec +local nvim_command, meths, funcs, curbufmeths, eq = + helpers.command, helpers.meths, helpers.funcs, + helpers.curbufmeths, helpers.eq +local exc_exec, redir_exec = helpers.exc_exec, helpers.redir_exec local shada_helpers = require('test.functional.shada.helpers') -local reset, set_additional_cmd, clear, get_shada_rw = - shada_helpers.reset, shada_helpers.set_additional_cmd, - shada_helpers.clear, shada_helpers.get_shada_rw +local reset, clear, get_shada_rw = + shada_helpers.reset, shada_helpers.clear, shada_helpers.get_shada_rw local read_shada_file = shada_helpers.read_shada_file local wshada, sdrcmd, shada_fname = @@ -142,7 +141,7 @@ describe('ShaDa history merging code', function() eq(0, exc_exec('wshada! ' .. shada_fname)) local items = {'ad', 'ab', 'ac', 'af', 'ae'} for i, v in ipairs(items) do - eq(v, nvim_eval(('histget(":", %i)'):format(i))) + eq(v, funcs.histget(':', i)) end local found = 0 for _, v in ipairs(read_shada_file(shada_fname)) do @@ -244,7 +243,7 @@ describe('ShaDa search pattern support code', function() eq(0, exc_exec(sdrcmd())) wshada('\002\000\011\130\162sX\194\162sp\196\001?') eq(0, exc_exec(sdrcmd())) - eq('-', nvim_eval('@/')) + eq('-', funcs.getreg('/')) end) it('uses last search pattern with gt tstamp from file when reading with bang', @@ -253,7 +252,7 @@ describe('ShaDa search pattern support code', function() eq(0, exc_exec(sdrcmd())) wshada('\002\000\011\130\162sX\194\162sp\196\001?') eq(0, exc_exec(sdrcmd(true))) - eq('?', nvim_eval('@/')) + eq('?', funcs.getreg('/')) end) it('uses last search pattern with eq timestamp from instance when reading', @@ -262,7 +261,7 @@ describe('ShaDa search pattern support code', function() eq(0, exc_exec(sdrcmd())) wshada('\002\001\011\130\162sX\194\162sp\196\001?') eq(0, exc_exec(sdrcmd())) - eq('-', nvim_eval('@/')) + eq('-', funcs.getreg('/')) end) it('uses last search pattern with gt timestamp from file when reading', @@ -271,7 +270,7 @@ describe('ShaDa search pattern support code', function() eq(0, exc_exec(sdrcmd())) wshada('\002\002\011\130\162sX\194\162sp\196\001?') eq(0, exc_exec(sdrcmd())) - eq('?', nvim_eval('@/')) + eq('?', funcs.getreg('/')) end) it('uses last search pattern with gt timestamp from instance when writing', @@ -279,7 +278,7 @@ describe('ShaDa search pattern support code', function() wshada('\002\001\011\130\162sX\194\162sp\196\001-') eq(0, exc_exec(sdrcmd())) wshada('\002\000\011\130\162sX\194\162sp\196\001?') - eq('-', nvim_eval('@/')) + eq('-', funcs.getreg('/')) eq(0, exc_exec('wshada ' .. shada_fname)) local found = 0 for _, v in ipairs(read_shada_file(shada_fname)) do @@ -295,7 +294,7 @@ describe('ShaDa search pattern support code', function() wshada('\002\001\011\130\162sX\194\162sp\196\001-') eq(0, exc_exec(sdrcmd())) wshada('\002\001\011\130\162sX\194\162sp\196\001?') - eq('-', nvim_eval('@/')) + eq('-', funcs.getreg('/')) eq(0, exc_exec('wshada ' .. shada_fname)) local found = 0 for _, v in ipairs(read_shada_file(shada_fname)) do @@ -311,7 +310,7 @@ describe('ShaDa search pattern support code', function() wshada('\002\001\011\130\162sX\194\162sp\196\001-') eq(0, exc_exec(sdrcmd())) wshada('\002\002\011\130\162sX\194\162sp\196\001?') - eq('-', nvim_eval('@/')) + eq('-', funcs.getreg('/')) eq(0, exc_exec('wshada ' .. shada_fname)) local found = 0 for _, v in ipairs(read_shada_file(shada_fname)) do @@ -328,7 +327,7 @@ describe('ShaDa search pattern support code', function() eq(0, exc_exec(sdrcmd())) wshada('\002\000\011\130\162ss\195\162sp\196\001?') eq(0, exc_exec(sdrcmd())) - eq('-', nvim_eval('@/')) + eq('-', funcs.getreg('/')) end) it('uses last s/ pattern with gt timestamp from file when reading with !', @@ -337,7 +336,7 @@ describe('ShaDa search pattern support code', function() eq(0, exc_exec(sdrcmd())) wshada('\002\000\011\130\162ss\195\162sp\196\001?') eq(0, exc_exec(sdrcmd(true))) - eq('?', nvim_eval('@/')) + eq('?', funcs.getreg('/')) end) it('uses last s/ pattern with eq timestamp from instance when reading', @@ -346,7 +345,7 @@ describe('ShaDa search pattern support code', function() eq(0, exc_exec(sdrcmd())) wshada('\002\001\011\130\162ss\195\162sp\196\001?') eq(0, exc_exec(sdrcmd())) - eq('-', nvim_eval('@/')) + eq('-', funcs.getreg('/')) end) it('uses last s/ pattern with gt timestamp from file when reading', @@ -355,7 +354,7 @@ describe('ShaDa search pattern support code', function() eq(0, exc_exec(sdrcmd())) wshada('\002\002\011\130\162ss\195\162sp\196\001?') eq(0, exc_exec(sdrcmd())) - eq('?', nvim_eval('@/')) + eq('?', funcs.getreg('/')) end) it('uses last s/ pattern with gt timestamp from instance when writing', @@ -363,7 +362,7 @@ describe('ShaDa search pattern support code', function() wshada('\002\001\011\130\162ss\195\162sp\196\001-') eq(0, exc_exec(sdrcmd())) wshada('\002\000\011\130\162ss\195\162sp\196\001?') - eq('-', nvim_eval('@/')) + eq('-', funcs.getreg('/')) eq(0, exc_exec('wshada ' .. shada_fname)) local found = 0 for _, v in ipairs(read_shada_file(shada_fname)) do @@ -379,7 +378,7 @@ describe('ShaDa search pattern support code', function() wshada('\002\001\011\130\162ss\195\162sp\196\001-') eq(0, exc_exec(sdrcmd())) wshada('\002\001\011\130\162ss\195\162sp\196\001?') - eq('-', nvim_eval('@/')) + eq('-', funcs.getreg('/')) eq(0, exc_exec('wshada ' .. shada_fname)) local found = 0 for _, v in ipairs(read_shada_file(shada_fname)) do @@ -395,7 +394,7 @@ describe('ShaDa search pattern support code', function() wshada('\002\001\011\130\162ss\195\162sp\196\001-') eq(0, exc_exec(sdrcmd())) wshada('\002\002\011\130\162ss\195\162sp\196\001?') - eq('-', nvim_eval('@/')) + eq('-', funcs.getreg('/')) eq(0, exc_exec('wshada ' .. shada_fname)) local found = 0 for _, v in ipairs(read_shada_file(shada_fname)) do @@ -421,7 +420,7 @@ describe('ShaDa replacement string support code', function() wshada('\003\000\004\145\196\001?') eq(0, exc_exec(sdrcmd())) nvim_command('s/.*/~') - eq('-', nvim_eval('getline(".")')) + eq('-', funcs.getline('.')) nvim_command('bwipeout!') end) @@ -432,7 +431,7 @@ describe('ShaDa replacement string support code', function() wshada('\003\000\004\145\196\001?') eq(0, exc_exec(sdrcmd(true))) nvim_command('s/.*/~') - eq('?', nvim_eval('getline(".")')) + eq('?', funcs.getline('.')) nvim_command('bwipeout!') end) @@ -443,7 +442,7 @@ describe('ShaDa replacement string support code', function() wshada('\003\001\004\145\196\001?') eq(0, exc_exec(sdrcmd())) nvim_command('s/.*/~') - eq('-', nvim_eval('getline(".")')) + eq('-', funcs.getline('.')) nvim_command('bwipeout!') end) @@ -454,7 +453,7 @@ describe('ShaDa replacement string support code', function() wshada('\003\002\004\145\196\001?') eq(0, exc_exec(sdrcmd())) nvim_command('s/.*/~') - eq('?', nvim_eval('getline(".")')) + eq('?', funcs.getline('.')) nvim_command('bwipeout!') end) @@ -518,7 +517,7 @@ describe('ShaDa marks support code', function() wshada('\007\000\018\131\162mX\195\161f\196\006/a/b/?\161nA') eq(0, exc_exec(sdrcmd())) nvim_command('normal! `A') - eq('-', nvim_eval('fnamemodify(bufname("%"), ":t")')) + eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) end) it('uses last A mark with gt timestamp from file when reading with !', @@ -528,7 +527,7 @@ describe('ShaDa marks support code', function() wshada('\007\000\018\131\162mX\195\161f\196\006/a/b/?\161nA') eq(0, exc_exec(sdrcmd(true))) nvim_command('normal! `A') - eq('?', nvim_eval('fnamemodify(bufname("%"), ":t")')) + eq('?', funcs.fnamemodify(curbufmeths.get_name(), ':t')) end) it('uses last A mark with eq timestamp from instance when reading', @@ -538,7 +537,7 @@ describe('ShaDa marks support code', function() wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/?\161nA') eq(0, exc_exec(sdrcmd())) nvim_command('normal! `A') - eq('-', nvim_eval('fnamemodify(bufname("%"), ":t")')) + eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) end) it('uses last A mark with gt timestamp from file when reading', @@ -548,7 +547,7 @@ describe('ShaDa marks support code', function() wshada('\007\002\018\131\162mX\195\161f\196\006/a/b/?\161nA') eq(0, exc_exec(sdrcmd())) nvim_command('normal! `A') - eq('?', nvim_eval('fnamemodify(bufname("%"), ":t")')) + eq('?', funcs.fnamemodify(curbufmeths.get_name(), ':t')) end) it('uses last A mark with gt timestamp from instance when writing', @@ -557,7 +556,7 @@ describe('ShaDa marks support code', function() eq(0, exc_exec(sdrcmd())) wshada('\007\000\018\131\162mX\195\161f\196\006/a/b/?\161nA') nvim_command('normal! `A') - eq('-', nvim_eval('fnamemodify(bufname("%"), ":t")')) + eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) eq(0, exc_exec('wshada ' .. shada_fname)) local found = 0 for _, v in ipairs(read_shada_file(shada_fname)) do @@ -574,7 +573,7 @@ describe('ShaDa marks support code', function() eq(0, exc_exec(sdrcmd())) wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/?\161nA') nvim_command('normal! `A') - eq('-', nvim_eval('fnamemodify(bufname("%"), ":t")')) + eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) eq(0, exc_exec('wshada ' .. shada_fname)) local found = 0 for _, v in ipairs(read_shada_file(shada_fname)) do @@ -591,7 +590,7 @@ describe('ShaDa marks support code', function() eq(0, exc_exec(sdrcmd())) wshada('\007\002\018\131\162mX\195\161f\196\006/a/b/?\161nA') nvim_command('normal! `A') - eq('-', nvim_eval('fnamemodify(bufname("%"), ":t")')) + eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) eq(0, exc_exec('wshada ' .. shada_fname)) local found = 0 for _, v in ipairs(read_shada_file(shada_fname)) do @@ -605,60 +604,60 @@ describe('ShaDa marks support code', function() it('uses last a mark with gt timestamp from instance when reading', function() nvim_command('edit /a/b/-') - nvim_eval('setline(1, ["-", "?"])') + funcs.setline(1, {'-', '?'}) wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na') eq(0, exc_exec(sdrcmd())) wshada('\010\000\017\131\161l\002\161f\196\006/a/b/-\161na') eq(0, exc_exec(sdrcmd())) nvim_command('normal! `a') - eq('-', nvim_eval('getline(".")')) + eq('-', funcs.getline('.')) end) it('uses last a mark with gt timestamp from file when reading with !', function() nvim_command('edit /a/b/-') - nvim_eval('setline(1, ["-", "?"])') + funcs.setline(1, {'-', '?'}) wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na') eq(0, exc_exec(sdrcmd())) wshada('\010\000\017\131\161l\002\161f\196\006/a/b/-\161na') eq(0, exc_exec(sdrcmd(true))) nvim_command('normal! `a') - eq('?', nvim_eval('getline(".")')) + eq('?', funcs.getline('.')) end) it('uses last a mark with eq timestamp from instance when reading', function() nvim_command('edit /a/b/-') - nvim_eval('setline(1, ["-", "?"])') + funcs.setline(1, {'-', '?'}) wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na') eq(0, exc_exec(sdrcmd())) wshada('\010\001\017\131\161l\002\161f\196\006/a/b/-\161na') eq(0, exc_exec(sdrcmd())) nvim_command('normal! `a') - eq('-', nvim_eval('getline(".")')) + eq('-', funcs.getline('.')) end) it('uses last a mark with gt timestamp from file when reading', function() nvim_command('edit /a/b/-') - nvim_eval('setline(1, ["-", "?"])') + funcs.setline(1, {'-', '?'}) wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na') eq(0, exc_exec(sdrcmd())) wshada('\010\002\017\131\161l\002\161f\196\006/a/b/-\161na') eq(0, exc_exec(sdrcmd())) nvim_command('normal! `a') - eq('?', nvim_eval('getline(".")')) + eq('?', funcs.getline('.')) end) it('uses last a mark with gt timestamp from instance when writing', function() nvim_command('edit /a/b/-') - nvim_eval('setline(1, ["-", "?"])') + funcs.setline(1, {'-', '?'}) wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na') eq(0, exc_exec(sdrcmd())) wshada('\010\000\017\131\161l\002\161f\196\006/a/b/-\161na') nvim_command('normal! `a') - eq('-', nvim_eval('getline(".")')) + eq('-', funcs.getline('.')) eq(0, exc_exec('wshada ' .. shada_fname)) local found = 0 for _, v in ipairs(read_shada_file(shada_fname)) do @@ -673,12 +672,12 @@ describe('ShaDa marks support code', function() it('uses last a mark with eq timestamp from instance when writing', function() nvim_command('edit /a/b/-') - nvim_eval('setline(1, ["-", "?"])') + funcs.setline(1, {'-', '?'}) wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na') eq(0, exc_exec(sdrcmd())) wshada('\010\001\017\131\161l\002\161f\196\006/a/b/-\161na') nvim_command('normal! `a') - eq('-', nvim_eval('getline(".")')) + eq('-', funcs.getline('.')) eq(0, exc_exec('wshada ' .. shada_fname)) local found = 0 for _, v in ipairs(read_shada_file(shada_fname)) do @@ -693,12 +692,12 @@ describe('ShaDa marks support code', function() it('uses last a mark with gt timestamp from file when writing', function() nvim_command('edit /a/b/-') - nvim_eval('setline(1, ["-", "?"])') + funcs.setline(1, {'-', '?'}) wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na') eq(0, exc_exec(sdrcmd())) wshada('\010\002\017\131\161l\002\161f\196\006/a/b/-\161na') nvim_command('normal! `a') - eq('-', nvim_eval('fnamemodify(bufname("%"), ":t")')) + eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) eq(0, exc_exec('wshada ' .. shada_fname)) local found = 0 for _, v in ipairs(read_shada_file(shada_fname)) do @@ -724,7 +723,7 @@ describe('ShaDa registers support code', function() eq(0, exc_exec(sdrcmd())) wshada('\005\000\015\131\161na\162rX\194\162rc\145\196\001?') eq(0, exc_exec(sdrcmd())) - eq('-', nvim_eval('@a')) + eq('-', funcs.getreg('a')) end) it('uses last a register with gt timestamp from file when reading with !', @@ -733,7 +732,7 @@ describe('ShaDa registers support code', function() eq(0, exc_exec(sdrcmd())) wshada('\005\000\015\131\161na\162rX\194\162rc\145\196\001?') eq(0, exc_exec(sdrcmd(true))) - eq('?', nvim_eval('@a')) + eq('?', funcs.getreg('a')) end) it('uses last a register with eq timestamp from instance when reading', @@ -742,7 +741,7 @@ describe('ShaDa registers support code', function() eq(0, exc_exec(sdrcmd())) wshada('\005\001\015\131\161na\162rX\194\162rc\145\196\001?') eq(0, exc_exec(sdrcmd())) - eq('-', nvim_eval('@a')) + eq('-', funcs.getreg('a')) end) it('uses last a register with gt timestamp from file when reading', @@ -751,7 +750,7 @@ describe('ShaDa registers support code', function() eq(0, exc_exec(sdrcmd())) wshada('\005\002\015\131\161na\162rX\194\162rc\145\196\001?') eq(0, exc_exec(sdrcmd())) - eq('?', nvim_eval('@a')) + eq('?', funcs.getreg('a')) end) it('uses last a register with gt timestamp from instance when writing', @@ -759,7 +758,7 @@ describe('ShaDa registers support code', function() wshada('\005\001\015\131\161na\162rX\194\162rc\145\196\001-') eq(0, exc_exec(sdrcmd())) wshada('\005\000\015\131\161na\162rX\194\162rc\145\196\001?') - eq('-', nvim_eval('@a')) + eq('-', funcs.getreg('a')) eq(0, exc_exec('wshada ' .. shada_fname)) local found = 0 for _, v in ipairs(read_shada_file(shada_fname)) do @@ -776,7 +775,7 @@ describe('ShaDa registers support code', function() wshada('\005\001\015\131\161na\162rX\194\162rc\145\196\001-') eq(0, exc_exec(sdrcmd())) wshada('\005\001\015\131\161na\162rX\194\162rc\145\196\001?') - eq('-', nvim_eval('@a')) + eq('-', funcs.getreg('a')) eq(0, exc_exec('wshada ' .. shada_fname)) local found = 0 for _, v in ipairs(read_shada_file(shada_fname)) do @@ -793,7 +792,7 @@ describe('ShaDa registers support code', function() wshada('\005\001\015\131\161na\162rX\194\162rc\145\196\001-') eq(0, exc_exec(sdrcmd())) wshada('\005\002\015\131\161na\162rX\194\162rc\145\196\001?') - eq('-', nvim_eval('@a')) + eq('-', funcs.getreg('a')) eq(0, exc_exec('wshada ' .. shada_fname)) local found = 0 for _, v in ipairs(read_shada_file(shada_fname)) do @@ -822,8 +821,7 @@ describe('ShaDa jumps support code', function() .. '\008\004\018\131\162mX\195\161f\196\006/a/b/d\161l\003' .. '\008\007\018\131\162mX\195\161f\196\006/a/b/f\161l\002') eq(0, exc_exec(sdrcmd())) - nvim_command('redir => g:jumps | jumps | redir END') - eq('', nvim_eval('bufname("%")')) + eq('', curbufmeths.get_name()) eq('\n' .. ' jump line col file/text\n' .. ' 6 2 0 /a/b/c\n' @@ -832,7 +830,7 @@ describe('ShaDa jumps support code', function() .. ' 3 2 0 /a/b/e\n' .. ' 2 2 0 /a/b/f\n' .. ' 1 1 0 \n' - .. '>', nvim_eval('g:jumps')) + .. '>', redir_exec('jumps')) end) it('merges jumps when writing', function() @@ -916,7 +914,6 @@ describe('ShaDa changes support code', function() .. '\011\004\018\131\162mX\195\161f\196\006/a/b/c\161l\005' .. '\011\008\018\131\162mX\195\161f\196\006/a/b/c\161l\004') eq(0, exc_exec(sdrcmd())) - nvim_command('redir => g:changes | changes | redir END') eq('\n' .. 'change line col text\n' .. ' 5 1 0 0\n' @@ -924,7 +921,7 @@ describe('ShaDa changes support code', function() .. ' 3 5 0 4\n' .. ' 2 3 0 2\n' .. ' 1 4 0 3\n' - .. '>', nvim_eval('g:changes')) + .. '>', redir_exec('changes')) end) it('merges changes when writing', function() diff --git a/test/functional/shada/registers_spec.lua b/test/functional/shada/registers_spec.lua index 67b0661151..f0133b1086 100644 --- a/test/functional/shada/registers_spec.lua +++ b/test/functional/shada/registers_spec.lua @@ -1,34 +1,23 @@ -- ShaDa registers saving/reading support local helpers = require('test.functional.helpers') -local nvim, nvim_window, nvim_curwin, nvim_command, nvim_feed, nvim_eval, eq = - helpers.nvim, helpers.window, helpers.curwin, helpers.command, helpers.feed, - helpers.eval, helpers.eq +local nvim_command, funcs, eq = helpers.command, helpers.funcs, helpers.eq local shada_helpers = require('test.functional.shada.helpers') local reset, set_additional_cmd, clear = shada_helpers.reset, shada_helpers.set_additional_cmd, shada_helpers.clear -local nvim_current_line = function() - return nvim_window('get_cursor', nvim_curwin())[1] -end - local setreg = function(name, contents, typ) - local expr = 'setreg("' .. name .. '", [' if type(contents) == 'string' then contents = {contents} end - for _, line in ipairs(contents) do - expr = expr .. '"' .. line:gsub('[\\"]', '\\\\\\0') .. '", ' - end - expr = expr .. '], "' .. typ .. '")' - nvim_eval(expr) + funcs.setreg(name, contents, typ) end local getreg = function(name) return { - nvim_eval(('getreg("%s", 1, 1)'):format(name)), - nvim_eval(('getregtype("%s")'):format(name)), + funcs.getreg(name, 1, 1), + funcs.getregtype(name), } end @@ -40,7 +29,7 @@ describe('ShaDa support code', function() setreg('c', {'d', 'e', ''}, 'c') setreg('l', {'a', 'b', 'cde'}, 'l') setreg('b', {'bca', 'abc', 'cba'}, 'b3') - nvim_command('qa') + nvim_command('qall') reset() eq({{'d', 'e', ''}, 'v'}, getreg('c')) eq({{'a', 'b', 'cde'}, 'V'}, getreg('l')) @@ -52,7 +41,7 @@ describe('ShaDa support code', function() setreg('c', {'d', 'e', ''}, 'c') setreg('l', {'a', 'b', 'cde'}, 'l') setreg('b', {'bca', 'abc', 'cba'}, 'b3') - nvim_command('qa') + nvim_command('qall') reset() eq({nil, ''}, getreg('c')) eq({nil, ''}, getreg('l')) @@ -64,7 +53,7 @@ describe('ShaDa support code', function() setreg('l', {'a', 'b', 'cde'}, 'l') setreg('b', {'bca', 'abc', 'cba'}, 'b3') set_additional_cmd('set shada=\'0,<0') - nvim_command('qa') + nvim_command('qall') reset() eq({{'d', 'e', ''}, 'v'}, getreg('c')) eq({{'a', 'b', 'cde'}, 'V'}, getreg('l')) @@ -76,7 +65,7 @@ describe('ShaDa support code', function() setreg('c', {'d', 'e', ''}, 'c') setreg('l', {'a', 'b', 'cde'}, 'l') setreg('b', {'bca', 'abc', 'cba'}, 'b3') - nvim_command('qa') + nvim_command('qall') reset() eq({nil, ''}, getreg('c')) eq({nil, ''}, getreg('l')) @@ -88,7 +77,7 @@ describe('ShaDa support code', function() setreg('l', {'a', 'b', 'cde'}, 'l') setreg('b', {'bca', 'abc', 'cba'}, 'b3') set_additional_cmd('set shada=\'0,\\"0') - nvim_command('qa') + nvim_command('qall') reset() eq({{'d', 'e', ''}, 'v'}, getreg('c')) eq({{'a', 'b', 'cde'}, 'V'}, getreg('l')) @@ -100,7 +89,7 @@ describe('ShaDa support code', function() setreg('c', {'d', 'e', ''}, 'c') setreg('l', {'a', 'b', 'cde'}, 'l') setreg('b', {'bca', 'abc', 'cba'}, 'b3') - nvim_command('qa') + nvim_command('qall') reset() eq({{'d', 'e', ''}, 'v'}, getreg('c')) eq({{'a', 'b', 'cde'}, 'V'}, getreg('l')) @@ -111,7 +100,7 @@ describe('ShaDa support code', function() nvim_command('set shada=\'0,<2') setreg('o', {'d'}, 'c') setreg('t', {'a', 'b', 'cde'}, 'l') - nvim_command('qa') + nvim_command('qall') reset() eq({{'d'}, 'v'}, getreg('o')) eq({nil, ''}, getreg('t')) @@ -121,7 +110,7 @@ describe('ShaDa support code', function() nvim_command('set shada=\'0,\\"2') setreg('o', {'d'}, 'c') setreg('t', {'a', 'b', 'cde'}, 'l') - nvim_command('qa') + nvim_command('qall') reset() eq({{'d'}, 'v'}, getreg('o')) eq({nil, ''}, getreg('t')) @@ -132,7 +121,7 @@ describe('ShaDa support code', function() setreg('o', {'d'}, 'c') setreg('t', {'a', 'b', 'cde'}, 'l') setreg('h', {'abc', 'acb', 'bac', 'bca', 'cab', 'cba'}, 'b3') - nvim_command('qa') + nvim_command('qall') reset() eq({{'d'}, 'v'}, getreg('o')) eq({{'a', 'b', 'cde'}, 'V'}, getreg('t')) diff --git a/test/functional/shada/shada_spec.lua b/test/functional/shada/shada_spec.lua index 6c2694b834..7a30d3b87d 100644 --- a/test/functional/shada/shada_spec.lua +++ b/test/functional/shada/shada_spec.lua @@ -1,8 +1,7 @@ -- Other ShaDa tests local helpers = require('test.functional.helpers') -local nvim, nvim_window, nvim_curwin, nvim_command, nvim_feed, nvim_eval, eq = - helpers.nvim, helpers.window, helpers.curwin, helpers.command, helpers.feed, - helpers.eval, helpers.eq +local meths, nvim_command, funcs, eq = + helpers.meths, helpers.command, helpers.funcs, helpers.eq local write_file, spawn, set_session, nvim_prog, exc_exec = helpers.write_file, helpers.spawn, helpers.set_session, helpers.nvim_prog, helpers.exc_exec @@ -12,12 +11,12 @@ local paths = require('test.config.paths') local msgpack = require('MessagePack') local shada_helpers = require('test.functional.shada.helpers') -local reset, set_additional_cmd, clear, get_shada_rw = - shada_helpers.reset, shada_helpers.set_additional_cmd, - shada_helpers.clear, shada_helpers.get_shada_rw +local reset, clear, get_shada_rw = + shada_helpers.reset, shada_helpers.clear, shada_helpers.get_shada_rw local read_shada_file = shada_helpers.read_shada_file -local wshada, sdrcmd, shada_fname, clean = get_shada_rw('Xtest-functional-shada-shada.shada') +local wshada, _, shada_fname, clean = + get_shada_rw('Xtest-functional-shada-shada.shada') describe('ShaDa support code', function() before_each(reset) @@ -52,8 +51,8 @@ describe('ShaDa support code', function() local hist1 = ('-'):rep(1024 - 5) local hist2 = ('-'):rep(1025 - 5) nvim_command('set shada-=s10 shada+=s1') - nvim_eval(('histadd(":", "%s")'):format(hist1)) - nvim_eval(('histadd(":", "%s")'):format(hist2)) + funcs.histadd(':', hist1) + funcs.histadd(':', hist2) eq(0, exc_exec('wshada ' .. shada_fname)) local found = 0 for _, v in ipairs(read_shada_file(shada_fname)) do @@ -143,7 +142,7 @@ describe('ShaDa support code', function() local session = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed'}, true) set_session(session) - eq('', nvim_eval('@a')) + eq('', funcs.getreg('a')) session:exit(0) os.remove('NONE') end) @@ -166,7 +165,7 @@ describe('ShaDa support code', function() end it('correctly uses shada-r option', function() - nvim('set_var', '__home', paths.test_source_path) + meths.set_var('__home', paths.test_source_path) nvim_command('let $HOME = __home') nvim_command('unlet __home') nvim_command('edit ~/README.md') @@ -188,12 +187,12 @@ describe('ShaDa support code', function() end) it('correctly ignores case with shada-r option', function() - local pwd = nvim('call_function', 'getcwd', {}) + local pwd = funcs.getcwd() local relfname = 'абв/test' local fname = pwd .. '/' .. relfname - nvim('set_var', '__fname', fname) + meths.set_var('__fname', fname) nvim_command('silent! edit `=__fname`') - nvim('call_function', 'setline', {1, {'a', 'b', 'c', 'd'}}) + funcs.setline(1, {'a', 'b', 'c', 'd'}) nvim_command('normal! GmAggmaAabc') nvim_command('undo') nvim_command('set shada+=%') diff --git a/test/functional/shada/variables_spec.lua b/test/functional/shada/variables_spec.lua index c2fa25bcb1..6225971e5f 100644 --- a/test/functional/shada/variables_spec.lua +++ b/test/functional/shada/variables_spec.lua @@ -1,7 +1,7 @@ -- ShaDa variables saving/reading support local helpers = require('test.functional.helpers') -local nvim, nvim_command, nvim_eval, eq = - helpers.nvim, helpers.command, helpers.eval, helpers.eq +local meths, funcs, nvim_command, eq = + helpers.meths, helpers.funcs, helpers.command, helpers.eq local shada_helpers = require('test.functional.shada.helpers') local reset, set_additional_cmd, clear = @@ -13,13 +13,13 @@ describe('ShaDa support code', function() after_each(clear) it('is able to dump and read back string variable', function() - nvim('set_var', 'STRVAR', 'foo') + meths.set_var('STRVAR', 'foo') nvim_command('set shada+=!') nvim_command('wshada') reset() nvim_command('set shada+=!') nvim_command('rshada') - eq('foo', nvim('get_var', 'STRVAR')) + eq('foo', meths.get_var('STRVAR')) end) local autotest = function(tname, varname, varval) @@ -27,12 +27,12 @@ describe('ShaDa support code', function() function() set_additional_cmd('set shada+=!') reset() - nvim('set_var', varname, varval) + meths.set_var(varname, varval) -- Exit during `reset` is not a regular exit: it does not write shada -- automatically nvim_command('qall') reset() - eq(varval, nvim('get_var', varname)) + eq(varval, meths.get_var(varname)) end) end @@ -43,43 +43,43 @@ describe('ShaDa support code', function() autotest('list', 'LSTVAR', {{a=10}, {b=10.5}, {c='str'}}) it('does not read back variables without `!` in &shada', function() - nvim('set_var', 'STRVAR', 'foo') + meths.set_var('STRVAR', 'foo') nvim_command('set shada+=!') nvim_command('wshada') set_additional_cmd('set shada-=!') reset() nvim_command('rshada') - eq(0, nvim_eval('exists("g:STRVAR")')) + eq(0, funcs.exists('g:STRVAR')) end) it('does not dump variables without `!` in &shada', function() nvim_command('set shada-=!') - nvim('set_var', 'STRVAR', 'foo') + meths.set_var('STRVAR', 'foo') nvim_command('wshada') reset() nvim_command('set shada+=!') nvim_command('rshada') - eq(0, nvim_eval('exists("g:STRVAR")')) + eq(0, funcs.exists('g:STRVAR')) end) it('does not dump session variables', function() nvim_command('set shada+=!') - nvim('set_var', 'StrVar', 'foo') + meths.set_var('StrVar', 'foo') nvim_command('wshada') reset() nvim_command('set shada+=!') nvim_command('rshada') - eq(0, nvim_eval('exists("g:StrVar")')) + eq(0, funcs.exists('g:StrVar')) end) it('does not dump regular variables', function() nvim_command('set shada+=!') - nvim('set_var', 'str_var', 'foo') + meths.set_var('str_var', 'foo') nvim_command('wshada') reset() nvim_command('set shada+=!') nvim_command('rshada') - eq(0, nvim_eval('exists("g:str_var")')) + eq(0, funcs.exists('g:str_var')) end) it('dumps and loads variables correctly when &encoding is not UTF-8', @@ -87,18 +87,18 @@ describe('ShaDa support code', function() set_additional_cmd('set encoding=latin1') reset() -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1 - nvim('set_var', 'STRVAR', '\171') - nvim('set_var', 'LSTVAR', {'\171'}) - nvim('set_var', 'DCTVAR', {['\171']='\171'}) - nvim('set_var', 'NESTEDVAR', {['\171']={{'\171'}, {['\171']='\171'}, - {a='Test'}}}) + meths.set_var('STRVAR', '\171') + meths.set_var('LSTVAR', {'\171'}) + meths.set_var('DCTVAR', {['\171']='\171'}) + meths.set_var('NESTEDVAR', {['\171']={{'\171'}, {['\171']='\171'}, + {a='Test'}}}) nvim_command('qall') reset() - eq('\171', nvim('get_var', 'STRVAR')) - eq({'\171'}, nvim('get_var', 'LSTVAR')) - eq({['\171']='\171'}, nvim('get_var', 'DCTVAR')) + eq('\171', meths.get_var('STRVAR')) + eq({'\171'}, meths.get_var('LSTVAR')) + eq({['\171']='\171'}, meths.get_var('DCTVAR')) eq({['\171']={{'\171'}, {['\171']='\171'}, {a='Test'}}}, - nvim('get_var', 'NESTEDVAR')) + meths.get_var('NESTEDVAR')) end) it('dumps and loads variables correctly when &encoding /= UTF-8 when dumping', @@ -106,34 +106,34 @@ describe('ShaDa support code', function() set_additional_cmd('set encoding=latin1') reset() -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1 - nvim('set_var', 'STRVAR', '\171') - nvim('set_var', 'LSTVAR', {'\171'}) - nvim('set_var', 'DCTVAR', {['\171']='\171'}) - nvim('set_var', 'NESTEDVAR', {['\171']={{'\171'}, {['\171']='\171'}, - {a='Test'}}}) + meths.set_var('STRVAR', '\171') + meths.set_var('LSTVAR', {'\171'}) + meths.set_var('DCTVAR', {['\171']='\171'}) + meths.set_var('NESTEDVAR', {['\171']={{'\171'}, {['\171']='\171'}, + {a='Test'}}}) set_additional_cmd('') nvim_command('qall') reset() - eq('«', nvim('get_var', 'STRVAR')) - eq({'«'}, nvim('get_var', 'LSTVAR')) - eq({['«']='«'}, nvim('get_var', 'DCTVAR')) - eq({['«']={{'«'}, {['«']='«'}, {a='Test'}}}, nvim('get_var', 'NESTEDVAR')) + eq('«', meths.get_var('STRVAR')) + eq({'«'}, meths.get_var('LSTVAR')) + eq({['«']='«'}, meths.get_var('DCTVAR')) + eq({['«']={{'«'}, {['«']='«'}, {a='Test'}}}, meths.get_var('NESTEDVAR')) end) it('dumps and loads variables correctly when &encoding /= UTF-8 when loading', function() -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1 - nvim('set_var', 'STRVAR', '«') - nvim('set_var', 'LSTVAR', {'«'}) - nvim('set_var', 'DCTVAR', {['«']='«'}) - nvim('set_var', 'NESTEDVAR', {['«']={{'«'}, {['«']='«'}, {a='Test'}}}) + meths.set_var('STRVAR', '«') + meths.set_var('LSTVAR', {'«'}) + meths.set_var('DCTVAR', {['«']='«'}) + meths.set_var('NESTEDVAR', {['«']={{'«'}, {['«']='«'}, {a='Test'}}}) set_additional_cmd('set encoding=latin1') nvim_command('qall') reset() - eq('\171', nvim('get_var', 'STRVAR')) - eq({'\171'}, nvim('get_var', 'LSTVAR')) - eq({['\171']='\171'}, nvim('get_var', 'DCTVAR')) + eq('\171', meths.get_var('STRVAR')) + eq({'\171'}, meths.get_var('LSTVAR')) + eq({['\171']='\171'}, meths.get_var('DCTVAR')) eq({['\171']={{'\171'}, {['\171']='\171'}, {a='Test'}}}, - nvim('get_var', 'NESTEDVAR')) + meths.get_var('NESTEDVAR')) end) end) -- cgit From 19785a01988c501760f7abc6b5da7010aa9cefc4 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 27 Sep 2015 22:23:48 +0300 Subject: ex_getln: Fix memory leak when overwriting history item --- src/nvim/ex_getln.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 7998d7e823..24e31b1ed7 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -4298,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) @@ -4305,6 +4306,7 @@ in_history ( history[type][last_i] = history[type][i]; last_i = i; } + list_unref(list); history[type][i].hisnum = ++hisnum[type]; history[type][i].hisstr = str; history[type][i].timestamp = os_time(); -- cgit From 937f6584d7210d6d342626e59b14d27794d3314f Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 3 Oct 2015 22:14:41 +0300 Subject: mark: Do not do unnecessary initialization --- src/nvim/mark.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/nvim/mark.c b/src/nvim/mark.c index bf6ef33ae0..dd49b311d3 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -553,14 +553,6 @@ int check_mark(pos_T *pos) */ void clrallmarks(buf_T *buf) { - static bool initialized = false; - - if (!initialized) { - // first call ever: initialize - memset(&(namedfm[0]), 0, sizeof(namedfm)); - initialized = true; - } - 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; -- cgit From 3a4a9418853338c3a0e00358b411186b5723184a Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 6 Oct 2015 02:24:16 +0300 Subject: shada: Fix memory leak and double free when setting both &vi and &sd --- src/nvim/option.c | 11 +++++++++++ test/functional/shada/shada_spec.lua | 27 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/nvim/option.c b/src/nvim/option.c index 653b217485..cbb22a0546 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -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]), @@ -2443,6 +2447,13 @@ did_set_string_option ( errmsg = e_invarg; /* '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) { diff --git a/test/functional/shada/shada_spec.lua b/test/functional/shada/shada_spec.lua index 7a30d3b87d..2bc855a239 100644 --- a/test/functional/shada/shada_spec.lua +++ b/test/functional/shada/shada_spec.lua @@ -202,4 +202,31 @@ describe('ShaDa support code', function() nvim_command('wshada! ' .. shada_fname) eq({}, find_file(fname)) end) + + it('is able to set &shada after &viminfo', function() + meths.set_option('viminfo', '\'10') + eq('\'10', meths.get_option('viminfo')) + eq('\'10', meths.get_option('shada')) + meths.set_option('shada', '') + eq('', meths.get_option('viminfo')) + eq('', meths.get_option('shada')) + end) + + it('is able to set all& after setting &shada', function() + meths.set_option('shada', '\'10') + eq('\'10', meths.get_option('viminfo')) + eq('\'10', meths.get_option('shada')) + nvim_command('set all&') + eq('!,\'100,<50,s10,h', meths.get_option('viminfo')) + eq('!,\'100,<50,s10,h', meths.get_option('shada')) + end) + + it('is able to set &shada after &viminfo using :set', function() + nvim_command('set viminfo=\'10') + eq('\'10', meths.get_option('viminfo')) + eq('\'10', meths.get_option('shada')) + nvim_command('set shada=') + eq('', meths.get_option('viminfo')) + eq('', meths.get_option('shada')) + end) end) -- cgit From 909d79e600625c3a899a10785c20f0822b0d8cc4 Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 8 Oct 2015 21:06:00 +0300 Subject: shada: Do not use msgpack_rpc functions for converting data to msgpack Should protect against #3431 --- src/nvim/shada.c | 47 ++++++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 44e2402fec..523f8db6f0 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1636,8 +1636,15 @@ static char *shada_filename(const char *file) } while (0) #define PACK_STRING(s) \ do { \ - msgpack_pack_str(spacker, s.size); \ - msgpack_pack_str_body(spacker, s.data, s.size); \ + 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 @@ -1707,8 +1714,7 @@ static bool shada_pack_entry(msgpack_packer *const packer, : entry.data.history_item.additional_elements->lv_len); msgpack_pack_array(spacker, arr_size); msgpack_pack_uint8(spacker, entry.data.history_item.histtype); - msgpack_rpc_from_string(cstr_as_string(entry.data.history_item.string), - spacker); + 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); } @@ -1721,8 +1727,7 @@ static bool shada_pack_entry(msgpack_packer *const packer, ? 0 : entry.data.global_var.additional_elements->lv_len); msgpack_pack_array(spacker, arr_size); - msgpack_rpc_from_string(cstr_as_string(entry.data.global_var.name), - spacker); + 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; } @@ -1735,8 +1740,7 @@ static bool shada_pack_entry(msgpack_packer *const packer, ? 0 : entry.data.sub_string.additional_elements->lv_len); msgpack_pack_array(spacker, arr_size); - msgpack_rpc_from_string(cstr_as_string(entry.data.sub_string.sub), - spacker); + PACK_BIN(cstr_as_string(entry.data.sub_string.sub)); DUMP_ADDITIONAL_ELEMENTS(entry.data.sub_string.additional_elements); break; } @@ -1758,8 +1762,7 @@ static bool shada_pack_entry(msgpack_packer *const packer, : 0)); msgpack_pack_map(spacker, map_size); PACK_STATIC_STR(SEARCH_KEY_PAT); - msgpack_rpc_from_string(cstr_as_string(entry.data.search_pattern.pat), - spacker); + PACK_BIN(cstr_as_string(entry.data.search_pattern.pat)); #define PACK_BOOL(entry, name, attr) \ do { \ if (!CHECK_DEFAULT(entry, search_pattern.attr)) { \ @@ -1802,8 +1805,7 @@ static bool shada_pack_entry(msgpack_packer *const packer, : entry.data.filemark.additional_data->dv_hashtab.ht_used)); msgpack_pack_map(spacker, map_size); PACK_STATIC_STR(KEY_FILE); - msgpack_rpc_from_string(cstr_as_string(entry.data.filemark.fname), - spacker); + 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); @@ -1835,8 +1837,7 @@ static bool shada_pack_entry(msgpack_packer *const packer, 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++) { - msgpack_rpc_from_string(cstr_as_string(entry.data.reg.contents[i]), - spacker); + PACK_BIN(cstr_as_string(entry.data.reg.contents[i])); } PACK_STATIC_STR(KEY_NAME_CHAR); msgpack_pack_char(spacker, entry.data.reg.name); @@ -1868,8 +1869,7 @@ static bool shada_pack_entry(msgpack_packer *const packer, ->dv_hashtab.ht_used))); msgpack_pack_map(spacker, map_size); PACK_STATIC_STR(KEY_FILE); - msgpack_rpc_from_string( - cstr_as_string(entry.data.buffer_list.buffers[i].fname), spacker); + 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( @@ -1888,7 +1888,20 @@ static bool shada_pack_entry(msgpack_packer *const packer, 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); - msgpack_rpc_from_object(entry.data.header.items[i].value, spacker); + 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; } -- cgit From db6cba7d5759e02379005702c7a9d760137f4389 Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 8 Oct 2015 21:11:38 +0300 Subject: documentation: Disambiguate ShaDa abbreviation --- runtime/doc/starting.txt | 4 +++- runtime/doc/usr_21.txt | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index 0712888284..84bd70db62 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -897,9 +897,11 @@ To automatically save and restore views for *.c files: > ============================================================================== 8. The ShaDa file *shada* *shada-file* + If you exit Vim and later start it again, you would normally lose a lot of information. The ShaDa file can be used to remember that information, which -enables you to continue where you left off. +enables you to continue where you left off. Its name is the abbreviation of +SHAred DAta because it is used for sharing data between Neovim sessions. This is introduced in section |21.3| of the user manual. diff --git a/runtime/doc/usr_21.txt b/runtime/doc/usr_21.txt index 96797a745c..8bc208dc30 100644 --- a/runtime/doc/usr_21.txt +++ b/runtime/doc/usr_21.txt @@ -84,7 +84,8 @@ After editing for a while you will have text in registers, marks in various files, a command line history filled with carefully crafted commands. When you exit Vim all of this is lost. But you can get it back! -The ShaDa file is designed to store status information: +The ShaDa (abbreviation of SHAred DAta) file is designed to store status +information: Command-line and Search pattern history Text in registers -- cgit