ZyX_I avatar ZyX_I committed 6473ffc

@%aurum/record: Added better undo support
Needs slightly better testing
Fixes #80

Comments (0)

Files changed (6)

aurum-addon-info.txt

         "test/maps-file.ok",
         "test/maps-log.in",
         "test/maps-log.ok",
+        "test/maps-record-undo.in",
+        "test/maps-record-undo.ok",
         "test/maps-status.in",
         "test/maps-status.ok",
         "test/opts-remembermsg.in",

autoload/aurum/record.vim

             \           '@%aurum/commit': '1.1',
             \         '@%aurum/cmdutils': '4.0',
             \        '@%aurum/lineutils': '0.0',
-            \             '@%aurum/edit': '1.0',
+            \             '@%aurum/edit': '1.5',
             \          '@%aurum/bufvars': '0.0',})
 let s:hasundo=exists('*undotree')
 let s:_options={
             \'recheight': {'default': 0,
             \               'filter': '(if type "" earg _  range 0 inf)'},
+            \'fullundo':  {'default': s:hasundo,
+            \               'filter': 'bool'},
         \}
 let s:_messages={
             \  'recex': 'There is already one AuRecord tab active '.
             \ 'recnof': 'No files were selected for commiting',
             \   'norm': 'Can’t remove file %s as it was not added',
             \   'noad': 'Can’t add file %s as it is already included',
+            \'noutree': 'No such item in saved undo tree. '.
+            \           'If you can reproduce this error file a bug report',
             \'tooundo': 'Undone too much changes and cannot redo',
             \ 'undona': "Can’t undo changes. Possible reasons:\n".
-            \           "  1. Current change is the oldest one\n".
-            \           "  2. You did some changes manually and thus buffer ".
+            \           "  1. You did some changes manually and thus buffer ".
             \                "was reset\n".
-            \           "  3. You edited a file which discards undo ".
-            \                "information (supporting undoing edits is too ".
-            \                "complex)",
+            \           "  2. You edited a file which discards undo ".
+            \                "information unless g:aurum_fullundo is set",
             \ 'redona': "Can’t redo changes. Possible reasons:\n".
-            \           "  1. Current change is the newest one\n".
-            \           "  2. You did some changes manually and thus buffer ".
+            \           "  1. You did some changes manually and thus buffer ".
             \                "was reset\n".
-            \           "  3. You edited a file which discards undo ".
-            \                "information (supporting undoing edits is too ".
-            \                "complex)",
+            \           "  2. You edited a file which discards undo ".
+            \                "information unless g:aurum_fullundo is set",
         \}
 "▶1 write
 function s:F.write(bvar)
     call feedkeys("\<C-\>\<C-n>:call ".
             \      "call(<SNR>".s:_sid."_Eval('s:F.runstatmap'), ".
-            \           "['commit', ".expand('<abuf>')."], {})\n", 'n')
+            \           "['commit', ".expand('<abuf>')."], {})\n","n")
     call map(copy(s:_r.allcachekeys), 's:_r.cache.wipe(v:val)')
 endfunction
 "▶1 recfunc
     let bvar.reset=0
     let bvar.backupfiles={}
     let bvar.filesbackup={}
-    let bvar.newfiles=[]
+    let bvar.newfiles={}
     let bvar.lines=map(copy(bvar.chars), 'v:val." ".bvar.files[v:key]')
     let bvar.swheight=height
     let bvar.startundo=s:F.curundo()
                 \        'autowrite': &autowrite,
                 \     'autowriteall': &autowriteall,
                 \         'autoread': &autoread,}
+    let bvar.fullundo=(s:hasundo && s:_f.getoption('fullundo'))
+    if bvar.fullundo
+        let bvar.undotree={bvar.startundo : {}}
+    endif
     setglobal noautowrite noautowriteall noautoread
-    if !s:hasundo
-        setglobal undolevels=-1
-    endif
     setlocal noreadonly buftype=acwrite
     augroup AuRecordVimLeave
         execute 'autocmd! VimLeave * '.
         setglobal undolevels=-1
         execute "normal! A \<BS>\e"
         let &undolevels=savedundolevels
+        if a:bvar.fullundo
+            let a:bvar.undotree={a:bvar.startundo : {}}
+        endif
     endif
 endfunction
 "▶1 supdate
-function s:F.supdate(bvar)
+function s:F.supdate(bvar, prevundo)
     if b:changedtick!=a:bvar.prevct
         let a:bvar.prevct=b:changedtick
         if a:bvar.reset
             let a:bvar.reset=0
         endif
+        let curundo=s:F.curundo()
+        if              a:bvar.fullundo
+                    \&& has_key(a:bvar.undotree, a:prevundo)
+                    \&& curundo!=a:prevundo
+                    \&& !has_key(a:bvar.undotree, curundo)
+            let a:bvar.undotree[curundo]=copy(a:bvar.undotree[a:prevundo])
+        endif
     endif
     setlocal nomodifiable
 endfunction
     else
         return
     endif
-    call map(copy(sbvar.backupfiles), 's:F.restorebackup(v:val, v:key)')
-    call map(copy(sbvar.newfiles),    's:F.restorebackup(v:val,   0  )')
+    let backupfiles=copy(sbvar.backupfiles)
+    let newfiles=copy(sbvar.newfiles)
+    call filter(backupfiles, 'filereadable(v:key)')
+    call filter(newfiles,    'filereadable(v:key)')
+    call map(backupfiles, 's:F.restorebackup(v:val, v:key)')
+    call map(newfiles,    's:F.restorebackup(v:key,   0  )')
     for [buf, savedopts] in items(filter(sbvar.oldbufs, 'bufexists(v:key)'))
         for [opt, optval] in items(savedopts)
             call setbufvar(buf, '&'.opt, optval)
         call s:_f.mapgroup.map('AuRecordLeft', bufnr('%'))
     endif
 endfunction
+let s:_augroups+=['AuRecordLeft']
 "▶1 srestore
 function s:F.srestore(bvar)
     let sbuf=get(a:bvar, 'sbuf', -1)
     let sbvar=a:bvar.sbvar
     execute 'silent botright sbuffer' sbuf
     execute 'resize' sbvar.swheight
+    call winrestview(a:bvar.winview)
+    redraw!
     let w:aurecid='AuRecordStatus'
     setlocal bufhidden=wipe
     return 1
 endfunction
-"▶1 restorefiles :: bvar, sline, eline → + FS
+"▶1 restorefiles :: bvar, sline::UInt, eline::UInt → + FS
 function s:F.restorefiles(bvar, sline, eline)
     for file in map(range(a:sline, a:eline), 'a:bvar.lines[v:val-1][2:]')
         let fullpath=s:_r.os.path.join(a:bvar.repo.path, file)
-        let backupfile=remove(a:bvar.filesbackup, fullpath)
-        call remove(a:bvar.backupfiles, backupfile)
+        let backupfile=a:bvar.filesbackup[fullpath]
         call s:F.restorebackup(fullpath, backupfile)
     endfor
 endfunction
 "▶1 undoup :: bvar → + bvar
-function s:F.udoup(bvar)
+function s:F.undoup(bvar)
     for line in range(1, line('$'))
         let a:bvar.statuses[line-1]=stridx(s:statchars, getline(line)[0])
     endfor
+    if a:bvar.fullundo
+        let curundo=s:F.curundo()
+        if !has_key(a:bvar.undotree, curundo)
+            call s:F.reset(a:bvar)
+            call s:_f.throw('noutree')
+        endif
+        for [file, contents] in items(a:bvar.undotree[curundo])
+            if contents is 0 && filereadable(file)
+                call delete(file)
+            else
+                call writefile(contents, file, 'b')
+            endif
+            unlet contents
+        endfor
+    endif
+endfunction
+"▶1 undoleafwrite :: bvar, lines::[String], path → + bvar, FS
+function s:F.undoleafwrite(bvar, lines, file)
+    if a:file is# a:bvar.file
+        let a:bvar.undoleaf[a:bvar.fullpath]=a:lines
+    endif
+    return a:bvar.ewrite(a:bvar, a:lines, a:file)
+endfunction
+"▶1 sactions
+let s:F.sactions={}
+"▶2 sactions.[v]add, sactions.[v]remove
+function s:F.sactions.add(action, bvar, buf)
+    if a:action[0] is# 'v'
+        let sline=line("'<")
+        let eline=line("'>")
+        if sline>eline
+            let [sline, eline]=[eline, sline]
+        endif
+    else
+        let sline=line('.')
+        let eline=line('.')+v:count1-1
+        if eline>line('$')
+            let eline=line('$')
+        endif
+    endif
+    let add=(a:action[-3:] is# 'add')
+    for line in range(sline, eline)
+        let status=a:bvar.statuses[line-1]
+        let oldstatus=status
+        if add
+            if status<2
+                let status+=2
+            elseif status==3
+                let status=2
+                call s:F.restorefiles(a:bvar, sline, eline)
+            endif
+        else
+            if status>1
+                let status-=2
+            elseif status==1
+                let status=0
+                call s:F.restorefiles(a:bvar, sline, eline)
+            endif
+        endif
+        if oldstatus==status
+            call s:_f.warn('no'.((add)?('ad'):('rm')), a:bvar.files[line-1])
+            continue
+        endif
+        let a:bvar.statuses[line-1]=status
+        call setline(line, s:statchars[status].a:bvar.lines[line-1])
+    endfor
+    return 1
+endfunction
+let s:F.sactions.vadd=s:F.sactions.add
+let s:F.sactions.remove=s:F.sactions.add
+let s:F.sactions.vremove=s:F.sactions.remove
+"▶2 sactions.discard
+function s:F.sactions.discard(action, bvar, buf)
+    call s:F.unload(a:bvar)
+    return 0
+endfunction
+"▶2 sactions.undo
+function s:F.sactions.undo(action, bvar, buf)
+    execute 'silent normal! '.v:count1.'u'
+    let curundo=s:F.curundo()
+    if curundo<a:bvar.startundo
+        while curundo<a:bvar.startundo
+            let pundo=curundo
+            silent redo
+            let curundo=s:F.curundo()
+            if curundo==pundo
+                call s:_f.throw('tooundo')
+                call s:F.reset(a:bvar)
+                setlocal nomodifiable
+                return 0
+            endif
+        endwhile
+    endif
+    return 1
+endfunction
+"▶2 sactions.redo, sactions.earlier, sactions.later
+let s:uactkey={
+            \'redo':    "\<C-r>",
+            \'earlier': "g+",
+            \'later':   "g-",
+        \}
+function s:F.sactions.redo(action, bvar, buf)
+    execute 'silent normal! '.v:count1.s:uactkey[a:action]
+    return 1
+endfunction
+let s:F.sactions.earlier=s:F.sactions.redo
+let s:F.sactions.later=s:F.sactions.earlier
+"▶2 sactions.edit
+function s:F.sactions.edit(action, bvar, buf)
+    let [lwnr, rwnr, swnr]=s:F.getwnrs()
+    let file=a:bvar.lines[line('.')-1][2:]
+    let type=a:bvar.types[line('.')-1]
+    let status=a:bvar.statuses[line('.')-1]
+    let modified=status%2
+    execute lwnr.'wincmd w'
+    diffoff!
+    let fullpath=s:_r.os.path.join(a:bvar.repo.path, file)
+    let ntype=get(s:ntypes, type, 0)
+    if !modified
+        if ntype is# 'm' || ntype is# 'a'
+            if has_key(a:bvar.filesbackup, fullpath)
+                let backupfile=a:bvar.filesbackup[fullpath]
+            else
+                let backupfile=fullpath.'.orig'
+                let i=0
+                while s:_r.os.path.exists(backupfile)
+                    let backupfile=fullpath.'.'.i.'.orig'
+                    let i+=1
+                endwhile
+            endif
+        elseif ntype is# 'r'
+            let a:bvar.newfiles[fullpath]=1
+        endif
+    endif
+    if ntype isnot 0
+        execute swnr.'wincmd w'
+        if !modified
+            let status=3
+            let a:bvar.statuses[line('.')-1]=status
+        endif
+        if a:bvar.fullundo
+            let prevundo=s:F.curundo()
+            let line=line('.')
+            call setline(line, s:statchars[status].a:bvar.lines[line-1])
+            call s:F.supdate(a:bvar, prevundo)
+            let curundo=s:F.curundo()
+        else
+            call s:F.reset(a:bvar)
+        endif
+        setlocal nomodifiable
+        execute lwnr.'wincmd w'
+        call s:F.edit(a:bvar, 'aurum://edit:'.fullpath, 0)
+        if a:bvar.fullundo
+            let ebvar=s:_r.bufvars[bufnr('%')]
+            let undoleaf=a:bvar.undotree[curundo]
+            let ebvar.undoleaf=undoleaf
+            let ebvar.fullpath=fullpath
+            let ebvar.ewrite=ebvar.write
+            let ebvar.write=s:F.undoleafwrite
+        endif
+        if ntype is# 'm' || (modified && ntype is# 'a')
+            if !modified
+                let fcontents=a:bvar.repo.functions.readfile(
+                            \     a:bvar.repo,
+                            \     a:bvar.repo.functions.getworkhex(a:bvar.repo),
+                            \     file)
+            endif
+            diffthis
+            execute rwnr.'wincmd w'
+            call s:F.edit(a:bvar, 'aurum://copy:'.
+                        \((modified)?(a:bvar.filesbackup[fullpath]):
+                        \            (fullpath)), 1)
+            diffthis
+            wincmd p
+        elseif modified
+            if ntype is# 'r'
+                diffthis
+                execute rwnr.'wincmd w'
+                call s:F.edit(a:bvar,
+                            \ ['file', a:bvar.repo,
+                            \  a:bvar.repo.functions.getworkhex(a:bvar.repo),
+                            \  file], 1)
+                diffthis
+                wincmd p
+            endif
+        else
+            if ntype is# 'a'
+                let fcontents=readfile(fullpath, 'b')
+            elseif ntype is# 'r'
+                let fcontents=a:bvar.repo.functions.readfile(
+                            \     a:bvar.repo,
+                            \     a:bvar.repo.functions.getworkhex(a:bvar.repo),
+                            \     file)
+            endif
+        endif
+        if !modified
+            if exists('backupfile')
+                let isexe=executable(fullpath)
+                if rename(fullpath, backupfile)
+                    call s:_f.warn('renfail', fullpath, backupfile)
+                    setlocal readonly nomodifiable
+                    execute swnr.'wincmd w'
+                    return 0
+                endif
+                let a:bvar.backupfiles[backupfile]=fullpath
+                let a:bvar.filesbackup[fullpath]=backupfile
+                if a:bvar.fullundo
+                    let undoleaf[fullpath]=0
+                    let undoleaf[backupfile]=readfile(backupfile, 'b')
+                    let oundoleafpart={fullpath   : copy(undoleaf[backupfile]),
+                                \      backupfile : 0}
+                    call map(a:bvar.undotree,
+                                \'extend(v:val, oundoleafpart, "keep")')
+                endif
+            else
+                let isexe=0
+            endif
+            let diff=&diff
+            if exists('fcontents')
+                silent %delete _
+                call s:_r.lineutils.setlines(fcontents, 0)
+                if diff
+                    diffupdate
+                endif
+            endif
+            silent write
+            if isexe && s:_r.os.name is# 'posix'
+                call system('chmod +x '.fnameescape(fullpath))
+            endif
+        endif
+        if !has_key(s:_r.bufvars, bufnr('%'))
+            let s:_r.bufvars[bufnr('%')]={}
+        endif
+        call extend(s:_r.bufvars[bufnr('%')], {'recfile': file,
+                    \                      'recmodified': modified,
+                    \                      'recfullpath': fullpath,
+                    \                       'recnewfile': 0,})
+        if exists('backupfile')
+            let s:_r.bufvars[bufnr('%')].recbackupfile=backupfile
+        else
+            let s:_r.bufvars[bufnr('%')].recnewfile=1
+        endif
+    endif
+    return 1
+endfunction
+"▶2 sactions.commit
+function s:F.sactions.commit(action, bvar, buf)
+    let files=filter(copy(a:bvar.files), 'a:bvar.statuses[v:key]>1')
+    if empty(files)
+        call s:_f.warn('recnof')
+        return 0
+    endif
+    setlocal bufhidden=hide
+    let winview=winsaveview()
+    try
+        let r=s:_r.commit.commit(a:bvar.repo, a:bvar.recopts, files,
+                    \            a:bvar.status, keys(s:ntypes), 'silent edit')
+    finally
+        if bufwinnr(a:buf)!=-1
+            call setbufvar(a:buf, '&bufhidden', 'wipe')
+        endif
+    endtry
+    if r
+        call s:F.unload(a:bvar)
+    else
+        let w:aurecid='AuRecordCommitMessage'
+        let cbvar=s:_r.bufvars[bufnr('%')]
+        let cbvar.sbvar=a:bvar
+        let cbvar.sbuf=a:buf
+        let cbvar.bwfunc=s:F.srestore
+        let cbvar.winview=winview
+    endif
+    return 0
 endfunction
 "▶1 runstatmap
 let s:statchars='-^+*'
             \'removed':  'r',
             \'deleted':  'r',
         \}
+let s:uactions=['undo', 'redo', 'earlier', 'later']
 function s:F.runstatmap(action, ...)
     "▶2 buf, bvar, reset
     let buf=get(a:000, 0, bufnr('%'))
     let bvar=s:_r.bufvars[buf]
     setlocal modifiable
     if !a:0 && b:changedtick!=bvar.prevct
-        call s:_f.warn('uchngs')
-        call s:F.reset(bvar)
-        setlocal nomodifiable
-        return
+        if bvar.fullundo && has_key(bvar.undotree, s:F.curundo())
+            call s:F.undoup(bvar)
+        else
+            call s:_f.warn('uchngs')
+            call s:F.reset(bvar)
+            setlocal nomodifiable
+            return
+        endif
     endif
-    if a:action[-2:] is# 'do'
+    "▶2 undo
+    let isundo=(index(s:uactions, a:action)!=-1)
+    if isundo
         if !s:hasundo
             call s:_f.warn('noundo')
             return
         endif
-        if bvar.reset || s:F.curundo()<=bvar.startundo
+        if bvar.reset
             setlocal nomodifiable
             call s:_f.warn(a:action.'na')
             return
         endif
     endif
-    "▶2 add/remove
-    if a:action[-3:] is# 'add' || a:action[-6:] is# 'remove'
-        if a:action[0] is# 'v'
-            let sline=line("'<")
-            let eline=line("'>")
-            if sline>eline
-                let [sline, eline]=[eline, sline]
+    let prevundo=s:F.curundo()
+    "▶2 action
+    if s:F.sactions[a:action](a:action, bvar, buf)
+        if bufnr('%')==buf
+            if isundo && s:F.curundo()!=prevundo
+                call s:F.undoup(bvar)
             endif
-        else
-            let sline=line('.')
-            let eline=line('.')+v:count1-1
-            if eline>line('$')
-                let eline=line('$')
-            endif
+            call s:F.supdate(bvar, prevundo)
         endif
-        let add=(a:action[-3:] is# 'add')
-        for line in range(sline, eline)
-            let status=bvar.statuses[line-1]
-            let oldstatus=status
-            if add
-                if status<2
-                    let status+=2
-                elseif status==3
-                    let status=2
-                    call s:F.restorefiles(bvar, sline, eline)
-                endif
-            else
-                if status>1
-                    let status-=2
-                elseif status==1
-                    let status=0
-                    call s:F.restorefiles(bvar, sline, eline)
-                endif
-            endif
-            if oldstatus==status
-                call s:_f.warn('no'.((add)?('ad'):('rm')), bvar.files[line-1])
-                continue
-            endif
-            let bvar.statuses[line-1]=status
-            call setline(line, s:statchars[status].bvar.lines[line-1])
-        endfor
-    "▶2 discard
-    elseif a:action is# 'discard'
-        call s:F.unload(bvar)
-        return
-    "▶2 undo
-    elseif a:action is# 'undo'
-        execute 'silent normal! '.v:count1.'u'
-        let curundo=s:F.curundo()
-        if curundo<bvar.startundo
-            while curundo<bvar.startundo
-                let prevundo=curundo
-                silent redo
-                let curundo=s:F.curundo()
-                if curundo==prevundo
-                    call s:_f.throw('tooundo')
-                    call s:F.reset(bvar)
-                    setlocal nomodifiable
-                    return
-                endif
-            endwhile
-        endif
-        call s:F.undoup(bvar)
-    "▶2 redo
-    elseif a:action is# 'redo'
-        execute 'silent normal! '.v:count1."\<C-r>"
-        call s:F.undoup(bvar)
-    "▶2 edit
-    elseif a:action is# 'edit'
-        let [lwnr, rwnr, swnr]=s:F.getwnrs()
-        let file=bvar.lines[line('.')-1][2:]
-        let type=bvar.types[line('.')-1]
-        let status=bvar.statuses[line('.')-1]
-        let modified=status%2
-        execute lwnr.'wincmd w'
-        diffoff!
-        let fullpath=s:_r.os.path.join(bvar.repo.path, file)
-        let ntype=get(s:ntypes, type, 0)
-        if !modified
-            if ntype is# 'm' || ntype is# 'a'
-                let backupfile=fullpath.'.orig'
-                let i=0
-                while s:_r.os.path.exists(backupfile)
-                    let backupfile=fullpath.'.'.i.'.orig'
-                    let i+=1
-                endwhile
-            elseif ntype is# 'r'
-                let bvar.newfiles+=[fullpath]
-            endif
-        endif
-        if ntype isnot 0
-            execute swnr.'wincmd w'
-            if !modified
-                let status=3
-                let bvar.statuses[line('.')-1]=status
-            endif
-            call s:F.reset(bvar)
-            setlocal nomodifiable
-            execute lwnr.'wincmd w'
-            call s:F.edit(bvar, 'aurum://edit:'.fullpath, 0)
-            if ntype is# 'm' || (modified && ntype is# 'a')
-                if !modified
-                    let fcontents=bvar.repo.functions.readfile(
-                                \     bvar.repo,
-                                \     bvar.repo.functions.getworkhex(bvar.repo),
-                                \     file)
-                endif
-                diffthis
-                execute rwnr.'wincmd w'
-                call s:F.edit(bvar, 'aurum://copy:'.
-                            \((modified)?(bvar.filesbackup[fullpath]):
-                            \            (fullpath)), 1)
-                diffthis
-                wincmd p
-            elseif modified
-                if ntype is# 'r'
-                    diffthis
-                    execute rwnr.'wincmd w'
-                    call s:F.edit(bvar,
-                                \ ['file', bvar.repo,
-                                \  bvar.repo.functions.getworkhex(bvar.repo),
-                                \  file], 1)
-                    diffthis
-                    wincmd p
-                endif
-            else
-                if ntype is# 'a'
-                    let fcontents=readfile(fullpath, 'b')
-                elseif ntype is# 'r'
-                    let fcontents=bvar.repo.functions.readfile(
-                                \     bvar.repo,
-                                \     bvar.repo.functions.getworkhex(bvar.repo),
-                                \     file)
-                endif
-            endif
-            if !modified
-                if exists('backupfile')
-                    let isexe=executable(fullpath)
-                    if rename(fullpath, backupfile)
-                        call s:_f.warn('renfail', fullpath, backupfile)
-                        setlocal readonly nomodifiable
-                        execute swnr.'wincmd w'
-                        return
-                    endif
-                    let bvar.backupfiles[backupfile]=fullpath
-                    let bvar.filesbackup[fullpath]=backupfile
-                else
-                    let isexe=0
-                endif
-                let diff=&diff
-                if exists('fcontents')
-                    silent %delete _
-                    call s:_r.lineutils.setlines(fcontents, 0)
-                    if diff
-                        diffupdate
-                    endif
-                endif
-                silent write
-                if isexe && s:_r.os.name is# 'posix'
-                    call system('chmod +x '.fnameescape(fullpath))
-                endif
-            endif
-            if !has_key(s:_r.bufvars, bufnr('%'))
-                let s:_r.bufvars[bufnr('%')]={}
-            endif
-            call extend(s:_r.bufvars[bufnr('%')], {'recfile': file,
-                        \                      'recmodified': modified,
-                        \                      'recfullpath': fullpath,
-                        \                       'recnewfile': 0,})
-            if exists('backupfile')
-                let s:_r.bufvars[bufnr('%')].recbackupfile=backupfile
-            else
-                let s:_r.bufvars[bufnr('%')].recnewfile=1
-            endif
-        endif
-    "▶2 commit
-    elseif a:action is# 'commit'
-        let files=filter(copy(bvar.files), 'bvar.statuses[v:key]>1')
-        if empty(files)
-            call s:_f.warn('recnof')
-            return
-        endif
-        setlocal bufhidden=hide
-        try
-            let r=s:_r.commit.commit(bvar.repo, bvar.recopts, files,
-                        \            bvar.status, keys(s:ntypes), 'silent edit')
-        finally
-            if bufwinnr(buf)!=-1
-                call setbufvar(buf, '&bufhidden', 'wipe')
-            endif
-        endtry
-        if r
-            call s:F.unload(bvar)
-        else
-            let w:aurecid='AuRecordCommitMessage'
-            let cbvar=s:_r.bufvars[bufnr('%')]
-            let cbvar.sbvar=bvar
-            let cbvar.sbuf=buf
-            let cbvar.bwfunc=s:F.srestore
-        endif
-        return
-    endif
-    "▶2 bvar.prevct, bvar.reset
-    if bufnr('%')==buf
-        call s:F.supdate(bvar)
     endif
 endfunction
-let s:_augroups+=['AuRecordLeft']
 "▶1 runleftmap
 function s:F.runleftmap(action)
     let [lwnr, rwnr, swnr]=s:F.getwnrs()
             let sbvar=s:_r.bufvars[bufnr('%')]
             if bvar.recnewfile
                 call s:F.restorebackup(bvar.recfullpath, 0)
-                call filter(sbvar.newfiles, 'v:val isnot# bvar.recfullpath')
             else
                 call s:F.restorebackup(bvar.recfullpath, bvar.recbackupfile)
-                unlet sbvar.backupfiles[bvar.recbackupfile]
             endif
             let fidx=index(sbvar.files, bvar.recfile)
             let sbvar.statuses[fidx]=0
             setlocal modifiable
+            let prevundo=s:F.curundo()
             call setline(fidx+1, s:statchars[0].sbvar.lines[fidx])
-            call s:F.supdate(sbvar)
+            call s:F.supdate(sbvar, prevundo)
         endif
     elseif a:action is# 'commit'
         silent update
         if sbvar.statuses[fidx]>1
             let sbvar.statuses[fidx]-=2
             setlocal modifiable
+            let prevundo=s:F.curundo()
             call setline(fidx+1, s:statchars[sbvar.statuses[fidx]].
                         \        sbvar.lines[fidx])
-            call s:F.supdate(sbvar)
+            call s:F.supdate(sbvar, prevundo)
         else
             call s:_f.warn('norm', bvar.recfile)
         endif
                 \          '{})<CR>'
 endfunction
 call s:_f.mapgroup.add('AuRecord', {
-            \   'Edit': {'lhs': 'O',     'rhs': s:F.gm('edit')},
-            \   'Undo': {'lhs': 'u',     'rhs': s:F.gm('undo')},
-            \   'Redo': {'lhs': '<C-r>', 'rhs': s:F.gm('redo')},
+            \   'Edit': {'lhs': 'O',     'rhs': s:F.gm('edit')   },
+            \   'Undo': {'lhs': 'u',     'rhs': s:F.gm('undo')   },
+            \   'Redo': {'lhs': '<C-r>', 'rhs': s:F.gm('redo')   },
+            \'Earlier': {'lhs': 'g-',    'rhs': s:F.gm('earlier')},
+            \  'Later': {'lhs': 'g+',    'rhs': s:F.gm('later')  },
         \}, {'mode': 'n', 'silent': 1, 'dontmap': 1})
 call s:_f.mapgroup.add('AuRecordLeft', {
             \'Discard': {'lhs': 'x', 'rhs': s:F.gml('discard')   },
            |aurum-m-AuRecord_Edit| mapping
 In status window there are additional mappings available (mgid=AuRecord, 
 without leader by default):                             *g:frawormap_AuRecord*
-                       *g:frawormap_AuRecord_Edit* *g:frawormap_AuRecord_Undo*
-                           *aurum-m-AuRecord_Edit*     *aurum-m-AuRecord_Undo*
+                    *g:frawormap_AuRecord_Edit* *g:frawormap_AuRecord_Undo*
+                    *g:frawormap_AuRecord_Redo* *g:frawormap_AuRecord_Earlier*
+                    *g:frawormap_AuRecord_Later*    *aurum-m-AuRecord_Later*
+                        *aurum-m-AuRecord_Redo*     *aurum-m-AuRecord_Earlier*
+                        *aurum-m-AuRecord_Edit*     *aurum-m-AuRecord_Undo*
 Mapping  LHS  Description ~
 Edit      O   Edit file under cursor. In original window there will be changed 
               version of file (for modified files) or nothing (files with 
               will be version of file modified in record mode and in original 
               window there will be backup of the file (or version from 
               repository if file in question was removed or deleted).
-Undo      u   Undo adds/removes of the file. Won’t undo changes made to the 
-              file using Edit mapping.
-Redo    <C-r> Like above, but redoes.
+Undo      u   Undo adds/removes of the file (|u|). Won’t undo changes made to 
+              the file using Edit mapping unless |g:aurum_fullundo| is set.
+Redo    <C-r> Like above, but redoes (|CTRL-R|).
+Earlier   g-  Like above, but uses |g-|. Unlike |g-| is subject to 'timeout'.
+Later     g+  Like above, but uses |g+|. Unlike |g+| is subject to 'timeout'.
+Note: undoing/redoing changes using other ways is not supported unless 
+      |g:aurum_fullundo| is set.
+
 :AuRecord also makes some AuStatus mappings change their meaning:
 Mapping                 New meaning ~
 |aurum-m-AuStatus_Track|  select changes made to file(s) for committing
 |aurum-m-AuStatus_Commit| finish record mode and commit selected changes
 |aurum-m-AuStatus_Forget| unselect changes made to file(s), don’t commit them
 
-Note that if you quit commit buffer or close status window you will also exit 
-     record mode discarding all changes you have made.
+Note that if you close status window you will also exit record mode discarding 
+     all changes you have made.
 
 :AuRecord defines the following mappings in recorded window 
 (mgid=AuRecordLeft, default leader: "<Leader>"):    *g:frawormap_AuRecordLeft*

test/gentests-setuptestrepos.zsh

 
 for test in cmd-annotate-buffers cmd+maps-commit cmd-record cmd-status \
             maps-status cmd+maps-vimdiff cmd-diff cmd-update opts-remembermsg \
-            opts-vimdiffusewin cmd-vimdiff-full cmdaus
+            opts-vimdiffusewin cmd-vimdiff-full cmdaus maps-record-undo
 do
     ln -s util-add-modified-repo.zsh $test.pre
 done

test/maps-record-undo.in

+:set undolevels&vim
+:cd `=g:curtest."repo"`
+:function GetFirstLastLine(file)
+:  let r=readfile(a:file, 'b')
+:  let lastempty=empty(get(r, -1, 1))
+:  if len(r)>4+lastempty
+:    call remove(r, 2, -3-lastempty)
+:  endif
+:  return r
+:endfunction
+:command -nargs=1 -bar RZ   :call WriteFile([<q-args>]+getline(1, '$'))
+:command -nargs=1 -bar RZC  :RZ <args> | bwipeout!
+:command -nargs=1 -bar WG   :call WriteFile(join(glob(<q-args>, 1, 1)))
+:command -nargs=1 -bar WF   :call WriteFile(<q-args>.':'.string(GetFirstLastLine(<q-args>)))
+:command -nargs=1 -bar WGF  :WG * | WF <args>
+:command -nargs=* -bar WGFs :WG * | for file in [<f-args>] | execute 'WF' file | endfor
+:W{{{1 no fullundo
+:AuRecord
+uAi:RZC{{{2 Add
+ui:RZ{{{2 Undo
+i:RZC{{{2 Redo
+jAi:RZC{{{2 Add
+2ui:RZ{{{2 2 Undo
+2i:RZC{{{2 2 Redo
+X
+:W{{{1 fullundo
+:let g:aurum_fullundo=1
+:AuRecord
+u:0/^-A
+OAbcdef,A:W{{{2 Edit
+:WGF addeddef
+u:W{{{2 Undo
+:WGF addeddef
+:W{{{2 Redo
+:WGF addeddef
+:0/^-M
+OAhijkl,A:W{{{2 Edit 2
+:WGF nohglinesrev.lst
+2u:W{{{2 2 Undo
+:WGFs addeddef nohglinesrev.lst
+2:W{{{2 2 Redo
+:WGFs addeddef nohglinesrev.lst

test/maps-record-undo.ok

+{{{1 no fullundo
+{{{2 Add
+
+# Added addeddef
+{{{2 Undo
+-A addeddef
+-! nohglines.lst
+-M nohglinesrev.lst
+-R hglines2.lst
+-? hglinesrev.lst
+{{{2 Redo
+
+# Added addeddef
+{{{2 Add
+
+# Added addeddef
+# Removed nohglines.lst
+{{{2 2 Undo
+-A addeddef
+-! nohglines.lst
+-M nohglinesrev.lst
+-R hglines2.lst
+-? hglinesrev.lst
+{{{2 2 Redo
+
+# Added addeddef
+# Removed nohglines.lst
+{{{1 fullundo
+{{{2 Edit
+ablines.lst addeddef addeddef.orig alines.lst blines.lst chgrepo.zsh clines.lst datelines.lst directory dlines.lst emerged.lst glines.lst glinescopy.lst hglines.lst hglinesrev.lst ignored10.lst ignoredabc nohglinesrev.lst
+addeddef:['Defbcdef', '']
+{{{2 Undo
+ablines.lst addeddef alines.lst blines.lst chgrepo.zsh clines.lst datelines.lst directory dlines.lst emerged.lst glines.lst glinescopy.lst hglines.lst hglinesrev.lst ignored10.lst ignoredabc nohglinesrev.lst
+addeddef:['Def', '']
+{{{2 Redo
+ablines.lst addeddef addeddef.orig alines.lst blines.lst chgrepo.zsh clines.lst datelines.lst directory dlines.lst emerged.lst glines.lst glinescopy.lst hglines.lst hglinesrev.lst ignored10.lst ignoredabc nohglinesrev.lst
+addeddef:['Defbcdef', '']
+{{{2 Edit 2
+ablines.lst addeddef addeddef.orig alines.lst blines.lst chgrepo.zsh clines.lst datelines.lst directory dlines.lst emerged.lst glines.lst glinescopy.lst hglines.lst hglinesrev.lst ignored10.lst ignoredabc nohglinesrev.lst nohglinesrev.lst.orig
+nohglinesrev.lst:['hsz/nib/!#hijkl', 'hsz L- etalume', '}', 'opertset zx.rat.opertset fJc rat', '']
+{{{2 2 Undo
+ablines.lst addeddef alines.lst blines.lst chgrepo.zsh clines.lst datelines.lst directory dlines.lst emerged.lst glines.lst glinescopy.lst hglines.lst hglinesrev.lst ignored10.lst ignoredabc nohglinesrev.lst
+addeddef:['Def', '']
+nohglinesrev.lst:['hsz/nib/!#', 'hsz L- etalume', 'opertset zx.rat.opertset fJc rat', 'abc', '']
+{{{2 2 Redo
+ablines.lst addeddef addeddef.orig alines.lst blines.lst chgrepo.zsh clines.lst datelines.lst directory dlines.lst emerged.lst glines.lst glinescopy.lst hglines.lst hglinesrev.lst ignored10.lst ignoredabc nohglinesrev.lst nohglinesrev.lst.orig
+addeddef:['Defbcdef', '']
+nohglinesrev.lst:['hsz/nib/!#hijkl', 'hsz L- etalume', '}', 'opertset zx.rat.opertset fJc rat', '']
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.