Skip to main content

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

Posted by cayhorstmann on November 24, 2010 at 2:39 AM PST

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

  • Surround the Scala execution with stty calls to adjust terminal settings:
    stty -icanon min 1 -echo
    # Run Scala (without exec)
    stty icanon echo
  • Use the command line option
    -Djline.terminal=jline.UnixTerminal
  • Capture the exit status from scala and return it after the second stty

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

  • If you use rxvt and Cygwin, fix up the script that launches Scala as described in section 1.
  • if you want to pipe input into the REPL (e.g. for checking homework), use rxvt in Cygwin, not cmd or Console.
  • If you want to run the Scala REPL inside Emacs and have tab completion, fix up the script as described in section 3 and run M-term. But don't try that from Windows.
Related Topics >>

Comments

Thank you so much, Mr. Horstmann, for posting this.  ...

Thank you so much, Mr. Horstmann, for posting this. Almost a year and a half later it's still helping others, even those who don't use Scala.

I work constantly in Cygwin/rxvt and encountered the same problem you describe above after upgrading to Grails 2.0.1. The few workarounds mentioned on other sites apparently don't apply to rxvt. Your solution worked perfectly and saved me much heartache.

A Report from the Sewer Hole:

I haven't ever tried to do any heavy development with it, but I have always found UnxUtils a bit easier to work with than Cygwin. Might be worth looking into as an alternative:
http://unxutils.sourceforge.net/