diff --git a/main/Command.scala b/main/Command.scala index c8e7188fe..352145a7b 100644 --- a/main/Command.scala +++ b/main/Command.scala @@ -50,8 +50,8 @@ object Command def single(name: String, briefHelp: (String, String), detail: String)(f: (State, String) => State): Command = single(name, Help(name, briefHelp, detail) )(f) def single(name: String, help: Help*)(f: (State, String) => State): Command = - make(name, help : _*)( state => token(trimmed(any.+.string) map apply1(f, state)) ) - + make(name, help : _*)( state => token(trimmed(spacedAny(name)) map apply1(f, state)) ) + def custom(parser: State => Parser[() => State], help: Seq[Help] = Nil): Command = new ArbitraryCommand(parser, help, AttributeMap.empty) def arb[T](parser: State => Parser[T], help: Help*)(effect: (State, T) => State): Command = custom(applyEffect(parser)(effect), help) @@ -130,6 +130,10 @@ object Command bs.map { b => (b, distance(a, b) ) } filter (_._2 <= maxDistance) sortBy(_._2) take(maxSuggestions) map(_._1) def distance(a: String, b: String): Int = EditDistance.levenshtein(a, b, insertCost = 1, deleteCost = 1, subCost = 2, transposeCost = 1, matchCost = -1, true) + + def spacedAny(name: String): Parser[String] = spacedC(name, any) + def spacedC(name: String, c: Parser[Char]): Parser[String] = + ( (c & opOrIDSpaced(name)) ~ c.+) map { case (f, rem) => (f +: rem).mkString } } trait Help diff --git a/main/Cross.scala b/main/Cross.scala index 93070cad3..1c1766a73 100644 --- a/main/Cross.scala +++ b/main/Cross.scala @@ -17,8 +17,12 @@ object Cross def switchParser(state: State): Parser[(String, String)] = { val knownVersions = crossVersions(state) - token(Switch ~ Space) flatMap { _ => token(NotSpace.examples(knownVersions : _*)) ~ (token(Space ~> matched(state.combinedParser)) ?? "") } + lazy val switchArgs = token(OptSpace ~> NotSpace.examples(knownVersions : _*)) ~ (token(Space ~> matched(state.combinedParser)) ?? "") + lazy val nextSpaced = spacedFirst(Switch) + token(Switch) flatMap { _ => switchArgs & nextSpaced } } + def spacedFirst(name: String) = opOrIDSpaced(name) ~ any.+ + lazy val switchVersion = Command.arb(requireSession(switchParser)) { case (state, (version, command)) => val x = Project.extract(state) import x._ @@ -32,7 +36,7 @@ object Cross s.key.key == scalaVersion.key || s.key.key == scalaHome.key def crossParser(state: State): Parser[String] = - token(Cross ~ Space) flatMap { _ => token(matched(state.combinedParser)) } + token(Cross <~ OptSpace) flatMap { _ => token(matched( state.combinedParser & spacedFirst(Cross) )) } lazy val crossBuild = Command.arb(requireSession(crossParser)) { (state, command) => val x = Project.extract(state) diff --git a/util/complete/Parsers.scala b/util/complete/Parsers.scala index 93ab9b9ac..4a82ab482 100644 --- a/util/complete/Parsers.scala +++ b/util/complete/Parsers.scala @@ -11,7 +11,7 @@ package sbt.complete // Some predefined parsers trait Parsers { - lazy val any: Parser[Char] = charClass(_ => true) + lazy val any: Parser[Char] = charClass(_ => true, "any character") lazy val DigitSet = Set("0","1","2","3","4","5","6","7","8","9") lazy val Digit = charClass(_.isDigit, "digit") examples DigitSet @@ -23,6 +23,14 @@ trait Parsers lazy val Op = OpChar.+.string lazy val OpOrID = ID | Op + def opOrIDSpaced(s: String): Parser[Char] = + if(DefaultParsers.matches(ID, s)) + OpChar | SpaceClass + else if(DefaultParsers.matches(Op, s)) + IDStart | SpaceClass + else + any + def isOpChar(c: Char) = !isDelimiter(c) && isOpType(getType(c)) def isOpType(cat: Int) = cat match { case MATH_SYMBOL | OTHER_SYMBOL | DASH_PUNCTUATION | OTHER_PUNCTUATION | MODIFIER_SYMBOL | CURRENCY_SYMBOL => true; case _ => false } def isIDChar(c: Char) = c.isLetterOrDigit || c == '-' || c == '_'