Orchard Clojure Library

repository·master·Indexed 43 days ago

https://github.com/clojure-emacs/orchard

A Clojure library providing runtime introspection functionality for development tools, editors, and IDEs. It enables code navigation, documentation, exploration, and debugging via a REPL-powered model. Key features include var metadata lookup via orchard.info, function dependency tracing with orchard.xref, Java interop support through orchard.java, and integration with ClojureDocs. Requires Clojure 1.10+ and Java 8+.

Tokens
1.7K
Snippets
7
Records
9
Agent score
6%

What's inside Orchard

  1. How Orchard works: REPL-powered introspection

    master

    Orchard is designed for building programmer tooling that inspects the state of a running REPL.

    Key Mental Model: Unlike static analysis tools that read source code files, Orchard relies primarily on runtime information. This means it can only inspect code that has been loaded and evaluated in the current REPL session.

    Design Principles:

    • No Runtime Dependencies: Orchard is designed to run alongside your application without introducing dependency collisions.
    • API Optimized for Editors: Most functions accept a combination of a namespace and a symbol (e.g., ns and name) rather than a var. This allows editors to resolve symbols without needing a direct reference to the var object.
    • REPL Agnostic: It works with nREPL, socket REPLs, or prepl.
  2. Install Orchard

    master

    Orchard requires Clojure 1.10+ and Java 8+. Note that Java 8 is soft-deprecated; while core functionality works, Java source parsing is not supported on JDK 8.

    To use Orchard, add it as a dependency to your project. For example, using a Clojure dependency vector:

    [cider/orchard "0.44.0"]
  3. Limitations of xref/fn-deps and xref/fn-refs

    master

    The orchard.xref/fn-deps and orchard.xref/fn-refs functions use Clojure compiler implementation details. While very fast, they have the following limitations:

    • Functions marked with the :inline meta will not be found (e.g., inc, +).
    • Redefining function vars that include lambdas will return the dependencies of both the old and the new versions.
    • They do not work on AoT (Ahead-of-Time) compiled functions.
  4. Debugging & Profiling with orchard.stacktrace and orchard.trace

    master

    Analyze errors and trace execution.

    • orchard.stacktrace/analyze: Provides structured analysis of exceptions.
    • orchard.trace/trace-var*: Traces all calls to a specific var (alternative to tools.trace).
    (require '[orchard.stacktrace :as stacktrace])
    (try (/ 1 0) (catch Exception e (stacktrace/analyze e)))
    ;; => [{:class "java.lang.ArithmeticException", :message "Divide by zero", :stacktrace [...], ...}]
    
    (require '[orchard.trace :as trace])
    (trace/trace-var* #'my.app/process)
    ;; All calls to my.app/process will now be traced
  5. Code Navigation with orchard.info and orchard.xref

    master

    Use Orchard to jump to definitions and trace code connections.

    • orchard.info/info: Look up var metadata, including namespace, name, file location, and arglists.
    • orchard.xref/fn-deps: Find function dependencies and usages.
    (require '[orchard.info :as info])
    (info/info 'clojure.core 'map)
    ;; => {:ns clojure.core, :name map, :file "clojure/core.clj", :arglists ([f] [f coll] ...), ...}
    
    (require '[orchard.xref :as xref])
    (xref/fn-deps 'my.app/handler)
    ;; => #{#'ring.util.response/response #'my.app/render-page ...}
  6. Java Interop with orchard.java

    master

    Orchard provides first-class support for navigating and documenting Java code.

    • Use orchard.java/member-info to get class and member info via reflection.
    • Supports Java source navigation and Javadoc extraction.
    • Can locate Java source files in the classpath, JDK's src.zip, or Maven -sources.jar artifacts and automatically download missing source JARs.
    (require '[orchard.java :as java])
    (java/member-info 'java.util.Map 'size)
    ;; => {:name size, :type java.util.Map, :argtypes [], :returns int, ...}
  7. Documentation with orchard.eldoc and orchard.clojuredocs

    master

    Surface documentation and function signatures.

    • orchard.eldoc/eldoc: Transforms var info into an Eldoc-compatible format (function signatures).
    • orchard.clojuredocs/find-doc: Integrates with ClojureDocs to find examples, notes, and see-also references.
    • Spec Support: Supports both clojure.spec.alpha and clojure.alpha.spec.
    (require '[orchard.eldoc :as eldoc])
    (-> (info/info 'clojure.core '+) eldoc/eldoc)
    ;; => {:ns "clojure.core", :name "+", :type "function", :eldoc [[] ["x"] ["x" "y"] ["x" "y" "&"] ...], ...}
    
    (require '[orchard.clojuredocs :as clojuredocs])
    (clojuredocs/find-doc "clojure.core" "map")
    ;; => {:examples [...], :see-also [...], :notes [...], ...}
  8. Editor Support with orchard.pp and orchard.indent

    master

    Essential utilities for polished editing experiences.

    • orchard.pp/pprint-str: An alternative to clojure.pprint for pretty printing strings.
    • orchard.indent/infer-style-indent: Infers indentation styles for specific forms.
    (require '[orchard.pp :as pp])
    (pp/pprint-str {:a 1 :b {:c 2 :d [3 4 5]}} {:max-width 30})
    
    (require '[orchard.indent :as indent])
    (indent/infer-style-indent (meta #'clojure.core/let))
    ;; => {:name let, ..., :style/indent [[:block 1]]}
  9. Exploration with orchard.apropos and orchard.inspect

    master

    Discover and understand code and data.

    • orchard.apropos/find-symbols: Performs enhanced regex searches across namespaces.
    • orchard.inspect/start: Provides a value inspector for examining data structures.
    (require '[orchard.apropos :as apropos])
    (apropos/find-symbols {:var-query {:search "^print"}})
    ;; => [{:name clojure.core/print, :doc "Prints the object(s)...", :type "function"} ...]
    
    (require '[orchard.inspect :as inspect])
    (inspect/start {:page-size 32} (range 100))
    ;; => {:rendered ("Class: ..." "--- Contents:" ...), :counter 1, :value (0 1 2 ...), ...}