]> rtime.felk.cvut.cz Git - sojka/company-mode.git/blob - company.el
Added configurable front-end mechanism.
[sojka/company-mode.git] / company.el
1 (eval-when-compile (require 'cl))
2
3 (defgroup company nil
4   ""
5   :group 'abbrev
6   :group 'convenience
7   :group 'maching)
8
9 (defface company-tooltip
10   '((t :background "yellow"
11        :foreground "black"))
12   "*"
13   :group 'company)
14
15 (defface company-tooltip-selection
16   '((t :background "orange1"
17        :foreground "black"))
18   "*"
19   :group 'company)
20
21 (defface company-tooltip-common
22   '((t :inherit company-tooltip
23        :foreground "red"))
24   "*"
25   :group 'company)
26
27 (defface company-tooltip-common-selection
28   '((t :inherit company-tooltip-selection
29        :foreground "red"))
30   "*"
31   :group 'company)
32
33 (defcustom company-tooltip-limit 10
34   "*"
35   :group 'company
36   :type 'integer)
37
38 (defface company-preview
39   '((t :background "blue4"
40        :foreground "wheat"))
41   "*"
42   :group 'company)
43
44 (defface company-preview-common
45   '((t :inherit company-preview
46        :foreground "red"))
47   "*"
48   :group 'company)
49
50 (defface company-echo nil
51   "*"
52   :group 'company)
53
54 (defface company-echo-common
55   '((((background dark)) (:foreground "firebrick1"))
56     (((background light)) (:background "firebrick4")))
57   "*"
58   :group 'company)
59
60 (defcustom company-frontends '(company-echo-frontend
61                                company-pseudo-tooltip-frontend
62                                company-completion-frontend)
63   "*"
64   :group 'company
65   :type '(repeat (function :tag "function" nil)))
66
67 (defcustom company-backends '(company-elisp-completion)
68   "*"
69   :group 'company
70   :type '(repeat (function :tag "function" nil)))
71
72 (defcustom company-minimum-prefix-length 3
73   "*"
74   :group 'company
75   :type '(integer :tag "prefix length"))
76
77 (defvar company-timer nil)
78
79 (defun company-timer-set (variable value)
80   (set variable value)
81   (when company-timer (cancel-timer company-timer))
82   (when (numberp value)
83     (setq company-timer (run-with-idle-timer value t 'company-idle-begin))))
84
85 (defcustom company-idle-delay .7
86   "*"
87   :set 'company-timer-set
88   :group 'company
89   :type '(choice (const :tag "never (nil)" nil)
90                  (const :tag "immediate (t)" t)
91                  (number :tag "seconds")))
92
93 ;;; mode ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
94
95 (defvar company-mode-map
96   (let ((keymap (make-sparse-keymap)))
97     (define-key keymap (kbd "M-n") 'company-select-next)
98     (define-key keymap (kbd "M-p") 'company-select-previous)
99     (define-key keymap (kbd "M-<return>") 'company-complete-selection)
100     (define-key keymap "\t" 'company-complete-common)
101     keymap))
102
103 ;;;###autoload
104 (define-minor-mode company-mode
105   ""
106   nil " comp" company-mode-map
107   (if company-mode
108       (progn
109         (add-hook 'pre-command-hook 'company-pre-command nil t)
110         (add-hook 'post-command-hook 'company-post-command nil t)
111         (company-timer-set 'company-idle-delay
112                            company-idle-delay))
113     (remove-hook 'pre-command-hook 'company-pre-command t)
114     (remove-hook 'post-command-hook 'company-post-command t)
115     (company-cancel)
116     (kill-local-variable 'company-point)))
117
118 ;;; backends ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
119
120 (defun company-grab (regexp &optional expression)
121   (when (looking-back regexp)
122     (or (match-string-no-properties (or expression 0)) "")))
123
124 (defun company-in-string-or-comment (&optional point)
125   (let ((pos (syntax-ppss)))
126     (or (nth 3 pos) (nth 4 pos) (nth 7 pos))))
127
128 ;;; elisp
129
130 (defvar company-lisp-symbol-regexp
131   "\\_<\\(\\sw\\|\\s_\\)+\\_>\\=")
132
133 (defun company-grab-lisp-symbol ()
134   (let ((prefix (or (company-grab company-lisp-symbol-regexp) "")))
135     (unless (and (company-in-string-or-comment (- (point) (length prefix)))
136                  (/= (char-before (- (point) (length prefix))) ?`))
137       prefix)))
138
139 (defun company-elisp-completion (command &optional arg &rest ignored)
140   (case command
141     ('prefix (and (eq major-mode 'emacs-lisp-mode)
142                   (company-grab-lisp-symbol)))
143     ('candidates (let ((completion-ignore-case nil))
144                    (all-completions arg obarray
145                                     (lambda (symbol) (or (boundp symbol)
146                                                          (fboundp symbol))))))))
147
148 ;;; completion mechanism ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
149
150 (defvar company-backend nil)
151 (make-variable-buffer-local 'company-backend)
152
153 (defvar company-prefix nil)
154 (make-variable-buffer-local 'company-prefix)
155
156 (defvar company-candidates nil)
157 (make-variable-buffer-local 'company-candidates)
158
159 (defvar company-common nil)
160 (make-variable-buffer-local 'company-common)
161
162 (defvar company-selection 0)
163 (make-variable-buffer-local 'company-selection)
164
165 (defvar company-selection-changed nil)
166 (make-variable-buffer-local 'company-selection-changed)
167
168 (defvar company-point nil)
169 (make-variable-buffer-local 'company-point)
170
171 (defsubst company-strip-prefix (str)
172   (substring str (length company-prefix)))
173
174 (defsubst company-offset (display-limit)
175   (let ((offset (- company-selection display-limit -1)))
176     (max offset 0)))
177
178 (defsubst company-should-complete (prefix)
179   (and (eq company-idle-delay t)
180        (>= (length prefix) company-minimum-prefix-length)))
181
182 (defsubst company-call-frontends (command)
183   (dolist (frontend company-frontends)
184     (funcall frontend command)))
185
186 (defun company-idle-begin ()
187   (and company-mode
188        (not company-candidates)
189        (not (equal (point) company-point))
190        (let ((company-idle-delay t))
191          (company-begin)
192          (company-post-command))))
193
194 (defun company-manual-begin ()
195   (and company-mode
196        (not company-candidates)
197        (let ((company-idle-delay t)
198              (company-minimum-prefix-length 0))
199          (company-begin)))
200   ;; Return non-nil if active.
201   company-candidates)
202
203 (defun company-continue-or-cancel ()
204   (when company-candidates
205     (let ((old-point (- company-point (length company-prefix)))
206           (company-idle-delay t)
207           (company-minimum-prefix-length 0))
208       ;; TODO: Make more efficient.
209       (setq company-candidates nil)
210       (company-begin)
211       (unless (and company-candidates
212                    (equal old-point (- company-point (length company-prefix))))
213         (company-cancel))
214       company-candidates)))
215
216 (defun company-begin ()
217   (or (company-continue-or-cancel)
218       (let ((completion-ignore-case nil) ;; TODO: make this optional
219             prefix)
220         (dolist (backend company-backends)
221           (when (setq prefix (funcall backend 'prefix))
222             (when (company-should-complete prefix)
223               (setq company-backend backend
224                     company-prefix prefix
225                     company-candidates
226                     (funcall company-backend 'candidates prefix)
227                     company-common (try-completion prefix company-candidates)
228                     company-selection 0
229                     company-point (point))
230               (company-call-frontends 'update))
231             (return prefix)))
232         (unless (and company-candidates
233                      (not (eq t company-common)))
234           (company-cancel)))))
235
236 (defun company-cancel ()
237   (setq company-backend nil
238         company-prefix nil
239         company-candidates nil
240         company-common nil
241         company-selection 0
242         company-selection-changed nil
243         company-point nil)
244   (company-call-frontends 'hide))
245
246 (defun company-abort ()
247   (company-cancel)
248   ;; Don't start again, unless started manually.
249   (setq company-point (point)))
250
251 (defun company-pre-command ()
252   (when company-candidates
253     (company-call-frontends 'pre-command)))
254
255 (defun company-post-command ()
256   (unless (equal (point) company-point)
257     (company-begin))
258   (when company-candidates
259     (company-call-frontends 'post-command)))
260
261 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
262
263 (defun company-select-next ()
264   (interactive)
265   (when (company-manual-begin)
266     (setq company-selection (min (1- (length company-candidates))
267                                  (1+ company-selection))
268           company-selection-changed t)))
269
270 (defun company-select-previous ()
271   (interactive)
272   (when (company-manual-begin)
273     (setq company-selection (max 0 (1- company-selection))
274           company-selection-changed t)))
275
276 (defun company-complete-selection ()
277   (interactive)
278   (when (company-manual-begin)
279     (insert (company-strip-prefix (nth company-selection company-candidates)))
280     (company-abort)))
281
282 (defun company-complete-common ()
283   (interactive)
284   (when (company-manual-begin)
285     (insert (company-strip-prefix company-common))))
286
287 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
288
289 (defconst company-space-strings-limit 100)
290
291 (defconst company-space-strings
292   (let (lst)
293     (dotimes (i company-space-strings-limit)
294       (push (make-string (- company-space-strings-limit 1 i) ?\  ) lst))
295     (apply 'vector lst)))
296
297 (defsubst company-space-string (len)
298   (if (< len company-space-strings-limit)
299       (aref company-space-strings len)
300     (make-string len ?\ )))
301
302 (defsubst company-safe-substring (str from &optional to)
303   (let ((len (length str)))
304     (if (> from len)
305         ""
306       (if (and to (> to len))
307           (concat (substring str from)
308                   (company-space-string (- to len)))
309         (substring str from to)))))
310
311 ;;; pseudo-tooltip ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
312
313 (defvar company-pseudo-tooltip-overlay nil)
314 (make-variable-buffer-local 'company-pseudo-tooltip-overlay)
315
316 ;;; propertize
317
318 (defun company-fill-propertize (line width selected)
319   (setq line (company-safe-substring line 0 width))
320   (add-text-properties 0 width
321                        (list 'face (if selected
322                                        'company-tooltip-selection
323                                      'company-tooltip)) line)
324   (add-text-properties 0 (length company-common)
325                        (list 'face (if selected
326                                        'company-tooltip-common-selection
327                                      'company-tooltip-common)) line)
328   line)
329
330 (defun company-fill-propertize-lines (column lines selection)
331   (let ((width 0)
332         (lines-copy lines)
333         (len (min company-tooltip-limit (length lines)))
334         new)
335     (dotimes (i len)
336       (setq width (max (length (pop lines-copy)) width)))
337     (setq width (min width (- (window-width) column)))
338     (dotimes (i len)
339       (push (company-fill-propertize (pop lines) width (equal i selection))
340             new))
341     (nreverse new)))
342
343 ;;; replace
344
345 (defun company-buffer-lines (beg end)
346   (goto-char beg)
347   (let ((row (cdr (posn-col-row (posn-at-point))))
348         lines)
349     (while (< (point) end)
350       (move-to-window-line (incf row))
351       (push (buffer-substring beg (min end (1- (point)))) lines)
352       (setq beg (point)))
353     (nreverse lines)))
354
355 (defun company-modify-line (old new offset)
356   (concat (company-safe-substring old 0 offset)
357           new
358           (company-safe-substring old (+ offset (length new)))))
359
360 (defun company-modified-substring (beg end lines column)
361   (let ((old (company-buffer-lines beg end))
362         new)
363     ;; Inject into old lines.
364     (while old
365       (push (company-modify-line (pop old) (pop lines) column) new))
366     ;; Append whole new lines.
367     (while lines
368       (push (company-modify-line "" (pop lines) column) new))
369     (concat (mapconcat 'identity (nreverse new) "\n")
370             "\n")))
371
372 ;; show
373
374 (defun company-pseudo-tooltip-show (row column lines selection)
375   (company-pseudo-tooltip-hide)
376   (unless lines (error "No text provided"))
377   (save-excursion
378
379     ;; Scroll to offset.
380     (let ((offset (company-offset company-tooltip-limit)))
381       (setq lines (nthcdr offset lines))
382       (decf selection offset))
383
384     (setq lines (company-fill-propertize-lines column lines selection))
385
386
387     (move-to-column 0)
388     (move-to-window-line row)
389     (let ((beg (point))
390           (end (save-excursion
391                  (move-to-window-line (min (window-height)
392                                            (+ row company-tooltip-limit)))
393                  (point)))
394           str)
395
396       (setq company-pseudo-tooltip-overlay (make-overlay beg end))
397
398       (overlay-put company-pseudo-tooltip-overlay 'before-string
399                    (company-modified-substring beg end lines column))
400       (overlay-put company-pseudo-tooltip-overlay 'invisible t)
401       (overlay-put company-pseudo-tooltip-overlay 'window (selected-window)))))
402
403 (defun company-pseudo-tooltip-show-at-point (pos)
404   (let ((col-row (posn-col-row (posn-at-point pos))))
405     (company-pseudo-tooltip-show (1+ (cdr col-row)) (car col-row)
406                                  company-candidates company-selection)))
407
408 (defun company-pseudo-tooltip-hide ()
409   (when company-pseudo-tooltip-overlay
410     (delete-overlay company-pseudo-tooltip-overlay)
411     (setq company-pseudo-tooltip-overlay nil)))
412
413 (defun company-pseudo-tooltip-frontend (command)
414   (case command
415     ('pre-command (company-pseudo-tooltip-hide))
416     ('post-command (company-pseudo-tooltip-show-at-point
417                     (- (point) (length company-prefix))))
418     ('hide (company-pseudo-tooltip-hide))))
419
420 ;;; overlay ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
421
422 (defvar company-preview-overlay nil)
423 (make-variable-buffer-local 'company-preview-overlay)
424
425 (defun company-preview-show-at-point (pos)
426   (company-preview-hide)
427
428   (setq company-preview-overlay (make-overlay pos pos))
429
430   (let ((completion (company-strip-prefix (nth company-selection
431                                                company-candidates))))
432     (and (equal pos (point))
433          (not (equal completion ""))
434          (add-text-properties 0 1 '(cursor t) completion))
435
436     (setq completion (propertize completion 'face 'company-preview))
437     (add-text-properties 0 (- (length company-common) (length company-prefix))
438                          '(face company-preview-common) completion)
439
440     (overlay-put company-preview-overlay 'after-string completion)
441     (overlay-put company-preview-overlay 'window (selected-window))))
442
443 (defun company-preview-hide ()
444   (when company-preview-overlay
445     (delete-overlay company-preview-overlay)
446     (setq company-preview-overlay nil)))
447
448 (defun company-preview-frontend (command)
449   (case command
450     ('pre-command (company-preview-hide))
451     ('post-command (company-preview-show-at-point (point)))
452     ('hide (company-preview-hide))))
453
454 ;;; echo ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
455
456 (defvar company-echo-last-msg nil)
457 (make-variable-buffer-local 'company-echo-last-msg)
458
459 (defun company-echo-refresh ()
460   (let ((message-log-max nil))
461     (if company-echo-last-msg
462         (message "%s" company-echo-last-msg)
463       (message ""))))
464
465 (defun company-echo-show (candidates)
466
467   ;; Roll to selection.
468   (setq candidates (nthcdr company-selection candidates))
469
470   (let ((limit (window-width (minibuffer-window)))
471         (len -1)
472         comp msg)
473     (while candidates
474       (setq comp (pop candidates)
475             len (+ len 1 (length comp)))
476       (if (>= len limit)
477           (setq candidates nil)
478         (setq comp (propertize comp 'face 'company-echo))
479         (add-text-properties 0 (length company-common)
480                              '(face company-echo-common) comp)
481         (push comp msg)))
482
483     (setq company-echo-last-msg (mapconcat 'identity (nreverse msg) " "))
484     (company-echo-refresh)))
485
486 (defun company-echo-frontend (command)
487   (case command
488     ('pre-command (company-echo-refresh))
489     ('post-command (company-echo-show company-candidates))
490     ('hide (setq company-echo-last-msg nil))))
491
492 (provide 'company)
493 ;;; company.el ends here