mirror of https://github.com/sbt/sbt.git
115 lines
3.9 KiB
Scala
115 lines
3.9 KiB
Scala
/* sbt -- Simple Build Tool
|
|
* Copyright 2011 Mark Harrah
|
|
*/
|
|
package sbt.complete
|
|
|
|
import jline.{CandidateListCompletionHandler,Completor,CompletionHandler,ConsoleReader}
|
|
import scala.annotation.tailrec
|
|
import collection.JavaConversions
|
|
|
|
object JLineCompletion
|
|
{
|
|
def installCustomCompletor(reader: ConsoleReader, parser: Parser[_]): Unit =
|
|
installCustomCompletor(parserAsCompletor(parser), reader)
|
|
def installCustomCompletor(reader: ConsoleReader)(complete: String => (Seq[String], Seq[String])): Unit =
|
|
installCustomCompletor(customCompletor(complete), reader)
|
|
def installCustomCompletor(complete: ConsoleReader => Boolean, reader: ConsoleReader): Unit =
|
|
{
|
|
reader.removeCompletor(DummyCompletor)
|
|
reader.addCompletor(DummyCompletor)
|
|
reader.setCompletionHandler(new CustomHandler(complete))
|
|
}
|
|
|
|
private[this] final class CustomHandler(completeImpl: ConsoleReader => Boolean) extends CompletionHandler
|
|
{
|
|
override def complete(reader: ConsoleReader, candidates: java.util.List[_], position: Int) = completeImpl(reader)
|
|
}
|
|
|
|
// always provides dummy completions so that the custom completion handler gets called
|
|
// (ConsoleReader doesn't call the handler if there aren't any completions)
|
|
// the custom handler will then throw away the candidates and call the custom function
|
|
private[this] final object DummyCompletor extends Completor
|
|
{
|
|
override def complete(buffer: String, cursor: Int, candidates: java.util.List[_]): Int =
|
|
{
|
|
candidates.asInstanceOf[java.util.List[String]] add "dummy"
|
|
0
|
|
}
|
|
}
|
|
|
|
def parserAsCompletor(p: Parser[_]): ConsoleReader => Boolean =
|
|
customCompletor(str => convertCompletions(Parser.completions(p, str)))
|
|
def convertCompletions(c: Completions): (Seq[String], Seq[String]) =
|
|
{
|
|
val cs = c.get
|
|
if(cs.isEmpty)
|
|
(Nil, "{invalid input}" :: Nil)
|
|
else
|
|
convertCompletions(cs)
|
|
}
|
|
def convertCompletions(cs: Set[Completion]): (Seq[String], Seq[String]) =
|
|
{
|
|
val (insert, display) =
|
|
( (Set.empty[String], Set.empty[String]) /: cs) { case ( t @ (insert,display), comp) =>
|
|
if(comp.isEmpty) t else (insert + comp.append, appendNonEmpty(display, comp.display.trim))
|
|
}
|
|
(insert.toSeq, display.toSeq.sorted)
|
|
}
|
|
def appendNonEmpty(set: Set[String], add: String) = if(add.isEmpty) set else set + add
|
|
|
|
def customCompletor(f: String => (Seq[String], Seq[String])): ConsoleReader => Boolean =
|
|
reader => {
|
|
val success = complete(beforeCursor(reader), f, reader)
|
|
reader.flushConsole()
|
|
success
|
|
}
|
|
|
|
def beforeCursor(reader: ConsoleReader): String =
|
|
{
|
|
val b = reader.getCursorBuffer
|
|
b.getBuffer.substring(0, b.cursor)
|
|
}
|
|
|
|
// returns false if there was nothing to insert and nothing to display
|
|
def complete(beforeCursor: String, completions: String => (Seq[String],Seq[String]), reader: ConsoleReader): Boolean =
|
|
{
|
|
val (insert,display) = completions(beforeCursor)
|
|
val common = commonPrefix(insert)
|
|
if(common.isEmpty)
|
|
if(display.isEmpty)
|
|
()
|
|
else
|
|
showCompletions(display, reader)
|
|
else
|
|
appendCompletion(common, reader)
|
|
|
|
!(common.isEmpty && display.isEmpty)
|
|
}
|
|
|
|
def appendCompletion(common: String, reader: ConsoleReader)
|
|
{
|
|
reader.getCursorBuffer.write(common)
|
|
reader.redrawLine()
|
|
}
|
|
|
|
def showCompletions(display: Seq[String], reader: ConsoleReader)
|
|
{
|
|
printCompletions(display, reader)
|
|
reader.drawLine()
|
|
}
|
|
def printCompletions(cs: Seq[String], reader: ConsoleReader): Unit =
|
|
{
|
|
// CandidateListCompletionHandler doesn't print a new line before the prompt
|
|
if(cs.size > reader.getAutoprintThreshhold)
|
|
reader.printNewline()
|
|
CandidateListCompletionHandler.printCandidates(reader, JavaConversions.asJavaList(cs), true)
|
|
}
|
|
|
|
def commonPrefix(s: Seq[String]): String = if(s.isEmpty) "" else s reduceLeft commonPrefix
|
|
def commonPrefix(a: String, b: String): String =
|
|
{
|
|
val len = a.length min b.length
|
|
@tailrec def loop(i: Int): Int = if(i >= len) len else if(a(i) != b(i)) i else loop(i+1)
|
|
a.substring(0, loop(0))
|
|
}
|
|
} |