diff --git a/main/src/main/scala/sbt/MainLoop.scala b/main/src/main/scala/sbt/MainLoop.scala index a2c4efd7d..99256ed79 100644 --- a/main/src/main/scala/sbt/MainLoop.scala +++ b/main/src/main/scala/sbt/MainLoop.scala @@ -23,6 +23,7 @@ import sbt.util.Logger import scala.annotation.tailrec import scala.util.control.NonFatal +import sbt.internal.FastTrackCommands object MainLoop { @@ -201,7 +202,14 @@ object MainLoop { StandardMain.exchange.setState(progressState) StandardMain.exchange.setExec(Some(exec)) StandardMain.exchange.unprompt(ConsoleUnpromptEvent(exec.source)) - val newState = Command.process(exec.commandLine, progressState) + /* + * FastTrackCommands.evaluate can be significantly faster than Command.process because + * it avoids an expensive parsing step for internal commands that are easy to parse. + * Dropping (FastTrackCommands.evaluate ... getOrElse) should be functionally identical + * but slower. + */ + val newState = FastTrackCommands.evaluate(progressState, exec.commandLine) getOrElse + Command.process(exec.commandLine, progressState) // Flush the terminal output after command evaluation to ensure that all output // is displayed in the thin client before we report the command status. val terminal = channelName.flatMap(exchange.channelForName(_).map(_.terminal)) diff --git a/main/src/main/scala/sbt/internal/Continuous.scala b/main/src/main/scala/sbt/internal/Continuous.scala index 557fa3300..cd6682433 100644 --- a/main/src/main/scala/sbt/internal/Continuous.scala +++ b/main/src/main/scala/sbt/internal/Continuous.scala @@ -1297,7 +1297,7 @@ private[sbt] object ContinuousCommands { case _ => state } } - private[this] val failWatchCommand = watchCommand(failWatch) { (channel, state) => + private[sbt] val failWatchCommand = watchCommand(failWatch) { (channel, state) => state.fail } /* diff --git a/main/src/main/scala/sbt/internal/FastTrackCommands.scala b/main/src/main/scala/sbt/internal/FastTrackCommands.scala new file mode 100644 index 000000000..8c53f81de --- /dev/null +++ b/main/src/main/scala/sbt/internal/FastTrackCommands.scala @@ -0,0 +1,53 @@ +/* + * sbt + * Copyright 2011 - 2018, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +package sbt +package internal + +import BasicCommandStrings._ +import BasicCommands._ +import BuiltinCommands.{ setTerminalCommand, shell, waitCmd } +import ContinuousCommands._ + +import sbt.internal.util.complete.Parser + +/** This is used to speed up command parsing. */ +private[sbt] object FastTrackCommands { + private def fromCommand( + cmd: String, + command: Command, + arguments: Boolean = true, + ): (State, String) => Option[State] = + (s, c) => + Parser.parse(if (arguments) c else "", command.parser(s)) match { + case Right(newState) => Some(newState()) + case l => None + } + private val commands = Map[String, (State, String) => Option[State]]( + FailureWall -> { case (s, c) => if (c == FailureWall) Some(s) else None }, + StashOnFailure -> fromCommand(StashOnFailure, stashOnFailure, arguments = false), + PopOnFailure -> fromCommand(PopOnFailure, popOnFailure, arguments = false), + Shell -> fromCommand(Shell, shell), + SetTerminal -> fromCommand(SetTerminal, setTerminalCommand), + failWatch -> fromCommand(failWatch, failWatchCommand), + preWatch -> fromCommand(preWatch, preWatchCommand), + postWatch -> fromCommand(postWatch, postWatchCommand), + runWatch -> fromCommand(runWatch, runWatchCommand), + stopWatch -> fromCommand(stopWatch, stopWatchCommand), + waitWatch -> fromCommand(waitWatch, waitCmd), + ) + private[sbt] def evaluate(state: State, cmd: String): Option[State] = { + cmd.trim.split(" ") match { + case Array(h, _*) => + commands.get(h) match { + case Some(command) => command(state, cmd) + case _ => None + } + case _ => None + } + } +}