├── 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 | --------------------------------------------------------------------------------