summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2010-05-23 23:34:36 +0200
committerBram Moolenaar <Bram@vim.org>2010-05-23 23:34:36 +0200
commit55debbe38429b81c0ce6e8400aef36812eb151d7 (patch)
tree992320729b697015fb4b99e9f8645cffe2eeddd6 /src
parentc39125d7c45d17566665c06358501073ea9c4141 (diff)
downloadvim-git-55debbe38429b81c0ce6e8400aef36812eb151d7.tar.gz
Included patch for persistent undo. Lots of changes and added test.
Diffstat (limited to 'src')
-rw-r--r--src/buffer.c5
-rw-r--r--src/eval.c3
-rw-r--r--src/ex_cmds.h4
-rw-r--r--src/ex_docmd.c30
-rw-r--r--src/feature.h8
-rw-r--r--src/fileio.c71
-rw-r--r--src/memline.c37
-rw-r--r--src/option.c28
-rw-r--r--src/option.h2
-rw-r--r--src/os_mac.h2
-rw-r--r--src/proto/memline.pro1
-rw-r--r--src/proto/sha256.pro3
-rw-r--r--src/proto/spell.pro5
-rw-r--r--src/proto/undo.pro3
-rw-r--r--src/sha256.c27
-rw-r--r--src/spell.c15
-rw-r--r--src/structs.h9
-rw-r--r--src/testdir/Makefile2
-rw-r--r--src/testdir/test61.in47
-rw-r--r--src/testdir/test61.ok9
-rw-r--r--src/undo.c803
-rw-r--r--src/version.c5
-rw-r--r--src/vim.h3
23 files changed, 1066 insertions, 56 deletions
diff --git a/src/buffer.c b/src/buffer.c
index 531e4b9a2..5ecb63872 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -61,8 +61,9 @@ static void buf_delete_signs __ARGS((buf_T *buf));
#endif
/*
- * Open current buffer, that is: open the memfile and read the file into memory
- * return FAIL for failure, OK otherwise
+ * Open current buffer, that is: open the memfile and read the file into
+ * memory.
+ * Return FAIL for failure, OK otherwise.
*/
int
open_buffer(read_stdin, eap)
diff --git a/src/eval.c b/src/eval.c
index 446df8ed3..3c238fcbe 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -11869,6 +11869,9 @@ f_has(argvars, rettv)
"perl",
#endif
#endif
+#ifdef FEAT_PERSISTENT_UNDO
+ "persistent_undo",
+#endif
#ifdef FEAT_PYTHON
#ifndef DYNAMIC_PYTHON
"python",
diff --git a/src/ex_cmds.h b/src/ex_cmds.h
index 1ef885acd..f41e0f449 100644
--- a/src/ex_cmds.h
+++ b/src/ex_cmds.h
@@ -773,6 +773,8 @@ EX(CMD_rubydo, "rubydo", ex_rubydo,
RANGE|DFLALL|EXTRA|NEEDARG|CMDWIN),
EX(CMD_rubyfile, "rubyfile", ex_rubyfile,
RANGE|FILE1|NEEDARG|CMDWIN),
+EX(CMD_rundo, "rundo", ex_rundo,
+ NEEDARG|EXTRA|XFILE),
EX(CMD_rviminfo, "rviminfo", ex_viminfo,
BANG|FILE1|TRLBAR|CMDWIN),
EX(CMD_substitute, "substitute", do_sub,
@@ -1061,6 +1063,8 @@ EX(CMD_wqall, "wqall", do_wqall,
BANG|FILE1|ARGOPT|DFLALL|TRLBAR),
EX(CMD_wsverb, "wsverb", ex_wsverb,
EXTRA|NOTADR|NEEDARG),
+EX(CMD_wundo, "wundo", ex_wundo,
+ BANG|NEEDARG|EXTRA|XFILE),
EX(CMD_wviminfo, "wviminfo", ex_viminfo,
BANG|FILE1|TRLBAR|CMDWIN),
EX(CMD_xit, "xit", ex_exit,
diff --git a/src/ex_docmd.c b/src/ex_docmd.c
index ff39040cf..4097f1d8f 100644
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -243,6 +243,10 @@ static void ex_popup __ARGS((exarg_T *eap));
# define ex_spellinfo ex_ni
# define ex_spellrepall ex_ni
#endif
+#ifndef FEAT_PERSISTENT_UNDO
+# define ex_rundo ex_ni
+# define ex_wundo ex_ni
+#endif
#ifndef FEAT_MZSCHEME
# define ex_mzscheme ex_script_ni
# define ex_mzfile ex_ni
@@ -298,6 +302,10 @@ static void ex_join __ARGS((exarg_T *eap));
static void ex_at __ARGS((exarg_T *eap));
static void ex_bang __ARGS((exarg_T *eap));
static void ex_undo __ARGS((exarg_T *eap));
+#ifdef FEAT_PERSISTENT_UNDO
+static void ex_wundo __ARGS((exarg_T *eap));
+static void ex_rundo __ARGS((exarg_T *eap));
+#endif
static void ex_redo __ARGS((exarg_T *eap));
static void ex_later __ARGS((exarg_T *eap));
static void ex_redir __ARGS((exarg_T *eap));
@@ -8452,6 +8460,28 @@ ex_undo(eap)
u_undo(1);
}
+#ifdef FEAT_PERSISTENT_UNDO
+ void
+ex_wundo(eap)
+ exarg_T *eap;
+{
+ char_u hash[UNDO_HASH_SIZE];
+
+ u_compute_hash(hash);
+ u_write_undo(eap->arg, eap->forceit, curbuf, hash);
+}
+
+ void
+ex_rundo(eap)
+ exarg_T *eap;
+{
+ char_u hash[UNDO_HASH_SIZE];
+
+ u_compute_hash(hash);
+ u_read_undo(eap->arg, hash);
+}
+#endif
+
/*
* ":redo".
*/
diff --git a/src/feature.h b/src/feature.h
index 6b8f600ec..61fe3345b 100644
--- a/src/feature.h
+++ b/src/feature.h
@@ -1275,3 +1275,11 @@
|| defined(FEAT_BIG)
# define FEAT_AUTOCHDIR
#endif
+
+/*
+ * +persistent_undo 'undofile', 'undodir' options, :wundo and :rundo, and
+ * implementation.
+ */
+#ifdef FEAT_NORMAL
+# define FEAT_PERSISTENT_UNDO
+#endif
diff --git a/src/fileio.c b/src/fileio.c
index 7a697ee02..b7c86af7e 100644
--- a/src/fileio.c
+++ b/src/fileio.c
@@ -253,6 +253,10 @@ readfile(fname, sfname, from, lines_to_skip, lines_to_read, eap, flags)
char_u *cryptkey = NULL;
int did_ask_for_key = FALSE;
#endif
+#ifdef FEAT_PERSISTENT_UNDO
+ context_sha256_T sha_ctx;
+ int read_undo_file = FALSE;
+#endif
int split = 0; /* number of split lines */
#define UNKNOWN 0x0fffffff /* file size is unknown */
linenr_T linecnt;
@@ -1178,6 +1182,12 @@ retry:
#ifdef FEAT_MBYTE
conv_restlen = 0;
#endif
+#ifdef FEAT_PERSISTENT_UNDO
+ read_undo_file = (newfile && curbuf->b_ffname != NULL && curbuf->b_p_udf
+ && !filtering && !read_stdin && !read_buffer);
+ if (read_undo_file)
+ sha256_start(&sha_ctx);
+#endif
}
while (!error && !got_int)
@@ -2133,6 +2143,10 @@ rewind_retry:
error = TRUE;
break;
}
+#ifdef FEAT_PERSISTENT_UNDO
+ if (read_undo_file)
+ sha256_update(&sha_ctx, line_start, len);
+#endif
++lnum;
if (--read_count == 0)
{
@@ -2197,6 +2211,10 @@ rewind_retry:
error = TRUE;
break;
}
+#ifdef FEAT_PERSISTENT_UNDO
+ if (read_undo_file)
+ sha256_update(&sha_ctx, line_start, len);
+#endif
++lnum;
if (--read_count == 0)
{
@@ -2237,11 +2255,17 @@ failed:
if (set_options)
curbuf->b_p_eol = FALSE;
*ptr = NUL;
- if (ml_append(lnum, line_start,
- (colnr_T)(ptr - line_start + 1), newfile) == FAIL)
+ len = (colnr_T)(ptr - line_start + 1);
+ if (ml_append(lnum, line_start, len, newfile) == FAIL)
error = TRUE;
else
+ {
+#ifdef FEAT_PERSISTENT_UNDO
+ if (read_undo_file)
+ sha256_update(&sha_ctx, line_start, len);
+#endif
read_no_eol_lnum = ++lnum;
+ }
}
if (set_options)
@@ -2555,6 +2579,19 @@ failed:
*/
write_no_eol_lnum = read_no_eol_lnum;
+#ifdef FEAT_PERSISTENT_UNDO
+ /*
+ * When opening a new file locate undo info and read it.
+ */
+ if (read_undo_file)
+ {
+ char_u hash[UNDO_HASH_SIZE];
+
+ sha256_finish(&sha_ctx, hash);
+ u_read_undo(NULL, hash);
+ }
+#endif
+
#ifdef FEAT_AUTOCMD
if (!read_stdin && !read_buffer)
{
@@ -3038,6 +3075,10 @@ buf_write(buf, fname, sfname, start, end, eap, append, forceit,
vim_acl_T acl = NULL; /* ACL copied from original file to
backup or new file */
#endif
+#ifdef FEAT_PERSISTENT_UNDO
+ int write_undo_file = FALSE;
+ context_sha256_T sha_ctx;
+#endif
if (fname == NULL || *fname == NUL) /* safety check */
return FAIL;
@@ -4344,6 +4385,14 @@ restore_backup:
write_info.bw_start_lnum = start;
#endif
+#ifdef FEAT_PERSISTENT_UNDO
+ write_undo_file = (buf->b_p_udf && overwriting && !append
+ && !filtering && reset_changed);
+ if (write_undo_file)
+ /* Prepare for computing the hash value of the text. */
+ sha256_start(&sha_ctx);
+#endif
+
write_info.bw_len = bufsize;
#ifdef HAS_BW_FLAGS
write_info.bw_flags = wb_flags;
@@ -4358,6 +4407,10 @@ restore_backup:
* Keep it fast!
*/
ptr = ml_get_buf(buf, lnum, FALSE) - 1;
+#ifdef FEAT_PERSISTENT_UNDO
+ if (write_undo_file)
+ sha256_update(&sha_ctx, ptr + 1, STRLEN(ptr + 1) + 1);
+#endif
while ((c = *++ptr) != NUL)
{
if (c == NL)
@@ -4886,6 +4939,20 @@ nofail:
}
msg_scroll = msg_save;
+#ifdef FEAT_PERSISTENT_UNDO
+ /*
+ * When writing the whole file and 'undofile' is set, also write the undo
+ * file.
+ */
+ if (retval == OK && write_undo_file)
+ {
+ char_u hash[UNDO_HASH_SIZE];
+
+ sha256_finish(&sha_ctx, hash);
+ u_write_undo(NULL, FALSE, buf, hash);
+ }
+#endif
+
#ifdef FEAT_AUTOCMD
#ifdef FEAT_EVAL
if (!should_abort(retval))
diff --git a/src/memline.c b/src/memline.c
index b3c172733..c3fdba313 100644
--- a/src/memline.c
+++ b/src/memline.c
@@ -245,9 +245,6 @@ static char_u *make_percent_swname __ARGS((char_u *dir, char_u *name));
#ifdef FEAT_BYTEOFF
static void ml_updatechunk __ARGS((buf_T *buf, long line, long len, int updtype));
#endif
-#ifdef HAVE_READLINK
-static int resolve_symlink __ARGS((char_u *fname, char_u *buf));
-#endif
/*
* Open a new memline for "buf".
@@ -3559,7 +3556,7 @@ ml_lineadd(buf, count)
}
}
-#ifdef HAVE_READLINK
+#if defined(HAVE_READLINK) || defined(PROTO)
/*
* Resolve a symlink in the last component of a file name.
* Note that f_resolve() does it for every part of the path, we don't do that
@@ -3567,7 +3564,7 @@ ml_lineadd(buf, count)
* If it worked returns OK and the resolved link in "buf[MAXPATHL]".
* Otherwise returns FAIL.
*/
- static int
+ int
resolve_symlink(fname, buf)
char_u *fname;
char_u *buf;
@@ -3862,7 +3859,7 @@ do_swapexists(buf, fname)
* Returns the name in allocated memory or NULL.
*
* Note: If BASENAMELEN is not correct, you will get error messages for
- * not being able to open the swapfile
+ * not being able to open the swap or undo file
* Note: May trigger SwapExists autocmd, pointers may change!
*/
static char_u *
@@ -3886,29 +3883,29 @@ findswapname(buf, dirp, old_fname)
# define CREATE_DUMMY_FILE
FILE *dummyfd = NULL;
-/*
- * If we start editing a new file, e.g. "test.doc", which resides on an MSDOS
- * compatible filesystem, it is possible that the file "test.doc.swp" which we
- * create will be exactly the same file. To avoid this problem we temporarily
- * create "test.doc".
- * Don't do this when the check below for a 8.3 file name is used.
- */
+ /*
+ * If we start editing a new file, e.g. "test.doc", which resides on an
+ * MSDOS compatible filesystem, it is possible that the file
+ * "test.doc.swp" which we create will be exactly the same file. To avoid
+ * this problem we temporarily create "test.doc". Don't do this when the
+ * check below for a 8.3 file name is used.
+ */
if (!(buf->b_p_sn || buf->b_shortname) && buf->b_fname != NULL
&& mch_getperm(buf->b_fname) < 0)
dummyfd = mch_fopen((char *)buf->b_fname, "w");
#endif
-/*
- * Isolate a directory name from *dirp and put it in dir_name.
- * First allocate some memory to put the directory name in.
- */
+ /*
+ * Isolate a directory name from *dirp and put it in dir_name.
+ * First allocate some memory to put the directory name in.
+ */
dir_name = alloc((unsigned)STRLEN(*dirp) + 1);
if (dir_name != NULL)
(void)copy_option_part(dirp, dir_name, 31000, ",");
-/*
- * we try different names until we find one that does not exist yet
- */
+ /*
+ * we try different names until we find one that does not exist yet
+ */
if (dir_name == NULL) /* out of memory */
fname = NULL;
else
diff --git a/src/option.c b/src/option.c
index 6c0618ab3..a4af18adf 100644
--- a/src/option.c
+++ b/src/option.c
@@ -177,6 +177,9 @@
#define PV_TS OPT_BUF(BV_TS)
#define PV_TW OPT_BUF(BV_TW)
#define PV_TX OPT_BUF(BV_TX)
+#ifdef FEAT_PERSISTENT_UNDO
+# define PV_UDF OPT_BUF(BV_UDF)
+#endif
#define PV_WM OPT_BUF(BV_WM)
/*
@@ -362,6 +365,9 @@ static char_u *p_spl;
static long p_ts;
static long p_tw;
static int p_tx;
+#ifdef FEAT_PERSISTENT_UNDO
+static int p_udf;
+#endif
static long p_wm;
#ifdef FEAT_KEYMAP
static char_u *p_keymap;
@@ -2586,6 +2592,22 @@ static struct vimoption
{"ttytype", "tty", P_STRING|P_EXPAND|P_NODEFAULT|P_NO_MKRC|P_VI_DEF|P_RALL,
(char_u *)&T_NAME, PV_NONE,
{(char_u *)"", (char_u *)0L} SCRIPTID_INIT},
+ {"undodir", "udir", P_STRING|P_EXPAND|P_COMMA|P_NODUP|P_SECURE|P_VI_DEF,
+#ifdef FEAT_PERSISTENT_UNDO
+ (char_u *)&p_udir, PV_NONE,
+ {(char_u *)".", (char_u *)0L}
+#else
+ (char_u *)NULL, PV_NONE,
+ {(char_u *)0L, (char_u *)0L}
+#endif
+ SCRIPTID_INIT},
+ {"undofile", "udf", P_BOOL|P_VI_DEF|P_VIM,
+#ifdef FEAT_PERSISTENT_UNDO
+ (char_u *)&p_udf, PV_UDF,
+#else
+ (char_u *)NULL, PV_NONE,
+#endif
+ {(char_u *)FALSE, (char_u *)0L} SCRIPTID_INIT},
{"undolevels", "ul", P_NUM|P_VI_DEF,
(char_u *)&p_ul, PV_NONE,
{
@@ -9404,6 +9426,9 @@ get_varp(p)
case PV_TS: return (char_u *)&(curbuf->b_p_ts);
case PV_TW: return (char_u *)&(curbuf->b_p_tw);
case PV_TX: return (char_u *)&(curbuf->b_p_tx);
+#ifdef FEAT_PERSISTENT_UNDO
+ case PV_UDF: return (char_u *)&(curbuf->b_p_udf);
+#endif
case PV_WM: return (char_u *)&(curbuf->b_p_wm);
#ifdef FEAT_KEYMAP
case PV_KMAP: return (char_u *)&(curbuf->b_p_keymap);
@@ -9774,6 +9799,9 @@ buf_copy_options(buf, flags)
#if defined(FEAT_BEVAL) && defined(FEAT_EVAL)
buf->b_p_bexpr = empty_option;
#endif
+#ifdef FEAT_PERSISTENT_UNDO
+ buf->b_p_udf = p_udf;
+#endif
/*
* Don't copy the options set by ex_help(), use the saved values,
diff --git a/src/option.h b/src/option.h
index b49c0c47f..f0ebc8de7 100644
--- a/src/option.h
+++ b/src/option.h
@@ -815,6 +815,7 @@ static char *(p_ttym_values[]) = {"xterm", "xterm2", "dec", "netterm", "jsbterm"
# define TTYM_JSBTERM 0x10
# define TTYM_PTERM 0x20
#endif
+EXTERN char_u *p_udir; /* 'undodir' */
EXTERN long p_ul; /* 'undolevels' */
EXTERN long p_uc; /* 'updatecount' */
EXTERN long p_ut; /* 'updatetime' */
@@ -1004,6 +1005,7 @@ enum
, BV_TS
, BV_TW
, BV_TX
+ , BV_UDF
, BV_WM
, BV_COUNT /* must be the last one */
};
diff --git a/src/os_mac.h b/src/os_mac.h
index 314ec3248..f1ed96541 100644
--- a/src/os_mac.h
+++ b/src/os_mac.h
@@ -204,7 +204,7 @@
#endif
#ifndef DFLT_VDIR
-# define DFLT_VDIR "$VIM/vimfiles/view" /* default for 'viewdir' */
+# define DFLT_VDIR "$VIM/vimfiles/view" /* default for 'viewdir' */
#endif
#define DFLT_ERRORFILE "errors.err"
diff --git a/src/proto/memline.pro b/src/proto/memline.pro
index de75a7dcf..4d5067129 100644
--- a/src/proto/memline.pro
+++ b/src/proto/memline.pro
@@ -25,6 +25,7 @@ int ml_delete __ARGS((linenr_T lnum, int message));
void ml_setmarked __ARGS((linenr_T lnum));
linenr_T ml_firstmarked __ARGS((void));
void ml_clearmarked __ARGS((void));
+int resolve_symlink __ARGS((char_u *fname, char_u *buf));
char_u *makeswapname __ARGS((char_u *fname, char_u *ffname, buf_T *buf, char_u *dir_name));
char_u *get_file_in_dir __ARGS((char_u *fname, char_u *dname));
void ml_setflags __ARGS((buf_T *buf));
diff --git a/src/proto/sha256.pro b/src/proto/sha256.pro
index a6d6be74a..e9a3d3cdd 100644
--- a/src/proto/sha256.pro
+++ b/src/proto/sha256.pro
@@ -1,4 +1,7 @@
/* sha256.c */
+void sha256_start __ARGS((context_sha256_T *ctx));
+void sha256_update __ARGS((context_sha256_T *ctx, char_u *input, uint32_t length));
+void sha256_finish __ARGS((context_sha256_T *ctx, char_u digest[32]));
char_u *sha256_key __ARGS((char_u *buf));
int sha256_self_test __ARGS((void));
void sha2_seed __ARGS((char_u header[], int header_len));
diff --git a/src/proto/spell.pro b/src/proto/spell.pro
index f497dc61c..8ab544dbf 100644
--- a/src/proto/spell.pro
+++ b/src/proto/spell.pro
@@ -2,11 +2,14 @@
int spell_check __ARGS((win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, int docount));
int spell_move_to __ARGS((win_T *wp, int dir, int allwords, int curline, hlf_T *attrp));
void spell_cat_line __ARGS((char_u *buf, char_u *line, int maxlen));
+int get2c __ARGS((FILE *fd));
+int get3c __ARGS((FILE *fd));
+int get4c __ARGS((FILE *fd));
char_u *did_set_spelllang __ARGS((buf_T *buf));
void spell_free_all __ARGS((void));
void spell_reload __ARGS((void));
int spell_check_msm __ARGS((void));
-void put_bytes __ARGS((FILE *fd, long_u nr, int len));
+int put_bytes __ARGS((FILE *fd, long_u nr, int len));
void ex_mkspell __ARGS((exarg_T *eap));
void ex_spell __ARGS((exarg_T *eap));
void spell_add_word __ARGS((char_u *word, int len, int bad, int idx, int undo));
diff --git a/src/proto/undo.pro b/src/proto/undo.pro
index e712f03ae..e5f28bec4 100644
--- a/src/proto/undo.pro
+++ b/src/proto/undo.pro
@@ -5,6 +5,9 @@ int u_savesub __ARGS((linenr_T lnum));
int u_inssub __ARGS((linenr_T lnum));
int u_savedel __ARGS((linenr_T lnum, long nlines));
int undo_allowed __ARGS((void));
+void u_compute_hash __ARGS((char_u *hash));
+void u_read_undo __ARGS((char_u *name, char_u *hash));
+void u_write_undo __ARGS((char_u *name, int forceit, buf_T *buf, char_u *hash));
void u_undo __ARGS((int count));
void u_redo __ARGS((int count));
void undo_time __ARGS((long step, int sec, int absolute));
diff --git a/src/sha256.c b/src/sha256.c
index 9372d5f00..048ce75fe 100644
--- a/src/sha256.c
+++ b/src/sha256.c
@@ -20,18 +20,9 @@
#include "vim.h"
-#ifdef FEAT_CRYPT
+#if defined(FEAT_CRYPT) || defined(FEAT_PERSISTENT_UNDO)
-typedef struct {
- UINT32_T total[2];
- UINT32_T state[8];
- char_u buffer[64];
-} context_sha256_T;
-
-static void sha256_starts __ARGS((context_sha256_T *ctx));
static void sha256_process __ARGS((context_sha256_T *ctx, char_u data[64]));
-static void sha256_update __ARGS((context_sha256_T *ctx, char_u *input, UINT32_T length));
-static void sha256_finish __ARGS((context_sha256_T *ctx, char_u digest[32]));
static char_u *sha256_bytes __ARGS((char_u *buf, int buflen));
static unsigned int get_some_time __ARGS((void));
@@ -52,8 +43,8 @@ static unsigned int get_some_time __ARGS((void));
(b)[(i) + 3] = (char_u)((n) ); \
}
- static void
-sha256_starts(ctx)
+ void
+sha256_start(ctx)
context_sha256_T *ctx;
{
ctx->total[0] = 0;
@@ -203,7 +194,7 @@ sha256_process(ctx, data)
ctx->state[7] += H;
}
- static void
+ void
sha256_update(ctx, input, length)
context_sha256_T *ctx;
char_u *input;
@@ -250,7 +241,7 @@ static char_u sha256_padding[64] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
- static void
+ void
sha256_finish(ctx, digest)
context_sha256_T *ctx;
char_u digest[32];
@@ -296,7 +287,7 @@ sha256_bytes(buf, buflen)
sha256_self_test();
- sha256_starts(&ctx);
+ sha256_start(&ctx);
sha256_update(&ctx, buf, buflen);
sha256_finish(&ctx, sha256sum);
for (j = 0; j < 32; j++)
@@ -368,7 +359,7 @@ sha256_self_test()
}
else
{
- sha256_starts(&ctx);
+ sha256_start(&ctx);
memset(buf, 'a', 1000);
for (j = 0; j < 1000; j++)
sha256_update(&ctx, (char_u *)buf, 1000);
@@ -416,7 +407,7 @@ sha2_seed(header, header_len)
for (i = 0; i < (int)sizeof(random_data) - 1; i++)
random_data[i] = (char_u)((get_some_time() ^ rand()) & 0xff);
- sha256_starts(&ctx);
+ sha256_start(&ctx);
sha256_update(&ctx, (char_u *)random_data, sizeof(random_data));
sha256_finish(&ctx, sha256sum);
@@ -424,4 +415,4 @@ sha2_seed(header, header_len)
header[i] = sha256sum[i % sizeof(sha256sum)];
}
-#endif /* FEAT_CRYPT */
+#endif /* FEAT_CRYPT || FEAT_PERSISTENT_UNDO */
diff --git a/src/spell.c b/src/spell.c
index 47b86ade8..1c3fd0f65 100644
--- a/src/spell.c
+++ b/src/spell.c
@@ -854,9 +854,6 @@ static char_u *spell_enc __ARGS((void));
static void int_wordlist_spl __ARGS((char_u *fname));
static void spell_load_cb __ARGS((char_u *fname, void *cookie));
static slang_T *spell_load_file __ARGS((char_u *fname, char_u *lang, slang_T *old_lp, int silent));
-static int get2c __ARGS((FILE *fd));
-static int get3c __ARGS((FILE *fd));
-static int get4c __ARGS((FILE *fd));
static time_t get8c __ARGS((FILE *fd));
static char_u *read_cnt_string __ARGS((FILE *fd, int cnt_bytes, int *lenp));
static char_u *read_string __ARGS((FILE *fd, int cnt));
@@ -2988,7 +2985,7 @@ endOK:
/*
* Read 2 bytes from "fd" and turn them into an int, MSB first.
*/
- static int
+ int
get2c(fd)
FILE *fd;
{
@@ -3002,7 +2999,7 @@ get2c(fd)
/*
* Read 3 bytes from "fd" and turn them into an int, MSB first.
*/
- static int
+ int
get3c(fd)
FILE *fd;
{
@@ -3017,7 +3014,7 @@ get3c(fd)
/*
* Read 4 bytes from "fd" and turn them into an int, MSB first.
*/
- static int
+ int
get4c(fd)
FILE *fd;
{
@@ -8018,7 +8015,7 @@ node_equal(n1, n2)
/*
* Write a number to file "fd", MSB first, in "len" bytes.
*/
- void
+ int
put_bytes(fd, nr, len)
FILE *fd;
long_u nr;
@@ -8027,7 +8024,9 @@ put_bytes(fd, nr, len)
int i;
for (i = len - 1; i >= 0; --i)
- putc((int)(nr >> (i * 8)), fd);
+ if (putc((int)(nr >> (i * 8)), fd) == EOF)
+ return FAIL;
+ return OK;
}
#ifdef _MSC_VER
diff --git a/src/structs.h b/src/structs.h
index a34fa664a..a7632b851 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -1465,6 +1465,9 @@ struct file_buffer
char_u *b_p_dict; /* 'dictionary' local value */
char_u *b_p_tsr; /* 'thesaurus' local value */
#endif
+#ifdef FEAT_PERSISTENT_UNDO
+ int b_p_udf; /* 'undofile' */
+#endif
/* end of buffer options */
@@ -2392,3 +2395,9 @@ typedef struct
#define CPT_KIND 2 /* "kind" */
#define CPT_INFO 3 /* "info" */
#define CPT_COUNT 4 /* Number of entries */
+
+typedef struct {
+ UINT32_T total[2];
+ UINT32_T state[8];
+ char_u buffer[64];
+} context_sha256_T;
diff --git a/src/testdir/Makefile b/src/testdir/Makefile
index 6a8e85f6b..53e085469 100644
--- a/src/testdir/Makefile
+++ b/src/testdir/Makefile
@@ -69,7 +69,7 @@ test1.out: test1.in
fi \
else echo $* NO OUTPUT >>test.log; \
fi"
- #-rm -rf X* test.ok viminfo
+ -rm -rf X* test.ok viminfo
test49.out: test49.vim
diff --git a/src/testdir/test61.in b/src/testdir/test61.in
index 7ce72e627..909d1230c 100644
--- a/src/testdir/test61.in
+++ b/src/testdir/test61.in
@@ -50,6 +50,53 @@ obbbbu:.w >>test.out
obbbb:set ul=100
:undojoin
occccu:.w >>test.out
+:"
+:" Test 'undofile': first a simple one-line change.
+:set nocp ul=100 undofile
+:e! Xtestfile
+ggdGithis is one line:set ul=100
+:s/one/ONE/
+:set ul=100
+:w
+:bwipe!
+:e Xtestfile
+u:.w >>test.out
+:"
+:" Test 'undofile', change in original file fails check
+:set noundofile
+:e! Xtestfile
+:s/line/Line/
+:w
+:set undofile
+:bwipe!
+:e Xtestfile
+u:.w >>test.out
+:"
+:" Test 'undofile', add 10 lines, delete 6 lines, undo 3
+:set undofile
+ggdGione
+two
+three
+four
+five
+six
+seven
+eight
+nine
+ten:set ul=100
+3Gdd:set ul=100
+dd:set ul=100
+dd:set ul=100
+dd:set ul=100
+dd:set ul=100
+dd:set ul=100
+:w
+:bwipe!
+:e Xtestfile
+uuu:w >>test.out
+:"
+:" Rename the undo file so that it gets cleaned up.
+:call rename(".Xtestfile.un~", "Xtestundo")
:qa!
ENDTEST
diff --git a/src/testdir/test61.ok b/src/testdir/test61.ok
index 020dd5383..ea88c07c3 100644
--- a/src/testdir/test61.ok
+++ b/src/testdir/test61.ok
@@ -22,3 +22,12 @@
123456abc
aaaa
aaaa
+this is one line
+this is ONE Line
+one
+two
+six
+seven
+eight
+nine
+ten
diff --git a/src/undo.c b/src/undo.c
index 403546614..b4945d12e 100644
--- a/src/undo.c
+++ b/src/undo.c
@@ -99,6 +99,14 @@ static void u_freeheader __ARGS((buf_T *buf, u_header_T *uhp, u_header_T **uhpp)
static void u_freebranch __ARGS((buf_T *buf, u_header_T *uhp, u_header_T **uhpp));
static void u_freeentries __ARGS((buf_T *buf, u_header_T *uhp, u_header_T **uhpp));
static void u_freeentry __ARGS((u_entry_T *, long));
+#ifdef FEAT_PERSISTENT_UNDO
+static void unserialize_pos __ARGS((pos_T *pos, FILE *fp));
+static void unserialize_visualinfo __ARGS((visualinfo_T *info, FILE *fp));
+static char_u *u_get_undo_file_name __ARGS((char_u *, int reading));
+static int serialize_uep __ARGS((u_entry_T *uep, FILE *fp));
+static void serialize_pos __ARGS((pos_T pos, FILE *fp));
+static void serialize_visualinfo __ARGS((visualinfo_T info, FILE *fp));
+#endif
#ifdef U_USE_MALLOC
# define U_FREE_LINE(ptr) vim_free(ptr)
@@ -119,6 +127,8 @@ static long u_newcount, u_oldcount;
*/
static int undo_undoes = FALSE;
+static int lastmark = 0;
+
#ifdef U_DEBUG
/*
* Check the undo structures for being valid. Print a warning when something
@@ -652,6 +662,795 @@ nomem:
return FAIL;
}
+#ifdef FEAT_PERSISTENT_UNDO
+
+# define UF_START_MAGIC 0xfeac /* magic at start of undofile */
+# define UF_HEADER_MAGIC 0x5fd0 /* magic at start of header */
+# define UF_END_MAGIC 0xe7aa /* magic after last header */
+# define UF_VERSION 1 /* 2-byte undofile version number */
+
+/*
+ * Compute the hash for the current buffer text into hash[UNDO_HASH_SIZE].
+ */
+ void
+u_compute_hash(hash)
+ char_u *hash;
+{
+ context_sha256_T ctx;
+ linenr_T lnum;
+ char_u *p;
+
+ sha256_start(&ctx);
+ for (lnum = 1; lnum < curbuf->b_ml.ml_line_count; ++lnum)
+ {
+ p = ml_get(lnum);
+ sha256_update(&ctx, p, STRLEN(p) + 1);
+ }
+ sha256_finish(&ctx, hash);
+}
+
+/*
+ * Unserialize the pos_T at the current position in fp.
+ */
+ static void
+unserialize_pos(pos, fp)
+ pos_T *pos;
+ FILE *fp;
+{
+ pos->lnum = get4c(fp);
+ pos->col = get4c(fp);
+#ifdef FEAT_VIRTUALEDIT
+ pos->coladd = get4c(fp);
+#else
+ (void)get4c(fp);
+#endif
+}
+
+/*
+ * Unserialize the visualinfo_T at the current position in fp.
+ */
+ static void
+unserialize_visualinfo(info, fp)
+ visualinfo_T *info;
+ FILE *fp;
+{
+ unserialize_pos(&info->vi_start, fp);
+ unserialize_pos(&info->vi_end, fp);
+ info->vi_mode = get4c(fp);
+ info->vi_curswant = get4c(fp);
+}
+
+/*
+ * Return an allocated string of the full path of the target undofile.
+ * When "reading" is TRUE find the file to read, go over all directories in
+ * 'undodir'.
+ * When "reading" is FALSE use the first name where the directory exists.
+ */
+ static char_u *
+u_get_undo_file_name(buf_ffname, reading)
+ char_u *buf_ffname;
+ int reading;
+{
+ char_u *dirp;
+ char_u dir_name[IOSIZE + 1];
+ char_u *munged_name = NULL;
+ char_u *undo_file_name = NULL;
+ int dir_len;
+ char_u *p;
+ struct stat st;
+ char_u *ffname = buf_ffname;
+#ifdef HAVE_READLINK
+ char_u fname_buf[MAXPATHL];
+#endif
+
+ if (ffname == NULL)
+ return NULL;
+
+#ifdef HAVE_READLINK
+ /* Expand symlink in the file name, so that we put the undo file with the
+ * actual file instead of with the symlink. */
+ if (resolve_symlink(ffname, fname_buf) == OK)
+ ffname = fname_buf;
+#endif
+
+ /* Loop over 'undodir'. When reading find the first file that exists.
+ * When not reading use the first directory that exists or ".". */
+ dirp = p_udir;
+ while (*dirp != NUL)
+ {
+ dir_len = copy_option_part(&dirp, dir_name, IOSIZE, ",");
+ if (dir_len == 1 && dir_name[0] == '.')
+ {
+ /* Use same directory as the ffname,
+ * "dir/name" -> "dir/.name.un~" */
+ undo_file_name = vim_strnsave(ffname, STRLEN(ffname) + 5);
+ if (undo_file_name == NULL)
+ break;
+ p = gettail(undo_file_name);
+ mch_memmove(p + 1, p, STRLEN(p) + 1);
+ *p = '.';
+ STRCAT(p, ".un~");
+ }
+ else
+ {
+ dir_name[dir_len] = NUL;
+ if (mch_isdir(dir_name))
+ {
+ if (munged_name == NULL)
+ {
+ munged_name = vim_strsave(ffname);
+ if (munged_name == NULL)
+ return NULL;
+ for (p = munged_name; *p != NUL; mb_ptr_adv(p))
+ if (vim_ispathsep(*p))
+ *p = '%';
+ }
+ undo_file_name = concat_fnames(dir_name, munged_name, TRUE);
+ }
+ }
+
+ /* When reading check if the file exists. */
+ if (undo_file_name != NULL && (!reading
+ || mch_stat((char *)undo_file_name, &st) >= 0))
+ break;
+ vim_free(undo_file_name);
+ undo_file_name = NULL;
+ }
+
+ vim_free(munged_name);
+ return undo_file_name;
+}
+
+/*
+ * Load the undo tree from an undo file.
+ * If "name" is not NULL use it as the undo file name. This also means being
+ * a bit more verbose.
+ * Otherwise use curbuf->b_ffname to generate the undo file name.
+ * "hash[UNDO_HASH_SIZE]" must be the hash value of the buffer text.
+ */
+ void
+u_read_undo(name, hash)
+ char_u *name;
+ char_u *hash;
+{
+ char_u *file_name;
+ FILE *fp;
+ long magic, version, str_len;
+ char_u *line_ptr = NULL;
+ linenr_T line_lnum;
+ colnr_T line_colnr;
+ linenr_T line_count;
+ int uep_len;
+ int line_len;
+ int num_head;
+ long old_header_seq, new_header_seq, cur_header_seq;
+ long seq_last, seq_cur;
+ short old_idx = -1, new_idx = -1, cur_idx = -1;
+ long num_read_uhps = 0;
+ time_t seq_time;
+ int i, j;
+ int c;
+ short found_first_uep = 0;
+ char_u **array;
+ char_u *line;
+ u_entry_T *uep, *last_uep, *nuep;
+ u_header_T *uhp;
+ u_header_T **uhp_table = NULL;
+ char_u read_hash[UNDO_HASH_SIZE];
+
+ if (name == NULL)
+ {
+ file_name = u_get_undo_file_name(curbuf->b_ffname, TRUE);
+ if (file_name == NULL)
+ return;
+ }
+ else
+ file_name = name;
+
+ if (p_verbose > 0)
+ smsg((char_u *)_("Reading undo file: %s"), file_name);
+ fp = mch_fopen((char *)file_name, "r");
+ if (fp == NULL)
+ {
+ if (name != NULL || p_verbose > 0)
+ EMSG2(_("E822: Cannot open undo file for reading: %s"), file_name);
+ goto error;
+ }
+
+ /* Begin overall file information */
+ magic = get2c(fp);
+ if (magic != UF_START_MAGIC)
+ {
+ EMSG2(_("E823: Corrupted undo file: %s"), file_name);
+ goto error;
+ }
+ version = get2c(fp);
+ if (version != UF_VERSION)
+ {
+ EMSG2(_("E824: Incompatible undo file: %s"), file_name);
+ goto error;
+ }
+
+ fread(read_hash, UNDO_HASH_SIZE, 1, fp);
+ line_count = (linenr_T)get4c(fp);
+ if (memcmp(hash, read_hash, UNDO_HASH_SIZE) != 0
+ || line_count != curbuf->b_ml.ml_line_count)
+ {
+ if (p_verbose > 0 || name != NULL)
+ {
+ verbose_enter();
+ give_warning((char_u *)_("Undo file contents changed"), TRUE);
+ verbose_leave();
+ }
+ goto error;
+ }
+
+ /* Begin undo data for U */
+ str_len = get4c(fp);
+ if (str_len < 0)
+ goto error;
+ else if (str_len > 0)
+ {
+ if ((line_ptr = U_ALLOC_LINE(str_len)) == NULL)
+ goto error;
+ for (i = 0; i < str_len; i++)
+ line_ptr[i] = (char_u)getc(fp);
+ line_ptr[i] = NUL;
+ }
+ line_lnum = (linenr_T)get4c(fp);
+ line_colnr = (colnr_T)get4c(fp);
+
+ /* Begin general undo data */
+ old_header_seq = get4c(fp);
+ new_header_seq = get4c(fp);
+ cur_header_seq = get4c(fp);
+ num_head = get4c(fp);
+ seq_last = get4c(fp);
+ seq_cur = get4c(fp);
+ seq_time = get4c(fp);
+
+ /* uhp_table will store the freshly created undo headers we allocate
+ * until we insert them into curbuf. The table remains sorted by the
+ * sequence numbers of the headers. */
+ uhp_table = (u_header_T **)U_ALLOC_LINE(num_head * sizeof(u_header_T *));
+ if (uhp_table == NULL)
+ goto error;
+ vim_memset(uhp_table, 0, num_head * sizeof(u_header_T *));
+
+ c = get2c(fp);
+ while (c == UF_HEADER_MAGIC)
+ {
+ found_first_uep = 0;
+ uhp = (u_header_T *)U_ALLOC_LINE((unsigned)sizeof(u_header_T));
+ if (uhp == NULL)
+ goto error;
+ vim_memset(uhp, 0, sizeof(u_header_T));
+ /* We're not actually trying to store pointers here. We're just storing
+ * IDs so we can swizzle them into pointers later - hence the type
+ * cast. */
+ uhp->uh_next = (u_header_T *)(long)get4c(fp);
+ uhp->uh_prev = (u_header_T *)(long)get4c(fp);
+ uhp->uh_alt_next = (u_header_T *)(long)get4c(fp);
+ uhp->uh_alt_prev = (u_header_T *)(long)get4c(fp);
+ uhp->uh_seq = get4c(fp);
+ if (uhp->uh_seq <= 0)
+ {
+ EMSG2(_("E825: Undo file corruption: invalid uh_seq.: %s"),
+ file_name);
+ U_FREE_LINE(uhp);
+ goto error;
+ }
+ uhp->uh_walk = 0;
+ unserialize_pos(&uhp->uh_cursor, fp);
+#ifdef FEAT_VIRTUALEDIT
+ uhp->uh_cursor_vcol = get4c(fp);
+#else
+ (void)get4c(fp);
+#endif
+ uhp->uh_flags = get2c(fp);
+ for (i = 0; i < NMARKS; ++i)
+ unserialize_pos(&uhp->uh_namedm[i], fp);
+#ifdef FEAT_VISUAL
+ unserialize_visualinfo(&uhp->uh_visual, fp);
+#else
+ {
+ visualinfo_T info;
+ unserialize_visualinfo(&info, fp);
+ }
+#endif
+ uhp->uh_time = get4c(fp);
+
+ /* Unserialize uep list. The first 4 bytes is the length of the
+ * entire uep in bytes minus the length of the strings within.
+ * -1 is a sentinel value meaning no more ueps.*/
+ last_uep = NULL;
+ while ((uep_len = get4c(fp)) != -1)
+ {
+ uep = (u_entry_T *)U_ALLOC_LINE((unsigned)sizeof(u_entry_T));
+ vim_memset(uep, 0, sizeof(u_entry_T));
+ if (uep == NULL)
+ goto error;
+ uep->ue_top = get4c(fp);
+ uep->ue_bot = get4c(fp);
+ uep->ue_lcount = get4c(fp);
+ uep->ue_size = get4c(fp);
+ uep->ue_next = NULL;
+ array = (char_u **)U_ALLOC_LINE(
+ (unsigned)(sizeof(char_u *) * uep->ue_size));
+ for (i = 0; i < uep->ue_size; i++)
+ {
+ line_len = get4c(fp);
+ /* U_ALLOC_LINE provides an extra byte for the NUL terminator.*/
+ line = (char_u *)U_ALLOC_LINE(
+ (unsigned) (sizeof(char_u) * line_len));
+ if (line == NULL)
+ goto error;
+ for (j = 0; j < line_len; j++)
+ {
+ line[j] = getc(fp);
+ }
+ line[j] = '\0';
+ array[i] = line;
+ }
+ uep->ue_array = array;
+ if (found_first_uep == 0)
+ {
+ uhp->uh_entry = uep;
+ found_first_uep = 1;
+ }
+ else
+ {
+ last_uep->ue_next = uep;
+ }
+ last_uep = uep;
+ }
+
+ /* Insertion sort the uhp into the table by its uh_seq. This is
+ * required because, while the number of uhps is limited to
+ * num_heads, and the uh_seq order is monotonic with respect to
+ * creation time, the starting uh_seq can be > 0 if any undolevel
+ * culling was done at undofile write time, and there can be uh_seq
+ * gaps in the uhps.
+ */
+ for (i = num_read_uhps - 1; i >= -1; i--)
+ {
+ /* if i == -1, we've hit the leftmost side of the table, so insert
+ * at uhp_table[0]. */
+ if (i == -1 || uhp->uh_seq > uhp_table[i]->uh_seq)
+ {
+ /* If we've had to move from the rightmost side of the table,
+ * we have to shift everything to the right by one spot. */
+ if (i < num_read_uhps - 1)
+ {
+ memmove(uhp_table + i + 2, uhp_table + i + 1,
+ (num_read_uhps - i) * sizeof(u_header_T *));
+ }
+ uhp_table[i + 1] = uhp;
+ break;
+ }
+ else if (uhp->uh_seq == uhp_table[i]->uh_seq)
+ {
+ EMSG2(_("E826 Undo file corruption: duplicate uh_seq: %s"),
+ file_name);
+ goto error;
+ }
+ }
+ num_read_uhps++;
+ c = get2c(fp);
+ }
+
+ if (c != UF_END_MAGIC)
+ {
+ EMSG2(_("E827: Undo file corruption; no end marker: %s"), file_name);
+ goto error;
+ }
+
+ /* We've organized all of the uhps into a table sorted by uh_seq. Now we
+ * iterate through the table and swizzle each sequence number we've
+ * stored in uh_foo into a pointer corresponding to the header with that
+ * sequence number. Then free curbuf's old undo structure, give curbuf
+ * the updated {old,new,cur}head pointers, and then free the table. */
+ for (i = 0; i < num_head; i++)
+ {
+ uhp = uhp_table[i];
+ if (uhp == NULL)
+ continue;
+ for (j = 0; j < num_head; j++)
+ {
+ if (uhp_table[j] == NULL)
+ continue;
+ if (uhp_table[j]->uh_seq == (long)uhp->uh_next)
+ uhp->uh_next = uhp_table[j];
+ if (uhp_table[j]->uh_seq == (long)uhp->uh_prev)
+ uhp->uh_prev = uhp_table[j];
+ if (uhp_table[j]->uh_seq == (long)uhp->uh_alt_next)
+ uhp->uh_alt_next = uhp_table[j];
+ if (uhp_table[j]->uh_seq == (long)uhp->uh_alt_prev)
+ uhp->uh_alt_prev = uhp_table[j];
+ }
+ if (old_header_seq > 0 && old_idx < 0 && uhp->uh_seq == old_header_seq)
+ old_idx = i;
+ if (new_header_seq > 0 && new_idx < 0 && uhp->uh_seq == new_header_seq)
+ new_idx = i;
+ if (cur_header_seq > 0 && cur_idx < 0 && uhp->uh_seq == cur_header_seq)
+ cur_idx = i;
+ }
+ u_blockfree(curbuf);
+ curbuf->b_u_oldhead = old_idx < 0 ? 0 : uhp_table[old_idx];
+ curbuf->b_u_newhead = new_idx < 0 ? 0 : uhp_table[new_idx];
+ curbuf->b_u_curhead = cur_idx < 0 ? 0 : uhp_table[cur_idx];
+ curbuf->b_u_line_ptr = line_ptr;
+ curbuf->b_u_line_lnum = line_lnum;
+ curbuf->b_u_line_colnr = line_colnr;
+ curbuf->b_u_numhead = num_head;
+ curbuf->b_u_seq_last = seq_last;
+ curbuf->b_u_seq_cur = seq_cur;
+ curbuf->b_u_seq_time = seq_time;
+ U_FREE_LINE(uhp_table);
+#ifdef U_DEBUG
+ u_check(TRUE);
+#endif
+ if (name != NULL)
+ smsg((char_u *)_("Finished reading undo file %s"), file_name);
+ goto theend;
+
+error:
+ if (line_ptr != NULL)
+ U_FREE_LINE(line_ptr);
+ if (uhp_table != NULL)
+ {
+ for (i = 0; i < num_head; i++)
+ {
+ if (uhp_table[i] != NULL)
+ {
+ uep = uhp_table[i]->uh_entry;
+ while (uep != NULL)
+ {
+ nuep = uep->ue_next;
+ u_freeentry(uep, uep->ue_size);
+ uep = nuep;
+ }
+ U_FREE_LINE(uhp_table[i]);
+ }
+ }
+ U_FREE_LINE(uhp_table);
+ }
+
+theend:
+ if (fp != NULL)
+ fclose(fp);
+ if (file_name != name)
+ vim_free(file_name);
+ return;
+}
+
+/*
+ * Serialize "uep" to "fp".
+ */
+ static int
+serialize_uep(uep, fp)
+ u_entry_T *uep;
+ FILE *fp;
+{
+ int i;
+ int uep_len;
+ int *entry_lens;
+
+ if (uep->ue_size > 0)
+ entry_lens = (int *)alloc(uep->ue_size * sizeof(int));
+
+ /* Define uep_len to be the size of the entire uep minus the size of its
+ * component strings, in bytes. The sizes of the component strings
+ * are written before each individual string.
+ * We have 4 entries each of 4 bytes, plus ue_size * 4 bytes
+ * of string size information. */
+
+ uep_len = uep->ue_size * 4;
+ /* Collect sizing information for later serialization. */
+ for (i = 0; i < uep->ue_size; i++)
+ {
+ entry_lens[i] = (int)STRLEN(uep->ue_array[i]);
+ uep_len += entry_lens[i];
+ }
+ put_bytes(fp, (long_u)uep_len, 4);
+ put_bytes(fp, (long_u)uep->ue_top, 4);
+ put_bytes(fp, (long_u)uep->ue_bot, 4);
+ put_bytes(fp, (long_u)uep->ue_lcount, 4);
+ put_bytes(fp, (long_u)uep->ue_size, 4);
+ for (i = 0; i < uep->ue_size; i++)
+ {
+ if (put_bytes(fp, (long_u)entry_lens[i], 4) == FAIL)
+ return FAIL;
+ fprintf(fp, "%s", uep->ue_array[i]);
+ }
+ if (uep->ue_size > 0)
+ vim_free(entry_lens);
+ return OK;
+}
+
+/*
+ * Serialize "pos" to "fp".
+ */
+ static void
+serialize_pos(pos, fp)
+ pos_T pos;
+ FILE *fp;
+{
+ put_bytes(fp, (long_u)pos.lnum, 4);
+ put_bytes(fp, (long_u)pos.col, 4);
+#ifdef FEAT_VIRTUALEDIT
+ put_bytes(fp, (long_u)pos.coladd, 4);
+#else
+ put_bytes(fp, (long_u)0, 4);
+#endif
+}
+
+/*
+ * Serialize "info" to "fp".
+ */
+ static void
+serialize_visualinfo(info, fp)
+ visualinfo_T info;
+ FILE *fp;
+{
+ serialize_pos(info.vi_start, fp);
+ serialize_pos(info.vi_end, fp);
+ put_bytes(fp, (long_u)info.vi_mode, 4);
+ put_bytes(fp, (long_u)info.vi_curswant, 4);
+}
+
+static char_u e_not_open[] = N_("E828: Cannot open undo file for writing: %s");
+
+/*
+ * Write the undo tree in an undo file.
+ * When "name" is not NULL, use it as the name of the undo file.
+ * Otherwise use buf->b_ffname to generate the undo file name.
+ * "buf" must never be null, buf->b_ffname is used to obtain the original file
+ * permissions.
+ * "forceit" is TRUE for ":wundo!", FALSE otherwise.
+ * "hash[UNDO_HASH_SIZE]" must be the hash value of the buffer text.
+ */
+ void
+u_write_undo(name, forceit, buf, hash)
+ char_u *name;
+ int forceit;
+ buf_T *buf;
+ char_u *hash;
+{
+ u_header_T *uhp;
+ u_entry_T *uep;
+ char_u *file_name;
+ int str_len, i, uep_len, mark;
+ int fd;
+ FILE *fp = NULL;
+ int perm;
+ int write_ok = FALSE;
+#ifdef UNIX
+ struct stat st_old;
+ struct stat st_new;
+#endif
+
+ if (name == NULL)
+ {
+ file_name = u_get_undo_file_name(buf->b_ffname, FALSE);
+ if (file_name == NULL)
+ return;
+ }
+ else
+ file_name = name;
+
+#ifdef UNIX
+ if (mch_stat((char *)buf->b_ffname, &st_old) >= 0)
+ perm = st_old.st_mode;
+ else
+ perm = 0600;
+#else
+ perm = mch_getperm(buf->b_ffname);
+ if (perm < 0)
+ perm = 0600;
+#endif
+ /* set file protection same as original file, but strip s-bit */
+ perm = perm & 0777;
+
+ /* If the undo file exists, verify that it actually is an undo file, and
+ * delete it. */
+ if (mch_getperm(file_name) >= 0)
+ {
+ if (name == NULL || !forceit)
+ {
+ /* Check we can read it and it's an undo file. */
+ fd = mch_open((char *)file_name, O_RDONLY|O_EXTRA, 0);
+ if (fd < 0)
+ {
+ if (name != NULL || p_verbose > 0)
+ smsg((char_u *)_("Will not overwrite with undo file, cannot read: %s"),
+ file_name);
+ goto theend;
+ }
+ else
+ {
+ char_u buf[2];
+
+ vim_read(fd, buf, 2);
+ close(fd);
+ if ((buf[0] << 8) + buf[1] != UF_START_MAGIC)
+ {
+ if (name != NULL || p_verbose > 0)
+ smsg((char_u *)_("Will not overwrite, this is not an undo file: %s"),
+ file_name);
+ goto theend;
+ }
+ }
+ }
+ mch_remove(file_name);
+ }
+
+ fd = mch_open((char *)file_name,
+ O_CREAT|O_EXTRA|O_WRONLY|O_EXCL|O_NOFOLLOW, perm);
+ (void)mch_setperm(file_name, perm);
+ if (fd < 0)
+ {
+ EMSG2(_(e_not_open), file_name);
+ goto theend;
+ }
+ if (p_verbose > 0)
+ smsg((char_u *)_("Writing undo file: %s"), file_name);
+
+#ifdef UNIX
+ /*
+ * Try to set the group of the undo file same as the original file. If
+ * this fails, set the protection bits for the group same as the
+ * protection bits for others.
+ */
+ if (mch_stat((char *)file_name, &st_new) >= 0
+ && st_new.st_gid != st_old.st_gid
+# ifdef HAVE_FCHOWN /* sequent-ptx lacks fchown() */
+ && fchown(fd, (uid_t)-1, st_old.st_gid) != 0
+# endif
+ )
+ mch_setperm(file_name, (perm & 0707) | ((perm & 07) << 3));
+# ifdef HAVE_SELINUX
+ mch_copy_sec(buf->b_ffname, file_name);
+# endif
+#endif
+
+ fp = fdopen(fd, "w");
+ if (fp == NULL)
+ {
+ EMSG2(_(e_not_open), file_name);
+ close(fd);
+ mch_remove(file_name);
+ goto theend;
+ }
+
+ /* Start writing, first overall file information */
+ put_bytes(fp, (long_u)UF_START_MAGIC, 2);
+ put_bytes(fp, (long_u)UF_VERSION, 2);
+
+ /* Write a hash of the buffer text, so that we can verify it is still the
+ * same when reading the buffer text. */
+ if (fwrite(hash, (size_t)UNDO_HASH_SIZE, (size_t)1, fp) != 1)
+ goto write_error;
+ put_bytes(fp, (long_u)buf->b_ml.ml_line_count, 4);
+
+ /* Begin undo data for U */
+ str_len = buf->b_u_line_ptr != NULL ? STRLEN(buf->b_u_line_ptr) : 0;
+ put_bytes(fp, (long_u)str_len, 4);
+ if (str_len > 0 && fwrite(buf->b_u_line_ptr, (size_t)str_len,
+ (size_t)1, fp) != 1)
+ goto write_error;
+
+ put_bytes(fp, (long_u)buf->b_u_line_lnum, 4);
+ put_bytes(fp, (long_u)buf->b_u_line_colnr, 4);
+
+ /* Begin general undo data */
+ uhp = buf->b_u_oldhead;
+ put_bytes(fp, (long_u)(uhp != NULL ? uhp->uh_seq : 0), 4);
+
+ uhp = buf->b_u_newhead;
+ put_bytes(fp, (long_u)(uhp != NULL ? uhp->uh_seq : 0), 4);
+
+ uhp = buf->b_u_curhead;
+ put_bytes(fp, (long_u)(uhp != NULL ? uhp->uh_seq : 0), 4);
+
+ put_bytes(fp, (long_u)buf->b_u_numhead, 4);
+ put_bytes(fp, (long_u)buf->b_u_seq_last, 4);
+ put_bytes(fp, (long_u)buf->b_u_seq_cur, 4);
+ put_bytes(fp, (long_u)buf->b_u_seq_time, 4);
+
+ /* Iteratively serialize UHPs and their UEPs from the top down. */
+ mark = ++lastmark;
+ uhp = buf->b_u_oldhead;
+ while (uhp != NULL)
+ {
+ /* Serialize current UHP if we haven't seen it */
+ if (uhp->uh_walk != mark)
+ {
+ if (put_bytes(fp, (long_u)UF_HEADER_MAGIC, 2) == FAIL)
+ goto write_error;
+
+ put_bytes(fp, (long_u)((uhp->uh_next != NULL)
+ ? uhp->uh_next->uh_seq : 0), 4);
+ put_bytes(fp, (long_u)((uhp->uh_prev != NULL)
+ ? uhp->uh_prev->uh_seq : 0), 4);
+ put_bytes(fp, (long_u)((uhp->uh_alt_next != NULL)
+ ? uhp->uh_alt_next->uh_seq : 0), 4);
+ put_bytes(fp, (long_u)((uhp->uh_alt_prev != NULL)
+ ? uhp->uh_alt_prev->uh_seq : 0), 4);
+ put_bytes(fp, uhp->uh_seq, 4);
+ serialize_pos(uhp->uh_cursor, fp);
+#ifdef FEAT_VIRTUALEDIT
+ put_bytes(fp, (long_u)uhp->uh_cursor_vcol, 4);
+#else
+ put_bytes(fp, (long_u)0, 4);
+#endif
+ put_bytes(fp, (long_u)uhp->uh_flags, 2);
+ /* Assume NMARKS will stay the same. */
+ for (i = 0; i < NMARKS; ++i)
+ {
+ serialize_pos(uhp->uh_namedm[i], fp);
+ }
+#ifdef FEAT_VISUAL
+ serialize_visualinfo(uhp->uh_visual, fp);
+#endif
+ put_bytes(fp, (long_u)uhp->uh_time, 4);
+
+ uep = uhp->uh_entry;
+ while (uep != NULL)
+ {
+ if (serialize_uep(uep, fp) == FAIL)
+ goto write_error;
+ uep = uep->ue_next;
+ }
+ /* Sentinel value: no more ueps */
+ uep_len = -1;
+ put_bytes(fp, (long_u)uep_len, 4);
+ uhp->uh_walk = mark;
+ }
+
+ /* Now walk through the tree - algorithm from undo_time */
+ if (uhp->uh_prev != NULL && uhp->uh_prev->uh_walk != mark)
+ uhp = uhp->uh_prev;
+ else if (uhp->uh_alt_next != NULL && uhp->uh_alt_next->uh_walk != mark)
+ uhp = uhp->uh_alt_next;
+ else if (uhp->uh_next != NULL && uhp->uh_alt_prev == NULL
+ && uhp->uh_next->uh_walk != mark)
+ uhp = uhp->uh_next;
+ else if (uhp->uh_alt_prev != NULL)
+ uhp = uhp->uh_alt_prev;
+ else
+ uhp = uhp->uh_next;
+ }
+
+ if (put_bytes(fp, (long_u)UF_END_MAGIC, 2) == OK)
+ write_ok = TRUE;
+
+write_error:
+ fclose(fp);
+ if (!write_ok)
+ EMSG2(_("E829: write error in undo file: %s"), file_name);
+
+#if defined(MACOS_CLASSIC) || defined(WIN3264)
+ (void)mch_copy_file_attribute(buf->b_ffname, file_name);
+#endif
+#ifdef HAVE_ACL
+ {
+ vim_acl_T acl;
+
+ /* For systems that support ACL: get the ACL from the original file. */
+ acl = mch_get_acl(buf->b_ffname);
+ mch_set_acl(file_name, acl);
+ }
+#endif
+
+theend:
+ if (file_name != name)
+ vim_free(file_name);
+}
+
+#endif /* FEAT_PERSISTENT_UNDO */
+
+
/*
* If 'cpoptions' contains 'u': Undo the previous undo or redo (vi compatible).
* If 'cpoptions' does not contain 'u': Always undo.
@@ -757,8 +1556,6 @@ u_doit(startcount)
u_undo_end(undo_undoes, FALSE);
}
-static int lastmark = 0;
-
/*
* Undo or redo over the timeline.
* When "step" is negative go back in time, otherwise goes forward in time.
@@ -927,7 +1724,7 @@ undo_time(step, sec, absolute)
if (absolute)
{
- EMSGN(_("Undo number %ld not found"), step);
+ EMSGN(_("E830: Undo number %ld not found"), step);
return;
}
diff --git a/src/version.c b/src/version.c
index fe82987dc..04a33b8f1 100644
--- a/src/version.c
+++ b/src/version.c
@@ -426,6 +426,11 @@ static char *(features[]) =
#else
"-perl",
#endif
+#ifdef FEAT_PERSISTENT_UNDO
+ "+persistent_undo",
+#else
+ "-persistent_undo",
+#endif
#ifdef FEAT_PRINTER
# ifdef FEAT_POSTSCRIPT
"+postscript",
diff --git a/src/vim.h b/src/vim.h
index d320d3a95..044727310 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -1398,6 +1398,9 @@ typedef enum
*/
#define MAXMAPLEN 50
+/* Size in bytes of the hash used in the undo file. */
+#define UNDO_HASH_SIZE 32
+
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif