From ddb626a9bed638faa461eba24839273f2ca33699 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Fri, 18 Sep 2020 12:19:35 -0700 Subject: [PATCH] Allow users to configure standard input setting Certain tasks may prefer to have the input set to raw mode and/or have echo off. The specific use case is that it is difficult to get the ammonite console to work correctly with the thin client. The problem is that the ammonite console runs some tty commands. These commands will only work on the tty of the thin client when the thin client itself has launched the sbt server session (since they share the same tty). Once the thin client that launched the server exits, the ammonite console will never work again with that server session. A workaround is to launch sbt separately and leave that server session open. Then, if the run task is configured with canonical input set to false and echo disabled, the thin client will work. In the future, it's possible that ammonite could be updated to not rely on calling stty commands and then the thin client could work with the ammonite console even after the initial thin client session has exited provided canonical input and echo are disabled. --- main/src/main/scala/sbt/Defaults.scala | 26 ++++++++++++++++++++++---- main/src/main/scala/sbt/Keys.scala | 2 ++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 64adc35a5..a41fa5f8c 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -386,6 +386,8 @@ object Defaults extends BuildCommon { sys.env.contains("CI") || SysProp.ci, // watch related settings pollInterval :== Watch.defaultPollInterval, + canonicalInput :== true, + echoInput :== true, ) ++ LintUnused.lintSettings ++ DefaultBackgroundJobService.backgroundJobServiceSettings ++ RemoteCache.globalSettings @@ -1700,6 +1702,20 @@ object Defaults extends BuildCommon { /** Implements `cleanFiles` task. */ private[sbt] def cleanFilesTask: Initialize[Task[Vector[File]]] = Def.task { Vector.empty[File] } + private[this] def termWrapper(canonical: Boolean, echo: Boolean): (() => Unit) => (() => Unit) = + (f: () => Unit) => + () => { + val term = Terminal.get + if (!canonical) { + term.enterRawMode() + if (echo) term.setEchoEnabled(echo) + } else if (!echo) term.setEchoEnabled(false) + try f() + finally { + if (!canonical) term.exitRawMode() + if (!echo) term.setEchoEnabled(true) + } + } def bgRunMainTask( products: Initialize[Task[Classpath]], classpath: Initialize[Task[Classpath]], @@ -1713,6 +1729,7 @@ object Defaults extends BuildCommon { val service = bgJobService.value val (mainClass, args) = parser.parsed val hashClasspath = (bgHashClasspath in bgRunMain).value + val wrapper = termWrapper(canonicalInput.value, echoInput.value) service.runInBackgroundWithLoader(resolvedScoped.value, state.value) { (logger, workingDir) => val files = if (copyClasspath.value) @@ -1722,9 +1739,9 @@ object Defaults extends BuildCommon { scalaRun.value match { case r: Run => val loader = r.newLoader(cp) - (Some(loader), () => r.runWithLoader(loader, cp, mainClass, args, logger).get) + (Some(loader), wrapper(() => r.runWithLoader(loader, cp, mainClass, args, logger).get)) case sr => - (None, () => sr.run(mainClass, cp, args, logger).get) + (None, wrapper(() => sr.run(mainClass, cp, args, logger).get)) } } } @@ -1743,6 +1760,7 @@ object Defaults extends BuildCommon { val service = bgJobService.value val mainClass = mainClassTask.value getOrElse sys.error("No main class detected.") val hashClasspath = (bgHashClasspath in bgRun).value + val wrapper = termWrapper(canonicalInput.value, echoInput.value) service.runInBackgroundWithLoader(resolvedScoped.value, state.value) { (logger, workingDir) => val files = if (copyClasspath.value) @@ -1753,9 +1771,9 @@ object Defaults extends BuildCommon { scalaRun.value match { case r: Run => val loader = r.newLoader(cp) - (Some(loader), () => r.runWithLoader(loader, cp, mainClass, args, logger).get) + (Some(loader), wrapper(() => r.runWithLoader(loader, cp, mainClass, args, logger).get)) case sr => - (None, () => sr.run(mainClass, cp, args, logger).get) + (None, wrapper(() => sr.run(mainClass, cp, args, logger).get)) } } } diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 7ef76e6e0..2e25ea715 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -68,6 +68,8 @@ object Keys { val logBuffered = settingKey[Boolean]("True if logging should be buffered until work completes.").withRank(CSetting) val sLog = settingKey[Logger]("Logger usable by settings during project loading.").withRank(CSetting) val serverLog = taskKey[Unit]("A dummy task to set server log level using Global / serverLog / logLevel.").withRank(CTask) + val canonicalInput = settingKey[Boolean]("Toggles whether a task should use canonical input (line buffered with echo) or raw input").withRank(DSetting) + val echoInput = settingKey[Boolean]("Toggles whether a task should echo user input").withRank(DSetting) // Project keys val autoGeneratedProject = settingKey[Boolean]("If it exists, represents that the project (and name) were automatically created, rather than user specified.").withRank(DSetting)