changelog shortlog graph tags branches changeset file revisions annotate raw help

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

revision 629: ab02408636b7
     1.1--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2+++ b/emacs/lib/slime-company.el	Tue Aug 27 21:36:50 2024 -0400
     1.3@@ -0,0 +1,388 @@
     1.4+;;; slime-company.el --- slime completion backend for company mode -*-lexical-binding:t-*-
     1.5+;;
     1.6+;; Copyright (C) 2009-2021  Ole Arndt
     1.7+;;
     1.8+;; Author: Ole Arndt <anwyn@sugarshark.com>
     1.9+;; Keywords: convenience, lisp, abbrev
    1.10+;; Version: 1.6
    1.11+;; Package-Requires: ((emacs "24.4") (slime "2.13") (company "0.9.0"))
    1.12+;;
    1.13+;; This file is free software; you can redistribute it and/or modify
    1.14+;; it under the terms of the GNU General Public License as published by
    1.15+;; the Free Software Foundation, either version 3 of the License, or
    1.16+;; (at your option) any later version.
    1.17+;;
    1.18+;; This program is distributed in the hope that it will be useful,
    1.19+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
    1.20+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    1.21+;; GNU General Public License for more details.
    1.22+;;
    1.23+;; You should have received a copy of the GNU General Public License
    1.24+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
    1.25+;;
    1.26+;;; Commentary:
    1.27+;;
    1.28+;; This is a backend implementation for the completion package
    1.29+;; company-mode by Nikolaj Schumacher. More info about this package
    1.30+;; is available at http://company-mode.github.io/
    1.31+;;
    1.32+;; As of version 1.0 this completion backend supports the normal and
    1.33+;; the fuzzy completion modes of SLIME.
    1.34+;;
    1.35+;;; Installation:
    1.36+;;
    1.37+;;  Put this file somewhere into your load-path
    1.38+;;  (or just into slime-path/contribs) and then call
    1.39+;;
    1.40+;;   (slime-setup '(slime-company))
    1.41+;;
    1.42+;; I also have the following, IMO more convenient key bindings for
    1.43+;; company mode in my .emacs:
    1.44+;;
    1.45+;;   (define-key company-active-map (kbd "\C-n") 'company-select-next)
    1.46+;;   (define-key company-active-map (kbd "\C-p") 'company-select-previous)
    1.47+;;   (define-key company-active-map (kbd "\C-d") 'company-show-doc-buffer)
    1.48+;;   (define-key company-active-map (kbd "M-.") 'company-show-location)
    1.49+;;
    1.50+;;; Code:
    1.51+
    1.52+;; TODO 2024-08-27: don't need this package but need to translate it
    1.53+;; to a cape capf.
    1.54+(require 'slime)
    1.55+(require 'company)
    1.56+(require 'cl-lib)
    1.57+(require 'eldoc)
    1.58+(require 'subr-x)
    1.59+
    1.60+(define-slime-contrib slime-company
    1.61+  "Interaction between slime and the company completion mode."
    1.62+  (:license "GPL")
    1.63+  (:authors "Ole Arndt <anwyn@sugarshark.com>")
    1.64+  (:swank-dependencies swank-arglists)
    1.65+  (:on-load
    1.66+   (dolist (h '(slime-mode-hook slime-repl-mode-hook sldb-mode-hook))
    1.67+     (add-hook h 'slime-company-maybe-enable)))
    1.68+  (:on-unload
    1.69+   (dolist (h '(slime-mode-hook slime-repl-mode-hook sldb-mode-hook))
    1.70+     (remove-hook h 'slime-company-maybe-enable))
    1.71+   (slime-company-disable)))
    1.72+
    1.73+;;; ----------------------------------------------------------------------------
    1.74+;;; * Customization
    1.75+
    1.76+(defgroup slime-company nil
    1.77+  "Interaction between slime and the company completion mode."
    1.78+  :group 'company
    1.79+  :group 'slime)
    1.80+
    1.81+(defcustom slime-company-after-completion nil
    1.82+  "What to do after a successful completion.
    1.83+In addition to displaying the arglist slime-company will also do one of:
    1.84+
    1.85+- `nil':  nothing,
    1.86+- insert a space. Useful if space does not select the completion candidate.
    1.87+  Works best if you also call `delete-horizontal-space' before closing
    1.88+  parentheses to remove excess whitespace.
    1.89+- call an arbitrary function with the completion string as the first parameter.
    1.90+  Do not call company-complete inside this function, company doesn't like to
    1.91+  be invoked recursively.
    1.92+"
    1.93+  :group 'slime-company
    1.94+  :type '(choice
    1.95+          (const :tag "Do nothing" nil)
    1.96+          (const :tag "Insert space" slime-company-just-one-space)
    1.97+          (function :tag "Custom function" nil)))
    1.98+
    1.99+(defcustom slime-company-transform-arglist 'downcase
   1.100+  "Before echoing the arglist it is passed to this function for transformation."
   1.101+  :group 'slime-company
   1.102+  :type '(choice
   1.103+          (const :tag "Downcase" downcase)
   1.104+          (const :tag "Do nothing" identity)
   1.105+          (function :tag "Custom function" nil)))
   1.106+
   1.107+(defcustom slime-company-display-arglist nil
   1.108+  "Whether to display the arglist of a function in the company popup."
   1.109+  :group 'slime-company
   1.110+  :type '(choice
   1.111+          (const :tag "Hide arglist" nil)
   1.112+          (const :tag "Show arglist" t)))
   1.113+
   1.114+(defcustom slime-company-display-flags t
   1.115+  "Whether to display the symbol's flags in the company popup.
   1.116+Symbol flags are only returned with the `fuzzy' completion type."
   1.117+  :group 'slime-company
   1.118+  :type '(choice
   1.119+          (const :tag "Hide flags" nil)
   1.120+          (const :tag "Show flags" t)))
   1.121+
   1.122+(defcustom slime-company-completion 'simple
   1.123+  "Which Slime completion method to use: `simple' or `fuzzy'.
   1.124+
   1.125+`simple' just displays the completion candidate,
   1.126+`fuzzy' also displays the classification flags as an annotation,
   1.127+alignment of annotations via `company-tooltip-align-annotations'
   1.128+is recommended. This method also can complete package names.
   1.129+"
   1.130+  :group 'slime-company
   1.131+  :type '(choice
   1.132+          (const simple)
   1.133+          (const fuzzy)))
   1.134+
   1.135+(defcustom slime-company-complete-in-comments-and-strings nil
   1.136+  "Should slime-company also complete in comments and strings."
   1.137+  :group 'slime-company
   1.138+  :type 'boolean)
   1.139+
   1.140+(defcustom slime-company-major-modes
   1.141+  '(lisp-mode clojure-mode slime-repl-mode scheme-mode)
   1.142+  "List of major modes in which slime-company should be active.
   1.143+Slime-company actually calls `derived-mode-p' on this list, so it will
   1.144+be active in derived modes as well."
   1.145+  :group 'slime-company
   1.146+  :type '(repeat symbol))
   1.147+
   1.148+(defun slime-company-just-one-space (completion-string)
   1.149+  (unless (string-suffix-p ":" completion-string)
   1.150+    (just-one-space)))
   1.151+
   1.152+(defsubst slime-company-active-p ()
   1.153+  "Test if the slime-company backend should be active in the current buffer."
   1.154+  (apply #'derived-mode-p slime-company-major-modes))
   1.155+
   1.156+(define-derived-mode slime-company-doc-mode help-mode "Doc"
   1.157+  "Documentation mode for slime-company."
   1.158+  (setq font-lock-defaults
   1.159+        '((("^\\([^ ]\\{4,\\}\\)\\b" . (1 font-lock-function-name-face t))
   1.160+           ("^[ 	]*\\b\\([A-Z][A-Za-z0-9_ 	%\\*\\-]+:\\)\\([ 	]\\|$\\)"
   1.161+            . (1 font-lock-doc-face))
   1.162+           ("^\\([A-Z][A-Za-z ]+:\\)\\([ 	]\\|$\\)"
   1.163+            . (1 font-lock-doc-face t))
   1.164+           ("(\\(FUNCTION\\|VALUES\\|OR\\|EQL\\|LAMBDA\\)\\b"
   1.165+            . (1 font-lock-keyword-face))
   1.166+           ("[	 (]+\\(&[A-Z0-9\\-]+\\)\\b" . (1 font-lock-type-face))
   1.167+           ("[	 (]+\\(:[A-Z0-9\\-]+\\)\\b" . (1 font-lock-builtin-face))
   1.168+           ("\\b\\(T\\|t\\|NIL\\|nil\\|NULL\\|null\\)\\b" . (1 font-lock-constant-face))
   1.169+           ("\\b[+-]?[0-9/\\.]+[sdeSDE]?\\+?[0-9]*\\b" . font-lock-constant-face)
   1.170+           ("#[xX][+-]?[0-9A-F/]+\\b" . font-lock-constant-face)
   1.171+           ("#[oO][+-]?[0-7/]+\\b" . font-lock-constant-face)
   1.172+           ("#[bB][+-]?[01/]+\\b" . font-lock-constant-face)
   1.173+           ("#[0-9]+[rR][+-]?[0-9A-Z/]+\\b" . font-lock-constant-face)
   1.174+           ("\\b\\([A-Z0-9:+%<>#*\\.\\-]\\{2,\\}\\)\\b"
   1.175+            . (1 font-lock-variable-name-face))))))
   1.176+
   1.177+;;; ----------------------------------------------------------------------------
   1.178+;;; * Activation
   1.179+
   1.180+(defun slime-company-maybe-enable ()
   1.181+  (when (slime-company-active-p)
   1.182+    (company-mode 1)
   1.183+    (add-to-list 'company-backends 'company-slime)
   1.184+    (unless (slime-find-contrib 'slime-fuzzy)
   1.185+      (setq slime-company-completion 'simple))))
   1.186+
   1.187+(defun slime-company-disable ()
   1.188+  (setq company-backends (remove 'company-slime company-backends)))
   1.189+
   1.190+;;; ----------------------------------------------------------------------------
   1.191+;;; * Internals
   1.192+
   1.193+(defun slime-company--fetch-candidates-async (prefix)
   1.194+  (when (slime-connected-p)
   1.195+    (cl-ecase slime-company-completion
   1.196+      (simple (slime-company--fetch-candidates-simple prefix))
   1.197+      (fuzzy (slime-company--fetch-candidates-fuzzy prefix)))))
   1.198+
   1.199+(defun slime-company--fetch-candidates-simple (prefix)
   1.200+  (let ((slime-current-thread :repl-thread)
   1.201+        (package (slime-current-package)))
   1.202+    (cons :async
   1.203+          (lambda (callback)
   1.204+            (slime-eval-async
   1.205+                `(swank:simple-completions ,prefix ',package)
   1.206+              (lambda (result)
   1.207+                (funcall callback (car result)))
   1.208+              package)))))
   1.209+
   1.210+(defun slime-company--fetch-candidates-fuzzy (prefix)
   1.211+  (let ((slime-current-thread :repl-thread)
   1.212+        (package (slime-current-package)))
   1.213+    (cons :async
   1.214+          (lambda (callback)
   1.215+            (slime-eval-async
   1.216+                `(swank:fuzzy-completions ,prefix ',package)
   1.217+              (lambda (result)
   1.218+                (funcall callback
   1.219+                         (mapcar
   1.220+                          (lambda (completion)
   1.221+                            (cl-destructuring-bind (sym score _ flags)
   1.222+                                completion
   1.223+                              (propertize sym 'score score 'flags flags)))
   1.224+                          (car result))))
   1.225+              package)))))
   1.226+
   1.227+(defun slime-company--fontify-lisp-buffer ()
   1.228+  "Return a buffer in lisp-mode usable for fontifying lisp expressions."
   1.229+  (let ((buffer-name " *slime-company-fontify*"))
   1.230+    (or (get-buffer buffer-name)
   1.231+        (with-current-buffer (get-buffer-create buffer-name)
   1.232+          (unless (derived-mode-p 'lisp-mode)
   1.233+            ;; Advice from slime: Just calling (lisp-mode) will turn slime-mode
   1.234+            ;; on in that buffer, which may interfere with the calling function
   1.235+            (setq major-mode 'lisp-mode)
   1.236+            (lisp-mode-variables t))
   1.237+          (current-buffer)))))
   1.238+
   1.239+(defun slime-company--fontify-lisp (string)
   1.240+  "Fontify STRING as `font-lock-mode' does in Lisp mode."
   1.241+  ;; copied functionality from slime, trimmed somewhat
   1.242+  (with-current-buffer (slime-company--fontify-lisp-buffer)
   1.243+    (erase-buffer)
   1.244+    (insert (funcall slime-company-transform-arglist string))
   1.245+    (let ((font-lock-verbose nil))
   1.246+      (font-lock-fontify-region (point-min) (point-max)))
   1.247+    (goto-char (point-min))
   1.248+    (buffer-substring (point-min) (point-max))))
   1.249+
   1.250+(defun slime-company--format (doc)
   1.251+  (let ((doc (slime-company--fontify-lisp doc)))
   1.252+    (cond ((eq eldoc-echo-area-use-multiline-p t) doc)
   1.253+	  (t (slime-oneliner (replace-regexp-in-string "[ \n\t]+" " " doc))))))
   1.254+
   1.255+(defun slime-company--arglist (arg)
   1.256+  (let ((arglist (slime-eval
   1.257+                  `(swank:operator-arglist ,arg ,(slime-current-package)))))
   1.258+    (when arglist
   1.259+      (slime-company--format arglist))))
   1.260+
   1.261+(defun slime-company--arglist-only (arg)
   1.262+  (let ((arglist (slime-eval
   1.263+                  `(swank:operator-arglist ,arg ,(slime-current-package)))))
   1.264+    (when arglist
   1.265+      (replace-regexp-in-string
   1.266+       (concat "(" (funcall slime-company-transform-arglist arg) " ")
   1.267+       " (" (funcall slime-company-transform-arglist arglist) t t))))
   1.268+
   1.269+(defun slime-company--echo-arglist (arg)
   1.270+  (slime-eval-async `(swank:operator-arglist ,arg ,(slime-current-package))
   1.271+    (lambda (arglist)
   1.272+      (when arglist
   1.273+        (slime-message "%s" (slime-company--format arglist))))))
   1.274+
   1.275+(defun slime-company--package-name (pkg)
   1.276+  "Convert a string into into a uninterned symbol name, if it looks
   1.277+like a package name, i.e. if it has a trailing colon.
   1.278+Returns NIL if the string does not look like a package name."
   1.279+  (when (string-suffix-p ":" pkg)
   1.280+    (format "#:%s" (string-remove-suffix ":" (string-remove-suffix ":" pkg)))))
   1.281+
   1.282+(defun slime-company--build-describe-request (candidate &optional verbose)
   1.283+  (let ((pkg-name (slime-company--package-name candidate)))
   1.284+    (cond (pkg-name
   1.285+           `(swank::describe-to-string
   1.286+             (cl:find-package
   1.287+              (cl:symbol-name (cl:read-from-string ,pkg-name)))))
   1.288+          (verbose
   1.289+           `(swank:describe-symbol ,candidate))
   1.290+          (t
   1.291+           `(swank:documentation-symbol ,candidate)))))
   1.292+
   1.293+(defun slime-company--fontify-doc-buffer (&optional doc)
   1.294+  "Return a buffer in `slime-compary-doc-mode' usable for fontifying documentation."
   1.295+  (with-current-buffer (company-doc-buffer)
   1.296+      (slime-company-doc-mode)
   1.297+      (setq buffer-read-only nil)
   1.298+      (when doc
   1.299+        (insert doc))
   1.300+      (goto-char (point-min))
   1.301+      (current-buffer)))
   1.302+
   1.303+(defun slime-company--doc-buffer (candidate)
   1.304+  "Show the Lisp symbol documentation for CANDIDATE in a buffer.
   1.305+Shows more type info than `slime-company--quickhelp-string'."
   1.306+  (let* ((slime-current-thread :repl-thread))
   1.307+    (slime-company--fontify-doc-buffer
   1.308+     (slime-eval (slime-company--build-describe-request candidate t)
   1.309+                 (slime-current-package)))))
   1.310+
   1.311+(defun slime-company--quickhelp-string (candidate)
   1.312+  "Retrieve the Lisp symbol documentation for CANDIDATE.
   1.313+This function does not fontify and displays the result of SWANK's
   1.314+`documentation-symbol' function, instead of the more verbose `describe-symbol'."
   1.315+  (let ((slime-current-thread :repl-thread))
   1.316+    (slime-eval (slime-company--build-describe-request candidate)
   1.317+                (slime-current-package))))
   1.318+
   1.319+(defun slime-company--location (candidate)
   1.320+  (let ((source-buffer (current-buffer)))
   1.321+    (save-window-excursion
   1.322+      (slime-edit-definition candidate)
   1.323+      (let ((buffer (if (eq source-buffer (current-buffer))
   1.324+                        slime-xref-last-buffer
   1.325+                      (current-buffer))))
   1.326+        (when (buffer-live-p buffer)
   1.327+          (cons buffer (with-current-buffer buffer
   1.328+                         (point))))))))
   1.329+
   1.330+(defun slime-company--post-completion (candidate)
   1.331+  (slime-company--echo-arglist candidate)
   1.332+  (when (functionp slime-company-after-completion)
   1.333+    (funcall slime-company-after-completion candidate)))
   1.334+
   1.335+(defun slime-company--in-string-or-comment ()
   1.336+  "Return non-nil if point is within a string or comment.
   1.337+In the REPL we disregard anything not in the current input area."
   1.338+  (save-restriction
   1.339+    (when (derived-mode-p 'slime-repl-mode)
   1.340+      (narrow-to-region slime-repl-input-start-mark (point)))
   1.341+    (let* ((sp (syntax-ppss))
   1.342+           (beg (nth 8 sp)))
   1.343+      (when (or (eq (char-after beg) ?\")
   1.344+                (nth 4 sp))
   1.345+        beg))))
   1.346+
   1.347+;;; ----------------------------------------------------------------------------
   1.348+;;; * Company backend function
   1.349+
   1.350+(defvar *slime-company--meta-request* nil
   1.351+  "Workaround lock for company-quickhelp, which invokes 'quickhelp-string' or
   1.352+doc-buffer' while a 'meta' request is running, causing SLIME to cancel requests.")
   1.353+
   1.354+(defun company-slime (command &optional arg &rest ignored)
   1.355+  "Company mode backend for slime."
   1.356+  (let ((candidate (and arg (substring-no-properties arg))))
   1.357+    (cl-case command
   1.358+      (init
   1.359+       (slime-company-active-p))
   1.360+      (prefix
   1.361+       (when (and (slime-company-active-p)
   1.362+                  (slime-connected-p)
   1.363+                  (or slime-company-complete-in-comments-and-strings
   1.364+                      (null (slime-company--in-string-or-comment))))
   1.365+         (company-grab-symbol)))
   1.366+      (candidates
   1.367+       (slime-company--fetch-candidates-async candidate))
   1.368+      (meta
   1.369+       (let ((*slime-company--meta-request* t))
   1.370+         (slime-company--arglist candidate)))
   1.371+      (annotation
   1.372+       (concat (when slime-company-display-arglist
   1.373+                 (slime-company--arglist-only candidate))
   1.374+               (when slime-company-display-flags
   1.375+                 (concat " " (get-text-property 0 'flags arg)))))
   1.376+      (doc-buffer
   1.377+       (unless *slime-company--meta-request*
   1.378+         (slime-company--doc-buffer candidate)))
   1.379+      (quickhelp-string
   1.380+       (unless *slime-company--meta-request*
   1.381+         (slime-company--quickhelp-string candidate)))
   1.382+      (location
   1.383+       (slime-company--location candidate))
   1.384+      (post-completion
   1.385+       (slime-company--post-completion candidate))
   1.386+      (sorted
   1.387+       (eq slime-company-completion 'fuzzy)))))
   1.388+
   1.389+(provide 'slime-company)
   1.390+
   1.391+;;; slime-company.el ends here