Question

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.

Was it helpful?

Solution

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

OTHER TIPS

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

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

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.

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

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.

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

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top