指定ディレクトリ下のファイルを更新時に自動アップロードするVimScript

ローカルのPC環境で,サーバーにアップロードすることを前提としたファイル,例えばHTMLファイルやCGIスクリプトなどを編集するときは,表示の確認やスクリプトの動作確認のために,以下のプロセスを繰り返すことになります.

  1. ファイルを更新
  2. サーバーにアップロード
  3. (動作|表示)確認

更新したファイルをサーバーにファイルをアップロードする際には,Linuxではftp,scpやrsyncといったコマンド,WindowsではFFFTPWinSCPなどのツールを使うことが多いと思います.このとき問題になるのが

たとえコマンド1回で済むとしても,ファイルを更新するたびにアップロードして確認するのが,激しく面倒くさい

ということです.個人的にこの面倒くささを解消したくて,過去に色々と対応策を試しましたが,どれも一長一短だというのが結論です.

リモートに編集環境を構築する場合の問題点

  • ファイルの編集に必要なソフトウェアをサーバーにインストールする権限が必要
  • サーバーが複数ある場合,編集環境がバラバラになりがち

ローカルに実行環境を構築する場合の問題点

  • ローカルとリモートの実行環境で,ソフトウェアやライブラリのバージョンを合わせる手間の発生
  • 編集したファイルを最終的にはアップロードしなければならない

ftpfs,sshfsなどでリモートディレクトリをローカルにマウントする際の問題点

  • 接続先の環境や回線状況によってはエディタの動作が不安定になる場合がある

そこで,上記「2. サーバーにアップロード」のプロセスを自動化するために,「特定のsディレクトリにあるファイルが更新された直後に,バックグラウンドで対応するリモートホストディレクトリにファイルがアップロードされる」VimScriptを書いてみました.Pythonを使っているので,お使いのVimが+pythonオプションを有効にしてコンパイルされている必要があります.

Vim6.1のVimScriptはデータ型として文字列と数字しかサポートされていないため,あまり複雑なことはやりにくいのに対して,Python拡張を使うとリスト(配列)や辞書(ハッシュ)が標準で使えるので,目的とする処理を書きやすいというメリットがあります.

autocmd BufWritePost * :call SyncFile()
function! SyncFile()
python << PYTHONEOF
import vim
import os
upload_settings = {
  "/home/username/srcdir": "scp://hostname/public_html/",
  "/home/username/srcdir2": "scp://hostname2/public_html/",
}
fullpath = vim.current.buffer.name
directory, filename = os.path.dirname(fullpath), os.path.basename(fullpath)
if directory in upload_settings:
  vim.command("silent Nwrite "+upload_settings[directory]+filename)
PYTHONEOF
endfunction

スクリプト中のupload_settingsは各自の環境に合わせて変更してください.

"ローカルのディレクトリ": "プロトコル://リモートのホスト名/ディレクトリ/”

という形式になっています.

この設定を.vimrcに追加すると,指定したディレクトリにあるファイルが更新(:w<CR>)された直後に,バックグラウンドで対応するリモートホストディレクトリにファイルがアップロードされます.この結果としてローカルとリモートのディレクトリの同期を,リアルタイムかつシームレスにとることが可能になります.なお,scp://などのプロトコルVimがサポートする範囲でftp://などに変更することが可能です.

ちなみに「3. (動作|表示)確認」のプロセスは,以下のma.la氏によるAutoReload.user.jsというGreaseMonkeyスクリプトで自動化できます.これを併用すると開発プロセスがさらに快適になるので,おすすめです

2007/11/08 3:10追記

コメント欄のns9tks氏による「Vim7では素のVimScriptでもリストや辞書が使える」というご指摘を受けて,Python拡張を使わないバージョンも作ってみました.こちらはVim7であれば+pythonオプション無しでコンパイルされたものでも動作するので,Vim7をお使いの場合はこちらがおすすめです.

autocmd BufWritePost * :call SyncFile()
function! SyncFile()
let upload_settings = { "/home/username/srcdir": "scp://hostname/public_html/" }
let directory = expand("%:p:h")
if has_key(upload_settings, directory)
  exec "silent Nwrite ".upload_settings[directory]."%"
endif
endfunction