Hiding Clojure and Elisp docstrings in Emacs

Docstrings are nice but some hours ago I just realized that they were taking up too much space, limiting my overview of the code.

I thought it would be nice to be able to simply toggle the visibility of the docstrings on and off and started googling but it was surprisingly difficult to find a good answer.

I came close with hide-lines.el but only almost, consider the following clojure function:

(defn one-plus-two
  "Some documentation
  here, btw the func looks like this: (+ one two) 
  yep, it is."
  [one two]
  (+ one two))

With the default functionality of hide-lines.el we can easily hide the first and the last line of the docstring but the middle line will be much more difficult to match. It would be so much easier if we can simply multi-line match everything between the double quotes and hide it.

Using Re Builder I finally came up with the following regex that managed to do the trick: “^[ ]*\”[\0-\377[:nonascii:]]*?\”".

Now how to use it to actually hide the docstrings? What I came up with was the following:

;;;###autoload
(defun hide-matching-regions (reg-search)
  (interactive "Hide areas matching regexp: ")
  (set (make-local-variable 'line-move-ignore-invisible) t)
  (save-excursion
    (goto-char (point-min))
    (let ((pos (re-search-forward reg-search nil t)) start-position)
      (while pos
        (setq start-position (match-beginning 0))
        (add-invisible-overlay start-position (+ 1 (point)))
        (if (eq (point) (point-max))
            (setq pos nil)
          (setq pos (re-search-forward reg-search nil t)))))))

If you examine hide-lines.el you will see that the above is very similar to the hide-matching-lines function which it is originally copied from. Of course all line related stuff have been thrown out and the magic happens through mainly re-search-forward which will incrementally catch all instances where the regex matches, it sets the point internally, hence we can use it to get the endpoint of each match. The crux is getting the start point which we do through the (match-beginning 0) call.

Through the start and end points we can apply the overlay.

That’s it! In my init.el I now have the following at the end:

(autoload 'hide-lines "hide-lines" "Hide lines based on a regexp" t)

(defun hide-docstrings ()
  (interactive)
  (hide-matching-regions "^[ ]*\"[\0-\377[:nonascii:]]*?\""))

(global-set-key (kbd "C-c C-h") 'hide-docstrings)
(global-set-key (kbd "C-c C-u h") 'show-all-invisible)

So far so good, haven’t noticed any issues yet.

Related Posts

Tags: , , , ,