bling / fzf.el

A front-end for fzf
GNU General Public License v3.0
366 stars 50 forks source link

Make `(fzf/start nil)` not permanently break fzf #16

Closed brandon-rhodes closed 7 years ago

brandon-rhodes commented 7 years ago

I repeatedly had the problem of fzf erroring with:

(wrong-type-argument stringp nil)

and not working again until I restarted Emacs. Turning on tracebacks with M-x toggle-debug-on-error showed that the error, confusingly, was in a call that did not even have a nil argument:

start-process("fzf" #<buffer *fzf*> "/bin/sh" "-c" "stty..." "fzf" "-x")

The start-process C code, however, uses another value in addition to its explicit arguments: the current buffer’s default-directory. I added some debug statements and verified that once the current directory of the *fzf* buffer first becomes nil, fzf never recovers. In other words, the sequence of calls:

(fzf/start "~/project")    ;; works fine
(fzf/start nil)            ;; dies with wrong-type-argument, as expected
(fzf/start "~/project")    ;; also dies with wrong-type-argument!

does not only raise wrong-type-argument in the second call as one would expect, but in the third and all subsequent calls, because the *fzf* buffer somehow remembers the nil working directory for the rest of its lifetime.

The solution is to replace the fzf/start local variable:

(let ((default-directory directory))

with the more emphatic action of reaching inside of the *fzf* buffer itself and setting its default-directory variable:

(with-current-buffer buf
  (setq default-directory directory))

With this improvement, fzf recovers perfectly from a nil argument on its very next call if a directory is provided instead.

How did I wind up ever calling fzf with a nil argument to begin with? The answer is that I have a key bound to a function:

(defun fzf-repository ()
  "Run the fzf file selection tool in the current repository."
  (interactive)
  (fzf-directory (vc-git-root default-directory)))

that usually passes fzf a directory, but when I accidentally invoke it outside a repository, passes nil instead. I am going to fix my macro so that it detects nil and prints a reasonable error message instead of passing nil on to fzf. But, as a second layer of defense, I would love to see this pull request land too, to protect fzf from all other situations where users might send it nil by accident.

Note that most of this pull request simply un-indents everything inside the (let).