Moving to Doom Emacs!

This commit is contained in:
Derek Taylor
2019-12-16 20:21:19 -06:00
parent d9f2f456f1
commit d4b4c33550
683 changed files with 51877 additions and 100 deletions

View File

@@ -0,0 +1,46 @@
#+TITLE: editor/snippets
#+DATE: February 11, 2017
#+SINCE: v2.0
#+STARTUP: inlineimages
* Table of Contents :TOC:
- [[#description][Description]]
- [[#module-flags][Module Flags]]
- [[#plugins][Plugins]]
- [[#hacks][Hacks]]
- [[#prerequisites][Prerequisites]]
- [[#features][Features]]
- [[#configuration][Configuration]]
- [[#disabling-the-built-in-snippets][Disabling the built-in snippets]]
- [[#troubleshooting][Troubleshooting]]
* Description
This module adds snippets to Emacs, powered by yasnippet.
** Module Flags
This module exposes no flags.
** Plugins
+ [[https://github.com/joaotavora/yasnippet][yasnippet]]
+ [[https://github.com/abo-abo/auto-yasnippet][auto-yasnippet]]
+ [[https://github.com/hlissner/doom-snippets][doom-snippets]]
** TODO Hacks
* Prerequisites
This module has no external dependencies.
* TODO Features
* Configuration
** Disabling the built-in snippets
Don't want to use provided one? Then add this to your private module,
#+BEGIN_SRC emacs-lisp
;; in ~/.doom.d/packages.el
(package! doom-snippets :ignore t)
;; If you want to replace it with yasnippet's default snippets
(package! yasnippet-snippets)
#+END_SRC
* TODO Troubleshooting

View File

@@ -0,0 +1,10 @@
;;; editor/snippets/autoload/settings.el -*- lexical-binding: t; -*-
;;;###autodef
(defun set-yas-minor-mode! (modes)
"Register minor MODES (one mode symbol or a list of them) with yasnippet so it
can have its own snippets category, if the folder exists."
(dolist (mode (doom-enlist modes))
(let ((fn (intern (format "+snippets-register-%s-h" mode))))
(fset fn (lambda () (yas-activate-extra-mode mode)))
(add-hook (intern (format "%s-hook" mode)) fn))))

View File

@@ -0,0 +1,304 @@
;;; editor/snippets/autoload/snippets.el -*- lexical-binding: t; -*-
(defun +snippets--remove-p (x y)
(and (equal (yas--template-key x) (yas--template-key y))
(file-in-directory-p (yas--template-get-file x) doom-emacs-dir)))
;;;###autoload
(defun +snippets-prompt-private (prompt choices &optional display-fn)
"Prioritize private snippets over built-in ones if there are multiple
choices.
There are two groups of snippets in Doom Emacs. The built in ones (under
`doom-emacs-dir'; provided by Doom or its plugins) or your private snippets
(outside of `doom-eamcs-dir').
If there are multiple snippets with the same key in either camp (but not both),
you will be prompted to select one.
If there are conflicting keys across the two camps, the built-in ones are
ignored. This makes it easy to override built-in snippets with private ones."
(when (eq this-command 'yas-expand)
(let* ((gc-cons-threshold most-positive-fixnum)
(choices (cl-remove-duplicates choices :test #'+snippets--remove-p)))
(if (cdr choices)
(cl-loop for fn in (cdr (memq '+snippets-prompt-private yas-prompt-functions))
if (funcall fn prompt choices display-fn)
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."
(interactive)
(let* ((snippet (car (yas-active-snippets)))
(active-field (yas--snippet-active-field snippet))
(position (if (yas--field-p active-field)
(yas--field-start active-field)
-1)))
(if (= (point) position)
(move-beginning-of-line 1)
(goto-char position))))
;;;###autoload
(defun +snippets/goto-end-of-field ()
"Go to the end of the current field."
(interactive)
(let* ((snippet (car (yas-active-snippets)))
(active-field (yas--snippet-active-field snippet))
(position (if (yas--field-p active-field)
(yas--field-end active-field)
-1)))
(if (= (point) position)
(move-end-of-line 1)
(goto-char position))))
;;;###autoload
(defun +snippets/delete-backward-char (&optional field)
"Prevents Yas from interfering with backspace deletion."
(interactive)
(let ((field (or field (and (overlayp yas--active-field-overlay)
(overlay-buffer yas--active-field-overlay)
(overlay-get yas--active-field-overlay 'yas--field)))))
(unless (and (yas--field-p field)
(eq (point) (marker-position (yas--field-start field))))
(call-interactively #'delete-backward-char))))
;;;###autoload
(defun +snippets/delete-forward-char-or-field (&optional field)
"Delete forward, or skip the current field if it's empty. This is to prevent
buggy behavior when <delete> is pressed in an empty field."
(interactive)
(let ((field (or field (and yas--active-field-overlay
(overlay-buffer yas--active-field-overlay)
(overlay-get yas--active-field-overlay 'yas--field)))))
(cond ((not (yas--field-p field))
(delete-char 1))
((and (not (yas--field-modified-p field))
(eq (point) (marker-position (yas--field-start field))))
(yas--skip-and-clear field)
(yas-next-field 1))
((eq (point) (marker-position (yas--field-end field))) nil)
((delete-char 1)))))
;;;###autoload
(defun +snippets/delete-to-start-of-field (&optional field)
"Delete to start-of-field."
(interactive)
(unless field
(setq field (and (overlayp yas--active-field-overlay)
(overlay-buffer yas--active-field-overlay)
(overlay-get yas--active-field-overlay 'yas--field))))
(when (yas--field-p field)
(let ((sof (marker-position (yas--field-start 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 'doom-snippets nil t)
(user-error "This command requires the `doom-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 "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-h ()
(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
;;;###autoload
(defun +snippets-enable-project-modes-h (mode &rest _)
"Automatically enable snippet libraries for project minor modes defined with
`def-project-mode!'."
(if (symbol-value mode)
(yas-activate-extra-mode mode)
(yas-deactivate-extra-mode mode)))
;;;###autoload
(defun +snippets-read-only-maybe-h ()
"Enable `read-only-mode' if snippet is built-in."
(when (file-in-directory-p default-directory doom-local-dir)
(read-only-mode 1)
(message "This is a built-in snippet, enabling read only mode. Use `yas-new-snippet' to redefine snippets")))
;;
;;; Advice
;;;###autoload
(defun +snippets-expand-on-region-a (orig-fn &optional no-condition)
"Fix off-by-one issue with expanding snippets on an evil visual region, and
switches to insert mode.
If evil-local-mode isn't enabled, run ORIG-FN as is."
(if (not (and (bound-and-true-p evil-local-mode)
(evil-visual-state-p)))
(funcall orig-fn no-condition)
(evil-visual-select evil-visual-beginning evil-visual-end 'inclusive)
(cl-letf (((symbol-function 'region-beginning) (lambda () evil-visual-beginning))
((symbol-function 'region-end) (lambda () evil-visual-end)))
(funcall orig-fn no-condition)))
(when (and (bound-and-true-p evil-local-mode)
(yas-active-snippets))
(evil-insert-state +1)))

View File

@@ -0,0 +1,101 @@
;;; editor/snippets/config.el -*- lexical-binding: t; -*-
(defvar +snippets-dir (expand-file-name "snippets/" doom-private-dir)
"Directory where `yasnippet' will search for your private snippets.")
;;
;; Packages
(use-package! yasnippet
:commands (yas-minor-mode-on
yas-expand
yas-expand-snippet
yas-lookup-snippet
yas-insert-snippet
yas-new-snippet
yas-visit-snippet-file)
:init
;; Remove default ~/.emacs.d/snippets
(defvar yas-snippet-dirs nil)
;; Ensure `yas-reload-all' is called as late as possible. Other modules could
;; have additional configuration for yasnippet. For example, file-templates.
(add-transient-hook! 'yas-minor-mode-hook (yas-reload-all))
(add-hook! '(text-mode-hook
prog-mode-hook
conf-mode-hook
snippet-mode-hook)
#'yas-minor-mode-on)
:config
(setq yas-verbosity (if doom-debug-mode 3 0)
yas-also-auto-indent-first-line t)
(add-to-list 'load-path +snippets-dir)
;; default snippets library, if available
(require 'doom-snippets nil t)
;; Allow private snippets in DOOMDIR/snippets
(add-to-list 'yas-snippet-dirs '+snippets-dir)
;; In case `+snippets-dir' and `doom-snippets-dir' are the same
(advice-add #'yas-snippet-dirs :filter-return #'delete-dups)
;; Remove GUI dropdown prompt (prefer ivy/helm)
(delq! 'yas-dropdown-prompt yas-prompt-functions)
;; Prioritize private snippets in `+snippets-dir' over built-in ones if there
;; are multiple choices.
(add-to-list 'yas-prompt-functions #'+snippets-prompt-private nil #'eq)
;; Register `def-project-mode!' modes with yasnippet. This enables project
;; specific snippet libraries (e.g. for Laravel, React or Jekyll projects).
(add-hook 'doom-project-hook #'+snippets-enable-project-modes-h)
;; Exit snippets on ESC from normal mode
(add-hook 'doom-escape-hook #'yas-abort-snippet)
(after! smartparens
;; tell smartparens overlays not to interfere with yasnippet keybinds
(advice-add #'yas-expand :before #'sp-remove-active-pair-overlay))
;; Enable `read-only-mode' for built-in snippets (in `doom-local-dir')
(add-hook 'snippet-mode-hook #'+snippets-read-only-maybe-h)
;; (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-a)
(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-h)
;; Replace commands with superior alternatives
(define-key! yas-minor-mode-map
[remap yas-new-snippet] #'+snippets/new
[remap yas-visit-snippet-file] #'+snippets/edit)
(map! :map yas-keymap
"C-e" #'+snippets/goto-end-of-field
"C-a" #'+snippets/goto-start-of-field
[M-right] #'+snippets/goto-end-of-field
[M-left] #'+snippets/goto-start-of-field
[M-backspace] #'+snippets/delete-to-start-of-field
[backspace] #'+snippets/delete-backward-char
[delete] #'+snippets/delete-forward-char-or-field))
(use-package! auto-yasnippet
:defer t
:init (setq aya-persist-snippets-dir (concat doom-etc-dir "auto-snippets/"))
:config
(defadvice! +snippets--inhibit-yas-global-mode-a (orig-fn &rest args)
"auto-yasnippet enables `yas-global-mode'. This is obnoxious for folks like
us who use yas-minor-mode and enable yasnippet more selectively. This advice
swaps `yas-global-mode' with `yas-minor-mode'."
:around '(aya-expand aya-open-line)
(cl-letf (((symbol-function #'yas-global-mode) #'yas-minor-mode)
(yas-global-mode yas-minor-mode))
(apply orig-fn args))))

View File

@@ -0,0 +1,10 @@
;; -*- no-byte-compile: t; -*-
;;; editor/snippets/packages.el
(package! yasnippet)
(package! auto-yasnippet)
(package! doom-snippets
:recipe (:host github
:repo "hlissner/doom-snippets"
:files ("*.el" "*")))