*filename-modifiers*
*:_%:* *::8* *::p* *::.* *::~* *::h* *::t* *::r* *::e* *::s* *::gs*
+ *%:8* *%:p* *%:.* *%:~* *%:h* *%:t* *%:r* *%:e* *%:s* *%:gs*
The file name modifiers can be used after "%", "#", "#n", "<cfile>", "<sfile>",
"<afile>" or "<abuf>". They are also used with the |fnamemodify()| function.
These are not available when Vim has been compiled without the |+modify_fname|
osfiletype Compiled with support for osfiletypes |+osfiletype|
path_extra Compiled with up/downwards search in 'path' and 'tags'
perl Compiled with Perl interface.
+persistent_undo Compiled with support for persistent undo history.
postscript Compiled with PostScript file printing.
printer Compiled with |:hardcopy| support.
profile Compiled with |:profile| support.
global
Alias for 'term', see above.
+ *'undodir'* *'udir'*
+'undodir' 'udir' string (default ".")
+ global
+ {not in Vi}
+ {only when compiled with the +persistent_undo feature}
+ List of directory names for undo files, separated with commas.
+ See |'backupdir'| for the format. Specifically, "." means using the
+ directory of the file.
+ When writing: The first directory that exists is used. "." always
+ works, no directories after "." will be used for writing.
+ When reading all entries are tried to find an undo file. The first
+ undo file that exists is used. When it cannot be read an error is
+ given, no further entry is used.
+ See |undo-persistence|.
+
+ *'undofile'* *'udf'*
+'undofile' 'udf' boolean (default off)
+ local to buffer
+ {not in Vi}
+ {only when compiled with the +persistent_undo feature}
+ When on, Vim automatically saves undo history to an undo file when
+ writing a buffer to a file, and restores undo history from the same
+ file on buffer read.
+ The name of the undo file is specified by 'undodir'.
+ See |undo-persistence|.
+ WARNING: this is a very new feature. Use at your own risc!
+
*'undolevels'* *'ul'*
'undolevels' 'ul' number (default 100, 1000 for Unix, VMS,
Win32 and OS/2)
$VIM-use version5.txt /*$VIM-use*
$VIMRUNTIME starting.txt /*$VIMRUNTIME*
% motion.txt /*%*
+%:. cmdline.txt /*%:.*
+%:8 cmdline.txt /*%:8*
+%:e cmdline.txt /*%:e*
+%:gs cmdline.txt /*%:gs*
+%:h cmdline.txt /*%:h*
+%:p cmdline.txt /*%:p*
+%:r cmdline.txt /*%:r*
+%:s cmdline.txt /*%:s*
+%:t cmdline.txt /*%:t*
+%:~ cmdline.txt /*%:~*
& change.txt /*&*
' motion.txt /*'*
'' motion.txt /*''*
'tw' options.txt /*'tw'*
'tx' options.txt /*'tx'*
'uc' options.txt /*'uc'*
+'udf' options.txt /*'udf'*
+'udir' options.txt /*'udir'*
'ul' options.txt /*'ul'*
+'undodir' options.txt /*'undodir'*
+'undofile' options.txt /*'undofile'*
'undolevels' options.txt /*'undolevels'*
'updatecount' options.txt /*'updatecount'*
'updatetime' options.txt /*'updatetime'*
+path_extra various.txt /*+path_extra*
+perl various.txt /*+perl*
+perl/dyn various.txt /*+perl\/dyn*
++persistent_undo various.txt /*+persistent_undo*
+postscript various.txt /*+postscript*
+printer various.txt /*+printer*
+profile various.txt /*+profile*
:rubydo if_ruby.txt /*:rubydo*
:rubyf if_ruby.txt /*:rubyf*
:rubyfile if_ruby.txt /*:rubyfile*
+:rundo undo.txt /*:rundo*
:runtime repeat.txt /*:runtime*
:rv starting.txt /*:rv*
:rviminfo starting.txt /*:rviminfo*
:write_f editing.txt /*:write_f*
:ws workshop.txt /*:ws*
:wsverb workshop.txt /*:wsverb*
+:wundo undo.txt /*:wundo*
:wv starting.txt /*:wv*
:wviminfo starting.txt /*:wviminfo*
:x editing.txt /*:x*
perl-patterns pattern.txt /*perl-patterns*
perl-using if_perl.txt /*perl-using*
perl.vim syntax.txt /*perl.vim*
+persistent-undo undo.txt /*persistent-undo*
pexpr-option print.txt /*pexpr-option*
pfn-option print.txt /*pfn-option*
pheader-option print.txt /*pheader-option*
undo-blocks undo.txt /*undo-blocks*
undo-branches undo.txt /*undo-branches*
undo-commands undo.txt /*undo-commands*
+undo-persistence undo.txt /*undo-persistence*
undo-redo undo.txt /*undo-redo*
undo-remarks undo.txt /*undo-remarks*
undo-tree undo.txt /*undo-tree*
*known-bugs*
-------------------- Known bugs and current work -----------------------
+When Vim crashes it may run out of stack while executing autocommands. Patch
+to not run autocommands when leaving Vim? (James Vega, 2010 May 23)
+
Cursor positioning wrong with 0x200e character. (John Becket, 2010 May 6)
E315 when trying to change a file in FileChangedRO autocommand event.
Vim 7.3:
Patches to include:
+8 Persistent undo bugs / fixes:
+ - Add tests. Also with different 'enc'
+ - Add undofile(name): get undo file name for buffer "name".
+- Extend test62 for gettabvar() and settabvar(). (Yegappan Lakshmanan, 2010
+ May 23)
+- Also crypt the undo file.
+- Also crypt the swap file, each block separately. Change mf_write() and
+ mf_read(). How to get b_p_key to these functions?
+- Do profiling on sha256 code to find obvious bottlenecks.
+- Do profiling on crypt code to find obvious bottlenecks.
+- Use off_t instead of long for bytes in a buffer. (James Vega, 2010 May 22,
+ update next day)
- Include conceal patch?
http://vince.negri.googlepages.com/
http://vim.wikia.com/wiki/Patch_to_conceal_parts_of_lines
Needs some more testing.
Update 2010 Apr 20, patch by Andy Kittner, May 16
- Easier/standard way to disable default plugins.
-8 Persistent undo: store undo in a file. Patch by Jordan Lewis, 2009 Feb
- 20. Repost 2009 Nov 16.
- Get tar file from: http://repo.or.cz/w/vim_extended.git/tree/feat/persistent-undo
- -> disable by default and add remark that it's new and may fail.
- Testing remarks by Christian Brabandt, 2010 May 1:
- - doesn't work well with symlinks (Jordan will look into it)
- - old undo files tend to pile up
- - :rundo should output a message (Jordan will fix this)
- Bugs / fixes:
- - Undo file should be stored with the original file by default, the undo
- directory doesn't handle remote files or directory renames.
- Use same mechanism as for swap files? But only with one file name.
- - Read coladd depending on FEAT_VIRTUALEDIT, should always read/write it
- - invoke u_wundo() inside buf_write()
- - invoke u_rundo() inside readfile()
- - Document that ":wundo" and ":rundo" should only be used in autocommands.
- - unserialize_pos() does not need a return value
- - function comments go before the function, not inside
- - u_get_undofile() changes its argument ffname
- - make magic four bytes.
- - errors need numbers "E000:"
- - also put 'enc' in undo file.
- - don't use timestamp, "touch file" or dir copy may change it and undo
- still works.
- Older ideas:
- - Use timestamps, so that a version a certain time ago can be found and
- info before some time/date can be flushed. 'undopersist' gives maximum
- time to keep undo: "3h", "1d", "2w", "1y", etc. For the file use dot
- and extension: ".filename.un~" (like swapfile but "un~" instead of
- "swp").
- ":{range}source": source the lines from the current file.
You can already yank lines and use :@" to execute them.
Most of do_source() would not be used, need a new function.
It's easy when not doing breakpoints or profiling.
+Probably not now:
+- Use timestamps for undo, so that a version a certain time ago can be found
+ and info before some time/date can be flushed. 'undopersist' gives maximum
+ time to keep undo: "3h", "1d", "2w", "1y", etc.
Before (beta) release:
- Add fixes for 7.2 to version7.txt
+- Rename vim73 branch to default (hints: Xavier de Gaye, 2010 May 23)
More patches:
to left as well? See patch of Dec 26. (Nadim Shaikli)
8 Option to lock all used memory so that it doesn't get swapped to disk
(uncrypted). Patch by Jason Holt, 2003 May 23. Uses mlock.
-7 Support a stronger encryption. Jason Holt implemented AES (May 6 2003).
7 Add ! register, for shell commands. (patch from Grenie)
8 In the gzip plugin, also recognize *.gz.orig, *.gz.bak, etc. Like it's
done for filetype detection. Patch from Walter Briscoe, 2003 Jul 1.
- When mouse click after 'r' command, get character that was pointed to.
-Crypt and security:
-8 Also crypt the swapfile, each block separately. Change mf_write() and
- mf_read(). How to get b_p_key to these functions?
-
-
Argument list:
6 Add command to put all filenames from the tag files in the argument list.
When given an argument, only use the files where that argument matches
2. Two ways of undo |undo-two-ways|
3. Undo blocks |undo-blocks|
4. Undo branches |undo-branches|
-5. Remarks about undo |undo-remarks|
+5. Undo persistence |undo-persistence|
+6. Remarks about undo |undo-remarks|
==============================================================================
1. Undo and redo commands *undo-commands*
*:u* *:un* *:undo*
:u[ndo] Undo one change. {Vi: only one level}
-
+ *E830*
:u[ndo] {N} Jump to after change number {N}. See |undo-branches|
for the meaning of {N}. {not in Vi}
To do the opposite, break a change into two undo blocks, in Insert mode use
CTRL-G u. This is useful if you want an insert command to be undoable in
parts. E.g., for each sentence. |i_CTRL-G_u|
+Setting the value of 'undolevels' also breaks undo. Even when the new value
+is equal to the old value.
==============================================================================
4. Undo branches *undo-branches* *undo-tree*
while repeating "g-" and "g+" does.
==============================================================================
-5. Remarks about undo *undo-remarks*
+5. Undo persistence *undo-persistence* *persistent-undo*
+
+When unloading a buffer Vim normally destroys the tree of undos created for
+that buffer. By setting the 'undofile' option, Vim will automatically save
+your undo history when you write a file and restore undo history when you edit
+the file again.
+
+The 'undofile' option is checked after writing a file, before the BufWritePost
+autocommands. If you want to control what files to write undo information
+for, you can use a BufWritePre autocommand: >
+ au BufWritePre /tmp/* setlocal noundofile
+
+Vim saves undo trees in a separate undo file, one for each edited file, using
+a simple scheme that maps filesystem paths directly to undo files. Vim will
+detect if an undo file is no longer synchronized with the file it was written
+for (with a hash of the file contents) and ignore it when the file was changed
+after the undo file was written, to prevent corruption.
+
+Undo files are normally saved in the same directory as the file. This can be
+changed with the 'undodir' option.
+
+You can also save and restore undo histories by using ":wundo" and ":rundo"
+respectively:
+ *:wundo* *:rundo*
+:wundo[!] {file}
+ Write undo history to {file}.
+ When {file} exists and it does not look like an undo file
+ (the magic number at the start of the file is wrong), then
+ this fails, unless the ! was added.
+ If it exists and does look like an undo file it is
+ overwritten.
+ {not in Vi}
+
+:rundo {file} Read undo history from {file}.
+ {not in Vi}
+
+You can use these in autocommands to explicitly specify the name of the
+history file. E.g.: >
+
+ au BufReadPost * rundo %:h/UNDO/%:t
+ au BufWritePost * wundo %:h/UNDO/%:t
+
+You should keep 'undofile' off, otherwise you end up with two undo files for
+every write.
+Note: I did not verify this always works!
+
+Note that while reading/writing files and 'undofile' is set most errors will
+be silent, unless 'verbose' is set. With :wundo and :rundo you will get more
+error messages, e.g., when the file cannot be read or written.
+
+NOTE: undo files are never deleted by Vim. You need to delete them yourself.
+
+Reading an existing undo file may fail for several reasons:
+*E822* It cannot be opened, because the file permissions don't allow it.
+*E823* The magic number at the start of the file doesn't match. This usually
+ means it is not an undo file.
+*E824* The version number of the undo file indicates that it's written by a
+ newer version of Vim. You need that newer version to open it. Don't
+ write the buffer if you want to keep the undo info in the file.
+"Undo file contents changed"
+ The file text differs from when the undo file was written. This means
+ the undo file cannot be used, it would corrupt the text.
+*E825* *E826* The undo file does not contain valid contents and cannot be
+ used.
+*E827* The magic number at the end of the file was not found. This usually
+ means the file was truncated.
+
+Writing an undo file may fail for these reasons:
+*E828* The file to be written cannot be created. Perhaps you do not have
+ write permissions in the directory.
+"Will not overwrite with undo file, cannot read"
+ A file exists with the name of the undo file to be written, but it
+ cannot be read. You may want to delete this file or rename it.
+"Will not overwrite, this is not an undo file"
+ A file exists with the name of the undo file to be written, but it
+ does not start with the right magic number. You may want to delete
+ this file or rename it.
+*E829* An error occurred while writing the undo file. You may want to try
+ again.
+
+==============================================================================
+6. Remarks about undo *undo-remarks*
The number of changes that are remembered is set with the 'undolevels' option.
If it is zero, the Vi-compatible way is always used. If it is negative no
N *+path_extra* Up/downwards search in 'path' and 'tags'
m *+perl* Perl interface |perl|
m *+perl/dyn* Perl interface |perl-dynamic| |/dyn|
+H *+persistent_undo* Persistent undo |undo-persistence|
*+postscript* |:hardcopy| writes a PostScript file
N *+printer* |:hardcopy| command
H *+profile* |:profile| command
Added *added-7.3*
-----
+Persistent undo: store undo information in a file. Can undo to before when
+the file was read, also for unloaded buffers. |undo-persistence|
+(partly by Jordan Lewis)
+
Added the 'relativenumber' option. (Markus Heidelberg)
Support for Blowfish encryption. Added the 'cryptmethod' option.
call append("$", "cmdwinheight\theight of the command-line window")
call <SID>OptionG("cwh", &cwh)
endif
+call append("$", "undofile\tautomatically save and restore undo history")
+call <SID>BinOptionG("udf", &udf)
+call append("$", "undodir\tlist of directories for undo files")
+call <SID>OptionG("udir", &udir)
call <SID>Header("executing external commands")
2. Batti d2w per cancellare le due parole MAIUSCOLE
- 3. Ripeti i passi 1 e 2 con un contatore diverso per cancellare la parole
+ 3. Ripeti i passi 1 e 2 con un contatore diverso per cancellare le parole
MAIUSCOLE consecutive con un solo comando
---> questa ABC DE linea FGHI JK LMN OP di parole è Q RS TUV ora ripulita.
2. Batti d2w per cancellare le due parole MAIUSCOLE
- 3. Ripeti i passi 1 e 2 con un contatore diverso per cancellare la parole
+ 3. Ripeti i passi 1 e 2 con un contatore diverso per cancellare le parole
MAIUSCOLE consecutive con un solo comando
---> questa ABC DE linea FGHI JK LMN OP di parole è Q RS TUV ora ripulita.
#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)
"perl",
#endif
#endif
+#ifdef FEAT_PERSISTENT_UNDO
+ "persistent_undo",
+#endif
#ifdef FEAT_PYTHON
#ifndef DYNAMIC_PYTHON
"python",
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,
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,
# 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
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));
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".
*/
|| 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
#ifdef FEAT_CRYPT
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 */
read_count = lines_to_read;
#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
}
error = TRUE;
break;
}
+#ifdef FEAT_PERSISTENT_UNDO
+ if (read_undo_file)
+ sha256_update(&sha_ctx, line_start, len);
+#endif
++lnum;
if (--read_count == 0)
{
error = TRUE;
break;
}
+#ifdef FEAT_PERSISTENT_UNDO
+ if (read_undo_file)
+ sha256_update(&sha_ctx, line_start, len);
+#endif
++lnum;
if (--read_count == 0)
{
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)
*/
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)
{
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;
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;
* 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)
}
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))
#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".
}
}
-#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
* 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;
* 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 *
# 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
#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)
/*
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;
{"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,
{
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);
#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,
# 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' */
, BV_TS
, BV_TW
, BV_TX
+ , BV_UDF
, BV_WM
, BV_COUNT /* must be the last one */
};
#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"
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));
/* 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));
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));
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));
#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));
(b)[(i) + 3] = (char_u)((n) ); \
}
- static void
-sha256_starts(ctx)
+ void
+sha256_start(ctx)
context_sha256_T *ctx;
{
ctx->total[0] = 0;
ctx->state[7] += H;
}
- static void
+ void
sha256_update(ctx, input, length)
context_sha256_T *ctx;
char_u *input;
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];
sha256_self_test();
- sha256_starts(&ctx);
+ sha256_start(&ctx);
sha256_update(&ctx, buf, buflen);
sha256_finish(&ctx, sha256sum);
for (j = 0; j < 32; j++)
}
else
{
- sha256_starts(&ctx);
+ sha256_start(&ctx);
memset(buf, 'a', 1000);
for (j = 0; j < 1000; j++)
sha256_update(&ctx, (char_u *)buf, 1000);
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);
header[i] = sha256sum[i % sizeof(sha256sum)];
}
-#endif /* FEAT_CRYPT */
+#endif /* FEAT_CRYPT || FEAT_PERSISTENT_UNDO */
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));
/*
* Read 2 bytes from "fd" and turn them into an int, MSB first.
*/
- static int
+ int
get2c(fd)
FILE *fd;
{
/*
* Read 3 bytes from "fd" and turn them into an int, MSB first.
*/
- static int
+ int
get3c(fd)
FILE *fd;
{
/*
* Read 4 bytes from "fd" and turn them into an int, MSB first.
*/
- static int
+ int
get4c(fd)
FILE *fd;
{
/*
* Write a number to file "fd", MSB first, in "len" bytes.
*/
- void
+ int
put_bytes(fd, nr, len)
FILE *fd;
long_u nr;
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
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 */
#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;
fi \
else echo $* NO OUTPUT >>test.log; \
fi"
- #-rm -rf X* test.ok viminfo
+ -rm -rf X* test.ok viminfo
test49.out: test49.vim
obbbb\e:set ul=100
:undojoin
occcc\eu:.w >>test.out
+:"
+:" Test 'undofile': first a simple one-line change.
+:set nocp ul=100 undofile
+:e! Xtestfile
+ggdGithis is one line\e: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\e: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
123456abc
aaaa
aaaa
+this is one line
+this is ONE Line
+one
+two
+six
+seven
+eight
+nine
+ten
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)
*/
static int undo_undoes = FALSE;
+static int lastmark = 0;
+
#ifdef U_DEBUG
/*
* Check the undo structures for being valid. Print a warning when something
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.
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.
if (absolute)
{
- EMSGN(_("Undo number %ld not found"), step);
+ EMSGN(_("E830: Undo number %ld not found"), step);
return;
}
#else
"-perl",
#endif
+#ifdef FEAT_PERSISTENT_UNDO
+ "+persistent_undo",
+#else
+ "-persistent_undo",
+#endif
#ifdef FEAT_PRINTER
# ifdef FEAT_POSTSCRIPT
"+postscript",
*/
#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