Skip to content

Commit

Permalink
Output to each session's *err* in the same manner as *out* (#387)
Browse files Browse the repository at this point in the history
Output to each session's *err* in the same manner as *out*; redirect System/out to *out* and System/err to *err*.
  • Loading branch information
bhagany authored and bbatsov committed Jan 2, 2017
1 parent 27a0b52 commit 65e6a11
Showing 1 changed file with 35 additions and 12 deletions.
47 changes: 35 additions & 12 deletions src/cider/nrepl/middleware/out.clj
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
(ns cider.nrepl.middleware.out
"Change *out* to print on sessions in addition to process out.
"Change *out*, *err*, System/out and System/err to print on sessions
in addition to process out.
Automatically changes the root binding of *out* to print to any
active sessions. An active session is one that has sent at least one
\"eval\" op.
Automatically changes the root binding of all output channels to
print to any active sessions. An active session is one that has sent
at least one \"eval\" op.
We use an eval message, instead of the clone op, because there's no
guarantee that the channel that sent the clone message will properly
Expand All @@ -14,13 +15,12 @@
[clojure.tools.nrepl.middleware :refer [set-descriptor!]]
[clojure.tools.nrepl.middleware.interruptible-eval :as ie]
[clojure.tools.nrepl.middleware.session :as session])
(:import [java.io PrintWriter Writer]))
(:import [java.io PrintWriter Writer PrintStream OutputStream]))

;;; OutStream
(defonce original-out *out*)
(defonce original-err *err*)

(declare tracked-sessions-map)
(declare unsubscribe-session)

(defmacro with-out-binding
Expand Down Expand Up @@ -64,19 +64,42 @@
(.flush printer))))
true))

(defn print-stream
"Returns a PrintStream suitable for binding as java.lang.System/out
or java.lang.System/err. All operations are forwarded to all output
bindings in the sessions of messages in addition to the server's
usual PrintWriter (saved in `original-out` or `original-err`).
type is either :out or :err."
[type]
(let [printer (case type
:out '*out*
:err '*err*)]
(PrintStream. (proxy [OutputStream] []
(close [] (.flush ^OutputStream this))
(write
([bytes]
(.write @(resolve printer) (String. bytes)))
([bytes ^Integer off ^Integer len]
(let [byte-range (byte-array
(take len (drop off bytes)))]
(.write @(resolve printer) (String. byte-range)))))
(flush []
(.flush @(resolve printer))))
true)))

;;; Known eval sessions
(def tracked-sessions-map
"Map from session ids to eval `*msg*`s.
Only the most recent message from each session is stored."
(atom {}))

(defn tracked-sessions-map-watch [_ _ _ new-state]
(let [o (forking-printer (vals new-state) :out)]
;; FIXME: This won't apply to Java loggers unless we also
;; `setOut`, but for that we need to convert a `PrintWriter` to a
;; `PrintStream` (or maybe just not use a `PrintWriter` above).
;; (System/setOut (PrintStream. o))
(alter-var-root #'*out* (constantly o))))
(let [out-writer (forking-printer (vals new-state) :out)
err-writer (forking-printer (vals new-state) :err)]
(alter-var-root #'*out* (constantly out-writer))
(alter-var-root #'*err* (constantly err-writer))
(System/setOut (print-stream :out))
(System/setErr (print-stream :err))))

(add-watch tracked-sessions-map :update-out tracked-sessions-map-watch)

Expand Down

0 comments on commit 65e6a11

Please sign in to comment.