Storax

Soon to be a major emacs mode.

Fast search directory access

I don't know whether this is cool or totally stupid. It's 1am so I'm almost at the end of the "eat sleep code repeat" cycle. I was on my way to bed when I had an idea for making searching a microsecond faster.

I use helm-ag a lot to search stuff in my projects. Spacemacs also comes with a handy search in current project function. The thing is, that I'm often not in the right buffer for that or I want to search in another project. I found that there are a couple of directories I frequently search. It's not so hard to create functions for each directory but that's cumbersome. So here is my plan:

Generate a bunch of keybindings to a list of directories to search in.

First we need a mapping of keys to directories. Also a prefix for the keybinding might be a good idea:

(defvar helm-ag-in-dir-prefix "sa SPC "
  "Prefix for all keybindings of `helm-ag-in-dir-alist'")
(defvar helm-ag-in-dir-alist '(("e" . "~/projects/emaci")
                                ("o" . "~/Documents/org"))
  "Mapping of keys to directories for fast search access with `helm-ag-in-dir'.")

Ok, so far so easy. Let's define a function that loops over all entries and creates a function and a keybinding. Here is the basic loop:

(defun create-helm-ag-in-dir-bindings (dir-alist)
  "Create keybindings for searching dirs with `helm-ag'.

The given DIR-ALIST should map keybindings to directories.
All keybindings are prepended with `helm-ag-in-dir-prefix'"
  (dolist (keydir dir-alist)
    (let* ((key (car keydir))
           (dir (cdr keydir))
           (keychord (concat helm-ag-in-dir-prefix key)))
      ...)))

But how do we create functions on the fly? Well, there is lambda or defun. lambda is very easy to use but if you assign it to a keybinding which-key will only display lambda in the interface. That's not really descriptive. We need to assign a name to that lambda. defun does that, but we can also work around that. Here is how you define a lambda:

(lambda () "docstring" (interactive) (message "cool lambda"))

This will return a cons cell or a function cell. The car is lambda and the cdr is (nil "docstring" (interactive) (message "cool lambda")). What we need is a new symbol, which will act as the name and then assign it. To create a symbol we can use make-symbol. The function takes a string which we can construct from the directory.

To reliably get the leaf directory I use:

(file-name-base (directory-file-name dir))

directory-file-name makes sure that /path/directory and /path/directory/ both work.

Another thing to keep in mind is that when we create the lambda, we have to keep the scope of variables in mind. This will fail:

(setq mylambda
      (let ((foo "foobar"))
        (lambda () (message "foo: %s" foo))))
(funcall mylambda)
Symbol's value as variable is void: foo

Emacs lisp has a weird and powerful backquote feature. You can quote something but selectively evaluate an expression:

(setq mylambda
      (let ((foo "foobar"))
        `(lambda () (message "foo: %s" ,foo))))
(funcall mylambda)
"foo: foobar"

Use a comma to evaluate an expression. Basically foo get's replaced with the value foobar. That's more like it. We can use fset to assign a lambda to another symbol. So putting it all together:

(defun create-helm-ag-in-dir-bindings (dir-alist)
  "Create keybindings for searching dirs with `helm-ag'.

The given DIR-ALIST should map keybindings to directories.
All keybindings are prepended with `helm-ag-in-dir-prefix'"
  (dolist (keydir dir-alist)
    (let* ((key (car keydir))
           (dir (cdr keydir))
           (keychord (concat helm-ag-in-dir-prefix key))
           (symname (concat "storax-helm-ag-" (file-name-base (directory-file-name dir))))
           (sym (make-symbol symname))
           (lf `(lambda ()
                  ,(concat "Search in " dir " with `helm-ag'.")
                  (interactive)
                  (storax/helm-ag-in-dir ,dir))))
      (fset sym lf)
      (spacemacs/set-leader-keys keychord sym))))

We use the backquote construct to also create the docstring of the lambda procedurally. Btw, let* let's you reference variables in definitions below.

Now we just have to call that function of ours:

(spacemacs/declare-prefix storax-helm-ag-dirs-prefix "dir" "helm-ag in dir")
(storax/create-helm-ag-bindings storax-helm-ag-dirs-alist)

If you don't use spacemacs, don't worry about the first line but you also have to replace spacemacs/set-leader-keys in our create-helm-ag-in-dir-bindings function with gobal-set-key.

/assets/blog/2016/05/03/fast-search-directory-access/helm-ag-fast-which-key.png

Good night! Gotta work tomorrow.

Comments

comments powered by Disqus