Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Overhaul jline workflow

  • Loading branch information...
commit f81e53cc713d1cef265f128cb208d97d8ead36bc 1 parent 847a7b7
@trptcolin authored
View
1  project.clj
@@ -16,7 +16,6 @@
:profiles {:dev {:dependencies ~dev-deps}}
:dev-dependencies ~dev-deps
:plugins ~dev-deps
- :aot [reply.reader.jline.JlineInputReader]
:source-path "src/clj"
:java-source-path "src/java"
:test-path "spec"
View
61 src/clj/reply/eval_modes/nrepl.clj
@@ -10,7 +10,8 @@
[reply.exit]
[reply.eval-state :as eval-state]
[reply.initialization]
- [reply.reader.jline :as reader.jline]
+ [reply.reader.simple-jline :as simple-jline]
+ [reply.reader.jline.completion :as jline.completion]
[reply.signals :as signals]
[net.cgrand.sjacket :as sjacket]
[net.cgrand.sjacket.parser :as sjacket.parser]))
@@ -23,7 +24,6 @@
(signals/set-signal-handler!
"INT"
(fn [sig]
- (reader.jline/print-interruption)
(when-let [command-id @current-command-id]
(client {:op "interrupt"
:session @current-session
@@ -38,10 +38,21 @@
TimeUnit/MILLISECONDS)
(session-responses session))))
-(defn safe-read-line [input-stream]
- (try (.readLine input-stream)
- (catch jline.console.UserInterruptException e
- :interrupted)))
+(defn make-completer [ns]
+ (fn [reader]
+ (let [eval-fn reply.initialization/eval-in-user-ns
+ redraw-line-fn (fn []
+ (.redrawLine reader)
+ (.flush reader))]
+ (if ns
+ (jline.completion/make-completer
+ eval-fn redraw-line-fn ns)
+ nil))))
+
+(defn safe-read-line
+ [{:keys [input-stream prompt-string ns] :as state}]
+ (simple-jline/safe-read-line
+ (assoc state :completer-factory (make-completer ns))))
(defn execute-with-client [client options form]
(let [command-id (nrepl.misc/uuid)
@@ -55,7 +66,8 @@
(some #{"done" "interrupted" "error"} (:status %))))
(filter identity (session-responses session)))]
(when (some #{"need-input"} (:status res))
- (let [input-result (safe-read-line *in*)]
+ (let [input-result (safe-read-line {:input-stream *in*
+ :prompt-string (constantly "STDIN> ")})]
(when-not (= :interrupted input-result)
(session-sender
{:op "stdin" :stdin (str input-result "\n")
@@ -68,10 +80,10 @@
(reset! current-command-id nil)
@current-ns))
-(defn parsed-forms
- ([request-exit] (parsed-forms request-exit nil))
- ([request-exit text-so-far]
- (if-let [next-text (safe-read-line *in*)]
+(defn parsed-forms [{:keys [ns request-exit text-so-far prompt-string] :as options}]
+ (if-let [next-text (safe-read-line {:ns ns
+ :input-stream *in*
+ :prompt-string prompt-string})]
(let [interrupted? (= :interrupted next-text)
parse-tree (when-not interrupted?
(sjacket.parser/parser
@@ -96,26 +108,33 @@
(lazy-seq
(concat form-strings
(parsed-forms
- request-exit
- (apply str (map sjacket/str-pt
- remainder)))))
+ (assoc options
+ :text-so-far
+ (apply str (map sjacket/str-pt
+ remainder))
+ :prompt-string
+ (apply str (concat (repeat (- (count prompt-string)
+ (count "#_=> "))
+ \space)
+ "#_=> "))))))
(seq form-strings)
form-strings
:else
(list "")))))
- (list request-exit))))
+ (list request-exit)))
(defn run-repl
([connection] (run-repl connection nil))
([connection {:keys [prompt] :as options}]
(let [{:keys [major minor incremental qualifier]} *clojure-version*]
(loop [ns (execute-with-client connection options "")]
- (prompt ns)
- (flush)
(let [eof (Object.)
execute (partial execute-with-client connection
(assoc options :interactive true))
- forms (parsed-forms eof)]
+ forms (parsed-forms {:request-exit eof
+ :prompt-string (prompt ns)
+ :ns ns
+ :text-so-far nil})]
(if (reply.exit/done? eof (first forms))
nil
(recur (last (doall (map execute forms))))))))))
@@ -172,7 +191,6 @@
(recur connection))
(defn main
- "Mostly ripped from nREPL's cmdline namespace."
[options]
(let [connection (get-connection options)
client (nrepl/client connection Long/MAX_VALUE)
@@ -182,10 +200,7 @@
(swap! response-queues assoc session (LinkedBlockingQueue.))
(swap! response-queues assoc completion-session (LinkedBlockingQueue.))
(let [options (assoc options :prompt
- (fn [ns]
- (reader.jline/prepare-for-read
- (partial completion-eval client completion-session)
- ns)))
+ (fn [ns] (str ns "=> ")))
options (if (:color options)
(merge options nrepl.cmdline/colored-output)
options)]
View
9 src/clj/reply/eval_modes/standalone.clj
@@ -3,14 +3,15 @@
[reply.eval-state :as eval-state]
[reply.exit :as exit]
[reply.initialization :as initialization]
- [reply.reader.jline :as reader.jline]
[reply.signals :as signals]))
(def reply-read
(fn [prompt exit]
(concurrency/starting-read!)
(binding [*ns* (eval-state/get-ns)]
- (let [read-result (reader.jline/read prompt exit)]
+ (print (str (ns-name *ns*) "=> "))
+ (flush)
+ (let [read-result (clojure.main/repl-read prompt exit)]
(if (exit/done? exit read-result)
exit
read-result)))))
@@ -25,9 +26,7 @@
(concurrency/act-in-future prn))
(defn handle-ctrl-c [signal]
- (reader.jline/print-interruption)
- (concurrency/stop-running-actions)
- (reader.jline/reset-reader))
+ (concurrency/stop-running-actions))
(defn main [options]
(signals/set-signal-handler! "INT" handle-ctrl-c)
View
17 src/clj/reply/main.clj
@@ -4,7 +4,7 @@
[reply.exit :as exit]
[reply.hacks.printing :as hacks.printing]
[reply.initialization :as initialization]
- [reply.reader.jline :as reader.jline]
+ ;[reply.reader.jline :as reader.jline]
[reply.signals :as signals]
[clojure.main]
[clojure.repl]
@@ -34,17 +34,13 @@
["--port" "Start new nREPL server on this port"]))
(defn handle-resume [signal]
- (println "Welcome back!")
- (reader.jline/resume-reader))
+ (println "Welcome back!"))
(defmacro with-launching-context [options & body]
`(try
- (.addShutdownHook (Runtime/getRuntime)
- (Thread. #(reader.jline/shutdown-reader)))
(signals/set-signal-handler! "CONT" handle-resume)
(with-redefs [clojure.core/print-sequential hacks.printing/print-sequential
clojure.repl/pst clj-stacktrace.repl/pst]
- (reader.jline/setup-reader! ~options)
~@body)
~@(filter identity
[(when (resolve 'ex-info)
@@ -58,26 +54,19 @@
'(catch Throwable t# (clojure.repl/pst t#))])
(finally (exit/exit))))
-(defn set-prompt [options]
- (when-let [prompt-form (:custom-prompt options)]
- (reader.jline/set-prompt-fn! (eval prompt-form))))
-
(defn launch-nrepl [options]
"Launches the nREPL version of REPL-y, with options already parsed out. The
options map can also include :input-stream and :output-stream entries, which
must be Java objects passed via this entry point, as they can't be parsed
from a command line."
(with-launching-context options
- (reader.jline/with-jline-in
- (set-prompt options)
- (eval-modes.nrepl/main options))))
+ (eval-modes.nrepl/main options)))
(defn launch-standalone
"Launches the standalone (non-nREPL) version of REPL-y, with options already
parsed out"
[options]
(with-launching-context options
- (set-prompt options)
(eval-modes.standalone/main options)))
(defn launch
View
7 src/clj/reply/reader/jline.clj
@@ -38,7 +38,7 @@
(or output-stream System/out))
history (FileHistory. (make-history-file history-file))
completer (jline.completion/make-completer
- reply.initialization/eval-in-user-ns #())]
+ reply.initialization/eval-in-user-ns #() *ns*)]
(.setBlinkMatchingParen (.getKeys reader) true)
(.setHandleUserInterrupt reader true)
(doto reader
@@ -98,7 +98,7 @@
(.restore (.getTerminal @jline-reader))))
(defn prepare-for-read [eval-fn ns]
- (when-not @jline-reader (setup-reader!))
+ (when-not @jline-reader (setup-reader! {}))
(.flush (.getHistory @jline-reader))
(.setPrompt @jline-reader (@prompt-fn ns))
(eval-state/set-ns ns)
@@ -108,7 +108,8 @@
eval-fn
(fn []
(.redrawLine @jline-reader)
- (.flush @jline-reader)))))
+ (.flush @jline-reader))
+ ns)))
(defmacro with-jline-in [& body]
`(do
View
8 src/clj/reply/reader/jline/completion.clj
@@ -3,17 +3,17 @@
[reply.eval-state :as eval-state])
(:import [jline.console.completer Completer]))
-(defn construct-possible-completions-form [prefix]
- `(~'complete.core/completions (~'str ~prefix) ~'*ns*))
+(defn construct-possible-completions-form [prefix ns]
+ `(~'complete.core/completions (~'str ~prefix) (symbol '~ns)))
-(defn make-completer [eval-fn redraw-line-fn]
+(defn make-completer [eval-fn redraw-line-fn ns]
(proxy [Completer] []
(complete [^String buffer cursor ^java.util.List candidates]
(let [buffer (or buffer "")
prefix (or (completion/get-word-ending-at buffer cursor) "")
prefix-length (.length prefix)
possible-completions-form (construct-possible-completions-form
- prefix)
+ prefix ns)
possible-completions (eval-fn possible-completions-form)]
(if (or (empty? possible-completions) (zero? prefix-length))
-1
View
93 src/clj/reply/reader/simple_jline.clj
@@ -0,0 +1,93 @@
+(ns reply.reader.simple-jline
+ (:require [reply.reader.jline.completion :as jline.completion])
+ (:import [java.io File FileInputStream FileDescriptor
+ PrintStream ByteArrayOutputStream]
+ [jline.console ConsoleReader]
+ [jline.console.history FileHistory]
+ [jline.internal Configuration Log]))
+
+(def ^:private current-console-reader (atom nil))
+
+(defn- make-history-file [history-path]
+ (if history-path
+ (let [history-file (File. history-path)]
+ (if (.getParentFile history-file)
+ history-file
+ (File. "." history-path)))
+ (File. (System/getProperty "user.home") ".jline-reply.history")))
+
+(defn shutdown [{:keys [reader] :as state}]
+ (when reader
+ (.restore (.getTerminal reader))
+ (.shutdown reader)))
+
+(defn- initialize-jline []
+ (.addShutdownHook (Runtime/getRuntime)
+ (Thread. #(when-let [reader @current-console-reader]
+ (shutdown reader))))
+ (when (= "dumb" (System/getenv "TERM"))
+ (.setProperty (Configuration/getProperties) "jline.terminal" "none"))
+ ;; TODO: what happens when nobody consumes from this stream?
+ (when-not (System/getenv "JLINE_LOGGING")
+ (Log/setOutput (PrintStream. (ByteArrayOutputStream.)))))
+
+(defn- prepare-for-next-read [{:keys [reader] :as state}]
+ (.flush (.getHistory reader))
+ (.removeCompleter reader (first (.getCompleters reader))))
+
+(defn- setup-console-reader
+ [{:keys [prompt-string reader input-stream output-stream
+ history-file completer-factory blink-parens]
+ :or {input-stream (FileInputStream. FileDescriptor/in)
+ output-stream System/out
+ prompt-string "=> "
+ blink-parens true}
+ :as state}]
+ (let [reader (ConsoleReader. input-stream output-stream)
+ history (FileHistory. (make-history-file history-file))
+ ;; TODO: rip out this default completer, make it a no-op
+ completer (if completer-factory
+ (completer-factory reader)
+ nil)]
+ (.setBlinkMatchingParen (.getKeys reader) blink-parens)
+ (when completer (.addCompleter reader completer))
+ (doto reader
+ (.setHistory history)
+ (.setHandleUserInterrupt true)
+ (.setExpandEvents false)
+ (.setPaginationEnabled true)
+ (.setPrompt prompt-string))))
+
+(defn get-input-line [state]
+ (if (:reader state)
+ (prepare-for-next-read state)
+ (initialize-jline))
+ (let [reader (setup-console-reader state)
+ input (try (.readLine reader)
+ (catch jline.console.UserInterruptException e
+ :interrupted))]
+ (if (= :interrupted input)
+ (assoc state
+ :reader reader
+ :input ""
+ :interrupted true)
+ (assoc state
+ :reader reader
+ :input input
+ :interrupted nil))))
+
+(def jline-state (atom {}))
+
+(defn safe-read-line
+ [{:keys [input-stream prompt-string completer-factory] :as state}]
+ (swap! jline-state
+ assoc
+ :prompt-string prompt-string
+ :completer-factory completer-factory)
+ (swap! jline-state
+ (fn [previous-state]
+ (get-input-line previous-state)))
+ (if (:interrupted @jline-state) ;; TODO: don't do this same check in 2 places
+ :interrupted
+ (:input @jline-state)))
+
Please sign in to comment.
Something went wrong with that request. Please try again.