mirror of https://github.com/sbt/sbt.git
implement ~
This commit is contained in:
parent
018ef2b3c7
commit
67682f32d3
|
|
@ -26,6 +26,8 @@ object CommandSupport
|
|||
def processLine(s: String) = { val trimmed = s.trim; if(ignoreLine(trimmed)) None else Some(trimmed) }
|
||||
def ignoreLine(s: String) = s.isEmpty || s.startsWith("#")
|
||||
|
||||
/** The prefix used to identify a request to execute the remaining input on source changes.*/
|
||||
val ContinuousExecutePrefix = "~"
|
||||
val HelpCommand = "help"
|
||||
val ProjectCommand = "project"
|
||||
val ProjectsCommand = "projects"
|
||||
|
|
@ -36,6 +38,9 @@ object CommandSupport
|
|||
/** The list of command names that may be used to terminate the program.*/
|
||||
val TerminateActions: Seq[String] = Seq(Exit, Quit)
|
||||
|
||||
|
||||
def continuousBriefHelp = (ContinuousExecutePrefix + " <action>", "Executes the specified command whenever source files change.")
|
||||
|
||||
def helpBrief = (HelpCommand + " command*", "Displays this help message or prints detailed help on requested commands.")
|
||||
def helpDetailed = "If an argument is provided, this prints detailed help for that command.\nOtherwise, this prints a help summary."
|
||||
|
||||
|
|
|
|||
|
|
@ -144,6 +144,12 @@ object Commands
|
|||
}
|
||||
}
|
||||
|
||||
def continuous = Command { case s @ State(p: Project with Watched) =>
|
||||
Apply( Help(continuousBriefHelp) ) {
|
||||
case in if in.line startsWith ContinuousExecutePrefix => Watched.executeContinuously(p, s, in)
|
||||
}
|
||||
}
|
||||
|
||||
def history = Command { case s @ State(p: HistoryEnabled) =>
|
||||
Apply( historyHelp: _* ) {
|
||||
case in if in.line startsWith("!") =>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009, 2010 Mikko Peltonen, Stuart Roebuck, Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
|
||||
import CommandSupport.FailureWall
|
||||
|
||||
trait Watched
|
||||
{
|
||||
/** A `PathFinder` that determines the files watched when an action is run with a preceeding ~ when this is the current
|
||||
* project. This project does not need to include the watched paths for projects that this project depends on.*/
|
||||
def watchPaths: PathFinder = Path.emptyPathFinder
|
||||
def terminateWatch(key: Int): Boolean = Watched.isEnter(key)
|
||||
}
|
||||
|
||||
object Watched
|
||||
{
|
||||
val ContinuousCompilePollDelaySeconds = 1
|
||||
def isEnter(key: Int): Boolean = key == 10 || key == 13
|
||||
|
||||
def watched(p: Project, s: State): Seq[Watched] = MultiProject.topologicalSort(p, s).collect { case w: Watched => w }
|
||||
def sourcePaths(p: Project, s: State): PathFinder = (Path.emptyPathFinder /: watched(p, s))(_ +++ _.watchPaths)
|
||||
def executeContinuously(project: Project with Watched, s: State, in: Input): State =
|
||||
{
|
||||
def shouldTerminate: Boolean = (System.in.available > 0) && (project.terminateWatch(System.in.read()) || shouldTerminate)
|
||||
val sourcesFinder = sourcePaths(project, s)
|
||||
val watchState = s get ContinuousState getOrElse WatchState.empty
|
||||
|
||||
if(watchState.count > 0)
|
||||
System.out.println(watchState.count + ". Waiting for source changes... (press enter to interrupt)")
|
||||
|
||||
val (triggered, newWatchState) = SourceModificationWatch.watch(sourcesFinder, ContinuousCompilePollDelaySeconds, watchState)(shouldTerminate)
|
||||
|
||||
if(triggered)
|
||||
(in.arguments :: FailureWall :: in.line :: s).put(ContinuousState, newWatchState)
|
||||
else
|
||||
{
|
||||
while (System.in.available() > 0) System.in.read()
|
||||
s.put(ContinuousState, WatchState.empty)
|
||||
}
|
||||
}
|
||||
val ContinuousState = AttributeKey[WatchState]("watch state")
|
||||
}
|
||||
|
|
@ -3,35 +3,45 @@
|
|||
*/
|
||||
package sbt
|
||||
|
||||
import annotation.tailrec
|
||||
|
||||
object SourceModificationWatch
|
||||
{
|
||||
def watchUntil(sourcesFinder: PathFinder, pollDelaySec: Int)(terminationCondition: => Boolean)(onSourcesModified: => Unit)
|
||||
@tailrec def watch(sourcesFinder: PathFinder, pollDelaySec: Int, state: WatchState)(terminationCondition: => Boolean): (Boolean, WatchState) =
|
||||
{
|
||||
import state._
|
||||
|
||||
def sourceFiles: Iterable[java.io.File] = sourcesFinder.getFiles
|
||||
def loop(lastCallbackCallTime: Long, previousFileCount: Int, awaitingQuietPeriod:Boolean)
|
||||
val (lastModifiedTime, fileCount) =
|
||||
( (0L, 0) /: sourceFiles) {(acc, file) => (math.max(acc._1, file.lastModified), acc._2 + 1)}
|
||||
|
||||
val sourcesModified =
|
||||
lastModifiedTime > lastCallbackCallTime ||
|
||||
previousFileCount != fileCount
|
||||
|
||||
val (triggered, newCallbackCallTime) =
|
||||
if (sourcesModified && !awaitingQuietPeriod)
|
||||
(false, System.currentTimeMillis)
|
||||
else if (!sourcesModified && awaitingQuietPeriod)
|
||||
(true, lastCallbackCallTime)
|
||||
else
|
||||
(false, lastCallbackCallTime)
|
||||
|
||||
val newState = new WatchState(newCallbackCallTime, fileCount, sourcesModified, if(triggered) count + 1 else count)
|
||||
if(triggered)
|
||||
(true, newState)
|
||||
else
|
||||
{
|
||||
val (lastModifiedTime, fileCount) =
|
||||
( (0L, 0) /: sourceFiles) {(acc, file) => (math.max(acc._1, file.lastModified), acc._2 + 1)}
|
||||
|
||||
val sourcesModified =
|
||||
lastModifiedTime > lastCallbackCallTime ||
|
||||
previousFileCount != fileCount
|
||||
|
||||
val newCallbackCallTime =
|
||||
if (sourcesModified && !awaitingQuietPeriod)
|
||||
System.currentTimeMillis
|
||||
else if (!sourcesModified && awaitingQuietPeriod)
|
||||
{
|
||||
onSourcesModified
|
||||
lastCallbackCallTime
|
||||
}
|
||||
else
|
||||
lastCallbackCallTime
|
||||
|
||||
Thread.sleep(pollDelaySec * 1000)
|
||||
if(!terminationCondition)
|
||||
loop(newCallbackCallTime, fileCount, sourcesModified)
|
||||
if(terminationCondition)
|
||||
(false, newState)
|
||||
else
|
||||
watch(sourcesFinder, pollDelaySec, newState)(terminationCondition)
|
||||
}
|
||||
loop(0L, 0, false)
|
||||
}
|
||||
}
|
||||
final class WatchState(val lastCallbackCallTime: Long, val previousFileCount: Int, val awaitingQuietPeriod:Boolean, val count: Int)
|
||||
object WatchState
|
||||
{
|
||||
def empty = new WatchState(0L, 0, false, 0)
|
||||
}
|
||||
Loading…
Reference in New Issue