diff --git a/main/Act.scala b/main/Act.scala index 9e13939b3..f3d21e891 100644 --- a/main/Act.scala +++ b/main/Act.scala @@ -86,8 +86,8 @@ object Act processResult(result, logger(s), show) s } - def actParser(s: State): Parser[() => State] = - if(s get Project.SessionKey isEmpty) failure("No project loaded") else actParser0(s) + def actParser(s: State): Parser[() => State] = requireSession(s, actParser0(s)) + private[this] def actParser0(state: State) = { val extracted = Project extract state @@ -103,4 +103,8 @@ object Act val defaultConf = (ref: ProjectRef) => if(Project.getProject(ref, structure).isDefined) defaultConfig(structure.data)(ref) else None scopedKey(structure.index.keyIndex, curi, cid, defaultConf, structure.index.keyMap) } + + + def requireSession[T](s: State, p: => Parser[T]): Parser[T] = + if(s get Project.SessionKey isEmpty) failure("No project loaded") else p } \ No newline at end of file diff --git a/main/CommandSupport.scala b/main/CommandSupport.scala index bf54f052e..eceff58a5 100644 --- a/main/CommandSupport.scala +++ b/main/CommandSupport.scala @@ -151,10 +151,10 @@ ProjectCommand + def DefaultsBrief = (DefaultsCommand, DefaultsDetailed) def DefaultsDetailed = "Registers default built-in commands" - def ReloadCommand = "reload" - def ReloadBrief = "Reloads the session and then executes the remaining commands." - def ReloadDetailed = -ReloadCommand + """ + def RebootCommand = "reboot" + def RebootBrief = "Reboots sbt and then executes the remaining commands." + def RebootDetailed = +RebootCommand + """ This command is equivalent to exiting, restarting, and running the remaining commands with the exception that the jvm is not shut down. """ @@ -232,14 +232,17 @@ CompileSyntax + """ Cached information about the compilation will be written to 'cache'. """ - val FailureWall = "--" + val FailureWall = "---" def Load = "load" def LoadLabel = "a project" def LoadCommand = "load-commands" def LoadCommandLabel = "commands" - def LoadProject = "loadp" + def LoadFailed = "load-failed" + + def LoadProjectImpl = "loadp" + def LoadProject = "reload" def LoadProjectBrief = LoadProjectDetailed def LoadProjectDetailed = "Loads the project in the current directory" @@ -247,6 +250,7 @@ CompileSyntax + """ def ShellBrief = ShellDetailed def ShellDetailed = "Provides an interactive prompt from which commands can be run." + def ClearOnFailure = "--" def OnFailure = "-" def OnFailureBrief = (OnFailure + " command", "Registers 'command' to run if a command fails.") def OnFailureDetailed = diff --git a/main/Main.scala b/main/Main.scala index bea60a5c8..ca2a8766f 100644 --- a/main/Main.scala +++ b/main/Main.scala @@ -24,10 +24,10 @@ class xMain extends xsbti.AppMain { final def run(configuration: xsbti.AppConfiguration): xsbti.MainResult = { - import BuiltinCommands.{initialize, defaults} + import BuiltinCommands.{initialize, defaults, DefaultBootCommands} import CommandSupport.{DefaultsCommand, InitCommand} val initialCommandDefs = Seq(initialize, defaults) - val commands = DefaultsCommand :: InitCommand :: configuration.arguments.map(_.trim).toList + val commands = DefaultsCommand +: InitCommand +: (DefaultBootCommands ++ configuration.arguments.map(_.trim)) val state = State( configuration, initialCommandDefs, Set.empty, None, commands, initialAttributes, Next.Continue ) run(state) } @@ -58,8 +58,9 @@ class xMain extends xsbti.AppMain import CommandSupport._ object BuiltinCommands { - def DefaultCommands: Seq[Command] = Seq(ignore, help, reload, read, history, continuous, exit, loadCommands, loadProject, compile, discover, - projects, project, setOnFailure, ifLast, multi, shell, set, inspect, eval, alias, append, last, lastGrep, nop, sessionCommand, act) + def DefaultCommands: Seq[Command] = Seq(ignore, help, reboot, read, history, continuous, exit, loadCommands, loadProject, loadProjectImpl, loadFailed, compile, discover, + projects, project, setOnFailure, clearOnFailure, ifLast, multi, shell, set, inspect, eval, alias, append, last, lastGrep, nop, sessionCommand, act) + def DefaultBootCommands: Seq[String] = LoadProject :: (IfLast + " " + Shell) :: Nil def nop = Command.custom(s => success(() => s)) def ignore = Command.command(FailureWall)(identity) @@ -135,8 +136,9 @@ object BuiltinCommands def setOnFailure = Command.single(OnFailure, OnFailureBrief, OnFailureDetailed) { (s, arg) => s.copy(onFailure = Some(arg)) } + def clearOnFailure = Command.command(ClearOnFailure)(s => s.copy(onFailure = None)) - def reload = Command.command(ReloadCommand, ReloadBrief, ReloadDetailed) { s => + def reboot = Command.command(RebootCommand, RebootBrief, RebootDetailed) { s => s.runExitHooks().reload } @@ -244,8 +246,8 @@ object BuiltinCommands Output.lastGrep(sk.scope, sk.key, Project.structure(s).streams, pattern) s } - val spacedKeyParser = (s: State) => token(Space) ~> Act.scopedKeyParser(s) - def lastGrepParser(s: State) = (token(Space) ~> token(NotSpace, "")) ~ spacedKeyParser(s) + val spacedKeyParser = (s: State) => Act.requireSession(s, token(Space) ~> Act.scopedKeyParser(s)) + def lastGrepParser(s: State) = Act.requireSession(s, (token(Space) ~> token(NotSpace, "")) ~ spacedKeyParser(s)) def last = Command(LastCommand, lastBrief, lastDetailed)(spacedKeyParser) { (s,sk) => Output.last(sk.scope, sk.key, Project.structure(s).streams) s @@ -305,7 +307,29 @@ object BuiltinCommands } catch { case e: xsbti.CompileFailed => s.fail /* already logged */ } } - def loadProject = Command.command(LoadProject, LoadProjectBrief, LoadProjectDetailed) { s => + def loadFailed = Command.command(LoadFailed)(handleLoadFailed) + @tailrec def handleLoadFailed(s: State): State = + { + val result = (SimpleReader.readLine("Project loading failed: (r)etry, (q)uit, or (i)gnore? ") getOrElse Quit).toLowerCase + def matches(s: String) = !result.isEmpty && (s startsWith result) + + if(matches("retry")) + LoadProject :: s + else if(matches(Quit)) + s.exit(ok = false) + else if(matches("ignore")) + s + else + { + println("Invalid response.") + handleLoadFailed(s) + } + } + + def loadProjectCommands = (OnFailure + " " + LoadFailed) :: LoadProjectImpl :: ClearOnFailure :: FailureWall :: Nil + def loadProject = Command.command(LoadProject, LoadProjectBrief, LoadProjectDetailed) { loadProjectCommands ::: _ } + + def loadProjectImpl = Command.command(LoadProjectImpl) { s => val (eval, structure) = Load.defaultLoad(s, logger(s)) val session = Load.initialSession(structure, eval) Project.setProject(session, structure, s) diff --git a/main/Project.scala b/main/Project.scala index ce3ac2f2d..1d483e70c 100644 --- a/main/Project.scala +++ b/main/Project.scala @@ -228,9 +228,11 @@ object SessionSettings { val project = Project.getProject(pref, structure).getOrElse(error("Invalid project reference " + pref)) val appendTo: File = BuildPaths.configurationSources(project.base).headOption.getOrElse(new File(project.base, "build.sbt")) - val sbtAppend = settingStrings(settings).flatMap("" :: _ :: Nil) - IO.writeLines(appendTo, sbtAppend, append = true) + val baseAppend = settingStrings(settings).flatMap("" :: _ :: Nil) + val adjustedLines = if( hasTrailingBlank(IO.readLines(appendTo)) ) baseAppend else baseAppend + IO.writeLines(appendTo, adjustedLines, append = true) } + def hasTrailingBlank(lines: Seq[String]) = lines.takeRight(1).exists(_.trim.isEmpty) def printAllSettings(s: State): State = withSettings(s){ session => for( ((uri,id), settings) <- session.append if !settings.isEmpty) { diff --git a/main/State.scala b/main/State.scala index c2c16fed3..b8bbaf307 100644 --- a/main/State.scala +++ b/main/State.scala @@ -65,16 +65,17 @@ object State { val remaining = s.commands.dropWhile(_ != FailureWall) if(remaining.isEmpty) - { - s.onFailure match - { - case Some(c) => s.copy(commands = c :: Nil, onFailure = None) - case None => exit(ok = false) - } - } + applyOnFailure(s, Nil, exit(ok = false)) else - s.copy(commands = remaining) + applyOnFailure(s, remaining, s.copy(commands = remaining)) } + private[this] def applyOnFailure(s: State, remaining: Seq[String], noHandler: => State): State = + s.onFailure match + { + case Some(c) => s.copy(commands = c +: remaining, onFailure = None) + case None => noHandler + } + def runExitHooks(): State = { ExitHooks.runExitHooks(s.exitHooks.toSeq) s.copy(exitHooks = Set.empty) diff --git a/main/Watched.scala b/main/Watched.scala index 803531315..7a07dbce4 100644 --- a/main/Watched.scala +++ b/main/Watched.scala @@ -3,7 +3,7 @@ */ package sbt - import CommandSupport.FailureWall + import CommandSupport.{ClearOnFailure,FailureWall} import annotation.tailrec trait Watched @@ -41,7 +41,7 @@ object Watched val (triggered, newWatchState) = SourceModificationWatch.watch(sourcesFinder, PollDelaySeconds, watchState)(shouldTerminate) if(triggered) - (next :: FailureWall :: repeat :: s).put(ContinuousState, newWatchState) + (ClearOnFailure :: next :: FailureWall :: repeat :: s).put(ContinuousState, newWatchState) else { while (System.in.available() > 0) System.in.read()