]> granicus.if.org Git - vim/commitdiff
Included patch for persistent undo. Lots of changes and added test.
authorBram Moolenaar <Bram@vim.org>
Sun, 23 May 2010 21:34:36 +0000 (23:34 +0200)
committerBram Moolenaar <Bram@vim.org>
Sun, 23 May 2010 21:34:36 +0000 (23:34 +0200)
34 files changed:
runtime/doc/cmdline.txt
runtime/doc/eval.txt
runtime/doc/options.txt
runtime/doc/tags
runtime/doc/todo.txt
runtime/doc/undo.txt
runtime/doc/various.txt
runtime/doc/version7.txt
runtime/optwin.vim
runtime/tutor/tutor.it
runtime/tutor/tutor.it.utf-8
src/buffer.c
src/eval.c
src/ex_cmds.h
src/ex_docmd.c
src/feature.h
src/fileio.c
src/memline.c
src/option.c
src/option.h
src/os_mac.h
src/proto/memline.pro
src/proto/sha256.pro
src/proto/spell.pro
src/proto/undo.pro
src/sha256.c
src/spell.c
src/structs.h
src/testdir/Makefile
src/testdir/test61.in
src/testdir/test61.ok
src/undo.c
src/version.c
src/vim.h

index 318c69992508cb63ce8e1b9f795f52578d5f2c4a..8abb16b8c4a6ed639e675070d029d526c1085ab5 100644 (file)
@@ -805,6 +805,7 @@ Note: these are typed literally, they are not special keys!
 
                                                         *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|
index 5b5e8fa00df15ffae4dbaafdfbaa3b40c86a3971..2a2d6015512385179b3c2321417178478685be52 100644 (file)
@@ -6053,6 +6053,7 @@ os2                       OS/2 version of Vim.
 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.
index 0bdde3a556d06c4b41c0c04956caa48b33437f08..9c9d584155b884c3122a788ded3cb1ae7179af6a 100644 (file)
@@ -7220,6 +7220,33 @@ A jump table for the options with a short description can be found at |Q_op|.
                        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)
index 259933eaae4ea7ee8de201368290750af6586e5d..9e2dac56553a8c475100e594af63c8f1bf98c655 100644 (file)
@@ -10,6 +10,16 @@ $VIM starting.txt    /*$VIM*
 $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      /*''*
@@ -1005,7 +1015,11 @@ $VIMRUNTIME      starting.txt    /*$VIMRUNTIME*
 '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'*
@@ -1163,6 +1177,7 @@ $VIMRUNTIME       starting.txt    /*$VIMRUNTIME*
 +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*
@@ -2582,6 +2597,7 @@ $VIMRUNTIME       starting.txt    /*$VIMRUNTIME*
 :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*
@@ -2964,6 +2980,7 @@ $VIMRUNTIME       starting.txt    /*$VIMRUNTIME*
 :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*
@@ -6913,6 +6930,7 @@ perl-overview     if_perl.txt     /*perl-overview*
 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*
@@ -7835,6 +7853,7 @@ undo      undo.txt        /*undo*
 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*
index a109c2ea0647f1438f348deab114f8258206396a..49fc70a4d559a0eeac883630a20451ef06bfc8a2 100644 (file)
@@ -30,6 +30,9 @@ be worked on, but only if you sponsor Vim development.  See |sponsor|.
                                                        *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.
@@ -1082,6 +1085,18 @@ restored. (Luc St-Louis)
 
 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
@@ -1150,42 +1165,17 @@ Needs some work:
   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:
@@ -1292,7 +1282,6 @@ Awaiting updated 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.
@@ -4320,11 +4309,6 @@ Mouse support:
 -   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
index 2c8c60c9aac7294cc2ce2d0219d07960bf876815..05e5555b779c47c010559f155e7a2e7f9b1ca6bf 100644 (file)
@@ -12,7 +12,8 @@ The basics are explained in section |02.5| of the user manual.
 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*
@@ -22,7 +23,7 @@ u                     Undo [count] changes.  {Vi: only one level}
 
                                                        *: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}
 
@@ -109,6 +110,8 @@ change.
 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*
@@ -201,7 +204,88 @@ Note that using "u" and CTRL-R will not get you to all possible text states
 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
index cfadfd629bb030e40a9f56430b40e04a58ddabc9..65688eead7cba21b2c8c9e316f3fc727f6a70673 100644 (file)
@@ -359,6 +359,7 @@ m  *+ole*           Win32 GUI only: |ole-interface|
 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
index a6e1f617546e95de0278d0d9ece4ba9d750f188f..d59b62264999248b394b552cfe63216400c79089 100644 (file)
@@ -7170,6 +7170,10 @@ the buffer is marked as modified.
 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.
index 06a76624d76efdbea3712c5d0b3348d245f38d96..0ace4cc6d4c78966af4c89e1b0b5094de80f3ab3 100644 (file)
@@ -1032,6 +1032,10 @@ if has("vertsplit")
   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")
index b1f6798d01e534496121e8880c5900810d426f69..69e83eb03d3725923f60680751f18617417f5819 100644 (file)
@@ -288,7 +288,7 @@ NOTA:  Se batti solo il movimento mentre sei in Modalit
 
   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.
index 051b51bbeaaff5c25c7fa83afc74f33a8de9d53e..bee3f6d8f231c18711a971b655c009d4928a588d 100644 (file)
@@ -288,7 +288,7 @@ NOTA:  Se batti solo il movimento mentre sei in Modalità Normale, senza
 
   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.
index 531e4b9a29f53c32c649d3b884b4a4d43aea2943..5ecb63872732f9f8ca9344cd6c36e23d640e97dd 100644 (file)
@@ -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)
index 446df8ed3ff203490bb3d5ffc120426841edea3f..3c238fcbec50b7b59148da3609197905970247d0 100644 (file)
@@ -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",
index 1ef885acdd8135817274f0beb286d9faa44f9190..f41e0f449d4012a565bf0019a1db0c207eeb86de 100644 (file)
@@ -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,
index ff39040cf716b502e0592d7906d6af73fa447639..4097f1d8f2d9642f3c6f01b900fe5c7984df9b23 100644 (file)
@@ -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".
  */
index 6b8f600ec0db22396f75027485f1c03374471b05..61fe3345bbcd4a408bfd5d03a154e8c01268143c 100644 (file)
            || 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
index 7a697ee020c733b8bf0c67723257cb5778875fcf..b7c86af7efc7146448d63d2c2d57ad1411b02dc8 100644 (file)
@@ -252,6 +252,10 @@ readfile(fname, sfname, from, lines_to_skip, lines_to_read, eap, flags)
 #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 */
@@ -1177,6 +1181,12 @@ retry:
        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
     }
 
@@ -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))
index b3c1727335ee40d97a886efa4a1401dff0c4428a..c3fdba313768a3633d0a4b3df194c113249a9d25 100644 (file)
@@ -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
index 6c0618ab37490528eeaac341d46af4cd4870c739..a4af18adfe139bf7d915bdda707368a2d867b568 100644 (file)
 #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,
index b49c0c47fcd7d1ae3347cfc1b0ee9188af6c38fe..f0ebc8de7a7b50f07251ce00f614ece6ae505149 100644 (file)
@@ -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 */
 };
index 314ec324826e154c1c0d6bd9400471a0de5b1048..f1ed96541f0678a178deb7199e105609e0f68eb9 100644 (file)
 #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"
index de75a7dcf61846f2d19c367c39b4b8d8990470e0..4d50671298977b3e7bb54a9c814975cce08c0fc2 100644 (file)
@@ -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));
index a6d6be74afad901fc054b67891a1456db727a00c..e9a3d3cdd81b15bc1c243aac2bfe7d61aa979bab 100644 (file)
@@ -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));
index f497dc61c8e9cf7499ce5798d79fc8961bb56fb7..8ab544dbf021ceb630cc84df8f46f07ee81d727b 100644 (file)
@@ -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));
index e712f03ae1dbb7c0341f2909fdcfdc910f4fc271..e5f28bec418e2080e69eb3c35219395a0638096e 100644 (file)
@@ -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));
index 9372d5f00fa0ebbd083e482eaa90aaf4a6bbb988..048ce75fe1f66f4e69d3e5254c67e86a619ce90c 100644 (file)
 
 #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 */
index 47b86ade83730dc0ff0d32dd599c711727bbd455..1c3fd0f651318ae4b51b2378033c419c198522be 100644 (file)
@@ -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
index a34fa664aa2fc7b37a52069802643591761d6fad..a7632b851753e07755f8f5addd96537f3d7dd950 100644 (file)
@@ -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;
index 6a8e85f6bd8373e0ff14c1faec3e8ed125243640..53e085469e1617c5d5b8dd533b7e4a5ff8510b22 100644 (file)
@@ -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
 
index 7ce72e627f719300298e30ba60aac4a7f62e4809..909d1230c24bbd36cf4684af52b685000b461d23 100644 (file)
@@ -50,6 +50,53 @@ obbbb\eu:.w >>test.out
 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
 
index 020dd5383b9b2bfe13450668fba1a84e1260646d..ea88c07c3620de4c8c1598733597d5073ee9f2fb 100644 (file)
 123456abc
 aaaa
 aaaa
+this is one line
+this is ONE Line
+one
+two
+six
+seven
+eight
+nine
+ten
index 4035466148bca14f7e6afa3a168d42730b67e832..b4945d12e122c01f448d9cf17509c5413eeca774 100644 (file)
@@ -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;
        }
 
index fe82987dc95311a86829f24a7ba3d3c59c55e174..04a33b8f10481d505ce8d6d84baad5b902ce9a6e 100644 (file)
@@ -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",
index d320d3a9520cae8a6592ed0f4ec93f41ebeb1edf..044727310646d0f2617e5c972f80dde4b6f356f6 100644 (file)
--- 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