27

In my emacs, let's say, I use a "elisp" yasnippet to extend a lisp block in org-mode. But before I extend it, company is triggered first, which gives me a menu like "1. elisp1, 2. elisp2" without an option "elisp". Now if I use tab to extend yasnippet, it is always annoying that "elisp1" always first goes on the screen. So I need to delete "1"firstly, and do the extension of yasnippet snippet.

So as a solution, I always use left arrow key to turn company completion menu off first, but the cursor now will go to "elis|p", so again I use right arrow key to move the cursor to end of "elisp|", and extend the snippet.

Here comes my question: how can I bind tab key firstly trigger yasnippet but not company to save me life?

4
  • 1
    I'm using tab for company and C-o for yasnippet. I can describe further if you're interested.
    – abo-abo
    Feb 3, 2015 at 10:07
  • @abo-abo, thanks reply. I know I can do it as you do, but I bind C-o to other command, and badly I have trained my hand muscle to adapt tab. So I would not like to change the binding.
    – Leu_Grady
    Feb 3, 2015 at 10:10
  • That's why I asked:) No point in going on a rant of how C-o could expand abbrevs and snippets and open lines etc. if you're not interested.
    – abo-abo
    Feb 3, 2015 at 10:14
  • seems interesting, can you describe more?:)
    – Leu_Grady
    Feb 3, 2015 at 10:17

2 Answers 2

25

This is what I created for myself, facing the same issue. It is from company-mode's Emacs Wiki page, but heavily extended:

(defun check-expansion ()
  (save-excursion
    (if (looking-at "\\_>") t
      (backward-char 1)
      (if (looking-at "\\.") t
    (backward-char 1)
    (if (looking-at "->") t nil)))))

(defun do-yas-expand ()
  (let ((yas/fallback-behavior 'return-nil))
    (yas/expand)))

(defun tab-indent-or-complete ()
  (interactive)
  (cond
   ((minibufferp)
    (minibuffer-complete))
   (t
    (indent-for-tab-command)
    (if (or (not yas/minor-mode)
        (null (do-yas-expand)))
    (if (check-expansion)
        (progn
          (company-manual-begin)
          (if (null company-candidates)
          (progn
            (company-abort)
            (indent-for-tab-command)))))))))

(defun tab-complete-or-next-field ()
  (interactive)
  (if (or (not yas/minor-mode)
      (null (do-yas-expand)))
      (if company-candidates
      (company-complete-selection)
    (if (check-expansion)
      (progn
        (company-manual-begin)
        (if (null company-candidates)
        (progn
          (company-abort)
          (yas-next-field))))
      (yas-next-field)))))

(defun expand-snippet-or-complete-selection ()
  (interactive)
  (if (or (not yas/minor-mode)
      (null (do-yas-expand))
      (company-abort))
      (company-complete-selection)))

(defun abort-company-or-yas ()
  (interactive)
  (if (null company-candidates)
      (yas-abort-snippet)
    (company-abort)))

(global-set-key [tab] 'tab-indent-or-complete)
(global-set-key (kbd "TAB") 'tab-indent-or-complete)
(global-set-key [(control return)] 'company-complete-common)

(define-key company-active-map [tab] 'expand-snippet-or-complete-selection)
(define-key company-active-map (kbd "TAB") 'expand-snippet-or-complete-selection)

(define-key yas-minor-mode-map [tab] nil)
(define-key yas-minor-mode-map (kbd "TAB") nil)

(define-key yas-keymap [tab] 'tab-complete-or-next-field)
(define-key yas-keymap (kbd "TAB") 'tab-complete-or-next-field)
(define-key yas-keymap [(control tab)] 'yas-next-field)
(define-key yas-keymap (kbd "C-g") 'abort-company-or-yas)

Basically, this makes <tab> do the right thing most of the time. Pressing tab will

  • Indent the current line,
  • If there is a yasnippet to expand, expand it, even if this means aborting a company completion (I don't use abbreviations much, so no abbreviation support yet),
  • If a company completion is ongoing, complete with the selected item,
  • Otherwise try to use company to start autocomplete,
  • If there is nothing to autocomplete and we're in a yasnippet placeholder, skip to the next placeholder.

Note that if there is an opportunity to autocomplete and you are currently editing in a snippet placeholder, the situation is ambigous. As a compromise, I bound C-<tab> to skip to the next placeholder directly.

The fact that the snippet's name does not appear in the company menu and the existence of a snippet silently modifies the behaviour of the tab key is not particularly nice, unfortunately... Although at least it is possible to type <return> instead to get the completion instead of the snippet.

7
  • This seems to interfere with magit. Causes tab in magit to raise Buffer is read-only: #<buffer *magit: ~/.emacs.d/*>. Any idea how I can fix that?
    – zsquare
    Sep 5, 2015 at 10:59
  • @zsquare I don't use magit (I know, I'm nuts) so I can't test this to be sure, but it sounds like magit's keymap for TAB, which binds it to magit-section-toggle, is conflicting with the line (global-set-key [tab] 'tab-indent-or-complete) above. A quick and dirty fix would be to add a check at the beginning of the function tab-indent-or-complete above to see whether we're in magit mode, e.g. for a global variable that gets set on magit-mode-hook. Sep 8, 2015 at 21:53
  • this is awesome, thanks! :) small style point, when is pretty much an if + progn Jan 14, 2016 at 6:19
  • @zsquare To support tab in magit mode add this to the tab-indent-or-complete cond ` ((derived-mode-p 'magit-mode) (magit-section-toggle (magit-current-section)))`
    – Bae
    Aug 19, 2017 at 8:35
  • To support ido instead of default minibuffer completion, replace the cond with ` ((minibufferp) (ido-complete))`
    – Bae
    Aug 19, 2017 at 8:36
13

Here's the code that I'm using:

(global-set-key "\C-o" 'aya-open-line)

(defun aya-open-line ()
  "Call `open-line', unless there are abbrevs or snippets at point.
In that case expand them.  If there's a snippet expansion in progress,
move to the next field. Call `open-line' if nothing else applies."
  (interactive)
  (cond ((expand-abbrev))

        ((yas--snippets-at-point)
         (yas-next-field-or-maybe-expand))

        ((ignore-errors
           (yas-expand)))

        (t
         (open-line 1))))

aya-open-line from auto-yasnippet does more than a plain open-line:

  • it tries to expand abbrevs
  • it tries to move to the next field of yasnippet
  • it tries to expand yasnippet
  • finally, it calls open-line if all else fails
2
  • thanks for your snippet. Pretty good. But the problem still exists. When I first use C-o, it just close company menu, so I need twice press to extend yasnippet.
    – Leu_Grady
    Feb 3, 2015 at 10:40
  • I have no such issue: C-o with company menu active will close the menu and expand snippet.
    – abo-abo
    Feb 3, 2015 at 10:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.