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 clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com 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 clojure+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.