From c09a54a798041e05f976d39626c442c6836d8bc7 Mon Sep 17 00:00:00 2001 From: Mohsin Kaleem Date: Wed, 20 Jan 2021 00:08:42 +0000 Subject: [PATCH 1/2] (consult-yasnippet): Initial commit yasnippet command This command works as a parallel to [[https://github.com/mkcms/ivy-yasnippet][ivy-yasnippet]] and allows you to expand yasnippet snippets through a completing-read interface. It supports previewing the current snippet expansion and overwriting the marked region with a new snippet completion. ** Embark This package also provides the `consult-yasnippet-visit-snippet-file` package. Which can be used as an alternative action for `consult-yasnippet` through for eg. embark: ```lisp (require 'embark) (embark-define-keymap embark-yasnippet-completion-actions "Embark actions for `consult-yasnippet' and derivatives" ;; NOTE: Binding differs from `ivy-yasnippet' which uses "v". ("d" consult-yasnippet-visit-snippet-file)) (add-to-list 'embark-keymap-alist '(yasnippet . embark-yasnippet-completion-actions)) ``` ** marginalia The default completion interface simply shows the name associated with a snippet. You can annotate it with the prefix/key needed to expand to it using marginalia. ```lisp (require 'marginalia) (defun marginalia-annotate-yasnippet (cand) ;; NOTE: Maybe there's a less indirect way to do this :/. (when-let* ((cand (marginalia--full-candidate cand)) (template (alist-get cand consult-yasnippet--snippets nil nil #'string-equal)) (key (yas--template-key template))) (concat " " (propertize (concat "[" key "]") ;; TODO: custom face 'face 'font-lock-type-face)))) (push '(yasnippet . marginalia-annotate-yasnippet) marginalia-annotators-light) (push '(yasnippet . marginalia-annotate-yasnippet) marginalia-annotators-heavy) ``` --- README.org | 4 ++ consult-yasnippet.el | 158 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 consult-yasnippet.el diff --git a/README.org b/README.org index 2d393b2b..0ca05f37 100644 --- a/README.org +++ b/README.org @@ -236,6 +236,9 @@ variables and functions with their descriptions. components. - =consult-flymake=: Jump to Flymake diagnostic, like =consult-flycheck=. +- =consult-yasnippet=: Expand [[https://github.com/joaotavora/yasnippet][yasnippet]] snippets in the current buffer. + This command is available from the additional =consult-yasnippet.el= + package. ** Histories :properties: @@ -644,6 +647,7 @@ It is recommended to install the following package combination: - consult: This package - consult-flycheck: Provides the consult-flycheck command +- consult-yasnippet: Provides the consult-yasnippet command - [[https://github.com/raxod502/selectrum][selectrum]] or [[https://github.com/oantolin/icomplete-vertical][icomplete-vertical]]: Vertical completion systems - [[https://github.com/minad/marginalia][marginalia]]: Annotations for the completion candidates - [[https://github.com/oantolin/embark][embark and embark-consult]]: Action commands, which can act on the completion candidates diff --git a/consult-yasnippet.el b/consult-yasnippet.el new file mode 100644 index 00000000..a9ed2f94 --- /dev/null +++ b/consult-yasnippet.el @@ -0,0 +1,158 @@ +;;; consult-yasnippet.el --- Provides the command `consult-yasnippet' -*- lexical-binding: t; -*- + +;; Author: Mohsin Kaleem, Consult and Selectrum contributors +;; Maintainer: Daniel Mendler +;; Created: 2021 +;; License: GPL-3.0-or-later +;; Version: 0.1 +;; Package-Requires: ((consult "0.1") (yasnippet "0.14") (emacs "26.1")) +;; Homepage: https://github.com/minad/consult + +;; This file is not part of GNU Emacs. + +;; This program is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; Provides the command `consult-yasnippet' which presents all the snippet +;; expansions available in the current buffer with in-buffer previews. +;; +;; This is an extra package, to avoid loading yasnippet in the base consult +;; package.. + + +;;; Code: + +(require 'consult) +(require 'yasnippet) + +(defgroup consult-yasnippet nil + "Consulting `completing-read' for yasnippet snippets." + :group 'consult + :prefix "consult-yasnippet-") + +(defvar consult-yasnippet--snippets nil + "Snippet collection for current `consult-snippet' session.") + +(defvar consult-yasnippet--buffer nil + "The buffer in which `consult-yasnippet' was begun") + +(defvar consult-yasnippet--region nil + "The position (a cons of the start and end `point's) of where `consult-yasnippet' was begun") + +(defvar consult-yasnippet--region-contents "" + "The original contents of `consult-yasnippet--region'.") + +(defun consult-yasnippet--expand-template (template) + "Expand the yasnippet template TEMPLATE at point." + (deactivate-mark) + (goto-char (car consult-yasnippet--region)) + (when (not (string-equal "" consult-yasnippet--region-contents)) + (push-mark (point)) + (push-mark (cdr consult-yasnippet--region) nil t)) + + (yas-expand-snippet (yas--template-content template) + nil nil + (yas--template-expand-env template))) + +(defun consult-yasnippet--preview (template _) + "Previewer for `consult--read'. +This function expands TEMPLATE at point in the buffer +`consult-yasnippet--read-snippet' was started in. This includes +overwriting any region that was active and removing any previous +previews that're already active. + +When TEMPLATE is not given, this function essentially just resets +the state of the current buffer to before any snippets were previewed." + (with-current-buffer consult-yasnippet--buffer + (let ((yas-verbosity 0) + (inhibit-redisplay t) + (inhibit-read-only t) + (orig-offset (- (point-max) (cdr consult-yasnippet--region))) + (yas-prompt-functions '(yas-no-prompt))) + + ;; We always undo any snippet previews before maybe setting up + ;; some new previews. + (delete-region (car consult-yasnippet--region) + (cdr consult-yasnippet--region)) + (goto-char (car consult-yasnippet--region)) + (setcar consult-yasnippet--region (point)) + (insert consult-yasnippet--region-contents) + (setcdr consult-yasnippet--region (point)) + + (when template + (unwind-protect + (consult-yasnippet--expand-template template) + (unwind-protect + (mapc #'yas--commit-snippet + (yas-active-snippets (point-min) (point-max))) + (setcdr consult-yasnippet--region (- (point-max) orig-offset)))) + (redisplay))))) + +(defmacro consult-yasnippet--setup (&rest body) + "Setup the local variables and environment for `consult-yasnippet'. +This environment is used both in `consult-yasnippet--preview'and +`consult-yasnippet--expand-template'." + `(progn + (barf-if-buffer-read-only) + (unless (bound-and-true-p yas-minor-mode) + (error "`yas-minor-mode' not enabled in current buffer")) + + (let* ((consult-yasnippet--snippets + (mapcar (lambda (template) + (cons (yas--template-name template) template)) + (yas--all-templates (yas--get-snippet-tables)))) + (consult-yasnippet--buffer (current-buffer)) + (consult-yasnippet--region (if (region-active-p) + (cons (region-beginning) (region-end)) + (cons (point) (point)))) + (consult-yasnippet--region-contents (buffer-substring (car consult-yasnippet--region) + (cdr consult-yasnippet--region)))) + ,@body))) + +(defun consult-yasnippet--read-snippet () + "Backend implementation of `consult-yasnippet'. + +This starts a `completing-read' session with all the snippets in the current +snippet table with support for previewing the snippet to be expanded and +replacing the active region with the snippet expansion. + +This function doesn't actually expand the snippet, it only reads and then +returns a snippet template from the user." + (consult-yasnippet--setup + (let ((buffer-undo-list t)) ; Prevent querying user (and showing previews) from updating the undo-history + (unwind-protect + (consult--read + "Choose a snippet: " + consult-yasnippet--snippets + :lookup 'consult--lookup-cdr + :require-match t + :preview #'consult-yasnippet--preview + :category 'yasnippet) + (consult-yasnippet--preview nil t))))) ; Restore contents of region from before preview (while still ignoring undo history). + +(defun consult-yasnippet-visit-snippet-file (snippet) + (interactive (list (consult-yasnippet--read-snippet))) + (yas--visit-snippet-file-1 snippet)) + +(defun consult-yasnippet (template) + (interactive (list (consult-yasnippet--read-snippet))) + ;; We need to first restore the local environment for + ;; `consult-yasnippet--expand-template' to work. + (consult-yasnippet--setup + (consult-yasnippet--expand-template template))) + +(provide 'consult-yasnippet) + +;;; consult-yasnippet.el ends here From 9bb9ec4e6d2489b4b27bffb90a40c453ad8692b1 Mon Sep 17 00:00:00 2001 From: Mohsin Kaleem Date: Wed, 20 Jan 2021 17:45:51 +0000 Subject: [PATCH 2/2] (consult-yasnippet): Cleanup and refactor implementation See #173. --- README.org | 6 +- consult-yasnippet.el | 194 ++++++++++++++++++++++--------------------- 2 files changed, 102 insertions(+), 98 deletions(-) diff --git a/README.org b/README.org index 921642ab..7c455f72 100644 --- a/README.org +++ b/README.org @@ -144,6 +144,9 @@ variables and functions with their descriptions. lines again. If the input begins with "! SPC", the filter matches the complement. In contrast to =consult-keep-lines= this function does not edit the buffer. + - =consult-yasnippet=: Expand [[https://github.com/joaotavora/yasnippet][yasnippet]] snippets in the current buffer. + This command is available from the additional =consult-yasnippet.el= + package. ** Navigation :properties: @@ -237,9 +240,6 @@ variables and functions with their descriptions. components. - =consult-flymake=: Jump to Flymake diagnostic, like =consult-flycheck=. -- =consult-yasnippet=: Expand [[https://github.com/joaotavora/yasnippet][yasnippet]] snippets in the current buffer. - This command is available from the additional =consult-yasnippet.el= - package. ** Histories :properties: diff --git a/consult-yasnippet.el b/consult-yasnippet.el index a9ed2f94..9bf6984f 100644 --- a/consult-yasnippet.el +++ b/consult-yasnippet.el @@ -1,11 +1,11 @@ ;;; consult-yasnippet.el --- Provides the command `consult-yasnippet' -*- lexical-binding: t; -*- -;; Author: Mohsin Kaleem, Consult and Selectrum contributors +;; Author: MichaƂ Krzywkowski, Daniel Mendler, Consult and Selectrum contributors ;; Maintainer: Daniel Mendler ;; Created: 2021 ;; License: GPL-3.0-or-later ;; Version: 0.1 -;; Package-Requires: ((consult "0.1") (yasnippet "0.14") (emacs "26.1")) +;; Package-Requires: ((consult "0.2") (yasnippet "0.14") (emacs "26.1")) ;; Homepage: https://github.com/minad/consult ;; This file is not part of GNU Emacs. @@ -31,97 +31,91 @@ ;; This is an extra package, to avoid loading yasnippet in the base consult ;; package.. +;;;; Credits + +;; This package has been derived from the ivy-yasnippet[1] package. +;; +;; [1] https://github.com/mkcms/ivy-yasnippet ;;; Code: (require 'consult) (require 'yasnippet) -(defgroup consult-yasnippet nil - "Consulting `completing-read' for yasnippet snippets." - :group 'consult - :prefix "consult-yasnippet-") - -(defvar consult-yasnippet--snippets nil - "Snippet collection for current `consult-snippet' session.") - -(defvar consult-yasnippet--buffer nil - "The buffer in which `consult-yasnippet' was begun") - -(defvar consult-yasnippet--region nil - "The position (a cons of the start and end `point's) of where `consult-yasnippet' was begun") - -(defvar consult-yasnippet--region-contents "" - "The original contents of `consult-yasnippet--region'.") - -(defun consult-yasnippet--expand-template (template) +(defun consult-yasnippet--expand-template (template region region-contents) "Expand the yasnippet template TEMPLATE at point." (deactivate-mark) - (goto-char (car consult-yasnippet--region)) - (when (not (string-equal "" consult-yasnippet--region-contents)) + (goto-char (car region)) + + ;; Restore marked region (when it existed) so that `yas-expand-snippet' + ;; overwrites it. + (when (not (string-equal "" region-contents)) (push-mark (point)) - (push-mark (cdr consult-yasnippet--region) nil t)) + (push-mark (cdr region) nil t)) - (yas-expand-snippet (yas--template-content template) - nil nil - (yas--template-expand-env template))) + (cl-letf (((symbol-function 'yas-completing-read) + (lambda (&rest _args) ""))) + (yas-expand-snippet (yas--template-content template) + nil nil + (yas--template-expand-env template)))) -(defun consult-yasnippet--preview (template _) +(defun consult-yasnippet--preview () "Previewer for `consult--read'. This function expands TEMPLATE at point in the buffer -`consult-yasnippet--read-snippet' was started in. This includes +`consult-yasnippet--read-template' was started in. This includes overwriting any region that was active and removing any previous previews that're already active. When TEMPLATE is not given, this function essentially just resets the state of the current buffer to before any snippets were previewed." - (with-current-buffer consult-yasnippet--buffer - (let ((yas-verbosity 0) - (inhibit-redisplay t) - (inhibit-read-only t) - (orig-offset (- (point-max) (cdr consult-yasnippet--region))) - (yas-prompt-functions '(yas-no-prompt))) - - ;; We always undo any snippet previews before maybe setting up - ;; some new previews. - (delete-region (car consult-yasnippet--region) - (cdr consult-yasnippet--region)) - (goto-char (car consult-yasnippet--region)) - (setcar consult-yasnippet--region (point)) - (insert consult-yasnippet--region-contents) - (setcdr consult-yasnippet--region (point)) - - (when template - (unwind-protect - (consult-yasnippet--expand-template template) - (unwind-protect - (mapc #'yas--commit-snippet - (yas-active-snippets (point-min) (point-max))) - (setcdr consult-yasnippet--region (- (point-max) orig-offset)))) - (redisplay))))) - -(defmacro consult-yasnippet--setup (&rest body) - "Setup the local variables and environment for `consult-yasnippet'. -This environment is used both in `consult-yasnippet--preview'and -`consult-yasnippet--expand-template'." - `(progn - (barf-if-buffer-read-only) - (unless (bound-and-true-p yas-minor-mode) - (error "`yas-minor-mode' not enabled in current buffer")) - - (let* ((consult-yasnippet--snippets - (mapcar (lambda (template) - (cons (yas--template-name template) template)) - (yas--all-templates (yas--get-snippet-tables)))) - (consult-yasnippet--buffer (current-buffer)) - (consult-yasnippet--region (if (region-active-p) - (cons (region-beginning) (region-end)) - (cons (point) (point)))) - (consult-yasnippet--region-contents (buffer-substring (car consult-yasnippet--region) - (cdr consult-yasnippet--region)))) - ,@body))) - -(defun consult-yasnippet--read-snippet () + (let* ((buf (current-buffer)) + (region (if (region-active-p) + (cons (region-beginning) (region-end)) + (cons (point) (point)))) + (region-contents (buffer-substring (car region) (cdr region)))) + (lambda (template restore) + (with-current-buffer buf + (let ((yas-verbosity 0) + (inhibit-redisplay t) + (inhibit-read-only t) + (orig-offset (- (point-max) (cdr region))) + (yas-prompt-functions '(yas-no-prompt))) + + ;; We always undo any snippet previews before maybe setting up + ;; some new previews. + (delete-region (car region) (cdr region)) + (goto-char (car region)) + (setcar region (point)) + (insert region-contents) + (setcdr region (point)) + + (when (and template (not restore)) + (unwind-protect + (consult-yasnippet--expand-template template region region-contents) + (unwind-protect + (mapc #'yas--commit-snippet + (yas-active-snippets (point-min) (point-max))) + (setcdr region (- (point-max) orig-offset)))) + (redisplay))))))) + +(defun consult-yasnippet--candidates (&rest body) + "Retrieve the list of available snippets in the current buffer." + (unless (bound-and-true-p yas-minor-mode) + (error "`yas-minor-mode' not enabled in current buffer")) + + (mapcar + (lambda (template) + (cons (concat (yas--template-name template) + (propertize " " + 'display (concat + " [" + (propertize (yas--template-key template) + 'face 'consult-key) + "]"))) + template)) + (yas--all-templates (yas--get-snippet-tables)))) + +(defun consult-yasnippet--read-template () "Backend implementation of `consult-yasnippet'. This starts a `completing-read' session with all the snippets in the current @@ -130,28 +124,38 @@ replacing the active region with the snippet expansion. This function doesn't actually expand the snippet, it only reads and then returns a snippet template from the user." - (consult-yasnippet--setup - (let ((buffer-undo-list t)) ; Prevent querying user (and showing previews) from updating the undo-history - (unwind-protect - (consult--read - "Choose a snippet: " - consult-yasnippet--snippets - :lookup 'consult--lookup-cdr - :require-match t - :preview #'consult-yasnippet--preview - :category 'yasnippet) - (consult-yasnippet--preview nil t))))) ; Restore contents of region from before preview (while still ignoring undo history). - -(defun consult-yasnippet-visit-snippet-file (snippet) - (interactive (list (consult-yasnippet--read-snippet))) - (yas--visit-snippet-file-1 snippet)) + (barf-if-buffer-read-only) + + (let* ((buffer-undo-list t)) ; Prevent querying user (and showing previews) from updating the undo-history + (consult--read + "Choose a snippet: " + (consult-yasnippet--candidates) + :lookup 'consult--lookup-cdr + :require-match t + :preview (consult-yasnippet--preview) + :category 'yasnippet))) + +(defun consult-yasnippet-visit-snippet-file (template) + "Visit the snippet file associated with TEMPLATE. +When called interactively this command previews snippet completions in +the current buffer, and then opens the selected snippets template file +using `yas--visit-snippet-file-1'." + (interactive (list (consult-yasnippet--read-template))) + (yas--visit-snippet-file-1 template)) (defun consult-yasnippet (template) - (interactive (list (consult-yasnippet--read-snippet))) - ;; We need to first restore the local environment for - ;; `consult-yasnippet--expand-template' to work. - (consult-yasnippet--setup - (consult-yasnippet--expand-template template))) + "Interactively select and expand the yasnippet template TEMPLATE. +When called interactively this command presents a completing read interface +containing all currently available snippet expansions, with live previews for +each snippet. Once selected a chosen snippet will be expanded at point using +`yas-expand-snippet'." + (interactive (list (consult-yasnippet--read-template))) + (barf-if-buffer-read-only) + (let* ((region (if (region-active-p) + (cons (region-beginning) (region-end)) + (cons (point) (point)))) + (region-contents (buffer-substring (car region) (cdr region)))) + (consult-yasnippet--expand-template template region region-contents))) (provide 'consult-yasnippet)