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.

Reply via email to