A Report from the Sewer Hole: Cygwin, JLine, rxvt, and the Scala REPL

I have students running Windows, Linux, and Mac OS X, and I like to encourage students to choose whatever platform makes them most productive. But I also like to be able to give out one set of instructions, grading scripts, etc. to everyone. Fortunately, bash is available everywhere, even on Windows, in the form of Cygwin.

Of course, some students groan because Cygwin can be a pain, with the DOS vs. Unix paths, separators, and line endings. But what's the alternative? To only learn Windows stuff? To learn two sets of commands for everything? I think it is much better to learn one shell language and get good at it.

That's just by way of background, to explain my interest in Cygwin. Today, I want to write down what I found out about running the Scala REPL from Cygwin, so that I have a record for the next time I have to go down the Windows sewer hole.

The Scala REPL uses the JLine library for reading user input. This enables history recall, editing of the current line, and tab completion. Lots of people have trouble with JLine and Cygwin and report various workarounds, and not just with Scala. The Jython and Clojure REPL have similar issues.

1. JLine and Cygwin/rxvt

The key observation is that JLine works (kind of—see below) inside a cmd window (the one with the ridiculous mechanism for copy/paste) and its saner variants such as Console. But it does not work with rxvt. rxvt is popular with Cygwin users because copy/paste is easy, it is a part of Cygwin, and it is less flaky (see below).

To make JLine work with rxvt in Cygwin, we need to

Like this:

cygterm=false
if $cygwin ; then
  case "$TERM" in
      rxvt* | xterm*) cygterm=true ;;
  esac
fi

EXEC=exec

if $cygterm ; then
  EXEC=
  JAVA_OPTS="$JAVA_OPTS -Djline.terminal=jline.UnixTerminal"
  stty -icanon min 1 -echo > /dev/null 2>&1
fi

$EXEC "${JAVACMD:=java}" $JAVA_OPTS -cp "$TOOL_CLASSPATH" \
  -Dscala.home="$SCALA_HOME" -Denv.emacs="$EMACS" \
  scala.tools.nsc.MainGenericRunner  "$@"

if $cygterm ; then
  # Record the exit status immediately, or it will be overridden.
  SCALA_STATUS=$?
  stty icanon echo > /dev/null 2>&1
  exit $SCALA_STATUS
fi

2. JLine and Cygwin/cmd

Using the cmd or Console window with JLine is more problematic. With the default JLine options, the arrow and tab keys work fine, but redirection does not work. For example,

echo 'println("Hello and goodbye")' | scala

hangs, waiting for keyboard input.

If you add the -Djline.terminal=jline.UnixTerminal option, the arrow keys and redirection work, but tab completion doesn't.

Adding the stty commands doesn't help in this case.

3. JLine/Scala and Emacs

As I poked around, I wondered about the -Denv.emacs="$EMACS" setting. This isn't a JLine option. Instead, the Scala REPL doesn't use JLine when env.emacs is set to a non-empty value.

That's unfortunate. In Emacs, there are two ways of running Scala.

In shell mode (M-x shell or M-x scala-run-scala), Emacs takes over the command line editing, and indeed JLine should be turned off. In that case, TERM is dumb and EMACS is t. All works well, except for tab completion. There is probably no hope to get that to work.

In terminal mode (M-x term), TERM is eterm and EMACS is something like 23.1.1 (term:0.96). Then JLine should not be turned off because all works as it should, even tab completion. This is the only way I know for tab completion in the Scala REPL from inside Emacs. So, a better script would use

case "$EMACS" in
  t) JAVA_OPTS="$JAVA_OPTS -Denv.emacs=$EMACS"
esac

For love or money, I could not get M-x term to work in Windows. The Emacs in Cygwin doesn't handle arrow keys, and with the standard Emacs for Windows, I get an error "Spawning child process: invalid argument".

It works fine in Linux, of course.

Conclusions