]>
Commit | Line | Data |
---|---|---|
1 | ;;; company-capf.el --- company-mode completion-at-point-functions backend -*- lexical-binding: t -*- | |
2 | ||
3 | ;; Copyright (C) 2013-2022 Free Software Foundation, Inc. | |
4 | ||
5 | ;; Author: Stefan Monnier <monnier@iro.umontreal.ca> | |
6 | ||
7 | ;; This file is part of GNU Emacs. | |
8 | ||
9 | ;; GNU Emacs is free software: you can redistribute it and/or modify | |
10 | ;; it under the terms of the GNU General Public License as published by | |
11 | ;; the Free Software Foundation, either version 3 of the License, or | |
12 | ;; (at your option) any later version. | |
13 | ||
14 | ;; GNU Emacs is distributed in the hope that it will be useful, | |
15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 | ;; GNU General Public License for more details. | |
18 | ||
19 | ;; You should have received a copy of the GNU General Public License | |
20 | ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. | |
21 | ||
22 | ||
23 | ;;; Commentary: | |
24 | ;; | |
25 | ;; The CAPF back-end provides a bridge to the standard | |
26 | ;; completion-at-point-functions facility, and thus can support any major mode | |
27 | ;; that defines a proper completion function, including emacs-lisp-mode, | |
28 | ;; css-mode and nxml-mode. | |
29 | ||
30 | ;;; Code: | |
31 | ||
32 | (require 'company) | |
33 | (require 'cl-lib) | |
34 | ||
35 | ;; Amortizes several calls to a c-a-p-f from the same position. | |
36 | (defvar company--capf-cache nil) | |
37 | ||
38 | ;; FIXME: Provide a way to save this info once in Company itself | |
39 | ;; (https://github.com/company-mode/company-mode/pull/845). | |
40 | (defvar-local company-capf--current-completion-data nil | |
41 | "Value last returned by `company-capf' when called with `candidates'. | |
42 | For most properties/actions, this is just what we need: the exact values | |
43 | that accompanied the completion table that's currently is use. | |
44 | ||
45 | `company-capf', however, could be called at some different positions during | |
46 | a completion session (most importantly, by `company-sort-by-occurrence'), | |
47 | so we can't just use the preceding variable instead.") | |
48 | ||
49 | (defun company--capf-data () | |
50 | (let ((cache company--capf-cache)) | |
51 | (if (and (equal (current-buffer) (car cache)) | |
52 | (equal (point) (car (setq cache (cdr cache)))) | |
53 | (equal (buffer-chars-modified-tick) (car (setq cache (cdr cache))))) | |
54 | (cadr cache) | |
55 | (let ((data (company--capf-data-real))) | |
56 | (setq company--capf-cache | |
57 | (list (current-buffer) (point) (buffer-chars-modified-tick) data)) | |
58 | data)))) | |
59 | ||
60 | (defun company--contains (elt lst) | |
61 | (when-let ((cur (car lst))) | |
62 | (cond | |
63 | ((symbolp cur) | |
64 | (or (eq elt cur) | |
65 | (company--contains elt (cdr lst)))) | |
66 | ((listp cur) | |
67 | (or (company--contains elt cur) | |
68 | (company--contains elt (cdr lst))))))) | |
69 | ||
70 | (defun company--capf-data-real () | |
71 | (cl-letf* (((default-value 'completion-at-point-functions) | |
72 | (if (company--contains 'company-etags company-backends) | |
73 | ;; Ignore tags-completion-at-point-function because it subverts | |
74 | ;; company-etags in the default value of company-backends, where | |
75 | ;; the latter comes later. | |
76 | (remove 'tags-completion-at-point-function | |
77 | (default-value 'completion-at-point-functions)) | |
78 | (default-value 'completion-at-point-functions))) | |
79 | (completion-at-point-functions (company--capf-workaround)) | |
80 | (data (run-hook-wrapped 'completion-at-point-functions | |
81 | ;; Ignore misbehaving functions. | |
82 | #'company--capf-wrapper 'optimist))) | |
83 | (when (and (consp (cdr data)) (integer-or-marker-p (nth 1 data))) data))) | |
84 | ||
85 | (defun company--capf-wrapper (fun which) | |
86 | (let ((buffer-read-only t) | |
87 | (inhibit-read-only nil) | |
88 | (completion-in-region-function | |
89 | (lambda (beg end coll pred) | |
90 | (throw 'company--illegal-completion-in-region | |
91 | (list fun beg end coll :predicate pred))))) | |
92 | (catch 'company--illegal-completion-in-region | |
93 | (condition-case nil | |
94 | (completion--capf-wrapper fun which) | |
95 | (buffer-read-only nil))))) | |
96 | ||
97 | (declare-function python-shell-get-process "python") | |
98 | ||
99 | (defun company--capf-workaround () | |
100 | ;; For http://debbugs.gnu.org/cgi/bugreport.cgi?bug=18067 | |
101 | (if (or (not (listp completion-at-point-functions)) | |
102 | (not (memq 'python-completion-complete-at-point completion-at-point-functions)) | |
103 | (python-shell-get-process)) | |
104 | completion-at-point-functions | |
105 | (remq 'python-completion-complete-at-point completion-at-point-functions))) | |
106 | ||
107 | (defun company-capf--save-current-data (data) | |
108 | (setq company-capf--current-completion-data data) | |
109 | (add-hook 'company-after-completion-hook | |
110 | #'company-capf--clear-current-data nil t)) | |
111 | ||
112 | (defun company-capf--clear-current-data (_ignored) | |
113 | (setq company-capf--current-completion-data nil)) | |
114 | ||
115 | (defvar-local company-capf--sorted nil) | |
116 | ||
117 | (defun company-capf (command &optional arg &rest _args) | |
118 | "`company-mode' backend using `completion-at-point-functions'." | |
119 | (interactive (list 'interactive)) | |
120 | (pcase command | |
121 | (`interactive (company-begin-backend 'company-capf)) | |
122 | (`prefix | |
123 | (let ((res (company--capf-data))) | |
124 | (when res | |
125 | (let ((length (plist-get (nthcdr 4 res) :company-prefix-length)) | |
126 | (prefix (buffer-substring-no-properties (nth 1 res) (point)))) | |
127 | (cond | |
128 | ((> (nth 2 res) (point)) 'stop) | |
129 | (length (cons prefix length)) | |
130 | (t prefix)))))) | |
131 | (`candidates | |
132 | (company-capf--candidates arg)) | |
133 | (`sorted | |
134 | company-capf--sorted) | |
135 | (`match | |
136 | ;; Ask the for the `:company-match' function. If that doesn't help, | |
137 | ;; fallback to sniffing for face changes to get a suitable value. | |
138 | (let ((f (plist-get (nthcdr 4 company-capf--current-completion-data) | |
139 | :company-match))) | |
140 | (if f (funcall f arg) | |
141 | (let* ((match-start nil) (pos -1) | |
142 | (prop-value nil) (faces nil) | |
143 | (has-face-p nil) chunks | |
144 | (limit (length arg))) | |
145 | (while (< pos limit) | |
146 | (setq pos | |
147 | (if (< pos 0) 0 (next-property-change pos arg limit))) | |
148 | (setq prop-value (or | |
149 | (get-text-property pos 'face arg) | |
150 | (get-text-property pos 'font-lock-face arg)) | |
151 | faces (if (listp prop-value) prop-value (list prop-value)) | |
152 | has-face-p (memq 'completions-common-part faces)) | |
153 | (cond ((and (not match-start) has-face-p) | |
154 | (setq match-start pos)) | |
155 | ((and match-start (not has-face-p)) | |
156 | (push (cons match-start pos) chunks) | |
157 | (setq match-start nil)))) | |
158 | (nreverse chunks))))) | |
159 | (`duplicates t) | |
160 | (`no-cache t) ;Not much can be done here, as long as we handle | |
161 | ;non-prefix matches. | |
162 | (`meta | |
163 | (let ((f (plist-get (nthcdr 4 company-capf--current-completion-data) | |
164 | :company-docsig))) | |
165 | (when f (funcall f arg)))) | |
166 | (`doc-buffer | |
167 | (let ((f (plist-get (nthcdr 4 company-capf--current-completion-data) | |
168 | :company-doc-buffer))) | |
169 | (when f (funcall f arg)))) | |
170 | (`location | |
171 | (let ((f (plist-get (nthcdr 4 company-capf--current-completion-data) | |
172 | :company-location))) | |
173 | (when f (funcall f arg)))) | |
174 | (`annotation | |
175 | (company-capf--annotation arg)) | |
176 | (`kind | |
177 | (let ((f (plist-get (nthcdr 4 company-capf--current-completion-data) | |
178 | :company-kind))) | |
179 | (when f (funcall f arg)))) | |
180 | (`deprecated | |
181 | (let ((f (plist-get (nthcdr 4 company-capf--current-completion-data) | |
182 | :company-deprecated))) | |
183 | (when f (funcall f arg)))) | |
184 | (`require-match | |
185 | (plist-get (nthcdr 4 (company--capf-data)) :company-require-match)) | |
186 | (`init nil) ;Don't bother: plenty of other ways to initialize the code. | |
187 | (`post-completion | |
188 | (company--capf-post-completion arg)) | |
189 | )) | |
190 | ||
191 | (defun company-capf--annotation (arg) | |
192 | (let* ((f (or (plist-get (nthcdr 4 company-capf--current-completion-data) | |
193 | :annotation-function) | |
194 | ;; FIXME: Add a test. | |
195 | (cdr (assq 'annotation-function | |
196 | (completion-metadata | |
197 | (buffer-substring (nth 1 company-capf--current-completion-data) | |
198 | (nth 2 company-capf--current-completion-data)) | |
199 | (nth 3 company-capf--current-completion-data) | |
200 | (plist-get (nthcdr 4 company-capf--current-completion-data) | |
201 | :predicate)))))) | |
202 | (annotation (when f (funcall f arg)))) | |
203 | (if (and company-format-margin-function | |
204 | (equal annotation " <f>") ; elisp-completion-at-point, pre-icons | |
205 | (plist-get (nthcdr 4 company-capf--current-completion-data) | |
206 | :company-kind)) | |
207 | nil | |
208 | annotation))) | |
209 | ||
210 | (defun company-capf--candidates (input) | |
211 | (let ((res (company--capf-data))) | |
212 | (company-capf--save-current-data res) | |
213 | (when res | |
214 | (let* ((table (nth 3 res)) | |
215 | (pred (plist-get (nthcdr 4 res) :predicate)) | |
216 | (meta (completion-metadata | |
217 | (buffer-substring (nth 1 res) (nth 2 res)) | |
218 | table pred)) | |
219 | (candidates (completion-all-completions input table pred | |
220 | (length input) | |
221 | meta)) | |
222 | (sortfun (cdr (assq 'display-sort-function meta))) | |
223 | (last (last candidates)) | |
224 | (base-size (and (numberp (cdr last)) (cdr last)))) | |
225 | (when base-size | |
226 | (setcdr last nil)) | |
227 | (setq company-capf--sorted (functionp sortfun)) | |
228 | (when sortfun | |
229 | (setq candidates (funcall sortfun candidates))) | |
230 | (if (not (zerop (or base-size 0))) | |
231 | (let ((before (substring input 0 base-size))) | |
232 | (mapcar (lambda (candidate) | |
233 | (concat before candidate)) | |
234 | candidates)) | |
235 | candidates))))) | |
236 | ||
237 | (defun company--capf-post-completion (arg) | |
238 | (let* ((res company-capf--current-completion-data) | |
239 | (exit-function (plist-get (nthcdr 4 res) :exit-function)) | |
240 | (table (nth 3 res))) | |
241 | (if exit-function | |
242 | ;; We can more or less know when the user is done with completion, | |
243 | ;; so we do something different than `completion--done'. | |
244 | (funcall exit-function arg | |
245 | ;; FIXME: Should probably use an additional heuristic: | |
246 | ;; completion-at-point doesn't know when the user picked a | |
247 | ;; particular candidate explicitly (it only checks whether | |
248 | ;; further completions exist). Whereas company user can press | |
249 | ;; RET (or use implicit completion with company-tng). | |
250 | (if (= (car (completion-boundaries arg table nil "")) | |
251 | (length arg)) | |
252 | 'sole | |
253 | 'finished))))) | |
254 | ||
255 | (provide 'company-capf) | |
256 | ||
257 | ;;; company-capf.el ends here |