From 312d33f705fa8ce76cf80598ae172b2b9bf6b7d2 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Mon, 5 Aug 2024 15:24:16 +0200 Subject: [PATCH] Allow JLine to fall back to a dumb terminal Set the `dumb` JLine option to `null` instead of `false` when it is not forced. This allows JLine to fall back to a dumb terminal. Also adapt `CoursierScalaTests` accordingly: test that the `scala` command executes commands fed through standard input correctly. --- .../src/dotty/tools/repl/JLineTerminal.scala | 13 ++++++--- .../tools/coursier/CoursierScalaTests.scala | 28 +++++++++++-------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/compiler/src/dotty/tools/repl/JLineTerminal.scala b/compiler/src/dotty/tools/repl/JLineTerminal.scala index 294f0a331ec2..e4ac1626525e 100644 --- a/compiler/src/dotty/tools/repl/JLineTerminal.scala +++ b/compiler/src/dotty/tools/repl/JLineTerminal.scala @@ -21,11 +21,16 @@ class JLineTerminal extends java.io.Closeable { // Logger.getLogger("org.jline").setLevel(Level.FINEST) private val terminal = - TerminalBuilder.builder() - .dumb(dumbTerminal) // fail early if not able to create a terminal - .build() + var builder = TerminalBuilder.builder() + if System.getenv("TERM") == "dumb" then + // Force dumb terminal if `TERM` is `"dumb"`. + // Note: the default value for the `dumb` option is `null`, which allows + // JLine to fall back to a dumb terminal. This is different than `true` or + // `false` and can't be set using the `dumb` setter. + // This option is used at https://github.com/jline/jline3/blob/894b5e72cde28a551079402add4caea7f5527806/terminal/src/main/java/org/jline/terminal/TerminalBuilder.java#L528. + builder.dumb(true) + builder.build() private val history = new DefaultHistory - def dumbTerminal = Option(System.getenv("TERM")) == Some("dumb") private def blue(str: String)(using Context) = if (ctx.settings.color.value != "never") Console.BLUE + str + Console.RESET diff --git a/compiler/test-coursier/dotty/tools/coursier/CoursierScalaTests.scala b/compiler/test-coursier/dotty/tools/coursier/CoursierScalaTests.scala index 115803d79dc1..86b22009d15a 100644 --- a/compiler/test-coursier/dotty/tools/coursier/CoursierScalaTests.scala +++ b/compiler/test-coursier/dotty/tools/coursier/CoursierScalaTests.scala @@ -75,8 +75,8 @@ class CoursierScalaTests: version() def emptyArgsEqualsRepl() = - val output = CoursierScalaTests.csScalaCmd() - assertTrue(output.mkString("\n").contains("Unable to create a terminal")) // Scala attempted to create REPL so we can assume it is working + val output = CoursierScalaTests.csScalaCmdWithStdin(Seq.empty, Some("println(\"Hello World\")\n:quit")) + assertTrue(output.mkString("\n").contains("Hello World")) emptyArgsEqualsRepl() def run() = @@ -132,8 +132,8 @@ class CoursierScalaTests: compileFilesToJarAndRun() def replWithArgs() = - val output = CoursierScalaTests.csScalaCmd("-source", "3.0-migration") - assertTrue(output.mkString("\n").contains("Unable to create a terminal")) // Scala attempted to create REPL so we can assume it is working + val output = CoursierScalaTests.csScalaCmdWithStdin(Seq("-source", "3.0-migration"), Some("println(\"Hello World\")\n:quit")) + assertTrue(output.mkString("\n").contains("Hello World")) replWithArgs() def argumentFile() = @@ -148,25 +148,31 @@ class CoursierScalaTests: object CoursierScalaTests: - def execCmd(command: String, options: String*): (Int, List[String]) = + private def execCmd(command: String, options: Seq[String] = Seq.empty, stdin: Option[String] = None): (Int, List[String]) = val cmd = (command :: options.toList).toSeq.mkString(" ") val out = new ListBuffer[String] - val code = cmd.!(ProcessLogger(out += _, out += _)) + val process = stdin match + case Some(input) => Process(cmd) #< new java.io.ByteArrayInputStream(input.getBytes) + case None => Process(cmd) + val code = process.!(ProcessLogger(out += _, out += _)) (code, out.toList) def csScalaCmd(options: String*): List[String] = - csCmd("dotty.tools.MainGenericRunner", options*) + csScalaCmdWithStdin(options, None) + + def csScalaCmdWithStdin(options: Seq[String], stdin: Option[String]): List[String] = + csCmd("dotty.tools.MainGenericRunner", options, stdin) def csScalaCompilerCmd(options: String*): List[String] = - csCmd("dotty.tools.dotc.Main", options*) + csCmd("dotty.tools.dotc.Main", options) - private def csCmd(entry: String, options: String*): List[String] = + private def csCmd(entry: String, options: Seq[String], stdin: Option[String] = None): List[String] = val (jOpts, args) = options.partition(_.startsWith("-J")) val newOptions = args match case Nil => args case _ => "--" +: args val newJOpts = jOpts.map(s => s"--java-opt ${s.stripPrefix("-J")}").mkString(" ") - execCmd("./cs", (s"""launch "org.scala-lang:scala3-compiler_3:${sys.env("DOTTY_BOOTSTRAPPED_VERSION")}" $newJOpts --main-class "$entry" --property "scala.usejavacp=true" --property "scala.use_legacy_launcher=true"""" +: newOptions)*)._2 + execCmd("./cs", (s"""launch "org.scala-lang:scala3-compiler_3:${sys.env("DOTTY_BOOTSTRAPPED_VERSION")}" $newJOpts --main-class "$entry" --property "scala.usejavacp=true" --property "scala.use_legacy_launcher=true"""" +: newOptions), stdin)._2 /** Get coursier script */ @BeforeClass def setup(): Unit = @@ -177,7 +183,7 @@ object CoursierScalaTests: case other => fail(s"Unsupported OS for coursier launcher: $other") def runAndCheckCmd(cmd: String, options: String*): Unit = - val (code, out) = execCmd(cmd, options*) + val (code, out) = execCmd(cmd, options) if code != 0 then fail(s"Failed to run $cmd ${options.mkString(" ")}, exit code: $code, output: ${out.mkString("\n")}")