branch: master commit 4c1fd0ea05bdcc188f6de43275cef73ccc89ec0a Merge: c0098af 66a9e65 Author: Dmitry Gutov <dgu...@yandex.ru> Commit: GitHub <nore...@github.com>
Merge pull request #706 from nikital/company-tng Company Tab and Go --- NEWS.md | 5 ++ company-tng.el | 155 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ company.el | 23 ++++++--- 3 files changed, 175 insertions(+), 8 deletions(-) diff --git a/NEWS.md b/NEWS.md index 5d5c63c..acc5968 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,6 +3,11 @@ ## Next * New user option `company-tooltip-maximum-width`. +* New feature `company-tng'. When added as a frontend, it allows you to insert + the candidates as soon they are selected, eliminating the need to press RET to + confirm your selection. See `company-tng.el` and use + `(company-tng-configure-default)` to give it a try. + ([#526](https://github.com/company-mode/company-mode/issues/526)) ## 2017-07-15 (0.9.4) diff --git a/company-tng.el b/company-tng.el new file mode 100644 index 0000000..3996f31 --- /dev/null +++ b/company-tng.el @@ -0,0 +1,155 @@ +;;; company-tng.el --- company-mode frontend that inserts a candidate +;;; into the buffer as soon as it's selected, Vim style + +;; Copyright (C) 2017 Free Software Foundation, Inc. + +;; Author: Nikita Leshenko + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + + +;;; Commentary: +;; +;; company-tng (tab and go) allows you to use TAB to both select a +;; completion candidate from the list and to insert it into the +;; buffer. +;; +;; It cycles the candidates like `yank-pop' or `dabbrev-expand' or +;; Vim: Pressing TAB selects the first item in the completion menu and +;; inserts it in the buffer. Pressing TAB again selects the second +;; item and replaces the inserted item with the second one. This can +;; continue as long as the user wishes to cycle through the menu. +;; +;; The benefits are that there is only one shortcut key to interact +;; with and there is no need to confirm an entry. +;; +;; Usage: +;; +;; To apply the default configuration for company-tng call +;; `company-tng-configure-default' from your configuration file. +;; +;; You can also configure company-tng manually: +;; +;; Add `company-tng-frontend' to `company-frontends': +;; (add-to-list 'company-frontends 'company-tng-frontend) +;; +;; We recommend to bind TAB to `company-select-next', S-TAB to +;; `company-select-previous', and unbind RET and other now-unnecessary +;; keys from `company-active-map': +;; (define-key company-active-map (kbd "TAB") 'company-select-next) +;; (define-key company-active-map (kbd "<backtab>") 'company-select-previous) +;; (define-key company-active-map (kbd "RET") nil) +;; Note that it's not necessary to rebind keys to use this frontend, +;; you can use the arrow keys or M-n/M-p to select and insert +;; candidates. You also need to decide which keys to unbind, depending +;; on whether you want them to do the Company action or the default +;; Emacs action (for example C-s or C-w). + +;;; Code: + +(require 'company) +(require 'cl-lib) + +(defvar-local company-tng--overlay nil) + +;;;###autoload +(defun company-tng-frontend (command) + "When the user changes the selection at least once, this +frontend will display the candidate in the buffer as if it's +already there and any key outside of `company-active-map' will +confirm the selection and finish the completion." + (cl-case command + (show + (let ((ov (make-overlay (point) (point)))) + (setq company-tng--overlay ov) + (overlay-put ov 'priority 2)) + (advice-add 'company-select-next :before-until 'company-tng--allow-unselected) + (advice-add 'company-fill-propertize :filter-args 'company-tng--adjust-tooltip-highlight)) + (update + (let ((ov company-tng--overlay) + (selected (nth company-selection company-candidates)) + (prefix (length company-prefix))) + (move-overlay ov (- (point) prefix) (point)) + (overlay-put ov + (if (= prefix 0) 'after-string 'display) + (and company-selection-changed selected)) + (overlay-put ov 'display (and company-selection-changed selected)))) + (hide + (when company-tng--overlay + (delete-overlay company-tng--overlay) + (kill-local-variable 'company-tng--overlay)) + (advice-remove 'company-select-next 'company-tng--allow-unselected) + (advice-remove 'company-fill-propertize 'company-tng--adjust-tooltip-highlight)) + (pre-command + (when (and company-selection-changed + (not (company--company-command-p (this-command-keys)))) + (company--unread-this-command-keys) + (setq this-command 'company-complete-selection))))) + +;;;###autoload +(defun company-tng-configure-default () + "Applies the default configuration to enable company-tng." + (add-to-list 'company-frontends 'company-tng-frontend) + (let ((keymap company-active-map)) + (define-key keymap [return] nil) + (define-key keymap (kbd "RET") nil) + (define-key keymap [tab] 'company-select-next) + (define-key keymap (kbd "TAB") 'company-select-next) + (define-key keymap [backtab] 'company-select-previous) + (define-key keymap (kbd "S-TAB") 'company-select-previous))) + +(defun company-tng--allow-unselected (&optional arg) + "Advice `company-select-next' to allow for an 'unselected' +state. Unselected means that no user interaction took place on the +completion candidates and it's marked by setting +`company-selection-changed' to nil. This advice will call the underlying +`company-select-next' unless we need to transition to or from an unselected +state. + +Possible state transitions: +- (arg > 0) unselected -> first candidate selected +- (arg < 0) first candidate selected -> unselected +- (arg < 0 wrap-round) unselected -> last candidate selected +- (arg < 0 no wrap-round) unselected -> unselected + +There is no need to advice `company-select-previous' because it calls +`company-select-next' internally." + (cond + ;; Selecting next + ((or (not arg) (> arg 0)) + (unless company-selection-changed + (company-set-selection (1- (or arg 1)) 'force-update) + t)) + ;; Selecting previous + ((< arg 0) + (when (and company-selection-changed + (< (+ company-selection arg) 0)) + (company-set-selection 0) + (setq company-selection-changed nil) + (company-call-frontends 'update) + t) + ))) + +(defun company-tng--adjust-tooltip-highlight (args) + "Prevent the tooltip from highlighting the current selection if it wasn't +made explicitly (i.e. `company-selection-changed' is true)" + (unless company-selection-changed + ;; The 4th arg of `company-fill-propertize' is selected + (setf (nth 3 args) nil)) + args) + +(provide 'company-tng) +;;; company-tng.el ends here diff --git a/company.el b/company.el index 1cb02ab..ad35411 100644 --- a/company.el +++ b/company.el @@ -815,6 +815,11 @@ means that `company-mode' is always turned on except in `message-mode' buffers." (defun company-uninstall-map () (setf (cdar company-emulation-alist) nil)) +(defun company--company-command-p (keys) + "Checks if the keys are part of company's overriding keymap" + (or (equal [company-dummy-event] keys) + (lookup-key company-my-keymap keys))) + ;; Hack: ;; Emacs calculates the active keymaps before reading the event. That means we ;; cannot change the keymap from a timer. So we send a bogus command. @@ -1840,7 +1845,7 @@ each one wraps a part of the input string." (interactive) (company--search-assert-enabled) (company-search-mode 0) - (company--unread-last-input)) + (company--unread-this-command-keys)) (defun company-search-delete-char () (interactive) @@ -1984,7 +1989,7 @@ With ARG, move by that many elements." (if (> company-candidates-length 1) (company-select-next arg) (company-abort) - (company--unread-last-input))) + (company--unread-this-command-keys))) (defun company-select-previous-or-abort (&optional arg) "Select the previous candidate if more than one, else abort @@ -1995,7 +2000,7 @@ With ARG, move by that many elements." (if (> company-candidates-length 1) (company-select-previous arg) (company-abort) - (company--unread-last-input))) + (company--unread-this-command-keys))) (defun company-next-page () "Select the candidate one page further." @@ -2063,7 +2068,7 @@ With ARG, move by that many elements." 0))) t) (company-abort) - (company--unread-last-input) + (company--unread-this-command-keys) nil))) (defun company-complete-mouse (event) @@ -2240,10 +2245,12 @@ character, stripping the modifiers. That character must be a digit." (< (- (window-height) row 2) company-tooltip-limit) (recenter (- (window-height) row 2)))))) -(defun company--unread-last-input () - (when last-input-event - (clear-this-command-keys t) - (setq unread-command-events (list last-input-event)))) +(defun company--unread-this-command-keys () + (when (> (length (this-command-keys)) 0) + (setq unread-command-events (nconc + (listify-key-sequence (this-command-keys)) + unread-command-events)) + (clear-this-command-keys t))) (defun company-show-doc-buffer () "Temporarily show the documentation buffer for the selection."