more document for selecting a snippet

This commit is contained in:
Zhang Chiyuan 2008-03-20 15:12:58 +00:00
parent 11a716c3b6
commit 62b10948e7
2 changed files with 366 additions and 2 deletions

View File

@ -378,6 +378,186 @@ comment to be evaluated to the symbol ``force-in-comment``. Then it
can be expanded as you expected, while other snippets like ``if`` can be expanded as you expected, while other snippets like ``if``
still can't expanded in comment. still can't expanded in comment.
Multiple snippet with the same key
----------------------------------
There can be multiple snippet bind to the same key. If you define a
snippet with a key that is already used, you'll overwrite the original
snippet definition. However, you can add a different *postfix* to the
key.
In general, the *extension* (consider a file name) is *ignored* when
defining a snippet. So ``def``, ``def.1`` and ``def.mine`` will all be
valid candidates when the key is ``def``.
When there are multiple candidates, YASnippet will let you select
one. The UI for selecting multiple candidate can be
customized. There're two variable related:
* ``yas/window-system-popup-function``: the function used when you
have a window system.
* ``yas/text-popup-function``: the function used when you don't have a
window system, i.e. when you are working in a terminal.
Currently there're three solution come with YASnippet.
Popup Menu
~~~~~~~~~~
The function ``yas/x-popup-menu-for-template`` can be used to show a
popup menu for you to select. This menu will be part of you native
window system widget, which means:
* It usually looks beautiful. E.g. when you compile Emacs with gtk
support, this menu will be rendered with your gtk theme.
* Emacs have little control over it. E.g. you can't use ``C-n``,
``C-p`` to navigate.
* This function can't be used when in a terminal.
Just select the first one
~~~~~~~~~~~~~~~~~~~~~~~~~
This one is originally used in terminal mode. It doesn't let you to
choose anything, it just select the first one on behalf of you. So I
bet you never want to use this. :p
Use a dropdown-menu.el
~~~~~~~~~~~~~~~~~~~~~~
Originally, only the above two function is available in
YASnippet. They are difficult to use -- especially in a
terminal. Until later Jaeyoun Chung show me his
``dropdown-menu.el``, I say wow! It's wonderful!
* It works in both window system and terminal.
* It is customizable, you can use ``C-n``, ``C-p`` to navigate, ``q``
to quite and even press ``6`` as a shortcut to select the 6th
candidate.
So I added ``yas/dropdown-list-popup-for-template`` to support
``dropdown-list.el``. And upload ``dropdown-list.el`` to YASnippet
hompage for an optional download (since Jaeyoun didn't provide a URL).
Then finally, in 0.4.0, I included a copy of the content of
``dropdown-list.el`` [1]_ in ``yasnippet.el`` and made it the default
way for selecting multiple candidates.
However, the original functions are still there, you can still use this
.. sourcecode:: common-lisp
(setq yas/window-system-popup-function
'yas/x-popup-menu-for-template)
if you prefer a *modern* UI. :)
The Trigger Key
---------------
YASnippet is implemented as a minor-mode (``yas/minor-mode``). The
trigger key ``yas/trigger-key`` is defined in ``yas/minor-mode-map``
to call ``yas/expand`` to try to expand a snippet.
The Minor Mode
~~~~~~~~~~~~~~
When ``yas/minor-mode`` is enabled, the trigger key will take
effect. The default key is ``(kbd "TAB")``, however, you can freely
set it to some other key. By default, YASnippet add a hook to
``after-change-major-mode-hook`` to enable ``yas/minor-mode`` [2]_ in
every buffer. This works fine for most modes, however, some mode
doesn't follow the Emacs convention and doens't call this hook. You
can either explicitly hook for those mode or just add it to
``yas/extra-mode-hooks`` to let YASnippet do it for you:
.. sourcecode:: common-lisp
(require 'yasnippet)
(add-to-list 'yas/extra-mode-hooks
'ruby-mode-hook)
(yas/initialize)
Note that **should** be put after ``(require 'yasnippet)`` and before
``(yas/initialize)``. Further more, you may report it to me, I'll add
that to the default value.
The Fallback
~~~~~~~~~~~~
If ``yas/expand`` failed to find any suitable snippet to expand, it
will disable the minor mode temporarily and find if there's any other
command bind the ``yas/trigger-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.
Other way to select a snippet
-----------------------------
When you use the trigger key (so ``yas/expand``) to expand a snippet,
the key for the snippet is deleted before the template for the snippet
is inserted.
However, there're other ways to insert a snippet.
The Menu
~~~~~~~~
YASnippet will setup a menu just after the *Buffers* Menu in the
menubar. The snippets for all *real* modes are listed there under the
menu. You can select a snippet from the menu to expand it. Since you
select manually from the menu, you can expand any snippet. For
example, you can expand a snippet defined for ``python-mode`` in a
``c-mode`` buffer by selecting it from the menu:
* Condition system is ignored since you select to expand it
explicitly.
* There will be no muliple candidates since they are listed in the
menu as different items.
This can be convenient sometimes. However, if you don't like the
menubar of Emacs and never use it. You can tell YASnippet don't boring
to build a menu by setting ``yas/use-menu`` to nil.
Another thing to note is that only *real* modes are listed under the
menu. As you know, common snippets can be shared by making up a
*virtual* parent mode. It's too bad if the menu is floored by those
*virtual* modes. So YASnippet only show menus for those *real*
modes. But the snippets fo the *virtual* modes can still be accessed
through the ``parent`` submenu of some *real* mode.
YASnippet use a simple way to check whether a mode is *real* or
*virtual*: ``(fboundp mode)``. For example, the symbol ``c-mode`` is
bound to a function while ``cc-mode`` is not. But this is not enough,
some modes aren't part of Emacs, and maybe when initializing
YASnippet, those modes haven't been initialized. So YASnippet also
maintain a list of known modes (``yas/known-modes``). You can add item
to that list if you need.
Expanding From Elisp Code
~~~~~~~~~~~~~~~~~~~~~~~~~
Sometimes you might want to expand a snippet directly by calling a
functin from elisp code. You should call ``yas/expand-snippet``
instead of ``yas/expand`` in this case.
As with expanding from the menubar, condition system and multiple
candidates won't exists here. In fact, expanding from menubar has the
same effect of evaluating the follow code:
.. sourcecode:: common-lisp
(yas/expand-snippet (point) (point) template)
Where ``template`` is the template of a snippet. It is never required
to belong to any snippet -- you can even make up it on the fly. The
1st and 2nd parameter defines the region to be deleted after YASnippet
inserted the template. It is used by ``yas/expand`` to indicate the
region of the key. There's usually no need to delete any region when
we are expanding a snippet from elisp code, so passing two ``(point)``
is fine. Note only ``(point)`` will be fine because the 1st parameter
also indicate where to insert and expand the ``template``.
The Syntax of the Template The Syntax of the Template
========================== ==========================
@ -391,3 +571,6 @@ The Syntax of the Template
* ``condition``: The condition of the snippet. This is a piece of * ``condition``: The condition of the snippet. This is a piece of
elisp code. If a snippet has a condition, then it will only be elisp code. If a snippet has a condition, then it will only be
expanded when the condition code evaluate to some non-nil value. expanded when the condition code evaluate to some non-nil value.
.. [1] With some minor change, mainly for fixing some trivial bugs.
.. [2] This is done when you call ``yas/initialize``.

View File

@ -94,11 +94,11 @@ mode will be listed under the menu \"yasnippet\".")
(t (:background "gray22"))) (t (:background "gray22")))
"The face used to highlight mirror fields of a snippet.") "The face used to highlight mirror fields of a snippet.")
(defvar yas/window-system-popup-function #'yas/x-popup-menu-for-template (defvar yas/window-system-popup-function #'yas/dropdown-list-popup-for-template
"When there's multiple candidate for a snippet key. This function "When there's multiple candidate for a snippet key. This function
is called to let user select one of them. `yas/text-popup-function' is called to let user select one of them. `yas/text-popup-function'
is used instead when not in a window system.") is used instead when not in a window system.")
(defvar yas/text-popup-function #'yas/text-popup-for-template (defvar yas/text-popup-function #'yas/dropdown-list-popup-for-template
"When there's multiple candidate for a snippet key. If not in a "When there's multiple candidate for a snippet key. If not in a
window system, this function is called to let user select one of window system, this function is called to let user select one of
them. `yas/window-system-popup-function' is used instead when in them. `yas/window-system-popup-function' is used instead when in
@ -1181,3 +1181,184 @@ handle the end-of-buffer error fired in it by calling
(condition-case err (condition-case err
ad-do-it ad-do-it
(error (message (error-message-string err))))) (error (message (error-message-string err)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Contents of dropdown-list.el
;;
;; dropdown-list.el is used by yasnippet to select multiple
;; candidate snippets.
;;
;; This is a slightly modified version of the original
;; dropdown-list.el
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; dropdown-list.el --- dropdown menu interface
;; Copyright (C) 2008 Jaeyoun Chung
;; Author: jay AT kldp DOT org
;; Keywords: convenience
;;
;; overlay code stolen from company-mode.el
;;
;;; Code:
(defface dropdown-list-face
'((t :inherit default
:background "lightyellow"
:foreground "black"))
"*Bla."
:group 'dropdown-list)
(defface dropdown-list-selection-face
'((t :inherit dropdown-list
:background "purple"))
"*Bla."
:group 'dropdown-list)
(defvar dropdown-list-overlays nil)
(defun dropdown-list-hide ()
(while dropdown-list-overlays
(delete-overlay (pop dropdown-list-overlays))))
(defun dropdown-list-put-overlay (beg end &optional prop value prop2
value2)
(let ((ov (make-overlay beg end)))
(overlay-put ov 'window t)
(when prop
(overlay-put ov prop value)
(when prop2
(overlay-put ov prop2 value2)))
ov))
(defun dropdown-list-line (start replacement &optional no-insert)
;; start might be in the middle of a tab, which means we need to hide the
;; tab and add spaces
(let ((end (+ start (length replacement)))
beg-point end-point
before-string after-string)
(goto-char (point-at-eol))
(if (< (current-column) start)
(progn (setq before-string
(make-string (- start (current-column)) ? ))
(setq beg-point (point)))
(goto-char (point-at-bol)) ;; Emacs bug, move-to-column is wrong otherwise
(move-to-column start)
(setq beg-point (point))
(when (> (current-column) start)
(goto-char (1- (point)))
(setq beg-point (point))
(setq before-string (make-string (- start (current-column)) ? ))))
(move-to-column end)
(setq end-point (point))
(let ((end-offset (- (current-column) end)))
(when (> end-offset 0)
(setq after-string (make-string end-offset ?b))))
(when no-insert
;; prevent inheriting of faces
(setq before-string (when before-string
(propertize before-string 'face 'default)))
(setq after-string (when after-string
(propertize after-string 'face 'default))))
(let ((string (concat before-string
replacement
after-string)))
(if no-insert
string
(push (dropdown-list-put-overlay beg-point end-point
'invisible t
'after-string string)
dropdown-list-overlays)))))
(defun dropdown-list-start-column (display-width)
(let ((column (mod (current-column) (window-width)))
(width (window-width)))
(cond ((<= (+ column display-width) width)
column)
((> column display-width)
(- column display-width))
((>= width display-width)
(- width display-width))
(t
nil))))
(defun dropdown-list-move-to-start-line (candidate-count)
(decf candidate-count)
(let ((above-line-count (save-excursion (- (vertical-motion (-
candidate-count)))))
(below-line-count (save-excursion (vertical-motion candidate-count))))
(cond ((= below-line-count candidate-count)
t)
((= above-line-count candidate-count)
(vertical-motion (- candidate-count))
t)
((>= (+ below-line-count above-line-count) candidate-count)
(vertical-motion (- (- candidate-count below-line-count)))
t)
(t
nil))))
(defun dropdown-list-at-point (candidates &optional selidx)
(dropdown-list-hide)
(let* ((lengths (mapcar #'length candidates))
(max-length (apply #'max lengths))
(start (dropdown-list-start-column (+ max-length 3)))
(i -1)
(candidates (mapcar* (lambda (candidate length)
(let ((diff (- max-length length)))
(propertize
(concat (if (> diff 0)
(concat candidate (make-string diff ? ))
(substring candidate 0 max-length))
(format "%3d" (+ 2 i)))
'face (if (eql (incf i) selidx)
'dropdown-list-selection-face
'dropdown-list-face))))
candidates lengths)))
(save-excursion
(and start
(dropdown-list-move-to-start-line (length candidates))
(loop initially (vertical-motion 0)
for candidate in candidates
do (dropdown-list-line (+ (current-column) start) candidate)
while (/= (vertical-motion 1) 0)
finally return t)))))
(defun dropdown-list (candidates)
(let ((selection) (temp-buffer))
(save-window-excursion
(unwind-protect
(let ((candidate-count (length candidates))
done key selidx)
(while (not done)
(unless (dropdown-list-at-point candidates selidx)
(switch-to-buffer (setq temp-buffer (get-buffer-create "*selection*")) 'norecord)
(delete-other-windows)
(delete-region (point-min) (point-max))
(insert (make-string (length candidates) ?\n))
(goto-char (point-min))
(dropdown-list-at-point candidates selidx))
(setq key (read-key-sequence ""))
(cond ((and (stringp key) (>= (aref key 0) ?1) (<= (aref key 0)
(+ ?0 (min 9 candidate-count))))
(setq selection (- (aref key 0) ?1)
done t))
((member key '("" [up]))
(setq selidx (mod (+ candidate-count (1- (or selidx 0)))
candidate-count)))
((member key '("" [down]))
(setq selidx (mod (1+ (or selidx -1)) candidate-count)))
((member key '(" ")))
((member key '("
" [return] ""))
(setq selection selidx
done t))
(t
(setq done t)))))
(dropdown-list-hide)
(and temp-buffer (kill-buffer temp-buffer)))
selection)))
;;; contents dropdown-list.el ends here