mirror of
https://github.com/sbt/sbt.git
synced 2026-08-22 06:07:24 +02:00
Rework watch options
In this commit, I both restore some sbt 1.2.8 behavior and enhance the
api for setting keyboard shortcuts in watch. I change the default start
message to just show the watch count, the tasks that are being monitored
and, on a new line, the instructions to terminate the watch or show more
options.
Here's what it looks like:
[info] 1. Monitoring source files for spark/compile...
[info] Press <enter> to interrupt or '?' for more options.
?
[info] Options:
[info] <enter> : interrupt (exits sbt in batch mode)
[info] <ctrl-d> : interrupt (exits sbt in batch mode)
[info] 'r' : re-run the command
[info] 's' : return to shell
[info] 'q' : quit sbt
[info] '?' : print options
I also made it so that the new options can be added (and old options
removed) with the watchInputOptions key. For example, to add an option
to reload the build with the key 'l', you could add
ThisBuild / watchInputOptions += Watch.InputOption('l', "reload", Watch.Reload)
to your global build.sbt.
After adding that to my global ~/sbt/1.0/global.sbt file, the output of
'?' became:
[info] Options:
[info] <ctrl-d> : interrupt (exits sbt in batch mode)
[info] <enter> : interrupt (exits sbt in batch mode)
[info] '?' : print options
[info] 'l' : reload
[info] 'q' : quit sbt
[info] 'r' : re-run the command
[info] 's' : return to shell
This commit is contained in:
@@ -28,7 +28,7 @@ import sbt.internal.util.complete.{ Parser, Parsers }
|
|||||||
import sbt.internal.util.{ AttributeKey, JLine, Util }
|
import sbt.internal.util.{ AttributeKey, JLine, Util }
|
||||||
import sbt.nio.FileStamper.LastModified
|
import sbt.nio.FileStamper.LastModified
|
||||||
import sbt.nio.Keys.{ fileInputs, _ }
|
import sbt.nio.Keys.{ fileInputs, _ }
|
||||||
import sbt.nio.Watch.{ Creation, Deletion, Update }
|
import sbt.nio.Watch.{ Creation, Deletion, ShowOptions, Update }
|
||||||
import sbt.nio.file.FileAttributes
|
import sbt.nio.file.FileAttributes
|
||||||
import sbt.nio.{ FileStamp, FileStamper, Watch }
|
import sbt.nio.{ FileStamp, FileStamper, Watch }
|
||||||
import sbt.util.{ Level, _ }
|
import sbt.util.{ Level, _ }
|
||||||
@@ -468,11 +468,13 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
|||||||
val beforeCommand = () => configs.foreach(_.watchSettings.beforeCommand())
|
val beforeCommand = () => configs.foreach(_.watchSettings.beforeCommand())
|
||||||
val onStart: () => Watch.Action =
|
val onStart: () => Watch.Action =
|
||||||
getOnStart(project, commands, configs, rawLogger, count, extracted)
|
getOnStart(project, commands, configs, rawLogger, count, extracted)
|
||||||
val nextInputEvent: () => Watch.Action = parseInputEvents(configs, state, inputStream, logger)
|
val (message, parser, altParser) = getWatchInputOptions(configs, extracted)
|
||||||
|
val nextInputEvent: () => Watch.Action =
|
||||||
|
parseInputEvents(parser, altParser, state, inputStream, logger)
|
||||||
val (nextFileEvent, cleanupFileMonitor): (() => Option[(Watch.Event, Watch.Action)], () => Unit) =
|
val (nextFileEvent, cleanupFileMonitor): (() => Option[(Watch.Event, Watch.Action)], () => Unit) =
|
||||||
getFileEvents(configs, rawLogger, state, count, commands, fileStampCache)
|
getFileEvents(configs, rawLogger, state, count, commands, fileStampCache)
|
||||||
val nextEvent: () => Watch.Action =
|
val nextEvent: () => Watch.Action =
|
||||||
combineInputAndFileEvents(nextInputEvent, nextFileEvent, logger)
|
combineInputAndFileEvents(nextInputEvent, nextFileEvent, message, logger, rawLogger)
|
||||||
val onExit = () => cleanupFileMonitor()
|
val onExit = () => cleanupFileMonitor()
|
||||||
val onTermination = getOnTermination(configs, isCommand)
|
val onTermination = getOnTermination(configs, isCommand)
|
||||||
new Callbacks(nextEvent, beforeCommand, onExit, onStart, onTermination)
|
new Callbacks(nextEvent, beforeCommand, onExit, onStart, onTermination)
|
||||||
@@ -494,6 +496,38 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def getWatchInputOptions(
|
||||||
|
configs: Seq[Config],
|
||||||
|
extracted: Extracted
|
||||||
|
): (String, Parser[Watch.Action], Option[(TaskKey[InputStream], InputStream => Watch.Action)]) = {
|
||||||
|
configs match {
|
||||||
|
case Seq(h) =>
|
||||||
|
val settings = h.watchSettings
|
||||||
|
val parser = settings.inputParser
|
||||||
|
val alt = settings.inputStream.map { k =>
|
||||||
|
k -> settings.inputHandler.getOrElse(defaultInputHandler(parser))
|
||||||
|
}
|
||||||
|
(settings.inputOptionsMessage, parser, alt)
|
||||||
|
case _ =>
|
||||||
|
val options =
|
||||||
|
extracted.getOpt(watchInputOptions in ThisBuild).getOrElse(Watch.defaultInputOptions)
|
||||||
|
val message = extracted
|
||||||
|
.getOpt(watchInputOptionsMessage in ThisBuild)
|
||||||
|
.getOrElse(Watch.defaultInputOptionsMessage(options))
|
||||||
|
val parser = extracted
|
||||||
|
.getOpt(watchInputParser in ThisBuild)
|
||||||
|
.getOrElse(Watch.defaultInputParser(options))
|
||||||
|
val alt = extracted
|
||||||
|
.getOpt(watchInputStream in ThisBuild)
|
||||||
|
.map { _ =>
|
||||||
|
(watchInputStream in ThisBuild) -> extracted
|
||||||
|
.getOpt(watchInputHandler in ThisBuild)
|
||||||
|
.getOrElse(defaultInputHandler(parser))
|
||||||
|
}
|
||||||
|
(message, parser, alt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private def getOnStart(
|
private def getOnStart(
|
||||||
project: ProjectRef,
|
project: ProjectRef,
|
||||||
commands: Seq[String],
|
commands: Seq[String],
|
||||||
@@ -708,7 +742,7 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
|||||||
}
|
}
|
||||||
logger.debug(s"Received file event actions: $builder. Returning: $min")
|
logger.debug(s"Received file event actions: $builder. Returning: $min")
|
||||||
if (min._2 == Watch.Trigger) onTrigger(min._1)
|
if (min._2 == Watch.Trigger) onTrigger(min._1)
|
||||||
Some(min)
|
if (min._2 == Watch.ShowOptions) None else Some(min)
|
||||||
} else None
|
} else None
|
||||||
}, () => monitor.close())
|
}, () => monitor.close())
|
||||||
}
|
}
|
||||||
@@ -729,7 +763,8 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
|||||||
* `() => Seq[Watch.Action]` which avoids actually exposing the InputStream anywhere.
|
* `() => Seq[Watch.Action]` which avoids actually exposing the InputStream anywhere.
|
||||||
*/
|
*/
|
||||||
private def parseInputEvents(
|
private def parseInputEvents(
|
||||||
configs: Seq[Config],
|
parser: Parser[Watch.Action],
|
||||||
|
alternative: Option[(TaskKey[InputStream], InputStream => Watch.Action)],
|
||||||
state: State,
|
state: State,
|
||||||
inputStream: InputStream,
|
inputStream: InputStream,
|
||||||
logger: Logger
|
logger: Logger
|
||||||
@@ -751,10 +786,9 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
|||||||
// If the Config.watchSettings.inputStream is set, the same process is applied except that
|
// If the Config.watchSettings.inputStream is set, the same process is applied except that
|
||||||
// instead of passing in the wrapped InputStream for the input string, we directly pass
|
// instead of passing in the wrapped InputStream for the input string, we directly pass
|
||||||
// in the inputStream provided by Config.watchSettings.inputStream.
|
// in the inputStream provided by Config.watchSettings.inputStream.
|
||||||
val inputHandlers: Seq[ActionParser] = configs.map { c =>
|
val inputHandler: String => Watch.Action = {
|
||||||
val any = Parsers.any.*
|
val any = Parsers.any.*
|
||||||
val inputParser = c.watchSettings.inputParser
|
val fullParser = any ~> parser ~ matched(any)
|
||||||
val parser = any ~> inputParser ~ matched(any)
|
|
||||||
// Each parser gets its own copy of System.in that it can modify while parsing.
|
// Each parser gets its own copy of System.in that it can modify while parsing.
|
||||||
val systemInBuilder = new StringBuilder
|
val systemInBuilder = new StringBuilder
|
||||||
|
|
||||||
@@ -762,34 +796,33 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
|||||||
|
|
||||||
// This string is provided in the closure below by reading from System.in
|
// This string is provided in the closure below by reading from System.in
|
||||||
val default: String => Watch.Action =
|
val default: String => Watch.Action =
|
||||||
string => parse(inputStream(string), systemInBuilder, parser)
|
string => parse(inputStream(string), systemInBuilder, fullParser)
|
||||||
val alternative = c.watchSettings.inputStream
|
val alt = alternative
|
||||||
.map { inputStreamKey =>
|
.map {
|
||||||
val is = extracted.runTask(inputStreamKey, state)._2
|
case (key, handler) =>
|
||||||
val handler = c.watchSettings.inputHandler.getOrElse(defaultInputHandler(inputParser))
|
val is = extracted.runTask(key, state)._2
|
||||||
() => handler(is)
|
() => handler(is)
|
||||||
}
|
}
|
||||||
.getOrElse(() => Watch.Ignore)
|
.getOrElse(() => Watch.Ignore)
|
||||||
(string: String) => (default(string) :: alternative() :: Nil).min
|
string: String => (default(string) :: alt() :: Nil).min
|
||||||
}
|
}
|
||||||
() => {
|
() => {
|
||||||
val stringBuilder = new StringBuilder
|
val stringBuilder = new StringBuilder
|
||||||
while (inputStream.available > 0) stringBuilder += inputStream.read().toChar
|
while (inputStream.available > 0) stringBuilder += inputStream.read().toChar
|
||||||
val newBytes = stringBuilder.toString
|
val newBytes = stringBuilder.toString
|
||||||
val parse: ActionParser => Watch.Action = parser => parser(newBytes)
|
val parse: ActionParser => Watch.Action = parser => parser(newBytes)
|
||||||
val allEvents = inputHandlers.map(parse).filterNot(_ == Watch.Ignore)
|
val event = parse(inputHandler)
|
||||||
if (allEvents.exists(_ != Watch.Ignore)) {
|
if (event != Watch.Ignore) logger.debug(s"Received input event: $event.")
|
||||||
val res = allEvents.min
|
event
|
||||||
logger.debug(s"Received input events: ${allEvents mkString ","}. Taking $res")
|
|
||||||
res
|
|
||||||
} else Watch.Ignore
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def combineInputAndFileEvents(
|
private def combineInputAndFileEvents(
|
||||||
nextInputAction: () => Watch.Action,
|
nextInputAction: () => Watch.Action,
|
||||||
nextFileEvent: () => Option[(Watch.Event, Watch.Action)],
|
nextFileEvent: () => Option[(Watch.Event, Watch.Action)],
|
||||||
logger: Logger
|
options: String,
|
||||||
|
logger: Logger,
|
||||||
|
rawLogger: Logger
|
||||||
): () => Watch.Action = () => {
|
): () => Watch.Action = () => {
|
||||||
val (inputAction: Watch.Action, fileEvent: Option[(Watch.Event, Watch.Action)] @unchecked) =
|
val (inputAction: Watch.Action, fileEvent: Option[(Watch.Event, Watch.Action)] @unchecked) =
|
||||||
Seq(nextInputAction, nextFileEvent).map(_.apply()).toIndexedSeq match {
|
Seq(nextInputAction, nextFileEvent).map(_.apply()).toIndexedSeq match {
|
||||||
@@ -808,7 +841,13 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
|||||||
(if (action != min) s" Dropping in favor of input event: $min" else "")
|
(if (action != min) s" Dropping in favor of input event: $min" else "")
|
||||||
}
|
}
|
||||||
.foreach(logger.debug(_))
|
.foreach(logger.debug(_))
|
||||||
min
|
min match {
|
||||||
|
case ShowOptions =>
|
||||||
|
println("") // This is so the [info] tag appears at the head of a newline
|
||||||
|
rawLogger.info(options)
|
||||||
|
Watch.Ignore
|
||||||
|
case m => m
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@tailrec
|
@tailrec
|
||||||
@@ -896,8 +935,12 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
|||||||
val deletionQuarantinePeriod: FiniteDuration =
|
val deletionQuarantinePeriod: FiniteDuration =
|
||||||
key.get(watchDeletionQuarantinePeriod).getOrElse(Watch.defaultDeletionQuarantinePeriod)
|
key.get(watchDeletionQuarantinePeriod).getOrElse(Watch.defaultDeletionQuarantinePeriod)
|
||||||
val inputHandler: Option[InputStream => Watch.Action] = key.get(watchInputHandler)
|
val inputHandler: Option[InputStream => Watch.Action] = key.get(watchInputHandler)
|
||||||
|
val inputOptions: Seq[Watch.InputOption] =
|
||||||
|
key.get(watchInputOptions).getOrElse(Watch.defaultInputOptions)
|
||||||
|
val inputOptionsMessage: String =
|
||||||
|
key.get(watchInputOptionsMessage).getOrElse(Watch.defaultInputOptionsMessage(inputOptions))
|
||||||
val inputParser: Parser[Watch.Action] =
|
val inputParser: Parser[Watch.Action] =
|
||||||
key.get(watchInputParser).getOrElse(Watch.defaultInputParser)
|
key.get(watchInputParser).getOrElse(Watch.defaultInputParser(inputOptions))
|
||||||
val logLevel: Level.Value = key.get(watchLogLevel).getOrElse(Level.Info)
|
val logLevel: Level.Value = key.get(watchLogLevel).getOrElse(Level.Info)
|
||||||
val beforeCommand: () => Unit = key.get(watchBeforeCommand).getOrElse(() => {})
|
val beforeCommand: () => Unit = key.get(watchBeforeCommand).getOrElse(() => {})
|
||||||
val onFileInputEvent: WatchOnEvent =
|
val onFileInputEvent: WatchOnEvent =
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ object Keys {
|
|||||||
private[sbt] val watchInputHandler = settingKey[InputStream => Watch.Action](
|
private[sbt] val watchInputHandler = settingKey[InputStream => Watch.Action](
|
||||||
"Function that is periodically invoked to determine if the continuous build should be stopped or if a build should be triggered. It will usually read from stdin to respond to user commands. This is only invoked if watchInputStream is set."
|
"Function that is periodically invoked to determine if the continuous build should be stopped or if a build should be triggered. It will usually read from stdin to respond to user commands. This is only invoked if watchInputStream is set."
|
||||||
).withRank(DSetting)
|
).withRank(DSetting)
|
||||||
|
val watchInputOptionsMessage = settingKey[String]("The help message for the watch input options")
|
||||||
|
val watchInputOptions = settingKey[Seq[Watch.InputOption]]("The available input options")
|
||||||
val watchInputStream = taskKey[InputStream](
|
val watchInputStream = taskKey[InputStream](
|
||||||
"The input stream to read for user input events. This will usually be System.in"
|
"The input stream to read for user input events. This will usually be System.in"
|
||||||
).withRank(DSetting)
|
).withRank(DSetting)
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import sbt.nio.file.FileAttributes
|
|||||||
import sbt.util.{ Level, Logger }
|
import sbt.util.{ Level, Logger }
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
|
import scala.collection.mutable
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.util.control.NonFatal
|
import scala.util.control.NonFatal
|
||||||
|
|
||||||
@@ -164,8 +165,9 @@ object Watch {
|
|||||||
*/
|
*/
|
||||||
implicit object ordering extends Ordering[ContinueWatch] {
|
implicit object ordering extends Ordering[ContinueWatch] {
|
||||||
override def compare(left: ContinueWatch, right: ContinueWatch): Int = left match {
|
override def compare(left: ContinueWatch, right: ContinueWatch): Int = left match {
|
||||||
|
case ShowOptions => if (right == ShowOptions) 0 else -1
|
||||||
case Ignore => if (right == Ignore) 0 else 1
|
case Ignore => if (right == Ignore) 0 else 1
|
||||||
case Trigger => if (right == Trigger) 0 else -1
|
case Trigger => if (right == Trigger) 0 else if (right == ShowOptions) 1 else -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -236,11 +238,24 @@ object Watch {
|
|||||||
override def toString: String = s"HandleUnexpectedError($throwable)"
|
override def toString: String = s"HandleUnexpectedError($throwable)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action that indicates that the watch should continue as though nothing happened. This may be
|
||||||
|
* because, for example, no user input was yet available. This trait can be used when we don't
|
||||||
|
* want to take action but we do want to perform a side effect like printing options to the
|
||||||
|
* terminal.
|
||||||
|
*/
|
||||||
|
sealed trait Ignore extends ContinueWatch
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action that indicates that the available options should be printed.
|
||||||
|
*/
|
||||||
|
case object ShowOptions extends Ignore
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action that indicates that the watch should continue as though nothing happened. This may be
|
* Action that indicates that the watch should continue as though nothing happened. This may be
|
||||||
* because, for example, no user input was yet available.
|
* because, for example, no user input was yet available.
|
||||||
*/
|
*/
|
||||||
case object Ignore extends ContinueWatch
|
case object Ignore extends Ignore
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action that indicates that the watch should pause while the build is reloaded. This is used to
|
* Action that indicates that the watch should pause while the build is reloaded. This is used to
|
||||||
@@ -258,7 +273,8 @@ object Watch {
|
|||||||
}
|
}
|
||||||
// For now leave this private in case this isn't the best unapply type signature since it can't
|
// For now leave this private in case this isn't the best unapply type signature since it can't
|
||||||
// be evolved in a binary compatible way.
|
// be evolved in a binary compatible way.
|
||||||
private object Run {
|
object Run {
|
||||||
|
def apply(commands: String*): Run = new Watch.Run(commands: _*)
|
||||||
def unapply(r: Run): Option[List[Exec]] = Some(r.commands.toList.map(Exec(_, None)))
|
def unapply(r: Run): Option[List[Exec]] = Some(r.commands.toList.map(Exec(_, None)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,6 +290,45 @@ object Watch {
|
|||||||
*/
|
*/
|
||||||
trait Custom extends CancelWatch
|
trait Custom extends CancelWatch
|
||||||
|
|
||||||
|
trait InputOption {
|
||||||
|
private[sbt] def parser: Parser[Watch.Action]
|
||||||
|
def input: String
|
||||||
|
def display: String
|
||||||
|
def description: String
|
||||||
|
override def toString: String =
|
||||||
|
s"InputOption(input = $input, display = $display, description = $description)"
|
||||||
|
}
|
||||||
|
object InputOption {
|
||||||
|
private class impl(
|
||||||
|
override val input: String,
|
||||||
|
override val display: String,
|
||||||
|
override val description: String,
|
||||||
|
action: Action
|
||||||
|
) extends InputOption {
|
||||||
|
override private[sbt] def parser: Parser[Watch.Action] = input ^^^ action
|
||||||
|
}
|
||||||
|
def apply(key: Char, description: String, action: Action): InputOption =
|
||||||
|
new impl(key.toString, s"'$key'", description, action)
|
||||||
|
def apply(key: Char, display: String, description: String, action: Action): InputOption =
|
||||||
|
new impl(key.toString, display, description, action)
|
||||||
|
def apply(
|
||||||
|
display: String,
|
||||||
|
description: String,
|
||||||
|
action: Action,
|
||||||
|
chars: Char*
|
||||||
|
): InputOption =
|
||||||
|
new impl(chars.mkString("|"), display, description, action) {
|
||||||
|
override private[sbt] def parser: Parser[Watch.Action] = chars match {
|
||||||
|
case Seq(c) => c ^^^ action
|
||||||
|
case Seq(h, rest @ _*) => rest.foldLeft(h: Parser[Char])(_ | _) ^^^ action
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def apply(input: String, description: String, action: Action): InputOption =
|
||||||
|
new impl(input, s"'$input'", description, action)
|
||||||
|
def apply(input: String, display: String, description: String, action: Action): InputOption =
|
||||||
|
new impl(input, display, description, action)
|
||||||
|
}
|
||||||
|
|
||||||
private type NextAction = () => Watch.Action
|
private type NextAction = () => Watch.Action
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -385,27 +440,44 @@ object Watch {
|
|||||||
* 2) 'r' or 'R' will trigger a build
|
* 2) 'r' or 'R' will trigger a build
|
||||||
* 3) new line characters cancel the watch and return to the shell
|
* 3) new line characters cancel the watch and return to the shell
|
||||||
*/
|
*/
|
||||||
final val defaultInputParser: Parser[Action] = {
|
final def defaultInputParser(options: Seq[Watch.InputOption]): Parser[Action] =
|
||||||
val exitParser: Parser[Action] = chars("xX") ^^^ new Run("exit")
|
distinctOptions(options) match {
|
||||||
val rebuildParser: Parser[Action] = chars("rR") ^^^ Trigger
|
case Seq() => (('\n': Parser[Char]) | '\r' | 4.toChar) ^^^ Run("")
|
||||||
val cancelParser: Parser[Action] = chars(legal = "\n\r") ^^^ new Run("iflast shell")
|
case Seq(h, rest @ _*) => rest.foldLeft(h.parser)(_ | _.parser)
|
||||||
exitParser | rebuildParser | cancelParser
|
|
||||||
}
|
}
|
||||||
|
final val defaultInputOptions: Seq[Watch.InputOption] = Seq(
|
||||||
private[this] val options = {
|
Watch.InputOption("<enter>", "interrupt (exits sbt in batch mode)", Run(""), '\n', '\r'),
|
||||||
val enter = "<enter>"
|
Watch.InputOption(4.toChar, "<ctrl-d>", "interrupt (exits sbt in batch mode)", Run("")),
|
||||||
val opts = Seq(
|
Watch.InputOption('r', "re-run the command", Trigger),
|
||||||
s"$enter: return to the shell",
|
Watch.InputOption('s', "return to shell", Run("iflast shell")),
|
||||||
s"'r': repeat the current command",
|
Watch.InputOption('q', "quit sbt", Run("exit")),
|
||||||
s"'x': exit sbt"
|
Watch.InputOption('?', "print options", ShowOptions)
|
||||||
)
|
)
|
||||||
s"Options:\n${opts.mkString(" ", "\n ", "")}"
|
|
||||||
|
def defaultInputOptionsMessage(options: Seq[InputOption]): String = {
|
||||||
|
val opts = distinctOptions(options).sortBy(_.input)
|
||||||
|
val alignmentLength = opts.map(_.display.length).max + 1
|
||||||
|
val formatted =
|
||||||
|
opts.map(o => s"${o.display}${" " * (alignmentLength - o.display.length)}: ${o.description}")
|
||||||
|
s"Options:\n${formatted.mkString(" ", "\n ", "")}"
|
||||||
}
|
}
|
||||||
private def waitMessage(project: ProjectRef, commands: Seq[String]): String = {
|
private def distinctOptions(options: Seq[InputOption]): Seq[InputOption] = {
|
||||||
val plural = if (commands.size > 1) "s" else ""
|
val distinctOpts = mutable.Set.empty[String]
|
||||||
val cmds = commands.mkString("; ")
|
val opts = new mutable.ArrayBuffer[InputOption]
|
||||||
s"Monitoring source files for updates...\n" +
|
(options match {
|
||||||
s"Project: ${project.project}\nCommand$plural: $cmds\n$options"
|
case Seq() => defaultInputOptions.headOption.toSeq
|
||||||
|
case s => s
|
||||||
|
}).reverse.foreach { o =>
|
||||||
|
if (distinctOpts.add(o.input)) opts += o
|
||||||
|
}
|
||||||
|
opts.reverse
|
||||||
|
}
|
||||||
|
private def waitMessage(project: ProjectRef, commands: Seq[String]): Seq[String] = {
|
||||||
|
val cmds = commands.map(project.project + "/" + _.trim).mkString("; ")
|
||||||
|
Seq(
|
||||||
|
s"Monitoring source files for $cmds...",
|
||||||
|
s"Press <enter> to interrupt or '?' for more options."
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -414,13 +486,16 @@ object Watch {
|
|||||||
*/
|
*/
|
||||||
val defaultStartWatch: (Int, ProjectRef, Seq[String]) => Option[String] = {
|
val defaultStartWatch: (Int, ProjectRef, Seq[String]) => Option[String] = {
|
||||||
(count: Int, project: ProjectRef, commands: Seq[String]) =>
|
(count: Int, project: ProjectRef, commands: Seq[String]) =>
|
||||||
Some(s"$count. ${waitMessage(project, commands)}")
|
{
|
||||||
|
val countStr = s"$count. "
|
||||||
|
Some(s"$countStr${waitMessage(project, commands).mkString(s"\n${" " * countStr.length}")}")
|
||||||
|
}
|
||||||
}.label("Watched.defaultStartWatch")
|
}.label("Watched.defaultStartWatch")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default no-op callback.
|
* Default no-op callback.
|
||||||
*/
|
*/
|
||||||
val defaultBeforeCommand: () => Unit = () => {}
|
val defaultBeforeCommand: () => Unit = (() => {}).label("Watch.defaultBeforeCommand")
|
||||||
|
|
||||||
private[sbt] val defaultCommandOnTermination: (Action, String, Int, State) => State =
|
private[sbt] val defaultCommandOnTermination: (Action, String, Int, State) => State =
|
||||||
onTerminationImpl(ContinuousExecutePrefix).label("Watched.defaultCommandOnTermination")
|
onTerminationImpl(ContinuousExecutePrefix).label("Watched.defaultCommandOnTermination")
|
||||||
@@ -495,6 +570,7 @@ object Watch {
|
|||||||
watchOnFileInputEvent :== Watch.trigger,
|
watchOnFileInputEvent :== Watch.trigger,
|
||||||
watchDeletionQuarantinePeriod :== Watch.defaultDeletionQuarantinePeriod,
|
watchDeletionQuarantinePeriod :== Watch.defaultDeletionQuarantinePeriod,
|
||||||
sbt.Keys.watchService :== Watched.newWatchService,
|
sbt.Keys.watchService :== Watched.newWatchService,
|
||||||
|
watchInputOptions :== Watch.defaultInputOptions,
|
||||||
watchStartMessage :== Watch.defaultStartWatch,
|
watchStartMessage :== Watch.defaultStartWatch,
|
||||||
watchTasks := Continuous.continuousTask.evaluated,
|
watchTasks := Continuous.continuousTask.evaluated,
|
||||||
sbt.Keys.aggregate in watchTasks :== false,
|
sbt.Keys.aggregate in watchTasks :== false,
|
||||||
|
|||||||
Reference in New Issue
Block a user