diff --git a/CHANGELOG.md b/CHANGELOG.md index 969a25c..fdd7fdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ - Complete fully-qualified classnames by their shortname prefix anywhere in the file (previously worked only in the `:import` section of the `ns` form). - Fix ' and #' being swallowed when completing vars prefixed by them. +- [#91](https://github.com/alexander-yakushev/compliment/pull/91): `compliment.utils/namespaces-on-classpath` (now deprecated) takes cljc files into account. + Add replacement `compliment.utils/namespaces&files-on-classpath` that yields a collection of maps and allows for filtering what files to consider (e.g. also cljs). ### 0.3.14 (2022-07-11) diff --git a/src/compliment/sources/namespaces_and_classes.clj b/src/compliment/sources/namespaces_and_classes.clj index 40ab636..a879a92 100644 --- a/src/compliment/sources/namespaces_and_classes.clj +++ b/src/compliment/sources/namespaces_and_classes.clj @@ -94,11 +94,12 @@ (get-all-full-names prefix)) ;; If prefix doesn't contain a period, using fuziness produces too many ;; irrelevant candidates. - (for [^String ns-str (utils/namespaces-on-classpath) + (for [{^String ns-str :ns, ^String file :file} + (utils/namespaces&files-on-classpath {:extensions #{"clj" "cljc"}}) :when (if has-dot (nscl-matches? prefix ns-str) (.startsWith ns-str prefix))] - {:candidate ns-str, :type :namespace}) + {:candidate 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. diff --git a/src/compliment/utils.clj b/src/compliment/utils.clj index 2376155..afbc401 100644 --- a/src/compliment/utils.clj +++ b/src/compliment/utils.clj @@ -19,6 +19,10 @@ Note that should always have the same value, regardless of OS." "/") +(defn- ensure-no-leading-slash [file] + (if (.startsWith file File/separator) + (.substring file 1) file)) + (defn fuzzy-matches? "Tests if symbol matches the prefix when symbol is split into parts on separator." @@ -181,25 +185,45 @@ Note that should always have the same value, regardless of OS." (->> (for [^String file (all-files-on-classpath classpath) :when (and (.endsWith file ".class") (not (.contains file "__")) (not (.contains file "$")))] - (.. (if (.startsWith file File/separator) - (.substring file 1) file) + (.. (ensure-no-leading-slash file) (replace ".class" "") ;; Address the issue #79 , on Windows, for prefix such ;; as "java.util.", the list of candidates was empty. (replace resource-separator "."))) (group-by #(subs % 0 (max (.indexOf ^String % ".") 0))))))) -(defn namespaces-on-classpath - "Returns the list of all Clojure namespaces obtained by classpath scanning." - [] - (let [classpath (classpath)] - (cache-last-result ::namespaces-on-classpath classpath - (set (for [^String file (all-files-on-classpath classpath) - :when (and (.endsWith file ".clj") - (not (.startsWith file "META-INF"))) - :let [[_ ^String nsname] (re-matches #"[^\w]?(.+)\.clj" file)] - :when nsname] - (.. nsname (replace resource-separator ".") (replace "_" "-"))))))) +(defn namespaces&files-on-classpath + "Returns the list of all clj(c) namespaces obtained by classpath scanning. + + Options: + - `extensions` (default `#{\"clj\", \"cljc\"}`) - what files to consider. Should be subset of `#{\"clj\", \"cljc\", \"cljs\"}`." + ([] (namespaces&files-on-classpath nil)) + ([{:keys [extensions] :or {extensions #{"clj" "cljc"}}}] + (let [classpath (classpath) + extensions (set extensions) + nses + (cache-last-result ::namespaces-on-classpath classpath + (for [^String file (all-files-on-classpath classpath) + :when (and (or (.endsWith file ".clj") + (.endsWith file ".cljc") + (.endsWith file ".cljs")) + (not (.startsWith file "META-INF"))) + :let [file (ensure-no-leading-slash file) + [_ ^String nsname ^String extension] (re-matches #"[^\w]?(.+)\.(clj[sc]?)" file)] + :when nsname] + (let [ns-str (.. nsname (replace resource-separator ".") (replace "_" "-"))] + {:ns ns-str, :file file, :extension extension}))) + xf (comp (filter (comp extensions :extension)) + (map #(dissoc % :extension)))] + (into #{} + (transduce xf conj nses))))) + +(defn ^:deprecated namespaces-on-classpath [] + (into #{} + (transduce + (map :ns) + conj + (namespaces&files-on-classpath {:extensions #{"clj" "cljc"}})))) (defn project-resources "Returns a list of all non-code files in the current project." @@ -208,9 +232,9 @@ Note that should always have the same value, regardless of OS." (cache-last-result ::project-resources classpath (for [path classpath ^String file (list-files path false) - :when (not (or (empty? file) (.endsWith file ".clj") + :when (not (or (empty? file) + (.endsWith file ".clj") (.endsWith file ".cljc") (.endsWith file ".cljs") (.endsWith file ".jar") (.endsWith file ".class")))] ;; resource pathes always use "/" regardless of platform - (.. (if (.startsWith file File/separator) - (.substring file 1) file) + (.. (ensure-no-leading-slash file) (replace File/separator resource-separator)))))) diff --git a/test/compliment/sources/t_namespaces_and_classes.clj b/test/compliment/sources/t_namespaces_and_classes.clj index 67d38b6..2d4aec2 100644 --- a/test/compliment/sources/t_namespaces_and_classes.clj +++ b/test/compliment/sources/t_namespaces_and_classes.clj @@ -37,8 +37,8 @@ => (just ["src"]) (src/candidates "clojure.java." (-ns) nil) - => (contains #{{:candidate "clojure.java.browse", :type :namespace} - {:candidate "clojure.java.shell", :type :namespace}} :gaps-ok) + => (contains #{{:candidate "clojure.java.browse", :type :namespace, :file "clojure/java/browse.clj"} + {:candidate "clojure.java.shell", :type :namespace, :file "clojure/java/shell.clj"}} :gaps-ok) (src/candidates "java.io.Stri" (-ns) nil) => (contains #{{:candidate "java.io.StringReader", :type :class} diff --git a/test/compliment/t_core.clj b/test/compliment/t_core.clj index ea62665..6789fd3 100644 --- a/test/compliment/t_core.clj +++ b/test/compliment/t_core.clj @@ -118,7 +118,7 @@ ;; Test for not required namespaces (core/completions "cl.test.ta" {}) => - (just [{:type :namespace, :candidate "clojure.test.tap"}]) + (just [{:type :namespace, :candidate "clojure.test.tap" :file "clojure/test/tap.clj"}]) ;; Test for aliases (core/completions "cor" {:ns 'compliment.t-core}) diff --git a/test/compliment/t_utils.clj b/test/compliment/t_utils.clj index 863e028..69a1711 100644 --- a/test/compliment/t_utils.clj +++ b/test/compliment/t_utils.clj @@ -52,3 +52,9 @@ (is (contains? all-classes "java.lang.Thread")) (is (contains? all-classes "java.io.File")) (is (contains? all-classes "java.nio.channels.FileChannel")))) + +(deftest namespaces&files-on-classpath-test + (is (contains? (namespaces&files-on-classpath {:extensions #{"clj"}}) + {:ns "compliment.t-utils" :file "compliment/t_utils.clj"})) + (is (= #{{:ns "dummy" :file "dummy.cljs"}} + (namespaces&files-on-classpath {:extensions #{"cljs"}})))) diff --git a/test/dummy.cljs b/test/dummy.cljs new file mode 100644 index 0000000..a465736 --- /dev/null +++ b/test/dummy.cljs @@ -0,0 +1,2 @@ +;; For testing (compliment.t-utils) purpose +(ns dummy)