changelog shortlog graph tags branches changeset files revisions annotate raw help

Mercurial > core / emacs/lib/slime-company.el

changeset 698: 96958d3eb5b0
parent: ab02408636b7
author: Richard Westhaver <ellis@rwest.io>
date: Fri, 04 Oct 2024 22:04:59 -0400
permissions: -rw-r--r--
description: fixes
1 ;;; slime-company.el --- slime completion backend for company mode -*-lexical-binding:t-*-
2 ;;
3 ;; Copyright (C) 2009-2021 Ole Arndt
4 ;;
5 ;; Author: Ole Arndt <anwyn@sugarshark.com>
6 ;; Keywords: convenience, lisp, abbrev
7 ;; Version: 1.6
8 ;; Package-Requires: ((emacs "24.4") (slime "2.13") (company "0.9.0"))
9 ;;
10 ;; This file is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation, either version 3 of the License, or
13 ;; (at your option) any later version.
14 ;;
15 ;; This program is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;; GNU General Public License for more details.
19 ;;
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
22 ;;
23 ;;; Commentary:
24 ;;
25 ;; This is a backend implementation for the completion package
26 ;; company-mode by Nikolaj Schumacher. More info about this package
27 ;; is available at http://company-mode.github.io/
28 ;;
29 ;; As of version 1.0 this completion backend supports the normal and
30 ;; the fuzzy completion modes of SLIME.
31 ;;
32 ;;; Installation:
33 ;;
34 ;; Put this file somewhere into your load-path
35 ;; (or just into slime-path/contribs) and then call
36 ;;
37 ;; (slime-setup '(slime-company))
38 ;;
39 ;; I also have the following, IMO more convenient key bindings for
40 ;; company mode in my .emacs:
41 ;;
42 ;; (define-key company-active-map (kbd "\C-n") 'company-select-next)
43 ;; (define-key company-active-map (kbd "\C-p") 'company-select-previous)
44 ;; (define-key company-active-map (kbd "\C-d") 'company-show-doc-buffer)
45 ;; (define-key company-active-map (kbd "M-.") 'company-show-location)
46 ;;
47 ;;; Code:
48 
49 ;; TODO 2024-08-27: don't need this package but need to translate it
50 ;; to a cape capf.
51 (require 'slime)
52 (require 'company)
53 (require 'cl-lib)
54 (require 'eldoc)
55 (require 'subr-x)
56 
57 (define-slime-contrib slime-company
58  "Interaction between slime and the company completion mode."
59  (:license "GPL")
60  (:authors "Ole Arndt <anwyn@sugarshark.com>")
61  (:swank-dependencies swank-arglists)
62  (:on-load
63  (dolist (h '(slime-mode-hook slime-repl-mode-hook sldb-mode-hook))
64  (add-hook h 'slime-company-maybe-enable)))
65  (:on-unload
66  (dolist (h '(slime-mode-hook slime-repl-mode-hook sldb-mode-hook))
67  (remove-hook h 'slime-company-maybe-enable))
68  (slime-company-disable)))
69 
70 ;;; ----------------------------------------------------------------------------
71 ;;; * Customization
72 
73 (defgroup slime-company nil
74  "Interaction between slime and the company completion mode."
75  :group 'company
76  :group 'slime)
77 
78 (defcustom slime-company-after-completion nil
79  "What to do after a successful completion.
80 In addition to displaying the arglist slime-company will also do one of:
81 
82 - `nil': nothing,
83 - insert a space. Useful if space does not select the completion candidate.
84  Works best if you also call `delete-horizontal-space' before closing
85  parentheses to remove excess whitespace.
86 - call an arbitrary function with the completion string as the first parameter.
87  Do not call company-complete inside this function, company doesn't like to
88  be invoked recursively.
89 "
90  :group 'slime-company
91  :type '(choice
92  (const :tag "Do nothing" nil)
93  (const :tag "Insert space" slime-company-just-one-space)
94  (function :tag "Custom function" nil)))
95 
96 (defcustom slime-company-transform-arglist 'downcase
97  "Before echoing the arglist it is passed to this function for transformation."
98  :group 'slime-company
99  :type '(choice
100  (const :tag "Downcase" downcase)
101  (const :tag "Do nothing" identity)
102  (function :tag "Custom function" nil)))
103 
104 (defcustom slime-company-display-arglist nil
105  "Whether to display the arglist of a function in the company popup."
106  :group 'slime-company
107  :type '(choice
108  (const :tag "Hide arglist" nil)
109  (const :tag "Show arglist" t)))
110 
111 (defcustom slime-company-display-flags t
112  "Whether to display the symbol's flags in the company popup.
113 Symbol flags are only returned with the `fuzzy' completion type."
114  :group 'slime-company
115  :type '(choice
116  (const :tag "Hide flags" nil)
117  (const :tag "Show flags" t)))
118 
119 (defcustom slime-company-completion 'simple
120  "Which Slime completion method to use: `simple' or `fuzzy'.
121 
122 `simple' just displays the completion candidate,
123 `fuzzy' also displays the classification flags as an annotation,
124 alignment of annotations via `company-tooltip-align-annotations'
125 is recommended. This method also can complete package names.
126 "
127  :group 'slime-company
128  :type '(choice
129  (const simple)
130  (const fuzzy)))
131 
132 (defcustom slime-company-complete-in-comments-and-strings nil
133  "Should slime-company also complete in comments and strings."
134  :group 'slime-company
135  :type 'boolean)
136 
137 (defcustom slime-company-major-modes
138  '(lisp-mode clojure-mode slime-repl-mode scheme-mode)
139  "List of major modes in which slime-company should be active.
140 Slime-company actually calls `derived-mode-p' on this list, so it will
141 be active in derived modes as well."
142  :group 'slime-company
143  :type '(repeat symbol))
144 
145 (defun slime-company-just-one-space (completion-string)
146  (unless (string-suffix-p ":" completion-string)
147  (just-one-space)))
148 
149 (defsubst slime-company-active-p ()
150  "Test if the slime-company backend should be active in the current buffer."
151  (apply #'derived-mode-p slime-company-major-modes))
152 
153 (define-derived-mode slime-company-doc-mode help-mode "Doc"
154  "Documentation mode for slime-company."
155  (setq font-lock-defaults
156  '((("^\\([^ ]\\{4,\\}\\)\\b" . (1 font-lock-function-name-face t))
157  ("^[ ]*\\b\\([A-Z][A-Za-z0-9_ %\\*\\-]+:\\)\\([ ]\\|$\\)"
158  . (1 font-lock-doc-face))
159  ("^\\([A-Z][A-Za-z ]+:\\)\\([ ]\\|$\\)"
160  . (1 font-lock-doc-face t))
161  ("(\\(FUNCTION\\|VALUES\\|OR\\|EQL\\|LAMBDA\\)\\b"
162  . (1 font-lock-keyword-face))
163  ("[ (]+\\(&[A-Z0-9\\-]+\\)\\b" . (1 font-lock-type-face))
164  ("[ (]+\\(:[A-Z0-9\\-]+\\)\\b" . (1 font-lock-builtin-face))
165  ("\\b\\(T\\|t\\|NIL\\|nil\\|NULL\\|null\\)\\b" . (1 font-lock-constant-face))
166  ("\\b[+-]?[0-9/\\.]+[sdeSDE]?\\+?[0-9]*\\b" . font-lock-constant-face)
167  ("#[xX][+-]?[0-9A-F/]+\\b" . font-lock-constant-face)
168  ("#[oO][+-]?[0-7/]+\\b" . font-lock-constant-face)
169  ("#[bB][+-]?[01/]+\\b" . font-lock-constant-face)
170  ("#[0-9]+[rR][+-]?[0-9A-Z/]+\\b" . font-lock-constant-face)
171  ("\\b\\([A-Z0-9:+%<>#*\\.\\-]\\{2,\\}\\)\\b"
172  . (1 font-lock-variable-name-face))))))
173 
174 ;;; ----------------------------------------------------------------------------
175 ;;; * Activation
176 
177 (defun slime-company-maybe-enable ()
178  (when (slime-company-active-p)
179  (company-mode 1)
180  (add-to-list 'company-backends 'company-slime)
181  (unless (slime-find-contrib 'slime-fuzzy)
182  (setq slime-company-completion 'simple))))
183 
184 (defun slime-company-disable ()
185  (setq company-backends (remove 'company-slime company-backends)))
186 
187 ;;; ----------------------------------------------------------------------------
188 ;;; * Internals
189 
190 (defun slime-company--fetch-candidates-async (prefix)
191  (when (slime-connected-p)
192  (cl-ecase slime-company-completion
193  (simple (slime-company--fetch-candidates-simple prefix))
194  (fuzzy (slime-company--fetch-candidates-fuzzy prefix)))))
195 
196 (defun slime-company--fetch-candidates-simple (prefix)
197  (let ((slime-current-thread :repl-thread)
198  (package (slime-current-package)))
199  (cons :async
200  (lambda (callback)
201  (slime-eval-async
202  `(swank:simple-completions ,prefix ',package)
203  (lambda (result)
204  (funcall callback (car result)))
205  package)))))
206 
207 (defun slime-company--fetch-candidates-fuzzy (prefix)
208  (let ((slime-current-thread :repl-thread)
209  (package (slime-current-package)))
210  (cons :async
211  (lambda (callback)
212  (slime-eval-async
213  `(swank:fuzzy-completions ,prefix ',package)
214  (lambda (result)
215  (funcall callback
216  (mapcar
217  (lambda (completion)
218  (cl-destructuring-bind (sym score _ flags)
219  completion
220  (propertize sym 'score score 'flags flags)))
221  (car result))))
222  package)))))
223 
224 (defun slime-company--fontify-lisp-buffer ()
225  "Return a buffer in lisp-mode usable for fontifying lisp expressions."
226  (let ((buffer-name " *slime-company-fontify*"))
227  (or (get-buffer buffer-name)
228  (with-current-buffer (get-buffer-create buffer-name)
229  (unless (derived-mode-p 'lisp-mode)
230  ;; Advice from slime: Just calling (lisp-mode) will turn slime-mode
231  ;; on in that buffer, which may interfere with the calling function
232  (setq major-mode 'lisp-mode)
233  (lisp-mode-variables t))
234  (current-buffer)))))
235 
236 (defun slime-company--fontify-lisp (string)
237  "Fontify STRING as `font-lock-mode' does in Lisp mode."
238  ;; copied functionality from slime, trimmed somewhat
239  (with-current-buffer (slime-company--fontify-lisp-buffer)
240  (erase-buffer)
241  (insert (funcall slime-company-transform-arglist string))
242  (let ((font-lock-verbose nil))
243  (font-lock-fontify-region (point-min) (point-max)))
244  (goto-char (point-min))
245  (buffer-substring (point-min) (point-max))))
246 
247 (defun slime-company--format (doc)
248  (let ((doc (slime-company--fontify-lisp doc)))
249  (cond ((eq eldoc-echo-area-use-multiline-p t) doc)
250  (t (slime-oneliner (replace-regexp-in-string "[ \n\t]+" " " doc))))))
251 
252 (defun slime-company--arglist (arg)
253  (let ((arglist (slime-eval
254  `(swank:operator-arglist ,arg ,(slime-current-package)))))
255  (when arglist
256  (slime-company--format arglist))))
257 
258 (defun slime-company--arglist-only (arg)
259  (let ((arglist (slime-eval
260  `(swank:operator-arglist ,arg ,(slime-current-package)))))
261  (when arglist
262  (replace-regexp-in-string
263  (concat "(" (funcall slime-company-transform-arglist arg) " ")
264  " (" (funcall slime-company-transform-arglist arglist) t t))))
265 
266 (defun slime-company--echo-arglist (arg)
267  (slime-eval-async `(swank:operator-arglist ,arg ,(slime-current-package))
268  (lambda (arglist)
269  (when arglist
270  (slime-message "%s" (slime-company--format arglist))))))
271 
272 (defun slime-company--package-name (pkg)
273  "Convert a string into into a uninterned symbol name, if it looks
274 like a package name, i.e. if it has a trailing colon.
275 Returns NIL if the string does not look like a package name."
276  (when (string-suffix-p ":" pkg)
277  (format "#:%s" (string-remove-suffix ":" (string-remove-suffix ":" pkg)))))
278 
279 (defun slime-company--build-describe-request (candidate &optional verbose)
280  (let ((pkg-name (slime-company--package-name candidate)))
281  (cond (pkg-name
282  `(swank::describe-to-string
283  (cl:find-package
284  (cl:symbol-name (cl:read-from-string ,pkg-name)))))
285  (verbose
286  `(swank:describe-symbol ,candidate))
287  (t
288  `(swank:documentation-symbol ,candidate)))))
289 
290 (defun slime-company--fontify-doc-buffer (&optional doc)
291  "Return a buffer in `slime-compary-doc-mode' usable for fontifying documentation."
292  (with-current-buffer (company-doc-buffer)
293  (slime-company-doc-mode)
294  (setq buffer-read-only nil)
295  (when doc
296  (insert doc))
297  (goto-char (point-min))
298  (current-buffer)))
299 
300 (defun slime-company--doc-buffer (candidate)
301  "Show the Lisp symbol documentation for CANDIDATE in a buffer.
302 Shows more type info than `slime-company--quickhelp-string'."
303  (let* ((slime-current-thread :repl-thread))
304  (slime-company--fontify-doc-buffer
305  (slime-eval (slime-company--build-describe-request candidate t)
306  (slime-current-package)))))
307 
308 (defun slime-company--quickhelp-string (candidate)
309  "Retrieve the Lisp symbol documentation for CANDIDATE.
310 This function does not fontify and displays the result of SWANK's
311 `documentation-symbol' function, instead of the more verbose `describe-symbol'."
312  (let ((slime-current-thread :repl-thread))
313  (slime-eval (slime-company--build-describe-request candidate)
314  (slime-current-package))))
315 
316 (defun slime-company--location (candidate)
317  (let ((source-buffer (current-buffer)))
318  (save-window-excursion
319  (slime-edit-definition candidate)
320  (let ((buffer (if (eq source-buffer (current-buffer))
321  slime-xref-last-buffer
322  (current-buffer))))
323  (when (buffer-live-p buffer)
324  (cons buffer (with-current-buffer buffer
325  (point))))))))
326 
327 (defun slime-company--post-completion (candidate)
328  (slime-company--echo-arglist candidate)
329  (when (functionp slime-company-after-completion)
330  (funcall slime-company-after-completion candidate)))
331 
332 (defun slime-company--in-string-or-comment ()
333  "Return non-nil if point is within a string or comment.
334 In the REPL we disregard anything not in the current input area."
335  (save-restriction
336  (when (derived-mode-p 'slime-repl-mode)
337  (narrow-to-region slime-repl-input-start-mark (point)))
338  (let* ((sp (syntax-ppss))
339  (beg (nth 8 sp)))
340  (when (or (eq (char-after beg) ?\")
341  (nth 4 sp))
342  beg))))
343 
344 ;;; ----------------------------------------------------------------------------
345 ;;; * Company backend function
346 
347 (defvar *slime-company--meta-request* nil
348  "Workaround lock for company-quickhelp, which invokes 'quickhelp-string' or
349 doc-buffer' while a 'meta' request is running, causing SLIME to cancel requests.")
350 
351 (defun company-slime (command &optional arg &rest ignored)
352  "Company mode backend for slime."
353  (let ((candidate (and arg (substring-no-properties arg))))
354  (cl-case command
355  (init
356  (slime-company-active-p))
357  (prefix
358  (when (and (slime-company-active-p)
359  (slime-connected-p)
360  (or slime-company-complete-in-comments-and-strings
361  (null (slime-company--in-string-or-comment))))
362  (company-grab-symbol)))
363  (candidates
364  (slime-company--fetch-candidates-async candidate))
365  (meta
366  (let ((*slime-company--meta-request* t))
367  (slime-company--arglist candidate)))
368  (annotation
369  (concat (when slime-company-display-arglist
370  (slime-company--arglist-only candidate))
371  (when slime-company-display-flags
372  (concat " " (get-text-property 0 'flags arg)))))
373  (doc-buffer
374  (unless *slime-company--meta-request*
375  (slime-company--doc-buffer candidate)))
376  (quickhelp-string
377  (unless *slime-company--meta-request*
378  (slime-company--quickhelp-string candidate)))
379  (location
380  (slime-company--location candidate))
381  (post-completion
382  (slime-company--post-completion candidate))
383  (sorted
384  (eq slime-company-completion 'fuzzy)))))
385 
386 (provide 'slime-company)
387 
388 ;;; slime-company.el ends here