Tweaking deft: no spaces in file names

Building on my earlier posts on deft, I have another minor improvement I wanted to make. I really like the way deft creates files from the search string, generating the file name from the string. The only problem is that I don’t want spaces in my file names, which I may well have in my deft search string.

My solution was to add the following advice to the function in deft that creates new files, telling it to replace spaces in the search string with hyphens before creating the file.

;;advise deft-new-file-named to replace spaces in file names with -
(defun bjm-deft-strip-spaces (orig-fun &rest file)
  ;;this probably doesn't need to be done in three steps!
  (setq name (pop file))
  (setq name (replace-regexp-in-string " " "-" name))
  (push name file)
  (apply orig-fun file)

(advice-add 'deft-new-file-named :around #'bjm-deft-strip-spaces)

Maybe someone can help me improve my novice lisp programming here, as I am sure there must be a more direct way to use replace-regexp-in-string here!

I think this is a nice illustration of how the user can relatively easily adjust the behaviour of a function which is quite buried inside a package. You have to be a bit careful when advising functions that you don’t introduce unwanted behaviour if that function is called in a different context, but in this case it should be safe as the function deft-new-file-named is not used elsewhere.

In a comment on my earlier post Kaushal Modi linked to his solution to this, which also removes any upper cases.


The wise commenters below pointed out that my function above is a bit dangerous, since the variable name that I use has global scope so could modify another variable called name elsewhere. This can be solved by using (let ...) to give the variable local scope, as Kaushal Modi points out, but I ended up going with the compact version suggested by Noam Postavsky:

;;advise deft-new-file-named to replace spaces in file names with -
(defun bjm-deft-strip-spaces (args)
  "Replace spaces with - in the string contained in the first element of the list args. Used to advise deft's file naming function."
  (list (replace-regexp-in-string " " "-" (car args)))
(advice-add 'deft-new-file-named :filter-args #'bjm-deft-strip-spaces)

Which uses filter-args in the advice function to specify that my bjm-quit-strip-spaces function is applied to the arguments to deft-new-file-named before they are passed to the latter function.

  • Kaushal Modi

    That’s a neat way of implementing it (using push and pop). I would have done it this way if I wanted to advise deft-new-file-named:

    (defun my-deft-strip-spaces (orig-fun &rest file)
    “FILE is a list containing one element: the file name string. This
    function strips off the spaces from that string element.”
    (let ((file-str (replace-regexp-in-string ” ” “-” (car file))))
    (setq file (list file-str))
    (apply orig-fun file)))

    Using the let binding creates and destroys the variable locally. Everything inside the let form will use the local var instead of a global var (if available). In you example, if a name var is used globally, it will update the global value too! (though, no one should have named a global variable called name :))

  • Rick Hanson

    Kaushal Modi
    is correct. Your use of the free variable `name` could only cause trouble for you. BTW, I’m enjoying your posts. Keep ’em coming!

  • Noam Postavsky

    You can use :filter-args instead of :around to simplify things:

    (defun my-deft-strip-spaces (args)
    (list (replace-regexp-in-string ” ” “-” (car args))))

    (advice-add ‘deft-new-file-named :filter-args #’my-deft-strip-spaces)

  • Kaushal Modi

    I got interested in getting a more customizable solution for this and have made this pull request in deft github repo. If the PR is accepted, you can have the kebab-case file naming by doing

    (setq deft-file-naming-rules ‘((nospace . “-“)
    (case-fn . downcase)))