diff --git a/main/Command.scala b/main/Command.scala index 2b58cc6e5..8fbefa4ba 100644 --- a/main/Command.scala +++ b/main/Command.scala @@ -79,9 +79,18 @@ trait Named { def name: String } + +trait Navigation[Project] +{ + def parentProject: Option[Project] + def self: Project + def initialProject(s: State): Project + def projectClosure(s: State): Seq[Project] + def rootProject: Project +} trait Member[Node <: Member[Node]] -{ self: Node => - def projectClosure(state: State): Seq[Node] +{ + def navigation: Navigation[Node] } trait Tasked { diff --git a/main/CommandSupport.scala b/main/CommandSupport.scala index 8fdc88082..138783e11 100644 --- a/main/CommandSupport.scala +++ b/main/CommandSupport.scala @@ -53,6 +53,14 @@ ProjectCommand + """ + ProjectCommand + """ name Changes to the project with the provided name. This command fails if there is no project with the given name. +""" + ProjectCommand + """ / + Changes to the initial project. +""" + ProjectCommand + """ .. + Changes to the parent project of the current project. + If there is no parent project, the current project is unchanged. + + Use n+1 dots to change to the nth parent. + For example, 'project ....' is equivalent to three consecutive 'project ..' commands. """ def projectsBrief = (ProjectsCommand, projectsDetailed) diff --git a/main/Main.scala b/main/Main.scala index f549bbab2..ecabf2c12 100644 --- a/main/Main.scala +++ b/main/Main.scala @@ -199,7 +199,7 @@ object Commands def projects = Command { case s @ State(d: Member[_]) => Apply.simple(ProjectsCommand, projectsBrief, projectsDetailed ) { (in,s) => val log = logger(s) - d.projectClosure(s).foreach { case n: Named => listProject(n, d eq n, log); case _ => () } + d.navigation.projectClosure(s).foreach { case n: Named => listProject(n, d eq n, log); case _ => () } s }(s) } @@ -212,17 +212,34 @@ object Commands logger(s).info(d.name) s } + else if(to == "/") + setProject(d.navigation.initialProject(s), s) + else if(to.forall(_ == '.')) + if(to.length > 1) gotoParent(to.length - 1, d, s) else s else { - d.projectClosure(s).find { case n: Named => n.name == to; case _ => false } match + d.navigation.projectClosure(s).find { case n: Named => n.name == to; case _ => false } match { - case Some(np) => logger(s).info("Set current project to " + to); s.copy(np)() + case Some(np) => setProject(np, s) case None => logger(s).error("Invalid project name '" + to + "' (type 'projects' to list available projects)."); s.fail } } }(s) } - + @tailrec def gotoParent[Node <: Member[Node]](n: Int, base: Member[Node], s: State): State = + base.navigation.parentProject match + { + case Some(pp) => if(n <= 1) setProject(pp, s) else gotoParent(n-1, pp, s) + case None => if(s.project == base) s else setProject(base, s) + } + + def setProject(np: AnyRef, s: State): State = + { + np match { case n: Named => + logger(s).info("Set current project to " + n.name) + } + s.copy(np)() + } def exit = Command { case s => Apply( Help(exitBrief) ) { case in if TerminateActions contains in.line => runExitHooks(s).exit(true) diff --git a/main/MultiProject.scala b/main/MultiProject.scala index 73e2f4220..28ed2e076 100644 --- a/main/MultiProject.scala +++ b/main/MultiProject.scala @@ -152,7 +152,12 @@ object MultiContext val static = (o: Owner, s: String) => context(o).static(o, s) } } - +final class MultiNavigation(val self: Project, val parentProject: Option[Project]) extends Navigation[Project] +{ + def initialProject(s: State): Project = MultiProject.initialProject(s, rootProject) + def projectClosure(s: State): Seq[Project] = MultiProject.topologicalSort(initialProject(s)) + @tailrec final lazy val rootProject: Project = parentProject match { case Some(p) => p.navigation.rootProject; case None => self } +} trait Project extends Tasked with HistoryEnabled with Member[Project] with Named with ConsoleTask with Watched { val info: ProjectInfo @@ -165,8 +170,7 @@ trait Project extends Tasked with HistoryEnabled with Member[Project] with Named def historyPath = Some(outputRootPath / ".history") def streamBase = outputRootPath / "streams" - def projectClosure(s: State): Seq[Project] = MultiProject.topologicalSort(MultiProject.initialProject(s, rootProject)) - @tailrec final def rootProject: Project = info.parent match { case Some(p) => p.rootProject; case None => this } + def navigation: Navigation[Project] = new MultiNavigation(this, info.parent) implicit def streams = Dummy.Streams def input = Dummy.In