From b96be5343b5db402ff65cd9f365381ef224734d6 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Sat, 11 May 2019 19:20:29 -0700 Subject: [PATCH] Support char buffered stdin on windows in continuous I finally realized that the trick is that for non cygwin windows, the available method on the jline wrapped input stream always returns zero. Unlike on posix, however, the read method is interruptible which means that we can just spin up a background thread that polls from the input stream and writes it into a buffer. I verified that it was no longer necessary to hit after 'r' to rerun the continuous command on my windows vm after this change. --- .../main/scala/sbt/internal/Continuous.scala | 48 +++++++++++++++---- main/src/main/scala/sbt/nio/Watch.scala | 5 +- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/main/src/main/scala/sbt/internal/Continuous.scala b/main/src/main/scala/sbt/internal/Continuous.scala index 450611fd6..693142ab4 100644 --- a/main/src/main/scala/sbt/internal/Continuous.scala +++ b/main/src/main/scala/sbt/internal/Continuous.scala @@ -9,7 +9,7 @@ package sbt package internal import java.io.{ ByteArrayInputStream, InputStream, File => _ } -import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.atomic.{ AtomicBoolean, AtomicInteger } import sbt.BasicCommandStrings.{ ContinuousExecutePrefix, @@ -269,13 +269,45 @@ private[sbt] object Continuous extends DeprecatedContinuous { f(commands, s, valid, invalid) } - private[this] def withCharBufferedStdIn[R](f: InputStream => R): R = - if (!Util.isWindows) { - val terminal = JLine.terminal - terminal.init() - terminal.setEchoEnabled(true) - f(terminal.wrapInIfNeeded(System.in)) - } else f(System.in) + private[this] def withCharBufferedStdIn[R](f: InputStream => R): R = { + val terminal = JLine.terminal + terminal.init() + terminal.setEchoEnabled(true) + val wrapped = terminal.wrapInIfNeeded(System.in) + if (Util.isNonCygwinWindows) { + val inputStream: InputStream with AutoCloseable = new InputStream with AutoCloseable { + private[this] val buffer = new java.util.LinkedList[Int] + private[this] val closed = new AtomicBoolean(false) + private[this] val thread = new Thread("Continuous-input-stream-reader") { + setDaemon(true) + start() + @tailrec + override def run(): Unit = { + try { + if (!closed.get()) { + buffer.add(wrapped.read()) + } + } catch { + case _: InterruptedException => + } + if (!closed.get()) run() + } + } + override def available(): Int = buffer.size() + override def read(): Int = buffer.poll() + override def close(): Unit = if (closed.compareAndSet(false, true)) { + thread.interrupt() + } + } + try { + f(inputStream) + } finally { + inputStream.close() + } + } else { + f(wrapped) + } + } private[sbt] def runToTermination( state: State, diff --git a/main/src/main/scala/sbt/nio/Watch.scala b/main/src/main/scala/sbt/nio/Watch.scala index 83e57fc3e..d911c98dc 100644 --- a/main/src/main/scala/sbt/nio/Watch.scala +++ b/main/src/main/scala/sbt/nio/Watch.scala @@ -393,11 +393,10 @@ object Watch { private[this] val options = { val enter = "" - val newLine = if (Util.isWindows) enter else "" val opts = Seq( s"$enter: return to the shell", - s"'r$newLine': repeat the current command", - s"'x$newLine': exit sbt" + s"'r': repeat the current command", + s"'x': exit sbt" ) s"Options:\n${opts.mkString(" ", "\n ", "")}" }