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