From e35f9bb11ef851590811e1bccfcb082b8a27fecf Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Mon, 22 Feb 2016 12:12:32 +0100 Subject: [PATCH 1/4] Completion for build-level keys sbt's shell provided completion only for keys that were relative to a defined project, but didn't provide completion for keys that belong to the build definition only. This commit fixes this issue by defining a new kind of `Parser` (from which completions are generated) which runs its input simultaneously on distinct parsers. We now define a parser for project-level keys and another parser for build-level keys. These two parsers are eventually combined, and we get the completions of both parsers. Fixes sbt/sbt#2460 --- main/src/main/scala/sbt/Act.scala | 22 +++++++++++---- .../src/main/scala/sbt/complete/Parser.scala | 28 +++++++++++++++++++ util/complete/src/test/scala/ParserTest.scala | 9 ++++++ 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/main/src/main/scala/sbt/Act.scala b/main/src/main/scala/sbt/Act.scala index 3606de881..cde3a73e5 100644 --- a/main/src/main/scala/sbt/Act.scala +++ b/main/src/main/scala/sbt/Act.scala @@ -48,12 +48,22 @@ object Act { new ParsedKey(makeScopedKey(proj, conf, task, extra, key), mask) } - for { - rawProject <- optProjectRef(index, current) - proj = resolveProject(rawProject, current) - confAmb <- config(index configs proj) - partialMask = ScopeMask(rawProject.isExplicit, confAmb.isExplicit, false, false) - } yield taskKeyExtra(proj, confAmb, partialMask) + val projectKeys = + for { + rawProject <- optProjectRef(index, current) + proj = resolveProject(rawProject, current) + confAmb <- config(index configs proj) + partialMask = ScopeMask(rawProject.isExplicit, confAmb.isExplicit, false, false) + } yield taskKeyExtra(proj, confAmb, partialMask) + + val build = Some(BuildRef(current.build)) + val buildKeys = + for { + confAmb <- config(index configs build) + partialMask = ScopeMask(false, confAmb.isExplicit, false, false) + } yield taskKeyExtra(build, confAmb, partialMask) + + buildKeys combinedWith projectKeys map (_.flatten) } def makeScopedKey(proj: Option[ResolvedReference], conf: Option[String], task: Option[AttributeKey[_]], extra: ScopeAxis[AttributeMap], key: AttributeKey[_]): ScopedKey[_] = ScopedKey(Scope(toAxis(proj, Global), toAxis(conf map ConfigKey.apply, Global), toAxis(task, Global), extra), key) diff --git a/util/complete/src/main/scala/sbt/complete/Parser.scala b/util/complete/src/main/scala/sbt/complete/Parser.scala index 892119f6c..4b87e876e 100644 --- a/util/complete/src/main/scala/sbt/complete/Parser.scala +++ b/util/complete/src/main/scala/sbt/complete/Parser.scala @@ -126,6 +126,9 @@ sealed trait RichParser[A] { /** Applies the original parser, applies `f` to the result to get the next parser, and applies that parser and uses its result for the overall result. */ def flatMap[B](f: A => Parser[B]): Parser[B] + + /** Applied both the original parser and `b` on the same input and returns the results produced by each parser */ + def combinedWith(b: Parser[A]): Parser[Seq[A]] } /** Contains Parser implementation helper methods not typically needed for using parsers. */ @@ -229,6 +232,12 @@ object Parser extends ParserMain { } } + def combinedParser[A](a: Parser[A], b: Parser[A]): Parser[Seq[A]] = + if (a.valid) + if (b.valid) new CombiningParser(a, b) else a.map(Seq(_)) + else + b.map(Seq(_)) + def choiceParser[A, B](a: Parser[A], b: Parser[B]): Parser[Either[A, B]] = if (a.valid) if (b.valid) new HetParser(a, b) else a.map(left.fn) @@ -309,6 +318,7 @@ trait ParserMain { def filter(f: A => Boolean, msg: String => String): Parser[A] = filterParser(a, f, "", msg) def string(implicit ev: A <:< Seq[Char]): Parser[String] = map(_.mkString) def flatMap[B](f: A => Parser[B]) = bindParser(a, f) + def combinedWith(b: Parser[A]): Parser[Seq[A]] = combinedParser(a, b) } implicit def literalRichCharParser(c: Char): RichParser[Char] = richParser(c) @@ -633,6 +643,24 @@ private final class HetParser[A, B](a: Parser[A], b: Parser[B]) extends ValidPar def completions(level: Int) = a.completions(level) ++ b.completions(level) override def toString = "(" + a + " || " + b + ")" } +private final class CombiningParser[T](a: Parser[T], b: Parser[T]) extends ValidParser[Seq[T]] { + lazy val result: Option[Seq[T]] = (a.result.toSeq ++ b.result.toSeq) match { case Seq() => None; case seq => Some(seq) } + def completions(level: Int) = a.completions(level) ++ b.completions(level) + def derive(i: Char) = + (a.valid, b.valid) match { + case (true, true) => new CombiningParser(a derive i, b derive i) + case (true, false) => a derive i map (Seq(_)) + case (false, true) => b derive i map (Seq(_)) + case (false, false) => new Invalid(mkFailure("No valid parser available.")) + } + def resultEmpty = + (a.resultEmpty, b.resultEmpty) match { + case (Value(ra), Value(rb)) => Value(Seq(ra, rb)) + case (Value(ra), _) => Value(Seq(ra)) + case (_, Value(rb)) => Value(Seq(rb)) + case _ => Value(Nil) + } +} private final class ParserSeq[T](a: Seq[Parser[T]], errors: => Seq[String]) extends ValidParser[Seq[T]] { assert(a.nonEmpty) lazy val resultEmpty: Result[Seq[T]] = diff --git a/util/complete/src/test/scala/ParserTest.scala b/util/complete/src/test/scala/ParserTest.scala index f02431e53..8bcffad47 100644 --- a/util/complete/src/test/scala/ParserTest.scala +++ b/util/complete/src/test/scala/ParserTest.scala @@ -107,6 +107,15 @@ object ParserTest extends Properties("Completing Parser") { property("repeatDep requires at least one token") = !matches(repeat, "") property("repeatDep accepts one token") = matches(repeat, colors.toSeq.head) property("repeatDep accepts two tokens") = matches(repeat, colors.toSeq.take(2).mkString(" ")) + property("combined parser gives completion of both parsers") = { + val prefix = "fix" + val p1Suffixes = Set("", "ated", "ation") + val p2Suffixes = Set("es", "ed") + val p1: Parser[String] = p1Suffixes map (suffix => (prefix + suffix): Parser[String]) reduce (_ | _) + val p2: Parser[String] = p2Suffixes map (suffix => (prefix + suffix): Parser[String]) reduce (_ | _) + val suggestions: Set[Completion] = p1Suffixes ++ p2Suffixes map (new Suggestion(_)) + checkAll(prefix, p1 combinedWith p2, Completions(suggestions)) + } } object ParserExample { val ws = charClass(_.isWhitespace)+ From 4fea604759100950bad1a296758c018787c3fd19 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Mon, 22 Feb 2016 12:58:30 +0100 Subject: [PATCH 2/4] Unspecified project axis means current project or its build --- main/src/test/scala/ParseKey.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/src/test/scala/ParseKey.scala b/main/src/test/scala/ParseKey.scala index d9607e2de..1081f3517 100644 --- a/main/src/test/scala/ParseKey.scala +++ b/main/src/test/scala/ParseKey.scala @@ -31,7 +31,7 @@ object ParseKey extends Properties("Key parser test") { parseExpected(structure, string, expected, mask) } - property("An unspecified project axis resolves to the current project") = + property("An unspecified project axis resolves to the current project or the build of the current project") = forAllNoShrink(structureDefinedKey) { (skm: StructureKeyMask) => import skm.{ structure, key } @@ -43,7 +43,7 @@ object ParseKey extends Properties("Key parser test") { ("Current: " + structure.current) |: parse(structure, string) { case Left(err) => false - case Right(sk) => sk.scope.project == Select(structure.current) + case Right(sk) => sk.scope.project == Select(structure.current) || sk.scope.project == Select(BuildRef(structure.current.build)) } } From 1bda41fc31372c3f1eefe38afb02127db035bc3b Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Tue, 23 Feb 2016 15:19:04 +0100 Subject: [PATCH 3/4] Address problems reported by Codacy --- .../src/main/scala/sbt/complete/Parser.scala | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/util/complete/src/main/scala/sbt/complete/Parser.scala b/util/complete/src/main/scala/sbt/complete/Parser.scala index 4b87e876e..9bf01e2ba 100644 --- a/util/complete/src/main/scala/sbt/complete/Parser.scala +++ b/util/complete/src/main/scala/sbt/complete/Parser.scala @@ -232,12 +232,6 @@ object Parser extends ParserMain { } } - def combinedParser[A](a: Parser[A], b: Parser[A]): Parser[Seq[A]] = - if (a.valid) - if (b.valid) new CombiningParser(a, b) else a.map(Seq(_)) - else - b.map(Seq(_)) - def choiceParser[A, B](a: Parser[A], b: Parser[B]): Parser[Either[A, B]] = if (a.valid) if (b.valid) new HetParser(a, b) else a.map(left.fn) @@ -318,7 +312,11 @@ trait ParserMain { def filter(f: A => Boolean, msg: String => String): Parser[A] = filterParser(a, f, "", msg) def string(implicit ev: A <:< Seq[Char]): Parser[String] = map(_.mkString) def flatMap[B](f: A => Parser[B]) = bindParser(a, f) - def combinedWith(b: Parser[A]): Parser[Seq[A]] = combinedParser(a, b) + def combinedWith(b: Parser[A]): Parser[Seq[A]] = + if (a.valid) + if (b.valid) new CombiningParser(a, b) else a.map(Seq(_)) + else + b.map(Seq(_)) } implicit def literalRichCharParser(c: Char): RichParser[Char] = richParser(c) From 791358abb043b501fcfdbd3cf3df8b9ed43dbbb2 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Thu, 25 Feb 2016 16:09:17 +0100 Subject: [PATCH 4/4] Notes for #2469 --- notes/0.13.12/autoplugins-buildsettings-keys.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 notes/0.13.12/autoplugins-buildsettings-keys.md diff --git a/notes/0.13.12/autoplugins-buildsettings-keys.md b/notes/0.13.12/autoplugins-buildsettings-keys.md new file mode 100644 index 000000000..35b91f18f --- /dev/null +++ b/notes/0.13.12/autoplugins-buildsettings-keys.md @@ -0,0 +1,11 @@ + [@Duhemm]: https://github.com/Duhemm + [#2460]: https://github.com/sbt/sbt/issues/2460 + [#2469]: https://github.com/sbt/sbt/pull/2469 + +### Fixes with compatibility implications + +### Improvements + +### Bug fixes + +- Fixes tab completion for tasks defined in AutoPlugin's buildSettings [#2460][#2460]/[#2469][#2469] by [@Duhemm][@Duhemm]