Skip to content

Commit

Permalink
Completion and documentation with leading literals
Browse files Browse the repository at this point in the history
Namespace completion works on inputs with leading literals, e.g.
"@clojure.", "#'clojure.", "'some.alias".

Documentation works on inputs with leading literals:
"#'clojure.core", "'some-alias" "#'clojure.core/map"
  • Loading branch information
eval authored and alexander-yakushev committed Jun 22, 2023
1 parent fde344d commit ba4f21b
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 13 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change log

### Unreleased

- Extend completion and getting docs for symbol-strings with leading literals.

### 0.3.15 (2023-06-22)

- Complete fully-qualified classnames by their shortname prefix anywhere in the
Expand Down
9 changes: 6 additions & 3 deletions src/compliment/sources/namespaces_and_classes.clj
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@
(comp cat (distinct))
[(for [ns-str (concat (map (comp name ns-name) (all-ns))
(map name (keys (ns-aliases ns))))
:let [[literals prefix] (utils/split-by-leading-literals prefix)]
:when (nscl-matches? prefix ns-str)]
{:candidate ns-str, :type :namespace})
{:candidate (str literals ns-str), :type :namespace})
(for [class-str (imported-classes ns)
:when (nscl-matches? prefix class-str)]
{:candidate class-str, :type :class})
Expand All @@ -94,11 +95,12 @@
;; If prefix doesn't contain a period, using fuziness produces too many
;; irrelevant candidates.
(for [{:keys [^String ns-str, ^String file]} (utils/namespaces&files-on-classpath)
:let [[literals prefix] (utils/split-by-leading-literals prefix)]
:when (and (re-find #"\.cljc?$" file)
(if has-dot
(nscl-matches? prefix ns-str)
(.startsWith ns-str prefix)))]
{:candidate ns-str, :type :namespace, :file file})
{:candidate (str literals ns-str), :type :namespace, :file file})
;; Fuzziness is too slow for all classes, so only startsWith. Also, if no
;; period in prefix, only complete root package names to maintain good
;; performance and not produce too many candidates.
Expand All @@ -115,7 +117,8 @@

(defn doc [ns-or-class-str curr-ns]
(when (nscl-symbol? ns-or-class-str)
(let [ns-or-class-sym (symbol ns-or-class-str)]
(let [strip-literals (comp second utils/split-by-leading-literals)
ns-or-class-sym (symbol (strip-literals ns-or-class-str))]
(if-let [ns (or (find-ns ns-or-class-sym)
(get (ns-aliases curr-ns) ns-or-class-sym))]
(str ns "\n" (:doc (meta ns)) "\n")
Expand Down
16 changes: 10 additions & 6 deletions src/compliment/sources/ns_mappings.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
(:require [clojure.string :as string]
[compliment.sources :refer [defsource]]
[compliment.utils :refer [fuzzy-matches? resolve-namespace
*extra-metadata*]])
*extra-metadata* split-by-leading-literals]])
(:import java.io.StringWriter))

(defn var-symbol?
Expand Down Expand Up @@ -74,7 +74,7 @@
either the scope (if prefix is scoped), `ns` arg or the namespace
extracted from context if inside `ns` declaration."
[^String prefix, ns context]
(let [[_ quoted prefix] (re-matches #"(#?')?(.+)" prefix)]
(let [[literals prefix] (split-by-leading-literals prefix)]
(when (var-symbol? prefix)
(let [[scope-name scope ^String prefix] (get-scope-and-prefix prefix ns)
ns-form-namespace (try-get-ns-from-context context)
Expand All @@ -93,25 +93,29 @@
;; Some classes don't have a package
(.getName ^Package pkg))}

(cond-> {:candidate (str quoted
(cond-> {:candidate (str literals
(if scope
(str scope-name "/" var-name)
var-name))
:type (cond (:macro var-meta) :macro
arglists :function
:else :var)
:ns (str (or (:ns var-meta) ns))}
(and arglists(:arglists *extra-metadata*))
(and arglists (:arglists *extra-metadata*))
(assoc :arglists (apply list (map pr-str arglists)))

(and doc (:doc *extra-metadata*))
(assoc :doc (generate-docstring var-meta)))))))))

(defn- resolve-var [symbol-str ns]
(let [strip-literals (comp second split-by-leading-literals)]
(ns-resolve ns (symbol (strip-literals symbol-str)))))

(defn doc
"Documentation function for this sources' completions."
[symbol-str ns]
(if (var-symbol? symbol-str)
(when-let [var (ns-resolve ns (symbol symbol-str))]
(when (var-symbol? symbol-str)
(when-let [var (resolve-var symbol-str ns)]
(when (meta var)
(generate-docstring (meta var))))))

Expand Down
14 changes: 14 additions & 0 deletions src/compliment/utils.clj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,20 @@
Note that should always have the same value, regardless of OS."
"/")

(defn split-by-leading-literals
"Meant for symbol-strings that might have leading @, #', or '.
Examples:
\"@some-atom\" => '(\"@\" \"some-atom\")
\"@#'a\" => '(\"@#'\" \"a\")
\"#'some.ns/some-var\" => '(\"#'\" \"some.ns/some-var\")
\" @wont-work\" => '(nil \" @wont-work\")
\"nothing-todo\" => '(nil \"nothing-todo\")
"
[symbol-str]
(next (re-matches #"(@{0,2}#'|'|@)?(.+)" symbol-str)))

(defn- ensure-no-leading-slash ^String [^String file]
(if (.startsWith file File/separator)
(.substring file 1) file))
Expand Down
10 changes: 9 additions & 1 deletion test/compliment/sources/t_namespaces_and_classes.clj
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
)

(fact "aliases are completed by this source too"
(strip-tags (src/candidates "#'clojure.co" (-ns) nil)) => (contains ["#'clojure.core"])

(do (require '[clojure.string :as str])
(strip-tags (src/candidates "st" (-ns) nil)))
=> (contains ["str"])
Expand Down Expand Up @@ -100,4 +102,10 @@
(fact "namespaces and classes have documentation"
(src/doc "clojure.core" (-ns)) => (checker string?)
(src/doc "java.lang.Runnable" (-ns)) => (checker string?)
(src/doc "utils" (-ns)) => (checker string?)))
;; aliases
(src/doc "utils" (-ns)) => (checker string?)
;; literals
(src/doc "'utils" (-ns)) => (checker string?)
(src/doc "#'utils" (-ns)) => (checker string?)
(src/doc "@#'clojure.core" (-ns)) => (checker string?)
(src/doc "@@#'utils" (-ns)) => (checker string?)))
19 changes: 16 additions & 3 deletions test/compliment/sources/t_ns_mappings.clj
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@
(strip-tags (src/candidates "c.str/cap" (-ns) nil)))
=> (just ["c.str/capitalize"]))

(fact "var can be prefixed by ' or #'"
(def some-atom (atom nil))

(fact "var can be prefixed by ', #' or @"
(strip-tags (src/candidates "'redu" (-ns) nil))
=> (contains ["'reduce" "'reductions" "'reduce-kv"] :gaps-ok)

Expand All @@ -101,7 +103,10 @@
=> (just ["'s/capitalize"])

(strip-tags (src/candidates "#'s/cap" (-ns) nil))
=> (just ["#'s/capitalize"]))
=> (just ["#'s/capitalize"])

(strip-tags (src/candidates "@some-a" (-ns) nil))
=> (just ["@some-atom"]))

(def foo:bar 1)
(def foo:baz 2)
Expand Down Expand Up @@ -145,4 +150,12 @@
(ctx/parse-context '(ns foo.bar
(:require [clojure.string
:refer [__prefix__]])))))
=> (just ["split" "split-lines"] :in-any-order)))
=> (just ["split" "split-lines"] :in-any-order))

(fact "ns-mappings have documentation"
(src/doc "map" (-ns)) => (checker string?)
(src/doc "clojure.core/map" (-ns)) => (checker string?)
(src/doc "#'clojure.core/map" (-ns)) => (checker string?)
(src/doc "#'src/var-symbol?" (-ns)) => (checker string?)
(src/doc "bogus" (-ns)) => nil
(src/doc "bo/gus" (-ns)) => nil))

0 comments on commit ba4f21b

Please sign in to comment.