Match parens with Emacs builtins
posted on 2023-06-02
When I was using ViM one of the most used movement keys was the jump-to-matching-parens (%
) one. I really liked that it was symmetrical – I could jump to a matching parenthesis and pressing the key once more it jumped right back were I was. This was one of the movement keys I felt the lack of when I stopped using Emacs evil-mode. Emacs do have a really useful forward-sexp
and backward-sexp
functions that can be used to get similar functionality but they are not symmetrical. One can jump forward-sexp
and executing backward-sexp
will not bring you to the previous position – it all depends on the context of the s-expression
you have jumped to.
I have used external packages to provide similar functionality, the last one was fingertip – lightweight package that uses tree-sitter
and one of its many functions was match-parens. It had some kind of fallback that worked well with modes that do not use tree-sitter
(like emacs-lisp-mode
), but it stopped working in non-tree-sitter modes after one of the updates. Instead of trying to bring this fallback mechanism back (not even sure if it was not an error and not intended behaviour) I quickly rolled my own function using the Emacs builtins and made it behave like I remember the ViM’s %
key.
(defun cc/jump-to-matching-paren () "Jumps to a matching parenthesis using forward/backward-sexp functions." (interactive) (let* ((cur-char (char-after)) (parens-begin '(?\( ?\[ ?\{ ?\<)) (parens-end '(?\) ?\] ?\} ?\>))) (cond ;; current character ends parenthesis -- we jump backward. ((memq cur-char parens-begin) ;; even if forward-sexp would position cursor after the paren ;; we don't need to backward-char as this function and Emacs ;; highlighting do treat such a parenthesis-before-cursor ;; as a candidate to match with previous one -- i.e. it works ;; as intended even though it is asymmetrical (forward-sexp)) ;; current character begin parenthesis -- we jump forward. ;; OR: previous character ends parenthesis and current is not parenthesis, ;; this may happen at the end of line when we want to jump to matching last ;; parenthesis when there are few parenthesis near themselves. ((or (memq cur-char parens-end) (memq (char-before) parens-end)) (backward-sexp) ;; when only one sexp is on the line, backward-sexp will position cursor ;; after the paren (i.e. inside the paren); we make sure we go back to the actual ;; paren when this happens. (when (not (memq (char-after) parens-begin)) (backward-char)) ) (t (message "Current character doesn't match known parenthesis „([{<>}])”.")))))
The function only works for parentheses, square braces, curly braces and greater than and lesser than signs, but that’s all I need. Binding the function to %
character on my modal key-map achieves exactly what I felt lacking after parting with evil-mode
. Hope it could be of use to someone else as well.
Happy hacking!