Hack 8. Don't Save Bad Perl
Don't even write out your file if the Perl isn't valid!
Perl tests tend to start by checking that your code compiles. Even if the tests don't check, you'll know it pretty quickly as all your code collapses in a string of compiler errors. Then you have to fire up your editor again and track down the problem. It's simple, though, to tell Vim that if your Perl code won't compile, it shouldn't even write it to disk.
Even better, you can load Perl's error messages back into Vim to jump right to the problem spots.
Vim supports filetype plug-ins that alter its behavior based on the type of file being edited. Enable these by adding a line to your .vimrc:
filetype plugin on
Now you can put files in ~/.vim/ftplugin (My Documents\\_vimfiles\\ftplugin on Windows) and Vim will load them when it needs them. Perl plug-ins start with perl_, so save the following file as perl_synwrite.vim:
" perl_synwrite.vim: check syntax of Perl before writing " latest version at: http://www.vim.org/scripts/script.php?script_id=896 "" abort if b:did_perl_synwrite is true: already loaded or user pref if exists("b:did_perl_synwrite") finish endif let b:did_perl_synwrite = 1 "" set buffer :au pref: if defined globally, inherit; otherwise, false if (exists("perl_synwrite_au") && !exists("b:perl_synwrite_au")) let b:perl_synwrite_au = perl_synwrite_au elseif !exists("b:perl_synwrite_au") let b:perl_synwrite_au = 0 endif "" set buffer quickfix pref: if defined globally, inherit; otherwise, false if (exists("perl_synwrite_qf") && !exists("b:perl_synwrite_qf")) let b:perl_synwrite_qf = perl_synwrite_qf elseif !exists("b:perl_synwrite_qf") let b:perl_synwrite_qf = 0 endif "" execute the given do_command if the buffer is syntactically correct perl "" -- or if do_anyway is true function! s:PerlSynDo(do_anyway,do_command) let command = "!perl -c" if (b:perl_synwrite_qf) " this env var tells Vi::QuickFix to replace "-" with actual filename let $VI_QUICKFIX_SOURCEFILE=expand("%") let command = command . " -MVi::QuickFix" endif " respect taint checking if (match(getline(1), "^#!.\\\\+perl.\\\\+-T") = = 0) let command = command . " -T" endif exec "write" command silent! cgetfile " try to read the error file if !v:shell_error || a:do_anyway exec a:do_command set nomod endif endfunction "" set up the autocommand, if b:perl_synwrite_au is true if (b:perl_synwrite_au > 0) let b:undo_ftplugin = "au! perl_synwrite * " . expand("%") augroup perl_synwrite exec "au BufWriteCmd,FileWriteCmd " . expand("%") . " call s:PerlSynDo(0,\\"write <afile>\\")" augroup END endif "" the :Write command command -buffer -nargs=* -complete=file -range=% -bang Write call \\ s:PerlSynDo("<bang>"= ="!","<line1>,<line2>write<bang> <args>")
Running the Hack
When you edit a Perl file, use :W instead of :w to write the file. If the file fails to compile with perl -c, Vim will refuse to write the file to disk. You can always fall back to :w, or use :W! to check, but write out the file even if it has bad syntax.
Hacking the Hack
The plug-in has two configurable options that you can set in your .vimrc. The first is perl_synwrite_au, which hooks the :W command's logic onto an autocommand that fires when you use :w. This will let you use :w for any sort of file, but still enjoy the syntax error catching of the plug-in. It's a little twitchy, though, when you start passing arguments to :w, so you're probably best off just using :W.
The second is perl_synwrite_qf, which lets the plug-in use the Vi::QuickFix module to parse perl's errors into a format that Vim can use to jump to problems. With this option set, perl will write errors to error.err, which Vim will read when you use its QuickFix commands. :help quickfix lists all of the commands, but the two most useful are :cf to jump to the first syntax error and :copen to open a new window listing all your errors. In that new window, you can move to the error that interests you, hit Enter, and jump to the error in your buffer.
Vi::QuickFix relies on tying the standard error stream, which isn't possible in Perl 5.6, so if you use perl_synwrite.vim in more than one development environment, you might want to set the perl_synwrite_qf option dynamically:
silent call system("perl -e0 -MVi::QuickFix") let perl_synwrite_qf = ! v:shell_error
In other words, if Perl can't use the Vi::QuickFix module, don't try using it for the plug-in.
Emacs users, you can use a minor mode to run a Perl syntax check before saving the file. Whenever perl -c fails, Emacs will not save your file. To save files anyway, toggle the mode off with M-x perl-syntax-mode. See "Enforce Local Style" [Hack #7] for a related tip on automatically tidying your code when saving.
(defvar perl-syntax-bin "perl" "The perl binary used to check syntax.") (defun perl-syntax-check-only () "Returns a either nil or t depending on whether \\ the current buffer passes perl's syntax check." (interactive) (let ((buf (get-buffer-create "*Perl syntax check*"))) (let ((syntax-ok (= 0 (save-excursion (widen) (call-process-region (point-min) (point-max) perl-syntax-bin nil buf nil "-c"))) )) (if syntax-ok (kill-buffer buf) (display-buffer buf)) syntax-ok))) (defvar perl-syntax-mode nil "Check perl syntax before saving.") (make-variable-buffer-local 'perl-syntax-mode) (defun perl-syntax-write-hook () "Check perl syntax during 'write-file-hooks' for 'perl-syntax-mode'" (if perl-syntax-mode (save-excursion (widen) (mark-whole-buffer) (not (perl-syntax-check-only))) nil)) (defun perl-syntax-mode (&optional arg) "Perl syntax checking minor mode." (interactive "P") (setq perl-syntax-mode (if (null arg) (not perl-syntax-mode) (> (prefix-numeric-value arg) 0))) (make-local-hook 'write-file-hooks) (if perl-syntax-mode (add-hook 'write-file-hooks 'perl-syntax-write-hook) (remove-hook 'write-file-hooks 'perl-syntax-write-hook))) (if (not (assq 'perl-syntax-mode minor-mode-alist)) (setq minor-mode-alist (cons '(perl-syntax-mode " Perl Syntax") minor-mode-alist))) (eval-after-load "cperl-mode" '(add-hook 'cperl-mode-hook 'perl-syntax-mode))
Hack 8. Don t Save Bad Perl