patch 7.4.1903
Problem: When writing viminfo merging current history with history in
viminfo may drop recent history entries.
Solution: Add new format for viminfo lines, use it for history entries. Use
a timestamp for ordering the entries. Add test_settime().
Add the viminfo version. Does not do merging on timestamp yet.
diff --git a/src/ex_cmds.c b/src/ex_cmds.c
index 12764b4..8bb24c3 100644
--- a/src/ex_cmds.c
+++ b/src/ex_cmds.c
@@ -1750,9 +1750,14 @@
#if defined(FEAT_VIMINFO) || defined(PROTO)
static int no_viminfo(void);
+static int read_viminfo_barline(vir_T *virp, int got_encoding, int writing);
+static void write_viminfo_version(FILE *fp_out);
static void write_viminfo_barlines(vir_T *virp, FILE *fp_out);
static int viminfo_errcnt;
+#define VIMINFO_VERSION 2
+#define VIMINFO_VERSION_WITH_HISTORY 2
+
static int
no_viminfo(void)
{
@@ -2156,6 +2161,7 @@
vir.vir_conv.vc_type = CONV_NONE;
#endif
ga_init2(&vir.vir_barlines, (int)sizeof(char_u *), 100);
+ vir.vir_version = -1;
if (fp_in != NULL)
{
@@ -2177,6 +2183,7 @@
fprintf(fp_out, _("# This viminfo file was generated by Vim %s.\n"),
VIM_VERSION_MEDIUM);
fputs(_("# You may edit it if you're careful!\n\n"), fp_out);
+ write_viminfo_version(fp_out);
#ifdef FEAT_MBYTE
fputs(_("# Value of 'encoding' when this file was written\n"), fp_out);
fprintf(fp_out, "*encoding=%s\n\n", p_enc);
@@ -2220,6 +2227,7 @@
{
int eof;
buf_T *buf;
+ int got_encoding = FALSE;
#ifdef FEAT_CMDHIST
prepare_viminfo_history(forceit ? 9999 : 0, writing);
@@ -2240,12 +2248,11 @@
case '#':
eof = viminfo_readline(virp);
break;
- case '|': /* copy line (for future use) */
- if (writing)
- ga_add_string(&virp->vir_barlines, virp->vir_line);
- eof = viminfo_readline(virp);
+ case '|':
+ eof = read_viminfo_barline(virp, got_encoding, writing);
break;
case '*': /* "*encoding=value" */
+ got_encoding = TRUE;
eof = viminfo_encoding(virp);
break;
case '!': /* global variable */
@@ -2274,10 +2281,13 @@
case '=':
case '@':
#ifdef FEAT_CMDHIST
- eof = read_viminfo_history(virp, writing);
-#else
- eof = viminfo_readline(virp);
+ /* When history is in bar lines skip the old style history
+ * lines. */
+ if (virp->vir_version < VIMINFO_VERSION_WITH_HISTORY)
+ eof = read_viminfo_history(virp, writing);
+ else
#endif
+ eof = viminfo_readline(virp);
break;
case '-':
case '\'':
@@ -2347,8 +2357,8 @@
}
/*
- * check string read from viminfo file
- * remove '\n' at the end of the line
+ * 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'
*
@@ -2463,6 +2473,283 @@
putc('\n', fd);
}
+/*
+ * Write a string in quotes that barline_parse() can read back.
+ * Breaks the line in less than LSIZE pieces when needed.
+ * Returns remaining characters in the line.
+ */
+ int
+barline_writestring(FILE *fd, char_u *s, int remaining_start)
+{
+ char_u *p;
+ int remaining = remaining_start;
+ int len = 2;
+
+ /* Count the number of characters produced, including quotes. */
+ for (p = s; *p != NUL; ++p)
+ {
+ if (*p == NL)
+ len += 2;
+ else if (*p == '"' || *p == '\\')
+ len += 2;
+ else
+ ++len;
+ }
+ if (len > remaining)
+ {
+ fprintf(fd, ">%d\n|<", len);
+ remaining = LSIZE - 20;
+ }
+
+ putc('"', fd);
+ for (p = s; *p != NUL; ++p)
+ {
+ if (*p == NL)
+ {
+ putc('\\', fd);
+ putc('n', fd);
+ --remaining;
+ }
+ else if (*p == '"' || *p == '\\')
+ {
+ putc('\\', fd);
+ putc(*p, fd);
+ --remaining;
+ }
+ else
+ putc(*p, fd);
+ --remaining;
+
+ if (remaining < 3)
+ {
+ putc('\n', fd);
+ putc('|', fd);
+ putc('<', fd);
+ /* Leave enough space for another continuation. */
+ remaining = LSIZE - 20;
+ }
+ }
+ putc('"', fd);
+ return remaining;
+}
+
+/*
+ * Parse a viminfo line starting with '|'.
+ * Put each decoded value in "values" and return the number of values found.
+ */
+ static int
+barline_parse(vir_T *virp, char_u *text, bval_T *values)
+{
+ char_u *p = text;
+ char_u *nextp = NULL;
+ char_u *buf = NULL;;
+ int count = 0;
+ int i;
+ int allocated = FALSE;
+
+ while (*p == ',')
+ {
+ if (count == BVAL_MAX)
+ {
+ EMSG2(e_intern2, "barline_parse()");
+ break;
+ }
+ ++p;
+
+ if (*p == '>')
+ {
+ /* Need to read a continuation line. Need to put strings in
+ * allocated memory, because virp->vir_line is overwritten. */
+ if (!allocated)
+ {
+ for (i = 0; i < count; ++i)
+ if (values[i].bv_type == BVAL_STRING)
+ {
+ values[i].bv_string = vim_strnsave(
+ values[i].bv_string, values[i].bv_len);
+ values[i].bv_allocated = TRUE;
+ }
+ allocated = TRUE;
+ }
+
+ if (vim_isdigit(p[1]))
+ {
+ int len;
+ int todo;
+ int n;
+
+ /* String value was split into lines that are each shorter
+ * than LSIZE:
+ * |{bartype},>{length of "{text}{text2}"}
+ * |<"{text1}
+ * |<{text2}",{value}
+ */
+ ++p;
+ len = getdigits(&p);
+ buf = alloc(len + 1);
+ p = buf;
+ for (todo = len; todo > 0; todo -= n)
+ {
+ if (viminfo_readline(virp) || virp->vir_line[0] != '|'
+ || virp->vir_line[1] != '<')
+ /* file was truncated or garbled */
+ return 0;
+ /* Get length of text, excluding |< and NL chars. */
+ n = STRLEN(virp->vir_line);
+ while (n > 0 && (virp->vir_line[n - 1] == NL
+ || virp->vir_line[n - 1] == CAR))
+ --n;
+ n -= 2;
+ if (n > todo)
+ {
+ /* more values follow after the string */
+ nextp = virp->vir_line + 2 + todo;
+ n = todo;
+ }
+ mch_memmove(p, virp->vir_line + 2, n);
+ p += n;
+ }
+ *p = NUL;
+ p = buf;
+ }
+ else
+ {
+ /* Line ending in ">" continues in the next line:
+ * |{bartype},{lots of values},>
+ * |<{value},{value}
+ */
+ if (viminfo_readline(virp) || virp->vir_line[0] != '|'
+ || virp->vir_line[1] != '<')
+ /* file was truncated or garbled */
+ return 0;
+ p = virp->vir_line + 2;
+ }
+ }
+
+ if (isdigit(*p))
+ {
+ values[count].bv_type = BVAL_NR;
+ values[count].bv_nr = getdigits(&p);
+ ++count;
+ }
+ else if (*p == '"')
+ {
+ int len = 0;
+ char_u *s = p;
+
+ /* Unescape special characters in-place. */
+ ++p;
+ while (*p != '"')
+ {
+ if (*p == NL || *p == NUL)
+ return count; /* syntax error, drop the value */
+ if (*p == '\\')
+ {
+ ++p;
+ if (*p == 'n')
+ s[len++] = '\n';
+ else
+ s[len++] = *p;
+ ++p;
+ }
+ else
+ s[len++] = *p++;
+ }
+ s[len] = NUL;
+
+ if (s != buf && allocated)
+ s = vim_strsave(s);
+ values[count].bv_string = s;
+ values[count].bv_type = BVAL_STRING;
+ values[count].bv_len = len;
+ values[count].bv_allocated = allocated;
+ ++count;
+ if (nextp != NULL)
+ {
+ /* values following a long string */
+ p = nextp;
+ nextp = NULL;
+ }
+ }
+ else if (*p == ',')
+ {
+ values[count].bv_type = BVAL_EMPTY;
+ ++count;
+ }
+ else
+ break;
+ }
+
+ return count;
+}
+
+ static int
+read_viminfo_barline(vir_T *virp, int got_encoding, int writing)
+{
+ char_u *p = virp->vir_line + 1;
+ int bartype;
+ bval_T values[BVAL_MAX];
+ int count = 0;
+ int i;
+
+ /* The format is: |{bartype},{value},...
+ * For a very long string:
+ * |{bartype},>{length of "{text}{text2}"}
+ * |<{text1}
+ * |<{text2},{value}
+ * For a long line not using a string
+ * |{bartype},{lots of values},>
+ * |<{value},{value}
+ */
+ if (*p == '<')
+ {
+ /* Continuation line of an unrecognized item. */
+ if (writing)
+ ga_add_string(&virp->vir_barlines, virp->vir_line);
+ }
+ else
+ {
+ bartype = getdigits(&p);
+ switch (bartype)
+ {
+ case BARTYPE_VERSION:
+ /* Only use the version when it comes before the encoding.
+ * If it comes later it was copied by a Vim version that
+ * doesn't understand the version. */
+ if (!got_encoding)
+ {
+ count = barline_parse(virp, p, values);
+ if (count > 0 && values[0].bv_type == BVAL_NR)
+ virp->vir_version = values[0].bv_nr;
+ }
+ break;
+
+ case BARTYPE_HISTORY:
+ count = barline_parse(virp, p, values);
+ handle_viminfo_history(values, count, writing);
+ break;
+
+ default:
+ /* copy unrecognized line (for future use) */
+ if (writing)
+ ga_add_string(&virp->vir_barlines, virp->vir_line);
+ }
+ }
+
+ for (i = 0; i < count; ++i)
+ if (values[i].bv_type == BVAL_STRING && values[i].bv_allocated)
+ vim_free(values[i].bv_string);
+
+ return viminfo_readline(virp);
+}
+
+ static void
+write_viminfo_version(FILE *fp_out)
+{
+ fprintf(fp_out, "# Viminfo version\n|%d,%d\n\n",
+ BARTYPE_VERSION, VIMINFO_VERSION);
+}
+
static void
write_viminfo_barlines(vir_T *virp, FILE *fp_out)
{