diff src/ex_cmds.c @ 9240:636cfa97200e v7.4.1903

commit https://github.com/vim/vim/commit/45d2eeaad66939348893b9254171067b0457cd9d Author: Bram Moolenaar <Bram@vim.org> Date: Mon Jun 6 21:07:52 2016 +0200 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.
author Christian Brabandt <cb@256bit.org>
date Mon, 06 Jun 2016 21:15:07 +0200
parents 6e80397a592c
children 931bbf7b6ee3
line wrap: on
line diff
--- a/src/ex_cmds.c
+++ b/src/ex_cmds.c
@@ -1750,9 +1750,14 @@ append_redir(
 #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 @@ do_viminfo(FILE *fp_in, FILE *fp_out, in
     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 @@ do_viminfo(FILE *fp_in, FILE *fp_out, in
 	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 @@ read_viminfo_up_to_marks(
 {
     int		eof;
     buf_T	*buf;
+    int		got_encoding = FALSE;
 
 #ifdef FEAT_CMDHIST
     prepare_viminfo_history(forceit ? 9999 : 0, writing);
@@ -2240,12 +2248,11 @@ read_viminfo_up_to_marks(
 	    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 @@ read_viminfo_up_to_marks(
 	    case '=':
 	    case '@':
 #ifdef FEAT_CMDHIST
-		eof = read_viminfo_history(virp, writing);
-#else
-		eof = viminfo_readline(virp);
-#endif
+		/* 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 @@ viminfo_readline(vir_T *virp)
 }
 
 /*
- * 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 @@ viminfo_writestring(FILE *fd, char_u *p)
     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)
 {