changeset 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 a744b63c4ed0
children 3657b2df1ca2
files src/eval.c src/ex_cmds.c src/ex_getln.c src/globals.h src/proto/ex_cmds.pro src/proto/ex_getln.pro src/structs.h src/testdir/test_viminfo.vim src/version.c
diffstat 9 files changed, 538 insertions(+), 44 deletions(-) [+]
line wrap: on
line diff
--- a/src/eval.c
+++ b/src/eval.c
@@ -820,6 +820,7 @@ static void f_test_null_job(typval_T *ar
 static void f_test_null_list(typval_T *argvars, typval_T *rettv);
 static void f_test_null_partial(typval_T *argvars, typval_T *rettv);
 static void f_test_null_string(typval_T *argvars, typval_T *rettv);
+static void f_test_settime(typval_T *argvars, typval_T *rettv);
 #ifdef FEAT_FLOAT
 static void f_tan(typval_T *argvars, typval_T *rettv);
 static void f_tanh(typval_T *argvars, typval_T *rettv);
@@ -8809,13 +8810,14 @@ static struct fst
 #ifdef FEAT_JOB_CHANNEL
     {"test_null_channel", 0, 0, f_test_null_channel},
 #endif
-    {"test_null_dict", 0, 0, f_test_null_dict},
+    {"test_null_dict",	0, 0, f_test_null_dict},
 #ifdef FEAT_JOB_CHANNEL
-    {"test_null_job", 0, 0, f_test_null_job},
-#endif
-    {"test_null_list", 0, 0, f_test_null_list},
+    {"test_null_job",	0, 0, f_test_null_job},
+#endif
+    {"test_null_list",	0, 0, f_test_null_list},
     {"test_null_partial", 0, 0, f_test_null_partial},
     {"test_null_string", 0, 0, f_test_null_string},
+    {"test_settime",	1, 1, f_test_settime},
 #ifdef FEAT_TIMERS
     {"timer_start",	2, 3, f_timer_start},
     {"timer_stop",	1, 1, f_timer_stop},
@@ -20849,7 +20851,7 @@ f_test_garbagecollect_now(typval_T *argv
 
 #ifdef FEAT_JOB_CHANNEL
     static void
-f_test_null_channel(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
+f_test_null_channel(typval_T *argvars UNUSED, typval_T *rettv)
 {
     rettv->v_type = VAR_CHANNEL;
     rettv->vval.v_channel = NULL;
@@ -20857,7 +20859,7 @@ f_test_null_channel(typval_T *argvars UN
 #endif
 
     static void
-f_test_null_dict(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
+f_test_null_dict(typval_T *argvars UNUSED, typval_T *rettv)
 {
     rettv->v_type = VAR_DICT;
     rettv->vval.v_dict = NULL;
@@ -20865,7 +20867,7 @@ f_test_null_dict(typval_T *argvars UNUSE
 
 #ifdef FEAT_JOB_CHANNEL
     static void
-f_test_null_job(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
+f_test_null_job(typval_T *argvars UNUSED, typval_T *rettv)
 {
     rettv->v_type = VAR_JOB;
     rettv->vval.v_job = NULL;
@@ -20873,26 +20875,32 @@ f_test_null_job(typval_T *argvars UNUSED
 #endif
 
     static void
-f_test_null_list(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
+f_test_null_list(typval_T *argvars UNUSED, typval_T *rettv)
 {
     rettv->v_type = VAR_LIST;
     rettv->vval.v_list = NULL;
 }
 
     static void
-f_test_null_partial(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
+f_test_null_partial(typval_T *argvars UNUSED, typval_T *rettv)
 {
     rettv->v_type = VAR_PARTIAL;
     rettv->vval.v_partial = NULL;
 }
 
     static void
-f_test_null_string(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
+f_test_null_string(typval_T *argvars UNUSED, typval_T *rettv)
 {
     rettv->v_type = VAR_STRING;
     rettv->vval.v_string = NULL;
 }
 
+    static void
+f_test_settime(typval_T *argvars, typval_T *rettv UNUSED)
+{
+    time_for_testing = (time_t)get_tv_number(&argvars[0]);
+}
+
 #if defined(FEAT_JOB_CHANNEL) || defined(FEAT_TIMERS) || defined(PROTO)
 /*
  * Get a callback from "arg".  It can be a Funcref or a function name.
--- 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)
 {
--- a/src/ex_getln.c
+++ b/src/ex_getln.c
@@ -58,6 +58,7 @@ 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 */
+    time_t	time_set;	/* when it was typed, zero if unknown */
 } histentry_T;
 
 static histentry_T *(history[HIST_COUNT]) = {NULL, NULL, NULL, NULL, NULL};
@@ -5407,6 +5408,20 @@ static char *(history_names[]) =
     NULL
 };
 
+/*
+ * Return the current time in seconds.  Calls time(), unless test_settime()
+ * was used.
+ */
+    static time_t
+vim_time(void)
+{
+#ifdef FEAT_EVAL
+    return time_for_testing == 0 ? time(NULL) : time_for_testing;
+#else
+    return time(NULL);
+#endif
+}
+
 #if defined(FEAT_CMDL_COMPL) || defined(PROTO)
 /*
  * Function given to ExpandGeneric() to obtain the possible first
@@ -5576,6 +5591,7 @@ in_history(
 	history[type][i].hisnum = ++hisnum[type];
 	history[type][i].viminfo = FALSE;
 	history[type][i].hisstr = str;
+	history[type][i].time_set = vim_time();
 	return TRUE;
     }
     return FALSE;
@@ -5663,6 +5679,7 @@ add_to_history(
 
 	hisptr->hisnum = ++hisnum[histype];
 	hisptr->viminfo = FALSE;
+	hisptr->time_set = vim_time();
 	if (histype == HIST_SEARCH && in_map)
 	    last_maptick = maptick;
     }
@@ -6131,9 +6148,10 @@ 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 histentry_T *viminfo_history[HIST_COUNT] =
+					       {NULL, NULL, NULL, NULL, NULL};
+static int	viminfo_hisidx[HIST_COUNT] = {0, 0, 0, 0, 0};
+static int	viminfo_hislen[HIST_COUNT] = {0, 0, 0, 0, 0};
 static int	viminfo_add_at_front = FALSE;
 
 static int	hist_type2char(int type, int use_question);
@@ -6191,8 +6209,8 @@ prepare_viminfo_history(int asklen, int 
 	if (len <= 0)
 	    viminfo_history[type] = NULL;
 	else
-	    viminfo_history[type] =
-		   (char_u **)lalloc((long_u)(len * sizeof(char_u *)), FALSE);
+	    viminfo_history[type] = (histentry_T *)lalloc(
+				  (long_u)(len * sizeof(histentry_T)), FALSE);
 	if (viminfo_history[type] == NULL)
 	    len = 0;
 	viminfo_hislen[type] = len;
@@ -6242,7 +6260,9 @@ read_viminfo_history(vir_T *virp, int wr
 			mch_memmove(p, val, (size_t)len + 1);
 			p[len + 1] = NUL;
 		    }
-		    viminfo_history[type][viminfo_hisidx[type]++] = p;
+		    viminfo_history[type][viminfo_hisidx[type]].hisstr = p;
+		    viminfo_history[type][viminfo_hisidx[type]].time_set = 0;
+		    viminfo_hisidx[type]++;
 		}
 	    }
 	}
@@ -6252,6 +6272,81 @@ read_viminfo_history(vir_T *virp, int wr
 }
 
 /*
+ * Accept a new style history line from the viminfo, store it in the history
+ * array when it's new.
+ */
+    void
+handle_viminfo_history(
+	bval_T	*values,
+	int	count,
+	int	writing)
+{
+    int		type;
+    long_u	len;
+    char_u	*val;
+    char_u	*p;
+
+    /* Check the format:
+     * |{bartype},{histtype},{timestamp},{separator},"text" */
+    if (count < 4
+	    || values[0].bv_type != BVAL_NR
+	    || values[1].bv_type != BVAL_NR
+	    || (values[2].bv_type != BVAL_NR && values[2].bv_type != BVAL_EMPTY)
+	    || values[3].bv_type != BVAL_STRING)
+	return;
+
+    type = values[0].bv_nr;
+    if (type >= HIST_COUNT)
+	return;
+    if (viminfo_hisidx[type] < viminfo_hislen[type])
+    {
+	val = values[3].bv_string;
+	if (val != NULL && *val != NUL)
+	{
+	    int sep = type == HIST_SEARCH && values[2].bv_type == BVAL_NR
+						      ? values[2].bv_nr : NUL;
+	    int idx;
+	    int overwrite = FALSE;
+
+	    if (!in_history(type, val, viminfo_add_at_front, sep, writing))
+	    {
+		/* If lines were written by an older Vim we need to avoid
+		 * getting duplicates. See if the entry already exists. */
+		for (idx = 0; idx < viminfo_hisidx[type]; ++idx)
+		{
+		    p = viminfo_history[type][idx].hisstr;
+		    if (STRCMP(val, p) == 0
+			  && (type != HIST_SEARCH || sep == p[STRLEN(p) + 1]))
+		    {
+			overwrite = TRUE;
+			break;
+		    }
+		}
+
+		if (!overwrite)
+		{
+		    /* Need to re-allocate to append the separator byte. */
+		    len = values[3].bv_len;
+		    p = lalloc(len + 2, TRUE);
+		}
+		if (p != NULL)
+		{
+		    viminfo_history[type][idx].time_set = values[1].bv_nr;
+		    if (!overwrite)
+		    {
+			mch_memmove(p, val, (size_t)len + 1);
+			/* Put the separator after the NUL. */
+			p[len + 1] = sep;
+			viminfo_history[type][idx].hisstr = p;
+			viminfo_hisidx[type]++;
+		    }
+		}
+	    }
+	}
+    }
+}
+
+/*
  * Finish reading history lines from viminfo.  Not used when writing viminfo.
  */
     void
@@ -6290,8 +6385,9 @@ finish_viminfo_history(void)
 	for (i = 0; i < viminfo_hisidx[type]; i++)
 	{
 	    vim_free(history[type][idx].hisstr);
-	    history[type][idx].hisstr = viminfo_history[type][i];
+	    history[type][idx].hisstr = viminfo_history[type][i].hisstr;
 	    history[type][idx].viminfo = TRUE;
+	    history[type][idx].time_set = viminfo_history[type][i].time_set;
 	    if (--idx < 0)
 		idx = hislen - 1;
 	}
@@ -6315,15 +6411,11 @@ finish_viminfo_history(void)
  * When "merge" is FALSE just write all history lines.  Used for ":wviminfo!".
  */
     void
-write_viminfo_history(
-    FILE    *fp,
-    int	    merge)
+write_viminfo_history(FILE *fp, int merge)
 {
     int	    i;
     int	    type;
     int	    num_saved;
-    char_u  *p;
-    int	    c;
     int     round;
 
     init_history();
@@ -6339,8 +6431,9 @@ write_viminfo_history(
 	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"));
+			    type == HIST_EXPR ? _("Expression") :
+			    type == HIST_INPUT ? _("Input Line") :
+					_("Debug Line"));
 	if (num_saved > hislen)
 	    num_saved = hislen;
 
@@ -6364,9 +6457,23 @@ write_viminfo_history(
 		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];
+		    char_u  *p;
+		    time_t  timestamp;
+		    int	    c = NUL;
+
+		    if (round == 1)
+		    {
+			p = history[type][i].hisstr;
+			timestamp = history[type][i].time_set;
+		    }
+		    else
+		    {
+			p = viminfo_history[type] == NULL ? NULL
+					    : viminfo_history[type][i].hisstr;
+			timestamp = viminfo_history[type] == NULL ? 0
+					  : viminfo_history[type][i].time_set;
+		    }
+
 		    if (p != NULL && (round == 2
 				       || !merge
 				       || !history[type][i].viminfo))
@@ -6381,6 +6488,21 @@ write_viminfo_history(
 			    putc(c == NUL ? ' ' : c, fp);
 			}
 			viminfo_writestring(fp, p);
+
+			{
+			    char    cbuf[NUMBUFLEN];
+
+			    /* New style history with a bar line. Format:
+			     * |{bartype},{histtype},{timestamp},{separator},"text" */
+			    if (c == NUL)
+				cbuf[0] = NUL;
+			    else
+				sprintf(cbuf, "%d", c);
+			    fprintf(fp, "|%d,%d,%ld,%s,", BARTYPE_HISTORY,
+						 type, (long)timestamp, cbuf);
+			    barline_writestring(fp, p, LSIZE - 20);
+			    putc('\n', fp);
+			}
 		    }
 		    if (round == 1)
 		    {
@@ -6400,7 +6522,7 @@ write_viminfo_history(
 	}
 	for (i = 0; i < viminfo_hisidx[type]; ++i)
 	    if (viminfo_history[type] != NULL)
-		vim_free(viminfo_history[type][i]);
+		vim_free(viminfo_history[type][i].hisstr);
 	vim_free(viminfo_history[type]);
 	viminfo_history[type] = NULL;
 	viminfo_hisidx[type] = 0;
--- a/src/globals.h
+++ b/src/globals.h
@@ -1639,6 +1639,10 @@ EXTERN int  in_free_unref_items INIT(= F
 EXTERN int  did_add_timer INIT(= FALSE);
 #endif
 
+#ifdef FEAT_EVAL
+EXTERN time_t time_for_testing INIT(= 0);
+#endif
+
 /*
  * Optional Farsi support.  Include it here, so EXTERN and INIT are defined.
  */
--- a/src/proto/ex_cmds.pro
+++ b/src/proto/ex_cmds.pro
@@ -16,6 +16,7 @@ void write_viminfo(char_u *file, int for
 int viminfo_readline(vir_T *virp);
 char_u *viminfo_readstring(vir_T *virp, int off, int convert);
 void viminfo_writestring(FILE *fd, char_u *p);
+int barline_writestring(FILE *fd, char_u *s, int remaining_start);
 void do_fixdel(exarg_T *eap);
 void print_line_no_prefix(linenr_T lnum, int use_number, int list);
 void print_line(linenr_T lnum, int use_number, int list);
--- a/src/proto/ex_getln.pro
+++ b/src/proto/ex_getln.pro
@@ -37,19 +37,20 @@ void init_history(void);
 int get_histtype(char_u *name);
 void add_to_history(int histype, char_u *new_entry, int in_map, int sep);
 int get_history_idx(int histype);
-char_u *get_cmdline_str(void);
-int get_cmdline_pos(void);
-int set_cmdline_pos(int pos);
-int get_cmdline_type(void);
 char_u *get_history_entry(int histype, int idx);
 int clr_history(int histype);
 int del_history_entry(int histype, char_u *str);
 int del_history_idx(int histype, int idx);
 void remove_key_from_history(void);
+char_u *get_cmdline_str(void);
+int get_cmdline_pos(void);
+int set_cmdline_pos(int pos);
+int get_cmdline_type(void);
 int get_list_range(char_u **str, int *num1, int *num2);
 void ex_history(exarg_T *eap);
 void prepare_viminfo_history(int asklen, int writing);
 int read_viminfo_history(vir_T *virp, int writing);
+void handle_viminfo_history(bval_T *values, int count, int writing);
 void finish_viminfo_history(void);
 void write_viminfo_history(FILE *fp, int merge);
 void cmd_pchar(int c, int offset);
--- a/src/structs.h
+++ b/src/structs.h
@@ -1014,6 +1014,7 @@ typedef struct
 #ifdef FEAT_MBYTE
     vimconv_T	vir_conv;	/* encoding conversion */
 #endif
+    int		vir_version;	/* viminfo version detected or -1 */
     garray_T	vir_barlines;	/* lines starting with | */
 } vir_T;
 
--- a/src/testdir/test_viminfo.vim
+++ b/src/testdir/test_viminfo.vim
@@ -1,6 +1,7 @@
 " Test for reading and writing .viminfo
 
 function Test_read_and_write()
+  call histdel(':')
   let lines = [
 	\ '# comment line',
 	\ '*encoding=utf-8',
@@ -18,14 +19,16 @@ function Test_read_and_write()
   for line in lines
     if line[0] == '|'
       if done == 0
+	call assert_equal('|1,2', line)
+      elseif done == 1
 	call assert_equal('|copied as-is', line)
-      elseif done == 1
+      elseif done == 2
 	call assert_equal('|and one more', line)
       endif
       let done += 1
     endif
   endfor
-  call assert_equal(2, done)
+  call assert_equal(3, done)
 
   call delete('Xviminfo')
 endfunc
@@ -48,3 +51,68 @@ func Test_global_vars()
   call delete('Xviminfo')
   set viminfo-=!
 endfunc
+
+func Test_cmdline_history()
+  call histdel(':')
+  call test_settime(11)
+  call histadd(':', "echo 'one'")
+  call test_settime(12)
+  " split into two lines
+  let long800 = repeat(" 'eight'", 100)
+  call histadd(':', "echo " . long800)
+  call test_settime(13)
+  " split into three lines
+  let long1400 = repeat(" 'fourteeeeen'", 100)
+  call histadd(':', "echo " . long1400)
+  wviminfo Xviminfo
+  let lines = readfile('Xviminfo')
+  let done_colon = 0
+  let done_bar = 0
+  let lnum = 0
+  while lnum < len(lines)
+    let line = lines[lnum] | let lnum += 1
+    if line[0] == ':'
+      if done_colon == 0
+	call assert_equal(":\x161408", line)
+	let line = lines[lnum] | let lnum += 1
+	call assert_equal('<echo ' . long1400, line)
+      elseif done_colon == 1
+	call assert_equal(":\x16808", line)
+	let line = lines[lnum] | let lnum += 1
+	call assert_equal("<echo " . long800, line)
+      elseif done_colon == 2
+	call assert_equal(":echo 'one'", line)
+      endif
+      let done_colon += 1
+    elseif line[0:4] == '|2,0,'
+      if done_bar == 0
+	call assert_equal("|2,0,13,,>1407", line)
+	let line = lines[lnum] | let lnum += 1
+	call assert_equal('|<"echo ' . long1400[0:484], line)
+	let line = lines[lnum] | let lnum += 1
+	call assert_equal('|<' . long1400[485:974], line)
+	let line = lines[lnum] | let lnum += 1
+	call assert_equal('|<' . long1400[975:] . '"', line)
+      elseif done_bar == 1
+	call assert_equal('|2,0,12,,>807', line)
+	let line = lines[lnum] | let lnum += 1
+	call assert_equal('|<"echo ' . long800[0:484], line)
+	let line = lines[lnum] | let lnum += 1
+	call assert_equal('|<' . long800[485:] . '"', line)
+      elseif done_bar == 2
+	call assert_equal("|2,0,11,,\"echo 'one'\"", line)
+      endif
+      let done_bar += 1
+    endif
+  endwhile
+  call assert_equal(3, done_colon)
+  call assert_equal(3, done_bar)
+
+  call histdel(':')
+  rviminfo Xviminfo
+  call assert_equal("echo " . long1400, histget(':', -1))
+  call assert_equal("echo " . long800, histget(':', -2))
+  call assert_equal("echo 'one'", histget(':', -3))
+
+  call delete('Xviminfo')
+endfunc
--- a/src/version.c
+++ b/src/version.c
@@ -754,6 +754,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1903,
+/**/
     1902,
 /**/
     1901,