52

When a previous Vim session crashed, you are greeted with the "Swap file ... already exists!" for each and every file that was open in the previous session.

Can you make this Vim recovery prompt smarter? (Without switching off recovery!) Specifically, I'm thinking of:

  • If the swapped version does not contain unsaved changes and the editing process is no longer running, can you make Vim automatically delete the swap file?
  • Can you automate the suggested process of saving the recovered file under a new name, merging it with file on disk and then deleting the old swap file, so that minimal interaction is required? Especially when the swap version and the disk version are the same, everything should be automatic.

I discovered the SwapExists autocommand but I don't know if it can help with these tasks.

Bruno De Fraine
  • 45,466
  • 8
  • 54
  • 65
  • See http://valmikam.blogspot.com/2010/09/vim-auto-backup-configuration.html for a copy pastable solution. –  Aug 28 '11 at 01:03

7 Answers7

38

I have vim store my swap files in a single local directory, by having this in my .vimrc:

set directory=~/.vim/swap,.

Among other benefits, this makes the swap files easy to find all at once. Now when my laptop loses power or whatever and I start back up with a bunch of swap files laying around, I just run my cleanswap script:

TMPDIR=$(mktemp -d) || exit 1
RECTXT="$TMPDIR/vim.recovery.$USER.txt"
RECFN="$TMPDIR/vim.recovery.$USER.fn"
trap 'rm -f "$RECTXT" "$RECFN"; rmdir "$TMPDIR"' 0 1 2 3 15
for q in ~/.vim/swap/.*sw? ~/.vim/swap/*; do
  [[ -f $q ]] || continue
  rm -f "$RECTXT" "$RECFN"
  vim -X -r "$q" \
      -c "w! $RECTXT" \
      -c "let fn=expand('%')" \
      -c "new $RECFN" \
      -c "exec setline( 1, fn )" \
      -c w\! \
      -c "qa"
  if [[ ! -f $RECFN ]]; then
    echo "nothing to recover from $q"
    rm -f "$q"
    continue
  fi
  CRNT="$(cat $RECFN)"
  if diff --strip-trailing-cr --brief "$CRNT" "$RECTXT"; then
      echo "removing redundant $q"
      echo "  for $CRNT"
      rm -f "$q"
  else
      echo $q contains changes
      vim -n -d "$CRNT" "$RECTXT"
      rm -i "$q" || exit
  fi
done

This will remove any swap files that are up-to-date with the real files. Any that don't match are brought up in a vimdiff window so I can merge in my unsaved changes.

--Chouser

Andy Lester
  • 91,102
  • 13
  • 100
  • 152
Chouser
  • 5,093
  • 1
  • 32
  • 24
  • What if you edit two files of the same name, for instance in two branches of an SVN repository – Nathan Fellman Sep 15 '08 at 18:44
  • 1
    When editing more than one file with the same name, Vim will go backwards in the alphabet for the extensions (*.swp, *.swo, *.swn, etc.). It should be noted that using this method disables the "swap file exists" feature. So you will *need* to use the `cleanswap` script provided above. – matpie Feb 05 '09 at 22:20
  • also see the answer by @coppit, which should save unwritten buffers? – alxndr Feb 10 '13 at 06:34
  • 2
    For anyone on OSX: Found Chouser's github for this (https://github.com/vim-scripts/cleanswap), which didn't work for me, so I forked it and made improvements: https://github.com/Kache/cleanswap – Kache Apr 30 '13 at 19:18
  • This script doesn't work. Tried it in a project folder with a bunch of swap files, and it didn't delete any. – Attila Szeremi Nov 19 '13 at 11:30
  • 4
    To make this folder work better with multiple files with the same name use the suffix `//`. Example: `set directory=~/.vim/swap//`. With this option, vim will create the swap files using the whole path to each file. – Juan Campa Feb 18 '14 at 20:07
  • This really won't work the same way as vim does by default. On shared mounts, if your laptop crashes, and you edit from another device, the swap file will be inaccessible for restoration. It's also possible for multiple users to accidentally edit a same file as well. – WhyNotHugo Apr 21 '15 at 13:12
  • When I tried this script locally, I needed to change one of the `-c` commands passed into VIM: specifically the `-c "qa"` needed to be changed to `-c "qa!"` to handle the case when the `-r` function actually recovered something. As a side note... the two `-c "w! ..."` commands did not need the `!` because they were always writing to a newly created file that was not being over-written. – CrashNeb May 29 '22 at 05:44
23

I just discovered this:

http://vimdoc.sourceforge.net/htmldoc/diff.html#:DiffOrig

I copied and pasted the DiffOrig command into my .vimrc file and it works like a charm. This greatly eases the recovery of swap files. I have no idea why it isn't included by default in VIM.

Here's the command for those who are in a hurry:

 command DiffOrig vert new | set bt=nofile | r # | 0d_ | diffthis
    \ | wincmd p | diffthis
Jack Senechal
  • 1,600
  • 2
  • 17
  • 20
  • So after loading the swap file, you call DiffOrig? – Ehtesh Choudhury Jun 11 '11 at 16:56
  • 8
    Yeah, so when the prompt comes up you hit "recover", and then type :DiffOrig. Then when you're done you save the buffer. Then to delete the swap file (because it'll still linger and bug you next time), you just type :e, which brings up the prompt again, and you can hit "delete". – Jack Senechal Jul 06 '11 at 20:53
  • 2
    This is now included with Vim – Juan Campa Feb 18 '14 at 20:05
16

The accepted answer is busted for a very important use case. Let's say you create a new buffer and type for 2 hours without ever saving, then your laptop crashes. If you run the suggested script it will delete your one and only record, the .swp swap file. I'm not sure what the right fix is, but it looks like the diff command ends up comparing the same file to itself in this case. The edited version below checks for this case and gives the user a chance to save the file somewhere.

#!/bin/bash

SWAP_FILE_DIR=~/temp/vim_swp
IFS=$'\n'

TMPDIR=$(mktemp -d) || exit 1
RECTXT="$TMPDIR/vim.recovery.$USER.txt"
RECFN="$TMPDIR/vim.recovery.$USER.fn"
trap 'rm -f "$RECTXT" "$RECFN"; rmdir "$TMPDIR"' 0 1 2 3 15
for q in $SWAP_FILE_DIR/.*sw? $SWAP_FILE_DIR/*; do
  echo $q
  [[ -f $q ]] || continue
  rm -f "$RECTXT" "$RECFN"
  vim -X -r "$q" \
      -c "w! $RECTXT" \
      -c "let fn=expand('%')" \
      -c "new $RECFN" \
      -c "exec setline( 1, fn )" \
      -c w\! \
      -c "qa"
  if [[ ! -f $RECFN ]]; then
    echo "nothing to recover from $q"
    rm -f "$q"
    continue
  fi
  CRNT="$(cat $RECFN)"
  if [ "$CRNT" = "$RECTXT" ]; then
      echo "Can't find original file. Press enter to open vim so you can save the file. The swap file will be deleted afterward!"
      read
      vim "$CRNT"
      rm -f "$q"
  else if diff --strip-trailing-cr --brief "$CRNT" "$RECTXT"; then
      echo "Removing redundant $q"
      echo "  for $CRNT"
      rm -f "$q"
  else
      echo $q contains changes, or there may be no original saved file
      vim -n -d "$CRNT" "$RECTXT"
      rm -i "$q" || exit
  fi
  fi
done
coppit
  • 169
  • 1
  • 5
  • 1
    Type two hours without saving? You should fix your attitude to saving, not accepted script. I save automatically when I pause to think. – ZyX Apr 30 '11 at 10:53
  • 25
    Whether it's two hours or two seconds, silently losing data is unacceptable and he was absolutely right to fix it. There's no need to be condescending. – cecilkorik Jan 11 '12 at 20:33
  • 2
    Danger: if you try to use this script on .swp files without setting a central swap file dir (e.g., changing `SWAP_FILE_DIR` to `.`), it will **delete** many of your files. So don't do it. Or take out the `$SWAP_FILE_DIR/*` in the `for` loop. – Dan Jul 21 '14 at 17:29
4

Great tip DiffOrig is perfect. Here is a bash script I use to run it on each swap file under the current directory:

#!/bin/bash

swap_files=`find . -name "*.swp"`

for s in $swap_files ; do
        orig_file=`echo $s | perl -pe 's!/\.([^/]*).swp$!/$1!' `
        echo "Editing $orig_file"
        sleep 1
        vim -r $orig_file -c "DiffOrig"
        echo -n "  Ok to delete swap file? [y/n] "
        read resp
        if [ "$resp" == "y" ] ; then
                echo "  Deleting $s"
                rm $s
        fi
done

Probably could use some more error checking and quoting but has worked so far.

kzh
  • 19,810
  • 13
  • 73
  • 97
Mark Grimes
  • 547
  • 4
  • 8
0

I prefer to not set my VIM working directory in the .vimrc. Here's a modification of chouser's script that copies the swap files to the swap path on demand checking for duplicates and then reconciles them. This was written rushed, make sure to evaluate it before putting it to practical use.

#!/bin/bash

if [[ "$1" == "-h" ]] || [[ "$1" == "--help" ]]; then
   echo "Moves VIM swap files under <base-path> to ~/.vim/swap and reconciles differences"
   echo "usage: $0 <base-path>"
   exit 0
fi

if [ -z "$1" ] || [ ! -d "$1" ]; then
   echo "directory path not provided or invalid, see $0 -h"
   exit 1
fi

echo looking for duplicate file names in hierarchy
swaps="$(find $1 -name '.*.swp' | while read file; do echo $(basename $file); done | sort | uniq -c | egrep -v "^[[:space:]]*1")"
if [ -z "$swaps" ]; then
   echo no duplicates found
   files=$(find $1 -name '.*.swp')
   if [ ! -d ~/.vim/swap ]; then mkdir ~/.vim/swap; fi
   echo "moving files to swap space ~./vim/swap"
   mv $files ~/.vim/swap
   echo "executing reconciliation"
   TMPDIR=$(mktemp -d) || exit 1
   RECTXT="$TMPDIR/vim.recovery.$USER.txt"
   RECFN="$TMPDIR/vim.recovery.$USER.fn"
   trap 'rm -f "$RECTXT" "$RECFN"; rmdir "$TMPDIR"' 0 1 2 3 15
   for q in ~/.vim/swap/.*sw? ~/.vim/swap/*; do
     [[ -f $q ]] || continue
     rm -f "$RECTXT" "$RECFN"
     vim -X -r "$q" \
         -c "w! $RECTXT" \
         -c "let fn=expand('%')" \
         -c "new $RECFN" \
         -c "exec setline( 1, fn )" \
         -c w\! \
         -c "qa"
     if [[ ! -f $RECFN ]]; then
       echo "nothing to recover from $q"
       rm -f "$q"
       continue
     fi
     CRNT="$(cat $RECFN)"
     if diff --strip-trailing-cr --brief "$CRNT" "$RECTXT"; then
         echo "removing redundant $q"
         echo "  for $CRNT"
         rm -f "$q"
     else
         echo $q contains changes
         vim -n -d "$CRNT" "$RECTXT"
         rm -i "$q" || exit
     fi
   done
else
   echo duplicates found, please address their swap reconciliation manually:
   find $1 -name '.*.swp' | while read file; do echo $(basename $file); done | sort | uniq -c | egrep '^[[:space:]]*[2-9][0-9]*.*'
fi
Mario Aguilera
  • 1,146
  • 1
  • 9
  • 16
0

I have this on my .bashrc file. I would like to give appropriate credit to part of this code but I forgot where I got it from.

mswpclean(){

for i in `find -L -name '*swp'`
do
    swpf=$i
    aux=${swpf//"/."/"/"}
    orif=${aux//.swp/}
    bakf=${aux//.swp/.sbak}

    vim -r $swpf -c ":wq! $bakf" && rm $swpf
    if cmp "$bakf" "$orif" -s
    then rm $bakf && echo "Swap file was not different: Deleted" $swpf
    else vimdiff $bakf $orif
    fi
done

for i in `find -L -name '*sbak'`
do
    bakf=$i
    orif=${bakf//.sbak/}
    if test $orif -nt $bakf
    then rm $bakf && echo "Backup file deleted:" $bakf
    else echo "Backup file kept as:" $bakf
    fi
done }

I just run this on the root of my project and, IF the file is different, it opens vim diff. Then, the last file to be saved will be kept. To make it perfect I would just need to replace the last else:

else echo "Backup file kept as:" $bakf

by something like

else vim $bakf -c ":wq! $orif" && echo "Backup file kept and saved as:" $orif

but I didn't get time to properly test it.

Hope it helps.

Miguel
  • 1,293
  • 1
  • 13
  • 30
0

find ./ -type f -name ".*sw[klmnop]" -delete

Credit: @Shwaydogg https://superuser.com/questions/480367/whats-the-easiest-way-to-delete-vim-swapfiles-ive-already-recovered-from

Navigate to directory first

Community
  • 1
  • 1
user3386050
  • 146
  • 1
  • 10