Search code examples
terminalclojureconsolestdintty

How can I read a single character from STDIN in Clojure?


I'm writing a loop that only works on single-character inputs. Depending on what the user pressed (without pressing ENTER), I want to display what key the user typed in, and then repeat. If the user presses "q", then the loop must exit.

Constraints:

  • I don't care about Unicode (only supporting US ASCII character set is desirable).
  • I only care about Unixy systems (only Linux is fine).
  • I am using Leiningen.

Can this be done? Some searching led me to jline2 which had ConsoleReader class but it seems to have disappeared in jline3.


Solution

  • I saw https://gist.github.com/mikeananev/f5138eeee12144a3ca82136184e7a742 and using the linked duplicate answer, came up with this:

    ; Need `(:import [org.jline.terminal TerminalBuilder Terminal])`
    
    (defn new-terminal
      "creates new JLine3 Terminal.
      returns terminal object"
      ^Terminal [term-name]
      (let [terminal (-> (TerminalBuilder/builder)
                         (.jna true)
                         (.system true)
                         (.name term-name)
                         (.build))]
        terminal))
    
    (defn interactive-loop []
      (let [t (new-terminal "xxx")]
        (.enterRawMode t)
        (let [reader (.reader t)]
        (loop [char-int (.read reader)]
          (case (char char-int)
            \q (do
              (println "Bye!")
              (.close reader)
              (.close t))
            (do (println (char char-int))
                (recur (.read reader))))))))
    

    I'm on NixOS and apparently the jline3 library depends on the infocmp binary being installed, which is part of the popular ncurses package. Unfortunately the Nixpkgs version I use currently does not package this binary so I put in a PR here: https://github.com/NixOS/nixpkgs/pull/72135