├── .github └── FUNDING.yml ├── rebel-readline-cljs ├── dev │ ├── user.clj │ └── rebel_cljs_dev │ │ └── main.clj ├── doc │ └── intro.md ├── test │ └── rebel_readline_cljs_service │ │ └── core_test.clj ├── .gitignore ├── src │ └── rebel_readline │ │ └── cljs │ │ ├── main.clj │ │ ├── repl.clj │ │ └── service │ │ └── local.clj ├── deps.edn ├── CHANGELOG.md ├── README.md ├── build.clj └── LICENSE ├── rebel-readline-paredit ├── doc │ └── intro.md ├── .gitignore ├── test │ └── rebel_readline_paredit │ │ └── core_test.clj ├── project.clj ├── README.md ├── CHANGELOG.md ├── src │ └── rebel_readline_paredit │ │ └── core.clj └── LICENSE ├── CHANGELOG.md ├── rebel-readline ├── .gitignore ├── src │ └── rebel_readline │ │ ├── tool.clj │ │ ├── clojure │ │ ├── service │ │ │ ├── simple.clj │ │ │ └── local.clj │ │ ├── utils.clj │ │ ├── main.clj │ │ └── sexp.clj │ │ ├── utils.clj │ │ ├── io │ │ └── callback_reader.clj │ │ ├── jline_api │ │ └── attributed_string.clj │ │ ├── main.clj │ │ ├── commands.clj │ │ ├── core.clj │ │ ├── tools.clj │ │ └── jline_api.clj ├── dev │ ├── user.clj │ └── rebel_dev │ │ └── main.clj ├── deps.edn ├── CHANGELOG.md ├── test │ └── rebel_readline │ │ └── clojure │ │ ├── line_reader_test.clj │ │ └── sexp_test.clj ├── build.clj ├── doc │ └── intro.md ├── README.md └── LICENSE ├── .editorconfig ├── rebel-readline-nrepl ├── src │ └── rebel_readline │ │ ├── nrepl.clj │ │ └── nrepl │ │ ├── service │ │ ├── commands.clj │ │ └── nrepl.clj │ │ └── main.clj ├── dev │ ├── user.clj │ └── rebel_nrepl │ │ └── main.clj ├── deps.edn ├── build.clj └── README.md ├── .gitignore ├── README.md └── LICENSE /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [bhauman] 2 | -------------------------------------------------------------------------------- /rebel-readline-cljs/dev/user.clj: -------------------------------------------------------------------------------- 1 | (ns user) 2 | -------------------------------------------------------------------------------- /rebel-readline-paredit/doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to rebel-readline-paredit 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/what-to-write/) 4 | -------------------------------------------------------------------------------- /rebel-readline-cljs/doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to rebel-readline-cljs-service 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/what-to-write/) 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # This is not the CHANGELOG :) 2 | 3 | See the main [CHANGELOG](./rebel-readline/CHANGELOG.md) at `[rebel-readline/CHANGELOG.md](rebel-readline/CHANGELOG.md)` 4 | -------------------------------------------------------------------------------- /rebel-readline/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | .cpcache 13 | scratch.clj 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /rebel-readline-nrepl/src/rebel_readline/nrepl.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-readline.nrepl 2 | "This is the interface for clojure tools" 3 | (:require [rebel-readline.nrepl.main :as main])) 4 | 5 | (defn connect [config] 6 | (main/start-repl (or config {}))) 7 | -------------------------------------------------------------------------------- /rebel-readline-paredit/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | 13 | \#* 14 | \#*\# 15 | .\#* 16 | debug-log 17 | nashorn_code_cache -------------------------------------------------------------------------------- /rebel-readline-paredit/test/rebel_readline_paredit/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-readline-paredit.core-test 2 | (:require [clojure.test :refer :all] 3 | [rebel-readline-paredit.core :refer :all])) 4 | 5 | (deftest a-test 6 | (testing "FIXME, I fail." 7 | (is (= 0 1)))) 8 | -------------------------------------------------------------------------------- /rebel-readline-cljs/test/rebel_readline_cljs_service/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-readline-cljs-service.core-test 2 | (:require [clojure.test :refer :all] 3 | [rebel-readline-cljs-service.core :refer :all])) 4 | 5 | (deftest a-test 6 | (testing "FIXME, I fail." 7 | (is (= 0 1)))) 8 | -------------------------------------------------------------------------------- /rebel-readline-cljs/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | 13 | *~ 14 | .cpcache 15 | 16 | \#* 17 | \#*\# 18 | .\#* 19 | debug-log 20 | nashorn_code_cache 21 | .cljs_nashorn_repl -------------------------------------------------------------------------------- /rebel-readline-paredit/project.clj: -------------------------------------------------------------------------------- 1 | (defproject rebel-readline-paredit "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.8.0"]]) 7 | -------------------------------------------------------------------------------- /rebel-readline-nrepl/dev/user.clj: -------------------------------------------------------------------------------- 1 | (ns user) 2 | #_(require '[nrepl.server :refer [start-server stop-server]]) 3 | 4 | #_(println "Starting NREPL server at 7888") 5 | #_(defonce server (start-server :port 7888)) 6 | 7 | (add-tap (fn [x] (spit "DEBUG.log" (prn-str x) :append true))) 8 | 9 | (defn long-runner [] 10 | (prn "hey")) 11 | -------------------------------------------------------------------------------- /rebel-readline-paredit/README.md: -------------------------------------------------------------------------------- 1 | # rebel-readline-paredit 2 | 3 | A Clojure library designed to ... well, that part is up to you. 4 | 5 | ## Usage 6 | 7 | FIXME 8 | 9 | ## License 10 | 11 | Copyright © 2018 FIXME 12 | 13 | Distributed under the Eclipse Public License either version 1.0 or (at 14 | your option) any later version. 15 | -------------------------------------------------------------------------------- /rebel-readline-cljs/src/rebel_readline/cljs/main.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-readline.cljs.main 2 | (:require 3 | [cljs.repl.nashorn :as nash] 4 | [rebel-readline.cljs.repl :as cljs-repl] 5 | [rebel-readline.core :as core])) 6 | 7 | ;; TODO need ot bring this in line with cljs.main 8 | (defn -main [& args] 9 | (let [repl-env (nash/repl-env)] 10 | (cljs-repl/repl repl-env))) 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | .nrepl-port 10 | .hgignore 11 | .hg/ 12 | \#* 13 | \#*\# 14 | .\#* 15 | debug-log 16 | dev/scratch.clj 17 | notes 18 | *.bak.clj 19 | .cpcache 20 | .rebel_readline_history 21 | *~ 22 | 23 | rebel-readline-cljs/rebel-readline-debug-log 24 | 25 | rebel-readline/rebel-readline-debug-log 26 | 27 | rebel-readline/.rebel_readline_history 28 | 29 | rebel-readline-cljs/.rebel_readline_history 30 | 31 | *.log 32 | -------------------------------------------------------------------------------- /rebel-readline/src/rebel_readline/tool.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-readline.tool 2 | (:require 3 | [clojure.spec.alpha :as s] 4 | [clojure.java.io :as io] 5 | [rebel-readline.tools :as tools] 6 | [rebel-readline.clojure.main :as main])) 7 | 8 | (defn repl [options] 9 | (try 10 | (main/repl* {:rebel-readline/config options}) 11 | (catch clojure.lang.ExceptionInfo e 12 | (let [{:keys [spec config] :as err} (ex-data e)] 13 | (when (-> err :type (= :rebel-readline/config-spec-error)) 14 | (tools/explain-config spec config)))))) 15 | -------------------------------------------------------------------------------- /rebel-readline/dev/user.clj: -------------------------------------------------------------------------------- 1 | (ns user) 2 | 3 | (defn test-bound-output [] 4 | (.start 5 | (Thread. 6 | (bound-fn [] 7 | (dotimes [n 10] 8 | (Thread/sleep 2000) 9 | (println "Testing Bound!!")))))) 10 | 11 | (defn test-output [] 12 | (.start 13 | (Thread. 14 | (fn [] 15 | (dotimes [n 10] 16 | (Thread/sleep 2000) 17 | (println "Testing!!")))))) 18 | 19 | (defn complex [depth] 20 | (if (zero? depth) 21 | depth 22 | (list (complex (dec depth)) (complex (dec depth))))) 23 | 24 | #_(complex 10) 25 | -------------------------------------------------------------------------------- /rebel-readline-cljs/deps.edn: -------------------------------------------------------------------------------- 1 | {:deps {com.bhauman/rebel-readline {:mvn/version "0.1.5"} 2 | org.clojure/clojurescript {:mvn/version "1.10.758"} 3 | cljs-tooling/cljs-tooling {:mvn/version "0.3.1"}} 4 | :aliases {:neil {:project {:name com.bhauman/rebel-readline-cljs 5 | :version "0.1.5"}} 6 | :build {:deps {io.github.clojure/tools.build {:git/tag "v0.10.6" 7 | :git/sha "52cf7d6"} 8 | slipset/deps-deploy {:mvn/version "0.2.2"}} 9 | :ns-default build}}} 10 | -------------------------------------------------------------------------------- /rebel-readline-nrepl/src/rebel_readline/nrepl/service/commands.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-readline.nrepl.service.commands 2 | (:require [rebel-readline.commands :refer [command command-doc]] 3 | [rebel-readline.jline-api :as api])) 4 | 5 | (defmethod command-doc :repl/toggle-background-print [_] 6 | "Toggle wether to continue the printing the output from backgrounded threads") 7 | 8 | (defmethod command :repl/toggle-background-print [_] 9 | (swap! api/*line-reader* update :background-print #(not %)) 10 | (if (:background-print @api/*line-reader*) 11 | (println "Background printing on!") 12 | (println "Background printing off!"))) 13 | -------------------------------------------------------------------------------- /rebel-readline/src/rebel_readline/clojure/service/simple.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-readline.clojure.service.simple 2 | (:require 3 | [rebel-readline.clojure.line-reader :as lr] 4 | [rebel-readline.tools :as tools])) 5 | 6 | (defn create 7 | "A very simple service that you can use to get rebel readline 8 | working without any introspecting functionality (doc, source, appropos, 9 | completion, eval). 10 | 11 | It's best overide the :prompt option with a fn returns a proper 12 | prompt with the current namespace." 13 | ([] (create nil)) 14 | ([options] 15 | (merge 16 | {:prompt (fn [] (println "clj=> "))} 17 | lr/default-config 18 | (tools/user-config) 19 | options))) 20 | -------------------------------------------------------------------------------- /rebel-readline-paredit/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). 3 | 4 | ## [Unreleased] 5 | ### Changed 6 | - Add a new arity to `make-widget-async` to provide a different widget shape. 7 | 8 | ## [0.1.1] - 2018-02-05 9 | ### Changed 10 | - Documentation on how to make the widgets. 11 | 12 | ### Removed 13 | - `make-widget-sync` - we're all async, all the time. 14 | 15 | ### Fixed 16 | - Fixed widget maker to keep working when daylight savings switches over. 17 | 18 | ## 0.1.0 - 2018-02-05 19 | ### Added 20 | - Files from the new template. 21 | - Widget maker public API - `make-widget-sync`. 22 | 23 | [Unreleased]: https://github.com/your-name/rebel-readline-paredit/compare/0.1.1...HEAD 24 | [0.1.1]: https://github.com/your-name/rebel-readline-paredit/compare/0.1.0...0.1.1 25 | -------------------------------------------------------------------------------- /rebel-readline-cljs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). 3 | 4 | ## [Unreleased] 5 | ### Changed 6 | - Add a new arity to `make-widget-async` to provide a different widget shape. 7 | 8 | ## [0.1.1] - 2018-02-05 9 | ### Changed 10 | - Documentation on how to make the widgets. 11 | 12 | ### Removed 13 | - `make-widget-sync` - we're all async, all the time. 14 | 15 | ### Fixed 16 | - Fixed widget maker to keep working when daylight savings switches over. 17 | 18 | ## 0.1.0 - 2018-02-05 19 | ### Added 20 | - Files from the new template. 21 | - Widget maker public API - `make-widget-sync`. 22 | 23 | [Unreleased]: https://github.com/your-name/rebel-readline-cljs-service/compare/0.1.1...HEAD 24 | [0.1.1]: https://github.com/your-name/rebel-readline-cljs-service/compare/0.1.0...0.1.1 25 | -------------------------------------------------------------------------------- /rebel-readline/src/rebel_readline/clojure/utils.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-readline.clojure.utils 2 | (:require 3 | [clojure.string :as string])) 4 | 5 | ;; taken from cljs-api-gen.encode 6 | (def cljs-api-encoding 7 | {"." "DOT" 8 | ">" "GT" 9 | "<" "LT" 10 | "!" "BANG" 11 | "?" "QMARK" 12 | "*" "STAR" 13 | "+" "PLUS" 14 | "=" "EQ" 15 | "/" "SLASH"}) 16 | 17 | ;; taken from cljs-api-gen.encode 18 | (defn cljs-api-encode-name [name-] 19 | (reduce (fn [s [a b]] (string/replace s a b)) 20 | (name name-) cljs-api-encoding)) 21 | 22 | (defn url-for [ns name] 23 | (cond 24 | (.startsWith (str ns) "clojure.") 25 | (cond-> "https://clojuredocs.org/" 26 | ns (str ns) 27 | name (str "/" (string/replace name #"\?$" "_q"))) 28 | (.startsWith (str ns) "cljs.") 29 | (cond-> "http://cljs.github.io/api/" 30 | ns (str ns) 31 | name (str "/" (cljs-api-encode-name name))) 32 | :else nil)) 33 | -------------------------------------------------------------------------------- /rebel-readline/deps.edn: -------------------------------------------------------------------------------- 1 | {:deps {org.clojure/clojure {:mvn/version "1.10.0" } 2 | org.jline/jline-reader {:mvn/version "3.30.0"} 3 | org.jline/jline-terminal {:mvn/version "3.30.0"} 4 | org.jline/jline-terminal-jni {:mvn/version "3.30.0"} 5 | dev.weavejester/cljfmt {:mvn/version "0.13.0"} 6 | compliment/compliment {:mvn/version "0.6.0"}} 7 | :tools/usage {:ns-default rebel-readline.tool} 8 | :aliases {:repl-tool {:exec-fn rebel-readline.tool/repl} 9 | :rebel {:main-opts ["-m" "rebel-readline.main"]} 10 | :dev {:extra-paths ["dev"] 11 | :main-opts ["-m" "rebel-dev.main"]} 12 | ;; build 13 | :neil {:project {:name com.bhauman/rebel-readline 14 | :version "0.1.6-SNAPSHOT"}} 15 | :build {:deps {io.github.clojure/tools.build {:git/tag "v0.10.6" 16 | :git/sha "52cf7d6"} 17 | slipset/deps-deploy {:mvn/version "0.2.2"}} 18 | :ns-default build}}} 19 | -------------------------------------------------------------------------------- /rebel-readline-nrepl/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src"] 2 | :deps {com.bhauman/rebel-readline {:mvn/version "0.1.5"} 3 | ;; DEV for cider 4 | ;;com.bhauman/rebel-readline {:local/root "../rebel-readline"} 5 | nrepl/nrepl {:mvn/version "1.3.1"} 6 | org.clojure/tools.cli {:mvn/version "1.1.230"}} 7 | :tools/usage {:ns-default rebel-readline.nrepl} 8 | :aliases 9 | {:rebel {:main-opts ["-m" "rebel-readline.main"]} 10 | :tlsnrepl {:main-opts ["-m" "nrepl.cmdline" "--port" 50670 "--tls-keys-file" "./server.keys"] 11 | :jvm-opts ["-Djdk.attach.allowAttachSelf"]} 12 | :nrepl {:main-opts ["-m" "nrepl.cmdline" "--port" 50668] 13 | :jvm-opts ["-Djdk.attach.allowAttachSelf"]} 14 | :dev {:override-deps {com.bhauman/rebel-readline {:local/root "../rebel-readline"}} 15 | :exec-fn rebel-readline.nrepl/connect 16 | :exec-args {:port 50668}} 17 | :neil {:project {:name com.bhauman/rebel-readline-nrepl 18 | :version "0.1.6"}} 19 | :build {:deps {io.github.clojure/tools.build {:git/tag "v0.10.6" 20 | :git/sha "52cf7d6"} 21 | slipset/deps-deploy {:mvn/version "0.2.2"}} 22 | :ns-default build}}} 23 | -------------------------------------------------------------------------------- /rebel-readline/dev/rebel_dev/main.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-dev.main 2 | (:require 3 | [rebel-readline.core :as core] 4 | [rebel-readline.clojure.main :as main] 5 | [rebel-readline.jline-api :as api] 6 | [rebel-readline.clojure.line-reader :as clj-line-reader] 7 | [rebel-readline.clojure.service.local :as clj-service] 8 | [rebel-readline.clojure.service.simple :as simple-service] 9 | [rebel-readline.utils :refer [*debug-log*]] 10 | [clojure.main])) 11 | 12 | ;; TODO refactor this like the cljs dev repl with a "stream" and "one-line" options 13 | (defn -main [& args] 14 | (println "This is the DEVELOPMENT REPL in rebel-dev.main") 15 | (binding [*debug-log* true] 16 | (core/with-line-reader 17 | (clj-line-reader/create 18 | (clj-service/create 19 | #_{:key-map :viins})) 20 | (println (core/help-message)) 21 | #_((clj-repl-read) (Object.) (Object.)) 22 | (clojure.main/repl 23 | :prompt (fn []) 24 | :print main/syntax-highlight-prn 25 | :read (main/create-repl-read))))) 26 | 27 | #_(defn -main [& args] 28 | (let [repl-env (nash/repl-env)] 29 | (with-readline-input-stream (cljs-service/create {:repl-env repl-env}) 30 | (cljs-repl/repl repl-env :prompt (fn []))))) 31 | -------------------------------------------------------------------------------- /rebel-readline-cljs/dev/rebel_cljs_dev/main.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-cljs-dev.main 2 | (:require 3 | [cljs.repl] 4 | [cljs.repl.nashorn :as nash] 5 | [rebel-readline.cljs.repl :as cljs-repl] 6 | [rebel-readline.cljs.service.local :as cljs-service] 7 | [rebel-readline.clojure.line-reader :as clj-line-reader] 8 | [rebel-readline.core :as rebel] 9 | [rebel-readline.utils :as utils] 10 | [rebel-readline.jline-api :as api])) 11 | 12 | (defn -main [& args] 13 | (let [repl-env (nash/repl-env) 14 | service (cljs-service/create {:repl-env repl-env})] 15 | (println "This is the DEVELOPMENT REPL in rebel-cljs-dev.main") 16 | (println (rebel/help-message)) 17 | (binding [utils/*debug-log* true] 18 | (if (= "stream" (first args)) 19 | (do 20 | (println "[[Input stream line reader]]") 21 | (rebel/with-readline-in 22 | (clj-line-reader/create service) 23 | (cljs.repl/repl 24 | repl-env 25 | :prompt (fn []) 26 | :print cljs-repl/syntax-highlight-println))) 27 | (rebel/with-line-reader 28 | (clj-line-reader/create service) 29 | (cljs.repl/repl 30 | repl-env 31 | :prompt (fn []) 32 | :print cljs-repl/syntax-highlight-println 33 | :read (cljs-repl/create-repl-read))))))) 34 | -------------------------------------------------------------------------------- /rebel-readline/src/rebel_readline/utils.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-readline.utils 2 | (:require 3 | [clojure.java.io :as io] 4 | [clojure.string :as string] 5 | [clojure.pprint :as pp])) 6 | 7 | (def ^:dynamic *debug-log* false) 8 | 9 | (defn log [& args] 10 | (when *debug-log* 11 | (spit "rebel-readline-debug-log" 12 | (string/join "" 13 | (map #(if (string? %) 14 | (str % "\n") 15 | (with-out-str (pp/pprint %))) 16 | args)) 17 | :append true)) 18 | (last args)) 19 | 20 | (defn terminal-background-color? [] 21 | (or (when-let [fgbg (System/getenv "COLORFGBG")] 22 | (when-let [[fg bg] (try (mapv #(Integer/parseInt (string/trim %)) 23 | (string/split fgbg #";")) 24 | (catch Throwable t nil))] 25 | (when (and fg bg) 26 | (if (< -1 fg bg 16) 27 | :light 28 | :dark)))) 29 | (when-let [term-program (System/getenv "TERM_PROGRAM")] 30 | (let [tp (string/lower-case term-program)] 31 | (cond 32 | (.startsWith tp "iterm") :dark 33 | (.startsWith tp "apple") :light 34 | :else nil))) 35 | :dark)) 36 | 37 | (defn require-resolve-var [var-sym] 38 | (when (symbol? var-sym) 39 | (or (resolve var-sym) 40 | (when-let [ns (namespace var-sym)] 41 | (when (try (require (symbol ns)) true (catch Throwable t false)) 42 | (when-let [var (resolve var-sym)] 43 | var)))))) 44 | -------------------------------------------------------------------------------- /rebel-readline/src/rebel_readline/io/callback_reader.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-readline.io.callback-reader 2 | (:import 3 | [java.nio CharBuffer])) 4 | 5 | (defn fill-buffer [closed-atom buf-atom read-callback] 6 | (when (and (not @closed-atom) (not (.hasRemaining @buf-atom))) 7 | (if-let [res (read-callback)] 8 | (reset! buf-atom (CharBuffer/wrap res)) 9 | (vreset! closed-atom true)))) 10 | 11 | (defn callback-reader [read-callback] 12 | (let [buf (atom (CharBuffer/wrap "")) 13 | closed (volatile! false)] 14 | (proxy [java.io.Reader] [] 15 | (read 16 | ([] 17 | (fill-buffer closed buf read-callback) 18 | (if @closed 19 | -1 20 | (let [c (.get @buf)] 21 | (if (integer? c) c (int c))))) 22 | ([^chars out-array] 23 | (fill-buffer closed buf read-callback) 24 | (if @closed 25 | -1 26 | (let [buflen (.length @buf) 27 | len (min (alength out-array) buflen)] 28 | (.get @buf out-array 0 len) 29 | len))) 30 | ([^chars out-array off maxlen] 31 | (fill-buffer closed buf read-callback) 32 | (if @closed 33 | -1 34 | (let [buflen (.length @buf) 35 | len (min buflen maxlen)] 36 | (.get @buf out-array off len) 37 | len)))) 38 | (close [] 39 | (vreset! closed true))))) 40 | 41 | 42 | (comment 43 | (with-in-str "asdf1\nasdf2\nasdf33333" 44 | (let [wr (callback-reader read-line) 45 | ca (char-array 30)] 46 | (.read wr ca) 47 | (.read wr ca) 48 | (.read wr ca) 49 | (.read wr ca) 50 | (into [] ca) 51 | )) 52 | 53 | ) 54 | -------------------------------------------------------------------------------- /rebel-readline/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.5 Introducing nREPL support 2 | 3 | The `rebel-readline-nrepl` package offers nREPL support. 4 | 5 | For detailed information, please refer to the `../rebel-readline-nrepl/README.md`. This library is separate (`[com.bhauman/rebel-readline-nrepl "0.1.5"]`) to keep startup time for `rebel-readline` lean. 6 | 7 | Support for a command-line interface (CLI) is now available through both `rebel-readline.main` and `rebel-readline.tool` entry points, along with the ability to specify a configuration file. 8 | 9 | Other updates include: 10 | * b11eaf8: Introduction of a :neutral-screen-theme 11 | * 9773dab: Enhanced lightweight Ctrl-C interrupt handling 12 | * Fixed issue #219: Removed lazy loading of packages 13 | 14 | # 0.1.4 Removed regex stack overflow for large strings 15 | 16 | It is very confusing to get an error from the value printer, so this merits a release. 17 | 18 | * https://github.com/bhauman/rebel-readline/issues/161 19 | 20 | # 0.1.3 Improved Completion and Faster startup 21 | 22 | Rebel Readline loads 2x faster and Completion now narrows choices as you type. 23 | 24 | * thanks to Alexander Yakushev for demonstrating how make startup faster 25 | * thanks to Michał Buczko for greatly improving how completion works 26 | 27 | # 0.1.2 Vi improvements and key binding configuration 28 | 29 | * Fixed PrintWriter->on name collision warning for Clojure 1.9 30 | * Added additional vi like bindings for clojure actions to :vi-cmd key map 31 | * make readline history file project local 32 | * make it easier to alter-var-root core repls to rebel-readline repls 33 | * fix blow up when COLORFGBG isn't formatted correctly 34 | * fix repl key-map swtiching feedback 35 | * add a clojure-force-accept-line widget that submits a line regardless 36 | * allow key binding configuration in the rebel_readline.edn config file 37 | 38 | # 0.1.1 Initial release 39 | -------------------------------------------------------------------------------- /rebel-readline/src/rebel_readline/jline_api/attributed_string.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-readline.jline-api.attributed-string 2 | (:refer-clojure :exclude [partition-all]) 3 | (:import 4 | [org.jline.utils AttributedStringBuilder AttributedString AttributedStyle])) 5 | 6 | (defn astr 7 | [& args] 8 | (AttributedString. 9 | (reduce #(if %2 10 | (if (vector? %2) 11 | (.styled %1 (second %2) (first %2)) 12 | (.append %1 %2)) 13 | %1) 14 | (AttributedStringBuilder.) args))) 15 | 16 | (defn split [^AttributedString at-str regex] 17 | (let [s (str at-str) 18 | m (.matcher #"\n" s)] 19 | (->> (repeatedly #(when (.find m) 20 | [(.start m) (.end m)])) 21 | (take-while some?) 22 | flatten 23 | (cons 0) 24 | (clojure.core/partition-all 2) 25 | (mapv 26 | #(.substring at-str (first %) (or (second %) (count s))))))) 27 | 28 | (defn partition-all [length ^AttributedString at-str] 29 | (mapv first 30 | (take-while some? 31 | (rest 32 | (iterate (fn [[_ at-str]] 33 | (when at-str 34 | (if (<= (count at-str) length) 35 | [at-str nil] 36 | [(.substring at-str 0 (min length (count at-str))) 37 | (.substring at-str length (count at-str))]))) 38 | [nil at-str]))))) 39 | 40 | (defn split-lines [^AttributedString at-str] 41 | (split at-str #"\r?\n")) 42 | 43 | (defn join [sep coll] 44 | (apply astr (interpose sep coll))) 45 | 46 | (defn ->ansi [at-str terminal] 47 | (.toAnsi (.toAttributedString at-str) terminal)) 48 | 49 | (defn ->ansi-256 [at-str] 50 | (.toAnsi (.toAttributedString at-str) 256 true)) 51 | -------------------------------------------------------------------------------- /rebel-readline-cljs/README.md: -------------------------------------------------------------------------------- 1 | # rebel-readline-cljs 2 | 3 | [![Clojars Project](https://img.shields.io/clojars/v/com.bhauman/rebel-readline-cljs.svg)](https://clojars.org/com.bhauman/rebel-readline-cljs) 4 | 5 | A library that supplies a rebel readline service for the default 6 | clojurescript repl and some helpers to create CLJS repls with 7 | rebel-readline. 8 | 9 | ## Quick try 10 | 11 | #### Clojure tools 12 | 13 | If you want to try this really quickly [install the Clojure CLI tools](https://clojure.org/guides/getting_started) and then invoke this: 14 | 15 | ```shell 16 | clojure -Sdeps '{:deps {com.bhauman/rebel-readline-cljs {:mvn/version "0.1.4"}}}' -m rebel-readline.cljs.main 17 | ``` 18 | 19 | That should start a Nashorn ClojureScript REPL that takes it's input 20 | from the Rebel readline editor. 21 | 22 | Note that I am using the `clojure` command and not the `clj` command 23 | because the latter wraps the process with another readline program (`rlwrap`). 24 | 25 | #### Leiningen 26 | 27 | Add `[com.bhauman/rebel-readline-cljs "0.1.4"]` to the dependencies in your 28 | `project.clj` then start a REPL like this: 29 | 30 | ```shell 31 | lein trampoline run -m rebel-readline.cljs.main 32 | ``` 33 | 34 | #### Clone this repo 35 | 36 | Clone this repo and then from the `rebel-readline-cljs` sub-directory 37 | typing `lein trampoline run -m rebel-readline.cljs.main` will get you into 38 | a Clojure REPL with the readline editor working. 39 | 40 | Note that `lein run -m rebel-readline.cljs.main` will not work! 41 | 42 | ## Usage 43 | 44 | A simple usage example: 45 | 46 | ```clojure 47 | (rebel-readline.core/with-line-reader 48 | (rebel-readline.clojure.core/create 49 | (rebel-readline.cljs.service.local/create)) 50 | (cljs.repl/repl 51 | :prompt (fn []) ;; prompt is handled by line-reader 52 | :read (rebel-readline.cljs.repl/create-repl-read))) 53 | ``` 54 | 55 | ## License 56 | 57 | Copyright © 2018 Bruce Hauman 58 | 59 | Distributed under the Eclipse Public License either version 1.0 or (at 60 | your option) any later version. 61 | -------------------------------------------------------------------------------- /rebel-readline/test/rebel_readline/clojure/line_reader_test.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-readline.clojure.line-reader-test 2 | (:require 3 | [rebel-readline.clojure.line-reader :as core :refer [indent-proxy-str]] 4 | [clojure.test :refer [deftest is are testing]])) 5 | 6 | #_ (remove-ns 'rebel-readline.clojure.line-reader-test) 7 | 8 | (deftest default-accept-line-test 9 | (is (core/default-accept-line "()" 0)) 10 | (is (not (core/default-accept-line "()" 1))) 11 | (is (core/default-accept-line "()" 2)) 12 | (is (not (core/default-accept-line "()(" 3))) 13 | 14 | (is (core/default-accept-line " \"2345" 1)) 15 | (is (not (core/default-accept-line " \"2345" 2))) 16 | (is (not (core/default-accept-line " \"2345" 5))) 17 | (is (not (core/default-accept-line " \"2345" 6))) 18 | 19 | (is (not (core/default-accept-line " \"2345\"" 2))) 20 | (is (not (core/default-accept-line " \"2345\"" 5))) 21 | (is (not (core/default-accept-line " \"2345\"" 6))) 22 | (is (core/default-accept-line " \"2345\"78" 7)) 23 | (is (core/default-accept-line " \"2345\"78" 8)) 24 | 25 | ;; don't accept a line if there is an imcomplete form at the end 26 | ;; TODO not sure about this behavior 27 | #_(is (not (core/default-accept-line "()(" 2))) 28 | 29 | ) 30 | 31 | 32 | (deftest indent-proxy-str-test 33 | (let [tstr "(let [x 1] mark11 (let [y 2] mark29 (list 1 2 3 mark48"] 34 | (is (= "(let [x 1] \n1)" 35 | (indent-proxy-str tstr 11))) 36 | (is (= " (let [y 2] \n1)" 37 | (indent-proxy-str tstr 29))) 38 | (is (= " (list 1 2 3 \n1)" 39 | (indent-proxy-str tstr 48)))) 40 | 41 | (testing "correct bondaries" 42 | (is (= "(list \n1)" 43 | (indent-proxy-str "(list ()" 6))) 44 | (is (= " (\n1)" 45 | (indent-proxy-str "(list ()" 7))) 46 | (is (= "(list ()\n1)" 47 | (indent-proxy-str "(list () " 8)))) 48 | 49 | ;; don't indent strings 50 | (is (= "(list \n1)" 51 | (indent-proxy-str "(list \"hello\"" 6))) 52 | (is (not (indent-proxy-str "(list \"hello\"" 7))) 53 | (is (not (indent-proxy-str "(list \"hello\"" 12))) 54 | (is (indent-proxy-str "(list \"hello\"" 13))) 55 | -------------------------------------------------------------------------------- /rebel-readline/build.clj: -------------------------------------------------------------------------------- 1 | (ns build 2 | (:require [clojure.tools.build.api :as b] 3 | [clojure.edn :as edn])) 4 | 5 | (def project (-> (edn/read-string (slurp "deps.edn")) 6 | :aliases :neil :project)) 7 | (def lib (:name project)) 8 | 9 | ;; use neil project set version 1.2.0 to update the version in deps.edn 10 | 11 | (def version (:version project)) 12 | (def class-dir "target/classes") 13 | (def basis (b/create-basis {:project "deps.edn"})) 14 | (def uber-file (format "target/%s-%s-standalone.jar" (name lib) version)) 15 | (def jar-file (format "target/%s-%s.jar" (name lib) version)) 16 | 17 | (defn clean [_] 18 | (b/delete {:path "target"})) 19 | 20 | (defn jar [_] 21 | (b/write-pom {:class-dir class-dir 22 | :lib lib 23 | :version version 24 | :basis basis 25 | :src-dirs ["src"] 26 | :pom-data [[:licenses 27 | [:license 28 | [:name "Eclipse Public License 1.0"] 29 | [:url "https://opensource.org/license/epl-1-0/"] 30 | [:distribution "repo"]]]]}) 31 | (b/copy-dir {:src-dirs ["src" "resources"] 32 | :target-dir class-dir}) 33 | (b/jar {:class-dir class-dir 34 | :jar-file jar-file})) 35 | 36 | (defn install [_] 37 | (jar {}) 38 | (b/install {:basis basis 39 | :lib lib 40 | :version version 41 | :jar-file jar-file 42 | :class-dir class-dir})) 43 | 44 | (defn uber [_] 45 | (clean nil) 46 | (b/copy-dir {:src-dirs ["src" "resources"] 47 | :target-dir class-dir}) 48 | (b/compile-clj {:basis basis 49 | :src-dirs ["src"] 50 | :class-dir class-dir}) 51 | (b/uber {:class-dir class-dir 52 | :uber-file uber-file 53 | :basis basis})) 54 | 55 | (defn deploy [opts] 56 | (jar opts) 57 | ((requiring-resolve 'deps-deploy.deps-deploy/deploy) 58 | (merge {:installer :remote 59 | :artifact jar-file 60 | :pom-file (b/pom-path {:lib lib :class-dir class-dir})} 61 | opts)) 62 | opts) 63 | 64 | -------------------------------------------------------------------------------- /rebel-readline-nrepl/build.clj: -------------------------------------------------------------------------------- 1 | (ns build 2 | (:require [clojure.tools.build.api :as b] 3 | [clojure.edn :as edn])) 4 | 5 | (def project (-> (edn/read-string (slurp "deps.edn")) 6 | :aliases :neil :project)) 7 | (def lib (or (:name project) 'my/lib1)) 8 | 9 | ;; use neil project set version 1.2.0 to update the version in deps.edn 10 | 11 | (def version (:version project)) 12 | (def class-dir "target/classes") 13 | (def basis (b/create-basis {:project "deps.edn"})) 14 | (def uber-file (format "target/%s-%s-standalone.jar" (name lib) version)) 15 | (def jar-file (format "target/%s-%s.jar" (name lib) version)) 16 | 17 | (defn clean [_] 18 | (b/delete {:path "target"})) 19 | 20 | (defn jar [_] 21 | (b/write-pom {:class-dir class-dir 22 | :lib lib 23 | :version version 24 | :basis basis 25 | :src-dirs ["src"] 26 | :pom-data [[:licenses 27 | [:license 28 | [:name "Eclipse Public License 1.0"] 29 | [:url "https://opensource.org/license/epl-1-0/"] 30 | [:distribution "repo"]]]]}) 31 | (b/copy-dir {:src-dirs ["src" "resources"] 32 | :target-dir class-dir}) 33 | (b/jar {:class-dir class-dir 34 | :jar-file jar-file})) 35 | 36 | (defn install [_] 37 | (jar {}) 38 | (b/install {:basis basis 39 | :lib lib 40 | :version version 41 | :jar-file jar-file 42 | :class-dir class-dir})) 43 | 44 | (defn uber [_] 45 | (clean nil) 46 | (b/copy-dir {:src-dirs ["src" "resources"] 47 | :target-dir class-dir}) 48 | (b/compile-clj {:basis basis 49 | :src-dirs ["src"] 50 | :class-dir class-dir}) 51 | (b/uber {:class-dir class-dir 52 | :uber-file uber-file 53 | :basis basis})) 54 | 55 | ;;; clojure -T:build deploy 56 | 57 | (defn deploy [opts] 58 | (jar opts) 59 | ((requiring-resolve 'deps-deploy.deps-deploy/deploy) 60 | (merge {:installer :remote 61 | :artifact jar-file 62 | :pom-file (b/pom-path {:lib lib :class-dir class-dir})} 63 | opts)) 64 | opts) 65 | 66 | -------------------------------------------------------------------------------- /rebel-readline-cljs/build.clj: -------------------------------------------------------------------------------- 1 | (ns build 2 | (:require [clojure.tools.build.api :as b] 3 | [clojure.edn :as edn])) 4 | 5 | (def project (-> (edn/read-string (slurp "deps.edn")) 6 | :aliases :neil :project)) 7 | (def lib (or (:name project) 'my/lib1)) 8 | 9 | ;; use neil project set version 1.2.0 to update the version in deps.edn 10 | 11 | (def version (or (:version project) 12 | "1.2.0")) 13 | (def class-dir "target/classes") 14 | (def basis (b/create-basis {:project "deps.edn"})) 15 | (def uber-file (format "target/%s-%s-standalone.jar" (name lib) version)) 16 | (def jar-file (format "target/%s-%s.jar" (name lib) version)) 17 | 18 | (defn clean [_] 19 | (b/delete {:path "target"})) 20 | 21 | (defn jar [_] 22 | (b/write-pom {:class-dir class-dir 23 | :lib lib 24 | :version version 25 | :basis basis 26 | :src-dirs ["src"] 27 | :pom-data [[:licenses 28 | [:license 29 | [:name "Eclipse Public License 1.0"] 30 | [:url "https://opensource.org/license/epl-1-0/"] 31 | [:distribution "repo"]]]]}) 32 | (b/copy-dir {:src-dirs ["src" "resources"] 33 | :target-dir class-dir}) 34 | (b/jar {:class-dir class-dir 35 | :jar-file jar-file})) 36 | 37 | (defn install [_] 38 | (jar {}) 39 | (b/install {:basis basis 40 | :lib lib 41 | :version version 42 | :jar-file jar-file 43 | :class-dir class-dir})) 44 | 45 | (defn uber [_] 46 | (clean nil) 47 | (b/copy-dir {:src-dirs ["src" "resources"] 48 | :target-dir class-dir}) 49 | (b/compile-clj {:basis basis 50 | :src-dirs ["src"] 51 | :class-dir class-dir}) 52 | (b/uber {:class-dir class-dir 53 | :uber-file uber-file 54 | :basis basis})) 55 | 56 | (defn deploy [opts] 57 | (jar opts) 58 | ((requiring-resolve 'deps-deploy.deps-deploy/deploy) 59 | (merge {:installer :remote 60 | :artifact jar-file 61 | :pom-file (b/pom-path {:lib lib :class-dir class-dir})} 62 | opts)) 63 | opts) 64 | 65 | -------------------------------------------------------------------------------- /rebel-readline-cljs/src/rebel_readline/cljs/repl.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-readline.cljs.repl 2 | (:require 3 | [cljs.repl] 4 | [clojure.tools.reader :as r] 5 | [clojure.tools.reader.reader-types :as rtypes] 6 | [rebel-readline.core :as rebel] 7 | [rebel-readline.clojure.line-reader :as clj-line-reader] 8 | [rebel-readline.cljs.service.local :as cljs-service] 9 | [rebel-readline.jline-api :as api]) 10 | (:import 11 | [org.jline.utils OSUtils])) 12 | 13 | (defn has-remaining? 14 | "Takes a clojure.tools.reader.reader-types/SourceLoggingPushbackReader 15 | and returns true if there is another character in the stream. 16 | i.e not the end of the readers stream." 17 | [pbr] 18 | (boolean 19 | (when-let [x (rtypes/read-char pbr)] 20 | (rtypes/unread pbr x) 21 | true))) 22 | 23 | (def create-repl-read 24 | "Creates a drop in replacement for cljs.repl/repl-read, since a 25 | readline can return multiple Clojure forms this function is stateful 26 | and buffers the forms and returns the next form on subsequent reads. 27 | 28 | This function is a constructor that takes a line-reader and returns 29 | a function that can replace `cljs.repl/repl-read`. 30 | 31 | Example Usage: 32 | 33 | (let [repl-env (nash/repl-env)] 34 | (cljs-repl/repl 35 | repl-env 36 | :prompt (fn []) 37 | :read (cljs-repl-read 38 | (rebel-readline.core/line-reader 39 | (rebel-readline-cljs.service/create {:repl-env repl-env}))])))" 40 | (rebel/create-buffered-repl-reader-fn 41 | (fn [s] (rtypes/source-logging-push-back-reader 42 | (java.io.StringReader. s))) 43 | has-remaining? 44 | cljs.repl/repl-read)) 45 | 46 | (defn syntax-highlight-println 47 | "Print a syntax highlighted clojure value. 48 | 49 | This printer respects the current color settings set in the 50 | service. 51 | 52 | The `rebel-readline.jline-api/*line-reader*` and 53 | `rebel-readline.jline-api/*service*` dynamic vars have to be set for 54 | this to work. 55 | 56 | See `rebel-readline-cljs.main` for an example of how this function is normally used" 57 | [x] 58 | (binding [*out* (.. api/*line-reader* getTerminal writer)] 59 | (try 60 | (println (api/->ansi (clj-line-reader/highlight-clj-str (or x "")))) 61 | (catch java.lang.StackOverflowError e 62 | (println (or x "")))))) 63 | 64 | ;; enable evil alter-var-root 65 | (let [cljs-repl* cljs.repl/repl*] 66 | (defn repl* [repl-env opts] 67 | (rebel/with-line-reader 68 | (clj-line-reader/create 69 | (cljs-service/create (assoc 70 | (when api/*line-reader* 71 | @api/*line-reader*) 72 | :repl-env repl-env))) 73 | (when-let [prompt-fn (:prompt opts)] 74 | (swap! api/*line-reader* assoc :prompt prompt-fn)) 75 | (println (rebel/help-message)) 76 | (binding [*out* (api/safe-terminal-writer api/*line-reader*)] 77 | (cljs-repl* 78 | repl-env 79 | (merge 80 | {:print syntax-highlight-println 81 | :read (create-repl-read)} 82 | opts 83 | {:prompt (fn [])})))))) 84 | 85 | (defn repl [repl-env & opts] 86 | (repl* repl-env (apply hash-map opts))) 87 | -------------------------------------------------------------------------------- /rebel-readline-nrepl/dev/rebel_nrepl/main.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-nrepl.main 2 | (:require 3 | [rebel-readline.core :as core] 4 | [rebel-readline.clojure.main :as main] 5 | [rebel-readline.jline-api :as api] 6 | [rebel-readline.clojure.line-reader :as clj-line-reader] 7 | ;[rebel-readline.clojure.service.local :as clj-service] 8 | [rebel-readline.nrepl.service.nrepl :as clj-service] 9 | ;[rebel-readline.clojure.service.simple :as simple-service] 10 | [rebel-readline.utils :refer [*debug-log*]] 11 | [clojure.main] 12 | [clojure.repl]) 13 | (:import (clojure.lang LispReader$ReaderException) 14 | [org.jline.terminal Terminal Terminal$SignalHandler Terminal$Signal])) 15 | 16 | (defn read-eval-print-fn [{:keys [read printer request-prompt request-exit]}] 17 | (fn [] 18 | (try 19 | (let [input (read request-prompt request-exit)] 20 | (if (#{request-prompt request-exit} input) 21 | input 22 | (do 23 | (api/toggle-input api/*terminal* false) 24 | (clj-service/eval-code 25 | @api/*line-reader* 26 | input 27 | (bound-fn* 28 | (->> identity 29 | (clj-service/out-err 30 | #(do (print %) (flush)) 31 | #(do (print %) (flush))) 32 | (clj-service/value #(do (printer %) (flush)))))) 33 | (api/toggle-input api/*terminal* true)))) 34 | #_(catch Throwable e 35 | 36 | (clojure.main/repl-caught e))))) 37 | 38 | (defn repl-loop [] 39 | (let [request-prompt (Object.) 40 | request-exit (Object.) 41 | read-eval-print (read-eval-print-fn 42 | {:read core/repl-read-line 43 | :printer main/syntax-highlight-prn-unwrapped 44 | :request-prompt request-prompt 45 | :request-exit request-exit})] 46 | (try 47 | (clj-service/tool-eval-code 48 | @api/*line-reader* 49 | (pr-str `(do 50 | (require 'clojure.main) 51 | (require 'clojure.repl)))) 52 | #_(catch Throwable e 53 | (clojure.main (clojure.main/repl-caught e)))) 54 | (loop [] 55 | (when-not 56 | (try 57 | (identical? (read-eval-print) request-exit) 58 | #_(catch Throwable e 59 | (clojure.main/repl-caught e))) 60 | (recur))))) 61 | 62 | ;; TODO refactor this like the cljs dev repl with a "stream" and "one-line" options 63 | (defn -main [& args] 64 | (println "This is the DEVELOPMENT REPL in rebel-dev.main") 65 | (binding [*debug-log* true] 66 | (core/with-line-reader 67 | (clj-line-reader/create 68 | (clj-service/create 69 | (when api/*line-reader* @api/*line-reader*))) 70 | (binding [*out* (api/safe-terminal-writer api/*line-reader*)] 71 | (clj-service/start-polling @api/*line-reader*) 72 | (.handle ^Terminal api/*terminal* 73 | Terminal$Signal/INT 74 | (let [line-reader api/*line-reader*] 75 | (proxy [Terminal$SignalHandler] [] 76 | (handle [sig] 77 | (tap> "HANDLED") 78 | (clj-service/interrupt @line-reader) 79 | (tap> "AFTER INT"))))) 80 | (println (core/help-message)) 81 | (repl-loop))))) 82 | 83 | 84 | -------------------------------------------------------------------------------- /rebel-readline/src/rebel_readline/main.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-readline.main 2 | (:require 3 | [rebel-readline.clojure.main :as main] 4 | [rebel-readline.tools :as tools] 5 | [clojure.string :as string] 6 | [clojure.java.io :as io] 7 | [clojure.tools.cli :as cli])) 8 | 9 | (def cli-options 10 | [["-h" "--help" "View cli help docs"] 11 | ["-k" "--key-map KEYMAP" "Either :viins or :emacs" 12 | :default :emacs 13 | :default-desc ":emacs" 14 | :parse-fn keyword 15 | :validate [#{:viins :emacs} "Must be either :viins or :emacs"]] 16 | ["-t" "--color-theme THEME" "Color theme :(light, dark, or neutral)-screen-theme" 17 | :default :dark-screen-theme 18 | :default-desc ":dark-screen-theme" 19 | :parse-fn keyword 20 | :validate [#{:light-screen-theme :dark-screen-theme :neutral-screen-theme} 21 | "Must be one of :light-screen-theme, :dark-screen-theme, or :neutral-screen-theme"]] 22 | #_[nil "--highlight" "Syntax highlighting" 23 | :default true] 24 | [nil "--no-highlight" "Disable syntax highlighting" 25 | :id :highlight 26 | :update-fn (constantly false)] 27 | #_[nil "--completion" "Enable code completion" 28 | :default true] 29 | [nil "--no-completion" "Disable code completion" 30 | :id :completion 31 | :update-fn (constantly false)] 32 | #_[nil "--eldoc" "Display function docs" 33 | :default true] 34 | [nil "--no-eldoc" "Disable function doc display" 35 | :id :eldoc 36 | :update-fn (constantly false)] 37 | #_[nil "--indent" "Auto indent code" 38 | :default true] 39 | [nil "--no-indent" "Disable auto indent" 40 | :id :indent 41 | :update-fn (constantly false)] 42 | #_[nil "--redirect-output" "Redirect output" 43 | :default true] 44 | [nil "--no-redirect-output" "Disable output redirection" 45 | :id :redirect-output 46 | :update-fn (constantly false)] 47 | ["-b" "--key-bindings BINDINGS" "Key bindings map" 48 | :parse-fn read-string 49 | :validate [map? "Must be a map"]] 50 | ["-c" "--config CONFIG" "Path to a config file" 51 | :parse-fn (comp str tools/absolutize-file) 52 | :validate [#(.exists (io/file %)) "Must be a valid path to a readable file"]]]) 53 | 54 | (defn usage [options-summary] 55 | (->> ["rebel-readline local: An enhanced terminal REPL for Clojure" 56 | "" 57 | "This is a readline enhanced REPL. Start your Clojure REPL with this" 58 | "to get a better editing experience. See the full README and docs" 59 | "at https://github.com/bhauman/rebel-readline" 60 | "" 61 | "Usage: clojure -M -m rebel-readline.main [options]" 62 | "" 63 | "Options:" 64 | (string/join \newline 65 | ;; removing default true noise from cli 66 | (remove #(re-find #"\s\strue\s\s" %) 67 | (string/split-lines options-summary)))] 68 | (string/join \newline))) 69 | 70 | (cli/parse-opts ["--no-highlight"] cli-options :no-defaults true) 71 | 72 | (defn error-msg [errors] 73 | (str "The following errors occurred while parsing your command:\n\n" 74 | (string/join "\n" errors))) 75 | 76 | (defn exit [status msg] 77 | (println msg) 78 | (System/exit status)) 79 | 80 | (defn validate-args 81 | [args] 82 | (let [{:keys [options arguments errors summary]} 83 | (cli/parse-opts args cli-options :no-defaults true)] 84 | (cond 85 | (:help options) 86 | (exit 0 (usage summary)) 87 | errors 88 | (exit 1 (error-msg errors)) 89 | :else options))) 90 | 91 | (defn -main [& args] 92 | (let [options (validate-args args)] 93 | (main/main {:rebel-readline/config options}))) 94 | -------------------------------------------------------------------------------- /rebel-readline/src/rebel_readline/clojure/service/local.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-readline.clojure.service.local 2 | (:require 3 | [rebel-readline.clojure.line-reader :as clj-reader] 4 | [rebel-readline.clojure.utils :as clj-utils] 5 | [rebel-readline.tools :as tools] 6 | [rebel-readline.utils :as utils] 7 | [compliment.core :as compliment] 8 | [clojure.repl])) 9 | 10 | ;; taken from replicant 11 | ;; https://github.com/puredanger/replicant/blobcl/master/src/replicant/util.clj 12 | (defn data-eval 13 | [form] 14 | (let [out-writer (java.io.StringWriter.) 15 | err-writer (java.io.StringWriter.) 16 | capture-streams (fn [] 17 | (.flush *out*) 18 | (.flush *err*) 19 | {:out (.toString out-writer) 20 | :err (.toString err-writer)})] 21 | (binding [*out* (java.io.BufferedWriter. out-writer) 22 | *err* (java.io.BufferedWriter. err-writer)] 23 | (try 24 | (let [result (eval form)] 25 | ;; important to note that there could be lazy errors in this result 26 | 27 | ;; the strategy embraced by prepl is passing an out-fn 28 | ;; callback that handles formatting and message sending in 29 | ;; the scope of the try catch 30 | (merge (capture-streams) {:result result})) 31 | (catch Throwable t 32 | (merge (capture-streams) 33 | (with-meta 34 | {:exception (Throwable->map t)} 35 | {:ex t}))))))) 36 | 37 | (defn call-with-timeout [thunk timeout-ms] 38 | (let [prom (promise) 39 | thread (Thread. (bound-fn [] (deliver prom (thunk)))) 40 | timed-out (Object.)] 41 | (.start thread) 42 | (let [res (deref prom timeout-ms timed-out)] 43 | (if (= res timed-out) 44 | (do 45 | (.join thread 100) 46 | (if (.isAlive thread) 47 | (.stop thread)) 48 | {:exception (Throwable->map (Exception. "Eval timed out!"))}) 49 | res)))) 50 | 51 | (defn safe-resolve [s] 52 | (some-> s 53 | symbol 54 | (-> resolve (try (catch Throwable e nil))))) 55 | 56 | (def safe-meta (comp meta safe-resolve)) 57 | 58 | (defn resolve-meta [var-str] 59 | (or (safe-meta var-str) 60 | (when-let [ns' (some-> var-str symbol find-ns)] 61 | (assoc (meta ns') 62 | :ns var-str)))) 63 | 64 | (derive ::service ::clj-reader/clojure) 65 | 66 | (defmethod clj-reader/-resolve-meta ::service [_ var-str] 67 | (resolve-meta var-str)) 68 | 69 | (defmethod clj-reader/-complete ::service [_ word options] 70 | (if options 71 | (compliment/completions word options) 72 | (compliment/completions word))) 73 | 74 | (defmethod clj-reader/-current-ns ::service [_] 75 | (some-> *ns* str)) 76 | 77 | (defmethod clj-reader/-source ::service [_ var-str] 78 | (some->> (clojure.repl/source-fn (symbol var-str)) 79 | (hash-map :source))) 80 | 81 | (defmethod clj-reader/-apropos ::service [_ var-str] 82 | (clojure.repl/apropos var-str)) 83 | 84 | (defmethod clj-reader/-doc ::service [self var-str] 85 | (when-let [{:keys [ns name]} (clj-reader/-resolve-meta self var-str)] 86 | (when-let [doc (compliment/documentation var-str)] 87 | (let [url (clj-utils/url-for (str ns) (str name))] 88 | (cond-> {:doc doc} 89 | url (assoc :url url)))))) 90 | 91 | (defmethod clj-reader/-eval ::service [self form] 92 | (let [res (call-with-timeout 93 | #(data-eval form) 94 | (get self :eval-timeout 3000))] 95 | ;; set! *e outside of the thread 96 | (when-let [ex (some-> res :exception meta :ex)] 97 | (set! *e ex)) 98 | res)) 99 | 100 | (defn default-read-string [form-str] 101 | (when (string? form-str) 102 | (try 103 | {:form (with-in-str form-str 104 | (read {:read-cond :allow} *in*))} 105 | (catch Throwable e 106 | {:exception (Throwable->map e)})))) 107 | 108 | (defmethod clj-reader/-read-string ::service [_ form-str] 109 | (default-read-string form-str)) 110 | 111 | (defn create 112 | ([] (create nil)) 113 | ([options] 114 | (assoc options :rebel-readline.service/type ::service))) 115 | -------------------------------------------------------------------------------- /rebel-readline-nrepl/README.md: -------------------------------------------------------------------------------- 1 | # rebel-readline-nrepl 2 | 3 | [![Clojars Project](https://img.shields.io/clojars/v/com.bhauman/rebel-readline-nrepl.svg)](https://clojars.org/com.bhauman/rebel-readline-nrepl) 4 | 5 | `rebel-readline-nrepl` is a Clojure library that brings the flexibility of Rebel Readline to your terminal as an nREPL (Network REPL) client. 6 | 7 | ## Prerequisites 8 | 9 | Before you begin, make sure you have the following installed: 10 | 11 | - [Clojure CLI tools](https://clojure.org/guides/install_clojure) 12 | 13 | ## Installation 14 | 15 | ### As a Clojure Tool 16 | 17 | 1. Add `rebel-readline-nrepl` to your `./clojure/deps.edn` file under aliases: 18 | 19 | ```clojure 20 | { 21 | :aliases { 22 | :nrebel { 23 | :extra-deps {com.bhauman/rebel-readline-nrepl {:mvn/version "0.1.5"}} 24 | :exec-fn rebel-readline.nrepl/connect 25 | :exec-args {:background-print false} ;; Optional configuration parameters 26 | :main-opts ["-m" "rebel-readline.nrepl.main"] 27 | } 28 | } 29 | } 30 | ``` 31 | 32 | 2. Launch it via the command line: 33 | 34 | ```bash 35 | clojure -T:nrebel :port <50668> 36 | ``` 37 | 38 | ### Alternative Installation Method 39 | 40 | You can also install `rebel-readline-nrepl` as a Clojure Tool with the following command: 41 | 42 | ```bash 43 | clojure -Ttools install-latest :lib com.github.bhauman/rebel-readline :coord '{:deps/root "rebel-readline-nrepl"}' :as nrebel 44 | ``` 45 | 46 | Call it with: 47 | 48 | ```bash 49 | clojure -Tnrebel connect :port <50668> 50 | ``` 51 | 52 | ## Usage 53 | 54 | ### Starting an nREPL Server 55 | 56 | To get started, you need an nREPL server. You can spin up a basic nREPL server by executing: 57 | 58 | ```bash 59 | clojure -Sdeps '{:deps {nrepl/nrepl {:mvn/version "1.3.1"}}}' -M -m nrepl.cmdline --port 7888 60 | ``` 61 | 62 | This command starts an nREPL server and outputs the port it is listening on. Usually, you set up the nREPL server as part of your Clojure project. Refer to the [nREPL server documentation](https://nrepl.org/nrepl/1.3/usage/server.html) for additional instructions. 63 | 64 | ### Connecting to the nREPL Server 65 | 66 | Once the nREPL server is running, you can connect to it using 67 | `rebel-readline-nrepl`.If you installed `rebel-readline-nrepl` in your 68 | `.clojure/deps.edn` as above hten you can run: 69 | 70 | ```bash 71 | clojure -T:nrebel :port 7888 72 | ``` 73 | 74 | To specify the host (default is `localhost`): 75 | 76 | ```bash 77 | clojure -T:nrebel :host localhost :port 7888 78 | ``` 79 | 80 | If you installed it as a Clojure Tool, connect like so: 81 | 82 | ```bash 83 | clojure -Tnrebel connect :host localhost :port 7888 84 | ``` 85 | 86 | Alternatively, if it's in your classpath you can invoke it directly: 87 | 88 | ```bash 89 | clojure -m rebel-readline.nrepl.main --host localhost --port 7888 90 | ``` 91 | 92 | ### Integrating with Your Project 93 | 94 | To include `rebel-readline-nrepl` in your project directly, add it to your `deps.edn` file: 95 | 96 | ```clojure 97 | {:aliases 98 | {:nrebelly 99 | {:extra-deps {com.bhauman/rebel-readline-nrepl {:mvn/version "NOT-PUBLISHED-YET"}} 100 | :exec-fn rebel-readline.nrepl/connect 101 | :exec-args {:host "localhost" :port 7888}}}} 102 | ``` 103 | 104 | You can execute it with: 105 | 106 | ```bash 107 | clojure -T:nrebelly 108 | ``` 109 | 110 | ## TLS Support 111 | 112 | For secure connections, refer to the [nREPL TLS documentation](https://nrepl.org/nrepl/1.3/usage/tls.html) for steps on generating keys and starting a TLS-enabled nREPL server. 113 | 114 | Connect over TLS by specifying the TLS key file: 115 | 116 | ```bash 117 | clojure -T:nrebel :port 50668 :tls-key-file '"client.keys"' 118 | ``` 119 | 120 | ## Configuration Parameters 121 | 122 | `rebel-readline-nrepl` supports specific configuration options: 123 | 124 | - `:port` - Required port number for the nREPL server. 125 | - `:host` - Optional; defaults to `localhost`. 126 | - `:tls-key-file` - Path to the TLS key file. 127 | - `:background-print` - Boolean indicating whether to allow background threads to continue printing. 128 | 129 | For configuration details, refer back to the Rebel Readline documentation [here](../README.md#config-parameters). 130 | 131 | ### License 132 | 133 | Copyright © 2023 Bruce Hauman 134 | 135 | Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version. 136 | -------------------------------------------------------------------------------- /rebel-readline-paredit/src/rebel_readline_paredit/core.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-readline-paredit.core 2 | (:require 3 | [rebel-readline.tools.indent :as ind]) 4 | (:use rebel-readline.jline-api) 5 | (:import 6 | [org.jline.keymap KeyMap] 7 | [org.jline.reader LineReader] 8 | [org.jline.utils AttributedStringBuilder AttributedString AttributedStyle 9 | #_InfoCmp$Capability] 10 | [java.util.regex Pattern])) 11 | 12 | ;; ---------------------------------------------- 13 | ;; this is a WIP and is out of date right now 14 | ;; ---------------------------------------------- 15 | 16 | ;; ------------ 17 | ;; paredit open 18 | 19 | (defn should-self-insert-open? [code-str cursor] 20 | #_(log :hey code-str cursor (ind/in-non-interp-bounds? code-str cursor)) 21 | (if-let [[start end token-type] (ind/in-non-interp-bounds? code-str cursor)] 22 | (not (and (= :character token-type) 23 | (not (= end (inc start))))) 24 | true)) 25 | 26 | #_(and (should-self-insert-open? " \\ a " 2) 27 | (not (should-self-insert-open? " \\a " 2)) 28 | ) 29 | #_(ind/in-non-interp-bounds? " \\ " (dec 2)) 30 | 31 | (defn paredit-insert-pair [open-char buffer] 32 | (let [cursor (.cursor buffer)] 33 | (when-not (or (ind/blank-at-position? (str buffer) (dec cursor)) 34 | (#{\( \{ \[} (char (.prevChar buffer)))) 35 | (.write buffer (int \space))) 36 | (doto buffer 37 | (.write (int open-char)) 38 | (.write (int (ind/flip-delimiter open-char))) 39 | (.move -1)) 40 | (when-not (or (ind/blank-at-position? (str buffer) (inc (.cursor buffer))) 41 | (#{\) \} \]} (char (.nextChar buffer)))) 42 | (.move buffer 1) 43 | (.write buffer (int \space)) 44 | (.move buffer -2)) 45 | true)) 46 | 47 | (defn paredit-open [open-char buffer] 48 | (let [cursor (.cursor buffer) 49 | s (str buffer)] 50 | (if (ind/in-non-interp-bounds? s cursor) 51 | (not (should-self-insert-open? s cursor)) 52 | (paredit-insert-pair open-char buffer)))) 53 | 54 | (defn paredit-open-widget [open-char] 55 | (create-widget 56 | (if (paredit-open open-char *buffer*) 57 | true 58 | (do (call-widget LineReader/SELF_INSERT) 59 | true)))) 60 | 61 | ;; ------------ 62 | ;; paredit close 63 | 64 | (comment 65 | 66 | (let [b (buffer "( )")] 67 | (.move b -1) 68 | (paredit-close-action b) 69 | b 70 | ) 71 | 72 | (list 1 73 | 2 74 | 3) 75 | 76 | (ind/find-open-sexp-end (ind/tag-for-sexp-traversal "()") 1) 77 | 78 | ) 79 | 80 | 81 | 82 | (defn backwards-clean-whitespace [buffer] 83 | (loop [] 84 | (when (Character/isWhitespace (.prevChar buffer)) 85 | (.backspace buffer) 86 | (recur)))) 87 | 88 | (defn paredit-close-action [buffer] 89 | (let [s (str buffer) 90 | tagged-parses (ind/tag-for-sexp-traversal s) 91 | [_ start _ _] (ind/find-open-sexp-end tagged-parses (.cursor buffer))] 92 | (if-not start 93 | false 94 | (do 95 | (.cursor buffer start) 96 | (backwards-clean-whitespace buffer) 97 | ;; TODO blink by calling widget a 98 | (.move buffer 1) 99 | true)))) 100 | 101 | (defn paredit-close [buffer] 102 | (let [cursor (.cursor buffer) 103 | s (str buffer)] 104 | (if (ind/in-non-interp-bounds? s cursor) 105 | (and (not (should-self-insert-open? s cursor)) :self-insert) 106 | (paredit-close-action buffer)))) 107 | 108 | (defn paredit-close-widget [line-reader] 109 | (create-widget 110 | (condp = (paredit-close *buffer*) 111 | :self-insert 112 | (do (call-widget LineReader/SELF_INSERT) 113 | true) 114 | true 115 | (do 116 | #_(.move buf -1) 117 | #_(.callWidget line-reader LineReader/VI_MATCH_BRACKET) 118 | #_(future 119 | (do 120 | (Thread/sleep 500) 121 | (.callWidget line-reader LineReader/VI_MATCH_BRACKET) 122 | (.move buf 1))) 123 | true) 124 | false false))) 125 | 126 | #_(defn add-paredit [line-reader] 127 | (-> line-reader 128 | (register-widget "paredit-open-paren" (paredit-open-widget \( line-reader)) 129 | (register-widget "paredit-open-brace" (paredit-open-widget \{ line-reader)) 130 | (register-widget "paredit-open-bracket" (paredit-open-widget \[ line-reader)) 131 | (register-widget "paredit-open-quote" (paredit-open-widget \" line-reader)) 132 | (register-widget "paredit-close" (paredit-close-widget line-reader)) 133 | 134 | (bind-key "paredit-open-paren" (str "(")) 135 | (bind-key "paredit-open-brace" (str "{")) 136 | (bind-key "paredit-open-bracket" (str "[")) 137 | 138 | (bind-key "paredit-close" (str ")")) 139 | (bind-key "paredit-close" (str "}")) 140 | (bind-key "paredit-close" (str "]")) 141 | 142 | ;; TODO backspace 143 | 144 | ) 145 | ) 146 | -------------------------------------------------------------------------------- /rebel-readline/doc/intro.md: -------------------------------------------------------------------------------- 1 | # Purpose 2 | 3 | Provide a better readline experience for Clojure languages. 4 | 5 | It is valid to ask why would Clojure programmers need a better 6 | readline experience, as our tools of choice normally allow us to edit 7 | in the environment of our choice and send off expressions to be 8 | evaluated in a REPL. 9 | 10 | While this is true, it doesn't hurt to make the REPL experience more 11 | helpful and pleasurable when we are forced to interact with it 12 | directly at the terminal. These rare situations do pop up, and when 13 | they do the anomalous situation is sometimes associated with some kind 14 | of urgency. In cases where we are trying to debug a live system, and 15 | our tools aren't available, there is no need to make the situation 16 | harder with a spartan terminal UX. 17 | 18 | So when you do have to use the REPL, it would be nice for it to 19 | work better. 20 | 21 | However, the main reason for this library is for the newcomers to 22 | Clojure. The path for newcomers to create an effective Clojure 23 | programming environment, is varied, difficult and confusing. It 24 | requires a level of investment and discernment that is too high for 25 | the language explorer who has never used a LISP before. As a result 26 | when newcomers come to Clojure the most intelligent decision they can 27 | make, is to not try and negotiate the tooling needed for an editor 28 | REPL connection but rather just use the `clojure.main/repl` or `lein 29 | repl` and/or a edit a file with a familiar editor and constantly 30 | re-run or re-load a script. Thus, they experience a stunted workflow 31 | that is all too familiar in other languages and it is easy to 32 | miss-construe this as the Clojure development experience. 33 | 34 | A fluid interactive programming workflow is a fundamental difference 35 | that Clojure offers, yet many newcomers will often never see or 36 | experience it. 37 | 38 | When I refer to a fluid interactive programming workflow in Clojure I 39 | am thinking mainly of inline-eval. Most programmers do not have an 40 | experience of what inline-eval is. They have nothing to compare it 41 | to. They have not used LISPs and SEXPs. You can describe inline eval 42 | and demonstrate it to them until you're blue in the face and they 43 | won't get it. 44 | 45 | It is not until a programmer actually experiences inline-eval as a 46 | programming tool that the light goes on. SEXPs start to make more 47 | sense, and the why of LISP starts to dawn. 48 | 49 | The idea here is to provide the opportunity to experience inline-eval 50 | at the first REPL a newcomer tries. The idea is to provide a tool that 51 | is sharp enough for newcomers to elegantly solve 52 | [4Clojure](http://www.4clojure.com/) problems and participate in 53 | [Advent Of Code](http://adventofcode.com/) without having to make a 54 | steep investment in an unfamiliar toolchain. 55 | 56 | As a bonus, when we provide this experience at the very first REPL, 57 | newcomers will have a base of experience from which they can now draw 58 | from to choose their tooling. 59 | 60 | They will understand the availability of online docs, source code, and 61 | apropos. They will understand the capabilities of inline eval and 62 | structural editing. IMHO this experience needs to be communicated as 63 | urgently as the other Clojure features. 64 | 65 | # Design priorities 66 | 67 | * keep dependency tree very simple and shallow. In order for a library 68 | like this to be adopted widely across the Clojure tooling system it 69 | needs to not bring extraneous dependencies. This means the core 70 | library should have as few dependencies as possible. When 71 | dependencies are required the transient dependencies again should be 72 | few to none. 73 | 74 | JLine is not a small or simple dependency but it is non-negotiable 75 | at the moment b/c manipulating a terminal and its capabilities in a 76 | cross platform compatible way is a very difficult problem. 77 | 78 | * provide an exceptional readline experience for Clojure programmers 79 | This experience should transcend currently available readline 80 | offerings in other languages. Clojure has many advantages (sexps, 81 | etc) that provide a readline library a great deal of leverage to 82 | do text manipulation. 83 | 84 | * as a readline library it shouldn't interfere with the input stream 85 | when it is not reading a line 86 | 87 | * as a readline library it is not responsible for REPL output, this 88 | doesn't mean it can't provide useful utilities that would help the 89 | library consumer, for example to query the last line read, or to 90 | redisplay the last line (possibly in place) with a code pointer 91 | indicating an error. 92 | 93 | * open and customizable, following in the example of Emacs this 94 | library's behavior should be customizable. Behavior should be 95 | modifiable/programable from the readline itself. 96 | 97 | This will allow the people to opt in to additional functionality like 98 | paredit while keeping the core as simple as possible. 99 | 100 | * timeline priority is to get something useful into the hands of 101 | programmers sooner than later 102 | 103 | * an eventual goal is to abstract the api enough so that it will work 104 | in on a JS platform, this is not an immediate goal. 105 | -------------------------------------------------------------------------------- /rebel-readline/src/rebel_readline/clojure/main.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-readline.clojure.main 2 | (:require 3 | [rebel-readline.core :as core] 4 | [rebel-readline.clojure.line-reader :as clj-line-reader] 5 | [rebel-readline.jline-api :as api] 6 | [rebel-readline.tools :as tools] 7 | [rebel-readline.clojure.service.local :as clj-service] 8 | [clojure.repl :as repl] 9 | [clojure.main])) 10 | 11 | (defn- handle-sigint-form 12 | [] 13 | `(let [thread# (Thread/currentThread)] 14 | (repl/set-break-handler! (fn [_signal#] (.stop thread#))))) 15 | 16 | (defn syntax-highlight-prn-unwrapped 17 | [x] 18 | (try 19 | (println (api/->ansi (clj-line-reader/highlight-clj-str x))) 20 | (catch java.lang.StackOverflowError e 21 | (println x)))) 22 | 23 | (defn syntax-highlight-prn* 24 | "Print a syntax highlighted clojure string. 25 | 26 | This printer respects the current color settings set in the 27 | service. 28 | 29 | The `rebel-readline.jline-api/*line-reader*` and 30 | `rebel-readline.jline-api/*service*` dynamic vars have to be set for 31 | this to work. 32 | 33 | See `rebel-readline.main` for an example of how this function is normally used" 34 | [x] 35 | (binding [*out* (.. api/*line-reader* getTerminal writer)] 36 | (syntax-highlight-prn-unwrapped x))) 37 | 38 | (defn syntax-highlight-prn 39 | "Print a syntax highlighted clojure value. 40 | 41 | This printer respects the current color settings set in the 42 | service. 43 | 44 | The `rebel-readline.jline-api/*line-reader*` and 45 | `rebel-readline.jline-api/*service*` dynamic vars have to be set for 46 | this to work. 47 | 48 | See `rebel-readline.main` for an example of how this function is normally used" 49 | [x] 50 | (syntax-highlight-prn* (pr-str x))) 51 | 52 | ;; this is intended to only be used with clojure repls 53 | (def create-repl-read 54 | "A drop in replacement for clojure.main/repl-read, since a readline 55 | can return multiple Clojure forms this function is stateful and 56 | buffers the forms and returns the next form on subsequent reads. 57 | 58 | This function is a constructor that takes a line-reader and returns 59 | a function that can replace `clojure.main/repl-read`. 60 | 61 | Example Usage: 62 | 63 | (clojure.main/repl 64 | :prompt (fn []) ;; prompt is handled by line-reader 65 | :read (clj-repl-read 66 | (line-reader 67 | (rebel-readline.clojure.service.local/create)))) 68 | 69 | Or catch a bad terminal error and fall back to clojure.main/repl-read: 70 | 71 | (clojure.main/repl 72 | :prompt (fn []) 73 | :read (try 74 | (clj-repl-read 75 | (line-reader 76 | (rebel-readline.clojure.service.local/create))) 77 | (catch clojure.lang.ExceptionInfo e 78 | (if (-> e ex-data :type (= :rebel-readline.jline-api/bad-terminal)) 79 | (do (println (.getMessage e)) 80 | clojure.main/repl-read) 81 | (throw e)))))" 82 | (core/create-buffered-repl-reader-fn 83 | (fn [s] (clojure.lang.LineNumberingPushbackReader. 84 | (java.io.StringReader. s))) 85 | core/has-remaining? 86 | clojure.main/repl-read)) 87 | 88 | (let [clj-repl clojure.main/repl] 89 | (defn repl* [{:keys [:rebel-readline/config] :as opts}] 90 | (let [opts (dissoc opts :rebel-readline/config) 91 | ;; would prefer not to have this here 92 | final-config (merge clj-line-reader/default-config 93 | (tools/user-config ::tools/arg-map config) 94 | (when api/*line-reader* @api/*line-reader*) 95 | config)] 96 | (core/with-line-reader 97 | (clj-line-reader/create 98 | (clj-service/create final-config)) 99 | ;; still debating about wether to include the following line in 100 | ;; `with-line-reader`. I am thinking that taking over out should 101 | ;; be opt in when using the lib taking over out provides 102 | ;; guarantees by Jline that Ascii commands are processed correctly 103 | ;; on different platforms, this particular writer also protects 104 | ;; the prompt from corruption by ensuring a newline on flush and 105 | ;; forcing a prompt to redisplay if the output is printed while 106 | ;; the readline editor is enguaged 107 | (binding [*out* (api/safe-terminal-writer api/*line-reader*)] 108 | (when-let [prompt-fn (:prompt opts)] 109 | (swap! api/*line-reader* assoc :prompt prompt-fn)) 110 | (println (core/help-message)) 111 | (apply 112 | clj-repl 113 | (-> {:print syntax-highlight-prn 114 | :eval (fn [form] 115 | (eval `(do ~(handle-sigint-form) ~form))) 116 | :read (create-repl-read)} 117 | (merge opts {:prompt (fn [])}) 118 | seq 119 | flatten))))))) 120 | 121 | (defn repl [& opts] 122 | (repl* (apply hash-map opts))) 123 | 124 | ;; -------------------------------------------- 125 | ;; Debug repl (Joy of Clojure) 126 | ;; -------------------------------------------- 127 | 128 | (defn contextual-eval [ctx expr] 129 | (eval 130 | `(let [~@(mapcat (fn [[k v]] [k `'~v]) ctx)] 131 | ~expr))) 132 | 133 | (defmacro local-context [] 134 | (let [symbols (keys &env)] 135 | (zipmap (map (fn [sym] `(quote ~sym)) symbols) symbols))) 136 | 137 | (defmacro break [] 138 | `(repl 139 | :prompt #(print "debug=> ") 140 | :eval (partial contextual-eval (local-context)))) 141 | 142 | (defn main [options] 143 | (core/ensure-terminal (repl* options))) 144 | 145 | (defn -main [& args] 146 | (core/ensure-terminal (repl))) 147 | -------------------------------------------------------------------------------- /rebel-readline-cljs/src/rebel_readline/cljs/service/local.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-readline.cljs.service.local 2 | (:require 3 | [cljs-tooling.complete :as cljs-complete] 4 | [cljs-tooling.info :as cljs-info] 5 | [cljs.analyzer :as ana] 6 | [cljs.analyzer.api :as ana-api] 7 | [cljs.core] 8 | [cljs.env] 9 | [cljs.repl] 10 | [cljs.tagged-literals :as tags] 11 | [clojure.string :as string] 12 | [clojure.tools.reader :as reader] 13 | [clojure.tools.reader.reader-types :as readers] 14 | [rebel-readline.clojure.utils :as clj-utils] 15 | [rebel-readline.clojure.line-reader :as clj-reader] 16 | [rebel-readline.clojure.service.local :refer [call-with-timeout]] 17 | [rebel-readline.tools :as tools] 18 | [rebel-readline.utils :refer [log]]) 19 | (:import 20 | [java.util.regex Pattern])) 21 | 22 | (defn format-document [{:keys [ns name type arglists doc]}] 23 | (when doc 24 | (string/join 25 | (System/getProperty "line.separator") 26 | (cond-> [] 27 | (and ns name) (conj (str ns "/" name)) 28 | type (conj (name type)) 29 | arglists (conj (pr-str arglists)) 30 | doc (conj (str " " doc)))))) 31 | 32 | ;; taken from cljs.repl 33 | (defn- named-publics-vars 34 | "Gets the public vars in a namespace that are not anonymous." 35 | [ns] 36 | (->> (ana-api/ns-publics ns) 37 | (remove (comp :anonymous val)) 38 | (map key))) 39 | 40 | ;; taken from cljs.repl and translated into a fn 41 | (defn apropos 42 | "Given a regular expression or stringable thing, return a seq of all 43 | public definitions in all currently-loaded namespaces that match the 44 | str-or-pattern." 45 | [str-or-pattern] 46 | (let [matches? (if (instance? Pattern str-or-pattern) 47 | #(re-find str-or-pattern (str %)) 48 | #(.contains (str %) (str str-or-pattern)))] 49 | (sort 50 | (mapcat 51 | (fn [ns] 52 | (let [ns-name (str ns)] 53 | (map #(symbol ns-name (str %)) 54 | (filter matches? (named-publics-vars ns))))) 55 | (ana-api/all-ns))))) 56 | 57 | (defn read-cljs-string [form-str] 58 | (when-not (string/blank? form-str) 59 | (try 60 | {:form (binding [*ns* (create-ns ana/*cljs-ns*) 61 | reader/resolve-symbol ana/resolve-symbol 62 | reader/*data-readers* tags/*cljs-data-readers* 63 | reader/*alias-map* 64 | (apply merge 65 | ((juxt :requires :require-macros) 66 | (ana/get-namespace ana/*cljs-ns*)))] 67 | (reader/read {:read-cond :allow :features #{:cljs}} 68 | (readers/source-logging-push-back-reader 69 | (java.io.StringReader. form-str))))} 70 | (catch Exception e 71 | {:exception (Throwable->map e)})))) 72 | 73 | (defn eval-cljs [repl-env env form] 74 | (let [res (cljs.repl/evaluate-form repl-env 75 | (assoc env :ns (ana/get-namespace ana/*cljs-ns*)) 76 | "" 77 | form 78 | (#'cljs.repl/wrap-fn form))] 79 | res)) 80 | 81 | (defn data-eval 82 | [eval-thunk] 83 | (let [out-writer (java.io.StringWriter.) 84 | err-writer (java.io.StringWriter.) 85 | capture-streams (fn [] 86 | (.flush *out*) 87 | (.flush *err*) 88 | {:out (.toString out-writer) 89 | :err (.toString err-writer)})] 90 | (binding [*out* (java.io.BufferedWriter. out-writer) 91 | *err* (java.io.BufferedWriter. err-writer)] 92 | (try 93 | (let [result (eval-thunk)] 94 | (Thread/sleep 100) ;; give printed data time to propagate 95 | (merge (capture-streams) {:printed-result result})) 96 | (catch Throwable t 97 | (merge (capture-streams) {:exception (Throwable->map t)})))))) 98 | 99 | (derive ::service ::clj-reader/clojure) 100 | 101 | (defmethod clj-reader/-current-ns ::service [_] (some-> ana/*cljs-ns* str)) 102 | 103 | (defmethod clj-reader/-complete ::service [_ word {:keys [ns]}] 104 | (let [options (cond-> nil 105 | ns (assoc :current-ns ns))] 106 | (cljs-complete/completions @cljs.env/*compiler* word options))) 107 | 108 | (defmethod clj-reader/-resolve-meta ::service [self var-str] 109 | (cljs-info/info @cljs.env/*compiler* var-str 110 | (clj-reader/-current-ns self))) 111 | 112 | (defmethod clj-reader/-doc ::service [self var-str] 113 | (when-let [{:keys [ns name] :as info} (clj-reader/-resolve-meta self var-str)] 114 | (when-let [doc (format-document info)] 115 | (let [url (clj-utils/url-for (str ns) (str name))] 116 | (cond-> {:doc doc} 117 | url (assoc :url url)))))) 118 | 119 | (defmethod clj-reader/-source ::service [_ var-str] 120 | (some->> (cljs.repl/source-fn @cljs.env/*compiler* (symbol var-str)) 121 | (hash-map :source))) 122 | 123 | (defmethod clj-reader/-apropos ::service [_ var-str] (apropos var-str)) 124 | 125 | (defmethod clj-reader/-read-string ::service [_ form-str] (read-cljs-string form-str)) 126 | 127 | (defmethod clj-reader/-eval ::service [self form] 128 | (when-let [repl-env (:repl-env self)] 129 | (call-with-timeout 130 | (fn [] 131 | (data-eval #(eval-cljs repl-env (ana/empty-env) form))) 132 | (get self :eval-timeout 3000)))) 133 | 134 | ;; this needs a :repl-env option 135 | (defn create 136 | ([] (create nil)) 137 | ([options] 138 | (merge clj-reader/default-config 139 | (tools/user-config identity options) 140 | options 141 | {:rebel-readline.service/type ::service}))) 142 | -------------------------------------------------------------------------------- /rebel-readline/test/rebel_readline/clojure/sexp_test.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-readline.clojure.sexp-test 2 | (:require 3 | [rebel-readline.clojure.tokenizer :as tokenize] 4 | [rebel-readline.clojure.sexp :refer :all] 5 | [clojure.test :refer [deftest is are testing]])) 6 | 7 | (defn find-open [sexp pos] 8 | (find-open-sexp-start (tokenize/tag-sexp-traversal sexp) pos)) 9 | 10 | (def find-open-pos (comp second find-open)) 11 | 12 | (deftest find-open-sexp-start-test 13 | (is (= 4 (find-open-pos "0123(5" 20))) 14 | (is (= 4 (find-open-pos "0123(5" 6))) 15 | (is (= 4 (find-open-pos "0123(5" 5))) 16 | ;; position is a cursor position 17 | (is (nil? (find-open-pos "0123(5" 4))) 18 | (is (= 4 (find-open-pos "0123(5)" 6))) 19 | (is (nil? (find-open-pos "0123(5)" 7))) 20 | 21 | (is (= 4 (find-open-pos "0123[5" 20))) 22 | (is (= 4 (find-open-pos "0123{5" 20))) 23 | 24 | ;; more complex example 25 | (is (= 6 (find-open-pos "0123(5[7{9}" 20))) 26 | (is (= 6 (find-open-pos "0123(5[7{9}" 11))) 27 | (is (= 8 (find-open-pos "0123(5[7{9}" 10))) 28 | (is (= 8 (find-open-pos "0123(5[7{9}" 9))) 29 | (is (= 6 (find-open-pos "0123(5[7{9}" 8))) 30 | (is (= 6 (find-open-pos "0123(5[7{9}" 7))) 31 | (is (= 4 (find-open-pos "0123(5[7{9}" 6))) 32 | (is (= 4 (find-open-pos "0123(5[7{9}" 5))) 33 | (is (nil? (find-open-pos "0123(5[7{9}" 4))) 34 | (is (nil? (find-open-pos "0123(5[7{9}" 3))) 35 | (is (nil? (find-open-pos "0123(5[7{9}" 1))) 36 | (is (nil? (find-open-pos "0123(5[7{9}" 0))) 37 | (is (nil? (find-open-pos "0123(5[7{9}" -1))) 38 | 39 | (testing "strings" 40 | (is (not (find-open-pos "0123\"56\"8\"ab" 4))) 41 | (is (= 4 (find-open-pos "0123\"56\"8\"ab" 5))) 42 | (is (= 4 (find-open-pos "0123\"56\"8\"ab" 6))) 43 | (is (= 4 (find-open-pos "0123\"56\"8\"ab" 7))) 44 | (is (not (find-open-pos "0123\"56\"8\"ab" 8))) 45 | (is (not (find-open-pos "0123\"56\"8\"ab" 9))) 46 | (is (= 9 (find-open-pos "0123\"56\"8\"ab" 10))) 47 | (is (= 9 (find-open-pos "0123\"56\"8\"ab" 11))) 48 | (is (= 9 (find-open-pos "0123\"56\"8\"ab" 20))) 49 | (is (= 9 (find-open-pos "0123\"56\"8\"ab" 20)))) 50 | 51 | ) 52 | 53 | (defn find-end [sexp pos] 54 | (find-open-sexp-end (tokenize/tag-sexp-traversal sexp) pos)) 55 | 56 | (def find-end-pos (comp second find-end)) 57 | 58 | (deftest find-open-sexp-end-test 59 | (is (= 4 (find-end-pos "0123)5" 0))) 60 | (is (= 4 (find-end-pos "0123)5" 2))) 61 | (is (= 4 (find-end-pos "0123)5" 3))) 62 | (is (= 4 (find-end-pos "0123)5" 4))) 63 | ;; position is a cursor position 64 | (is (nil? (find-end-pos "0123)5" 5))) 65 | 66 | (is (nil? (find-end-pos "0123(5)" 3))) 67 | (is (nil? (find-end-pos "0123(5)" 4))) 68 | (is (= 6 (find-end-pos "0123(5)" 5))) 69 | (is (= 6 (find-end-pos "0123(5)" 6))) 70 | (is (nil? (find-end-pos "0123(5)" 7))) 71 | 72 | (is (= 5 (find-end-pos "01234]6" 0))) 73 | (is (= 5 (find-end-pos "01234}6" 4))) 74 | 75 | ;; more complex example 76 | (is (= 7 (find-end-pos "012{4}6]8)a" -1))) 77 | (is (= 7 (find-end-pos "012{4}6]8)a" 0))) 78 | (is (= 7 (find-end-pos "012{4}6]8)a" 1))) 79 | (is (= 7 (find-end-pos "012{4}6]8)a" 2))) 80 | (is (= 7 (find-end-pos "012{4}6]8)a" 3))) 81 | 82 | (is (= 5 (find-end-pos "012{4}6]8)a" 4))) 83 | (is (= 5 (find-end-pos "012{4}6]8)a" 5))) 84 | 85 | (is (= 7 (find-end-pos "012{4}6]8)a" 6))) 86 | (is (= 7 (find-end-pos "012{4}6]8)a" 7))) 87 | 88 | (is (= 9 (find-end-pos "012{4}6]8)a" 8))) 89 | 90 | (is (= 9 (find-end-pos "012{4}6]8)a" 9))) 91 | (is (nil? (find-end-pos "012{4}6]8)a" 10))) 92 | 93 | (is (not (find-end-pos "012\"45\"78" 3))) 94 | (is (= 6 (find-end-pos "012\"45\"78" 4))) 95 | (is (= 6 (find-end-pos "012\"45\"78" 5))) 96 | (is (= 6 (find-end-pos "012\"45\"78" 6))) 97 | (is (not (find-end-pos "012\"45\"78" 7))) 98 | 99 | ) 100 | 101 | (defn in-quote* [sexp pos] 102 | (in-quote? (tokenize/tag-sexp-traversal sexp) pos)) 103 | 104 | (deftest in-quote-test 105 | (is (not (in-quote* "0123\"56\"8\"ab" 3))) 106 | (is (not (in-quote* "0123\"56\"8\"ab" 4))) 107 | (is (in-quote* "0123\"56\"8\"ab" 5)) 108 | (is (in-quote* "0123\"56\"8\"ab" 6)) 109 | (is (in-quote* "0123\"56\"8\"ab" 7)) 110 | (is (not (in-quote* "0123\"56\"8\"ab" 8))) 111 | (is (not (in-quote* "0123\"56\"8\"ab" 9))) 112 | (is (in-quote* "0123\"56\"8\"ab" 10)) 113 | (is (in-quote* "0123\"56\"8\"ab" 11)) 114 | (is (in-quote* "0123\"56\"8\"ab" 12)) 115 | (is (not (in-quote* "0123\"56\"8\"ab" 13))) 116 | 117 | (is (not (in-quote* "012 \\a" 3))) 118 | (is (not (in-quote* "012 \\a" 4))) 119 | (is (in-quote* "012 \\a" 5)) 120 | (is (in-quote* "012 \\a" 6)) 121 | (is (not (in-quote* "012 \\a " 7))) 122 | 123 | ) 124 | 125 | (defn in-line-comment* [sexp pos] 126 | (in-line-comment? (tokenize/tag-sexp-traversal sexp) pos)) 127 | 128 | (deftest in-line-comment-test 129 | (is (in-line-comment* "012;456" 4)) 130 | (is (in-line-comment* "012;456" 5)) 131 | (is (in-line-comment* "012;456" 6)) 132 | (is (in-line-comment* "012;456" 7)) 133 | (is (not (in-line-comment* "012;456" 8))) 134 | (is (in-line-comment* "012;456\n" 7)) 135 | (is (not (in-line-comment* "012;456\n" 8))) 136 | ) 137 | 138 | (deftest valid-sexp-from-point-test 139 | (is (= "(do (let [x y] (.asdf sdfsd)))" 140 | (valid-sexp-from-point " (do (let [x y] (.asdf sdfsd) " 22))) 141 | (is (= "(do (let [x y]))" 142 | (valid-sexp-from-point " (do (let [x y] (.asdf sdfsd) " 20))) 143 | (is (= "(do (let [x y] ))" 144 | (valid-sexp-from-point " (do (let [x y] " 22))) 145 | (is (= "([{(\"hello\")}])" 146 | (valid-sexp-from-point " ([{(\"hello\" " 10))) 147 | (is (= "{(\"hello\")}" 148 | (valid-sexp-from-point " ([{(\"hello\")}) " 10))) 149 | 150 | 151 | 152 | ) 153 | 154 | (deftest word-at-position-test 155 | (is (not (word-at-position " 1234 " 0))) 156 | (is (= ["1234" 1 5 :word] 157 | (word-at-position " 1234 " 1))) 158 | (is (= ["1234" 1 5 :word] 159 | (word-at-position " 1234 " 4))) 160 | (is (= ["1234" 1 5 :word] 161 | (word-at-position " 1234 " 5))) 162 | (is (not (word-at-position " 1234 " 6))) 163 | 164 | ) 165 | 166 | (deftest sexp-ending-at-position-test 167 | (is (= ["(34)" 2 6 :sexp] 168 | (sexp-ending-at-position "01(34)" 5))) 169 | (is (= ["\"34\"" 2 6 :sexp] 170 | (sexp-ending-at-position "01\"34\"" 5))) 171 | (is (not (sexp-ending-at-position "01(34)" 4))) 172 | (is (not (sexp-ending-at-position "01\"34\"" 4))) 173 | (is (not (sexp-ending-at-position "01(34)" 1))) 174 | (is (not (sexp-ending-at-position "01\"34\"" 1))) 175 | 176 | (is (not (sexp-ending-at-position "01(34)" 6))) 177 | (is (not (sexp-ending-at-position "01\"34\"" 6))) 178 | 179 | ) 180 | -------------------------------------------------------------------------------- /rebel-readline/src/rebel_readline/clojure/sexp.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-readline.clojure.sexp 2 | (:require 3 | [clojure.string :as string] 4 | [rebel-readline.clojure.tokenizer :as tokenize]) 5 | (:import 6 | [java.util.regex Pattern])) 7 | 8 | (defn position-in-range? [s pos] 9 | (<= 0 pos (dec (count s)))) 10 | 11 | (defn blank-at-position? [s pos] 12 | (or (not (position-in-range? s pos)) 13 | (Character/isWhitespace (.charAt s pos)))) 14 | 15 | (defn non-interp-bounds [code-str] 16 | (map rest 17 | (tokenize/tag-non-interp code-str))) 18 | 19 | (defn in-non-interp-bounds? [code-str pos] ;; position of insertion not before 20 | (or (some #(and (< (first %) pos (second %)) %) 21 | (non-interp-bounds code-str)) 22 | (and (<= 0 pos (dec (count code-str))) 23 | (= (.charAt code-str pos) \\) 24 | [pos (inc pos) :character]))) 25 | 26 | (def delims #{:bracket :brace :paren :quote}) 27 | (def openers (set (map #(->> % name (str "open-") keyword) delims))) 28 | (def closers (set (map #(->> % name (str "close-") keyword) delims))) 29 | 30 | (def flip-it 31 | (->> openers 32 | (map 33 | (juxt identity #(as-> % x 34 | (name x) 35 | (string/split x #"-") 36 | (str "close-" (second x)) 37 | (keyword x)))) 38 | ((juxt identity (partial map (comp vec reverse)))) 39 | (apply concat) 40 | (into {}))) 41 | 42 | (def delim-key->delim 43 | {:open-paren \( 44 | :close-paren \) 45 | :open-brace \{ 46 | :close-brace \} 47 | :open-bracket \[ 48 | :close-bracket \] 49 | :open-quote \" 50 | :close-quote \"}) 51 | 52 | (def flip-delimiter-char 53 | (into {} (map (partial mapv delim-key->delim)) flip-it)) 54 | 55 | (defn scan-builder [open-test close-test] 56 | (fn [specific-test stack x] 57 | (cond 58 | (open-test x) 59 | (cons x stack) 60 | (close-test x) 61 | (cond 62 | (and (empty? stack) (specific-test x)) 63 | (reduced [:finished x]) 64 | (empty? stack) (reduced [:finished nil]) ;; found closing bracket of wrong type 65 | (= (-> stack first last) (flip-it (last x))) 66 | (rest stack) 67 | ;; unbalanced 68 | :else (reduced [:finished nil])) 69 | :else stack))) 70 | 71 | (def end-scan (scan-builder (comp openers last) (comp closers last))) 72 | (def start-scan (scan-builder (comp closers last) (comp openers last))) 73 | 74 | (declare in-quote?) 75 | 76 | (defn find-open-sexp-end 77 | ([tokens pos] 78 | (find-open-sexp-end tokens pos nil)) 79 | ([tokens pos final-delim-pred] 80 | (let [res (reduce 81 | (partial end-scan (or final-delim-pred identity)) 82 | nil 83 | (drop-while 84 | #(<= (nth % 2) pos) 85 | tokens))] 86 | (when (= :finished (first res)) 87 | (second res))))) 88 | 89 | (defn find-open-sexp-ends [tokens pos] 90 | (when-let [[_ _ end _ :as res] (find-open-sexp-end tokens pos)] 91 | (cons res 92 | (lazy-seq 93 | (find-open-sexp-ends tokens end))))) 94 | 95 | (defn find-open-sexp-start 96 | ([tokens pos] 97 | (find-open-sexp-start tokens pos nil)) 98 | ([tokens pos final-delim-pred] 99 | (let [res (reduce 100 | (partial start-scan (or final-delim-pred identity)) 101 | nil 102 | (reverse (take-while 103 | #(<= (nth % 2) pos) 104 | tokens)))] 105 | (when (= :finished (first res)) 106 | (second res))))) 107 | 108 | (defn find-open-sexp-starts [tokens pos] 109 | (when-let [[_ start _ :as res] (find-open-sexp-start tokens pos)] 110 | (cons res 111 | (lazy-seq 112 | (find-open-sexp-starts tokens start))))) 113 | 114 | ;; TODO :character should not be in in-quote? 115 | (defn in-quote? [tokens pos] 116 | (->> tokens 117 | (filter #(#{:string-literal-body 118 | :unterm-string-literal-body 119 | :character} (last %))) 120 | (filter (fn [[_ start end typ]] 121 | (if (= :character typ) 122 | (< start pos (inc end)) 123 | (<= start pos end)))) 124 | first)) 125 | 126 | (defn in-line-comment? [tokens pos] 127 | (->> tokens 128 | (filter #(#{:end-line-comment} (last %))) 129 | (filter (fn [[_ start end _]] 130 | (< start pos (inc end)))) 131 | first)) 132 | 133 | (defn search-for-line-start [s pos] 134 | (loop [p pos] 135 | (cond 136 | (<= p 0) 0 137 | (= (.charAt ^String s p) \newline) 138 | (inc p) 139 | :else (recur (dec p))))) 140 | 141 | (defn count-leading-white-space [s] (count (re-find #"^[^\S\n]+" s))) 142 | 143 | (defn delims-outward-from-pos [tokens pos] 144 | (map vector 145 | (find-open-sexp-starts tokens pos) 146 | (concat (find-open-sexp-ends tokens pos) 147 | (repeat nil)))) 148 | 149 | (defn valid-sexp-from-point [s pos] 150 | (let [tokens (tokenize/tag-sexp-traversal s) 151 | delims (take-while 152 | (fn [[a b]] 153 | (or (= (last a) (flip-it (last b))) 154 | (nil? (last b)))) 155 | (delims-outward-from-pos tokens pos)) 156 | max-exist (last (take-while some? (map second delims))) 157 | end (max (nth max-exist 2 0) pos) 158 | need-repairs (filter (complement second) delims) 159 | [_ start _ _] (first (last delims))] 160 | (when (not-empty delims) 161 | (->> need-repairs 162 | (map (comp delim-key->delim flip-it last first)) 163 | (apply str (subs s start end)))))) 164 | 165 | (defn word-at-position [s pos] 166 | (->> (tokenize/tag-words s) 167 | (filter #(= :word (last %))) 168 | (filter #(<= (second %) pos (nth % 2))) 169 | first)) 170 | 171 | (defn whitespace? [c] 172 | (re-matches #"[\s,]+" (str c))) 173 | 174 | (defn scan-back-from [pred s pos] 175 | (first (filter #(pred (.charAt s %)) 176 | (range (min (dec (count s)) pos) -1 -1)))) 177 | 178 | (defn first-non-whitespace-char-backwards-from [s pos] 179 | (scan-back-from (complement whitespace?) s pos)) 180 | 181 | (defn sexp-ending-at-position [s pos] 182 | (let [c (try (.charAt s pos) (catch Exception e nil))] 183 | (when (#{ \" \) \} \] } c) 184 | (let [sexp-tokens (tokenize/tag-sexp-traversal s)] 185 | (when-let [[_ start] (find-open-sexp-start sexp-tokens pos)] 186 | [(subs s start (inc pos)) start (inc pos) :sexp]))))) 187 | 188 | (defn sexp-or-word-ending-at-position [s pos] 189 | (or (sexp-ending-at-position s pos) 190 | (word-at-position s (inc pos)))) 191 | 192 | (defn funcall-word 193 | "Given a string with sexps an a position into that string that 194 | points to an open paren, return the first token that is the function 195 | call word" 196 | [code-str open-paren-pos] 197 | (some->> 198 | (tokenize/tag-matches (subs code-str open-paren-pos) 199 | ;; matches first word after paren 200 | (Pattern/compile (str "(\\()\\s*(" tokenize/not-delimiter-exp "+)")) 201 | :open-paren 202 | :word) 203 | not-empty 204 | (take 2) 205 | ((fn [[a b]] 206 | (when (= a ["(" 0 1 :open-paren]) 207 | b))))) 208 | -------------------------------------------------------------------------------- /rebel-readline-nrepl/src/rebel_readline/nrepl/main.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-readline.nrepl.main 2 | (:require 3 | [rebel-readline.core :as core] 4 | [rebel-readline.main] 5 | [rebel-readline.clojure.main :as main] 6 | [rebel-readline.jline-api :as api] 7 | [rebel-readline.clojure.line-reader :as clj-line-reader] 8 | [rebel-readline.nrepl.service.nrepl :as clj-service] 9 | [rebel-readline.tools :as tools] 10 | [rebel-readline.nrepl.service.commands] 11 | [clojure.tools.cli :as cli] 12 | [clojure.spec.alpha :as s] 13 | [clojure.string :as string] 14 | [clojure.java.io :as io] 15 | [clojure.main] 16 | [clojure.repl]) 17 | (:import (clojure.lang LispReader$ReaderException) 18 | [org.jline.terminal Terminal Terminal$SignalHandler Terminal$Signal])) 19 | 20 | (defn repl-caught [e] 21 | (println "Internal REPL Error: this shouldn't happen. :repl/*e for stacktrace") 22 | (some-> @api/*line-reader* :repl/error (reset! e)) 23 | (clojure.main/repl-caught e)) 24 | 25 | (defn read-eval-print-fn [{:keys [read printer request-prompt request-exit]}] 26 | (fn [] 27 | (try 28 | (let [input (read request-prompt request-exit)] 29 | (cond 30 | (#{request-prompt request-exit} input) input 31 | (not (clj-service/polling? @api/*line-reader*)) request-exit 32 | :else 33 | (do 34 | (api/toggle-input api/*terminal* false) 35 | (clj-service/eval-code 36 | @api/*line-reader* 37 | input 38 | (bound-fn* 39 | (cond->> identity 40 | (not (:background-print @api/*line-reader*)) 41 | (clj-service/out-err 42 | #(do (print %) (flush)) 43 | #(do (print %) (flush))) 44 | true (clj-service/value #(do (printer %) (flush))) 45 | true (clj-service/need-input 46 | (fn [_] 47 | (api/toggle-input api/*terminal* true) 48 | (try 49 | ;; we could use a more sophisticated input reader here 50 | (clj-service/send-input @api/*line-reader* (clojure.core/read-line)) 51 | (catch Throwable e 52 | (repl-caught e)) 53 | (finally 54 | (api/toggle-input api/*terminal* false)))))))) 55 | (api/toggle-input api/*terminal* true)))) 56 | (catch Throwable e 57 | (repl-caught e)) 58 | (finally 59 | (api/toggle-input api/*terminal* true))))) 60 | 61 | (defn repl-loop [] 62 | (let [request-prompt (Object.) 63 | request-exit (Object.) 64 | read-eval-print (read-eval-print-fn 65 | {:read core/repl-read-line 66 | :printer main/syntax-highlight-prn-unwrapped 67 | :request-prompt request-prompt 68 | :request-exit request-exit})] 69 | (try 70 | (clj-service/tool-eval-code 71 | @api/*line-reader* 72 | (pr-str `(do 73 | (require 'clojure.main) 74 | (require 'clojure.repl)))) 75 | (catch Throwable e 76 | (repl-caught e))) 77 | (loop [] 78 | (when (and (clj-service/polling? @api/*line-reader*) 79 | (try 80 | (not (identical? (read-eval-print) request-exit)) 81 | (catch Throwable e 82 | (repl-caught e) 83 | true))) 84 | (recur))))) 85 | 86 | (defn start-repl* [options] 87 | (core/with-line-reader 88 | (clj-line-reader/create 89 | (clj-service/create 90 | (merge (when api/*line-reader* @api/*line-reader*) 91 | options))) 92 | (binding [*out* (api/safe-terminal-writer api/*line-reader*)] 93 | (clj-service/register-background-printing api/*line-reader*) 94 | (clj-service/start-polling @api/*line-reader*) 95 | (.handle ^Terminal api/*terminal* 96 | Terminal$Signal/INT 97 | (let [line-reader api/*line-reader*] 98 | (proxy [Terminal$SignalHandler] [] 99 | (handle [sig] 100 | (clj-service/interrupt @line-reader))))) 101 | (println (core/help-message)) 102 | (repl-loop)))) 103 | 104 | (s/def ::sym-or-string (s/and (s/or :sym symbol? 105 | :str string?) 106 | (s/conformer #(-> % second str)))) 107 | 108 | (s/def ::tls-keys-file string?) 109 | (s/def ::host ::sym-or-string) 110 | (s/def ::background-print boolean?) 111 | (s/def ::port (s/and number? #(< 0 % 0x10000))) 112 | (s/def ::arg-map (s/merge 113 | (s/keys :req-un [::port] 114 | :opt-un 115 | [::host 116 | ::tls-keys-file 117 | ::background-print]) 118 | :rebel-readline.tools/arg-map)) 119 | 120 | (defn start-repl [options] 121 | (try 122 | (start-repl* 123 | (merge 124 | clj-line-reader/default-config 125 | (tools/user-config ::arg-map options) 126 | {:background-print true} 127 | (s/conform ::arg-map options))) 128 | (catch clojure.lang.ExceptionInfo e 129 | (let [{:keys [spec config] :as err} (ex-data e)] 130 | (if (-> err :type (= :rebel-readline/config-spec-error)) 131 | (tools/explain-config spec config) 132 | (throw e)))))) 133 | 134 | ;; CLI 135 | 136 | (def cli-options 137 | ;; An option with a required argument 138 | (vec 139 | (concat 140 | [["-p" "--port PORT" "nREPL server Port number" 141 | :parse-fn #(Long/parseLong %) 142 | :required "PORT" 143 | :default-desc "7888" 144 | :missing "Must supply a -p PORT to connect to" 145 | :validate [#(< 0 % 0x10000) "Must be a number between 0 and 65536"]] 146 | ["-H" "--host HOST" "nREPL Server host" 147 | :default "localhost" 148 | :validate [string? "Must be a string"]] 149 | [nil "--tls-keys-file KEYFILE" "client keys file to connect via TLS" 150 | :default-desc "client.keys" 151 | :parse-fn (comp str tools/absolutize-file) 152 | :validate [#(.exists (io/file %)) "Must be a valid path to a readable file"]] 153 | [nil "--no-background-print" "Disable background threads from printing" 154 | :id :background-print 155 | :update-fn (constantly false)]] 156 | rebel-readline.main/cli-options))) 157 | 158 | (defn usage [options-summary] 159 | (->> ["rebel-readline nREPL: An enhanced client for Clojure hosted nREPL servers" 160 | "" 161 | "This is a readline enhanced REPL client intended to connect to a " 162 | "nREPL servers hosed by a Clojure dilect that includes the base" 163 | "nREPL middleware." 164 | "" 165 | "See the full README at" 166 | "at https://github.com/bhauman/rebel-readline-nrepl" 167 | "" 168 | "Usage: clojure -M -m rebel-readline.nrepl.main --port 50668" 169 | "" 170 | "Options:" 171 | options-summary] 172 | (string/join \newline))) 173 | 174 | (defn error-msg [errors] 175 | (str "The following errors occurred while parsing your command:\n\n" 176 | (string/join "\n" errors))) 177 | 178 | (defn validate-args 179 | [args] 180 | (let [{:keys [options arguments errors summary]} 181 | (cli/parse-opts args cli-options :no-defaults true)] 182 | (cond 183 | (:help options) 184 | {:exit-message (usage summary) :ok? true} 185 | errors 186 | {:exit-message (error-msg errors)} 187 | :else 188 | {:options options}))) 189 | 190 | (defn exit [status msg] 191 | (println msg) 192 | (System/exit status)) 193 | 194 | (defn -main [& args] 195 | (let [{:keys [options exit-message ok?]} (validate-args args)] 196 | (if exit-message 197 | (exit (if ok? 0 1) exit-message) 198 | (start-repl options)))) 199 | 200 | #_(-main "--port" "55" "--background-print-off") 201 | 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rebel Readline 2 | 3 | [![Clojars Project](https://img.shields.io/clojars/v/com.bhauman/rebel-readline.svg)](https://clojars.org/com.bhauman/rebel-readline) 4 | [![Clojars Project](https://img.shields.io/clojars/v/com.bhauman/rebel-readline-cljs.svg)](https://clojars.org/com.bhauman/rebel-readline-cljs) 5 | [![Clojars Project](https://img.shields.io/clojars/v/com.bhauman/rebel-readline-nrepl.svg)](https://clojars.org/com.bhauman/rebel-readline-nrepl) 6 | 7 | Welcome to **Rebel Readline** – the snazzy terminal REPL for Clojure! 8 | 9 | [![asciicast](https://asciinema.org/a/160597.png)](https://asciinema.org/a/160597) 10 | 11 | ## Features 12 | 13 | Rebel Readline offers a Clojure REPL with: 14 | 15 | - Easy multi-line editing 16 | - Auto-indentation 17 | - TAB completion 18 | - Argument documentation displayed after typing a function name 19 | - Inline evaluation, allowing you to evaluate code without pressing enter 20 | - Quick access to documentation, source, and apropos for the symbol under the cursor 21 | - Familiar terminal key bindings, including history search, etc. 22 | 23 | Rebel Readline is also a library that provides a line reader for Clojure dialects. 24 | 25 | ## Purpose 26 | 27 | Learn more about the motivations behind creating this terminal readline library [here](https://github.com/bhauman/rebel-readline/blob/master/rebel-readline/doc/intro.md). 28 | 29 | ## nREPL Support 30 | 31 | Recent updates include nREPL support. For details, refer to [rebel-readline-nrepl](./rebel-readline-nrepl). 32 | 33 | ## Important Note 34 | 35 | The line reader requires direct terminal access. Therefore, do not launch Rebel Readline using `clj` or any other readline processes (like `rlwrap`) to avoid conflicts. Use one of the following options to start the JVM: 36 | 37 | - The java command 38 | - The Clojure `clojure` tool (without readline support) 39 | - `lein trampoline` 40 | - `boot` (must run in Boot's worker pod) 41 | 42 | ## Quick Start 43 | 44 | To quickly try Rebel Readline, [install the Clojure CLI tools](https://clojure.org/guides/getting_started) and execute: 45 | 46 | ```shell 47 | clojure -Sdeps "{:deps {com.bhauman/rebel-readline {:mvn/version \"0.1.5\"}}}" -M -m rebel-readline.main 48 | ``` 49 | 50 | ## Usage 51 | 52 | ### Key Bindings in the REPL 53 | 54 | Rebel Readline defaults to Emacs-style key bindings, which can be configured. 55 | 56 | #### Notable Key Bindings: 57 | 58 | - **Ctrl-C**: Abort the current line 59 | - **Ctrl-D** at line start: Send an end-of-stream signal (usually quits the REPL) 60 | - **TAB**: Word completion or code indentation 61 | - **Ctrl-X Ctrl-D**: Show documentation for the current symbol 62 | - **Ctrl-X Ctrl-S**: Show source code for the current symbol 63 | - **Ctrl-X Ctrl-A**: Show apropos information for the current symbol 64 | - **Ctrl-X Ctrl-E**: Inline evaluation for SEXP 65 | 66 | You can explore additional key bindings with the `:repl/key-bindings` command. 67 | 68 | ### Commands in the REPL 69 | 70 | Commands start with the `:repl/...` keyword. For available commands, type `:repl/help` or `:repl` followed by TAB. 71 | 72 | You can add new commands by implementing methods for the `rebel-readline.commands/command` multimethod and documenting them using `rebel-readline.commands/command-doc`. 73 | 74 | ## Installation 75 | 76 | Add Rebel Readline as a tool within your `~/.clojure/deps.edn`: 77 | 78 | ```clojure 79 | { 80 | ... 81 | :aliases {:rebel {:extra-deps {com.bhauman/rebel-readline {:mvn/version "0.1.5"}} 82 | :exec-fn rebel-readline.tool/repl 83 | :exec-args {} 84 | :main-opts ["-m" "rebel-readline.main"]}} 85 | ... 86 | } 87 | ``` 88 | 89 | You can then launch the REPL in your project directory with: 90 | 91 | ```shell 92 | clojure -Xrebel 93 | ``` 94 | Remember to use `clojure` instead of `clj` to avoid interference from other readline tools. 95 | 96 | Alternatively, run it as a standalone tool: 97 | 98 | ```shell 99 | clojure -T:rebel 100 | ``` 101 | 102 | ## CLI Parameters 103 | 104 | You can pass [Configurable Parameters](#config) when launching the REPL: 105 | 106 | ```shell 107 | clojure -Xrebel :highlight false 108 | ``` 109 | 110 | It's also possible to specify parameters in the `:exec-args` key of your `~/.clojure/deps.edn`. 111 | 112 | ## CLI Usage with `rebel-readline.main` 113 | 114 | You can also launch with the `rebel-readline.main` CLI. With the 115 | configuration above you can use: 116 | 117 | ```shell 118 | clojure -Mrebel --no-highlight 119 | ``` 120 | 121 | ```shell 122 | Options: 123 | -h, --help Display help 124 | -k, --key-map KEYMAP :emacs Choose between :viins or :emacs 125 | -t, --color-theme THEME :dark-screen-theme :(light, dark, or neutral)-screen-theme 126 | --no-highlight Disable syntax highlighting 127 | --no-completion Disable code completion 128 | --no-eldoc Disable function documentation display 129 | --no-indent Disable auto indentation 130 | --no-redirect-output Disable output redirection 131 | -b, --key-bindings BINDINGS Specify custom key bindings 132 | -c, --config CONFIG Path to a config file 133 | ``` 134 | 135 | ## Installing with Leiningen 136 | 137 | Add the dependency to your `project.clj`: 138 | 139 | ```clojure 140 | [com.bhauman/rebel-readline "0.1.5"] 141 | ``` 142 | 143 | Start the REPL with: 144 | 145 | ```shell 146 | lein trampoline run -m rebel-readline.main 147 | ``` 148 | 149 | You can also add it to `$HOME/.lein/profiles.clj` so you don't have to 150 | add it to individual projects. 151 | 152 | To simplify REPL launches, create an alias in `project.clj`: 153 | 154 | ```clojure 155 | :aliases {"rebl" ["trampoline" "run" "-m" "rebel-readline.main"]} 156 | ``` 157 | 158 | This lets you start the REPL using `lein rebl`. 159 | 160 | ## Boot Integration 161 | 162 | Start Rebel Readline using Boot with: 163 | 164 | ```shell 165 | boot -d com.bhauman/rebel-readline call -f rebel-readline.main/-main 166 | ``` 167 | 168 | ## Default to vi Bindings 169 | 170 | You can set vi key bindings either in your `deps.edn` or in `~/.clojure/rebel_readline.edn`: 171 | 172 | ```clojure 173 | {:key-map :viins} 174 | ``` 175 | 176 | ## Configuration 177 | 178 | You can provide various configurable options in your `deps.edn` or `~/.clojure/rebel_readline.edn`: 179 | 180 | ```clojure 181 | :config - path to an edn configuration file 182 | 183 | :key-map - :viins or :emacs (default: :emacs) 184 | 185 | :color-theme - (:light, :dark or :neutral)-screen-theme 186 | 187 | :highlight - (boolean) enable syntax highlighting (default: true) 188 | 189 | :completion - (boolean) enable code completion (default: true) 190 | 191 | :eldoc - (boolean) enable function documentation display (default: true) 192 | 193 | :indent - (boolean) enable auto indentation (default: true) 194 | 195 | :redirect-output - (boolean) rebinds output during read (default: true) 196 | 197 | :key-bindings - map of key bindings to apply after others 198 | ``` 199 | 200 | ### Key Binding Configuration 201 | 202 | To configure key bindings, use your configuration file. Ensure correct 203 | serialization of key names. 204 | 205 | ## Using Rebel Readline as a Readline Library 206 | 207 | Rebel Readline can replace the `clojure.main/repl-read` behavior: 208 | 209 | ```clojure 210 | (rebel-readline.core/with-line-reader 211 | (rebel-readline.clojure.line-reader/create 212 | (rebel-readline.clojure.service.local/create {:highlight false})) 213 | (clojure.main/repl 214 | :prompt (fn []) ;; prompt is handled by line-reader 215 | :read (rebel-readline.clojure.main/create-repl-read))) 216 | ``` 217 | 218 | You can also use `rebel-readline.core/with-readline-in` for easier wrapping: 219 | 220 | ```clojure 221 | (rebel-readline.core/with-readline-in 222 | (rebel-readline.clojure.line-reader/create 223 | (rebel-readline.clojure.service.local/create)) 224 | (clojure.main/repl :prompt (fn []))) 225 | ``` 226 | 227 | ## Services 228 | 229 | The line reader provides capabilities like completion, documentation, and evaluation through a service. The common service is `rebel-readline.services.clojure.local`, which queries the local Clojure process. 230 | 231 | For environments without a suitable service, you could use `clojure.service.local` or `clojure.service.simple`, though with less optimal results. 232 | 233 | ## CLJS Support 234 | 235 | For ClojureScript, visit [this repository section](https://github.com/bhauman/rebel-readline/tree/master/rebel-readline-cljs). 236 | 237 | ## SocketREPL and pREPL Support 238 | 239 | Currently, services for SocketREPL and pREPL are not available. 240 | 241 | ## Contributing 242 | 243 | We welcome contributions! Look for issues marked `help wanted` for good starting points. 244 | 245 | When contributing: 246 | 247 | - File an issue for non-trivial changes before creating a PR. 248 | - Consolidate PR changes into one commit. 249 | - Make changes small and easy to understand; this allows for better review. 250 | - Break larger solutions into manageable PRs. 251 | - Communicate if a PR represents more exploratory efforts. 252 | 253 | If you need assistance on what to work on, feel free to reach out on the Clojurians Slack channel. 254 | 255 | ## License 256 | 257 | Copyright © 2018 Bruce Hauman 258 | 259 | Distributed under the Eclipse Public License, version 1.0 or (at your option) any later version. 260 | -------------------------------------------------------------------------------- /rebel-readline/src/rebel_readline/commands.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-readline.commands 2 | (:require 3 | [clojure.pprint :refer [pprint]] 4 | [clojure.string :as string] 5 | [clojure.repl] 6 | [rebel-readline.jline-api :as api] 7 | [rebel-readline.jline-api.attributed-string :as astring] 8 | [rebel-readline.tools :as tools :refer [color]]) 9 | (:import 10 | [org.jline.utils AttributedStringBuilder AttributedString AttributedStyle] 11 | [org.jline.reader LineReader EndOfFileException])) 12 | 13 | (defmulti command first) 14 | (defmulti command-doc identity) 15 | 16 | (defmethod command :default [[com]] 17 | (println "No command" (pr-str com) "found.")) 18 | 19 | (defmethod command-doc :repl/toggle-indent [_] 20 | "Toggle the automatic indenting of Clojure code on and off.") 21 | 22 | (defmethod command :repl/toggle-indent [_] 23 | (swap! api/*line-reader* update :indent #(not %)) 24 | (if (:indent @api/*line-reader*) 25 | (println "Indenting on!") 26 | (println "Indenting off!"))) 27 | 28 | (defmethod command-doc :repl/toggle-highlight [_] 29 | "Toggle readline syntax highlighting on and off. See 30 | `:repl/toggle-color` if you want to turn color off completely.") 31 | 32 | (defmethod command :repl/toggle-highlight [_] 33 | (swap! api/*line-reader* update :highlight #(not %)) 34 | (if (:highlight @api/*line-reader*) 35 | (println "Highlighting on!") 36 | (println "Highlighting off!"))) 37 | 38 | (defmethod command-doc :repl/toggle-eldoc [_] 39 | "Toggle the auto display of function signatures on and off.") 40 | 41 | (defmethod command :repl/toggle-eldoc [_] 42 | (swap! api/*line-reader* update :eldoc #(not %)) 43 | (if (:eldoc @api/*line-reader*) 44 | (println "Eldoc on!") 45 | (println "Eldoc off!"))) 46 | 47 | (defmethod command-doc :repl/toggle-completion [_] 48 | "Toggle the completion functionality on and off.") 49 | 50 | (defmethod command :repl/toggle-completion [_] 51 | (swap! api/*line-reader* update :completion #(not %)) 52 | (if (:completion @api/*line-reader*) 53 | (println "Completion on!") 54 | (println "Completion off!"))) 55 | 56 | (defmethod command-doc :repl/toggle-color [_] 57 | "Toggle ANSI text coloration on and off.") 58 | 59 | (defmethod command :repl/toggle-color [_] 60 | (let [{:keys [color-theme backup-color-theme]} @api/*line-reader*] 61 | (cond 62 | (and (nil? color-theme) 63 | (some? backup-color-theme) 64 | (tools/color-themes backup-color-theme)) 65 | (do (println "Activating color, using theme: " backup-color-theme) 66 | (swap! api/*line-reader* assoc :color-theme backup-color-theme)) 67 | (nil? color-theme) 68 | (do 69 | (swap! api/*line-reader* assoc :color-theme :dark-screen-theme) 70 | (println "Activating color, theme not found, defaulting to " :dark-screen-theme)) 71 | (some? color-theme) 72 | (do 73 | (swap! api/*line-reader* assoc :backup-color-theme color-theme) 74 | (swap! api/*line-reader* dissoc :color-theme) 75 | (println "Color deactivated!"))))) 76 | 77 | (defmethod command-doc :repl/set-color-theme [_] 78 | (str "Change the color theme to one of the available themes:" 79 | "\n" 80 | (with-out-str 81 | (pprint (keys tools/color-themes))))) 82 | 83 | (defmethod command :repl/set-color-theme [[_ new-theme]] 84 | (let [new-theme (keyword new-theme)] 85 | (if-not (tools/color-themes new-theme) 86 | (println 87 | (str (pr-str new-theme) " is not a known color theme, please choose one of:" 88 | "\n" 89 | (with-out-str 90 | (pprint (keys tools/color-themes))))) 91 | (swap! api/*line-reader* assoc :color-theme new-theme)))) 92 | 93 | (defmethod command-doc :repl/key-bindings [_] 94 | "With an argument displays a search of the current key bindings 95 | Without any arguments displays all the current key bindings") 96 | 97 | (defn readable-key-bindings [km] 98 | (map 99 | (fn [[k v]] [(if (= k (pr-str "^I")) 100 | (pr-str "TAB") 101 | k) v]) 102 | km)) 103 | 104 | (defn classify-keybindings [km] 105 | (let [res (group-by (fn [[k v]] 106 | (and (string? v) 107 | (.startsWith v "clojure-"))) km)] 108 | (-> res 109 | (assoc :clojure (get res true)) 110 | (assoc :other (get res false)) 111 | (dissoc true false)))) 112 | 113 | (defn display-key-bindings [search & groups] 114 | (let [km (get (.getKeyMaps api/*line-reader*) "main") 115 | key-data (filter 116 | (if search 117 | (fn [[k v]] 118 | (or (.contains k (name search)) 119 | (.contains v (name search)))) 120 | identity) 121 | (api/key-map->display-data km)) 122 | binding-groups (classify-keybindings (readable-key-bindings key-data)) 123 | binding-groups (if (not-empty groups) 124 | (select-keys binding-groups groups) 125 | binding-groups)] 126 | (when-let [map-name (api/main-key-map-name)] 127 | (println (api/->ansi (astring/astr 128 | ["Current key map: " (.bold AttributedStyle/DEFAULT)] 129 | [(pr-str (keyword map-name)) (color :font-lock/core-form)])))) 130 | (if (and search (empty? key-data)) 131 | (println "Binding search: No bindings found that match" (pr-str (name search))) 132 | (doseq [[k data] binding-groups] 133 | (when (not-empty data) 134 | (println 135 | (api/->ansi 136 | (astring/astr 137 | [(format "%s key bindings:" (string/capitalize (name k))) 138 | (.bold AttributedStyle/DEFAULT)]))) 139 | (println 140 | (string/join "\n" 141 | (map (fn [[k v]] 142 | (format " %-12s%s" k v)) 143 | data)))))))) 144 | 145 | (defmethod command :repl/key-bindings [[_ search]] 146 | (display-key-bindings search)) 147 | 148 | (defmethod command-doc :repl/set-key-map [_] 149 | (let [map-names (->> (api/key-maps) 150 | keys 151 | (filter (complement #{".safe" 152 | "visual" 153 | "main" 154 | "menu" 155 | "viopp"})) 156 | sort 157 | (map keyword) 158 | pr-str)] 159 | (str "Changes the key bindings to the given key-map. Choose from: " 160 | map-names))) 161 | 162 | (defn set-key-map! [key-map-name] 163 | (boolean 164 | (when (and key-map-name (api/set-main-key-map! (name key-map-name))) 165 | ;; its dicey to have parallel state like this so 166 | ;; we are just going to have the service config 167 | ;; state reflect the jline reader state 168 | (swap! api/*line-reader* 169 | assoc :key-map (keyword (api/main-key-map-name)))))) 170 | 171 | (defmethod command :repl/set-key-map [[_ key-map-name]] 172 | (if (set-key-map! key-map-name) 173 | (println "Changed key map to" (pr-str key-map-name)) 174 | (println "Failed to change key map!"))) 175 | 176 | ;; TODO this should be here the underlying repl should handle this 177 | ;; or consider a cross repl solution that works 178 | ;; maybe something you can put in service interface 179 | (defmethod command-doc :repl/quit [_] 180 | "Quits the REPL. This may only work in certain contexts.") 181 | 182 | (defmethod command :repl/quit [_] 183 | (println "Bye!") 184 | ;; request exit 185 | (throw (EndOfFileException.))) 186 | 187 | (defn handle-command [command-str] 188 | (let [cmd? 189 | (try (read-string (str "[" command-str "]")) 190 | (catch Throwable e 191 | []))] 192 | (if (and (keyword? (first cmd?)) 193 | (= "repl" (namespace (first cmd?)))) 194 | (do (command cmd?) true) 195 | false))) 196 | 197 | (defn all-commands [] 198 | (filter #(= (namespace %) "repl") 199 | (keys (.getMethodTable command)))) 200 | 201 | (defmethod command-doc :repl/help [_] 202 | "Prints the documentation for all available commands.") 203 | 204 | (defmethod command :repl/help [_] 205 | (display-key-bindings nil :clojure) 206 | (println 207 | (api/->ansi 208 | (apply 209 | astring/astr 210 | ["Available Commands:" (.bold AttributedStyle/DEFAULT)] 211 | "\n" 212 | (keep 213 | #(when-let [doc (command-doc %)] 214 | (astring/astr 215 | " " 216 | [(prn-str %) (color :widget/anchor)] 217 | (string/join 218 | "\n" 219 | (map (fn [x] (str " " x)) 220 | (string/split-lines doc))) 221 | "\n")) 222 | (sort (all-commands))))))) 223 | 224 | (defmethod command-doc :repl/*e [_] 225 | "Prints last exception that occured internally to the Rebel 226 | Readline. Takes a single argument n the number of lines of the 227 | stacktrace to print. CAREFUL this shouldn't ever hold errors caused 228 | by evaluated code you should use *e* to inspect those. This is mainly 229 | to diagnose problems in the toolchain. ") 230 | 231 | (defmethod command :repl/*e [n] 232 | (some-> api/*line-reader* 233 | deref 234 | :repl/error 235 | deref 236 | (clojure.repl/pst (or (and 237 | (number? (second n)) 238 | (second n)) 239 | 12)))) 240 | 241 | (defn add-command [command-keyword action-fn doc] 242 | {:pre [(keyword? command-keyword) 243 | (fn? action-fn) 244 | (string? doc)]} 245 | (defmethod command command-keyword [_] (action-fn)) 246 | (defmethod command-doc command-keyword [_] doc)) 247 | -------------------------------------------------------------------------------- /rebel-readline-nrepl/src/rebel_readline/nrepl/service/nrepl.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-readline.nrepl.service.nrepl 2 | (:require 3 | [rebel-readline.clojure.line-reader :as clj-reader] 4 | [rebel-readline.tools :as tools] 5 | [rebel-readline.clojure.service.local :as service-local] 6 | [rebel-readline.jline-api.attributed-string :as as] 7 | [rebel-readline.clojure.utils :as clj-utils] 8 | [clojure.string :as string] 9 | [clojure.main] 10 | [nrepl.core :as nrepl] 11 | [nrepl.misc :as nrepl.misc]) 12 | (:import 13 | [java.util.concurrent LinkedBlockingQueue TimeUnit])) 14 | 15 | (derive ::service ::clj-reader/clojure) 16 | 17 | ;; callback system 18 | (defn add-callback! [{:keys [::state]} id f] 19 | (swap! state assoc-in [:id-callbacks id] f)) 20 | 21 | (defn remove-callback! [{:keys [::state]} id] 22 | (swap! state update :id-callbacks dissoc id)) 23 | 24 | (defn set-current-eval-id! [{:keys [::state]} id] 25 | (swap! state assoc :current-eval-id id)) 26 | 27 | (defn remove-current-eval-id! [{:keys [::state]}] 28 | (swap! state dissoc :current-eval-id)) 29 | 30 | (defn dispatch-response! [{:keys [::state] :as service} msg] 31 | (doseq [f (vals (get @state :id-callbacks))] 32 | (f msg))) 33 | 34 | ;; message callback-api 35 | (defn new-id [] (nrepl.misc/uuid)) 36 | 37 | (defn select-key [key shouldbe k] 38 | (fn [msg] 39 | (when (= (get msg key) shouldbe) 40 | (k msg)))) 41 | 42 | (defn on-key [key callback k] 43 | (fn [msg] 44 | (when-let [v (get msg key)] 45 | (callback v)) 46 | (k msg))) 47 | 48 | (defn session-id [session' id' k] 49 | (->> k 50 | (select-key :id id') 51 | (select-key :session session'))) 52 | 53 | (defn out-err [print-out print-err k] 54 | (->> k 55 | (on-key :out print-out) 56 | (on-key :err print-err))) 57 | 58 | (defn value [callback k] 59 | (on-key :value callback k)) 60 | 61 | (defn handle-statuses [pred callback k] 62 | (fn [{:keys [status] :as msg}] 63 | (when (some pred status) 64 | (callback msg)) 65 | (k msg))) 66 | 67 | (defn done [callback k] 68 | (handle-statuses #{"done" "interrupt"} callback k)) 69 | 70 | (defn error [callback k] 71 | (handle-statuses #{"error" "eval-error"} callback k)) 72 | 73 | (defn need-input [callback k] 74 | (handle-statuses #{"need-input"} callback k)) 75 | 76 | (defn send-msg! [{:keys [::state] :as service} {:keys [session id] :as msg} callback] 77 | (assert session) 78 | (assert id) 79 | (add-callback! 80 | service id 81 | (->> callback 82 | (error (fn [_] (remove-callback! service id))) 83 | (done (fn [_] (remove-callback! service id))) 84 | (session-id session id))) 85 | (tap> msg) 86 | (nrepl.transport/send (:conn @state) msg)) 87 | 88 | (defn eval-session [{:keys [::state]}] 89 | (get @state :session)) 90 | 91 | (defn tool-session [{:keys [::state]}] 92 | (get @state :tool-session)) 93 | 94 | (defn new-message [{:keys [::state] :as service} msg] 95 | (merge 96 | {:session (eval-session service) 97 | :id (new-id) 98 | :ns (:current-ns @state)} 99 | msg)) 100 | 101 | (defn new-tool-message [service msg] 102 | (new-message 103 | service 104 | (merge {:session (tool-session service)} 105 | msg))) 106 | 107 | (defn eval-code [{:keys [::state] :as service} code-str k] 108 | (let [{:keys [id] :as message} 109 | (new-message service {:op "eval" :code code-str}) 110 | prom (promise) 111 | finish (fn [_] 112 | (deliver prom ::done) 113 | (remove-current-eval-id! service))] 114 | (set-current-eval-id! service id) 115 | (send-msg! service 116 | message 117 | (->> k 118 | (on-key :ns #(swap! state assoc :current-ns %)) 119 | (done finish) 120 | (error finish))) 121 | @prom)) 122 | 123 | (defn interrupt [{:keys [::state] :as service}] 124 | ;; TODO having a timeout and then calling the 125 | ;; callback with a done message could prevent 126 | ;; terminal lockup in extreme cases 127 | (let [{:keys [current-eval-id]} @state] 128 | (when current-eval-id 129 | (send-msg! 130 | service 131 | (new-message service {:op "interrupt" :interrupt-id current-eval-id}) 132 | identity)))) 133 | 134 | (defn lookup [{:keys [::state] :as service} symbol] 135 | (let [prom (promise)] 136 | (send-msg! service 137 | (new-tool-message service {:op "lookup" :sym symbol}) 138 | (->> identity 139 | (done #(deliver prom 140 | (some-> % 141 | :info 142 | not-empty 143 | (update :arglists clojure.edn/read-string)))))) 144 | (deref prom 400 nil))) 145 | 146 | (defn completions [{:keys [::state] :as service } prefix] 147 | (let [prom (promise)] 148 | (send-msg! service 149 | (new-tool-message service {:op "completions" :prefix prefix}) 150 | (->> identity 151 | (done #(deliver prom (get % :completions))))) 152 | (deref prom 400 nil))) 153 | 154 | (defn tool-eval-code [service code-str] 155 | (let [prom (promise)] 156 | (send-msg! service 157 | (new-tool-message service {:op "eval" :code code-str}) 158 | (->> identity 159 | (value #(deliver prom %)))) 160 | (deref prom 400 nil))) 161 | 162 | (defn ls-middleware [{:keys [state] :as service}] 163 | (let [prom (promise)] 164 | (send-msg! service 165 | (new-tool-message service {:op "ls-middleware"}) 166 | (->> identity 167 | (on-key :middleware #(deliver prom %)))) 168 | (deref prom 400 nil))) 169 | 170 | (defn send-input [{:keys [::state] :as service} input] 171 | (send-msg! service 172 | (new-message service {:op "stdin" :stdin (when input 173 | (str input "\n"))}) 174 | identity)) 175 | 176 | (defmethod clj-reader/-resolve-meta ::service [self var-str] 177 | (lookup self var-str)) 178 | 179 | (defmethod clj-reader/-current-ns ::service [{:keys [::state] :as self}] 180 | (:current-ns @state)) 181 | 182 | (defmethod clj-reader/-source ::service [self var-str] 183 | (some->> (pr-str `(clojure.repl/source-fn (symbol ~var-str))) 184 | (tool-eval-code self) 185 | read-string 186 | (hash-map :source))) 187 | 188 | (defmethod clj-reader/-apropos ::service [self var-str] 189 | (some->> (pr-str `(clojure.repl/apropos ~var-str)) 190 | (tool-eval-code self) 191 | read-string)) 192 | 193 | (defmethod clj-reader/-complete ::service [self word options] 194 | (some->> (completions self word) 195 | (map #(update % :type keyword)))) 196 | 197 | (defmethod clj-reader/-doc ::service [self var-str] 198 | (when-let [{:keys [ns name arglists doc]} (lookup self var-str)] 199 | (when doc 200 | (let [url (clj-utils/url-for (str ns) (str name))] 201 | (cond-> {:doc (str ns "/" name "\n" 202 | arglists "\n " doc)} 203 | url (assoc :url url)))))) 204 | 205 | (defmethod clj-reader/-eval ::service [self form] 206 | (let [res (atom {}) 207 | prom (promise)] 208 | (send-msg! 209 | self 210 | (new-tool-message self {:op "eval" :code (pr-str form)}) 211 | (->> identity 212 | (out-err #(swap! res update :out str %) 213 | #(swap! res update :err str %)) 214 | (value (fn [x] (deliver prom (assoc @res :printed-result x)))))) 215 | (deref prom 5000 :timed-out))) 216 | 217 | (defmethod clj-reader/-read-string ::service [self form-str] 218 | (service-local/default-read-string form-str)) 219 | 220 | (defn stop-polling [{:keys [::state]}] 221 | (swap! state dissoc :response-poller)) 222 | 223 | (defn polling? [{:keys [::state]}] 224 | (:response-poller @state)) 225 | 226 | (defn poll-for-responses [{:keys [::state] :as options} conn] 227 | (loop [] 228 | (when (polling? options) 229 | (let [continue 230 | (try 231 | (when-let [{:keys [id out err value ns session] :as resp} 232 | (nrepl.transport/recv conn 100)] 233 | (tap> resp) 234 | (dispatch-response! options resp)) 235 | :success 236 | (catch java.io.IOException e 237 | (println (class e)) 238 | (println (ex-message e)) 239 | ;; this will stop the loop here and the 240 | ;; main repl loop which queries polling? 241 | (stop-polling options)) 242 | (catch Throwable e 243 | (println "Internal REPL Error: this shouldn't happen. :repl/*e for stacktrace") 244 | (some-> options :repl/error (reset! e)) 245 | (clojure.main/repl-caught e) 246 | :success))] 247 | (when (= :success continue) 248 | (recur)))))) 249 | 250 | (defn start-polling [{:keys [::state] :as service}] 251 | (let [response-poller (Thread. ^Runnable (bound-fn [] (poll-for-responses service (:conn @state))))] 252 | (swap! state assoc :response-poller response-poller) 253 | (doto ^Thread response-poller 254 | (.setName "Rebel Readline nREPL response poller") 255 | (.setDaemon true) 256 | (.start)))) 257 | 258 | (defn register-background-printing [line-reader] 259 | (let [{:keys [::state background-print] :as service} @line-reader] 260 | (when background-print 261 | (add-callback! 262 | service 263 | :background-printing 264 | (->> identity 265 | (out-err #(do (print %) (flush)) 266 | #(do (print %) (flush))) 267 | (select-key :session (:session @state))))))) 268 | 269 | (defn create 270 | ([] (create nil)) 271 | ([config] 272 | (let [conn (nrepl/connect 273 | (select-keys config [:port :host :tls-keys-file])) 274 | client (nrepl/client conn Long/MAX_VALUE) 275 | session (nrepl/new-session client) 276 | tool-session (nrepl/new-session client)] 277 | (assoc config 278 | :rebel-readline.service/type ::service 279 | :repl/error (atom nil) 280 | ::state (atom {:conn conn 281 | :current-ns "user" 282 | :client client 283 | :session session 284 | :tool-session tool-session}))))) 285 | 286 | #_(add-tap (fn [x] (prn x))) 287 | 288 | #_(let [out *out* 289 | service (create {:port 1667})] 290 | (start-polling service) 291 | #_(swap! (::state service) assoc :command-id (nrepl.misc/uuid)) 292 | (try 293 | (let [res (completions service "ma")] 294 | 295 | res 296 | #_(::state service) 297 | ) 298 | (finally 299 | (stop-polling service)))) 300 | 301 | 302 | #_(add-tap (fn [x] (spit "DEBUG.log" (prn-str x) :append true))) 303 | -------------------------------------------------------------------------------- /rebel-readline/src/rebel_readline/core.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-readline.core 2 | (:refer-clojure :exclude [read-line]) 3 | (:require 4 | [clojure.string :as string] 5 | [rebel-readline.commands :as commands] 6 | [rebel-readline.io.callback-reader] 7 | [rebel-readline.jline-api :as api] 8 | [rebel-readline.tools :as tools] 9 | [rebel-readline.utils :as utils]) 10 | (:import 11 | [org.jline.reader 12 | UserInterruptException 13 | EndOfFileException])) 14 | 15 | (defmacro ensure-terminal 16 | "Bind the rebel-readline.jline-api/*terminal* var to a new Jline 17 | terminal if needed, otherwise use the currently bound one. 18 | 19 | Will throw a clojure.lang.ExceptionInfo with a data payload of 20 | `{:type :rebel-readline.jline-api/bad-terminal}` if JVM wasn't 21 | launched from a terminal process. 22 | 23 | There should really only be one instance of a Jline terminal as it 24 | represents a \"connection\" to the terminal that launched JVM 25 | process. 26 | 27 | -------------------------------------------------------------------- 28 | IMPORTANT NOTE: 29 | -------------------------------------------------------------------- 30 | 31 | This function will attempt to manipulate the terminal that initiated 32 | the JVM process. For this reason it is important to start your JVM 33 | in a terminal. 34 | 35 | That means launching your process using the 36 | 37 | - the java command 38 | - the Clojure `clojure` tool 39 | - lein trampoline 40 | - boot - would need to run in boot's worker pod 41 | 42 | Launching from a process initiated by lein will not work and 43 | launching from a boot pod will not cut it either. 44 | 45 | The underlying Terminal manipulation code is Jline3 and it makes 46 | every effort to be compatible with a wide array of terminals. It is 47 | entirely possible that your terminal is not well supported." 48 | [& body] 49 | `(binding [rebel-readline.jline-api/*terminal* 50 | (or rebel-readline.jline-api/*terminal* (rebel-readline.jline-api/create-terminal))] 51 | ~@body)) 52 | 53 | (defmacro with-line-reader 54 | "This macro take a line-reader and binds it. It is one of the 55 | primary ways to utilize this library. You can think of the 56 | rebel-readline.jline-api/*line-reader* binding as an alternative in 57 | source that the rebel-readline.core/read-line function reads from. 58 | 59 | Example: 60 | (require '[rebel-readline.core :as rebel]) 61 | 62 | (rebel/with-line-reader 63 | (rebel-readline.clojure.line-reader/create 64 | (rebel-readline.clojure.service.local/create)) 65 | ;; optionally bind the output directly to the jline terminal 66 | ;; in such a way so that output won't corrupt the terminal 67 | ;; this is optional 68 | (binding [*out* (rebel-readline.jline-api/safe-terminal-writer)] 69 | (clojure.main/repl 70 | ;; this will create a fn that reads from the *line-reader* 71 | :read (rebel-readline.clojure.main/create-repl-read) 72 | :prompt (fn []))))" 73 | [line-reader & body] 74 | `(ensure-terminal 75 | (binding [rebel-readline.jline-api/*line-reader* ~line-reader] 76 | ~@body))) 77 | 78 | (defn help-message 79 | "Returns a help message to print before enguaging the 80 | readline. Helpful for repl development." 81 | [] 82 | "[Rebel readline] Type :repl/help for online help info") 83 | 84 | (defn read-line-opts 85 | "Like read-line, but allows overriding of the LineReader prompt, buffer, and mask parameters. 86 | 87 | :prompt 88 | Allows overriding with a cusom prompt 89 | :buffer 90 | The default value presented to the user to edit, may be null. 91 | :mask 92 | Should be set to a single character used by jline to bit-mask. 93 | Characters will not be echoed if they mask to 0 94 | Might do crazy stuff with rebel-readline, use with caution. 95 | defaults to nil (no mask) 96 | :command-executed 97 | sentinal value to be returned when a repl command is executed, otherwise a 98 | blank string will be returned when a repl command is executed. 99 | " 100 | [ & {prompt :prompt 101 | mask :mask 102 | buffer :buffer 103 | command-executed :command-executed 104 | :or {prompt nil buffer nil mask nil command-executed ""}}] 105 | 106 | (let [redirect-output? (:redirect-output @api/*line-reader*) 107 | save-out (volatile! *out*) 108 | redirect-print-writer (api/safe-terminal-writer api/*line-reader*)] 109 | (when redirect-output? 110 | (alter-var-root 111 | #'*out* 112 | (fn [root-out] 113 | (vreset! save-out root-out) 114 | redirect-print-writer))) 115 | (try 116 | (binding [*out* redirect-print-writer] 117 | ;; this is intensely disatisfying 118 | ;; but we are blocking concurrent redisplays while the 119 | ;; readline prompt is initially drawn 120 | (api/block-redisplay-millis 100) 121 | (let [res' (.readLine api/*line-reader* (or prompt (tools/prompt)) mask buffer)] 122 | (if-not (commands/handle-command res') 123 | res' 124 | command-executed))) 125 | (finally 126 | (when redirect-output? 127 | (flush) 128 | (alter-var-root #'*out* (fn [_] @save-out))))))) 129 | 130 | (defn read-line 131 | "Reads a line from the currently bound 132 | rebel-readline.jline-api/*line-reader*. If you supply the optional 133 | `command-executed` sentinal value, it will be returned when a repl 134 | command is executed, otherwise a blank string will be returned when 135 | a repl command is executed. 136 | 137 | This function activates the rebel line reader which, in turn, will put 138 | the terminal that launched the jvm process into \"raw mode\" during the 139 | readline operation. 140 | 141 | You can think of the readline operation as a launching of an editor 142 | for the brief period that the line is read. 143 | 144 | If readline service value of :redirect-output is truthy (the default 145 | value) in the supplied rebel line reader service config this 146 | function will alter the root binding of the *out* var to prevent 147 | extraneous output from corrupting the read line editors output. 148 | 149 | Once the reading is done it returns the terminal to its original 150 | settings." 151 | ;; much of this code is intended to protect the prompt. If the 152 | ;; prompt gets corrupted by extraneous output it can lead to the 153 | ;; horrible condition of the readline program thinking the cursor is 154 | ;; in a different position than it is. We try to prevent this by 155 | ;; creating a safe writer that will print the output and redraw the 156 | ;; readline, while ensuring that the printed output has a newline at 157 | ;; the end. 158 | 159 | ;; We then expand the scope of this print-writer by temorarily 160 | ;; redefining the root binding of *out* to it. 161 | 162 | ;; The idea being that we want to catch as much concurrant output as 163 | ;; possible while the readline is enguaged. 164 | [& [command-executed]] 165 | (read-line-opts :command-executed (or command-executed ""))) 166 | 167 | (defn repl-read-line 168 | "A readline function that converts the Exceptions normally thrown by 169 | org.jline.reader.impl.LineReaderImpl that signal user interrupt or 170 | the end of the parent stream into concrete sentinal objects that one 171 | can act on. 172 | 173 | This follows the pattern established by `clojure.main/repl-read` 174 | 175 | This function either returns the string read by this readline or the 176 | request-exit or request-prompt sentinal objects." 177 | [request-prompt request-exit] 178 | (try 179 | (read-line request-prompt) 180 | (catch UserInterruptException e 181 | request-prompt) 182 | (catch EndOfFileException e 183 | request-exit))) 184 | 185 | (defn has-remaining? 186 | "Takes a PushbackReader and returns true if the next character is not negative. 187 | i.e not the end of the readers stream." 188 | [pbr] 189 | (let [x (.read pbr)] 190 | (and (not (neg? x)) 191 | (do (.unread pbr x) true)))) 192 | 193 | (defn create-buffered-repl-reader-fn [create-buffered-reader-fn has-remaining-pred repl-read-fn] 194 | (fn [] 195 | (let [reader-buffer (atom (create-buffered-reader-fn ""))] 196 | (fn [request-prompt request-exit] 197 | (if (has-remaining-pred @reader-buffer) 198 | (binding [*in* @reader-buffer] 199 | (repl-read-fn request-prompt request-exit)) 200 | (let [possible-forms (repl-read-line request-prompt request-exit)] 201 | (if (#{request-prompt request-exit} possible-forms) 202 | possible-forms 203 | (if-not (string/blank? possible-forms) 204 | (do 205 | (reset! reader-buffer (create-buffered-reader-fn (str possible-forms "\n"))) 206 | (binding [*in* @reader-buffer] 207 | (repl-read-fn request-prompt request-exit))) 208 | request-prompt)))))))) 209 | 210 | (defn stream-read-line 211 | "This function reads lines and returns them ready to be read by a 212 | java.io.Reader. This basically adds newlines at the end of readline 213 | results. 214 | 215 | This function returns `nil` if it is end of the supplied readlines 216 | parent input stream or if a process exit is requested. 217 | 218 | This function was designed to be supplied to a `rebel-readline.io.calback-reader` 219 | 220 | Example: 221 | 222 | ;; this will create an input stream to be read from by a Clojure/Script REPL 223 | 224 | (rebel-readline.io.calback-reader/callback-reader #(stream-read-line))" 225 | [] 226 | (let [request-prompt (Object.) 227 | request-exit (Object.) 228 | possible-result (repl-read-line request-prompt request-exit)] 229 | (cond 230 | (= request-prompt possible-result) "\n" 231 | (= request-exit possible-result) nil 232 | :else (str possible-result "\n")))) 233 | 234 | (defmacro with-readline-in 235 | "This macro takes a rebel readline service and binds *in* to an a 236 | `clojure.lang.LineNumberingPushbackReader` that is backed by the 237 | readline. 238 | 239 | This is perhaps the easiest way to utilize this readline library. 240 | 241 | The downside to using this method is if you are working in a REPL on 242 | something that reads from the *in* that wouldn't benefit from the 243 | features of this readline lib. In that case I would look at 244 | `clj-repl-read` where the readline is only engaged during the read 245 | portion of the REPL. 246 | 247 | Examples: 248 | 249 | (with-readline-in 250 | (rebel-readline.clojure.line-reader/create 251 | (rebel-readline.clojure.service.local/create {:prompt clojure.main/repl-prompt} )) 252 | (clojure.main/repl :prompt (fn[])))" 253 | [line-reader & body] 254 | `(with-line-reader ~line-reader 255 | (binding [*in* (clojure.lang.LineNumberingPushbackReader. 256 | (rebel-readline.io.callback-reader/callback-reader 257 | stream-read-line))] 258 | ~@body))) 259 | 260 | (defn basic-line-reader [& opts] 261 | (api/create-line-reader api/*terminal* nil (apply hash-map opts))) 262 | -------------------------------------------------------------------------------- /rebel-readline/src/rebel_readline/tools.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-readline.tools 2 | (:require 3 | [rebel-readline.jline-api :as api] 4 | [rebel-readline.utils :refer [log]] 5 | [clojure.java.io :as io] 6 | [clojure.edn :as edn] 7 | [clojure.spec.alpha :as s]) 8 | (:import 9 | [org.jline.utils AttributedStringBuilder AttributedStyle] 10 | [org.jline.keymap KeyMap])) 11 | 12 | ;; ---------------------------------------------- 13 | ;; Extra Abilities 14 | ;; ---------------------------------------------- 15 | 16 | ;; Color 17 | ;; ---------------------------------------------- 18 | 19 | (def color-themes {}) 20 | 21 | (defn register-color-theme! [ky color-map] 22 | (assert (keyword? ky)) 23 | (assert (map? color-map)) 24 | (alter-var-root #'color-themes assoc ky color-map)) 25 | 26 | (defn fg-color [color] 27 | (.foreground AttributedStyle/DEFAULT color)) 28 | 29 | (defn color [sk] 30 | (-> 31 | (get @api/*line-reader* :color-theme) 32 | color-themes 33 | (get sk AttributedStyle/DEFAULT))) 34 | 35 | ;; String Highlighting 36 | ;; ---------------------------------------------- 37 | 38 | (defn highlight-tokens [color-fn tokens syntax-str] 39 | (let [sb (AttributedStringBuilder.)] 40 | (loop [pos 0 41 | hd tokens] 42 | (let [[_ start end sk] (first hd)] 43 | (cond 44 | (= (.length sb) (count syntax-str)) sb 45 | (= (-> hd first second) pos) ;; style active 46 | (do 47 | (if-let [st (color-fn sk)] 48 | (.styled sb st (subs syntax-str start end)) 49 | (.append sb (subs syntax-str start end))) 50 | (recur end (rest hd))) 51 | ;; TODO this could be faster if we append whole sections 52 | ;; instead of advancing one char at a time 53 | ;; but its pretty fast now 54 | :else 55 | (do (.append sb (.charAt syntax-str pos)) 56 | (recur (inc pos) hd))))))) 57 | 58 | (defn highlight-str [color-fn tokenizer-fn syntax-str] 59 | (highlight-tokens color-fn (tokenizer-fn syntax-str) syntax-str)) 60 | 61 | ;; Baseline service config 62 | ;; ---------------------------------------------- 63 | 64 | ;; follow tools deps conventions 65 | 66 | (defn user-config-file [] 67 | (->> [(System/getenv "CLJ_CONFIG") 68 | (some-> (System/getenv "XDG_CONFIG_HOME") 69 | (io/file "clojure")) 70 | (io/file (System/getProperty "user.home") 71 | ".clojure")] 72 | (keep identity) 73 | (map #(io/file % "rebel_readline.edn")) 74 | (filter #(.exists %)) 75 | first)) 76 | 77 | (defn translate-serialized-key-bindings [key-binding-map] 78 | (into {} 79 | (map 80 | (fn [[map-name binds]] 81 | [map-name 82 | (mapv (fn [[k v]] 83 | [(cond 84 | (string? k) 85 | (KeyMap/translate k) 86 | ;; bypass translation with a list of ints and chars 87 | (and (list? k) (every? #(or (integer? %) (char? %)) k)) 88 | (apply str (map char k)) 89 | (vector? k) 90 | (mapv #(KeyMap/translate %) k)) v]) 91 | binds)]) 92 | key-binding-map))) 93 | 94 | ;; 95 | (s/def ::key-map #{:viins :emacs}) 96 | (s/def ::completion boolean?) 97 | (s/def ::color-theme #{:light-screen-theme 98 | :dark-screen-theme 99 | :neutral-screen-theme}) 100 | (s/def ::highlight boolean?) 101 | (s/def ::eldoc boolean?) 102 | (s/def ::indent boolean?) 103 | (s/def ::redirect-output boolean?) 104 | (s/def ::key-bindings map?) 105 | 106 | (defn absolutize-file 107 | "Replaces a leading '~' in the path-string with the user's home directory, 108 | converts the string to a java.io.File, and normalizes it (collapsing any '/./' 109 | or '/../' references)." 110 | [path-string] 111 | (when path-string 112 | (-> path-string 113 | (clojure.string/replace-first #"^~" (System/getProperty "user.home")) 114 | io/file 115 | .toPath 116 | .normalize 117 | .toFile))) 118 | 119 | (defn file-exists? [file-path] 120 | (.exists (absolutize-file file-path))) 121 | 122 | #_(absolutize-file "~/workspace/rebel-readline/rebel-readline/./src/../README.md") 123 | 124 | (s/def ::config (s/and string? file-exists?)) 125 | (s/def ::arg-map (s/keys :opt-un [::key-map 126 | ::color-theme 127 | ::highlight 128 | ::completion 129 | ::eldoc 130 | ::indent 131 | ::redirect-output 132 | ::key-bindings])) 133 | 134 | (defn explain-config-header [config] 135 | (println "Arguments didn't pass spec") 136 | (println "Received these args:") 137 | (clojure.pprint/pprint config) 138 | (println "Which failed these specifications:")) 139 | 140 | (defn explain-config [spec config] 141 | (explain-config-header config) 142 | (s/explain spec config)) 143 | 144 | (defn valid-config? [spec options] 145 | (s/valid? spec (or options {}))) 146 | 147 | #_(valid-config? {:highlight 1}) 148 | 149 | (defn user-config [spec overrides] 150 | (let [cli-file (absolutize-file (:config overrides)) 151 | config-file (user-config-file)] 152 | (when-let [file (if (and cli-file (.exists cli-file)) 153 | cli-file 154 | config-file)] 155 | (try (let [config 156 | (try (edn/read-string (slurp file)) 157 | (catch Throwable e 158 | (binding [*out* *err*] 159 | (println 160 | (format "[Rebel Readline] Error reading config file %s: %s" 161 | (str file) 162 | (.getMessage e))))))] 163 | (if (valid-config? spec (merge config overrides)) 164 | (cond-> config 165 | (:key-bindings config) 166 | (update-in [:key-bindings] translate-serialized-key-bindings)) 167 | (throw (throw (ex-info "Invalid configuration" 168 | {:type :rebel-readline/config-spec-error 169 | :config (merge config overrides) 170 | :spec spec}))))))))) 171 | 172 | ;; Baseline services 173 | ;; ---------------------------------------------- 174 | 175 | (defn resolve-fn? [f] 176 | (cond 177 | (fn? f) f 178 | (or (string? f) (symbol? f)) 179 | (resolve (symbol f)) 180 | :else nil)) 181 | 182 | (defn not-implemented! [service fn-name] 183 | (throw (ex-info (format "The %s service does not implement the %s function." 184 | (pr-str (:rebel-readline.service/type service)) 185 | fn-name) 186 | {}))) 187 | 188 | (defn service-dispatch [a & args] (:rebel-readline.service/type a)) 189 | 190 | ;; Prompt 191 | ;; ---------------------------------------------- 192 | 193 | (defmulti -prompt 194 | "returns a read-line prompt string" 195 | service-dispatch) 196 | 197 | (defmethod -prompt :default [_] "") 198 | 199 | (defn prompt [] 200 | (if-let [f (resolve-fn? (:prompt @api/*line-reader*))] 201 | ;; follow the prompt function convention here 202 | (with-out-str (f)) 203 | (-prompt @api/*line-reader*))) 204 | 205 | ;; Initial Themes 206 | 207 | (def dark-screen-theme 208 | {:font-lock/string (.bold (fg-color 180)) 209 | :font-lock/comment (.bold (fg-color 243)) 210 | :font-lock/doc (.bold (fg-color 223)) 211 | :font-lock/core-form (.bold (fg-color 39)) 212 | :font-lock/function-name (.bold (fg-color 178)) 213 | :font-lock/variable-name (.bold (fg-color 85)) 214 | :font-lock/constant (.bold (fg-color 149)) 215 | :font-lock/type (.bold (fg-color 123)) 216 | :font-lock/foreign (.bold (fg-color 220)) 217 | :font-lock/builtin (.bold (fg-color 167)) 218 | 219 | :widget/half-contrast (fg-color 243) 220 | :widget/half-contrast-inverse (.inverse (fg-color 243)) 221 | 222 | ;; system widget colors 223 | :widget/eldoc-namespace (.faint (fg-color 123)) 224 | :widget/eldoc-varname (.faint (fg-color 178)) 225 | :widget/eldoc-separator (fg-color 243) 226 | :widget/arglists (fg-color 243) 227 | 228 | :widget/doc (fg-color 222) 229 | :widget/anchor (fg-color 39) 230 | :widget/light-anchor (.faint (fg-color 39)) 231 | 232 | :widget/apropos-word AttributedStyle/DEFAULT 233 | :widget/apropos-highlight (fg-color 45) 234 | :widget/apropos-namespace (.faint (fg-color 243)) 235 | 236 | :widget/warning AttributedStyle/DEFAULT 237 | :widget/error (fg-color 196) 238 | 239 | }) 240 | 241 | (register-color-theme! :dark-screen-theme dark-screen-theme) 242 | 243 | ;; TODO fix these 244 | (def light-screen-theme 245 | (assoc dark-screen-theme 246 | :font-lock/type (.bold (fg-color 28)) 247 | :font-lock/constant (.bold (fg-color 31)) 248 | :font-lock/function-name (.bold (fg-color 21)) 249 | ;:font-lock/core-form (.bold (fg-color 21)) 250 | :font-lock/variable-name (.bold (fg-color 130)) 251 | :font-lock/core-form (.bold (fg-color 127)) 252 | :font-lock/string (.bold (fg-color 127)) 253 | :font-lock/foreign (.bold (fg-color 97)) 254 | :font-lock/doc (.bold (fg-color 132)) 255 | :font-lock/comment (.bold (fg-color 247)) 256 | 257 | :widget/eldoc-namespace (fg-color 28) 258 | :widget/eldoc-varname (fg-color 21) 259 | ;:widget/eldoc-separator (fg-color 243) 260 | ;:widget/arglists (fg-color 243) 261 | 262 | :widget/doc (fg-color 238) 263 | :widget/light-anchor (.underline (.faint (fg-color 26))) 264 | 265 | :widget/apropos-word AttributedStyle/DEFAULT 266 | :widget/apropos-highlight (fg-color 27) 267 | :widget/apropos-namespace (fg-color 243))) 268 | 269 | (register-color-theme! :light-screen-theme light-screen-theme) 270 | 271 | (def neutral-screen-theme 272 | {:font-lock/string (.bold (fg-color 2)) 273 | :font-lock/comment (.bold (fg-color 8)) 274 | :font-lock/doc (.bold (fg-color 5)) 275 | :font-lock/core-form (.bold (fg-color 2)) 276 | :font-lock/function-name (.bold (fg-color 6)) 277 | :font-lock/variable-name (.bold (fg-color 3)) 278 | :font-lock/constant (.bold (fg-color 4)) 279 | :font-lock/type (.bold (fg-color 1)) 280 | :font-lock/foreign (.bold (fg-color 1)) 281 | :font-lock/builtin (.bold (fg-color 6)) 282 | 283 | :widget/half-contrast (fg-color 7) 284 | :widget/half-contrast-inverse (.inverse (fg-color 7)) 285 | 286 | ;; system widget colors 287 | :widget/eldoc-namespace (.faint (fg-color 2)) 288 | :widget/eldoc-varname (.faint (fg-color 3)) 289 | :widget/eldoc-separator (fg-color 4) 290 | :widget/arglists (fg-color 5) 291 | 292 | :widget/doc (fg-color 1) 293 | :widget/anchor (fg-color 5) 294 | :widget/light-anchor (.faint (fg-color 5)) 295 | 296 | :widget/apropos-word AttributedStyle/DEFAULT 297 | :widget/apropos-highlight (fg-color 7) 298 | :widget/apropos-namespace (.faint (fg-color 7)) 299 | 300 | :widget/warning AttributedStyle/DEFAULT 301 | :widget/error (fg-color 1)}) 302 | 303 | (register-color-theme! :neutral-screen-theme neutral-screen-theme) 304 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and 10 | documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are 19 | distributed by that particular Contributor. A Contribution 'originates' from 20 | a Contributor if it was added to the Program by such Contributor itself or 21 | anyone acting on such Contributor's behalf. Contributions do not include 22 | additions to the Program which: (i) are separate modules of software 23 | distributed in conjunction with the Program under their own license 24 | agreement, and (ii) are not derivative works of the Program. 25 | 26 | "Contributor" means any person or entity that distributes the Program. 27 | 28 | "Licensed Patents" mean patent claims licensable by a Contributor which are 29 | necessarily infringed by the use or sale of its Contribution alone or when 30 | combined with the Program. 31 | 32 | "Program" means the Contributions distributed in accordance with this 33 | Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, 36 | including all Contributors. 37 | 38 | 2. GRANT OF RIGHTS 39 | 40 | a) Subject to the terms of this Agreement, each Contributor hereby grants 41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 42 | reproduce, prepare derivative works of, publicly display, publicly perform, 43 | distribute and sublicense the Contribution of such Contributor, if any, and 44 | such derivative works, in source code and object code form. 45 | 46 | b) Subject to the terms of this Agreement, each Contributor hereby grants 47 | Recipient a non-exclusive, worldwide, royalty-free patent license under 48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 49 | transfer the Contribution of such Contributor, if any, in source code and 50 | object code form. This patent license shall apply to the combination of the 51 | Contribution and the Program if, at the time the Contribution is added by the 52 | Contributor, such addition of the Contribution causes such combination to be 53 | covered by the Licensed Patents. The patent license shall not apply to any 54 | other combinations which include the Contribution. No hardware per se is 55 | licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other 60 | intellectual property rights of any other entity. Each Contributor disclaims 61 | any liability to Recipient for claims brought by any other entity based on 62 | infringement of intellectual property rights or otherwise. As a condition to 63 | exercising the rights and licenses granted hereunder, each Recipient hereby 64 | assumes sole responsibility to secure any other intellectual property rights 65 | needed, if any. For example, if a third party patent license is required to 66 | allow Recipient to distribute the Program, it is Recipient's responsibility 67 | to acquire that license before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license 71 | set forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on 96 | or through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a 117 | manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product 119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend 120 | and indemnify every other Contributor ("Indemnified Contributor") against any 121 | losses, damages and costs (collectively "Losses") arising from claims, 122 | lawsuits and other legal actions brought by a third party against the 123 | Indemnified Contributor to the extent caused by the acts or omissions of such 124 | Commercial Contributor in connection with its distribution of the Program in 125 | a commercial product offering. The obligations in this section do not apply 126 | to any claims or Losses relating to any actual or alleged intellectual 127 | property infringement. In order to qualify, an Indemnified Contributor must: 128 | a) promptly notify the Commercial Contributor in writing of such claim, and 129 | b) allow the Commercial Contributor to control, and cooperate with the 130 | Commercial Contributor in, the defense and any related settlement 131 | negotiations. The Indemnified Contributor may participate in any such claim 132 | at its own expense. 133 | 134 | For example, a Contributor might include the Program in a commercial product 135 | offering, Product X. That Contributor is then a Commercial Contributor. If 136 | that Commercial Contributor then makes performance claims, or offers 137 | warranties related to Product X, those performance claims and warranties are 138 | such Commercial Contributor's responsibility alone. Under this section, the 139 | Commercial Contributor would have to defend claims against the other 140 | Contributors related to those performance claims and warranties, and if a 141 | court requires any other Contributor to pay any damages as a result, the 142 | Commercial Contributor must pay those damages. 143 | 144 | 5. NO WARRANTY 145 | 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR 149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A 150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the 151 | appropriateness of using and distributing the Program and assumes all risks 152 | associated with its exercise of rights under this Agreement , including but 153 | not limited to the risks and costs of program errors, compliance with 154 | applicable laws, damage to or loss of data, programs or equipment, and 155 | unavailability or interruption of operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | 159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 166 | OF SUCH DAMAGES. 167 | 168 | 7. GENERAL 169 | 170 | If any provision of this Agreement is invalid or unenforceable under 171 | applicable law, it shall not affect the validity or enforceability of the 172 | remainder of the terms of this Agreement, and without further action by the 173 | parties hereto, such provision shall be reformed to the minimum extent 174 | necessary to make such provision valid and enforceable. 175 | 176 | If Recipient institutes patent litigation against any entity (including a 177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 178 | (excluding combinations of the Program with other software or hardware) 179 | infringes such Recipient's patent(s), then such Recipient's rights granted 180 | under Section 2(b) shall terminate as of the date such litigation is filed. 181 | 182 | All Recipient's rights under this Agreement shall terminate if it fails to 183 | comply with any of the material terms or conditions of this Agreement and 184 | does not cure such failure in a reasonable period of time after becoming 185 | aware of such noncompliance. If all Recipient's rights under this Agreement 186 | terminate, Recipient agrees to cease use and distribution of the Program as 187 | soon as reasonably practicable. However, Recipient's obligations under this 188 | Agreement and any licenses granted by Recipient relating to the Program shall 189 | continue and survive. 190 | 191 | Everyone is permitted to copy and distribute copies of this Agreement, but in 192 | order to avoid inconsistency the Agreement is copyrighted and may only be 193 | modified in the following manner. The Agreement Steward reserves the right to 194 | publish new versions (including revisions) of this Agreement from time to 195 | time. No one other than the Agreement Steward has the right to modify this 196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 197 | Eclipse Foundation may assign the responsibility to serve as the Agreement 198 | Steward to a suitable separate entity. Each new version of the Agreement will 199 | be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the 201 | Agreement under which it was received. In addition, after a new version of 202 | the Agreement is published, Contributor may elect to distribute the Program 203 | (including its Contributions) under the new version. Except as expressly 204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 205 | licenses to the intellectual property of any Contributor under this 206 | Agreement, whether expressly, by implication, estoppel or otherwise. All 207 | rights in the Program not expressly granted under this Agreement are 208 | reserved. 209 | 210 | This Agreement is governed by the laws of the State of New York and the 211 | intellectual property laws of the United States of America. No party to this 212 | Agreement will bring a legal action under this Agreement more than one year 213 | after the cause of action arose. Each party waives its rights to a jury trial 214 | in any resulting litigation. 215 | -------------------------------------------------------------------------------- /rebel-readline-cljs/LICENSE: -------------------------------------------------------------------------------- 1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and 10 | documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are 19 | distributed by that particular Contributor. A Contribution 'originates' from 20 | a Contributor if it was added to the Program by such Contributor itself or 21 | anyone acting on such Contributor's behalf. Contributions do not include 22 | additions to the Program which: (i) are separate modules of software 23 | distributed in conjunction with the Program under their own license 24 | agreement, and (ii) are not derivative works of the Program. 25 | 26 | "Contributor" means any person or entity that distributes the Program. 27 | 28 | "Licensed Patents" mean patent claims licensable by a Contributor which are 29 | necessarily infringed by the use or sale of its Contribution alone or when 30 | combined with the Program. 31 | 32 | "Program" means the Contributions distributed in accordance with this 33 | Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, 36 | including all Contributors. 37 | 38 | 2. GRANT OF RIGHTS 39 | 40 | a) Subject to the terms of this Agreement, each Contributor hereby grants 41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 42 | reproduce, prepare derivative works of, publicly display, publicly perform, 43 | distribute and sublicense the Contribution of such Contributor, if any, and 44 | such derivative works, in source code and object code form. 45 | 46 | b) Subject to the terms of this Agreement, each Contributor hereby grants 47 | Recipient a non-exclusive, worldwide, royalty-free patent license under 48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 49 | transfer the Contribution of such Contributor, if any, in source code and 50 | object code form. This patent license shall apply to the combination of the 51 | Contribution and the Program if, at the time the Contribution is added by the 52 | Contributor, such addition of the Contribution causes such combination to be 53 | covered by the Licensed Patents. The patent license shall not apply to any 54 | other combinations which include the Contribution. No hardware per se is 55 | licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other 60 | intellectual property rights of any other entity. Each Contributor disclaims 61 | any liability to Recipient for claims brought by any other entity based on 62 | infringement of intellectual property rights or otherwise. As a condition to 63 | exercising the rights and licenses granted hereunder, each Recipient hereby 64 | assumes sole responsibility to secure any other intellectual property rights 65 | needed, if any. For example, if a third party patent license is required to 66 | allow Recipient to distribute the Program, it is Recipient's responsibility 67 | to acquire that license before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license 71 | set forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on 96 | or through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a 117 | manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product 119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend 120 | and indemnify every other Contributor ("Indemnified Contributor") against any 121 | losses, damages and costs (collectively "Losses") arising from claims, 122 | lawsuits and other legal actions brought by a third party against the 123 | Indemnified Contributor to the extent caused by the acts or omissions of such 124 | Commercial Contributor in connection with its distribution of the Program in 125 | a commercial product offering. The obligations in this section do not apply 126 | to any claims or Losses relating to any actual or alleged intellectual 127 | property infringement. In order to qualify, an Indemnified Contributor must: 128 | a) promptly notify the Commercial Contributor in writing of such claim, and 129 | b) allow the Commercial Contributor to control, and cooperate with the 130 | Commercial Contributor in, the defense and any related settlement 131 | negotiations. The Indemnified Contributor may participate in any such claim 132 | at its own expense. 133 | 134 | For example, a Contributor might include the Program in a commercial product 135 | offering, Product X. That Contributor is then a Commercial Contributor. If 136 | that Commercial Contributor then makes performance claims, or offers 137 | warranties related to Product X, those performance claims and warranties are 138 | such Commercial Contributor's responsibility alone. Under this section, the 139 | Commercial Contributor would have to defend claims against the other 140 | Contributors related to those performance claims and warranties, and if a 141 | court requires any other Contributor to pay any damages as a result, the 142 | Commercial Contributor must pay those damages. 143 | 144 | 5. NO WARRANTY 145 | 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR 149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A 150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the 151 | appropriateness of using and distributing the Program and assumes all risks 152 | associated with its exercise of rights under this Agreement , including but 153 | not limited to the risks and costs of program errors, compliance with 154 | applicable laws, damage to or loss of data, programs or equipment, and 155 | unavailability or interruption of operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | 159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 166 | OF SUCH DAMAGES. 167 | 168 | 7. GENERAL 169 | 170 | If any provision of this Agreement is invalid or unenforceable under 171 | applicable law, it shall not affect the validity or enforceability of the 172 | remainder of the terms of this Agreement, and without further action by the 173 | parties hereto, such provision shall be reformed to the minimum extent 174 | necessary to make such provision valid and enforceable. 175 | 176 | If Recipient institutes patent litigation against any entity (including a 177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 178 | (excluding combinations of the Program with other software or hardware) 179 | infringes such Recipient's patent(s), then such Recipient's rights granted 180 | under Section 2(b) shall terminate as of the date such litigation is filed. 181 | 182 | All Recipient's rights under this Agreement shall terminate if it fails to 183 | comply with any of the material terms or conditions of this Agreement and 184 | does not cure such failure in a reasonable period of time after becoming 185 | aware of such noncompliance. If all Recipient's rights under this Agreement 186 | terminate, Recipient agrees to cease use and distribution of the Program as 187 | soon as reasonably practicable. However, Recipient's obligations under this 188 | Agreement and any licenses granted by Recipient relating to the Program shall 189 | continue and survive. 190 | 191 | Everyone is permitted to copy and distribute copies of this Agreement, but in 192 | order to avoid inconsistency the Agreement is copyrighted and may only be 193 | modified in the following manner. The Agreement Steward reserves the right to 194 | publish new versions (including revisions) of this Agreement from time to 195 | time. No one other than the Agreement Steward has the right to modify this 196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 197 | Eclipse Foundation may assign the responsibility to serve as the Agreement 198 | Steward to a suitable separate entity. Each new version of the Agreement will 199 | be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the 201 | Agreement under which it was received. In addition, after a new version of 202 | the Agreement is published, Contributor may elect to distribute the Program 203 | (including its Contributions) under the new version. Except as expressly 204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 205 | licenses to the intellectual property of any Contributor under this 206 | Agreement, whether expressly, by implication, estoppel or otherwise. All 207 | rights in the Program not expressly granted under this Agreement are 208 | reserved. 209 | 210 | This Agreement is governed by the laws of the State of New York and the 211 | intellectual property laws of the United States of America. No party to this 212 | Agreement will bring a legal action under this Agreement more than one year 213 | after the cause of action arose. Each party waives its rights to a jury trial 214 | in any resulting litigation. 215 | -------------------------------------------------------------------------------- /rebel-readline-paredit/LICENSE: -------------------------------------------------------------------------------- 1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and 10 | documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are 19 | distributed by that particular Contributor. A Contribution 'originates' from 20 | a Contributor if it was added to the Program by such Contributor itself or 21 | anyone acting on such Contributor's behalf. Contributions do not include 22 | additions to the Program which: (i) are separate modules of software 23 | distributed in conjunction with the Program under their own license 24 | agreement, and (ii) are not derivative works of the Program. 25 | 26 | "Contributor" means any person or entity that distributes the Program. 27 | 28 | "Licensed Patents" mean patent claims licensable by a Contributor which are 29 | necessarily infringed by the use or sale of its Contribution alone or when 30 | combined with the Program. 31 | 32 | "Program" means the Contributions distributed in accordance with this 33 | Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, 36 | including all Contributors. 37 | 38 | 2. GRANT OF RIGHTS 39 | 40 | a) Subject to the terms of this Agreement, each Contributor hereby grants 41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 42 | reproduce, prepare derivative works of, publicly display, publicly perform, 43 | distribute and sublicense the Contribution of such Contributor, if any, and 44 | such derivative works, in source code and object code form. 45 | 46 | b) Subject to the terms of this Agreement, each Contributor hereby grants 47 | Recipient a non-exclusive, worldwide, royalty-free patent license under 48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 49 | transfer the Contribution of such Contributor, if any, in source code and 50 | object code form. This patent license shall apply to the combination of the 51 | Contribution and the Program if, at the time the Contribution is added by the 52 | Contributor, such addition of the Contribution causes such combination to be 53 | covered by the Licensed Patents. The patent license shall not apply to any 54 | other combinations which include the Contribution. No hardware per se is 55 | licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other 60 | intellectual property rights of any other entity. Each Contributor disclaims 61 | any liability to Recipient for claims brought by any other entity based on 62 | infringement of intellectual property rights or otherwise. As a condition to 63 | exercising the rights and licenses granted hereunder, each Recipient hereby 64 | assumes sole responsibility to secure any other intellectual property rights 65 | needed, if any. For example, if a third party patent license is required to 66 | allow Recipient to distribute the Program, it is Recipient's responsibility 67 | to acquire that license before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license 71 | set forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on 96 | or through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a 117 | manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product 119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend 120 | and indemnify every other Contributor ("Indemnified Contributor") against any 121 | losses, damages and costs (collectively "Losses") arising from claims, 122 | lawsuits and other legal actions brought by a third party against the 123 | Indemnified Contributor to the extent caused by the acts or omissions of such 124 | Commercial Contributor in connection with its distribution of the Program in 125 | a commercial product offering. The obligations in this section do not apply 126 | to any claims or Losses relating to any actual or alleged intellectual 127 | property infringement. In order to qualify, an Indemnified Contributor must: 128 | a) promptly notify the Commercial Contributor in writing of such claim, and 129 | b) allow the Commercial Contributor to control, and cooperate with the 130 | Commercial Contributor in, the defense and any related settlement 131 | negotiations. The Indemnified Contributor may participate in any such claim 132 | at its own expense. 133 | 134 | For example, a Contributor might include the Program in a commercial product 135 | offering, Product X. That Contributor is then a Commercial Contributor. If 136 | that Commercial Contributor then makes performance claims, or offers 137 | warranties related to Product X, those performance claims and warranties are 138 | such Commercial Contributor's responsibility alone. Under this section, the 139 | Commercial Contributor would have to defend claims against the other 140 | Contributors related to those performance claims and warranties, and if a 141 | court requires any other Contributor to pay any damages as a result, the 142 | Commercial Contributor must pay those damages. 143 | 144 | 5. NO WARRANTY 145 | 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR 149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A 150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the 151 | appropriateness of using and distributing the Program and assumes all risks 152 | associated with its exercise of rights under this Agreement , including but 153 | not limited to the risks and costs of program errors, compliance with 154 | applicable laws, damage to or loss of data, programs or equipment, and 155 | unavailability or interruption of operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | 159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 166 | OF SUCH DAMAGES. 167 | 168 | 7. GENERAL 169 | 170 | If any provision of this Agreement is invalid or unenforceable under 171 | applicable law, it shall not affect the validity or enforceability of the 172 | remainder of the terms of this Agreement, and without further action by the 173 | parties hereto, such provision shall be reformed to the minimum extent 174 | necessary to make such provision valid and enforceable. 175 | 176 | If Recipient institutes patent litigation against any entity (including a 177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 178 | (excluding combinations of the Program with other software or hardware) 179 | infringes such Recipient's patent(s), then such Recipient's rights granted 180 | under Section 2(b) shall terminate as of the date such litigation is filed. 181 | 182 | All Recipient's rights under this Agreement shall terminate if it fails to 183 | comply with any of the material terms or conditions of this Agreement and 184 | does not cure such failure in a reasonable period of time after becoming 185 | aware of such noncompliance. If all Recipient's rights under this Agreement 186 | terminate, Recipient agrees to cease use and distribution of the Program as 187 | soon as reasonably practicable. However, Recipient's obligations under this 188 | Agreement and any licenses granted by Recipient relating to the Program shall 189 | continue and survive. 190 | 191 | Everyone is permitted to copy and distribute copies of this Agreement, but in 192 | order to avoid inconsistency the Agreement is copyrighted and may only be 193 | modified in the following manner. The Agreement Steward reserves the right to 194 | publish new versions (including revisions) of this Agreement from time to 195 | time. No one other than the Agreement Steward has the right to modify this 196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 197 | Eclipse Foundation may assign the responsibility to serve as the Agreement 198 | Steward to a suitable separate entity. Each new version of the Agreement will 199 | be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the 201 | Agreement under which it was received. In addition, after a new version of 202 | the Agreement is published, Contributor may elect to distribute the Program 203 | (including its Contributions) under the new version. Except as expressly 204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 205 | licenses to the intellectual property of any Contributor under this 206 | Agreement, whether expressly, by implication, estoppel or otherwise. All 207 | rights in the Program not expressly granted under this Agreement are 208 | reserved. 209 | 210 | This Agreement is governed by the laws of the State of New York and the 211 | intellectual property laws of the United States of America. No party to this 212 | Agreement will bring a legal action under this Agreement more than one year 213 | after the cause of action arose. Each party waives its rights to a jury trial 214 | in any resulting litigation. 215 | -------------------------------------------------------------------------------- /rebel-readline/README.md: -------------------------------------------------------------------------------- 1 | # rebel-readline 2 | 3 | [![Clojars Project](https://img.shields.io/clojars/v/com.bhauman/rebel-readline.svg)](https://clojars.org) 4 | [![Clojars Project](https://img.shields.io/clojars/v/com.bhauman/rebel-readline-cljs.svg)](https://clojars.org/com.bhauman/rebel-readline-cljs) 5 | 6 | A terminal readline library for Clojure Dialects 7 | 8 | [![asciicast](https://asciinema.org/a/160597.png)](https://asciinema.org/a/160597) 9 | 10 | ## Why create a terminal readline library? 11 | 12 | https://github.com/bhauman/rebel-readline/blob/master/rebel-readline/doc/intro.md 13 | 14 | ## Important note!!! 15 | 16 | The line reader will attempt to manipulate the terminal that initiates 17 | the JVM process. For this reason it is important to start your JVM in 18 | a terminal. 19 | 20 | That means you should launch your Java process using the 21 | 22 | * the java command 23 | * the Clojure `clojure` tool (without readline support) 24 | * lein trampoline 25 | * boot - would need to run in boot's worker pod 26 | 27 | Launching the terminal readline process from another Java process will not work. 28 | 29 | It's best to not launch this readline behind other readline tools like `rlwrap`. 30 | 31 | ## Quick try 32 | 33 | #### Clojure tools 34 | 35 | If you want to try this really quickly 36 | [install the Clojure CLI tools](https://clojure.org/guides/getting_started) 37 | and then invoke this: 38 | 39 | ```shell 40 | clojure -Sdeps "{:deps {com.bhauman/rebel-readline {:mvn/version \"0.1.4\"}}}" -m rebel-readline.main 41 | ``` 42 | 43 | That should start a Clojure REPL that takes its input from the Rebel readline editor. 44 | 45 | Note that I am using the `clojure` command and not the `clj` command 46 | because the latter wraps the process with another readline program (rlwrap). 47 | 48 | Alternatively you can specify an alias in your `$HOME/.clojure/deps.edn` 49 | 50 | ```clojure 51 | { 52 | ... 53 | :aliases {:rebel {:extra-deps {com.bhauman/rebel-readline {:mvn/version "0.1.4"}} 54 | :main-opts ["-m" "rebel-readline.main"]}} 55 | } 56 | ``` 57 | 58 | And then run with a simpler: 59 | 60 | ```shell 61 | $ clojure -A:rebel 62 | ``` 63 | 64 | #### Leiningen 65 | 66 | Add `[com.bhauman/rebel-readline "0.1.4"]` to the dependencies in your 67 | `project.clj` then start a REPL like this: 68 | 69 | ```shell 70 | lein trampoline run -m rebel-readline.main 71 | ``` 72 | 73 | Alternatively, you can add rebel-readline globally to `$HOME/.lein/profiles.clj` 74 | 75 | ```clojure 76 | { 77 | ... 78 | :user {:dependencies [[com.bhauman/rebel-readline "0.1.4"]]} 79 | } 80 | ``` 81 | 82 | Then you can call 83 | 84 | ```shell 85 | lein trampoline run -m rebel-readline.main 86 | ``` 87 | 88 | To make this less verbose you can use an alias in your `project.clj`: 89 | 90 | ```clojure 91 | { 92 | ... 93 | :aliases {"rebl" ["trampoline" "run" "-m" "rebel-readline.main"]} 94 | } 95 | ``` 96 | 97 | Alternatively, you can do this globally in `$HOME/.lein/profiles.clj`: 98 | 99 | ```clojure 100 | { 101 | ... 102 | :user {:aliases {"rebl" ["trampoline" "run" "-m" "rebel-readline.main"]}} 103 | } 104 | ``` 105 | 106 | Now you can start a rebel-readline REPL with `lein rebl`. 107 | 108 | #### Boot 109 | 110 | ``` 111 | boot -d com.bhauman/rebel-readline call -f rebel-readline.main/-main 112 | ``` 113 | 114 | #### Clone repo 115 | 116 | Clone this repo and then from the `rebel-readline` sub-directory 117 | typing `lein trampoline run -m rebel-readline.main` will get you into 118 | a Clojure REPL with the readline editor working. 119 | 120 | Note that `lein run -m rebel-readline.main` will not work! See above. 121 | 122 | ## How do I default to vi bindings? 123 | 124 | In `~/.clojure/rebel_readline.edn` put 125 | 126 | ``` 127 | {:key-map :viins} 128 | ``` 129 | 130 | ## Config 131 | 132 | In `~/.clojure/rebel_readline.edn` you can provide a map with the 133 | following options: 134 | 135 | ``` 136 | :key-map - either :viins or :emacs. Defaults to :emacs 137 | 138 | :color-theme - either :light-screen-theme or :dark-screen-theme 139 | 140 | :highlight - boolean, whether to syntax highlight or not. Defaults to true 141 | 142 | :completion - boolean, whether to complete on tab. Defaults to true 143 | 144 | :eldoc - boolean, whether to display function docs as you type. 145 | Defaults to true 146 | 147 | :indent - boolean, whether to auto indent code on newline. Defaults to true 148 | 149 | :redirect-output - boolean, rebinds root *out* during read to protect linereader 150 | Defaults to true 151 | 152 | :key-bindings - map of key-bindings that get applied after all other key 153 | bindings have been applied 154 | ``` 155 | 156 | #### Key binding config 157 | 158 | You can configure key bindings in the config file, but your milage may vary. 159 | 160 | Example: 161 | 162 | ``` 163 | { 164 | ... 165 | :key-bindings { :emacs [["^D" :clojure-doc-at-point]] 166 | :viins [["^J" :clojure-force-accept-line]] } 167 | } 168 | ``` 169 | 170 | Serialized keybindings are tricky and the keybinding strings are translated with 171 | `org.jline.keymap.KeyMap/translate` which is a bit peculiar in how it translates things. 172 | 173 | If you want literal characters you can use a list of chars or ints i.e 174 | `(\\ \d)` instead of the serialized key names. So you can use `(4 4)` inplace of `"^D^D"`. 175 | 176 | The best way to look up the available widget names is to use the `:repl/key-bindings` 177 | command at the REPL prompt. 178 | 179 | Note: I have found that JLine handles control characters and 180 | alphanumeric characters quite well but if you want to bind special 181 | characters you shouldn't be surprised if it doesn't work. 182 | 183 | ## Quick Lay of the land 184 | 185 | You should look at `rebel-readline.clojure.main` and `rebel-readline.core` 186 | to give you top level usage information. 187 | 188 | The core of the functionality is in 189 | `rebel-readline.clojure.line-reader` everything else is just support. 190 | 191 | ## Quick Usage 192 | 193 | These are some quick examples demonstrating how to use the rebel-readline 194 | API. 195 | 196 | The main way to utilize this readline editor is to replace the 197 | `clojure.main/repl-read` behavior in `clojure.main/repl`. 198 | 199 | The advantage of doing this is that it won't interfere with the input 200 | stream if you are working on something that needs to read from 201 | `*in*`. This is because the line-reader will only be engaged when the 202 | REPL loop is reading. 203 | 204 | Example: 205 | 206 | ```clojure 207 | (rebel-readline.core/with-line-reader 208 | (rebel-readline.clojure.line-reader/create 209 | (rebel-readline.clojure.service.local/create)) 210 | (clojure.main/repl 211 | :prompt (fn []) ;; prompt is handled by line-reader 212 | :read (rebel-readline.clojure.main/create-repl-read))) 213 | ``` 214 | 215 | Another option is to just wrap a call you your REPL with 216 | `rebel-readline.core/with-readline-in` this will bind `*in*` to an 217 | input-stream that is supplied by the line reader. 218 | 219 | ```clojure 220 | (rebel-readline.core/with-readline-in 221 | (rebel-readline.clojure.line-reader/create 222 | (rebel-readline.clojure.service.local/create)) 223 | (clojure.main/repl :prompt (fn[]))) 224 | ``` 225 | 226 | Or with a fallback: 227 | 228 | ```clojure 229 | (try 230 | (rebel-readline.core/with-readline-in 231 | (rebel-readline.clojure.line-reader/create 232 | (rebel-readline.clojure.service.local/create)) 233 | (clojure.main/repl :prompt (fn[]))) 234 | (catch clojure.lang.ExceptionInfo e 235 | (if (-> e ex-data :type (= :rebel-readline.jline-api/bad-terminal)) 236 | (do (println (.getMessage e)) 237 | (clojure.main/repl)) 238 | (throw e)))) 239 | ``` 240 | 241 | ## Services 242 | 243 | The line reader provides features like completion, documentation, 244 | source, apropos, eval and more. The line reader needs a Service to 245 | provide this functionality. 246 | 247 | When you create a `rebel-readline.clojure.line-reader` 248 | you need to supply this service. 249 | 250 | The more common service is the 251 | `rebel-readline.services.clojure.local` which uses the 252 | local clojure process to provide this functionality and its a good 253 | example of how a service works. 254 | 255 | https://github.com/bhauman/rebel-readline/blob/master/rebel-readline/src/rebel_readline/clojure/service/local.clj 256 | 257 | In general, it's much better if the service is querying the Clojure process 258 | where the eventual REPL eval takes place. 259 | 260 | However, the service doesn't necessarily have to query the same 261 | environment that the REPL is using for evaluation. All the editing 262 | functionality that rebel readline provides works without an 263 | environment to query. And the apropos, doc and completion functionality is 264 | still sensible when you provide those abilities from the local clojure process. 265 | 266 | This could be helpful when you have a Clojurey REPL process and you 267 | don't have a Service for it. In this case you can just use a 268 | `clojure.service.local` or a `clojure.service.simple` service. If you 269 | do this you can expect less than optimal results but multi-line 270 | editing, syntax highlighting, auto indenting will all work just fine. 271 | 272 | ## Key-bindings 273 | 274 | **Bindings of interest** 275 | 276 | * Ctrl-C => aborts editing the current line 277 | * Ctrl-D at the start of a line => sends an end of stream message 278 | which in most cases should quit the REPL 279 | 280 | * TAB => word completion or code indent if the cursor is in the whitespace at the 281 | start of a line 282 | * Ctrl-X_Ctrl-D => Show documentation for word at point 283 | * Ctrl-X_Ctrl-S => Show source for word at point 284 | * Ctrl-X_Ctrl-A => Show apropos for word at point 285 | * Ctrl-X_Ctrl-E => Inline eval for SEXP before the point 286 | 287 | You can examine the key-bindings with the `:repl/key-bindings` command. 288 | 289 | ## Commands 290 | 291 | There is a command system. If the line starts with a "repl" namespaced 292 | keyword then the line-reader will attempt to interpret it as a command. 293 | 294 | Type `:repl/help` or `:repl` TAB to see a list of available commands. 295 | 296 | You can add new commands by adding methods to the 297 | `rebel-readline.commands/command` multimethod. You can add 298 | documentation for the command by adding a method to the 299 | `rebel-readline.commands/command-doc` multimethod. 300 | 301 | ## CLJS 302 | 303 | See https://github.com/bhauman/rebel-readline/tree/master/rebel-readline-cljs 304 | 305 | ## nREPL, SocketREPL, pREPL? 306 | 307 | Services have not been written for these REPLs yet!! 308 | 309 | But you can use the `rebel-readline.clojure.service.simple` service in the meantime. 310 | 311 | ## Contributing 312 | 313 | Please contribute! 314 | 315 | I'm trying to mark issues with `help wanted` for issues that I feel 316 | are good opportunities for folks to help out. If you want to work on 317 | one of these please mention it in the issue. 318 | 319 | If you do contribute: 320 | 321 | * if the change isn't small please file an issue before a PR. 322 | * please put all PR changes into one commit 323 | * make small grokable changes. Large changes are more likely to be 324 | ignored and or used as a starting issue for exploration. 325 | * break larger solutions down into a logical series of small PRs 326 | * mention it at the start, if you are filing a PR that is more of an 327 | exploration of an idea 328 | 329 | I'm going to be more open to repairing current behavior than I will be 330 | to increasing the scope of rebel-readline. 331 | 332 | I will have a preference for creating hooks so that additional functionality 333 | can be layered on with libraries. 334 | 335 | If you are wanting to contribute but don't know what to work on reach 336 | out to me on the clojurians slack channel. 337 | 338 | ## License 339 | 340 | Copyright © 2018 Bruce Hauman 341 | 342 | Distributed under the Eclipse Public License either version 1.0 or (at 343 | your option) any later version. 344 | -------------------------------------------------------------------------------- /rebel-readline/LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 4 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 5 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | a) in the case of the initial Contributor, the initial code and 11 | documentation distributed under this Agreement, and 12 | b) in the case of each subsequent Contributor: 13 | i) changes to the Program, and 14 | ii) additions to the Program; 15 | 16 | where such changes and/or additions to the Program originate from and are 17 | distributed by that particular Contributor. A Contribution 'originates' from a 18 | Contributor if it was added to the Program by such Contributor itself or 19 | anyone acting on such Contributor's behalf. Contributions do not include 20 | additions to the Program which: (i) are separate modules of software 21 | distributed in conjunction with the Program under their own license agreement, 22 | and (ii) are not derivative works of the Program. 23 | "Contributor" means any person or entity that distributes the Program. 24 | 25 | "Licensed Patents" mean patent claims licensable by a Contributor which are 26 | necessarily infringed by the use or sale of its Contribution alone or when 27 | combined with the Program. 28 | 29 | "Program" means the Contributions distributed in accordance with this 30 | Agreement. 31 | 32 | "Recipient" means anyone who receives the Program under this Agreement, 33 | including all Contributors. 34 | 35 | 2. GRANT OF RIGHTS 36 | 37 | a) Subject to the terms of this Agreement, each Contributor hereby grants 38 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 39 | reproduce, prepare derivative works of, publicly display, publicly 40 | perform, distribute and sublicense the Contribution of such Contributor, 41 | if any, and such derivative works, in source code and object code form. 42 | 43 | b) Subject to the terms of this Agreement, each Contributor hereby grants 44 | Recipient a non-exclusive, worldwide, royalty-free patent license under 45 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 46 | transfer the Contribution of such Contributor, if any, in source code and 47 | object code form. This patent license shall apply to the combination of 48 | the Contribution and the Program if, at the time the Contribution is 49 | added by the Contributor, such addition of the Contribution causes such 50 | combination to be covered by the Licensed Patents. The patent license 51 | shall not apply to any other combinations which include the Contribution. 52 | No hardware per se is licensed hereunder. 53 | 54 | c) Recipient understands that although each Contributor grants the 55 | licenses to its Contributions set forth herein, no assurances are 56 | provided by any Contributor that the Program does not infringe the patent 57 | or other intellectual property rights of any other entity. Each 58 | Contributor disclaims any liability to Recipient for claims brought by 59 | any other entity based on infringement of intellectual property rights or 60 | otherwise. As a condition to exercising the rights and licenses granted 61 | hereunder, each Recipient hereby assumes sole responsibility to secure 62 | any other intellectual property rights needed, if any. For example, if a 63 | third party patent license is required to allow Recipient to distribute 64 | the Program, it is Recipient's responsibility to acquire that license 65 | before distributing the Program. 66 | 67 | d) Each Contributor represents that to its knowledge it has sufficient 68 | copyright rights in its Contribution, if any, to grant the copyright 69 | license set forth in this Agreement. 70 | 71 | 3. REQUIREMENTS 72 | A Contributor may choose to distribute the Program in object code form under 73 | its own license agreement, provided that: 74 | 75 | a) it complies with the terms and conditions of this Agreement; and 76 | 77 | b) its license agreement: 78 | i) effectively disclaims on behalf of all Contributors all 79 | warranties and conditions, express and implied, including warranties 80 | or conditions of title and non-infringement, and implied warranties 81 | or conditions of merchantability and fitness for a particular 82 | purpose; 83 | ii) effectively excludes on behalf of all Contributors all liability 84 | for damages, including direct, indirect, special, incidental and 85 | consequential damages, such as lost profits; 86 | iii) states that any provisions which differ from this Agreement are 87 | offered by that Contributor alone and not by any other party; and 88 | iv) states that source code for the Program is available from such 89 | Contributor, and informs licensees how to obtain it in a reasonable 90 | manner on or through a medium customarily used for software 91 | exchange. 92 | 93 | When the Program is made available in source code form: 94 | 95 | a) it must be made available under this Agreement; and 96 | 97 | b) a copy of this Agreement must be included with each copy of the 98 | Program. 99 | Contributors may not remove or alter any copyright notices contained within 100 | the Program. 101 | 102 | Each Contributor must identify itself as the originator of its Contribution, 103 | if any, in a manner that reasonably allows subsequent Recipients to identify 104 | the originator of the Contribution. 105 | 106 | 4. COMMERCIAL DISTRIBUTION 107 | Commercial distributors of software may accept certain responsibilities with 108 | respect to end users, business partners and the like. While this license is 109 | intended to facilitate the commercial use of the Program, the Contributor who 110 | includes the Program in a commercial product offering should do so in a manner 111 | which does not create potential liability for other Contributors. Therefore, 112 | if a Contributor includes the Program in a commercial product offering, such 113 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify 114 | every other Contributor ("Indemnified Contributor") against any losses, 115 | damages and costs (collectively "Losses") arising from claims, lawsuits and 116 | other legal actions brought by a third party against the Indemnified 117 | Contributor to the extent caused by the acts or omissions of such Commercial 118 | Contributor in connection with its distribution of the Program in a commercial 119 | product offering. The obligations in this section do not apply to any claims 120 | or Losses relating to any actual or alleged intellectual property 121 | infringement. In order to qualify, an Indemnified Contributor must: a) 122 | promptly notify the Commercial Contributor in writing of such claim, and b) 123 | allow the Commercial Contributor to control, and cooperate with the Commercial 124 | Contributor in, the defense and any related settlement negotiations. The 125 | Indemnified Contributor may participate in any such claim at its own expense. 126 | 127 | For example, a Contributor might include the Program in a commercial product 128 | offering, Product X. That Contributor is then a Commercial Contributor. If 129 | that Commercial Contributor then makes performance claims, or offers 130 | warranties related to Product X, those performance claims and warranties are 131 | such Commercial Contributor's responsibility alone. Under this section, the 132 | Commercial Contributor would have to defend claims against the other 133 | Contributors related to those performance claims and warranties, and if a 134 | court requires any other Contributor to pay any damages as a result, the 135 | Commercial Contributor must pay those damages. 136 | 137 | 5. NO WARRANTY 138 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN 139 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 140 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, 141 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each 142 | Recipient is solely responsible for determining the appropriateness of using 143 | and distributing the Program and assumes all risks associated with its 144 | exercise of rights under this Agreement , including but not limited to the 145 | risks and costs of program errors, compliance with applicable laws, damage to 146 | or loss of data, programs or equipment, and unavailability or interruption of 147 | operations. 148 | 149 | 6. DISCLAIMER OF LIABILITY 150 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 151 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 152 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 153 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 154 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 155 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 156 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 157 | OF SUCH DAMAGES. 158 | 159 | 7. GENERAL 160 | 161 | If any provision of this Agreement is invalid or unenforceable under 162 | applicable law, it shall not affect the validity or enforceability of the 163 | remainder of the terms of this Agreement, and without further action by the 164 | parties hereto, such provision shall be reformed to the minimum extent 165 | necessary to make such provision valid and enforceable. 166 | 167 | If Recipient institutes patent litigation against any entity (including a 168 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 169 | (excluding combinations of the Program with other software or hardware) 170 | infringes such Recipient's patent(s), then such Recipient's rights granted 171 | under Section 2(b) shall terminate as of the date such litigation is filed. 172 | 173 | All Recipient's rights under this Agreement shall terminate if it fails to 174 | comply with any of the material terms or conditions of this Agreement and does 175 | not cure such failure in a reasonable period of time after becoming aware of 176 | such noncompliance. If all Recipient's rights under this Agreement terminate, 177 | Recipient agrees to cease use and distribution of the Program as soon as 178 | reasonably practicable. However, Recipient's obligations under this Agreement 179 | and any licenses granted by Recipient relating to the Program shall continue 180 | and survive. 181 | 182 | Everyone is permitted to copy and distribute copies of this Agreement, but in 183 | order to avoid inconsistency the Agreement is copyrighted and may only be 184 | modified in the following manner. The Agreement Steward reserves the right to 185 | publish new versions (including revisions) of this Agreement from time to 186 | time. No one other than the Agreement Steward has the right to modify this 187 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 188 | Eclipse Foundation may assign the responsibility to serve as the Agreement 189 | Steward to a suitable separate entity. Each new version of the Agreement will 190 | be given a distinguishing version number. The Program (including 191 | Contributions) may always be distributed subject to the version of the 192 | Agreement under which it was received. In addition, after a new version of the 193 | Agreement is published, Contributor may elect to distribute the Program 194 | (including its Contributions) under the new version. Except as expressly 195 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 196 | licenses to the intellectual property of any Contributor under this Agreement, 197 | whether expressly, by implication, estoppel or otherwise. All rights in the 198 | Program not expressly granted under this Agreement are reserved. 199 | 200 | This Agreement is governed by the laws of the State of New York and the 201 | intellectual property laws of the United States of America. No party to this 202 | Agreement will bring a legal action under this Agreement more than one year 203 | after the cause of action arose. Each party waives its rights to a jury trial 204 | in any resulting litigation. 205 | -------------------------------------------------------------------------------- /rebel-readline/src/rebel_readline/jline_api.clj: -------------------------------------------------------------------------------- 1 | (ns rebel-readline.jline-api 2 | (:require 3 | [clojure.string :as string] 4 | [rebel-readline.jline-api.attributed-string :as astring]) 5 | (:import 6 | [org.jline.keymap KeyMap] 7 | [org.jline.reader 8 | Highlighter 9 | Completer 10 | Candidate 11 | Parser 12 | Parser$ParseContext 13 | ParsedLine 14 | LineReader 15 | LineReader$Option 16 | LineReaderBuilder 17 | UserInterruptException 18 | EndOfFileException 19 | EOFError 20 | Widget] 21 | [org.jline.reader.impl LineReaderImpl DefaultParser BufferImpl] 22 | [org.jline.terminal Terminal TerminalBuilder Attributes Attributes$LocalFlag Attributes$InputFlag] 23 | [org.jline.terminal.impl DumbTerminal] 24 | [java.io Writer] 25 | [org.jline.utils AttributedStringBuilder AttributedString AttributedStyle])) 26 | 27 | (def ^:dynamic *terminal* nil) 28 | (def ^:dynamic *line-reader* nil) 29 | (def ^:dynamic *buffer* nil) 30 | 31 | ;; helper for development 32 | (defn buffer* 33 | ([s] (buffer* s nil)) 34 | ([s c] 35 | (doto (BufferImpl.) 36 | (.write s) 37 | (.cursor (or c (count s)))))) 38 | 39 | ;; helper for development 40 | #_(defmacro with-buffer [b & body] 41 | `(binding [rebel-readline.jline-api/*buffer* ~b 42 | rebel-readline.service/*service* 43 | (rebel-readline.service.local-clojure/create)] 44 | ~@body)) 45 | 46 | ;; ---------------------------------------- 47 | ;; Create a terminal 48 | ;; ---------------------------------------- 49 | 50 | (defn assert-system-terminal [terminal] 51 | (when (instance? DumbTerminal terminal) 52 | (throw (ex-info 53 | "Unable to detect a system Terminal, you must not launch the Rebel readline 54 | from an intermediate process. 55 | If you are using `lein` you may need to use `lein trampoline`." 56 | {:type ::bad-terminal})))) 57 | 58 | (defn create-terminal [& [assert-system-terminal']] 59 | (let [terminal (-> (TerminalBuilder/builder) 60 | (.system true) 61 | (.build))] 62 | (when (not (false? assert-system-terminal')) 63 | (assert-system-terminal terminal)) 64 | terminal)) 65 | 66 | (defn toggle-input [^Terminal term on?] 67 | (let [attr ^Attributes (.getAttributes term)] 68 | (.setAttributes 69 | term 70 | (doto attr 71 | (.setLocalFlag Attributes$LocalFlag/ICANON on?) 72 | (.setLocalFlag Attributes$LocalFlag/ECHO on?) 73 | (.setInputFlag Attributes$InputFlag/IGNCR (not on?)))) 74 | (.flush (.writer term)))) 75 | 76 | (declare display-message) 77 | 78 | (defn widget-exec [line-reader thunk] 79 | (binding [*line-reader* line-reader 80 | *buffer* (.getBuffer line-reader)] 81 | (try 82 | (thunk) 83 | (catch clojure.lang.ExceptionInfo e 84 | (if-let [message (.getMessage e)] 85 | (do 86 | (some-> @line-reader :repl/error (reset! e)) 87 | (display-message 88 | (AttributedString. 89 | (str "Internal REPL Error: this shouldn't happen. :repl/*e for stacktrace\n" 90 | (class e) "\n" message) 91 | (.foreground AttributedStyle/DEFAULT AttributedStyle/RED))) 92 | true) 93 | (throw e)))))) 94 | 95 | (defmacro create-widget [& body] 96 | `(fn [line-reader#] 97 | (reify Widget 98 | (apply [_#] 99 | (widget-exec line-reader# (fn [] ~@body)))))) 100 | 101 | ;; very naive 102 | (def get-accessible-field 103 | (memoize (fn [obj field-name] 104 | (or (when-let [field (-> obj 105 | .getClass 106 | .getSuperclass 107 | (.getDeclaredField field-name))] 108 | (doto field 109 | (.setAccessible true))))))) 110 | 111 | (defn supplier [f] 112 | (proxy [java.util.function.Supplier] [] 113 | (get [] (f)))) 114 | 115 | ;; -------------------------------------- 116 | ;; Key maps 117 | ;; -------------------------------------- 118 | 119 | (defn key-map->clj [key-map] 120 | (mapv (juxt key val) 121 | (.getBoundKeys key-map))) 122 | 123 | (defn key-map->display-data [key-map] 124 | (->> (key-map->clj key-map) 125 | (filter #(instance? org.jline.reader.Reference (second %))) 126 | (map (fn [[k v]] 127 | [(KeyMap/display k) (.name v)])) 128 | (filter 129 | (fn [[k v]] 130 | (not 131 | (#{ ;; these don't exist for some reason 132 | "character-search" 133 | "character-search-backward" 134 | "infer-next-history" 135 | ;; these are too numerous 136 | ;; TODO would be nice to display 137 | ;; rolled up abbreviations 138 | "self-insert" 139 | "digit-argument" 140 | "do-lowercase-version"} v)))))) 141 | 142 | (defn key-maps [] 143 | (-> *line-reader* (.getKeyMaps))) 144 | 145 | (defn key-map [key-map-name] 146 | (get (key-maps) (name key-map-name))) 147 | 148 | (defn set-key-map! [key-map-name key-map] 149 | (-> (key-maps) 150 | (.put (name key-map-name) key-map))) 151 | 152 | (defn set-main-key-map! [key-map-name] 153 | (boolean 154 | (when-let [km (key-map key-map-name)] 155 | (.put (key-maps) "main" km)))) 156 | 157 | (defn key-map-name [key-map] 158 | (when-let [res (first (filter #(and (= (val %) key-map) 159 | (not= (key %) "main")) 160 | (key-maps)))] 161 | (key res))) 162 | 163 | (defn main-key-map-name [] 164 | (key-map-name (key-map "main"))) 165 | 166 | (defn orig-key-map-clone [key-map-name] 167 | (get (.defaultKeyMaps *line-reader*) key-map-name)) 168 | 169 | (defn bind-key [key-map widget-id key-str] 170 | (when key-str 171 | (.bind key-map (org.jline.reader.Reference. widget-id) key-str))) 172 | 173 | (defn key-binding [key-map-name key-str widget-name] 174 | (swap! *line-reader* update-in 175 | [::key-bindings (keyword key-map-name)] 176 | #((fnil conj []) % [key-str widget-name]))) 177 | 178 | (defn apply-key-bindings [key-bindings & {:keys [look-up-keymap-fn] :or {look-up-keymap-fn orig-key-map-clone}}] 179 | (doall 180 | (for [[key-map-name bindings'] key-bindings] 181 | (when-let [km (look-up-keymap-fn (name key-map-name))] 182 | (doseq [[key-str widget-name] bindings'] 183 | (when (and widget-name key-str) 184 | (bind-key km (name widget-name) key-str))) 185 | (set-key-map! (name key-map-name) km))))) 186 | 187 | (defn apply-key-bindings! [] 188 | (when-let [kbs (not-empty (::key-bindings @*line-reader*))] 189 | (apply-key-bindings kbs)) 190 | ;; user level key bindings applied after 191 | (when-let [kbs (not-empty (:key-bindings @*line-reader*))] 192 | (apply-key-bindings kbs :look-up-keymap-fn key-map)) 193 | *line-reader*) 194 | 195 | ;; -------------------------------------- 196 | ;; contextual ANSI 197 | ;; -------------------------------------- 198 | 199 | (defn ->ansi [at-str] 200 | (if-let [t (and *line-reader* (.getTerminal *line-reader*))] 201 | (astring/->ansi at-str t) 202 | (astring/->ansi at-str nil))) 203 | 204 | ;; -------------------------------------- 205 | ;; Buffer operations 206 | ;; -------------------------------------- 207 | 208 | (defn cursor 209 | ([] (.cursor *buffer*)) 210 | ([i] (.cursor *buffer* i))) 211 | 212 | (defn move-cursor [offset] 213 | (.move *buffer* offset)) 214 | 215 | (defn delete 216 | ([] (.delete *buffer*)) 217 | ([n] (.delete *buffer* n))) 218 | 219 | (defn backspace 220 | ([] (.backspace *buffer*)) 221 | ([n] (.backspace *buffer* n))) 222 | 223 | (defn write [s] 224 | (.write *buffer* s)) 225 | 226 | (defn buffer-as-string [] 227 | (str *buffer*)) 228 | 229 | (defn buffer-subs 230 | ([start] (.substring *buffer* start)) 231 | ([start end] (.substring *buffer* start end))) 232 | 233 | (defn up-to-cursor [] 234 | (.upToCursor *buffer*)) 235 | 236 | (defn char-at [idx] 237 | (char (.atChar *buffer* idx))) 238 | 239 | (defn curr-char [] 240 | (char (.currChar *buffer*))) 241 | 242 | (defn prev-char [] 243 | (char (.prevChar *buffer*))) 244 | 245 | (defn next-char [] 246 | (char (.nextChar *buffer*))) 247 | 248 | (defn next-char [] 249 | (char (.nextChar *buffer*))) 250 | 251 | 252 | ;; -------------------------------------- 253 | ;; Line Reader operations 254 | ;; -------------------------------------- 255 | 256 | (defn register-widget [widget-id widget] 257 | (doto *line-reader* 258 | (-> (.getWidgets) 259 | (.put widget-id (if (fn? widget) (widget *line-reader*) widget))))) 260 | 261 | (defn terminal-size [] 262 | (let [size-field (get-accessible-field *line-reader* "size")] 263 | (when-let [sz (.get size-field *line-reader*)] 264 | {:rows (.getRows sz) 265 | :cols (.getColumns sz)}))) 266 | 267 | (defn redisplay [] 268 | (locking (.writer (.getTerminal *line-reader*)) 269 | (.redisplay *line-reader*))) 270 | 271 | (defn block-redisplay-millis [time-ms] 272 | (let [writer (.writer (.getTerminal *line-reader*))] 273 | (.start 274 | (Thread. 275 | (fn [] 276 | (locking writer 277 | (Thread/sleep time-ms))))))) 278 | 279 | (defn display-message 280 | ([message] 281 | (display-message *line-reader* message)) 282 | ([line-reader message] 283 | (let [post-field (get-accessible-field line-reader "post")] 284 | (.set post-field line-reader (supplier (fn [] (AttributedString. message))))))) 285 | 286 | (defn rows-available-for-post-display [] 287 | (let [rows (:rows (terminal-size)) 288 | buffer-rows (count (string/split-lines (buffer-as-string)))] 289 | (max 0 (- rows buffer-rows)))) 290 | 291 | (defn reading? [line-reader] 292 | (.isReading line-reader)) 293 | 294 | (defn call-widget [widget-name] 295 | (.callWidget *line-reader* widget-name)) 296 | 297 | (defn create-line-reader [terminal app-name service] 298 | (let [service-variable-name (str ::service)] 299 | (proxy [LineReaderImpl clojure.lang.IDeref clojure.lang.IAtom] 300 | [terminal 301 | (or app-name "Rebel Readline") 302 | (java.util.HashMap. {service-variable-name (atom (or service {}))})] 303 | (selfInsert [] 304 | (when-let [hooks (not-empty (:self-insert-hooks @this))] 305 | (widget-exec this #(doseq [hook hooks] (hook)))) 306 | (proxy-super selfInsert)) 307 | (deref [] 308 | (deref (.getVariable this service-variable-name))) 309 | (swap [f & args] 310 | (apply swap! (.getVariable this service-variable-name) f args)) 311 | (reset [a] 312 | (reset! (.getVariable this service-variable-name) a))))) 313 | 314 | ;; taken from Clojure 1.10 core.print 315 | (defn- ^java.io.PrintWriter PrintWriter-on* 316 | "implements java.io.PrintWriter given flush-fn, which will be called 317 | when .flush() is called, with a string built up since the last call to .flush(). 318 | if not nil, close-fn will be called with no arguments when .close is called" 319 | {:added "1.10"} 320 | [flush-fn close-fn] 321 | (let [sb (StringBuilder.)] 322 | (-> (proxy [Writer] [] 323 | (flush [] 324 | (when (pos? (.length sb)) 325 | (flush-fn sb))) 326 | (close [] 327 | (.flush ^Writer this) 328 | (when close-fn (close-fn)) 329 | nil) 330 | (write [str-cbuf off len] 331 | (when (pos? len) 332 | (if (instance? String str-cbuf) 333 | (.append sb ^String str-cbuf ^int off ^int len) 334 | (.append sb ^chars str-cbuf ^int off ^int len))))) 335 | java.io.BufferedWriter. 336 | java.io.PrintWriter.))) 337 | 338 | (defn ^java.io.PrintWriter safe-terminal-writer [line-reader] 339 | (PrintWriter-on* 340 | (fn [^StringBuilder buffer] 341 | (let [s (.toString buffer)] 342 | (if (reading? line-reader) 343 | (let [last-newline (.lastIndexOf s "\n")] 344 | (when (>= last-newline 0) 345 | (let [substring-to-print (.substring s 0 (inc last-newline))] 346 | (.printAbove line-reader substring-to-print) 347 | (.delete buffer 0 (inc last-newline))))) 348 | (do 349 | (doto (.writer (.getTerminal line-reader)) 350 | (.print s) 351 | (.flush)) 352 | (.setLength buffer 0))))) 353 | nil)) 354 | 355 | --------------------------------------------------------------------------------