• Stars
    star
    680
  • Rank 66,440 (Top 2 %)
  • Language
    Clojure
  • License
    Eclipse Public Li...
  • Created almost 7 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Terminal readline library for Clojure dialects

rebel-readline

Clojars Project Clojars Project

A terminal readline library for Clojure Dialects

asciicast

Why create a terminal readline library?

https://github.com/bhauman/rebel-readline/blob/master/rebel-readline/doc/intro.md

Important note!!!

The line reader will attempt to manipulate the terminal that initiates the JVM process. For this reason it is important to start your JVM in a terminal.

That means you should launch your Java process using the

  • the java command
  • the Clojure clojure tool (without readline support)
  • lein trampoline
  • boot - would need to run in boot's worker pod

Launching the terminal readline process from another Java process will not work.

It's best to not launch this readline behind other readline tools like rlwrap.

Quick try

Clojure tools

If you want to try this really quickly install the Clojure CLI tools and then invoke this:

clojure -Sdeps "{:deps {com.bhauman/rebel-readline {:mvn/version \"0.1.4\"}}}" -m rebel-readline.main

That should start a Clojure REPL that takes its input from the Rebel readline editor.

Note that I am using the clojure command and not the clj command because the latter wraps the process with another readline program (rlwrap).

Alternatively you can specify an alias in your $HOME/.clojure/deps.edn

{
 ...
 :aliases {:rebel {:extra-deps {com.bhauman/rebel-readline {:mvn/version "0.1.4"}}
                   :main-opts  ["-m" "rebel-readline.main"]}}
}

And then run with a simpler:

clojure -M:rebel

Leiningen

Add [com.bhauman/rebel-readline "0.1.4"] to the dependencies in your project.clj then start a REPL like this:

lein trampoline run -m rebel-readline.main

Alternatively, you can add rebel-readline globally to $HOME/.lein/profiles.clj

{
 ...
 :user {:dependencies [[com.bhauman/rebel-readline "0.1.4"]]}
}

Then you can call

lein trampoline run -m rebel-readline.main

To make this less verbose you can use an alias in your project.clj:

{
 ...
 :aliases {"rebl" ["trampoline" "run" "-m" "rebel-readline.main"]}
}

Alternatively, you can do this globally in $HOME/.lein/profiles.clj:

{
 ...
 :user {:aliases {"rebl" ["trampoline" "run" "-m" "rebel-readline.main"]}}
}

Now you can start a rebel-readline REPL with lein rebl.

Boot

boot -d com.bhauman/rebel-readline call -f rebel-readline.main/-main

Clone repo

Clone this repo and then from the rebel-readline sub-directory typing lein trampoline run -m rebel-readline.main will get you into a Clojure REPL with the readline editor working.

Note that lein run -m rebel-readline.main will not work! See above.

How do I default to vi bindings?

In ~/.clojure/rebel_readline.edn put

{:key-map :viins}

Config

In ~/.clojure/rebel_readline.edn you can provide a map with the following options:

:key-map         - either :viins or :emacs. Defaults to :emacs

:color-theme     - either :light-screen-theme or :dark-screen-theme

:highlight       - boolean, whether to syntax highlight or not. Defaults to true

:completion      - boolean, whether to complete on tab. Defaults to true

:eldoc           - boolean, whether to display function docs as you type.
                   Defaults to true

:indent          - boolean, whether to auto indent code on newline. Defaults to true

:redirect-output - boolean, rebinds root *out* during read to protect linereader
                   Defaults to true
                   
:key-bindings    - map of key-bindings that get applied after all other key 
                   bindings have been applied

Key binding config

You can configure key bindings in the config file, but your milage may vary.

Example:

{ 
...
:key-bindings { :emacs [["^D" :clojure-doc-at-point]] 
                :viins [["^J" :clojure-force-accept-line]] }
}

Serialized keybindings are tricky and the keybinding strings are translated with org.jline.keymap.KeyMap/translate which is a bit peculiar in how it translates things.

If you want literal characters you can use a list of chars or ints i.e (\\ \d) instead of the serialized key names. So you can use (4 4) inplace of "^D^D".

The best way to look up the available widget names is to use the :repl/key-bindings command at the REPL prompt.

Note: I have found that JLine handles control characters and alphanumeric characters quite well but if you want to bind special characters you shouldn't be surprised if it doesn't work.

Quick Lay of the land

You should look at rebel-readline.clojure.main and rebel-readline.core to give you top level usage information.

The core of the functionality is in rebel-readline.clojure.line-reader everything else is just support.

Quick Usage

These are some quick examples demonstrating how to use the rebel-readline API.

The main way to utilize this readline editor is to replace the clojure.main/repl-read behavior in clojure.main/repl.

The advantage of doing this is that it won't interfere with the input stream if you are working on something that needs to read from *in*. This is because the line-reader will only be engaged when the REPL loop is reading.

Example:

(rebel-readline.core/with-line-reader
  (rebel-readline.clojure.line-reader/create
    (rebel-readline.clojure.service.local/create))
  (clojure.main/repl
     :prompt (fn []) ;; prompt is handled by line-reader
     :read (rebel-readline.clojure.main/create-repl-read)))

Another option is to just wrap a call you your REPL with rebel-readline.core/with-readline-in this will bind *in* to an input-stream that is supplied by the line reader.

(rebel-readline.core/with-readline-in
  (rebel-readline.clojure.line-reader/create
    (rebel-readline.clojure.service.local/create))
  (clojure.main/repl :prompt (fn[])))

Or with a fallback:

(try
  (rebel-readline.core/with-readline-in
    (rebel-readline.clojure.line-reader/create
      (rebel-readline.clojure.service.local/create))
    (clojure.main/repl :prompt (fn[])))
  (catch clojure.lang.ExceptionInfo e
    (if (-> e ex-data :type (= :rebel-readline.jline-api/bad-terminal))
      (do (println (.getMessage e))
        (clojure.main/repl))
      (throw e))))

Services

The line reader provides features like completion, documentation, source, apropos, eval and more. The line reader needs a Service to provide this functionality.

When you create a rebel-readline.clojure.line-reader you need to supply this service.

The more common service is the rebel-readline.services.clojure.local which uses the local clojure process to provide this functionality and its a good example of how a service works.

https://github.com/bhauman/rebel-readline/blob/master/rebel-readline/src/rebel_readline/clojure/service/local.clj

In general, it's much better if the service is querying the Clojure process where the eventual REPL eval takes place.

However, the service doesn't necessarily have to query the same environment that the REPL is using for evaluation. All the editing functionality that rebel readline provides works without an environment to query. And the apropos, doc and completion functionality is still sensible when you provide those abilities from the local clojure process.

This could be helpful when you have a Clojurey REPL process and you don't have a Service for it. In this case you can just use a clojure.service.local or a clojure.service.simple service. If you do this you can expect less than optimal results but multi-line editing, syntax highlighting, auto indenting will all work just fine.

Key-bindings

Bindings of interest

  • Ctrl-C => aborts editing the current line

  • Ctrl-D at the start of a line => sends an end of stream message which in most cases should quit the REPL

  • TAB => word completion or code indent if the cursor is in the whitespace at the start of a line

  • Ctrl-X_Ctrl-D => Show documentation for word at point

  • Ctrl-X_Ctrl-S => Show source for word at point

  • Ctrl-X_Ctrl-A => Show apropos for word at point

  • Ctrl-X_Ctrl-E => Inline eval for SEXP before the point

You can examine the key-bindings with the :repl/key-bindings command.

Commands

There is a command system. If the line starts with a "repl" namespaced keyword then the line-reader will attempt to interpret it as a command.

Type :repl/help or :repl TAB to see a list of available commands.

You can add new commands by adding methods to the rebel-readline.commands/command multimethod. You can add documentation for the command by adding a method to the rebel-readline.commands/command-doc multimethod.

CLJS

See https://github.com/bhauman/rebel-readline/tree/master/rebel-readline-cljs

nREPL, SocketREPL, pREPL?

Services have not been written for these REPLs yet!!

But you can use the rebel-readline.clojure.service.simple service in the meantime.

Contributing

Please contribute!

I'm trying to mark issues with help wanted for issues that I feel are good opportunities for folks to help out. If you want to work on one of these please mention it in the issue.

If you do contribute:

  • if the change isn't small please file an issue before a PR.
  • please put all PR changes into one commit
  • make small grokable changes. Large changes are more likely to be ignored and or used as a starting issue for exploration.
  • break larger solutions down into a logical series of small PRs
  • mention it at the start, if you are filing a PR that is more of an exploration of an idea

I'm going to be more open to repairing current behavior than I will be to increasing the scope of rebel-readline.

I will have a preference for creating hooks so that additional functionality can be layered on with libraries.

If you are wanting to contribute but don't know what to work on reach out to me on the clojurians slack channel.

License

Copyright © 2018 Bruce Hauman

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

More Repositories

1

lein-figwheel

Figwheel builds your ClojureScript code and hot loads it into the browser as you are coding!
Clojure
2,891
star
2

devcards

Devcards aims to provide a visual REPL experience for ClojureScript
Clojure
1,521
star
3

figwheel-main

Figwheel Main provides tooling for developing ClojureScript applications
Clojure
640
star
4

flappy-bird-demo

Live interactive coding Flappy Bird in ClojureScript
JavaScript
145
star
5

figwheel-template

A Leinigen template for figwheel
Clojure
137
star
6

spell-spec

clojure.spec.alpha helpers that check for misspelled map keys, with expound integration
Clojure
132
star
7

dotsters

A game implemented in ClojureScript with the core.async library.
CSS
125
star
8

cljs-test-display

Visual display for ClojureScript tests.
Clojure
107
star
9

figwheel-main-template

A template to generate a minimal figwheel-main based ClojureScript project
Clojure
83
star
10

react-native-figwheel-bridge

Enables React Native projects written in ClojureScript to use Figwheel's live reloading and REPL.
JavaScript
78
star
11

certifiable

A helper tool that simplifies the creation of development time SSL certificates for use with Java Webservers like Jetty.
Clojure
44
star
12

figwheel-repl

Figwheel REPL provides a stable multiplexing REPL for ClojureScript
Clojure
44
star
13

crashverse

Clojure
41
star
14

advent-of-clojure-2016

My Advent of Code 2016 solutions in Clojure
Clojure
31
star
15

advent-of-clojure

My solutions to the Advent of Code
Clojure
28
star
16

flappy-bird-demo-new

This is the original Flappy Bird Demo updated to use the latest incarnation of Figwheel.
Clojure
24
star
17

strictly-specking

Clojure
22
star
18

figwheel-core

Figwheel core provides code reloading facilities for ClojureScript.
Clojure
22
star
19

devcards-template

A leinigen template for ClojureScript devcards
Clojure
19
star
20

jspec-rails

Javascript BDD integration for Ruby on Rails
JavaScript
16
star
21

cljs-react-reload

Hot reload React classes in cljs
Clojure
15
star
22

async-explore

Exploring how core async interfaces with the daily grind of web dev.
Clojure
14
star
23

sonic-cljs

Using ClojureScript, Tone.js and React to make a declarative live coding library
JavaScript
14
star
24

javascript_yome_widget

A JavaScript Yome widget written for a blog post.
JavaScript
14
star
25

bhauman.github.com

My blog
JavaScript
13
star
26

figwheel.tools

nREPL support for ClojureScript
Clojure
10
star
27

cohortly

Cohort analysis engine for Rails utilizing mongodb and map reduce
JavaScript
9
star
28

talk-slides

Clojure
5
star
29

font_squirreler

A thor script to squirrel away some fontsquirrel.com webfonts locally or on s3.
4
star
30

unabjah

Simply Unobtrusive Javascript
JavaScript
3
star
31

moriscore

Giving mori.js underscore like usage patterns.
CoffeeScript
3
star
32

nextpress

A jekyl like static site generator that runs in the browser and uses AWS S3
Clojure
3
star
33

metacircular

interuptable metacircular interpreter with first class macros
Clojure
2
star
34

strange-loop-2015

Clojure
2
star
35

frontier

functional front end web dev - WIP
JavaScript
1
star
36

advent-of-code-2022

Advent of code solutions in Clojure for 2022
Clojure
1
star
37

figwheel-main-testing

integration testing for figwheel-main
Clojure
1
star
38

rgba_rack

A set of image servers to make up for lack of rgba() support and linear_gradient support.
Ruby
1
star
39

leaves-client

This is an experimental client for an immutable JSON document service
CoffeeScript
1
star
40

ml-class

Machine learning class from stanford
MATLAB
1
star
41

simple-lein-profile-merge

A simple Clojure library to help fetch configuration information from your project.clj without needing to load Leiningen.
Clojure
1
star