To anyone who's interested in having precise completion for Java classes –
a feature like this is available in the latest CIDER
snapshot: https://twitter.com/unlog1c/status/1066748173094453248. You can
get a filtered completion by prepending a type tag to the next symbol.
On Friday, October 12, 2018 at 3:22:37 AM UTC+3,
somewhat-functional-programmer wrote:
>
> I'd like to share an idea and prototype code for better Java code
> completion in CIDER. While my main development environment is CIDER, the
> small modifications I made to support this idea were both to cider-nrepl
> and compliment -- which are both used by other Clojure tooling besides
> CIDER -- so maybe this is immediately more widely applicable.
>
> In an effort to make it easier on the tooling, I'm using a slightly
> different syntax for calling Java methods. My inspiration is Kawa scheme,
> and the notation is very similar:
>
> (String:charAt "my-string" 0) => \m
> (Integer:parseInt "12") => 12
> (possibly.fully.qualified.YourClassName:method this args)
>
> For this syntax to be properly compiled of course it needs to be wrapped
> in a macro:
>
> One form:
> (jvm (String:charAt "my-string" 0))
>
> Any number of forms:
> (jvm
> (lots of code)
> (JavaClass:method ...)
> (more code)
> (AnotherJavaClass:method ...))
>
> The jvm macro will transform any symbol it finds in the calling position
> of a list that follows the ClassName:method convention. I was thinking
> maybe of limiting it to just a particular namespace to absolutely prevent
> any name collisions with real clojure functions, something like:
>
> (jvm/String:charAt "my-string" 0)
>
> This will also work with the one-off test code I'm including here for
> folks to see what they think.
>
> I actually like the syntax (though I wish I didn't have to wrap it in a
> jvm macro -- though if this actually idea was worth fully implementing, I'd
> imagine having new let or function macros so you don't even have to
> sprinkle "jvm" macros in code much at all).
>
> There is one additional advantages to this style of Java interop besides
> the far better code completion:
> - The jvm macro uses reflection to find the appropriate method at
> compile time, and as such, you get a compile error if the method cannot be
> found.
> - This is a downside if you *want* reflection, but this of course
> doesn't preclude using the normal (.method obj args) notation.
>
> You could even use this style for syntactic sugar for Java method handles:
> - Though not implemented in my toy code here, you could also pass
> String:charAt as a clojure function -- assuming there were no overloads of
> the same arity.
>
>
> So, I'm hoping you will try this out. Two things to copy/paste -- one is
> a boot command, the other is the 100-200 lines of clojure that implements a
> prototype of this.
>
> This command pulls the necessary dependencies as well as starts up the
> rebel-readline repl (which is fantastic tool, and it also uses compliment
> for code completion):
>
> # Run this somewhere where you can make an empty source directory,
> # something fails in boot-tools-deps if you don't have one
> # (much appreciate boot-tools-deps -- as cider-nrepl really needs to
> # be a git dep for my purpose here since it's run through mranderson
> for its normal distro)
> mkdir src && \
> boot -d seancorfield/boot-tools-deps:0.4.6 \
> -d compliment:0.3.6 -d cider/orchard:0.3.1 \
> -d com.rpl/specter:1.1.1 -d com.taoensso/timbre:4.10.0 \
> -d com.bhauman/rebel-readline:0.1.4 \
> -d nrepl/nrepl:0.4.5 \
> deps --config-data \
> '{:deps {cider/cider-nrepl {:git/url "
> https://github.com/clojure-emacs/cider-nrepl.git" :sha
> "b2c0b920d762fdac2f8210805df2055af63f2eb1"}}}' \
> call -f rebel-readline.main/-main
>
> Paste the following code into the repl:
>
> (require 'cider.nrepl.middleware.info)
>
> (ns java-interop.core
> (:require
> [taoensso.timbre :as timbre
> :refer [log trace debug info warn error fatal report
> logf tracef debugf infof warnf errorf fatalf reportf
> spy get-env]]
> [clojure.reflect :as reflect]
> [clojure.string :as s :refer [includes?]]
> [com.rpl.specter :as sp]
> [orchard.java :as java]))
>
> (defn specific-class-member? [prefix]
> ;; NOTE: get a proper java class identifier here
> (when-let [prefix (if (symbol? prefix)
> (name prefix)
> prefix)]
> (and
> (not (.startsWith prefix ":"))
> (not (includes? prefix "::"))
> (includes? prefix ":"))))
>
> (def select-j-path
> (sp/recursive-path
> [] p
> (sp/cond-path
> #(and (seq? %) (specific-class-member? (first %)))
> [(sp/continue-then-stay sp/ALL-WITH-META p)]
> map? (sp/multi-path
> [sp/MAP-KEYS p]
> [sp/MAP-VALS p])
> vector? [sp/ALL-WITH-META p]
> seq? [sp/ALL-WITH-META p])))
>
> (defmacro j [[s & [obj & args]]]
> ;; FIXME: Add better error checking later
> ;; FIXME: Java fields can have the same name as a method
> (let [[clazz-str method-or-field & too-many-semis] (.split (name s) ":")
> method-or-field-sym (symbol method-or-field)
> clazz-sym (symbol clazz-str)]
> (if-let [{:keys [flags return-type]} (first
> (filter
> #(= (:name %)
> method-or-field-sym)
> (:members
> (reflect/reflect
> (ns-resolve *ns* clazz-sym)
> :ancestors true))))]
> (cond
> (contains? flags :static) (concat
> `(. ~clazz-sym ~method-or-field-sym)
> (if obj
> `(~obj))
> args)
> :else
> (concat
>
> `(. ~(if (symbol? obj)
> (with-meta
> obj
> {:tag clazz-sym})
> obj)
> ~(symbol method-or-field))
> args))
> (throw (ex-info "Method or field does not exist in class."
> {:method method-or-field-sym
> :class clazz-sym})))))
>
> (defmacro jvm [& body]
> (concat
> `(do)
> (map
> #(sp/transform
> select-j-path
> (fn [form]
> `(j ~form))
> %)
> body)))
>
>
> ;; for compliment code complete
> (in-ns 'compliment.sources.class-members)
> (require 'java-interop.core)
>
> (defn members-candidates
> "Returns a list of Java non-static fields and methods candidates."
> [prefix ns context]
> (cond
> (class-member-symbol? prefix)
> (let [prefix (subs prefix 1)
> inparts? (re-find #"[A-Z]" prefix)
> klass (try-get-object-class ns context)]
> (for [[member-name members] (get-all-members ns)
> :when (if inparts?
> (camel-case-matches? prefix member-name)
> (.startsWith ^String member-name prefix))
> :when
> (or (not klass)
> (some #(= klass (.getDeclaringClass ^Member %)) members))]
> {:candidate (str "." member-name)
> :type (if (instance? Method (first members))
> :method :field)}))
>
> (java-interop.core/specific-class-member? prefix)
> (let [sym (symbol prefix)
> [clazz-str member-str & too-many-semis] (.split (name sym)
> #_prefix ":")]
> (when (not too-many-semis)
> (when-let [clazz
> (resolve-class ns (symbol clazz-str))]
> (->>
> (clojure.reflect/reflect clazz :ancestors true)
> (:members)
> (filter #(and
> ;; public
> (contains? (:flags %) :public)
> ;; but not a constructor
> (not (and (not (:return-type %)) (:parameter-types
> %)))
> ;; and of course, the name must match
> (or
> (clojure.string/blank? member-str)
> (.startsWith (str (:name %)) member-str))))
> (map
> (fn [{:keys [name type return-type]}]
> {:candidate (str (when-let [n (namespace sym)]
> (str (namespace sym) "/")) clazz-str ":"
> name)
> :type (if return-type
> :method
> :field)}))))))))
>
> ;; for eldoc support in cider
> (in-ns 'cider.nrepl.middleware.info)
> (require 'orchard.info)
>
> (defn java-special-sym [ns sym]
> (let [sym-str (name sym)]
> (if (clojure.string/includes? sym-str ":")
> (when-let [[class member & too-many-semis] (.split sym-str ":")]
> (if (and class
> member
> (not too-many-semis))
> (when-let [resolved-clazz-sym
> (some->>
> (symbol class)
> ^Class (compliment.utils/resolve-class ns)
> (.getName)
> (symbol))]
> [resolved-clazz-sym
> (symbol member)]))))))
>
> (defn info [{:keys [ns symbol class member] :as msg}]
> (let [[ns symbol class member] (map orchard.misc/as-sym [ns symbol
> class member])]
> (if-let [cljs-env (cider.nrepl.middleware.util.cljs/grab-cljs-env
> msg)]
> (info-cljs cljs-env symbol ns)
> (let [var-info (cond (and ns symbol) (or
> (orchard.info/info ns symbol)
> (when-let [[clazz member]
> (java-special-sym
> ns symbol)]
> (orchard.info/info-java
> clazz member)))
> (and class member) (orchard.info/info-java
> class member)
> :else (throw (Exception.
> "Either \"symbol\", or
> (\"class\", \"member\") must be supplied")))
> ;; we have to use the resolved (real) namespace and name here
> see-also (orchard.info/see-also (:ns var-info) (:name
> var-info))]
> (if (seq see-also)
> (merge {:see-also see-also} var-info)
> var-info)))))
>
> ;; cider blows up if we don't have a project.clj file for it to read the
> version
> ;; string from
>
> (ns cider.nrepl.version
> ;; We require print-method here because `cider.nrepl.version`
> ;; namespace is used by every connection.
> (:require [cider.nrepl.print-method]
> [clojure.java.io :as io]))
>
> #_(def version-string
> "The current version for cider-nrepl as a string."
> (-> (or (io/resource "cider/cider-nrepl/project.clj")
> "project.clj")
> slurp
> read-string
> (nth 2)))
>
> (def version-string "0.19.0-SNAPSHOT")
>
> (def version
> "Current version of CIDER nREPL as a map.
> Map of :major, :minor, :incremental, :qualifier,
> and :version-string."
> (assoc (->> version-string
> (re-find #"(\d+)\.(\d+)\.(\d+)-?(.*)")
> rest
> (map #(try (Integer/parseInt %) (catch Exception e nil)))
> (zipmap [:major :minor :incremental :qualifier]))
> :version-string version-string))
>
> (defn cider-version-reply
> "Returns CIDER-nREPL's version as a map which contains `:major`,
> `:minor`, `:incremental`, and `:qualifier` keys, just as
> `*clojure-version*` does."
> [msg]
> {:cider-version version})
>
>
> (in-ns 'boot.user)
> (require 'nrepl.server)
>
> (defn nrepl-handler []
> (require 'cider.nrepl)
> (ns-resolve 'cider.nrepl 'cider-nrepl-handler))
>
> (nrepl.server/start-server :port 7888 :handler (nrepl-handler))
>
> (require '[java-interop.core :refer [jvm]])
>
> ;; NOTE: Code completion works in rebel-readline,
> ;; but it limits how many completions are shown at once
> ;; Try CIDER (you have an nrepl instance running now localhost:7888)
> ;; Eldoc also works in cider
> ;;
> ;; example
> (jvm
> [(Integer:parseInt "12") (String:charAt "test-string" 0)])
>
>
> You should now have an nrepl server running on localhost:7888 which you
> can connect to from CIDER. You can try the completion (though not
> documentation) right in rebel-readline's repl.
>
> So give it a try.... you'll notice static fields aren't handled perfectly
> (easy fix, but again I really am looking for feedback on the concept, and
> am wondering who would use it etc).
>
> Right now you can access a static field like a method call:
> (jvm (Integer:MAX_VALUE))
>
> I think the code completion + eldoc in CIDER is a productivity boost for
> sure.
>
>
>
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to [email protected]
Note that posts from new members are moderated - please be patient with your
first post.
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.