Use :filter instead of yas--fallback

Instead of making yas-expand and yas-expand-from-keymap search for the
command that would have been called, use a conditional keybinding so the
Emacs' builtin keybinding lookup code will do the searching instead.

* doc/faq.org: Remove section about old method binding method.  Update
example to use new method.
* doc/snippet-expansion.org (Trigger key): Update explanation for new
method.
* yasnippet.el (yas-fallback-behavior): Mark obsolete.
(yas--maybe-expand-key-filter): New function.
(yas-maybe-expand): New conditional binding.
(yas-minor-mode-map): Bind it to TAB and <tab>.
(yas--maybe-expand-from-keymap-filter): New function, extracted from
`yas-expand-from-keymap'.
(yas-maybe-expand-from-keymap): New conditional binding.
* yasnippet-tests.el (yas--key-binding): New function, like
`key-binding' but overrides `this-command-keys-vector'.
(snippet-load-uuid): Use it.
(test-yas-tab-binding, test-yas-in-org): Insert snippet key before
testing binding.
This commit is contained in:
Noam Postavsky 2016-12-11 23:44:44 -05:00
parent 48cd7163b2
commit 0311fe2619
4 changed files with 104 additions and 135 deletions

View File

@ -13,73 +13,6 @@ Note that some editors will automatically add a newline for you. In
Emacs, if you set =require-final-newline= to =t=, it will add the Emacs, if you set =require-final-newline= to =t=, it will add the
final newline automatically. final newline automatically.
* Why doesn't TAB expand a snippet?
First check the mode line to see if there's =yas=. If not, then try
=M-x yas-minor-mode= to manually turn on the minor mode and try to
expand the snippet again. If it works, then, you can add the following
code to your =.emacs= /before/ loading YASnippet:
#+BEGIN_SRC emacs-lisp
(add-hook 'the-major-mode-hook 'yas-minor-mode-on)
#+END_SRC
where =the-major-mode= is the major mode in which [[sym:yas-minor-mode][=yas-minor-mode=]] isn't
enabled by default.
The command =M-x yas-global-mode= turns YASnippet on automatically for
/all/ major modes.
If [[sym:yas-minor-mode][=yas-minor-mode=]] is on but the snippet still not expanded. Then try
to see what command is bound to the =TAB= key: press =C-h k= and then
press =TAB=. Emacs will show you the result.
You'll see a buffer prompted by Emacs saying that
=TAB runs the command ...=. Alternatively, you might see
=<tab> runs the command ...=, note the difference between =TAB= and
=<tab>= where the latter has priority. If you see =<tab>= bound to a
command other than [[sym:yas-expand][=yas-expand=]], (e.g. in =org-mode=) you can try the
following code to work around:
#+BEGIN_SRC emacs-lisp
(add-hook 'org-mode-hook
(let ((original-command (lookup-key org-mode-map [tab])))
`(lambda ()
(setq yas-fallback-behavior
'(apply ,original-command))
(local-set-key [tab] 'yas-expand))))
#+END_SRC
replace =org-mode-hook= and =org-mode-map= with the major mode hook you
are dealing with (Use =C-h m= to see what major mode you are in).
As an alternative, you can also try
#+BEGIN_SRC emacs-lisp
(defun yas-advise-indent-function (function-symbol)
(eval `(defadvice ,function-symbol (around yas-try-expand-first activate)
,(format
"Try to expand a snippet before point, then call `%s' as usual"
function-symbol)
(let ((yas-fallback-behavior nil))
(unless (and (interactive-p)
(yas-expand))
ad-do-it)))))
(yas-advise-indent-function 'ruby-indent-line)
#+END_SRC
To /advise/ the modes indentation function bound to TAB, (in this case
=ruby-indent-line=) to first try to run [[sym:yas-expand][=yas-expand=]].
If the output of =C-h k RET <tab>= tells you that =<tab>= is indeed
bound to [[sym:yas-expand][=yas-expand=]] but YASnippet still doesn't work, check your
configuration and you may also ask for help on the [[http://groups.google.com/group/smart-snippet][discussion group]].
See this particular [[http://code.google.com/p/yasnippet/issues/detail?id=93&can=1][thread]] for quite some solutions and alternatives.
Don't forget to attach the information on what command is bound to TAB
as well as the mode information (Can be obtained by =C-h m=).
* Why doesn't TAB navigation work with flyspell * Why doesn't TAB navigation work with flyspell
A workaround is to inhibit flyspell overlays while the snippet is A workaround is to inhibit flyspell overlays while the snippet is
@ -107,7 +40,7 @@ Edit the keymaps [[sym:yas-minor-mode-map][=yas-minor-mode-map=]] and
#+begin_src emacs-lisp :exports code #+begin_src emacs-lisp :exports code
(define-key yas-minor-mode-map (kbd "<tab>") nil) (define-key yas-minor-mode-map (kbd "<tab>") nil)
(define-key yas-minor-mode-map (kbd "TAB") nil) (define-key yas-minor-mode-map (kbd "TAB") nil)
(define-key yas-minor-mode-map (kbd "<the new key>") 'yas-expand) (define-key yas-minor-mode-map (kbd "<the new key>") yas-maybe-expand)
;;keys for navigation ;;keys for navigation
(define-key yas-keymap [(tab)] nil) (define-key yas-keymap [(tab)] nil)

View File

@ -32,17 +32,30 @@
** Trigger key ** Trigger key
[[sym:yas-expand][=yas-expand=]] tries to expand a /snippet abbrev/ (also known as [[sym:yas-expand][=yas-expand=]] tries to expand a /snippet abbrev/ (also known as
/snippet key/) before point. /snippet key/) before point. YASnippet also provides a /conditional
binding/ for this command: the variable [[sym:yas-expand][=yas-maybe-expand=]] contains a
special value which, when bound in a keymap, tells Emacs to call
[[sym:yas-expand][=yas-expand=]] if and only if there is a snippet abbrev before point. If there is not
When [[sym:yas-minor-mode][=yas-minor-mode=]] is enabled, it binds [[sym:yas-expand][=yas-expand=]] to =TAB= and When [[sym:yas-minor-mode][=yas-minor-mode=]] is enabled, it binds [[sym:yas-maybe-expand][=yas-maybe-expand=]] to =TAB=
=<tab>= by default, however, you can freely set it to some other key: and =<tab>= by default, however, you can freely remove those bindings:
#+begin_src emacs-lisp :exports code #+begin_src emacs-lisp :exports code
(define-key yas-minor-mode-map (kbd "<tab>") nil) (define-key yas-minor-mode-map (kbd "<tab>") nil)
(define-key yas-minor-mode-map (kbd "TAB") nil) (define-key yas-minor-mode-map (kbd "TAB") nil)
(define-key yas-minor-mode-map (kbd "<the new key>") 'yas-expand)
#+end_src #+end_src
And set your own:
#+begin_src emacs-lisp :exports code
;; Bind `SPC' to `yas-expand' when snippet expansion available (it
;; will still call `self-insert-command' otherwise).
(define-key yas-minor-mode-map (kbd "SPC") yas-maybe-expand)
;; Bind `C-c y' to `yas-expand' ONLY.
(define-key yas-minor-mode-map (kbd "C-c y") #'yas-expand)
#+end_src
To enable the YASnippet minor mode in all buffers globally use the To enable the YASnippet minor mode in all buffers globally use the
command [[sym:yas-global-mode][=yas-global-mode=]]. This will enable a modeline indicator, command [[sym:yas-global-mode][=yas-global-mode=]]. This will enable a modeline indicator,
=yas=: =yas=:
@ -50,24 +63,14 @@ command [[sym:yas-global-mode][=yas-global-mode=]]. This will enable a modeline
[[./images/minor-mode-indicator.png]] [[./images/minor-mode-indicator.png]]
When you use [[sym:yas-global-mode][=yas-global-mode=]] you can also selectively disable When you use [[sym:yas-global-mode][=yas-global-mode=]] you can also selectively disable
YASnippet in some buffers by setting the buffer-local variable YASnippet in some buffers by calling [[sym:yas-minor-mode][=yas-minor-mode=]] with a negative
[[sym:yas-dont-active][=yas-dont-active=]] in the buffer's mode hook. argument in the buffer's mode hook.
*** Fallback behaviour *** Fallback behaviour
[[sym:yas-fallback-behaviour][=yas-fallback-behaviour=]] is a customization variable bound to YASnippet used to support a more complicated way of sharing
'=call-other-command= by default. If [[sym:yas-expand][=yas-expand=]] failed to find any keybindings before [[sym:yas-expand][=yas-maybe-expand=]] was added. This is now
suitable snippet to expand, it will disable the minor mode temporarily obsolete.
and find if there's any other command bound to the same key.
If found, the command will be called. Usually this works very well
--when there's a snippet, expand it, otherwise, call whatever command
originally bind to the trigger key.
However, you can change this behavior by customizing the
[[sym:yas-fallback-behavior][=yas-fallback-behavior=]] variable. If you set this variable to
'=return-nil=, it will return =nil= instead of trying to call the
/original/ command when no snippet is found.
** Insert at point ** Insert at point

View File

@ -668,11 +668,11 @@ TODO: correct this bug!"
(text-mode) (text-mode)
(yas-minor-mode +1) (yas-minor-mode +1)
(should (equal (yas-lookup-snippet "one") "one")) (should (equal (yas-lookup-snippet "one") "one"))
(should (eq (key-binding "\C-c1") 'yas-expand-from-keymap)) (should (eq (yas--key-binding "\C-c1") 'yas-expand-from-keymap))
(yas-define-snippets (yas-define-snippets
'text-mode '(("_1" "one!" "won" nil nil nil nil nil "uuid-1"))) 'text-mode '(("_1" "one!" "won" nil nil nil nil nil "uuid-1")))
(should (null (yas-lookup-snippet "one" nil 'noerror))) (should (null (yas-lookup-snippet "one" nil 'noerror)))
(should (null (key-binding "\C-c1"))) (should (null (yas--key-binding "\C-c1")))
(should (equal (yas-lookup-snippet "won") "one!"))))) (should (equal (yas-lookup-snippet "won") "one!")))))
(ert-deftest snippet-save () (ert-deftest snippet-save ()
@ -963,8 +963,15 @@ TODO: be meaner"
;;; The infamous and problematic tab keybinding ;;; The infamous and problematic tab keybinding
;;; ;;;
(ert-deftest test-yas-tab-binding () (ert-deftest test-yas-tab-binding ()
(yas-saving-variables
(yas-with-snippet-dirs
'((".emacs.d/snippets"
("fundamental-mode"
("foo" . "foobar"))))
(yas-reload-all)
(with-temp-buffer (with-temp-buffer
(yas-minor-mode -1) (yas-minor-mode -1)
(insert "foo")
(should (not (eq (key-binding (yas--read-keybinding "<tab>")) 'yas-expand))) (should (not (eq (key-binding (yas--read-keybinding "<tab>")) 'yas-expand)))
(yas-minor-mode 1) (yas-minor-mode 1)
(should (eq (key-binding (yas--read-keybinding "<tab>")) 'yas-expand)) (should (eq (key-binding (yas--read-keybinding "<tab>")) 'yas-expand))
@ -972,7 +979,7 @@ TODO: be meaner"
(should (eq (key-binding [(tab)]) 'yas-next-field-or-maybe-expand)) (should (eq (key-binding [(tab)]) 'yas-next-field-or-maybe-expand))
(should (eq (key-binding (kbd "TAB")) 'yas-next-field-or-maybe-expand)) (should (eq (key-binding (kbd "TAB")) 'yas-next-field-or-maybe-expand))
(should (eq (key-binding [(shift tab)]) 'yas-prev-field)) (should (eq (key-binding [(shift tab)]) 'yas-prev-field))
(should (eq (key-binding [backtab]) 'yas-prev-field)))) (should (eq (key-binding [backtab]) 'yas-prev-field))))))
(ert-deftest test-rebindings () (ert-deftest test-rebindings ()
(let* ((yas-minor-mode-map (copy-keymap yas-minor-mode-map)) (let* ((yas-minor-mode-map (copy-keymap yas-minor-mode-map))
@ -992,11 +999,18 @@ TODO: be meaner"
(should (eq (key-binding (kbd "SPC")) 'yas-expand))))) (should (eq (key-binding (kbd "SPC")) 'yas-expand)))))
(ert-deftest test-yas-in-org () (ert-deftest test-yas-in-org ()
(yas-saving-variables
(yas-with-snippet-dirs
'((".emacs.d/snippets"
("org-mode"
("foo" . "foobar"))))
(yas-reload-all)
(with-temp-buffer (with-temp-buffer
(org-mode) (org-mode)
(yas-minor-mode 1) (yas-minor-mode 1)
(insert "foo")
(should (eq (key-binding [(tab)]) 'yas-expand)) (should (eq (key-binding [(tab)]) 'yas-expand))
(should (eq (key-binding (kbd "TAB")) 'yas-expand)))) (should (eq (key-binding (kbd "TAB")) 'yas-expand))))))
(ert-deftest test-yas-activate-extra-modes () (ert-deftest test-yas-activate-extra-modes ()
"Given a symbol, `yas-activate-extra-mode' should be able to "Given a symbol, `yas-activate-extra-mode' should be able to
@ -1067,6 +1081,13 @@ add the snippets associated with the given mode."
(let ((interprogram-paste-function (lambda () string))) (let ((interprogram-paste-function (lambda () string)))
(ert-simulate-command '(yank nil)))) (ert-simulate-command '(yank nil))))
(defun yas--key-binding (key)
"Like `key-binding', but override `this-command-keys-vector'.
This lets `yas--maybe-expand-from-keymap-filter' work as expected."
(cl-letf (((symbol-function 'this-command-keys-vector)
(lambda () (cl-coerce key 'vector))))
(key-binding key)))
(defun yas-make-file-or-dirs (ass) (defun yas-make-file-or-dirs (ass)
(let ((file-or-dir-name (car ass)) (let ((file-or-dir-name (car ass))
(content (cdr ass))) (content (cdr ass)))

View File

@ -271,22 +271,20 @@ Otherwise `yas-next-field-or-maybe-expand' just moves on to the
next field" next field"
:type 'boolean) :type 'boolean)
(defcustom yas-fallback-behavior 'call-other-command (defcustom yas-fallback-behavior 'return-nil
"How to act when `yas-expand' does *not* expand a snippet. "This option is obsolete.
Now that the conditional keybinding `yas-maybe-expand' is
- `call-other-command' means try to temporarily disable YASnippet available, there's no more need for it."
and call the next command bound to whatever key was used to
invoke `yas-expand'.
- nil or the symbol `return-nil' mean do nothing. (and
`yas-expand' returns nil)
- A Lisp form (apply COMMAND . ARGS) means interactively call
COMMAND. If ARGS is non-nil, call COMMAND non-interactively
with ARGS as arguments."
:type '(choice (const :tag "Call previous command" call-other-command) :type '(choice (const :tag "Call previous command" call-other-command)
(const :tag "Do nothing" return-nil))) (const :tag "Do nothing" return-nil)))
(make-obsolete-variable
'yas-fallback-behavior
"For `call-other-command' behavior bind to the conditional
command value `yas-maybe-expand', for `return-nil' behavior bind
directly to `yas-expand'."
"0.12")
(defcustom yas-choose-keys-first nil (defcustom yas-choose-keys-first nil
"If non-nil, prompt for snippet key first, then for template. "If non-nil, prompt for snippet key first, then for template.
@ -560,10 +558,20 @@ snippet itself contains a condition that returns the symbol
(defvar yas--minor-mode-menu nil (defvar yas--minor-mode-menu nil
"Holds the YASnippet menu.") "Holds the YASnippet menu.")
(defun yas--maybe-expand-key-filter (cmd)
(if (yas--templates-for-key-at-point) cmd))
(defconst yas-maybe-expand
'(menu-item "" yas-expand :filter yas--maybe-expand-key-filter)
"A conditional key definition.
This can be used as a key definition in keymaps to bind a key to
`yas-expand' only when there is a snippet available to be
expanded.")
(defvar yas-minor-mode-map (defvar yas-minor-mode-map
(let ((map (make-sparse-keymap))) (let ((map (make-sparse-keymap)))
(define-key map [(tab)] 'yas-expand) (define-key map [(tab)] yas-maybe-expand)
(define-key map (kbd "TAB") 'yas-expand) (define-key map (kbd "TAB") yas-maybe-expand)
(define-key map "\C-c&\C-s" 'yas-insert-snippet) (define-key map "\C-c&\C-s" 'yas-insert-snippet)
(define-key map "\C-c&\C-n" 'yas-new-snippet) (define-key map "\C-c&\C-n" 'yas-new-snippet)
(define-key map "\C-c&\C-v" 'yas-visit-snippet-file) (define-key map "\C-c&\C-v" 'yas-visit-snippet-file)
@ -703,10 +711,10 @@ This variable is placed in `emulation-mode-map-alists'.
Its elements looks like (TABLE-NAME . KEYMAP). They're Its elements looks like (TABLE-NAME . KEYMAP). They're
instantiated on `yas-reload-all' but KEYMAP is added to only when instantiated on `yas-reload-all' but KEYMAP is added to only when
loading snippets. `yas--direct-TABLE-NAME' is then a variable set loading snippets. `yas--direct-TABLE-NAME' is then a variable
buffer-locally when entering `yas-minor-mode'. KEYMAP binds all set buffer-locally when entering `yas-minor-mode'. KEYMAP binds
defined direct keybindings to the command all defined direct keybindings to `yas-maybe-expand-from-keymap'
`yas-expand-from-keymap' which then which snippet to expand.") which decides on the snippet to expand.")
(defun yas-direct-keymaps-reload () (defun yas-direct-keymaps-reload ()
"Force reload the direct keybinding for active snippet tables." "Force reload the direct keybinding for active snippet tables."
@ -976,7 +984,7 @@ Has the following fields:
A keymap for the snippets in this table that have direct A keymap for the snippets in this table that have direct
keybindings. This is kept in sync with the keyhash, i.e., all keybindings. This is kept in sync with the keyhash, i.e., all
the elements of the keyhash that are vectors appear here as the elements of the keyhash that are vectors appear here as
bindings to `yas-expand-from-keymap'. bindings to `yas-maybe-expand-from-keymap'.
`yas--table-uuidhash' `yas--table-uuidhash'
@ -1075,6 +1083,10 @@ Has the following fields:
;; ;;
(remhash uuid (yas--table-uuidhash table)))))) (remhash uuid (yas--table-uuidhash table))))))
(defconst yas-maybe-expand-from-keymap
'(menu-item "" yas-expand-from-keymap
:filter yas--maybe-expand-from-keymap-filter))
(defun yas--add-template (table template) (defun yas--add-template (table template)
"Store in TABLE the snippet template TEMPLATE. "Store in TABLE the snippet template TEMPLATE.
@ -1093,7 +1105,7 @@ keybinding)."
(make-hash-table :test 'equal) (make-hash-table :test 'equal)
(yas--table-hash table)))) (yas--table-hash table))))
(when (vectorp k) (when (vectorp k)
(define-key (yas--table-direct-keymap table) k 'yas-expand-from-keymap))) (define-key (yas--table-direct-keymap table) k yas-maybe-expand-from-keymap)))
;; Update TABLE's `yas--table-uuidhash' ;; Update TABLE's `yas--table-uuidhash'
(puthash (yas--template-uuid template) (puthash (yas--template-uuid template)
@ -2167,12 +2179,7 @@ object satisfying `yas--field-p' to restrict the expansion to."
(nth 2 templates-and-pos))) (nth 2 templates-and-pos)))
(yas--fallback)))) (yas--fallback))))
(defun yas-expand-from-keymap () (defun yas--maybe-expand-from-keymap-filter (cmd)
"Directly expand some snippets, searching `yas--direct-keymaps'.
If expansion fails, execute the previous binding for this key"
(interactive)
(setq yas--condition-cache-timestamp (current-time))
(let* ((vec (cl-subseq (this-command-keys-vector) (let* ((vec (cl-subseq (this-command-keys-vector)
(if current-prefix-arg (if current-prefix-arg
(length (this-command-keys)) (length (this-command-keys))
@ -2180,10 +2187,15 @@ If expansion fails, execute the previous binding for this key"
(templates (cl-mapcan (lambda (table) (templates (cl-mapcan (lambda (table)
(yas--fetch table vec)) (yas--fetch table vec))
(yas--get-snippet-tables)))) (yas--get-snippet-tables))))
(if templates (if templates (or cmd templates))))
(yas--expand-or-prompt-for-template templates)
(let ((yas-fallback-behavior 'call-other-command)) (defun yas-expand-from-keymap ()
(yas--fallback))))) "Directly expand some snippets, searching `yas--direct-keymaps'."
(interactive)
(setq yas--condition-cache-timestamp (current-time))
(let* ((templates (yas--maybe-expand-from-keymap-filter nil)))
(when templates
(yas--expand-or-prompt-for-template templates))))
(defun yas--expand-or-prompt-for-template (templates &optional start end) (defun yas--expand-or-prompt-for-template (templates &optional start end)
"Expand one of TEMPLATES from START to END. "Expand one of TEMPLATES from START to END.