5

Using the backup option, I'd like vim to keep the backup of a file until that file is closed, instead of overwriting the backup every time the file is saved.

I want Vim to do that because I often find myself wishing that I could see a file as it was at the beginning of the editing session (for files that I don't want to keep under version control).

NB: the backup should become a copy of the file as the file was when I last saved it before closing. That is, if I edit a file and quit without saving, the backup should not be written.

  • So you're saying you don't want to write to the actual file until you close the buffer? Can I ask why you want this behavior? – Tumbler41 Feb 19 '18 at 17:40
  • @Tumbler41 Nope, I'm saying I don't want to write to the backup until I close the buffer, so the backup remains a copy of the actual file as it was at the beginning of the editing session. As it is now the backup is the copy of the last saved version of the actual file. – Arch Stanton Feb 19 '18 at 17:47
  • And the backup that you're referring to is the .swp file? – Tumbler41 Feb 19 '18 at 17:51
  • I'm referring to the file that ends in ~ – Arch Stanton Feb 19 '18 at 17:52
  • 1
    Not sure if the backup option can do this by default. It might be easier just to make your own backup using the BufReadPre and BufLeave autocmds. – Tumbler41 Feb 19 '18 at 18:08
3
+50

I'm pretty sure it's not possible to use Vim's backup feature to do this, so instead I've written a proof of concept that achieves your goal (of being able to see the changes made since the beginning of the session) more directly, with autocommands.

It writes out a file containing the current contents of the buffer when the buffer is read, and then deletes it when you unload the buffer (with e.g. :bdelete or by quitting Vim).

augroup MyInitialState
  au!
  au BufReadPost * call CreateInitialState()
  au BufUnload * call DeleteInitialState()
augroup END

function! CreateInitialState() abort
  let file_name = BackupFileName('<afile>')
  if filewritable(file_name) == 1 ||
      \ (!glob(file_name) &&
      \ filewritable(fnamemodify(file_name, ':p:h')) == 2)
    execute "write!" file_name
  endif
endfunction

function! DeleteInitialState() abort
  let file_name = BackupFileName('<afile>')
  if filewritable(file_name) == 1
    call delete(file_name)
  endif
endfunction

function! BackupFileName(file_name) abort
  return expand(a:file_name) . '.initstate'
endfunction

It will need a bit of tidying up if you want a really robust solution — for example it doesn't really work with Vim's default :set nohidden (one solution would be to keep a list of backup files and delete them all when quitting Vim instead of individually on buffer unload), and I'd imagine there will be edge cases that need handling — but it should be enough to get you started.

Extra Credit

Here's a :CompareInitialState command you can use to check your current file content against your "backups".

function! CompareInitialState() abort
  let file_name = BackupFileName('%')
  diffthis
  vnew
  setlocal buftype=nofile
  setlocal bufhidden=delete
  setlocal noswapfile
  execute "read" file_name
  1delete
  diffthis
  wincmd p
endfunction

command! CompareInitialState :call CompareInitialState()

Older version

Here's my initial version that I wrote before it occurred to me that you don't actually need the "backup" file to exist when you're not in Vim.

augroup MyInitialState
  au!
  au BufReadPost * write! <afile>.initstate
  au BufUnload * call DeleteInitial()
augroup END

function! DeleteInitial() abort
  let file_name = expand('<afile>') . '.initstate'
  echom file_name
  if filewritable(file_name)
    call delete(file_name)
  endif
endfunction
| improve this answer | |
1

------ EDIT ------

Sorry, I think I've mistaken the functionality of BackupDir... maybe what you need it's to call copy on BufWinLeave (?):

autocmd! BufWinLeave * :!cp % %.bak

Re-Edit As @Rich has suggested in comments, it seems easier to use :w instead of :!cp. Also the SwitchToNextBuffer() function here could be needed to iterate over all buffers.

------ original answer ------

(Disclaimer: I'm a ultra-noob, but) I looked for "vim manual backup" (found this BackupDir() function) and "vim on quit event" (found this) so maybe something like that would work (I didn't try):

function! BackupDir()
   if has('win32') || has('win64')
      let l:backupdir=$VIM.'/backup/'.
               \substitute(expand('%:p:h'), '\:', '~', '')
   else
      let l:backupdir=$HOME.'/.vim/backup/'.
               \substitute(expand('%:p:h'), '^'.$HOME, '~', '')
   endif

   if !isdirectory(l:backupdir)
      call mkdir(l:backupdir, 'p', 0700)
   endif

   let &backupdir=l:backupdir
   let &backupext=strftime('~%Y-%m-%d_%H-%M-%S~')
endfunction

autocmd! BufWinLeave * call BackupDir()
| improve this answer | |
  • You don't need to check has('win64'): has('win32') also returns true in 64-bit Vim. – Rich Feb 20 '18 at 11:51
  • Sorry, just copy-pasted... I-have-no-idea-what-i'm-doing, also I don't even know if that function really makes any backup at all (see EDIT). – raul.vila Feb 20 '18 at 11:53
  • Rather than using an operating-system-specific external command !cp, you could just use Vim's normal :write command: :w %.bak – Rich Feb 20 '18 at 12:00
  • @Rich Yeah, good point. I'm also looking at SwitchToNextBuffer() function here where they cycle through all buffers. Maybe it would be needed to make the backup of all open buffers. – raul.vila Feb 20 '18 at 12:02
1

Isn't it simpler to use a wrapper that'll do exactly that? You might want to check for permissions in final solution but proof of concept:

#!/bin/bash

CURRENT="$1"
BACKUP="$1".backup

cp "$CURRENT" "$BACKUP"
vi "$CURRENT"
vimdiff "$BACKUP" "$CURRENT"
rm "$BACKUP"

None of the commands is forked so shell will wait for execution of the previous line to finish before invoking the next one, seems exactly as what you need. Save it to ~/bin/vimbak and add permissions to run:

chmod u+x ~/bin/vimbak

Then just:

vimbak file_you_want_to_edit
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.