diff --git a/main/src/main/scala/sbt/internal/Continuous.scala b/main/src/main/scala/sbt/internal/Continuous.scala index 6c26587fa..94fe88a4a 100644 --- a/main/src/main/scala/sbt/internal/Continuous.scala +++ b/main/src/main/scala/sbt/internal/Continuous.scala @@ -351,17 +351,17 @@ private[sbt] object Continuous extends DeprecatedContinuous { aggregate(configs, logger, in, s, currentCount, isCommand, commands, fileStampCache) val task = () => { currentCount.getAndIncrement() + callbacks.beforeCommand() // abort as soon as one of the tasks fails valid.takeWhile(_._3.apply()) updateLegacyWatchState(s, configs.flatMap(_.inputs().map(_.glob)), currentCount.get()) () } - callbacks.onEnter() - // Here we enter the Watched.watch state machine. We will not return until one of the - // state machine callbacks returns Watched.CancelWatch, Watched.Custom, Watched.HandleError - // or Watched.ReloadException. The task defined above will be run at least once. It will be run - // additional times whenever the state transition callbacks return Watched.Trigger. try { + // Here we enter the Watched.watch state machine. We will not return until one of the + // state machine callbacks returns Watched.CancelWatch, Watched.Custom, Watched.HandleError + // or Watched.ReloadException. The task defined above will be run at least once. It will be run + // additional times whenever the state transition callbacks return Watched.Trigger. val terminationAction = Watch(task, callbacks.onStart, callbacks.nextEvent) terminationAction match { case e: Watch.HandleUnexpectedError => @@ -427,7 +427,7 @@ private[sbt] object Continuous extends DeprecatedContinuous { private class Callbacks( val nextEvent: () => Watch.Action, - val onEnter: () => Unit, + val beforeCommand: () => Unit, val onExit: () => Unit, val onStart: () => Watch.Action, val onTermination: (Watch.Action, String, Int, State) => State @@ -468,19 +468,16 @@ private[sbt] object Continuous extends DeprecatedContinuous { ): Callbacks = { val project = extracted.currentRef.project val logger = setLevel(rawLogger, configs.map(_.watchSettings.logLevel).min, state) - val onEnter = () => configs.foreach(_.watchSettings.onEnter()) + val beforeCommand = () => configs.foreach(_.watchSettings.beforeCommand()) val onStart: () => Watch.Action = getOnStart(project, commands, configs, rawLogger, count) val nextInputEvent: () => Watch.Action = parseInputEvents(configs, state, inputStream, logger) val (nextFileEvent, cleanupFileMonitor): (() => Option[(Watch.Event, Watch.Action)], () => Unit) = getFileEvents(configs, rawLogger, state, count, commands, fileStampCache) val nextEvent: () => Watch.Action = combineInputAndFileEvents(nextInputEvent, nextFileEvent, logger) - val onExit = () => { - cleanupFileMonitor() - configs.foreach(_.watchSettings.onExit()) - } + val onExit = () => cleanupFileMonitor() val onTermination = getOnTermination(configs, isCommand) - new Callbacks(nextEvent, onEnter, onExit, onStart, onTermination) + new Callbacks(nextEvent, beforeCommand, onExit, onStart, onTermination) } private def getOnTermination( @@ -509,7 +506,7 @@ private[sbt] object Continuous extends DeprecatedContinuous { val f: () => Seq[Watch.Action] = () => { configs.map { params => val ws = params.watchSettings - ws.onIteration.map(_(count.get)).getOrElse { + ws.onIteration.map(_(count.get, project, commands)).getOrElse { if (configs.size == 1) { // Only allow custom start messages for single tasks ws.startMessage match { case Some(Left(sm)) => logger.info(sm(params.watchState(count.get()))) @@ -900,11 +897,10 @@ private[sbt] object Continuous extends DeprecatedContinuous { val inputParser: Parser[Watch.Action] = key.get(watchInputParser).getOrElse(Watch.defaultInputParser) val logLevel: Level.Value = key.get(watchLogLevel).getOrElse(Level.Info) - val onEnter: () => Unit = key.get(watchOnEnter).getOrElse(() => {}) - val onExit: () => Unit = key.get(watchOnExit).getOrElse(() => {}) + val beforeCommand: () => Unit = key.get(watchBeforeCommand).getOrElse(() => {}) val onFileInputEvent: WatchOnEvent = key.get(watchOnFileInputEvent).getOrElse(Watch.trigger) - val onIteration: Option[Int => Watch.Action] = key.get(watchOnIteration) + val onIteration: Option[(Int, String, Seq[String]) => Watch.Action] = key.get(watchOnIteration) val onTermination: Option[(Watch.Action, String, Int, State) => State] = key.get(watchOnTermination) val startMessage: StartMessage = getStartMessage(key) diff --git a/main/src/main/scala/sbt/nio/Keys.scala b/main/src/main/scala/sbt/nio/Keys.scala index 528cfaabd..b85271515 100644 --- a/main/src/main/scala/sbt/nio/Keys.scala +++ b/main/src/main/scala/sbt/nio/Keys.scala @@ -68,12 +68,12 @@ object Keys { "detected regardless of whether or not the underlying file has actually changed." // watch related keys + val watchBeforeCommand = settingKey[() => Unit]( + "Function to run prior to running a command in a continuous build." + ).withRank(DSetting) val watchForceTriggerOnAnyChange = Def.settingKey[Boolean](forceTriggerOnAnyChangeMessage).withRank(DSetting) - val watchLogLevel = - settingKey[sbt.util.Level.Value]("Transform the default logger in continuous builds.") - .withRank(DSetting) - 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." ).withRank(DSetting) val watchInputStream = taskKey[InputStream]( @@ -82,16 +82,13 @@ object Keys { val watchInputParser = settingKey[Parser[Watch.Action]]( "A parser of user input that can be used to trigger or exit a continuous build" ).withRank(DSetting) - val watchOnEnter = settingKey[() => Unit]( - "Function to run prior to beginning a continuous build. This will run before the continuous task(s) is(are) first evaluated." - ).withRank(DSetting) - val watchOnExit = settingKey[() => Unit]( - "Function to run upon exit of a continuous build. It can be used to cleanup resources used during the watch." - ).withRank(DSetting) + val watchLogLevel = + settingKey[sbt.util.Level.Value]("Transform the default logger in continuous builds.") + .withRank(DSetting) val watchOnFileInputEvent = settingKey[(Int, Watch.Event) => Watch.Action]( "Callback to invoke if an event is triggered in a continuous build by one of the files matching an fileInput glob for the task and its transitive dependencies" ).withRank(DSetting) - val watchOnIteration = settingKey[Int => Watch.Action]( + val watchOnIteration = settingKey[(Int, String, Seq[String]) => Watch.Action]( "Function that is invoked before waiting for file system events or user input events." ).withRank(DSetting) val watchOnTermination = settingKey[(Watch.Action, String, Int, State) => State]( diff --git a/main/src/main/scala/sbt/nio/Watch.scala b/main/src/main/scala/sbt/nio/Watch.scala index 745af45a1..9a073f45b 100644 --- a/main/src/main/scala/sbt/nio/Watch.scala +++ b/main/src/main/scala/sbt/nio/Watch.scala @@ -420,7 +420,7 @@ object Watch { /** * Default no-op callback. */ - val defaultOnEnter: () => Unit = () => {} + val defaultBeforeCommand: () => Unit = () => {} private[sbt] val defaultCommandOnTermination: (Action, String, Int, State) => State = onTerminationImpl(ContinuousExecutePrefix).label("Watched.defaultCommandOnTermination") @@ -473,16 +473,25 @@ object Watch { final val defaultPollInterval: FiniteDuration = 500.milliseconds /** - * A constant function that returns an Option wrapped string that clears the screen when - * written to stdout. + * Clears the console screen when evaluated. */ - final val clearOnTrigger: Int => Option[String] = - ((_: Int) => Some(Watched.clearScreen)).label("Watched.clearOnTrigger") + final val clearScreen: () => Unit = + (() => println("\u001b[2J\u001b[0;0H")).label("Watch.clearScreen") + + /** + * A function that first clears the screen and then returns the default on trigger message. + */ + final val clearScreenOnTrigger: (Int, Path, Seq[String]) => Option[String] = { + (count: Int, path: Path, commands: Seq[String]) => + clearScreen() + defaultOnTriggerMessage(count, path, commands) + }.label("Watch.clearScreenOnTrigger") + private[sbt] def defaults: Seq[Def.Setting[_]] = Seq( sbt.Keys.watchAntiEntropy :== Watch.defaultAntiEntropy, watchAntiEntropyRetentionPeriod :== Watch.defaultAntiEntropyRetentionPeriod, watchLogLevel :== Level.Info, - watchOnEnter :== Watch.defaultOnEnter, + watchBeforeCommand :== Watch.defaultBeforeCommand, watchOnFileInputEvent :== Watch.trigger, watchDeletionQuarantinePeriod :== Watch.defaultDeletionQuarantinePeriod, sbt.Keys.watchService :== Watched.newWatchService, diff --git a/sbt/src/sbt-test/watch/alias/build.sbt b/sbt/src/sbt-test/watch/alias/build.sbt index 0fab94fd9..f1d0d6c0a 100644 --- a/sbt/src/sbt-test/watch/alias/build.sbt +++ b/sbt/src/sbt-test/watch/alias/build.sbt @@ -1,6 +1,6 @@ val foo = taskKey[Unit]("foo") foo := println("foo") -foo / watchOnIteration := { _ => sbt.nio.Watch.CancelWatch } +foo / watchOnIteration := { (_, _, _) => sbt.nio.Watch.CancelWatch } addCommandAlias("bar", "foo") addCommandAlias("baz", "foo") diff --git a/sbt/src/sbt-test/watch/file-input-aggregation/project/Build.scala b/sbt/src/sbt-test/watch/file-input-aggregation/project/Build.scala index 6fe14128f..32b999320 100644 --- a/sbt/src/sbt-test/watch/file-input-aggregation/project/Build.scala +++ b/sbt/src/sbt-test/watch/file-input-aggregation/project/Build.scala @@ -40,7 +40,7 @@ object Build { watchOnFileInputEvent := { (_, _) => Watch.CancelWatch }, - Compile / compile / watchOnIteration := { _ => + Compile / compile / watchOnIteration := { (_, _, _) => Watch.CancelWatch }, checkTriggers := { diff --git a/sbt/src/sbt-test/watch/on-change/build.sbt b/sbt/src/sbt-test/watch/on-change/build.sbt index 3864a2ef0..260e5d1c2 100644 --- a/sbt/src/sbt-test/watch/on-change/build.sbt +++ b/sbt/src/sbt-test/watch/on-change/build.sbt @@ -10,7 +10,7 @@ watchTriggeredMessage := { (i, path: Path, c) => prev(i, path, c) } -watchOnIteration := { i: Int => +watchOnIteration := { (i: Int, _, _) => val base = baseDirectory.value.toPath val src = base.resolve("src").resolve("main").resolve("scala").resolve("sbt").resolve("test") diff --git a/sbt/src/sbt-test/watch/on-start-watch/changes/extra.sbt b/sbt/src/sbt-test/watch/on-start-watch/changes/extra.sbt index 4c4c95a65..85c2f3c85 100644 --- a/sbt/src/sbt-test/watch/on-start-watch/changes/extra.sbt +++ b/sbt/src/sbt-test/watch/on-start-watch/changes/extra.sbt @@ -1,7 +1,7 @@ val checkReloaded = taskKey[Unit]("Asserts that the build was reloaded") checkReloaded := { () } -watchOnIteration := { _ => sbt.nio.Watch.CancelWatch } +watchOnIteration := { (_, _, _) => sbt.nio.Watch.CancelWatch } Compile / compile := { Count.increment() diff --git a/sbt/src/sbt-test/watch/on-start-watch/test b/sbt/src/sbt-test/watch/on-start-watch/test index 905322c07..f796f7a13 100644 --- a/sbt/src/sbt-test/watch/on-start-watch/test +++ b/sbt/src/sbt-test/watch/on-start-watch/test @@ -3,19 +3,19 @@ # verify that the watch terminates when we reach the specified count > resetCount -> set watchOnIteration := { (count: Int) => if (count == 2) sbt.nio.Watch.CancelWatch else sbt.nio.Watch.Ignore } +> set watchOnIteration := { (count: Int, _, _) => if (count == 2) sbt.nio.Watch.CancelWatch else sbt.nio.Watch.Ignore } > ~compile > checkCount 2 # verify that the watch terminates and returns an error when we reach the specified count > resetCount -> set watchOnIteration := { (count: Int) => if (count == 2) new sbt.nio.Watch.HandleError(new Exception("")) else sbt.nio.Watch.Ignore } +> set watchOnIteration := { (count: Int, _, _) => if (count == 2) new sbt.nio.Watch.HandleError(new Exception("")) else sbt.nio.Watch.Ignore } # Returning Watch.HandleError causes the '~' command to fail -> ~compile > checkCount 2 # verify that a re-build is triggered when we reach the specified count > resetCount -> set watchOnIteration := { (count: Int) => if (count == 2) sbt.nio.Watch.Trigger else if (count == 3) sbt.nio.Watch.CancelWatch else sbt.nio.Watch.Ignore } +> set watchOnIteration := { (count: Int, _, _) => if (count == 2) sbt.nio.Watch.Trigger else if (count == 3) sbt.nio.Watch.CancelWatch else sbt.nio.Watch.Ignore } > ~compile > checkCount 3 diff --git a/sbt/src/sbt-test/watch/task/changes/Build.scala b/sbt/src/sbt-test/watch/task/changes/Build.scala index 5478afedf..28a790802 100644 --- a/sbt/src/sbt-test/watch/task/changes/Build.scala +++ b/sbt/src/sbt-test/watch/task/changes/Build.scala @@ -24,6 +24,6 @@ object Build { IO.touch(baseDirectory.value / "foo.txt", true) Some("watching") }, - watchOnIteration := { _ => Watch.CancelWatch } + watchOnIteration := { (_, _, _) => Watch.CancelWatch } ) }