Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Add 'apro' variant of 'apropos' that returns symbols with namespaces #107

Merged
merged 1 commit into from

2 participants

@jafingerhut

This is probably not in a form that you would want to include, if you are interested in including it in the first place. Suggestions for changes welcome. No problem if it isn't of interest. Just close the issue.

In particular, I was guessing on how to make symbols usable in reply, like help and cdoc.

Examples of output below. Compare against output of similar apropos calls for the difference.

user=> (apro "replace")
(replace
clojure.string/replace
clojure.string/replace-first
clojure.walk/postwalk-replace
clojure.walk/prewalk-replace
clojure.zip/replace)
nil
user=> (require '[clojure.string :as str])
nil
user=> (apro "replace")
(replace
clojure.walk/postwalk-replace
clojure.walk/prewalk-replace
clojure.zip/replace
str/replace
str/replace-first)
nil
user=> (in-ns 'clojure.string)
#
clojure.string=> (user/apro "replace")
(replace
replace-by
replace-first
replace-first-by
replace-first-char
clojure.core/replace
clojure.walk/postwalk-replace
clojure.walk/prewalk-replace
clojure.zip/replace)
nil

@trptcolin
Owner

Sorry for taking awhile to get back to you on this. I do like this better than the default behavior of apropos, and I'd definitely upvote / comment favorably on a patch for this behavior in clojure.repl.

I'm all for this patch in general. I do feel like the naming is a little awkward; how would you feel about something like find-var instead? I'm not at all set on that name, but I feel like apro is a bit opaque. And I feel like apropos is already a bit strange since it only searches var names, rather than including docstrings, which would have been more similar to the Unix apropos command which searches descriptions (which find-doc really does). find-var would be kind of symmetric with find-doc, which I kind of like.

I'm happy to merge this and update the naming myself, just wanted to get your thoughts on that idea first.

@jafingerhut

No problem on the response time. One of the reasons I submitted it to reply was that I suspect it would take months to get into clojure.repl, if it ever did. I can submit it there, too, if you'd like it to have a chance of eventually being there.

I have no problem with find-var as a name for this -- apro was simply intended to be similar to the existing apropos but shorter to type.

A quick question: Would you also be interested in something like find-doc, except it only prints out the lines of doc strings that actually match, rather than the entire doc strings? The output of find-doc can be quite long and it isn't always clear from the output where the matches occur.

@trptcolin
Owner

OK, great. In that case I'll merge this and do the naming update before a release. I'll be sure to take a close look at the exporting, too - in particular, I'm not certain but suspect unresolve may need to be exported/interned as well. I've had all kinds of things go wrong here, and definitely need to restructure the design so it's simpler to test.

And I would personally love to see a clojure.repl patch for it. I consider it to be a bug that apropos tells you a bit about vars that are accessible, but not enough to actually get ahold of them. And being printed output from the clojure.repl ns, I don't think there should really be any breaking-change concerns around it (famous last words, eh?).

Yeah, I agree find-doc's output is hard to parse. My main concern there would be not having the context from other lines of the docstrings. I'm imagining that an IDE might do this by highlighting the matches. We could actually do something similar with ANSI codes:

(defn highlighted-find-doc [search-term] 
  (println 
    (clojure.string/replace 
      (with-out-str (find-doc search-term)) 
      search-term 
      (str (char 27) "[32m" search-term (char 27) "[0m"))))

That'll need some modification for the regex side of find-doc, and also to not print when there are no results, but I kind of like it. Might also be nice to make the highlight color tweakable. I can imagine a few other uses for colors in the repl now that I think of it. What do you think?

@jafingerhut

I will submit an enhancement ticket for clojure.repl, too.

Regarding the color highlighting, my first question would be how to do it in a way that works as portably as reasonable. I guess reply is not used from within Emacs, Vim, etc. when they do things like Emacs's nrepl-jack-in?

Even if reply isn't used in those cases, there are many different terminals with different escape codes around, which is exactly why things like Unix termcap were created. It looks like there are one or two Java libraries that wrap termcap, but as native libraries, which isn't so nice for distributing within reply. Lanterna is pure Java -- it doesn't work across as many different terminal types, but it looks like most of the common ones in use on Linux today:

http://stackoverflow.com/questions/1321308/whats-the-best-way-to-get-text-user-interfaces-ncurses-like-functionality-in

http://code.google.com/p/lanterna/

@trptcolin
Owner

Ah, good point, I was only considering the frontend side of reply. I guess people do use the initialization stuff from other clients, when they connect to a lein repl.

The portability stuff I hadn't thought through either. I think Lanterna is really interesting, but probably a bigger overhaul than I'm ready to undertake right now (and may be an either/or proposition with jline).

Thanks for the input!

@trptcolin trptcolin merged commit a59f816 into from
@jafingerhut

A couple of minor things you might want to clean up besides the name: Get rid of the pprint call, perhaps.

Remove "& opts" from arguments to better-apropos. Leftover from somewhere I don't recall right now.

@trptcolin
Owner

Thanks. I think that makes sense. I'd like to eventually have the option of automatically pprinting all result values in the repl, so that could cover this use case. Pretty sure that's doable via an nREPL middleware (and may already exist).

Also, rats! find-var is taken in clojure.core (not sure how I missed it when looking earlier). I'm going with find-name for now.

This initialization stuff is pretty hairy; needs to be simplified pretty drastically after I get the latest big jline refactor tested and released.

@trptcolin trptcolin referenced this pull request from a commit
@trptcolin Update naming for apropos replacement
Use "apropos-better" to nudge people via auto-complete, and alias as
find-name since it seems more descriptive. Removes the pprint to be more
similar to apropos.

refs #107
73a987e
@jafingerhut

FYI a simplified modification to apropos made its way into Clojure master today: clojure/clojure@f413bf0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
Showing with 65 additions and 0 deletions.
  1. +65 −0 src/clj/reply/initialization.clj
View
65 src/clj/reply/initialization.clj
@@ -33,6 +33,66 @@
(class (clojure.main/repl-exception e)))
(throw e))))))
+(defn unresolve
+ "Given a var, return a sequence of all symbols that resolve to the
+ var from the current namespace *ns*."
+ [var]
+ (when-not (instance? clojure.lang.Var var)
+ (throw (Exception. (format "unresolve: first arg must be Var"))))
+ (let [home-ns (.ns var)
+ sym-name-str (second (re-find #"/(.*)$" (str var)))]
+ (sort-by
+ #(count (str %))
+ (concat
+ ;; The symbols in the current namespace that map to the var, if
+ ;; any
+ (->> (ns-map *ns*)
+ (filter (fn [[k v]] (= var v)))
+ (map first))
+ ;; This is the "canonical" symbol that resolves to the var, with
+ ;; full namespace/symbol-name
+ (list (symbol (str home-ns) sym-name-str))
+ ;; There might be one or more aliases for the symbol's home
+ ;; namespace defined in the current namespace.
+ (->> (ns-aliases *ns*)
+ (filter (fn [[ns-alias ns]] (= ns home-ns)))
+ (map first)
+ (map (fn [ns-alias-symbol]
+ (symbol (str ns-alias-symbol) sym-name-str))))))))
+
+(defn better-apropos
+ "Given a regular expression or stringable thing, calculate a
+ sequence of all symbols in all currently-loaded namespaces such that
+ it matches the str-or-pattern, with at most one such symbol per Var.
+ The sequence returned contains symbols that map to those Vars, and are
+ the shortest symbols that map to the Var, when qualified with the
+ namespace name or alias, if that qualification is necessary to name
+ the Var. Note that it is possible the symbol returned does not match
+ the str-or-pattern itself, e.g. if the symbol-to-var mapping was
+ created with :rename.
+
+ Searches through all non-Java symbols in the current namespace, but
+ only public symbols of other namespaces."
+ [str-or-pattern & opts]
+ (let [matches? (if (instance? java.util.regex.Pattern str-or-pattern)
+ #(re-find str-or-pattern (str %))
+ #(.contains (str %) (str str-or-pattern)))]
+ (map #(first (reply.initialization/unresolve %))
+ (set
+ (mapcat (fn [ns]
+ (map second
+ (filter (fn [[s v]] (matches? s))
+ (if (= ns *ns*)
+ (concat (ns-interns ns) (ns-refers ns))
+ (ns-publics ns)))))
+ (all-ns))))))
+
+(defn apro
+ "Shorter-name version of apropos that also sorts and pretty-prints
+ the results."
+ [str-or-pattern & opts]
+ (clojure.pprint/pprint (sort (apply reply.initialization/better-apropos str-or-pattern opts))))
+
(def clojuredocs-available?
(delay
(try
@@ -104,6 +164,11 @@
~(export-definition 'reply.initialization/help)
(~'intern-with-meta '~'user '~'help ~'#'help)
+ ~(export-definition 'reply.initialization/apro)
+ ~(export-definition 'reply.initialization/better-apropos)
+ (~'intern-with-meta '~'user '~'apro ~'#'apro)
+ (~'intern-with-meta '~'user '~'better-apropos ~'#'better-apropos)
+
~(export-definition 'reply.initialization/clojuredocs-available?)
~(export-definition 'reply.initialization/call-with-ns-and-name)
~(export-definition 'reply.initialization/handle-fns-etc)
Something went wrong with that request. Please try again.