about summary refs log tree commit diff
path: root/configs/shared/emacs/.emacs.d/elpa/company-20180913.805/company-tng.el
blob: a1d71737eaf2428c2aaf2caa43aff538b6a1f7cf (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
;;; company-tng.el --- company-mode configuration for single-button interaction

;; 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 perform completion using just TAB.
;; Pressing it will both select the next completion candidate in the list and
;; insert it into the buffer (or make it look like it's inserted, in fact).
;;
;; 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. You can also press S-TAB to select the
;; previous candidate, of course.
;;
;; The benefits are that you only have to use one shortcut key and there is no
;; need to confirm the entry.
;;
;; Usage:
;;
;; To apply the default configuration for company-tng call
;; `company-tng-configure-default' from your init script.
;;
;; 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).
;;
;; We recommend to disable `company-require-match' to allow free typing at any
;; point.

;;; 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))))
    (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)
       (advice-add 'company-call-backend :before-until 'company-tng--supress-post-completion)))))

;;;###autoload
(defun company-tng-configure-default ()
  "Applies the default configuration to enable company-tng."
  (setq company-require-match nil)
  (setq company-frontends '(company-tng-frontend
                            company-pseudo-tooltip-frontend
                            company-echo-metadata-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)

(defun company-tng--supress-post-completion (command &rest args)
  "Installed as a :before-until advice on `company-call-backend' and
prevents the 'post-completion command from being delivered to the backend
for the next iteration. post-completion do things like expand snippets
which are undesirable because completions are implicit in company-tng and
visible side-effects after the completion are surprising."
  (when (eq command 'post-completion)
    (advice-remove 'company-call-backend 'company-tng--supress-post-completion)
    t))

(provide 'company-tng)
;;; company-tng.el ends here