├── README.org
├── demo.gif
└── ivy-yasnippet.el
/README.org:
--------------------------------------------------------------------------------
1 | * ivy-yasnippet.el
2 | [[https://melpa.org/#/ivy-yasnippet][file:https://melpa.org/packages/ivy-yasnippet-badge.svg]]
3 |
4 | Emacs package that lets you preview yasnippet snippets with ivy.
5 |
6 | [[./demo.gif]]
7 |
8 | * Table of contents :TOC:
9 | - [[#ivy-yasnippetel][ivy-yasnippet.el]]
10 | - [[#installation][Installation]]
11 | - [[#from-melpa][From melpa]]
12 | - [[#manual][Manual]]
13 | - [[#usage][Usage]]
14 | - [[#options][Options]]
15 | - [[#license][License]]
16 |
17 | * Installation
18 | ** From melpa
19 | Use ~M-x package-install RET ivy-yasnippet RET~ to install this package from MELPA.
20 | ** Manual
21 | Install dash, ivy and yasnippet, then put path to file
22 | ~ivy-yasnippet.el~ somewhere in your ~load-path~.
23 | * Usage
24 | To use it, call ~ivy-yasnippet~ in ~yas-minor-mode~.
25 |
26 | * Options :noexport_1:
27 | ** ~ivy-yasnippet-expand-keys~
28 | Value that says how to expand keys before point.
29 | - If it's nil, never expand keys.
30 | - If it's the symbol ~always~, always try to expand keys.
31 | - If it's the symbol ~smart~, expand when a matching candidate is
32 | selected for the first time. Once a candidate whose key doesn't
33 | match whatever is before point is selected, behave like nil until
34 | the minibuffer exits.
35 |
36 | Default value is ~smart~.
37 |
38 | ** ~ivy-yasnippet-create-snippet-if-not-matched~
39 | If non-nil, allow exiting the minibuffer without exact match.
40 | Doing so will pop up a new buffer for writing a snippet.
41 |
42 | ** ~ivy-yasnippet-new-snippet~
43 | Snippet to expand when creating new snippet.
44 | During expansion, ~name~ is bound to whatever was returned by ~ivy-read~.
45 |
46 | ** ~ivy-yasnippet-key~
47 | Face used for keys.
48 |
49 | ** ~ivy-yasnippet-key-matching~
50 | Face used for keys that match whatever is before point.
51 |
52 | * License
53 | This program is free software: you can redistribute it and/or modify
54 | it under the terms of the GNU General Public License as published by
55 | the Free Software Foundation, either version 3 of the License, or
56 | (at your option) any later version.
57 |
58 | This program is distributed in the hope that it will be useful,
59 | but WITHOUT ANY WARRANTY; without even the implied warranty of
60 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
61 | GNU General Public License for more details.
62 |
63 | You should have received a copy of the GNU General Public License
64 | along with this program. If not, see .
65 |
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkcms/ivy-yasnippet/83402d91b4eba5307f71884a72df8e11cc6a994e/demo.gif
--------------------------------------------------------------------------------
/ivy-yasnippet.el:
--------------------------------------------------------------------------------
1 | ;;; ivy-yasnippet.el --- Preview yasnippets with ivy
2 |
3 | ;; Author: Michał Krzywkowski
4 | ;; URL: https://github.com/mkcms/ivy-yasnippet
5 | ;; Package-Requires: ((emacs "24.1") (cl-lib "0.6") (ivy "0.10.0") (yasnippet "0.12.2") (dash "2.14.1"))
6 | ;; Version: 0.0.1
7 | ;; Keywords: convenience
8 |
9 | ;; Copyright (C) 2018 Michał Krzywkowski
10 |
11 | ;; This program is free software; you can redistribute it and/or modify
12 | ;; it under the terms of the GNU General Public License as published by
13 | ;; the Free Software Foundation, either version 3 of the License, or
14 | ;; (at your option) any later version.
15 |
16 | ;; This program is distributed in the hope that it will be useful,
17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | ;; GNU General Public License for more details.
20 |
21 | ;; You should have received a copy of the GNU General Public License
22 | ;; along with this program. If not, see .
23 |
24 | ;;; Commentary:
25 |
26 | ;; This package allows you to select yasnippet snippets using ivy completion.
27 | ;; When current selection changes in the minibuffer, the snippet contents
28 | ;; are temporarily expanded in the buffer.
29 |
30 | ;; To use it, call M-x ivy-yasnippet (but make sure you have enabled
31 | ;; `yas-minor-mode' first).
32 |
33 | ;;; Code:
34 |
35 | (require 'ivy)
36 | (require 'yasnippet)
37 | (require 'dash)
38 | (require 'cl-lib)
39 |
40 | (defvar ivy-yasnippet--buffer nil)
41 | (defvar ivy-yasnippet--template-alist nil)
42 | (defvar ivy-yasnippet--region nil)
43 | (defvar ivy-yasnippet--region-contents nil)
44 | (defvar ivy-yasnippet--key nil)
45 | (defvar ivy-yasnippet--key-deleted nil)
46 | (defvar ivy-yasnippet--should-delete-key nil)
47 |
48 | (defgroup ivy-yasnippet nil
49 | "Preview yasnippets with ivy."
50 | :group 'ivy
51 | :group 'yasnippet)
52 |
53 | (defcustom ivy-yasnippet-expand-keys 'smart
54 | "Value that says how to expand keys before point.
55 | If it's nil, never expand keys.
56 | If it's the symbol `always`, always try to expand keys.
57 | If it's the symbol `smart`, expand when a matching candidate is selected for
58 | the first time. Once a candidate whose key doesn't match whatever is before
59 | point is selected, behave like nil until the minibuffer exits."
60 | :group 'ivy-yasnippet
61 | :type '(choice (const :tag "Never" nil)
62 | (const :tag "Always" always)
63 | (const :tag "Expand until a nonexpandable candidate\
64 | is selected" smart)))
65 |
66 | (defcustom ivy-yasnippet-create-snippet-if-not-matched t
67 | "If non-nil, allow exiting the minibuffer without exact match.
68 | Doing so will pop up a new buffer for writing a snippet."
69 | :group 'ivy-yasnippet
70 | :type 'boolean)
71 |
72 | (defcustom ivy-yasnippet-new-snippet
73 | "# name: ${1:`name`}${2:
74 | # key: ${3:key}}${4:
75 | # keybinding: ${5:keybinding}}${6:
76 | # expand-env: (${7:(var val)})}${8:
77 | # contributor: $9}
78 | # --
79 | $0`yas-selected-text`"
80 | "Snippet to expand when creating new snippet.
81 | During expansion, `name` is bound to whatever was returned by `ivy-read'."
82 | :group 'ivy-yasnippet
83 | :type 'string)
84 |
85 | (defface ivy-yasnippet-key
86 | '((t . (:inherit font-lock-type-face)))
87 | "Face used for keys."
88 | :group 'ivy-yasnippet)
89 |
90 | (defface ivy-yasnippet-key-matching
91 | '((t . (:inherit ivy-yasnippet-key :weight bold)))
92 | "Face used for keys that match whatever is before point."
93 | :group 'ivy-yasnippet)
94 |
95 | (defun ivy-yasnippet--lookup-template (name)
96 | (cdr (assoc name ivy-yasnippet--template-alist)))
97 |
98 | (defun ivy-yasnippet--revert ()
99 | (delete-region (car ivy-yasnippet--region)
100 | (cdr ivy-yasnippet--region))
101 | (goto-char (car ivy-yasnippet--region))
102 | (when ivy-yasnippet--key-deleted
103 | (insert ivy-yasnippet--key)
104 | (setq ivy-yasnippet--key-deleted nil))
105 | (setcar ivy-yasnippet--region (point))
106 | (insert ivy-yasnippet--region-contents)
107 | (setcdr ivy-yasnippet--region (point)))
108 |
109 | (defun ivy-yasnippet--expand-template (template)
110 | (deactivate-mark)
111 | (goto-char (car ivy-yasnippet--region))
112 | (cond
113 | ((not (string-equal "" ivy-yasnippet--region-contents))
114 | (push-mark (point))
115 | (push-mark (cdr ivy-yasnippet--region) nil t))
116 | ((and ivy-yasnippet--should-delete-key
117 | (not ivy-yasnippet--key-deleted)
118 | ivy-yasnippet--key
119 | (string-equal ivy-yasnippet--key (yas--template-key template)))
120 | (let ((length (length ivy-yasnippet--key)))
121 | (delete-char (- length))
122 | (cl-decf (car ivy-yasnippet--region) length)
123 | (cl-decf (cdr ivy-yasnippet--region) length)
124 | (setq ivy-yasnippet--key-deleted t)))
125 | (t
126 | (setq ivy-yasnippet--should-delete-key
127 | (eq ivy-yasnippet-expand-keys 'always))))
128 | (yas-expand-snippet (yas--template-content template)
129 | nil nil
130 | (yas--template-expand-env template)))
131 |
132 | (defun ivy-yasnippet--preview (template)
133 | (with-current-buffer ivy-yasnippet--buffer
134 | (let ((yas-verbosity 0)
135 | (inhibit-redisplay t)
136 | (inhibit-read-only t)
137 | (orig-offset (- (point-max) (cdr ivy-yasnippet--region)))
138 | (yas-prompt-functions '(yas-no-prompt)))
139 | (ivy-yasnippet--revert)
140 | (unwind-protect
141 | (ivy-yasnippet--expand-template template)
142 | (unwind-protect
143 | (mapc #'yas--commit-snippet
144 | (yas-active-snippets (point-min) (point-max)))
145 | (setcdr ivy-yasnippet--region (- (point-max) orig-offset)))))
146 | (redisplay)))
147 |
148 | (defun ivy-yasnippet--update-fn ()
149 | (let* ((candidate (ivy-state-current ivy-last))
150 | (template (ivy-yasnippet--lookup-template candidate)))
151 | (when template
152 | (condition-case-unless-debug err
153 | (ivy-yasnippet--preview template)
154 | (error (warn "ivy-yasnippet--update-fn: %S" err))))))
155 |
156 | (defun ivy-yasnippet--visit-snippet-action (template-name)
157 | (let ((inhibit-read-only t))
158 | (ivy-yasnippet--revert))
159 | (yas--visit-snippet-file-1
160 | (ivy-yasnippet--lookup-template template-name)))
161 |
162 | ;;;###autoload
163 | (defun ivy-yasnippet ()
164 | "Read a snippet name from the minibuffer and expand it at point.
165 | The completion is done using `ivy-read'.
166 |
167 | In the minibuffer, each time selection changes, the selected
168 | snippet is temporarily expanded at point for preview.
169 |
170 | If text before point matches snippet key of any candidate, that
171 | candidate will be initially selected, unless variable
172 | `ivy-yasnippet-expand-keys' is set to nil."
173 | (interactive)
174 | (barf-if-buffer-read-only)
175 | (unless yas-minor-mode
176 | (error "`yas-minor-mode' not enabled in current buffer"))
177 | (let* ((ivy-yasnippet--buffer (current-buffer))
178 |
179 | (ivy-yasnippet--region
180 | (if (region-active-p)
181 | (cons (region-beginning) (region-end))
182 | (cons (point) (point))))
183 | (ivy-yasnippet--region-contents
184 | (buffer-substring (car ivy-yasnippet--region)
185 | (cdr ivy-yasnippet--region)))
186 |
187 | (key-info (yas--templates-for-key-at-point))
188 | (ivy-yasnippet--key
189 | (and key-info
190 | (buffer-substring (cadr key-info) (cl-caddr key-info))))
191 | (templates-for-key-at-point (mapcar #'cdr (car key-info)))
192 | (ivy-yasnippet--key-deleted nil)
193 | (ivy-yasnippet--should-delete-key
194 | (memq ivy-yasnippet-expand-keys '(always smart)))
195 |
196 | (ivy-yasnippet--template-alist
197 | (mapcar (lambda (template)
198 | (cons (yas--template-name template) template))
199 | (yas--all-templates (yas--get-snippet-tables))))
200 |
201 | (modified-flag (buffer-modified-p))
202 |
203 | candidates selection)
204 | (let ((buffer-undo-list t))
205 | (setq candidates
206 | (-map #'car
207 | (-flatten
208 | (-map (-partial #'-sort (lambda (a b)
209 | (string-lessp (car a) (car b))))
210 | (-separate
211 | (-lambda ((_ . template))
212 | (memq template templates-for-key-at-point))
213 | ivy-yasnippet--template-alist)))))
214 |
215 | (unwind-protect
216 | (let ((buffer-read-only t))
217 | (ivy-read "Choose a snippet: " candidates
218 | :require-match
219 | (not ivy-yasnippet-create-snippet-if-not-matched)
220 | :update-fn #'ivy-yasnippet--update-fn
221 | :action (lambda (candidate) (setq selection candidate))
222 | :preselect
223 | (when ivy-yasnippet--key
224 | (-find-index
225 | (lambda (x)
226 | (string-equal
227 | ivy-yasnippet--key
228 | (yas--template-key
229 | (ivy-yasnippet--lookup-template x))))
230 | candidates))
231 | :caller 'ivy-yasnippet))
232 | (ivy-yasnippet--revert)
233 | (set-buffer-modified-p modified-flag)))
234 | (when selection
235 | (let ((template (ivy-yasnippet--lookup-template selection)))
236 | (if template
237 | (ivy-yasnippet--expand-template template)
238 | (setq template ivy-yasnippet-new-snippet)
239 | (yas-new-snippet t)
240 | (when (derived-mode-p 'snippet-mode)
241 | (yas-expand-snippet
242 | template nil nil
243 | `((name ,selection)
244 | (yas-selected-text
245 | ,ivy-yasnippet--region-contents)))))))))
246 |
247 | (defun ivy-yasnippet-transformer (template-name)
248 | (let* ((template (ivy-yasnippet--lookup-template template-name))
249 | (key (yas--template-key template)))
250 | (if key
251 | (concat template-name
252 | " " (propertize (concat "[" key "]")
253 | 'face
254 | (if (and (string-equal key ivy-yasnippet--key)
255 | ivy-yasnippet--should-delete-key)
256 | 'ivy-yasnippet-key-matching
257 | 'ivy-yasnippet-key)))
258 | template-name)))
259 |
260 | (ivy-set-display-transformer 'ivy-yasnippet
261 | #'ivy-yasnippet-transformer)
262 |
263 | (ivy-add-actions
264 | #'ivy-yasnippet
265 | '(("v" ivy-yasnippet--visit-snippet-action "Visit snippet file")))
266 |
267 | (provide 'ivy-yasnippet)
268 |
269 | ;;; ivy-yasnippet.el ends here
270 |
--------------------------------------------------------------------------------