]> crepu.dev Git - config.git/blame_incremental - djavu-asus/emacs/elpa/company-20230926.32/company-clang.el
Reorganización de directorios
[config.git] / djavu-asus / emacs / elpa / company-20230926.32 / company-clang.el
... / ...
CommitLineData
1;;; company-clang.el --- company-mode completion backend for Clang -*- lexical-binding: t -*-
2
3;; Copyright (C) 2009-2011, 2013-2023 Free Software Foundation, Inc.
4
5;; Author: Nikolaj Schumacher
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
26;;; Code:
27
28(require 'company)
29(require 'company-template)
30(require 'cl-lib)
31
32(defgroup company-clang nil
33 "Completion backend for Clang."
34 :group 'company)
35
36(defcustom company-clang-executable
37 (executable-find "clang")
38 "Location of clang executable."
39 :type 'file)
40
41(defcustom company-clang-begin-after-member-access t
42 "When non-nil, start automatic completion after member access operators.
43
44Automatic completion starts whenever the current symbol is preceded by
45\".\", \"->\" or \"::\", ignoring `company-minimum-prefix-length'.
46
47If `company-begin-commands' is a list, it should include `c-electric-lt-gt'
48and `c-electric-colon', for automatic completion right after \">\" and
49\":\"."
50 :type 'boolean)
51
52(defcustom company-clang-use-compile-flags-txt nil
53 "When non-nil, use flags from compile_flags.txt if present.
54
55The lines from that files will be appended to `company-clang-arguments'.
56
57And if such file is found, Clang is called from the directory containing
58it. That allows the flags use relative file names within the project."
59 :type 'boolean
60 :safe 'booleanp)
61
62(defcustom company-clang-arguments nil
63 "A list of additional arguments to pass to clang when completing.
64Prefix files (-include ...) can be selected with `company-clang-set-prefix'
65or automatically through a custom `company-clang-prefix-guesser'."
66 :type '(repeat (string :tag "Argument")))
67
68(defcustom company-clang-prefix-guesser 'company-clang-guess-prefix
69 "A function to determine the prefix file for the current buffer."
70 :type '(function :tag "Guesser function" nil))
71
72(defvar company-clang-modes '(c-mode c++-mode objc-mode)
73 "Major modes which clang may complete.")
74
75(defcustom company-clang-insert-arguments t
76 "When non-nil, insert function arguments as a template after completion."
77 :type 'boolean
78 :package-version '(company . "0.8.0"))
79
80;; prefix ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
81
82(defvar company-clang--prefix nil)
83
84(defsubst company-clang--guess-pch-file (file)
85 (let ((dir (directory-file-name (file-name-directory file))))
86 (when (equal (file-name-nondirectory dir) "Classes")
87 (setq dir (file-name-directory dir)))
88 (car (directory-files dir t "\\([^.]h\\|[^h]\\).pch\\'" t))))
89
90(defsubst company-clang--file-substring (file beg end)
91 (with-temp-buffer
92 (insert-file-contents-literally file nil beg end)
93 (buffer-string)))
94
95(defun company-clang-guess-prefix ()
96 "Try to guess the prefix file for the current buffer."
97 ;; Prefixes seem to be called .pch. Pre-compiled headers do, too.
98 ;; So we look at the magic number to rule them out.
99 (let* ((file (company-clang--guess-pch-file buffer-file-name))
100 (magic-number (and file (company-clang--file-substring file 0 4))))
101 (unless (member magic-number '("CPCH" "gpch"))
102 file)))
103
104(defun company-clang-set-prefix (&optional prefix)
105 "Use PREFIX as a prefix (-include ...) file for clang completion."
106 (interactive (let ((def (funcall company-clang-prefix-guesser)))
107 (unless (stringp def)
108 (setq def default-directory))
109 (list (read-file-name "Prefix file: "
110 (when def (file-name-directory def))
111 def t (when def (file-name-nondirectory def))))))
112 ;; TODO: pre-compile?
113 (setq company-clang--prefix (and (stringp prefix)
114 (file-regular-p prefix)
115 prefix)))
116
117;; Clean-up on exit.
118(add-hook 'kill-emacs-hook 'company-clang-set-prefix)
119
120;; parsing ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
121
122;; Do we ever see OVERLOAD (or OVERRIDE)?
123(defconst company-clang--completion-pattern
124 "^COMPLETION: \\_<\\(%s[a-zA-Z0-9_:]*\\|Pattern\\)\\(?:\\(?: (InBase)\\)? : \\(.*\\)$\\)?$")
125
126(defconst company-clang--error-buffer-name "*clang-error*")
127
128(defun company-clang--lang-option ()
129 (if (eq major-mode 'objc-mode)
130 (if (string= "m" (file-name-extension buffer-file-name))
131 "objective-c" "objective-c++")
132 (substring (symbol-name major-mode) 0 -5)))
133
134(defun company-clang--parse-output (prefix _objc)
135 (goto-char (point-min))
136 (let ((pattern (format company-clang--completion-pattern
137 (regexp-quote prefix)))
138 (case-fold-search nil)
139 (results (make-hash-table :test 'equal :size (/ (point-max) 100)))
140 lines)
141 (while (re-search-forward pattern nil t)
142 (let ((match (match-string-no-properties 1))
143 (meta (match-string-no-properties 2)))
144 (when (equal match "Pattern")
145 (setq match (company-clang--pattern-to-match meta)))
146 (when (string-match ":" match)
147 (setq match (substring match 0 (match-beginning 0))))
148 ;; Avoiding duplicates:
149 ;; https://github.com/company-mode/company-mode/issues/841
150 (cond
151 ;; Either meta != completion (not a macro)
152 ((not (equal match meta))
153 (puthash match meta results))
154 ;; Or it's the first time we see this completion
155 ((eq (gethash match results 'none) 'none)
156 (puthash match nil results)))))
157 (maphash
158 (lambda (match meta)
159 (when meta
160 (put-text-property 0 1 'meta (company-clang--strip-formatting meta) match))
161 (push match lines))
162 results)
163 lines))
164
165(defun company-clang--pattern-to-match (pat)
166 (let ((start 0)
167 (end nil))
168 (when (string-match "#]" pat)
169 (setq start (match-end 0)))
170 (when (string-match "[ \(]<#" pat start)
171 (setq end (match-beginning 0)))
172 (substring pat start end)))
173
174(defun company-clang--meta (candidate)
175 (get-text-property 0 'meta candidate))
176
177(defun company-clang--annotation (candidate)
178 (let ((ann (company-clang--annotation-1 candidate)))
179 (if (not (and ann (string-prefix-p "(*)" ann)))
180 ann
181 (with-temp-buffer
182 (insert ann)
183 (search-backward ")")
184 (let ((pt (1+ (point))))
185 (re-search-forward ".\\_>" nil t)
186 (delete-region pt (point)))
187 (buffer-string)))))
188
189;; TODO: Parse the original formatting here, rather than guess.
190;; Strip it every time in the `meta' handler instead.
191(defun company-clang--annotation-1 (candidate)
192 (let ((meta (company-clang--meta candidate)))
193 (cond
194 ((null meta) nil)
195 ((string-match "[^:]:[^:]" meta)
196 (substring meta (1+ (match-beginning 0))))
197 ((string-match "(anonymous)" meta) nil)
198 ((string-match "\\((.*)[ a-z]*\\'\\)" meta)
199 (let ((paren (match-beginning 1)))
200 (if (not (eq (aref meta (1- paren)) ?>))
201 (match-string 1 meta)
202 (with-temp-buffer
203 (insert meta)
204 (goto-char paren)
205 (substring meta (1- (search-backward "<"))))))))))
206
207(defun company-clang--strip-formatting (text)
208 (replace-regexp-in-string
209 "#]" " "
210 (replace-regexp-in-string "[<{[]#\\|#[>}]" "" text t)
211 t))
212
213(defun company-clang--handle-error (res args)
214 (goto-char (point-min))
215 (let* ((buf (get-buffer-create company-clang--error-buffer-name))
216 (cmd (concat company-clang-executable " " (mapconcat 'identity args " ")))
217 (pattern (format company-clang--completion-pattern ""))
218 (message-truncate-lines t)
219 (err (if (and (re-search-forward pattern nil t)
220 ;; Something in the Windows build?
221 ;; Looks like Clang doesn't always include the error text
222 ;; before completions (even if exited with error).
223 (> (match-beginning 0) (point-min)))
224 (buffer-substring-no-properties (point-min)
225 (1- (match-beginning 0)))
226 ;; Warn the user more aggressively if no match was found.
227 (message "clang failed with error %d: %s" res cmd)
228 (buffer-string))))
229
230 (with-current-buffer buf
231 (let ((inhibit-read-only t))
232 (erase-buffer)
233 (insert (current-time-string)
234 (format "\nclang failed with error %d:\n" res)
235 cmd "\n\n")
236 (insert err)
237 (setq buffer-read-only t)
238 (goto-char (point-min))))))
239
240(defun company-clang--start-process (prefix callback &rest args)
241 (let* ((objc (derived-mode-p 'objc-mode))
242 (buf (get-buffer-create "*clang-output*"))
243 ;; Looks unnecessary in Emacs 25.1 and later.
244 ;; (Inconclusive, needs more testing):
245 ;; https://github.com/company-mode/company-mode/pull/288#issuecomment-72491808
246 (process-adaptive-read-buffering nil)
247 (existing-process (get-buffer-process buf)))
248 (when existing-process
249 (kill-process existing-process))
250 (with-current-buffer buf
251 (erase-buffer)
252 (setq buffer-undo-list t))
253 (let* ((process-connection-type nil)
254 (process (apply #'start-file-process "company-clang" buf
255 company-clang-executable args)))
256 (set-process-sentinel
257 process
258 (lambda (proc status)
259 (unless (string-match-p "hangup\\|killed" status)
260 (funcall
261 callback
262 (let ((res (process-exit-status proc)))
263 (with-current-buffer buf
264 (unless (eq 0 res)
265 (company-clang--handle-error res args))
266 ;; Still try to get any useful input.
267 (company-clang--parse-output prefix objc)))))))
268 (unless (company-clang--auto-save-p)
269 (send-region process (point-min) (point-max))
270 (send-string process "\n")
271 (process-send-eof process)))))
272
273(defsubst company-clang--build-location (pos)
274 (save-excursion
275 (goto-char pos)
276 (format "%s:%d:%d"
277 (if (company-clang--auto-save-p) buffer-file-name "-")
278 (line-number-at-pos)
279 (1+ (length
280 (encode-coding-region
281 (line-beginning-position)
282 (point)
283 'utf-8
284 t))))))
285
286(defsubst company-clang--build-complete-args (pos)
287 (append '("-fsyntax-only" "-Xclang" "-code-completion-macros")
288 (unless (company-clang--auto-save-p)
289 (list "-x" (company-clang--lang-option)))
290 (company-clang--arguments)
291 (when (stringp company-clang--prefix)
292 (list "-include" (expand-file-name company-clang--prefix)))
293 (list "-Xclang" (format "-code-completion-at=%s"
294 (company-clang--build-location pos)))
295 (list (if (company-clang--auto-save-p) buffer-file-name "-"))))
296
297(defun company-clang--arguments ()
298 (let ((fname "compile_flags.txt")
299 (args company-clang-arguments)
300 current-dir-rel)
301 (when company-clang-use-compile-flags-txt
302 (let ((dir (locate-dominating-file default-directory fname)))
303 (when dir
304 (setq current-dir-rel (file-relative-name default-directory dir))
305 (setq default-directory dir)
306 (with-temp-buffer
307 (insert-file-contents fname)
308 (setq args
309 (append
310 args
311 (split-string (buffer-substring-no-properties
312 (point-min) (point-max))
313 "[\n\r]+"
314 t
315 "[ \t]+"))))
316 (unless (equal current-dir-rel "./")
317 (push (format "-I%s" current-dir-rel) args)))))
318 args))
319
320(defun company-clang--candidates (prefix callback)
321 (and (company-clang--auto-save-p)
322 (buffer-modified-p)
323 (basic-save-buffer))
324 (when (null company-clang--prefix)
325 (company-clang-set-prefix (or (funcall company-clang-prefix-guesser)
326 'none)))
327 (let ((default-directory default-directory))
328 (apply 'company-clang--start-process
329 prefix
330 callback
331 (company-clang--build-complete-args
332 (if (company-clang--check-version 4.0 9.0)
333 (point)
334 (- (point) (length prefix)))))))
335
336(defun company-clang--prefix ()
337 (if company-clang-begin-after-member-access
338 (company-grab-symbol-cons "\\.\\|->\\|::" 2)
339 (company-grab-symbol)))
340
341;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
342
343(defconst company-clang-required-version 1.1)
344
345(defvar company-clang--version nil)
346
347(defun company-clang--auto-save-p ()
348 (not
349 (company-clang--check-version 2.9 3.1)))
350
351(defun company-clang--check-version (min apple-min)
352 (pcase-exhaustive company-clang--version
353 (`(apple . ,ver) (>= ver apple-min))
354 (`(normal . ,ver) (>= ver min))))
355
356(defsubst company-clang-version ()
357 "Return the version of `company-clang-executable'."
358 (with-temp-buffer
359 (call-process company-clang-executable nil t nil "--version")
360 (goto-char (point-min))
361 (if (re-search-forward
362 "\\(clang\\|Apple LLVM\\|bcc32x\\|bcc64\\) version \\([0-9.]+\\)" nil t)
363 (cons
364 (if (equal (match-string-no-properties 1) "Apple LLVM")
365 'apple
366 'normal)
367 (string-to-number (match-string-no-properties 2)))
368 0)))
369
370(defun company-clang (command &optional arg &rest _ignored)
371 "`company-mode' completion backend for Clang.
372Clang is a parser for C and ObjC. Clang version 1.1 or newer is required.
373
374Additional command line arguments can be specified in
375`company-clang-arguments'. Prefix files (-include ...) can be selected
376with `company-clang-set-prefix' or automatically through a custom
377`company-clang-prefix-guesser'.
378
379With Clang versions before 2.9, we have to save the buffer before
380performing completion. With Clang 2.9 and later, buffer contents are
381passed via standard input."
382 (interactive (list 'interactive))
383 (cl-case command
384 (interactive (company-begin-backend 'company-clang))
385 (init (when (memq major-mode company-clang-modes)
386 (unless company-clang-executable
387 (error "Company found no clang executable"))
388 (setq company-clang--version (company-clang-version))
389 (unless (company-clang--check-version
390 company-clang-required-version
391 company-clang-required-version)
392 (error "Company requires clang version %s"
393 company-clang-required-version))))
394 (prefix (and (memq major-mode company-clang-modes)
395 buffer-file-name
396 company-clang-executable
397 (not (company-in-string-or-comment))
398 (or (company-clang--prefix) 'stop)))
399 (candidates (cons :async
400 (lambda (cb) (company-clang--candidates arg cb))))
401 (meta (company-clang--meta arg))
402 (kind (company-clang--kind arg))
403 (annotation (company-clang--annotation arg))
404 (post-completion (let ((anno (company-clang--annotation arg)))
405 (when (and company-clang-insert-arguments anno)
406 (insert anno)
407 (if (string-match "\\`:[^:]" anno)
408 (company-template-objc-templatify anno)
409 (company-template-c-like-templatify
410 (concat arg anno))))))))
411
412(defun company-clang--kind (arg)
413 ;; XXX: Not very precise.
414 ;; E.g. it will say that an arg-less ObjC method is a variable (perhaps we
415 ;; could look around for brackets, etc, if there any actual users who's
416 ;; bothered by it).
417 ;; And we can't distinguish between local vars and struct fields.
418 ;; Or between keywords and macros.
419 (let ((meta (company-clang--meta arg)))
420 (cond
421 ((null meta) 'keyword)
422 ((string-match "(" meta)
423 (if (string-match-p (format "\\`%s *\\'" (regexp-quote arg))
424 (substring meta 0 (match-beginning 0)))
425 'keyword ; Also macro, actually (no return type).
426 'function))
427 (t 'variable))))
428
429(provide 'company-clang)
430;;; company-clang.el ends here