Fine-tune Curly Braces Style of Yasnippet Snippet on the Fly

Yasnippet is a good friend to help us type less and write more, whenever we write some text snippets repeatedly. And there is also an official repository called yasnippet-snippets that contains various snippets for many programming languages (modes), so that we can have many snippets in no time by installing it.

But there is a little problem when it comes to conforming to different coding styles.

Take the if snippet for example, normally it will generate code like this:

if (a) {

This is fine for a K&R style project, but will be kind of annoyed if we work on an allman style project, because we need to manually adjust the style for every if clauses generated to like this:

if (a)

How to fine-tune the coding styles according to the project requirement? Or at least curly braces styles, which is the one that matters most for me.

Maintaining two similar copies for every snippet is obviously not a good option, it's daunting and boring.

Fortunately, yasnippet provides a hook called yas-after-exit-snippet-hook, which I can take advantage of to adjust the curly braces style when needed. Below is a trivial hook that I come up with, it assumes that the snippets are in K&R style, which is the style that yasnippet-snippet takes, and I prepend { with newlines and surround } with newlines when this behaviour is asked by setting (setq w/yasnippet-c-style 'allman) .

(defun w/style-braces-in-allman (snippet)
  "Style the SNIPPET in allman brace style.

There are roughly 3 basic brace styles:
- Attached: The braces are attached to the end of the last line of the previous block. (Java).
- Broken: The braces are broken from the previous block. (Allman).
- Linux: The braces are attached except for the opening brace of a function, class, or namespace (K&R, Linux)."
  (let ((len (length snippet))
        (i 0)
        chars char new-str)
    (while (< i len)
      (setq char (aref snippet i))
      (case char
         (push ?\n chars)
         (push char chars))
         (push ?\n chars)
         (push char chars)
         (push ?\n chars))
         (push char chars)))
      (setq i (1+ i)))
    (setq new-str (replace-regexp-in-string "[\n \t]+\n"
                                            (apply #'string (nreverse chars))))

(defcustom w/yasnippet-c-style nil
  "Style of curly braces, e.g. 'allman."
  :type '(symbol))

(defun w/yasnippet-exit-hook-c ()
  (let* ((text-marker "the-yasnippet-exit-point;") ; workaround. text property is more elegant.
         (begin yas-snippet-beg)
         (end yas-snippet-end)
         (snippet (buffer-substring-no-properties begin end))
    (when (and (string-match "[{}]" snippet)
               (eq 'allman w/yasnippet-c-style))
      (insert text-marker)
      (setq end (+ yas-snippet-end (length text-marker)))

      (setq snippet (buffer-substring-no-properties begin end)) ; re-fetch content
      (setq new-snippet (w/style-braces-in-allman snippet))
      (delete-region begin end)
      (insert new-snippet)

      (goto-char begin)
      ;; re-indent it in the context
      (indent-region begin (+ end (- (length new-snippet)
                                     (length snippet))))
      (re-search-forward text-marker)
      (delete-char (- 0 (length text-marker))))))

(defun w/yasnippet-exit-hook ()
  "My yasnippet exit hook."
  (case major-mode
    ((c-mode c++-mode)

;; see
(add-to-list 'yas-after-exit-snippet-hook #'w/yasnippet-exit-hook)

Now the coding experience is much better :)


See also

comments powered by Disqus