diff --git a/main-command/src/main/scala/sbt/State.scala b/main-command/src/main/scala/sbt/State.scala index 11d0de3eb..e76546533 100644 --- a/main-command/src/main/scala/sbt/State.scala +++ b/main-command/src/main/scala/sbt/State.scala @@ -196,14 +196,18 @@ object State { /** Provides operations and transformations on State. */ implicit def stateOps(s: State): StateOps = new StateOps { - def process(f: (Exec, State) => State): State = - s.remainingCommands match { - case List() => exit(true) - case x :: xs => - log.debug(s"> $x") - f(x, s.copy(remainingCommands = xs, currentCommand = Some(x), history = x :: s.history)) + def process(f: (Exec, State) => State): State = { + def doX(x: Exec, xs: List[Exec]) = { + log.debug(s"> $x") + f(x, s.copy(remainingCommands = xs, currentCommand = Some(x), history = x :: s.history)) } - + def isInteractive = System.console() != null + def hasInput = System.console().reader().ready() + s.remainingCommands match { + case List() => if (isInteractive && hasInput) doX(Exec("shell", s.source), Nil) else exit(true) + case List(x, xs @ _*) => doX(x, xs.toList) + } + } def :::(newCommands: List[String]): State = ++:(newCommands map { Exec(_, s.source) }) def ++:(newCommands: List[Exec]): State = s.copy(remainingCommands = newCommands ::: s.remainingCommands) def ::(command: String): State = +:(Exec(command, s.source)) diff --git a/main/src/main/scala/sbt/MainLoop.scala b/main/src/main/scala/sbt/MainLoop.scala index f269d571e..d4be1d232 100644 --- a/main/src/main/scala/sbt/MainLoop.scala +++ b/main/src/main/scala/sbt/MainLoop.scala @@ -72,6 +72,19 @@ object MainLoop { val newLogging = state.globalLogging.newAppender(full, out, logBacking) // transferLevels(state, newLogging) val loggedState = state.copy(globalLogging = newLogging) + def isInteractive = System.console() != null + def hasCommand(cmd: String): Boolean = + (state.remainingCommands find { x => x.commandLine == cmd }).isDefined + /** + * The "boot" command adds "iflast shell" ("if last shell") + * which basically means it falls back to shell if there are no further commands + */ + def endsWithBoot = state.remainingCommands.lastOption exists (_.commandLine == "boot") + if (isInteractive && !hasCommand("shell") && !hasCommand("server") && !endsWithBoot) { + state.log warn "Executing in batch mode." + state.log warn " For better performance, hit [ENTER] to switch to interactive mode, or" + state.log warn " consider launching sbt without any commands, or explicitly passing 'shell'" + } try run(loggedState) finally out.close() } diff --git a/notes/0.13.14/stay-in-shell.md b/notes/0.13.14/stay-in-shell.md new file mode 100644 index 000000000..be4c7f956 --- /dev/null +++ b/notes/0.13.14/stay-in-shell.md @@ -0,0 +1,7 @@ +### Improvements + +- Notifies & enables users to stay in sbt's shell on the warm JVM by hitting \[ENTER\] while sbt is running. [#2987][]/[#2996][] by [@dwijnand][] + +[#2987]: https://github.com/sbt/sbt/issues/2987 +[#2996]: https://github.com/sbt/sbt/pull/2996 +[@dwijnand]: https://github.com/dwijnand