|
;;; cider-completion.el --- Smart REPL-powered code completion -*- lexical-binding: t -*-
|
|
|
|
;; Copyright © 2013-2019 Bozhidar Batsov, Artur Malabarba and CIDER contributors
|
|
;;
|
|
;; Author: Bozhidar Batsov <bozhidar@batsov.com>
|
|
;; Artur Malabarba <bruce.connor.am@gmail.com>
|
|
|
|
;; This program is free software: you can redistribute it and/or modify
|
|
;; it under the terms of the GNU General Public License as published by
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
;; (at your option) any later version.
|
|
|
|
;; This program is distributed in the hope that it will be useful,
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
;; GNU General Public License for more details.
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
;; This file is not part of GNU Emacs.
|
|
|
|
;;; Commentary:
|
|
|
|
;; Smart REPL-powered code completion and integration with company-mode.
|
|
|
|
;;; Code:
|
|
|
|
(require 'subr-x)
|
|
(require 'thingatpt)
|
|
|
|
(require 'cider-client)
|
|
(require 'cider-common)
|
|
(require 'cider-eldoc)
|
|
(require 'nrepl-dict)
|
|
|
|
(defcustom cider-completion-use-context t
|
|
"When true, uses context at point to improve completion suggestions."
|
|
:type 'boolean
|
|
:group 'cider
|
|
:package-version '(cider . "0.7.0"))
|
|
|
|
(defcustom cider-annotate-completion-candidates t
|
|
"When true, annotate completion candidates with some extra information."
|
|
:type 'boolean
|
|
:group 'cider
|
|
:package-version '(cider . "0.8.0"))
|
|
|
|
(defcustom cider-annotate-completion-function
|
|
#'cider-default-annotate-completion-function
|
|
"Controls how the annotations for completion candidates are formatted.
|
|
Must be a function that takes two arguments: the abbreviation of the
|
|
candidate type according to `cider-completion-annotations-alist' and the
|
|
candidate's namespace."
|
|
:type 'function
|
|
:group 'cider
|
|
:package-version '(cider . "0.9.0"))
|
|
|
|
(defcustom cider-completion-annotations-alist
|
|
'(("class" "c")
|
|
("field" "fi")
|
|
("function" "f")
|
|
("import" "i")
|
|
("keyword" "k")
|
|
("local" "l")
|
|
("macro" "m")
|
|
("method" "me")
|
|
("namespace" "n")
|
|
("protocol" "p")
|
|
("protocol-function" "pf")
|
|
("record" "r")
|
|
("special-form" "s")
|
|
("static-field" "sf")
|
|
("static-method" "sm")
|
|
("type" "t")
|
|
("var" "v"))
|
|
"Controls the abbreviations used when annotating completion candidates.
|
|
|
|
Must be a list of elements with the form (TYPE . ABBREVIATION), where TYPE
|
|
is a possible value of the candidate's type returned from the completion
|
|
backend, and ABBREVIATION is a short form of that type."
|
|
:type '(alist :key-type string :value-type string)
|
|
:group 'cider
|
|
:package-version '(cider . "0.9.0"))
|
|
|
|
(defcustom cider-completion-annotations-include-ns 'unqualified
|
|
"Controls passing of namespaces to `cider-annotate-completion-function'.
|
|
|
|
When set to 'always, the candidate's namespace will always be passed if it
|
|
is available. When set to 'unqualified, the namespace will only be passed
|
|
if the candidate is not namespace-qualified."
|
|
:type '(choice (const always)
|
|
(const unqualified)
|
|
(const :tag "never" nil))
|
|
:group 'cider
|
|
:package-version '(cider . "0.9.0"))
|
|
|
|
(defvar cider-completion-last-context nil)
|
|
|
|
(defun cider-completion-symbol-start-pos ()
|
|
"Find the starting position of the symbol at point, unless inside a string."
|
|
(let ((sap (symbol-at-point)))
|
|
(when (and sap (not (nth 3 (syntax-ppss))))
|
|
(car (bounds-of-thing-at-point 'symbol)))))
|
|
|
|
(defun cider-completion-get-context-at-point ()
|
|
"Extract the context at point.
|
|
If point is not inside the list, returns nil; otherwise return \"top-level\"
|
|
form, with symbol at point replaced by __prefix__."
|
|
(when (save-excursion
|
|
(condition-case _
|
|
(progn
|
|
(up-list)
|
|
(check-parens)
|
|
t)
|
|
(scan-error nil)
|
|
(user-error nil)))
|
|
(save-excursion
|
|
(let* ((pref-end (point))
|
|
(pref-start (cider-completion-symbol-start-pos))
|
|
(context (cider-defun-at-point))
|
|
(_ (beginning-of-defun))
|
|
(expr-start (point)))
|
|
(concat (when pref-start (substring context 0 (- pref-start expr-start)))
|
|
"__prefix__"
|
|
(substring context (- pref-end expr-start)))))))
|
|
|
|
(defun cider-completion-get-context ()
|
|
"Extract context depending on `cider-completion-use-context' and major mode."
|
|
(let ((context (if (and cider-completion-use-context
|
|
;; Important because `beginning-of-defun' and
|
|
;; `ending-of-defun' work incorrectly in the REPL
|
|
;; buffer, so context extraction fails there.
|
|
(derived-mode-p 'clojure-mode))
|
|
(or (cider-completion-get-context-at-point)
|
|
"nil")
|
|
"nil")))
|
|
(if (string= cider-completion-last-context context)
|
|
":same"
|
|
(setq cider-completion-last-context context)
|
|
context)))
|
|
|
|
(defun cider-completion--parse-candidate-map (candidate-map)
|
|
"Get \"candidate\" from CANDIDATE-MAP.
|
|
Put type and ns properties on the candidate"
|
|
(let ((candidate (nrepl-dict-get candidate-map "candidate"))
|
|
(type (nrepl-dict-get candidate-map "type"))
|
|
(ns (nrepl-dict-get candidate-map "ns")))
|
|
(put-text-property 0 1 'type type candidate)
|
|
(put-text-property 0 1 'ns ns candidate)
|
|
candidate))
|
|
|
|
(defun cider-complete (str)
|
|
"Complete STR with context at point."
|
|
(let* ((context (cider-completion-get-context))
|
|
(candidates (cider-sync-request:complete str context)))
|
|
(mapcar #'cider-completion--parse-candidate-map candidates)))
|
|
|
|
(defun cider-completion--get-candidate-type (symbol)
|
|
"Get candidate type for SYMBOL."
|
|
(let ((type (get-text-property 0 'type symbol)))
|
|
(or (cadr (assoc type cider-completion-annotations-alist))
|
|
type)))
|
|
|
|
(defun cider-completion--get-candidate-ns (symbol)
|
|
"Get candidate ns for SYMBOL."
|
|
(when (or (eq 'always cider-completion-annotations-include-ns)
|
|
(and (eq 'unqualified cider-completion-annotations-include-ns)
|
|
(not (cider-namespace-qualified-p symbol))))
|
|
(get-text-property 0 'ns symbol)))
|
|
|
|
(defun cider-default-annotate-completion-function (type ns)
|
|
"Get completion function based on TYPE and NS."
|
|
(concat (when ns (format " (%s)" ns))
|
|
(when type (format " <%s>" type))))
|
|
|
|
(defun cider-annotate-symbol (symbol)
|
|
"Return a string suitable for annotating SYMBOL.
|
|
If SYMBOL has a text property `type` whose value is recognised, its
|
|
abbreviation according to `cider-completion-annotations-alist' will be
|
|
used. If `type` is present but not recognised, its value will be used
|
|
unaltered. If SYMBOL has a text property `ns`, then its value will be used
|
|
according to `cider-completion-annotations-include-ns'. The formatting is
|
|
performed by `cider-annotate-completion-function'."
|
|
(when cider-annotate-completion-candidates
|
|
(let* ((type (cider-completion--get-candidate-type symbol))
|
|
(ns (cider-completion--get-candidate-ns symbol)))
|
|
(funcall cider-annotate-completion-function type ns))))
|
|
|
|
(defun cider-complete-at-point ()
|
|
"Complete the symbol at point."
|
|
(when-let* ((bounds (bounds-of-thing-at-point 'symbol)))
|
|
(when (and (cider-connected-p)
|
|
(not (or (cider-in-string-p) (cider-in-comment-p))))
|
|
(list (car bounds) (cdr bounds)
|
|
(completion-table-dynamic #'cider-complete)
|
|
:annotation-function #'cider-annotate-symbol
|
|
:company-doc-buffer #'cider-create-doc-buffer
|
|
:company-location #'cider-company-location
|
|
:company-docsig #'cider-company-docsig))))
|
|
|
|
(defun cider-completion-flush-caches ()
|
|
"Force Compliment to refill its caches.
|
|
This command should be used if Compliment fails to pick up new classnames
|
|
and methods from dependencies that were loaded dynamically after the REPL
|
|
has started."
|
|
(interactive)
|
|
(cider-sync-request:complete-flush-caches))
|
|
|
|
(defun cider-company-location (var)
|
|
"Open VAR's definition in a buffer.
|
|
Returns the cons of the buffer itself and the location of VAR's definition
|
|
in the buffer."
|
|
(when-let* ((info (cider-var-info var))
|
|
(file (nrepl-dict-get info "file"))
|
|
(line (nrepl-dict-get info "line"))
|
|
(buffer (cider-find-file file)))
|
|
(with-current-buffer buffer
|
|
(save-excursion
|
|
(goto-char (point-min))
|
|
(forward-line (1- line))
|
|
(cons buffer (point))))))
|
|
|
|
(defun cider-company-docsig (thing)
|
|
"Return signature for THING."
|
|
(let* ((eldoc-info (cider-eldoc-info thing))
|
|
(ns (lax-plist-get eldoc-info "ns"))
|
|
(symbol (lax-plist-get eldoc-info "symbol"))
|
|
(arglists (lax-plist-get eldoc-info "arglists")))
|
|
(when eldoc-info
|
|
(format "%s: %s"
|
|
(cider-eldoc-format-thing ns symbol thing
|
|
(cider-eldoc-thing-type eldoc-info))
|
|
(cider-eldoc-format-arglist arglists 0)))))
|
|
|
|
;; Fuzzy completion for company-mode
|
|
|
|
(defun cider-company-unfiltered-candidates (string &rest _)
|
|
"Return CIDER completion candidates for STRING as is, unfiltered."
|
|
(cider-complete string))
|
|
|
|
(add-to-list 'completion-styles-alist
|
|
'(cider
|
|
cider-company-unfiltered-candidates
|
|
cider-company-unfiltered-candidates
|
|
"CIDER backend-driven completion style."))
|
|
|
|
(defun cider-company-enable-fuzzy-completion ()
|
|
"Enable backend-driven fuzzy completion in the current buffer."
|
|
(setq-local completion-styles '(cider)))
|
|
|
|
(provide 'cider-completion)
|
|
;;; cider-completion.el ends here
|