Skip to content

Commit 30be3cb

Browse files
committed
Remove Worksheet dependency on JLine
The worksheet used to spawn a REPL with JLine just to be able to read from an InputStream. This is alternative solution that simply uses java.util.Scanner to read from an InputStream. For that, we need to agree on a delimiter. We choose a character within the private user area to not conflict with the output produced by the REPL.
1 parent 86e5c12 commit 30be3cb

File tree

9 files changed

+59
-69
lines changed

9 files changed

+59
-69
lines changed

compiler/src/dotty/tools/repl/JLineTerminal.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ import org.jline.reader.impl.history.DefaultHistory
1313
import org.jline.terminal.TerminalBuilder
1414
import org.jline.utils.AttributedString
1515

16-
final class JLineTerminal(needsTerminal: Boolean) extends java.io.Closeable {
16+
final class JLineTerminal extends java.io.Closeable {
1717
// import java.util.logging.{Logger, Level}
1818
// Logger.getLogger("org.jline").setLevel(Level.FINEST)
1919

2020
private val terminal =
2121
TerminalBuilder.builder()
22-
.dumb(!needsTerminal) // fail early if we need a terminal and can't create one
22+
.dumb(false) // fail early if not able to create a terminal
2323
.build()
2424
private val history = new DefaultHistory
2525

compiler/src/dotty/tools/repl/Main.scala

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,5 @@ package dotty.tools.repl
33
/** Main entry point to the REPL */
44
object Main {
55
def main(args: Array[String]): Unit =
6-
new ReplDriver(args).runUntilQuit(needsTerminal = true)
7-
}
8-
9-
object WorksheetMain {
10-
def main(args: Array[String]): Unit =
11-
new ReplDriver(args).runUntilQuit(needsTerminal = false)
6+
new ReplDriver(args).runUntilQuit()
127
}

compiler/src/dotty/tools/repl/ReplDriver.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ class ReplDriver(settings: Array[String],
101101
* observable outside of the CLI, for this reason, most helper methods are
102102
* `protected final` to facilitate testing.
103103
*/
104-
final def runUntilQuit(needsTerminal: Boolean, initialState: State = initialState): State = {
105-
val terminal = new JLineTerminal(needsTerminal)
104+
final def runUntilQuit(initialState: State = initialState): State = {
105+
val terminal = new JLineTerminal
106106

107107
/** Blockingly read a line, getting back a parse result */
108108
def readLine(state: State): ParseResult = {

language-server/src/dotty/tools/languageserver/worksheet/Evaluator.scala

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ package dotty.tools.languageserver.worksheet
22

33
import dotty.tools.dotc.core.Contexts.Context
44

5-
import java.io.{File, PrintStream}
5+
import java.io.{File, PrintStream, IOException}
6+
import java.lang.ProcessBuilder.Redirect
67

78
import org.eclipse.lsp4j.jsonrpc.CancelChecker
89

@@ -57,7 +58,7 @@ private class Evaluator private (javaExec: String,
5758
new ProcessBuilder(
5859
javaExec,
5960
"-classpath", scala.util.Properties.javaClassPath,
60-
dotty.tools.repl.WorksheetMain.getClass.getName.stripSuffix("$"),
61+
ReplProcess.getClass.getName.stripSuffix("$"),
6162
"-classpath", userClasspath,
6263
"-color:never")
6364
.redirectErrorStream(true)
@@ -67,15 +68,12 @@ private class Evaluator private (javaExec: String,
6768
private val processInput = new PrintStream(process.getOutputStream())
6869

6970
// Messages coming out of the REPL
70-
private val processOutput = new ReplReader(process.getInputStream())
71-
processOutput.start()
71+
private val processOutput = new InputStreamConsumer(process.getInputStream())
7272

7373
// The thread that monitors cancellation
7474
private val cancellationThread = new CancellationThread(cancelChecker, this)
7575
cancellationThread.start()
7676

77-
// Wait for the REPL to be ready
78-
processOutput.next()
7977

8078
/** Is the process that runs the REPL still alive? */
8179
def isAlive(): Boolean = process.isAlive()
@@ -86,10 +84,13 @@ private class Evaluator private (javaExec: String,
8684
* @param command The command to evaluate.
8785
* @return The result from the REPL.
8886
*/
89-
def eval(command: String): Option[String] = {
90-
processInput.println(command)
87+
def eval(command: String): String = {
88+
processInput.print(command)
89+
processInput.print(InputStreamConsumer.delimiter)
9190
processInput.flush()
92-
processOutput.next().map(_.trim)
91+
92+
try processOutput.next().trim
93+
catch { case _: IOException => "" }
9394
}
9495

9596
/**
@@ -102,7 +103,6 @@ private class Evaluator private (javaExec: String,
102103

103104
/** Terminate this JVM. */
104105
def exit(): Unit = {
105-
processOutput.interrupt()
106106
process.destroyForcibly()
107107
Evaluator.previousEvaluator = None
108108
cancellationThread.interrupt()
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package dotty.tools.languageserver.worksheet
2+
3+
import java.io.{InputStream, IOException}
4+
import java.util.Scanner
5+
6+
class InputStreamConsumer(in: InputStream) {
7+
private[this] val scanner =
8+
new Scanner(in).useDelimiter(InputStreamConsumer.delimiter)
9+
10+
/** Finds and returns the next complete token from this input stream.
11+
*
12+
* A complete token is preceded and followed by input that matches the delimiter pattern.
13+
* This method may block while waiting for input
14+
*/
15+
def next(): String =
16+
try scanner.next()
17+
catch {
18+
case _: NoSuchElementException =>
19+
throw new IOException("InputStream closed")
20+
}
21+
}
22+
23+
object InputStreamConsumer {
24+
def delimiter = "\uE000" // withing private use area
25+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package dotty.tools.languageserver.worksheet
2+
3+
import dotty.tools.repl.ReplDriver
4+
5+
object ReplProcess {
6+
def main(args: Array[String]): Unit = {
7+
val driver = new ReplDriver(args)
8+
val in = new InputStreamConsumer(System.in)
9+
var state = driver.initialState
10+
11+
while (true) {
12+
val code = in.next() // blocking
13+
state = driver.run(code)(state)
14+
print(InputStreamConsumer.delimiter) // needed to mark the end of REPL output
15+
}
16+
}
17+
}

language-server/src/dotty/tools/languageserver/worksheet/ReplReader.scala

Lines changed: 0 additions & 47 deletions
This file was deleted.

language-server/src/dotty/tools/languageserver/worksheet/Worksheet.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ object Worksheet {
4949
}
5050
queries.foreach { (line, code) =>
5151
cancelChecker.checkCanceled()
52-
val res = evaluator.eval(code).getOrElse("")
52+
val res = evaluator.eval(code)
5353
cancelChecker.checkCanceled()
5454
if (res.nonEmpty)
5555
sendMessage(line, res)

sbt-bridge/src/xsbt/ConsoleInterface.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class ConsoleInterface {
4141

4242
val s1 = driver.run(initialCommands)(s0)
4343
// TODO handle failure during initialisation
44-
val s2 = driver.runUntilQuit(needsTerminal = true, s1)
44+
val s2 = driver.runUntilQuit(s1)
4545
driver.run(cleanupCommands)(s2)
4646
}
4747
}

0 commit comments

Comments
 (0)