]>
Commit | Line | Data |
---|---|---|
53e6db90 DC |
1 | ;;; company-elisp.el --- company-mode completion backend for Emacs Lisp -*- lexical-binding: t -*- |
2 | ||
3 | ;; Copyright (C) 2009-2015, 2017, 2020 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 | ;; In newer versions of Emacs, company-capf is used instead. | |
26 | ||
27 | ;;; Code: | |
28 | ||
29 | (require 'company) | |
30 | (require 'cl-lib) | |
31 | (require 'help-mode) | |
32 | (require 'find-func) | |
33 | ||
34 | (defgroup company-elisp nil | |
35 | "Completion backend for Emacs Lisp." | |
36 | :group 'company) | |
37 | ||
38 | (defcustom company-elisp-detect-function-context t | |
39 | "If enabled, offer Lisp functions only in appropriate contexts. | |
40 | Functions are offered for completion only after \\=' and \(." | |
41 | :type '(choice (const :tag "Off" nil) | |
42 | (const :tag "On" t))) | |
43 | ||
44 | (defcustom company-elisp-show-locals-first t | |
45 | "If enabled, locally bound variables and functions are displayed | |
46 | first in the candidates list." | |
47 | :type '(choice (const :tag "Off" nil) | |
48 | (const :tag "On" t))) | |
49 | ||
50 | (defun company-elisp--prefix () | |
51 | (let ((prefix (company-grab-symbol))) | |
52 | (if prefix | |
53 | (when (if (company-in-string-or-comment) | |
54 | (= (char-before (- (point) (length prefix))) ?`) | |
55 | (company-elisp--should-complete)) | |
56 | prefix) | |
57 | 'stop))) | |
58 | ||
59 | (defun company-elisp--predicate (symbol) | |
60 | (or (boundp symbol) | |
61 | (fboundp symbol) | |
62 | (facep symbol) | |
63 | (featurep symbol))) | |
64 | ||
65 | (defun company-elisp--fns-regexp (&rest names) | |
66 | (concat "\\_<\\(?:cl-\\)?" (regexp-opt names) "\\*?\\_>")) | |
67 | ||
68 | (defvar company-elisp-parse-limit 30) | |
69 | (defvar company-elisp-parse-depth 100) | |
70 | ||
71 | (defvar company-elisp-defun-names '("defun" "defmacro" "defsubst")) | |
72 | ||
73 | (defvar company-elisp-var-binding-regexp | |
74 | (apply #'company-elisp--fns-regexp "let" "lambda" "lexical-let" | |
75 | company-elisp-defun-names) | |
76 | "Regular expression matching head of a multiple variable bindings form.") | |
77 | ||
78 | (defvar company-elisp-var-binding-regexp-1 | |
79 | (company-elisp--fns-regexp "dolist" "dotimes") | |
80 | "Regular expression matching head of a form with one variable binding.") | |
81 | ||
82 | (defvar company-elisp-fun-binding-regexp | |
83 | (company-elisp--fns-regexp "flet" "labels") | |
84 | "Regular expression matching head of a function bindings form.") | |
85 | ||
86 | (defvar company-elisp-defuns-regexp | |
87 | (concat "([ \t\n]*" | |
88 | (apply #'company-elisp--fns-regexp company-elisp-defun-names))) | |
89 | ||
90 | (defun company-elisp--should-complete () | |
91 | (let ((start (point)) | |
92 | (depth (car (syntax-ppss)))) | |
93 | (not | |
94 | (when (> depth 0) | |
95 | (save-excursion | |
96 | (up-list (- depth)) | |
97 | (when (looking-at company-elisp-defuns-regexp) | |
98 | (forward-char) | |
99 | (forward-sexp 1) | |
100 | (unless (= (point) start) | |
101 | (condition-case nil | |
102 | (let ((args-end (scan-sexps (point) 2))) | |
103 | (or (null args-end) | |
104 | (> args-end start))) | |
105 | (scan-error | |
106 | t))))))))) | |
107 | ||
108 | (defun company-elisp--locals (prefix functions-p) | |
109 | (let ((regexp (concat "[ \t\n]*\\(\\_<" (regexp-quote prefix) | |
110 | "\\(?:\\sw\\|\\s_\\)*\\_>\\)")) | |
111 | (pos (point)) | |
112 | res) | |
113 | (condition-case nil | |
114 | (save-excursion | |
115 | (dotimes (_ company-elisp-parse-depth) | |
116 | (up-list -1) | |
117 | (save-excursion | |
118 | (when (eq (char-after) ?\() | |
119 | (forward-char 1) | |
120 | (when (ignore-errors | |
121 | (save-excursion (forward-list) | |
122 | (<= (point) pos))) | |
123 | (skip-chars-forward " \t\n") | |
124 | (cond | |
125 | ((looking-at (if functions-p | |
126 | company-elisp-fun-binding-regexp | |
127 | company-elisp-var-binding-regexp)) | |
128 | (down-list 1) | |
129 | (condition-case nil | |
130 | (dotimes (_ company-elisp-parse-limit) | |
131 | (save-excursion | |
132 | (when (looking-at "[ \t\n]*(") | |
133 | (down-list 1)) | |
134 | (when (looking-at regexp) | |
135 | (cl-pushnew (match-string-no-properties 1) res))) | |
136 | (forward-sexp)) | |
137 | (scan-error nil))) | |
138 | ((unless functions-p | |
139 | (looking-at company-elisp-var-binding-regexp-1)) | |
140 | (down-list 1) | |
141 | (when (looking-at regexp) | |
142 | (cl-pushnew (match-string-no-properties 1) res))))))))) | |
143 | (scan-error nil)) | |
144 | res)) | |
145 | ||
146 | (defun company-elisp-candidates (prefix) | |
147 | (let* ((predicate (company-elisp--candidates-predicate prefix)) | |
148 | (locals (company-elisp--locals prefix (eq predicate 'fboundp))) | |
149 | (globals (company-elisp--globals prefix predicate)) | |
150 | (locals (cl-loop for local in locals | |
151 | when (not (member local globals)) | |
152 | collect local))) | |
153 | (if company-elisp-show-locals-first | |
154 | (append (sort locals 'string<) | |
155 | (sort globals 'string<)) | |
156 | (append locals globals)))) | |
157 | ||
158 | (defun company-elisp--globals (prefix predicate) | |
159 | (all-completions prefix obarray predicate)) | |
160 | ||
161 | (defun company-elisp--candidates-predicate (prefix) | |
162 | (let* ((completion-ignore-case nil) | |
163 | (beg (- (point) (length prefix))) | |
164 | (before (char-before beg))) | |
165 | (if (and company-elisp-detect-function-context | |
166 | (not (memq before '(?' ?`)))) | |
167 | (if (and (eq before ?\() | |
168 | (not | |
169 | (save-excursion | |
170 | (ignore-errors | |
171 | (goto-char (1- beg)) | |
172 | (or (company-elisp--before-binding-varlist-p) | |
173 | (progn | |
174 | (up-list -1) | |
175 | (company-elisp--before-binding-varlist-p))))))) | |
176 | 'fboundp | |
177 | 'boundp) | |
178 | 'company-elisp--predicate))) | |
179 | ||
180 | (defun company-elisp--before-binding-varlist-p () | |
181 | (save-excursion | |
182 | (and (prog1 (search-backward "(") | |
183 | (forward-char 1)) | |
184 | (looking-at company-elisp-var-binding-regexp)))) | |
185 | ||
186 | (defun company-elisp--doc (symbol) | |
187 | (let* ((symbol (intern symbol)) | |
188 | (doc (if (fboundp symbol) | |
189 | (documentation symbol t) | |
190 | (documentation-property symbol 'variable-documentation t)))) | |
191 | (and (stringp doc) | |
192 | (string-match ".*$" doc) | |
193 | (match-string 0 doc)))) | |
194 | ||
195 | ;;;###autoload | |
196 | (defun company-elisp (command &optional arg &rest _ignored) | |
197 | "`company-mode' completion backend for Emacs Lisp." | |
198 | (interactive (list 'interactive)) | |
199 | (cl-case command | |
200 | (interactive (company-begin-backend 'company-elisp)) | |
201 | (prefix (and (derived-mode-p 'emacs-lisp-mode 'inferior-emacs-lisp-mode) | |
202 | (company-elisp--prefix))) | |
203 | (candidates (company-elisp-candidates arg)) | |
204 | (sorted company-elisp-show-locals-first) | |
205 | (meta (company-elisp--doc arg)) | |
206 | (doc-buffer (let ((symbol (intern arg))) | |
207 | (save-window-excursion | |
208 | (ignore-errors | |
209 | (cond | |
210 | ((fboundp symbol) (describe-function symbol)) | |
211 | ((boundp symbol) (describe-variable symbol)) | |
212 | ((featurep symbol) (describe-package symbol)) | |
213 | ((facep symbol) (describe-face symbol)) | |
214 | (t (signal 'user-error nil))) | |
215 | (help-buffer))))) | |
216 | (location (let ((sym (intern arg))) | |
217 | (cond | |
218 | ((fboundp sym) (find-definition-noselect sym nil)) | |
219 | ((boundp sym) (find-definition-noselect sym 'defvar)) | |
220 | ((featurep sym) (cons (find-file-noselect (find-library-name | |
221 | (symbol-name sym))) | |
222 | 0)) | |
223 | ((facep sym) (find-definition-noselect sym 'defface))))))) | |
224 | ||
225 | (provide 'company-elisp) | |
226 | ;;; company-elisp.el ends here |