summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2010-02-01 22:35:12 +0000
committerPaul Phillips <paulp@improving.org>2010-02-01 22:35:12 +0000
commitb80125cb3fa7ab12b5921ee930c04e9d95384861 (patch)
tree2e3c09c8c62a7e13facef213d01e0d72009e432b
parent3282ac260cebe12a2d0dcb6bb7c0e479e0e20c6c (diff)
downloadscala-b80125cb3fa7ab12b5921ee930c04e9d95384861.tar.gz
scala-b80125cb3fa7ab12b5921ee930c04e9d95384861.tar.bz2
scala-b80125cb3fa7ab12b5921ee930c04e9d95384861.zip
Quite a lot more work on completion.
completion is now avilable, with some caveats. Review by community.
-rw-r--r--src/compiler/scala/tools/nsc/Interpreter.scala15
-rw-r--r--src/compiler/scala/tools/nsc/InterpreterLoop.scala9
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/Completion.scala108
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala44
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/Delimited.scala36
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/FileCompletion.scala54
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala1
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/Parsed.scala71
-rw-r--r--src/compiler/scala/tools/nsc/io/Path.scala25
9 files changed, 270 insertions, 93 deletions
diff --git a/src/compiler/scala/tools/nsc/Interpreter.scala b/src/compiler/scala/tools/nsc/Interpreter.scala
index c98b54304b..406761eddb 100644
--- a/src/compiler/scala/tools/nsc/Interpreter.scala
+++ b/src/compiler/scala/tools/nsc/Interpreter.scala
@@ -208,7 +208,18 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
private val usedNameMap = new HashMap[Name, Request]()
private val boundNameMap = new HashMap[Name, Request]()
private def allHandlers = prevRequests.toList flatMap (_.handlers)
- private def mostRecentHandler = prevRequests.last.handlers.last
+
+ /** Most recent handler which wasn't wholly synthetic. */
+ private def mostRecentHandler: MemberHandler = {
+ for {
+ req <- prevRequests.reverse
+ handler <- req.handlers.reverse
+ name <- handler.generatesValue
+ if !isSynthVarName(name)
+ } return handler
+
+ error("No handlers found.")
+ }
def recordRequest(req: Request) {
def tripart[T](set1: Set[T], set2: Set[T]) = {
@@ -1055,7 +1066,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
}
def clazzForIdent(id: String): Option[Class[_]] =
- extractionValueForIdent(id) map (_.getClass)
+ extractionValueForIdent(id) flatMap (x => Option(x) map (_.getClass))
private def methodsCode(name: String) =
"%s.%s(%s)".format(classOf[ReflectionCompletion].getName, "methodsOf", name)
diff --git a/src/compiler/scala/tools/nsc/InterpreterLoop.scala b/src/compiler/scala/tools/nsc/InterpreterLoop.scala
index ab643def53..afc3247170 100644
--- a/src/compiler/scala/tools/nsc/InterpreterLoop.scala
+++ b/src/compiler/scala/tools/nsc/InterpreterLoop.scala
@@ -513,18 +513,21 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) {
/** Here we place ourselves between the user and the interpreter and examine
* the input they are ostensibly submitting. We intervene in several cases:
*
- * 1) If the line starts with "." it is treated as an invocation on the last result.
- * 2) If the line starts with "scala> " it is assumed to be an interpreter paste.
+ * 1) If the line starts with "scala> " it is assumed to be an interpreter paste.
+ * 2) If the line starts with "." (but not ".." or "./") it is treated as an invocation
+ * on the previous result.
* 3) If the Completion object's execute returns Some(_), we inject that value
* and avoid the interpreter, as it's likely not valid scala code.
*/
if (code == "") None
- else if (code startsWith ".") interpretStartingWith(interpreter.mostRecentVar + code)
else if (code startsWith PROMPT_STRING) {
updatePasteStamp()
interpretAsPastedTranscript(List(code))
None
}
+ else if (Completion.looksLikeInvocation(code)) {
+ interpretStartingWith(interpreter.mostRecentVar + code)
+ }
else {
val result = for (comp <- in.completion ; res <- comp execute code) yield res
result match {
diff --git a/src/compiler/scala/tools/nsc/interpreter/Completion.scala b/src/compiler/scala/tools/nsc/interpreter/Completion.scala
index f9e6b33763..6ca108d773 100644
--- a/src/compiler/scala/tools/nsc/interpreter/Completion.scala
+++ b/src/compiler/scala/tools/nsc/interpreter/Completion.scala
@@ -22,13 +22,24 @@ import jline._
import java.net.URL
import java.util.{ List => JList }
import java.lang.reflect
+import io.{ Path, Directory }
+
+object Completion {
+ def looksLikeInvocation(code: String) = (
+ (code != null)
+ && (code startsWith ".")
+ && !(code startsWith "./")
+ && !(code startsWith "..")
+ )
-trait ForwardingCompletion extends CompletionAware {
- def forwardTo: Option[CompletionAware]
+ trait Forwarder extends CompletionAware {
+ def forwardTo: Option[CompletionAware]
- override def completions() = forwardTo map (_.completions()) getOrElse Nil
- override def follow(s: String) = forwardTo flatMap (_ follow s)
+ override def completions() = forwardTo map (_.completions()) getOrElse Nil
+ override def follow(s: String) = forwardTo flatMap (_ follow s)
+ }
}
+import Completion._
// REPL completor - queries supplied interpreter for valid
// completions based on current contents of buffer.
@@ -59,7 +70,7 @@ class Completion(repl: Interpreter) {
)
}
// members of scala.*
- val scalalang = new pkgs.SubCompletor("scala") with ForwardingCompletion {
+ val scalalang = new pkgs.SubCompletor("scala") with Forwarder {
def forwardTo = pkgs follow "scala"
val arityClasses = {
val names = List("Tuple", "Product", "Function")
@@ -69,15 +80,15 @@ class Completion(repl: Interpreter) {
}
override def filterNotFunction(s: String) = {
- val parsed = new Parsed(s)
+ val simple = s.reverse takeWhile (_ != '.') reverse
- (arityClasses contains parsed.unqualifiedPart) ||
+ (arityClasses contains simple) ||
(s endsWith "Exception") ||
(s endsWith "Error")
}
}
// members of java.lang.*
- val javalang = new pkgs.SubCompletor("java.lang") with ForwardingCompletion {
+ val javalang = new pkgs.SubCompletor("java.lang") with Forwarder {
def forwardTo = pkgs follow "java.lang"
import reflect.Modifier.isPublic
private def existsAndPublic(s: String): Boolean = {
@@ -99,19 +110,34 @@ class Completion(repl: Interpreter) {
val parent = self
}
+ def lastResult = new Forwarder {
+ def forwardTo = ids follow repl.mostRecentVar
+ }
+
+ def lastResultFor(parsed: Parsed) = {
+ /** The logic is a little tortured right now because normally '.' is
+ * ignored as a delimiter, but on .<tab> it needs to be propagated.
+ */
+ val xs = lastResult completionsFor parsed
+ if (parsed.isEmpty) xs map ("." + _) else xs
+ }
+
// the list of completion aware objects which should be consulted
val topLevel: List[CompletionAware] = List(ids, pkgs, predef, scalalang, javalang, literals)
- def topLevelFor(buffer: String) = topLevel flatMap (_ completionsFor buffer)
+
+ // the first tier of top level objects (doesn't include file completion)
+ def topLevelFor(parsed: Parsed) = topLevel flatMap (_ completionsFor parsed)
// chasing down results which won't parse
def execute(line: String): Option[Any] = {
- val parsed = new Parsed(line)
- import parsed._
+ val parsed = Parsed(line)
+ def noDotOrSlash = line forall (ch => ch != '.' && ch != '/')
- if (!isQualified) None
+ if (noDotOrSlash) None // we defer all unqualified ids to the repl.
else {
- (ids executionFor buffer) orElse
- (pkgs executionFor buffer)
+ (ids executionFor parsed) orElse
+ (pkgs executionFor parsed) orElse
+ (FileCompletion executionFor line)
}
}
@@ -119,21 +145,8 @@ class Completion(repl: Interpreter) {
def lastCommand: Option[String] = None
// jline's entry point
- lazy val jline: ArgumentCompletor = {
- // TODO - refine the delimiters
- //
- // public static interface ArgumentDelimiter {
- // ArgumentList delimit(String buffer, int argumentPosition);
- // boolean isDelimiter(String buffer, int pos);
- // }
- val delimiters = new ArgumentCompletor.AbstractArgumentDelimiter {
- // val delimChars = "(){},`; \t".toArray
- val delimChars = "{},`; \t".toArray
- def isDelimiterChar(s: String, pos: Int) = delimChars contains s.charAt(pos)
- }
-
- returning(new ArgumentCompletor(new JLineCompletion, delimiters))(_ setStrict false)
- }
+ lazy val jline: ArgumentCompletor =
+ returning(new ArgumentCompletor(new JLineCompletion, new JLineDelimiter))(_ setStrict false)
class JLineCompletion extends Completor {
// For recording the buffer on the last tab hit
@@ -147,21 +160,34 @@ class Completion(repl: Interpreter) {
private var verbosity = 0
// This is jline's entry point for completion.
- override def complete(_buffer: String, cursor: Int, candidates: JList[String]): Int = {
+ override def complete(buf: String, cursor: Int, candidates: JList[String]): Int = {
+ // println("complete: buf = %s, cursor = %d".format(buf, cursor))
if (!isInitialized)
return cursor
- // println("_buffer = %s, cursor = %d".format(_buffer, cursor))
- verbosity = if (isConsecutiveTabs(_buffer)) verbosity + 1 else 0
- lastTab = (_buffer, lastCommand orNull)
-
- // parse the command buffer
- val parsed = new Parsed(_buffer)
- import parsed._
-
- // modify in place and return the position
- topLevelFor(buffer) foreach (candidates add _)
- position
+ verbosity = if (isConsecutiveTabs(buf)) verbosity + 1 else 0
+ lastTab = (buf, lastCommand orNull)
+
+ // we don't try lower priority completions unless higher ones return no results.
+ def tryCompletion(p: Parsed, completionFunction: Parsed => List[String]): Option[Int] = {
+ completionFunction(p) match {
+ case Nil => None
+ case xs =>
+ // modify in place and return the position
+ xs foreach (candidates add _)
+ Some(p.position)
+ }
+ }
+
+ // a single dot is special cased to completion on the previous result
+ def lastResultCompletion =
+ if (!looksLikeInvocation(buf)) None
+ else tryCompletion(Parsed.dotted(buf drop 1, cursor), lastResultFor)
+
+ def regularCompletion = tryCompletion(Parsed.dotted(buf, cursor), topLevelFor)
+ def fileCompletion = tryCompletion(Parsed.undelimited(buf, cursor), FileCompletion completionsFor _.buffer)
+
+ (lastResultCompletion orElse regularCompletion orElse fileCompletion) getOrElse cursor
}
}
}
diff --git a/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala b/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala
index 91b7a77ce5..bdb390c9fc 100644
--- a/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala
+++ b/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala
@@ -12,6 +12,12 @@ import scala.reflect.NameTransformer
* will supply their own candidates and resolve their own paths.
*/
trait CompletionAware {
+ /** The delimiters which are meaningful when this CompletionAware
+ * object is in control.
+ */
+ // TODO
+ // def delimiters(): List[Char] = List('.')
+
/** The complete list of unqualified Strings to which this
* object will complete.
*/
@@ -45,39 +51,43 @@ trait CompletionAware {
* to which it can complete. This may involve delegating
* to other CompletionAware objects.
*/
- def completionsFor(buf: String): List[String] = {
- val parsed = new Parsed(buf)
+ def completionsFor(parsed: Parsed): List[String] = {
import parsed._
- (
+ val cs =
if (isEmpty) completions()
- else if (isFirstCharDot) Nil // XXX for now
- else if (isUnqualified && !isLastCharDot) completions(buf)
- else follow(hd) match {
- case Some(next) => next completionsFor remainder
- case _ => Nil
- }
- ) filterNot filterNotFunction map mapFunction sortWith (sortFunction _)
+ else if (isUnqualified && !isLastDelimiter) completions(buffer)
+ else follow(bufferHead) map (_ completionsFor bufferTail) getOrElse Nil
+
+ cs filterNot filterNotFunction map mapFunction sortWith (sortFunction _)
}
/** TODO - unify this and completionsFor under a common traverser.
*/
- def executionFor(buf: String): Option[Any] = {
- val parsed = new Parsed(buf)
+ def executionFor(parsed: Parsed): Option[Any] = {
import parsed._
- if (isUnqualified && !isLastCharDot && (completions contains buf)) execute(buf)
+ if (isUnqualified && !isLastDelimiter && (completions contains buffer)) execute(buffer)
else if (!isQualified) None
- else follow(hd) match {
- case Some(next) => next executionFor remainder
- case _ => None
- }
+ else follow(bufferHead) flatMap (_ executionFor bufferTail)
}
}
object CompletionAware {
val Empty = new CompletionAware { val completions = Nil }
+ // class Forwarder(underlying: CompletionAware) extends CompletionAware {
+ // override def completions() = underlying.completions()
+ // override def filterNotFunction(s: String) = underlying.filterNotFunction(s)
+ // override def sortFunction(s1: String, s2: String) = underlying.sortFunction(s1, s2)
+ // override def mapFunction(s: String) = underlying.mapFunction(s)
+ // override def follow(id: String) = underlying.follow(id)
+ // override def execute(id: String) = underlying.execute(id)
+ // override def completionsFor(parsed: Parsed) = underlying.completionsFor(parsed)
+ // override def executionFor(parsed: Parsed) = underlying.executionFor(parsed)
+ // }
+ //
+
def unapply(that: Any): Option[CompletionAware] = that match {
case x: CompletionAware => Some((x))
case _ => None
diff --git a/src/compiler/scala/tools/nsc/interpreter/Delimited.scala b/src/compiler/scala/tools/nsc/interpreter/Delimited.scala
new file mode 100644
index 0000000000..cdf5a343da
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/interpreter/Delimited.scala
@@ -0,0 +1,36 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2010 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.tools.nsc
+package interpreter
+
+import jline.ArgumentCompletor.{ ArgumentDelimiter, ArgumentList }
+
+class JLineDelimiter extends ArgumentDelimiter {
+ def delimit(buffer: String, cursor: Int) = Parsed(buffer, cursor).asJlineArgumentList
+ def isDelimiter(buffer: String, cursor: Int) = Parsed(buffer, cursor).isDelimiter
+}
+
+trait Delimited {
+ self: Parsed =>
+
+ def delimited: Char => Boolean
+ def escapeChars: List[Char] = List('\\')
+ def quoteChars: List[(Char, Char)] = List(('\'', '\''), ('"', '"'))
+
+ /** Break String into args based on delimiting function.
+ */
+ protected def toArgs(s: String): List[String] =
+ if (s == "") Nil
+ else (s indexWhere isDelimiterChar) match {
+ case -1 => List(s)
+ case idx => (s take idx) :: toArgs(s drop (idx + 1))
+ }
+
+ def isDelimiterChar(ch: Char) = delimited(ch)
+ def isEscapeChar(ch: Char): Boolean = escapeChars contains ch
+ def isQuoteStart(ch: Char): Boolean = quoteChars map (_._1) contains ch
+ def isQuoteEnd(ch: Char): Boolean = quoteChars map (_._2) contains ch
+}
diff --git a/src/compiler/scala/tools/nsc/interpreter/FileCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/FileCompletion.scala
new file mode 100644
index 0000000000..c564562a63
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/interpreter/FileCompletion.scala
@@ -0,0 +1,54 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2010 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.tools.nsc
+package interpreter
+
+/** TODO
+ * Spaces, dots, and other things in filenames are not correctly handled.
+ * space-escaping, knowing when we're inside quotes, etc. would be nice.
+ */
+
+import io.{ Directory, Path }
+
+/** This isn't 100% clean right now, but it works and is simple. Rather
+ * than delegate to new objects on each '/' in the path, we treat the
+ * buffer like a path and process it directly.
+ */
+object FileCompletion {
+ def executionFor(buffer: String): Option[Path] = {
+ val p = Path(buffer)
+ if (p.exists) Some(p) else None
+ }
+
+ private def fileCompletionForwarder(buffer: String, where: Directory): List[String] = {
+ completionsFor(where.path + buffer) map (_ stripPrefix where.path) toList
+ }
+
+ private def homeCompletions(buffer: String): List[String] = {
+ require(buffer startsWith "~/")
+ val home = Directory.Home getOrElse (return Nil)
+ fileCompletionForwarder(buffer.tail, home) map ("~" + _)
+ }
+ private def cwdCompletions(buffer: String): List[String] = {
+ require(buffer startsWith "./")
+ val cwd = Directory.Current getOrElse (return Nil)
+ fileCompletionForwarder(buffer.tail, cwd) map ("." + _)
+ }
+
+ def completionsFor(buffer: String): List[String] =
+ if (buffer startsWith "~/") homeCompletions(buffer)
+ else if (buffer startsWith "./") cwdCompletions(buffer)
+ else {
+ val p = Path(buffer)
+ val (dir, stub) =
+ // don't want /foo/. expanding "."
+ if (p.name == ".") (p.parent, ".")
+ else if (p.isDirectory) (p.toDirectory, "")
+ else (p.parent, p.name)
+
+ dir.list filter (_.name startsWith stub) map (_.path) toList
+ }
+} \ No newline at end of file
diff --git a/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala
index 4e1d16c87c..a0d83e1880 100644
--- a/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala
+++ b/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala
@@ -58,6 +58,7 @@ class PackageCompletion(classpath: List[URL]) extends CompletionAware {
aliasCompletor(root + "." + segment)
}
}
+ override def toString = "SubCompletor(%s)" format root
}
}
diff --git a/src/compiler/scala/tools/nsc/interpreter/Parsed.scala b/src/compiler/scala/tools/nsc/interpreter/Parsed.scala
index 885893ee53..05cb2641cd 100644
--- a/src/compiler/scala/tools/nsc/interpreter/Parsed.scala
+++ b/src/compiler/scala/tools/nsc/interpreter/Parsed.scala
@@ -6,26 +6,59 @@
package scala.tools.nsc
package interpreter
+import jline.ArgumentCompletor.{ ArgumentDelimiter, ArgumentList }
+
/** One instance of a command buffer.
*/
-class Parsed(_buf: String) {
- val buffer = if (_buf == null) "" else _buf
- val segments = (buffer split '.').toList filterNot (_ == "")
- lazy val hd :: tl = segments
- def stub = firstDot + hd + "."
- def remainder = buffer stripPrefix stub
- def unqualifiedPart = segments.last
-
- def isEmpty = segments.size == 0
- def isUnqualified = segments.size == 1
- def isQualified = segments.size > 1
-
- def isFirstCharDot = buffer startsWith "."
- def isLastCharDot = buffer endsWith "."
- def firstDot = if (isFirstCharDot) "." else ""
- def lastDot = if (isLastCharDot) "." else ""
-
- // sneakily, that is 0 when there is no dot, which is what we want
- def position = (buffer lastIndexOf '.') + 1
+class Parsed private (
+ val buffer: String,
+ val cursor: Int,
+ val delimited: Char => Boolean
+) extends Delimited {
+ def isEmpty = buffer == ""
+ def isUnqualified = args.size == 1
+ def isQualified = args.size > 1
+ def isAtStart = cursor <= 0
+
+ def args = toArgs(buffer take cursor).toList
+ def bufferHead = args.head
+ def headLength = bufferHead.length + 1
+ def bufferTail = new Parsed(buffer drop headLength, cursor - headLength, delimited)
+
+ def prev = new Parsed(buffer, cursor - 1, delimited)
+ def next = new Parsed(buffer, cursor + 1, delimited)
+ def currentChar = buffer(cursor)
+ def currentArg = args.last
+ def position =
+ if (isEmpty) 0
+ else if (isLastDelimiter) cursor
+ else cursor - currentArg.length
+
+ def isFirstDelimiter = !isEmpty && isDelimiterChar(buffer.head)
+ def isLastDelimiter = !isEmpty && isDelimiterChar(buffer.last)
+ def firstIfDelimiter = if (isFirstDelimiter) buffer.head.toString else ""
+ def lastIfDelimiter = if (isLastDelimiter) buffer.last.toString else ""
+
+ def isQuoted = false // TODO
+ def isEscaped = !isAtStart && isEscapeChar(currentChar) && !isEscapeChar(prev.currentChar)
+ def isDelimiter = !isQuoted && !isEscaped && isDelimiterChar(currentChar)
+
+ def asJlineArgumentList =
+ if (isEmpty) new ArgumentList(Array[String](), 0, 0, cursor)
+ else new ArgumentList(args.toArray, args.size - 1, currentArg.length, cursor)
+
+ override def toString = "Parsed(%s / %d)".format(buffer, cursor)
}
+object Parsed {
+ def onull(s: String) = if (s == null) "" else s
+ def apply(s: String): Parsed = apply(onull(s), onull(s).length)
+ def apply(s: String, cursor: Int): Parsed = apply(onull(s), cursor, "(){},`; \t" contains _)
+ def apply(s: String, cursor: Int, delimited: Char => Boolean): Parsed =
+ new Parsed(onull(s), cursor, delimited)
+
+ def dotted(s: String): Parsed = dotted(onull(s), onull(s).length)
+ def dotted(s: String, cursor: Int): Parsed = new Parsed(onull(s), cursor, _ == '.')
+
+ def undelimited(s: String, cursor: Int): Parsed = new Parsed(onull(s), cursor, _ => false)
+}
diff --git a/src/compiler/scala/tools/nsc/io/Path.scala b/src/compiler/scala/tools/nsc/io/Path.scala
index de1d129e00..b3f7ea5ab5 100644
--- a/src/compiler/scala/tools/nsc/io/Path.scala
+++ b/src/compiler/scala/tools/nsc/io/Path.scala
@@ -92,6 +92,7 @@ class Path private[io] (val jfile: JFile)
def name: String = jfile.getName()
def path: String = jfile.getPath()
def normalize: Path = Path(jfile.getCanonicalPath())
+ def isRootPath: Boolean = roots exists (_ isSame this)
def resolve(other: Path) = if (other.isAbsolute || isEmpty) other else /(other)
def relativize(other: Path) = {
@@ -113,17 +114,19 @@ class Path private[io] (val jfile: JFile)
/**
* @return The path of the parent directory, or root if path is already root
*/
- def parent: Path = {
- val p = path match {
- case "" | "." => ".."
- case _ if path endsWith ".." => path + separator + ".." // the only solution
- case _ => jfile.getParent match {
- case null if isAbsolute => path // it should be a root. BTW, don't need to worry about relative pathed root
- case null => "." // a file ot dir under pwd
- case x => x
- }
- }
- new Directory(new JFile(p))
+ def parent: Directory = path match {
+ case "" | "." => Directory("..")
+ case _ =>
+ // the only solution <-- a comment which could have used elaboration
+ if (segments.nonEmpty && segments.last == "..")
+ (path / "..").toDirectory
+ else jfile.getParent match {
+ case null =>
+ if (isAbsolute) toDirectory // it should be a root. BTW, don't need to worry about relative pathed root
+ else Directory(".") // a dir under pwd
+ case x =>
+ Directory(x)
+ }
}
def parents: List[Path] = {
val p = parent