Single character console input in java/clojure
Asked Answered
C

3

8

How can I read a single character/key from the console without having to hit Enter? There is an old entry in Sun's bug database claiming that it can't be done in pure java. I've found these approaches

  • JNI
  • JLine [http://jline.sourceforge.net/]
  • Javacurses [http://sourceforge.net/projects/javacurses/]

I'd expect to add a single magic-readkey.jar to my classpath, and to write a few lines of code, like (def just-hit (com.acme.MagicConsole/read-char)).

Caryophyllaceous answered 11/7, 2010 at 23:24 Comment(0)
W
12

Here's an "immediate echo" app using JLine which will print ints corresponding to registered keypresses, structured as a Leiningen project:

  1. project.clj:

    (defproject con "1.0.0-SNAPSHOT"
      :description "FIXME: write"
      :main con.core
      :dependencies [[org.clojure/clojure "1.1.0"]
                     [org.clojure/clojure-contrib "1.1.0"]
                     [jline "0.9.94"]])
    
  2. src/con/core.clj:

    (ns con.core
      (:import jline.Terminal)
      (:gen-class))
    
    (defn -main [& args]
      (let [term (Terminal/getTerminal)]
        (while true
          (println (.readCharacter term System/in)))))
    

The functionality in question is provided by the jline.Terminal class, which provides a static method getTerminal returning an instance of a platform-specific subclass which can be used to interact with the terminal. See the Javadoc for more details.

Let's see what asdf looks like...

$ java -jar con-1.0.0-SNAPSHOT-standalone.jar 
97
115
100
102

(C-c still kills the app, of course.)

Warranty answered 11/7, 2010 at 23:45 Comment(3)
If you try the code above, you will find that it does in fact work without enter/return. (Verified just now on an Ubuntu box with openjdk-8-jdk. It seems that clojure-contrib 1.1.0 is no longer available from Central, but you can simply remove it and things will still work – dependency versions are not up to date because this answer dates back to 2010.)Masseur
Not sure, but my Termina.readCharacter simply reads from System.in and still needs to wait for enterDespondency
If that's the behaviour you see with the above version of JLine etc., then my first guess would be that this is a problem with your terminal application, or else some other platform issue. You could see if, say, a simple C program echoing keys works fine – that would make a problem external to the JVM less likely. Might be worth experimenting with other approaches in that case – Lanterna seems very cool indeed (couldn't use it in 2010, as it was released in 2012).Masseur
D
4

For anyone who may be reading this in 2015 and beyond, note that more recent versions of JLine no longer have the method Terminal/getTerminal. I'm sure there is another (possibly better) way to do this now with JLine2, but you can always just use jline "0.9.94" and the accepted answer will still work, at least up to Clojure 1.6 (of note, you no longer need to require clojure.contrib).

As an alternative, I would recommend the excellent clojure-lanterna, which is a Clojure wrapper around the Java Lanterna library. As you can see in the docs, there are get-key and get-key-blocking functions for reading in single characters of input.

Ditmore answered 9/4, 2015 at 15:18 Comment(0)
S
2

If you want to use jline2 there is a ConsoleReader class available which does pretty much the same thing Michał Marczyk explained above:

(ns con.core
  (:import jline.console.ConsoleReader)
  (:gen-class))


(defn -main [& args]
  (while true (->> (ConsoleReader.) (.readCharacter) (println))))
Seal answered 19/11, 2015 at 18:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.