/* sbt -- Simple Build Tool * Copyright 2010 Mark Harrah */ package sbt.complete /** * Represents a set of completions. * It exists instead of implicitly defined operations on top of Set[Completion] * for laziness. */ sealed trait Completions { def get: Set[Completion] final def x(o: Completions): Completions = flatMap(_ x o) final def ++(o: Completions): Completions = Completions( get ++ o.get ) final def +:(o: Completion): Completions = Completions(get + o) final def filter(f: Completion => Boolean): Completions = Completions(get filter f) final def filterS(f: String => Boolean): Completions = filter(c => f(c.append)) override def toString = get.mkString("Completions(",",",")") final def flatMap(f: Completion => Completions): Completions = Completions(get.flatMap(c => f(c).get)) final def map(f: Completion => Completion): Completions = Completions(get map f) override final def hashCode = get.hashCode override final def equals(o: Any) = o match { case c: Completions => get == c.get; case _ => false } } object Completions { /** Returns a lazy Completions instance using the provided Completion Set. */ def apply(cs: => Set[Completion]): Completions = new Completions { lazy val get = cs } /** Returns a strict Completions instance using the provided Completion Set. */ def strict(cs: Set[Completion]): Completions = apply(cs) /** No suggested completions, not even the empty Completion. * This typically represents invalid input. */ val nil: Completions = strict(Set.empty) /** Only includes an empty Suggestion. * This typically represents valid input that either has no completions or accepts no further input. */ val empty: Completions = strict(Set.empty + Completion.empty) /** Returns a strict Completions instance containing only the provided Completion.*/ def single(c: Completion): Completions = strict(Set.empty + c) } /** * Represents a completion. * The abstract members `display` and `append` are best explained with an example. * * Assuming space-delimited tokens, processing this: * am is are w * could produce these Completions: * Completion { display = "was"; append = "as" } * Completion { display = "were"; append = "ere" } * to suggest the tokens "was" and "were". * * In this way, two pieces of information are preserved: * 1) what needs to be appended to the current input if a completion is selected * 2) the full token being completed, which is useful for presenting a user with choices to select */ sealed trait Completion { /** The proposed suffix to append to the existing input to complete the last token in the input.*/ def append: String /** The string to present to the user to represent the full token being suggested.*/ def display: String /** True if this Completion is suggesting the empty string.*/ def isEmpty: Boolean /** Appends the completions in `o` with the completions in this Completion.*/ def ++(o: Completion): Completion = Completion.concat(this, o) final def x(o: Completions): Completions = if(Completion evaluatesRight this) o.map(this ++ _) else Completions.strict(Set.empty + this) override final lazy val hashCode = Completion.hashCode(this) override final def equals(o: Any) = o match { case c: Completion => Completion.equal(this, c); case _ => false } } final class DisplayOnly(val display: String) extends Completion { def isEmpty = display.isEmpty def append = "" override def toString = "{" + display + "}" } final class Token(val display: String, val append: String) extends Completion { @deprecated("Retained only for compatibility. All information is now in `display` and `append`.", "0.12.1") lazy val prepend = display.stripSuffix(append) def isEmpty = display.isEmpty && append.isEmpty override final def toString = "[" + display + "]++" + append } final class Suggestion(val append: String) extends Completion { def isEmpty = append.isEmpty def display = append override def toString = append } object Completion { def concat(a: Completion, b: Completion): Completion = (a,b) match { case (as: Suggestion, bs: Suggestion) => suggestion(as.append + bs.append) case (at: Token, _) if at.append.isEmpty => b case _ if a.isEmpty => b case _ => a } def evaluatesRight(a: Completion): Boolean = a match { case _: Suggestion => true case at: Token if at.append.isEmpty => true case _ => a.isEmpty } def equal(a: Completion, b: Completion): Boolean = (a,b) match { case (as: Suggestion, bs: Suggestion) => as.append == bs.append case (ad: DisplayOnly, bd: DisplayOnly) => ad.display == bd.display case (at: Token, bt: Token) => at.display == bt.display && at.append == bt.append case _ => false } def hashCode(a: Completion): Int = a match { case as: Suggestion => (0, as.append).hashCode case ad: DisplayOnly => (1, ad.display).hashCode case at: Token => (2, at.display, at.append).hashCode } val empty: Completion = suggestion("") def single(c: Char): Completion = suggestion(c.toString) // TODO: make strict in 0.13.0 to match DisplayOnly def displayOnly(value: => String): Completion = new DisplayOnly(value) @deprecated("Use displayOnly.", "0.12.1") def displayStrict(value: String): Completion = displayOnly(value) // TODO: make strict in 0.13.0 to match Token def token(prepend: => String, append: => String): Completion = new Token(prepend+append, append) @deprecated("Use token.", "0.12.1") def tokenStrict(prepend: String, append: String): Completion = token(prepend, append) /** @since 0.12.1 */ def tokenDisplay(append: String, display: String): Completion = new Token(display, append) // TODO: make strict in 0.13.0 to match Suggestion def suggestion(value: => String): Completion = new Suggestion(value) @deprecated("Use suggestion.", "0.12.1") def suggestStrict(value: String): Completion = suggestion(value) }