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?

Leu_Grady
  • 2,420
  • 1
  • 17
  • 27
  • 1
    I'm using `tab` for `company` and `C-o` for `yasnippet`. I can describe further if you're interested. – abo-abo Feb 03 '15 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 03 '15 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 03 '15 at 10:14
  • seems interesting, can you describe more?:) – Leu_Grady Feb 03 '15 at 10:17

2 Answers2

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.

Kristóf Marussy
  • 606
  • 4
  • 10
  • This seems to interfere with magit. Causes tab in magit to raise `Buffer is read-only: #`. Any idea how I can fix that? – zsquare Sep 05 '15 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`](https://github.com/magit/magit/blob/5309fe9416495c434ece122698492a5d11ae5125/lisp/magit-mode.el#L223), 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`. – dodgethesteamroller Sep 08 '15 at 21:53
  • this is awesome, thanks! :) small style point, `when` is pretty much an `if` + `progn` – Matt Briggs Jan 14 '16 at 06: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 '17 at 08:35
  • To support ido instead of default minibuffer completion, replace the cond with ` ((minibufferp) (ido-complete))` – Bae Aug 19 '17 at 08:36
  • I also needed to fix ido in the minibuffer (add-hook 'ido-setup-hook (lambda () (define-key ido-completion-map [tab] 'ido-complete))) – Bae Aug 25 '17 at 11:04
  • Unfortunately, today, hitting tab in the company completion drop-down expands the snippet regardless. – RichieHH Jan 29 '20 at 08:26
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
abo-abo
  • 13,943
  • 1
  • 29
  • 43
  • 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 03 '15 at 10:40
  • I have no such issue: `C-o` with company menu active will close the menu **and** expand snippet. – abo-abo Feb 03 '15 at 10:52