2

I have put the following code (partially adapted from a company-mode issue and a Stack Exchange question) in my init.el in order to adjust the keybindings active in the company-mode completions menu:

(defun company-complete-if-explicit ()
  "Complete the current selection, but only if the user has interacted
explicitly with Company."
  (interactive)
  (if (company-explicit-action-p)
      (company-complete)
    (call-interactively
     (key-binding (this-command-keys)))))

(define-key company-active-map (kbd "<return>") #'company-complete-if-explicit)
(define-key company-active-map (kbd "RET") #'company-complete-if-explicit)
(define-key company-active-map (kbd "TAB") #'company-complete-selection)
(define-key company-active-map (kbd "SPC") nil)

(setq company-auto-complete-chars nil)

In terminal Emacs, pressing RET will dismiss the completions menu and insert a newline. However, if I have explicitly interacted with the menu (for instance, using up and down), then RET will instead insert the selected completion.

The problem is that this doesn't work in windowed Emacs: if I press <return> while the completions menu is showing (but I haven't explicitly interacted with it), then instead of Emacs inserting a newline, I receive the following error:

Debugger entered--Lisp error: (wrong-type-argument commandp nil)
  call-interactively(nil)
  (if (company-explicit-action-p) (company-complete) (call-interactively (key-binding (this-command-keys))))
  company-complete-if-explicit()
  call-interactively(company-complete-if-explicit nil nil)
  command-execute(company-complete-if-explicit)

I debugged this, and here is what I think is happening:

  • In windowed Emacs, the Return key sends <return> instead of RET.
  • Only RET has a binding (to newline), but normally <return> is translated automatically to RET, so the Return key still inserts a newline in windowed Emacs.
  • When I call this-command-keys in company-complete-if-explicit, the returned value is <return>.
  • The key-binding function does not appear to do the key translation that usually occurs, so that (key-binding (kbd "RET")) is newline but (key-binding (kbd "<return>")) is nil.
  • The error is thrown because nil is then passed to call-interactively.

My question is therefore: what is the best way to fix this problem (either by making key-binding perform automatic translations, or otherwise)?

Resigned June 2023
  • 1,502
  • 15
  • 20
  • 1
    I don't have time to write up a full answer at the moment, but if I read your intent correctly, [this](http://stackoverflow.com/questions/16090517/elisp-conditionally-change-keybinding/22863701#22863701) seems to be quite relevant. – PythonNut Sep 30 '16 at 00:21
  • @PythonNut It's beautiful and perfect! The patch is at: https://github.com/raxod502/radian/commit/af57ab37d7532f1441fa39e72733d473502800ff I will add an answer summarizing it, unless you would like to add your own answer. – Resigned June 2023 Sep 30 '16 at 02:02

1 Answers1

2

The solution that worked for me was to replace

(defun company-complete-if-explicit ()
  "Complete the current selection, but only if the user has interacted
explicitly with Company."
  (interactive)
  (if (company-explicit-action-p)
      (company-complete)
    (call-interactively
     (key-binding (this-command-keys)))))

;; <return> is for windowed Emacs; RET is for terminal Emacs
(define-key company-active-map (kbd "<return>") #'company-complete-if-explicit)
(define-key company-active-map (kbd "RET") #'company-complete-if-explicit)

with

;; <return> is for windowed Emacs; RET is for terminal Emacs
(dolist (key '("<return>" "RET"))
  ;; Here we are using an advanced feature of define-key that lets
  ;; us pass an "extended menu item" instead of an interactive
  ;; function. Doing this allows RET to regain its usual
  ;; functionality when the user has not explicitly interacted with
  ;; Company.
  (define-key company-active-map (kbd key)
    `(menu-item nil company-complete
                :filter ,(lambda (cmd)
                           (when (company-explicit-action-p)
                             cmd)))))

Now RET will act like normal when I don't interact with Company, but it will insert the selected completion when I have interacted with Company—in both terminal and windowed Emacs.

Here is the complete patch.

Resigned June 2023
  • 1,502
  • 15
  • 20