Blame


1 dfa58710 2020-12-01 op Snippets are small templates that can be “expanded”, and are generally used to avoid typing scaffolding. Since they are a common features available on various editors and IDEs, I have confidence the reader has seen them at some point.
2 dfa58710 2020-12-01 op
3 dfa58710 2020-12-01 op Yasnippet is an Emacs package to manage and expand snippets; actually, yasnippet is probably THE Emacs package for snippets.
4 dfa58710 2020-12-01 op
5 dfa58710 2020-12-01 op => https://github.com/joaotavora/yasnippet Yasnippet repository
6 dfa58710 2020-12-01 op
7 dfa58710 2020-12-01 op (the full setup is at the end of the post)
8 dfa58710 2020-12-01 op
9 dfa58710 2020-12-01 op By default, yasnippet will expand the snippets when the TAB key is pressed. I found this to be pretty inconvenient. The TAB key is already used to indent text and to trigger the ‘complete-at-point’, and it happened multiple times that instead of indenting or triggering the completions I expanded a snippet by accident. This had to be fixed.
10 dfa58710 2020-12-01 op
11 dfa58710 2020-12-01 op Reading the yasnippet manual, I got this idea of using the space to trigger the snippet expansion. It may seem a bit crazy, but after playing a bit with it I felt that this was the way to go. To achieve it, one has to bind SPC to the ‘yas-maybe-expand’:
12 dfa58710 2020-12-01 op
13 dfa58710 2020-12-01 op ``` elisp
14 dfa58710 2020-12-01 op (define-key yas-minor-mode-map (kbd "SPC") yas-maybe-expand)
15 dfa58710 2020-12-01 op ```
16 dfa58710 2020-12-01 op
17 dfa58710 2020-12-01 op and remove the binding for TAB and ‘<tab>’ in ‘yas-minor-mode-map’.
18 dfa58710 2020-12-01 op
19 dfa58710 2020-12-01 op But this isn’t enough, as you may find pretty soon. Let’s say that you have a snippet called ‘let’ that expands into a full let binding. That snippet gets expanded also when you type ‘let’ inside a comment or a string, and that’s not what you probably want.
20 dfa58710 2020-12-01 op
21 dfa58710 2020-12-01 op To solve this issue, yasnippet provides a buffer-local variable ‘yas-buffer-local-condition’: it holds a lisp form that gets evaluated before the snippet expansion: if it evaluates to nil the expansion is cancelled (check the documentation for some other values it can return).
22 dfa58710 2020-12-01 op
23 dfa58710 2020-12-01 op ``` elisp
24 dfa58710 2020-12-01 op (defun my/inside-comment-or-string-p ()
25 dfa58710 2020-12-01 op "T if point is inside a string or comment."
26 dfa58710 2020-12-01 op (let ((s (syntax-ppss)))
27 dfa58710 2020-12-01 op (or (nth 4 s) ;comment
28 dfa58710 2020-12-01 op (nth 3 s)))) ;string
29 dfa58710 2020-12-01 op ```
30 dfa58710 2020-12-01 op
31 dfa58710 2020-12-01 op ‘syntax-ppss’ returns a list with a bunch of syntactical information. The 4th and 3rd field are if the point is inside a comment or inside a string (respectively).
32 dfa58710 2020-12-01 op
33 dfa58710 2020-12-01 op By setting ‘yas-buffer-local-condition’ to the form (mind you, it’s a quoted list!) '(not (my/inside-comment-or-string-p)) you prevent yasnippet to expand when within comments or strings.
34 dfa58710 2020-12-01 op
35 dfa58710 2020-12-01 op I’m using snippets mostly in LISPs buffers (elisp, common lisp, clojure), and there was still one thing that bothered me. I have a bunch of snippets for various special forms (defun/defn, let/let*, etc). Now, if by any chance I type a space after a preexisting ‘let’ (for instance) that let gets expanded AGAIN. So I have an additional condition to check that I’m not trying to expand something that is at the start of a list.
36 dfa58710 2020-12-01 op
37 dfa58710 2020-12-01 op The full setup is this:
38 dfa58710 2020-12-01 op
39 dfa58710 2020-12-01 op ``` elisp
40 dfa58710 2020-12-01 op (use-package yasnippet
41 dfa58710 2020-12-01 op :bind (:map yas-minor-mode-map
42 dfa58710 2020-12-01 op ("<tab>" . nil)
43 dfa58710 2020-12-01 op ("TAB" . nil))
44 dfa58710 2020-12-01 op :custom (yas-wrap-around-region t)
45 dfa58710 2020-12-01 op :config
46 dfa58710 2020-12-01 op (yas-global-mode +1)
47 dfa58710 2020-12-01 op (define-key yas-minor-mode-map (kbd "SPC") yas-maybe-expand)
48 dfa58710 2020-12-01 op
49 dfa58710 2020-12-01 op (defun my/inside-comment-or-string-p ()
50 dfa58710 2020-12-01 op "T if point is inside a string or comment."
51 dfa58710 2020-12-01 op (let ((s (syntax-ppss)))
52 dfa58710 2020-12-01 op (or (nth 4 s) ;comment
53 dfa58710 2020-12-01 op (nth 3 s)))) ;string
54 dfa58710 2020-12-01 op
55 dfa58710 2020-12-01 op (defun my/in-start-of-sexp-p ()
56 dfa58710 2020-12-01 op "T if point is after the first symbol in the list."
57 dfa58710 2020-12-01 op (save-excursion
58 dfa58710 2020-12-01 op (backward-char (length (current-word)))
59 dfa58710 2020-12-01 op (= ?\( (char-before))))
60 dfa58710 2020-12-01 op
61 dfa58710 2020-12-01 op (defun my/yas-fix-local-condition ()
62 dfa58710 2020-12-01 op (setq yas-buffer-local-condition
63 dfa58710 2020-12-01 op '(not (or (my/inside-comment-or-string-p)
64 dfa58710 2020-12-01 op (my/in-start-of-sexp-p)))))
65 dfa58710 2020-12-01 op
66 dfa58710 2020-12-01 op (mapcar (lambda (mode-hook)
67 dfa58710 2020-12-01 op (add-hook mode-hook #'my/yas-fix-local-condition))
68 dfa58710 2020-12-01 op '(emacs-lisp-mode-hook
69 dfa58710 2020-12-01 op lisp-interaction-mode-hook
70 dfa58710 2020-12-01 op clojure-mode-hook
71 dfa58710 2020-12-01 op c-mode-hook)))
72 dfa58710 2020-12-01 op ```
73 dfa58710 2020-12-01 op
74 dfa58710 2020-12-01 op There’s still an issue that I’m not sure how to fix: prevent the expansion inside specific form. For instance, writing a ‘cl-loop’ it isn’t strange to type ‘while’, but it shouldn’t get expanded, but there are various situations where snippet shouldn’t be expandend, and since these are diverse and pretty rare, I’m not bothering (not for now at least). To prevent a snippet from expansion you can always type the space as C-q SPC.