editor/snippets: expand on snippet commands & keybinds

- Introduces the +snippets/new (SPC s n) command for creating a new
  private snippet
- Introduces the +snippets/new-lias (SPC s N) command for creating a new
  private snippet alias, which will invoke another snippet (you will be
  prompted to select one). This will only work with the emacs-snippets
  library bundled with Doom Emacs, however, as it depends on its API.
- Introduces +snippets/edit (SPC s c) for modifying existing snippets.
  How this differs from yas-visit-snippet-file is it will copy the
  contents of built-in snippets into a buffer primed for your private
  snippets (in DOOMDIR/snippets), while yas-visit-snippet-file will
  simply open the originating snippet.
- Introduces the +snippets/find (SPC s ?),
  +snippets/find-for-current-mode (SPC s /) and
  +snippets/find-private (SPC s f) commands for, respectively, finding a
  snippet file among *all* directories in yas-snippet-dirs, finding a
  snippet for the current major mode (plus parents), and finding a
  snippet from among your private library. This opens built-in snippets
  in read-only mode, but you can press C-c C-e to open it in
  +snippets/edit.
This commit is contained in:
Henrik Lissner 2019-07-12 20:41:50 +02:00
parent f90c0b8040
commit 05eb333a0c
No known key found for this signature in database
GPG Key ID: 5F6C0EA160557395
3 changed files with 190 additions and 27 deletions

View File

@ -793,14 +793,16 @@
;;; <leader> s --- snippets
(:when (featurep! :editor snippets)
(:prefix-map ("s" . "snippets")
:desc "New snippet" "n" #'yas-new-snippet
:desc "View snippet for mode" "/" #'+snippets/find-for-current-mode
:desc "View snippet (global)" "?" #'+snippets/find
:desc "Edit snippet" "c" #'+snippet/edit
:desc "View private snippet" "f" #'+snippets/find-private
:desc "Insert snippet" "i" #'yas-insert-snippet
:desc "Jump to mode snippet" "/" #'yas-visit-snippet-file
:desc "Jump to snippet" "s" #'+snippets/find-file
:desc "Browse snippets" "S" #'+snippets/browse
:desc "New snippet" "n" #'+snippet/new
:desc "New snippet alias" "N" #'+snippet/new-alias
:desc "Reload snippets" "r" #'yas-reload-all
:desc "Create temporary snippet" "c" #'aya-create
:desc "Use temporary snippet" "e" #'aya-expand))
:desc "Create temporary snippet" "s" #'aya-create
:desc "Expand temporary snippet" "e" #'aya-expand))
;;; <leader> t --- toggle
(:prefix-map ("t" . "toggle")

View File

@ -27,6 +27,50 @@ ignored. This makes it easy to override built-in snippets with private ones."
return it)
(car choices)))))
(defun +snippet--ensure-dir (dir)
(unless (file-directory-p dir)
(if (y-or-n-p (format "%S doesn't exist. Create it?" (abbreviate-file-name dir)))
(make-directory dir t)
(error "%S doesn't exist" (abbreviate-file-name dir)))))
(defun +snippet--completing-read-uuid (prompt all-snippets &rest args)
(plist-get
(text-properties-at
0 (apply #'completing-read prompt
(cl-loop for (_ . tpl) in (mapcan #'yas--table-templates (if all-snippets
(hash-table-values yas--tables)
(yas--get-snippet-tables)))
for txt = (format "%-25s%-30s%s"
(yas--template-key tpl)
(yas--template-name tpl)
(abbreviate-file-name (yas--template-load-file tpl)))
collect
(progn
(set-text-properties 0 (length txt) `(uuid ,(yas--template-name tpl)
path ,(yas--template-load-file tpl))
txt)
txt))
args))
'uuid))
(defun +snippet--abort ()
(interactive)
(set-buffer-modified-p nil)
(kill-current-buffer))
(defvar +snippet--current-snippet-uuid nil)
(defun +snippet--edit ()
(interactive)
(when +snippet--current-snippet-uuid
(let ((buf (current-buffer)))
(+snippets/edit +snippet--current-snippet-uuid)
(kill-buffer buf))))
;;
;;; Commands
;;;###autoload
(defun +snippets/goto-start-of-field ()
"Go to the beginning of the current field."
@ -94,9 +138,134 @@ buggy behavior when <delete> is pressed in an empty field."
(when (and field (> (point) sof))
(delete-region sof (point))))))
;;;###autoload
(defun +snippets/find ()
"Open a snippet file (in all of `yas-snippet-dirs')."
(interactive)
(let* ((dirs (doom-files-in (cl-loop for dir in yas-snippet-dirs
if (symbolp dir)
collect (symbol-value dir)
else collect dir)
:depth 0 :type 'dirs))
(files (doom-files-in dirs :depth 0 :full t)))
(let ((template-path (completing-read "Find snippet: " (mapcar #'abbreviate-file-name files))))
(unless (file-readable-p template-path)
(user-error "Cannot read %S" template-path))
(find-file template-path)
(unless (file-in-directory-p template-path +snippets-dir)
(read-only-mode +1)
(setq header-line-format "This is a built-in snippet. Press C-c C-e to modify it"
+snippet--current-snippet-uuid template-uuid)))))
;;;###autoload
(defun +snippets/find-private ()
"Open a private snippet file in `+snippets-dir'."
(interactive)
(doom-project-find-file +snippets-dir))
;;;###autoload
(defun +snippets/find-for-current-mode (template-uuid)
"Open a snippet for this mode."
(interactive
(list
(+snippet--completing-read-uuid "Visit snippet: " current-prefix-arg)))
(if-let (template (yas--get-template-by-uuid major-mode template-uuid))
(let* ((template (yas--get-template-by-uuid major-mode template-uuid))
(template-path (yas--template-load-file template)))
(unless (file-readable-p template-path)
(user-error "Cannot read %S" template-path))
(find-file template-path)
(unless (file-in-directory-p template-path +snippets-dir)
(read-only-mode +1)
(setq header-line-format "This is a built-in snippet. Press C-c C-e to modify it"
+snippet--current-snippet-uuid template-uuid)))
(user-error "Cannot find template with UUID %S" template-uuid)))
;;;###autoload
(defun +snippets/new ()
"Create a new snippet in `+snippets-dir'."
(interactive)
(let ((default-directory
(expand-file-name (symbol-name major-mode)
+snippets-dir)))
(+snippet--ensure-dir default-directory)
(with-current-buffer (switch-to-buffer "untitled-snippet")
(snippet-mode)
(erase-buffer)
(yas-expand-snippet (concat "# -*- mode: snippet -*-\n"
"# name: $1\n"
"# uuid: $2\n"
"# key: ${3:trigger-key}${4:\n"
"# condition: t}\n"
"# --\n"
"$0"))
(when (bound-and-true-p evil-local-mode)
(evil-insert-state)))))
;;;###autoload
(defun +snippets/new-alias (template-uuid)
"Create an alias for a snippet with uuid TEMPLATE-UUID.
You will be prompted for a snippet to alias."
(interactive
(list
(+snippet--completing-read-uuid "Select snippet to alias: "
current-prefix-arg)))
(unless (require 'emacs-snippets nil t)
(user-error "This command requires the `emacs-snippets' library bundled with Doom Emacs"))
(let ((default-directory (expand-file-name (symbol-name major-mode) +snippets-dir)))
(+snippet--ensure-dir default-directory)
(with-current-buffer (switch-to-buffer "untitled-snippet")
(snippet-mode)
(erase-buffer)
(yas-expand-snippet
(concat "# -*- mode: snippet -*-\n"
"# name: $1\n"
"# key: ${2:trigger-key}${3:\n"
"# condition: t}\n"
"# type: command\n"
"# --\n"
"(%alias \"${4:" (or template "uuid") "}\")"))
(when (bound-and-true-p evil-local-mode)
(evil-insert-state)))))
;;;###autoload
(defun +snippets/edit (template-uuid)
"Edit a snippet with uuid TEMPLATE-UUID.
If the snippet isn't in `+snippets-dir', it will be copied there (where it will
shadow the default snippet)."
(interactive
(list
(+snippet--completing-read-uuid "Select snippet to alias: "
current-prefix-arg)))
(let* ((major-mode (if (eq major-mode 'snippet-mode)
(intern (file-name-base (directory-file-name default-directory)))
major-mode))
(template (yas--get-template-by-uuid major-mode template-uuid))
(template-path (yas--template-load-file template)))
(if (file-in-directory-p template-path +snippets-dir)
(find-file template-path)
(let ((buf (get-buffer-create (format "*%s*" (file-name-nondirectory template-path)))))
(with-current-buffer (switch-to-buffer buf)
(insert-file-contents template-path)
(snippet-mode)
(setq default-directory
(expand-file-name (file-name-nondirectory template-path)
(expand-file-name (symbol-name major-mode)
+snippets-dir))))))))
;;;###autoload
(defun +snippets|show-hints-in-header-line ()
(setq header-line-format
(substitute-command-keys
(concat "\\[yas-load-snippet-buffer-and-close] to finish, "
"\\[+snippet--abort] to abort, "
"\\[yas-tryout-snippet] to test it"))))
;;
;; Hooks
;;; Hooks
;;;###autoload
(defun +snippets|enable-project-modes (mode &rest _)
@ -133,21 +302,3 @@ If evil-local-mode isn't enabled, run ORIG-FN as is."
(when (and (bound-and-true-p evil-local-mode)
(yas-active-snippets))
(evil-insert-state +1)))
;;
;; Commands
;;;###autoload
(defun +snippets/browse (_arg)
"TODO"
(interactive "P")
(doom-project-browse +snippets-dir))
;;;###autoload
(defun +snippets/find-file ()
"TODO"
(interactive)
(if (file-directory-p +snippets-dir)
(doom-project-find-file +snippets-dir)
(yas-visit-snippet-file)))

View File

@ -48,9 +48,19 @@
;; Enable `read-only-mode' for built-in snippets (in `doom-local-dir')
(add-hook 'snippet-mode-hook #'+snippets|read-only-maybe)
;; (Evil only) fix off-by-one issue with visual-mode selections in
;; (Evil only) fix off-by-one issue with line-wise visual selections in
;; `yas-insert-snippet', and switches to insert mode afterwards.
(advice-add #'yas-insert-snippet :around #'+snippets*expand-on-region))
(advice-add #'yas-insert-snippet :around #'+snippets*expand-on-region)
(define-key! snippet-mode-map
"C-c C-k" #'+snippet--abort
"C-c C-e" #'+snippet--edit)
(add-hook 'snippet-mode-hook #'+snippets|show-hints-in-header-line)
;; Replace commands with superior alternatives
(define-key! yas-minor-mode-map
[remap yas-new-snippet] #'+snippets/new
[remap yas-visit-snippet-file] #'+snippets/edit))
;; `auto-yasnippet'