summaryrefslogtreecommitdiff
path: root/src/repl
diff options
context:
space:
mode:
authorJason Zaugg <jzaugg@gmail.com>2015-09-02 13:23:44 +1000
committerJason Zaugg <jzaugg@gmail.com>2015-09-02 20:59:35 +1000
commitf279894a3efe84b85e9becfd0ce96aaa0c21bfbd (patch)
treea6848021a9b1e733d02cffdb45b08da68a4d4a8a /src/repl
parent9a3089bd4ac68db798ac006731ebd1b99e9aaaff (diff)
downloadscala-f279894a3efe84b85e9becfd0ce96aaa0c21bfbd.tar.gz
scala-f279894a3efe84b85e9becfd0ce96aaa0c21bfbd.tar.bz2
scala-f279894a3efe84b85e9becfd0ce96aaa0c21bfbd.zip
Use the presentation compiler to drive REPL tab completion
The old implementation is still avaiable under a flag, but we'll remove it in due course. Design goal: - Push as much code in src/interactive as possible to enable reuse outside of the REPL - Don't entangle the REPL completion with JLine. The enclosed test case drives the REPL and autocompletion programatically. - Don't hard code UI choices, like how to render symbols or how to filter candidates. When completion is requested, we wrap the entered code into the same "interpreter wrapper" synthetic code as is done for regular execution. We then start a throwaway instance of the presentation compiler, which takes this as its one and only source file, and has a classpath formed from the REPL's classpath and the REPL's output directory (by default, this is in memory). We can then typecheck the tree, and find the position in the synthetic source corresponding to the cursor location. This is enough to use the new completion APIs in the presentation compiler to prepare a list of candidates. We go to extra lengths to allow completion of partially typed identifiers that appear to be keywords, e.g `global.def` should offer `definitions`. Two secret handshakes are included; move the the end of the line, type `// print<TAB>` and you'll see the post-typer tree. `// typeAt 4 6<TAB>` shows the type of the range position within the buffer. The enclosed unit test exercises most of the new functionality.
Diffstat (limited to 'src/repl')
-rw-r--r--src/repl/scala/tools/nsc/interpreter/ILoop.scala27
-rw-r--r--src/repl/scala/tools/nsc/interpreter/IMain.scala47
-rw-r--r--src/repl/scala/tools/nsc/interpreter/Imports.scala8
-rw-r--r--src/repl/scala/tools/nsc/interpreter/JLineCompletion.scala2
-rw-r--r--src/repl/scala/tools/nsc/interpreter/MemberHandlers.scala13
-rw-r--r--src/repl/scala/tools/nsc/interpreter/PresentationCompilation.scala111
-rw-r--r--src/repl/scala/tools/nsc/interpreter/PresentationCompilerCompleter.scala118
7 files changed, 292 insertions, 34 deletions
diff --git a/src/repl/scala/tools/nsc/interpreter/ILoop.scala b/src/repl/scala/tools/nsc/interpreter/ILoop.scala
index a0cc116df8..f934bbe46c 100644
--- a/src/repl/scala/tools/nsc/interpreter/ILoop.scala
+++ b/src/repl/scala/tools/nsc/interpreter/ILoop.scala
@@ -1,8 +1,7 @@
/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
+ * Copyright 2005-2015 LAMP/EPFL
* @author Alexander Spoon
*/
-
package scala
package tools.nsc
package interpreter
@@ -819,9 +818,10 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
case _ =>
}
- /** Tries to create a JLineReader, falling back to SimpleReader:
- * unless settings or properties are such that it should start
- * with SimpleReader.
+ /** Tries to create a JLineReader, falling back to SimpleReader,
+ * unless settings or properties are such that it should start with SimpleReader.
+ * The constructor of the InteractiveReader must take a Completion strategy,
+ * supplied as a `() => Completion`; the Completion object provides a concrete Completer.
*/
def chooseReader(settings: Settings): InteractiveReader = {
if (settings.Xnojline || Properties.isEmacsShell) SimpleReader()
@@ -829,20 +829,25 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
type Completer = () => Completion
type ReaderMaker = Completer => InteractiveReader
- def instantiate(className: String): ReaderMaker = completer => {
- if (settings.debug) Console.println(s"Trying to instantiate a InteractiveReader from $className")
+ def instantiater(className: String): ReaderMaker = completer => {
+ if (settings.debug) Console.println(s"Trying to instantiate an InteractiveReader from $className")
Class.forName(className).getConstructor(classOf[Completer]).
newInstance(completer).
asInstanceOf[InteractiveReader]
}
- def mkReader(maker: ReaderMaker) =
- if (settings.noCompletion) maker(() => NoCompletion)
- else maker(() => new JLineCompletion(intp)) // JLineCompletion is a misnomer -- it's not tied to jline
+ def mkReader(maker: ReaderMaker) = maker { () =>
+ settings.completion.value match {
+ case _ if settings.noCompletion => NoCompletion
+ case "none" => NoCompletion
+ case "adhoc" => new JLineCompletion(intp) // JLineCompletion is a misnomer; it's not tied to jline
+ case "pc" | _ => new PresentationCompilerCompleter(intp)
+ }
+ }
def internalClass(kind: String) = s"scala.tools.nsc.interpreter.$kind.InteractiveReader"
val readerClasses = sys.props.get("scala.repl.reader").toStream ++ Stream(internalClass("jline"), internalClass("jline_embedded"))
- val readers = readerClasses map (cls => Try { mkReader(instantiate(cls)) })
+ val readers = readerClasses map (cls => Try { mkReader(instantiater(cls)) })
val reader = (readers collect { case Success(reader) => reader } headOption) getOrElse SimpleReader()
diff --git a/src/repl/scala/tools/nsc/interpreter/IMain.scala b/src/repl/scala/tools/nsc/interpreter/IMain.scala
index 3b54f5274e..2085fd4e50 100644
--- a/src/repl/scala/tools/nsc/interpreter/IMain.scala
+++ b/src/repl/scala/tools/nsc/interpreter/IMain.scala
@@ -15,10 +15,13 @@ import scala.concurrent.{ Future, ExecutionContext }
import scala.reflect.runtime.{ universe => ru }
import scala.reflect.{ ClassTag, classTag }
import scala.reflect.internal.util.{ BatchSourceFile, SourceFile }
+import scala.tools.nsc.interactive
+import scala.tools.nsc.reporters.StoreReporter
+import scala.tools.nsc.util.ClassPath.DefaultJavaContext
import scala.tools.util.PathResolverFactory
import scala.tools.nsc.io.AbstractFile
import scala.tools.nsc.typechecker.{ TypeStrings, StructuredTypeStrings }
-import scala.tools.nsc.util.{ ScalaClassLoader, stringFromReader, stringFromWriter, StackTraceOps, ClassPath, MergedClassPath }
+import scala.tools.nsc.util._
import ScalaClassLoader.URLClassLoader
import scala.tools.nsc.util.Exceptional.unwrap
import scala.tools.nsc.backend.JavaPlatform
@@ -58,7 +61,7 @@ import java.io.File
* @author Moez A. Abdel-Gawad
* @author Lex Spoon
*/
-class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Settings, protected val out: JPrintWriter) extends AbstractScriptEngine with Compilable with Imports {
+class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Settings, protected val out: JPrintWriter) extends AbstractScriptEngine with Compilable with Imports with PresentationCompilation {
imain =>
setBindings(createBindings, ScriptContext.ENGINE_SCOPE)
@@ -446,7 +449,7 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set
/** Build a request from the user. `trees` is `line` after being parsed.
*/
- private def buildRequest(line: String, trees: List[Tree]): Request = {
+ private[interpreter] def buildRequest(line: String, trees: List[Tree]): Request = {
executingRequest = new Request(line, trees)
executingRequest
}
@@ -465,11 +468,12 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set
pos
}
- private def requestFromLine(line: String, synthetic: Boolean): Either[IR.Result, Request] = {
- val content = line //indentCode(line)
- val trees = parse(content) match {
- case parse.Incomplete => return Left(IR.Incomplete)
- case parse.Error => return Left(IR.Error)
+ private[interpreter] def requestFromLine(line: String, synthetic: Boolean): Either[IR.Result, Request] = {
+ val content = line
+
+ val trees: List[global.Tree] = parse(content) match {
+ case parse.Incomplete(_) => return Left(IR.Incomplete)
+ case parse.Error(_) => return Left(IR.Error)
case parse.Success(trees) => trees
}
repltrace(
@@ -854,7 +858,7 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set
}
/** One line of code submitted by the user for interpretation */
- class Request(val line: String, val trees: List[Tree]) {
+ class Request(val line: String, val trees: List[Tree], generousImports: Boolean = false) {
def defines = defHandlers flatMap (_.definedSymbols)
def imports = importedSymbols
def value = Some(handlers.last) filter (h => h.definesValue) map (h => definedSymbols(h.definesTerm.get)) getOrElse NoSymbol
@@ -888,7 +892,7 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set
* append to objectName to access anything bound by request.
*/
lazy val ComputedImports(headerPreamble, importsPreamble, importsTrailer, accessPath) =
- exitingTyper(importsCode(referencedNames.toSet, ObjectSourceCode, definesClass))
+ exitingTyper(importsCode(referencedNames.toSet, ObjectSourceCode, definesClass, generousImports))
/** the line of code to compute */
def toCompute = line
@@ -915,6 +919,7 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set
|${envLines mkString (" ", ";\n ", ";\n")}
|$importsPreamble
|${indentCode(toCompute)}""".stripMargin
+ def preambleLength = preamble.length - toCompute.length - 1
val generate = (m: MemberHandler) => m extraCodeToEvaluate Request.this
@@ -954,7 +959,7 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set
def postwrap = s"}\nval $iw = new $iw\n"
}
- private lazy val ObjectSourceCode: Wrapper =
+ private[interpreter] lazy val ObjectSourceCode: Wrapper =
if (isClassBased) new ClassBasedWrapper else new ObjectBasedWrapper
private object ResultObjectSourceCode extends IMain.CodeAssembler[MemberHandler] {
@@ -1015,6 +1020,7 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set
}
lazy val resultSymbol = lineRep.resolvePathToSymbol(fullAccessPath)
+
def applyToResultMember[T](name: Name, f: Symbol => T) = exitingTyper(f(resultSymbol.info.nonPrivateDecl(name)))
/* typeOf lookup with encoding */
@@ -1168,20 +1174,22 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set
/** Parse a line into and return parsing result (error, incomplete or success with list of trees) */
object parse {
- abstract sealed class Result
- case object Error extends Result
- case object Incomplete extends Result
+ abstract sealed class Result { def trees: List[Tree] }
+ case class Error(trees: List[Tree]) extends Result
+ case class Incomplete(trees: List[Tree]) extends Result
case class Success(trees: List[Tree]) extends Result
- def apply(line: String): Result = debugging(s"""parse("$line")""") {
+ def apply(line: String, forPresentation: Boolean = false): Result = debugging(s"""parse("$line")""") {
var isIncomplete = false
- currentRun.parsing.withIncompleteHandler((_, _) => isIncomplete = true) {
+ def parse = {
reporter.reset()
val trees = newUnitParser(line).parseStats()
- if (reporter.hasErrors) Error
- else if (isIncomplete) Incomplete
+ if (reporter.hasErrors) Error(trees)
+ else if (isIncomplete) Incomplete(trees)
else Success(trees)
}
+ currentRun.parsing.withIncompleteHandler((_, _) => isIncomplete = true) {parse}
+
}
}
@@ -1264,6 +1272,8 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set
/** Utility methods for the Interpreter. */
object IMain {
import java.util.Arrays.{ asList => asJavaList }
+ /** Dummy identifier fragement inserted at the cursor before presentation compilation. Needed to support completion of `global.def<TAB>` */
+ val DummyCursorFragment = "_CURSOR_"
class Factory extends ScriptEngineFactory {
@BeanProperty
@@ -1361,3 +1371,4 @@ object IMain {
def stripImpl(str: String): String = naming.unmangle(str)
}
}
+
diff --git a/src/repl/scala/tools/nsc/interpreter/Imports.scala b/src/repl/scala/tools/nsc/interpreter/Imports.scala
index 5b231d94b6..5742c1d0d8 100644
--- a/src/repl/scala/tools/nsc/interpreter/Imports.scala
+++ b/src/repl/scala/tools/nsc/interpreter/Imports.scala
@@ -95,7 +95,8 @@ trait Imports {
* last one imported is actually usable.
*/
case class ComputedImports(header: String, prepend: String, append: String, access: String)
- protected def importsCode(wanted: Set[Name], wrapper: Request#Wrapper, definesClass: Boolean): ComputedImports = {
+
+ protected def importsCode(wanted: Set[Name], wrapper: Request#Wrapper, definesClass: Boolean, generousImports: Boolean): ComputedImports = {
val header, code, trailingBraces, accessPath = new StringBuilder
val currentImps = mutable.HashSet[Name]()
var predefEscapes = false // only emit predef import header if name not resolved in history, loosely
@@ -116,8 +117,9 @@ trait Imports {
def keepHandler(handler: MemberHandler) = handler match {
// While defining classes in class based mode - implicits are not needed.
case h: ImportHandler if isClassBased && definesClass => h.importedNames.exists(x => wanted.contains(x))
- case _: ImportHandler => true
- case x => x.definesImplicit || (x.definedNames exists wanted)
+ case _: ImportHandler => true
+ case x if generousImports => x.definesImplicit || (x.definedNames exists (d => wanted.exists(w => d.startsWith(w))))
+ case x => x.definesImplicit || (x.definedNames exists wanted)
}
reqs match {
diff --git a/src/repl/scala/tools/nsc/interpreter/JLineCompletion.scala b/src/repl/scala/tools/nsc/interpreter/JLineCompletion.scala
index d878988e26..e9b0234a4f 100644
--- a/src/repl/scala/tools/nsc/interpreter/JLineCompletion.scala
+++ b/src/repl/scala/tools/nsc/interpreter/JLineCompletion.scala
@@ -9,6 +9,7 @@ package interpreter
import Completion._
import scala.collection.mutable.ListBuffer
import scala.reflect.internal.util.StringOps.longestCommonPrefix
+import scala.tools.nsc.interactive.Global
// REPL completor - queries supplied interpreter for valid
// completions based on current contents of buffer.
@@ -296,7 +297,6 @@ class JLineCompletion(val intp: IMain) extends Completion with CompletionOutput
override def complete(buf: String, cursor: Int): Candidates = {
verbosity = if (isConsecutiveTabs(buf, cursor)) verbosity + 1 else 0
repldbg(f"%ncomplete($buf, $cursor%d) last = ($lastBuf, $lastCursor%d), verbosity: $verbosity")
-
// we don't try lower priority completions unless higher ones return no results.
def tryCompletion(p: Parsed, completionFunction: Parsed => List[String]): Option[Candidates] = {
val winners = completionFunction(p)
diff --git a/src/repl/scala/tools/nsc/interpreter/MemberHandlers.scala b/src/repl/scala/tools/nsc/interpreter/MemberHandlers.scala
index bcba7b6dfd..7c935b429c 100644
--- a/src/repl/scala/tools/nsc/interpreter/MemberHandlers.scala
+++ b/src/repl/scala/tools/nsc/interpreter/MemberHandlers.scala
@@ -35,7 +35,18 @@ trait MemberHandlers {
// XXX this is obviously inadequate but it's going to require some effort
// to get right.
if (name.toString startsWith "x$") ()
- else importVars += name
+ else {
+ importVars += name
+ if (name.endsWith(IMain.DummyCursorFragment)) {
+ val stripped = name.stripSuffix(IMain.DummyCursorFragment)
+ importVars += stripped
+ if (stripped.isTypeName)
+ // Needed to import `xxx` during line 2 of:
+ // scala> val xxx = ""
+ // scala> def foo: x<TAB>
+ importVars += stripped.toTermName
+ }
+ }
case _ => super.traverse(ast)
}
}
diff --git a/src/repl/scala/tools/nsc/interpreter/PresentationCompilation.scala b/src/repl/scala/tools/nsc/interpreter/PresentationCompilation.scala
new file mode 100644
index 0000000000..81c4fb0b70
--- /dev/null
+++ b/src/repl/scala/tools/nsc/interpreter/PresentationCompilation.scala
@@ -0,0 +1,111 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2015 LAMP/EPFL
+ * @author Martin Odersky
+ */
+package scala.tools.nsc.interpreter
+
+import scala.reflect.internal.util.RangePosition
+import scala.tools.nsc.backend.JavaPlatform
+import scala.tools.nsc.{interactive, Settings}
+import scala.tools.nsc.io._
+import scala.tools.nsc.reporters.StoreReporter
+import scala.tools.nsc.util.ClassPath.DefaultJavaContext
+import scala.tools.nsc.util.{DirectoryClassPath, MergedClassPath}
+
+trait PresentationCompilation {
+ self: IMain =>
+
+ /** Typecheck a line of REPL input, suitably wrapped with "interpreter wrapper" objects/classes, with the
+ * presentation compiler. The result of this method gives access to the typechecked tree and to autocompletion
+ * suggestions.
+ *
+ * The caller is responsible for calling [[PresentationCompileResult#cleanup]] to dispose of the compiler instance.
+ */
+ private[scala] def presentationCompile(line: String): Either[IR.Result, PresentationCompileResult] = {
+ if (global == null) Left(IR.Error)
+ else {
+ // special case for:
+ //
+ // scala> 1
+ // scala> .toInt
+ val line1 = if (Completion.looksLikeInvocation(line)) {
+ self.mostRecentVar + line
+ } else line
+
+ val compiler = newPresentationCompiler()
+ val trees = compiler.newUnitParser(line1).parseStats()
+ val importer = global.mkImporter(compiler)
+ val request = new Request(line1, trees map (t => importer.importTree(t)), generousImports = true)
+ val wrappedCode: String = request.ObjectSourceCode(request.handlers)
+ val unit = compiler.newCompilationUnit(wrappedCode)
+ import compiler._
+ val richUnit = new RichCompilationUnit(unit.source)
+ unitOfFile(richUnit.source.file) = richUnit
+ enteringTyper(typeCheck(richUnit))
+ val result = PresentationCompileResult(compiler)(richUnit, request.ObjectSourceCode.preambleLength + line1.length - line.length)
+ Right(result)
+ }
+ }
+
+ /** Create an instance of the presentation compiler with a classpath comprising the REPL's configured classpath
+ * and the classes output by previously compiled REPL lines.
+ *
+ * You may directly interact with this compiler from any thread, although you must not access it concurrently
+ * from multiple threads.
+ *
+ * You may downcast the `reporter` to `StoreReporter` to access type errors.
+ */
+ def newPresentationCompiler(): interactive.Global = {
+ val replOutClasspath: DirectoryClassPath = new DirectoryClassPath(replOutput.dir, DefaultJavaContext)
+ val mergedClasspath = new MergedClassPath[AbstractFile](replOutClasspath :: global.platform.classPath :: Nil, DefaultJavaContext)
+ def copySettings: Settings = {
+ val s = new Settings(_ => () /* ignores "bad option -nc" errors, etc */)
+ s.processArguments(global.settings.recreateArgs, processAll = false)
+ s.YpresentationAnyThread.value = true
+ s
+ }
+ val storeReporter: StoreReporter = new StoreReporter
+ val interactiveGlobal = new interactive.Global(copySettings, storeReporter) { self =>
+ override lazy val platform: ThisPlatform = new JavaPlatform {
+ val global: self.type = self
+
+ override def classPath: PlatformClassPath = mergedClasspath
+ }
+ }
+ new interactiveGlobal.TyperRun()
+ interactiveGlobal
+ }
+
+ abstract class PresentationCompileResult {
+ val compiler: scala.tools.nsc.interactive.Global
+ def unit: compiler.RichCompilationUnit
+ /** The length of synthetic code the precedes the user writtn code */
+ def preambleLength: Int
+ def cleanup(): Unit = {
+ compiler.askShutdown()
+ }
+ import compiler.CompletionResult
+
+ def completionsAt(cursor: Int): CompletionResult = {
+ val pos = unit.source.position(preambleLength + cursor)
+ compiler.completionsAt(pos)
+ }
+ def typedTreeAt(code: String, selectionStart: Int, selectionEnd: Int): compiler.Tree = {
+ val start = selectionStart + preambleLength
+ val end = selectionEnd + preambleLength
+ val pos = new RangePosition(unit.source, start, start, end)
+ compiler.typedTreeAt(pos)
+ }
+ }
+
+ object PresentationCompileResult {
+ def apply(compiler0: interactive.Global)(unit0: compiler0.RichCompilationUnit, preambleLength0: Int) = new PresentationCompileResult {
+
+ override val compiler = compiler0
+
+ override def unit = unit0.asInstanceOf[compiler.RichCompilationUnit]
+
+ override def preambleLength = preambleLength0
+ }
+ }
+}
diff --git a/src/repl/scala/tools/nsc/interpreter/PresentationCompilerCompleter.scala b/src/repl/scala/tools/nsc/interpreter/PresentationCompilerCompleter.scala
new file mode 100644
index 0000000000..ee901db1a0
--- /dev/null
+++ b/src/repl/scala/tools/nsc/interpreter/PresentationCompilerCompleter.scala
@@ -0,0 +1,118 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2013 LAMP/EPFL
+ * @author Martin Odersky
+ */
+package scala.tools.nsc.interpreter
+
+import scala.reflect.internal.Flags
+import scala.reflect.internal.util.StringOps
+import scala.tools.nsc.interpreter.Completion.{ScalaCompleter, Candidates}
+import scala.util.control.NonFatal
+
+class PresentationCompilerCompleter(intp: IMain) extends Completion with ScalaCompleter {
+ import PresentationCompilerCompleter._
+ import intp.{PresentationCompileResult => Result}
+
+ private type Handler = Result => Candidates
+
+ private var lastRequest = NoRequest
+ private var tabCount = 0
+ private var lastCommonPrefixCompletion: Option[String] = None
+
+ def resetVerbosity(): Unit = { tabCount = 0 ; lastRequest = NoRequest }
+ def completer(): ScalaCompleter = this
+
+ // A convenience for testing
+ def complete(before: String, after: String = ""): Candidates = complete(before + after, before.length)
+ override def complete(buf: String, cursor: Int): Candidates = {
+ val request = Request(buf, cursor)
+ if (request == lastRequest)
+ tabCount += 1
+ else {
+ tabCount = 0
+ lastRequest = request
+ }
+
+ // secret handshakes
+ val slashPrint = """.*// *print *""".r
+ val slashTypeAt = """.*// *typeAt *(\d+) *(\d+) *""".r
+ val Cursor = IMain.DummyCursorFragment
+
+ def print(result: Result) = {
+ val offset = result.preambleLength
+ val pos1 = result.unit.source.position(offset).withEnd(offset + buf.length)
+ import result.compiler._
+ val tree = new Locator(pos1) locateIn result.unit.body match {
+ case Template(_, _, constructor :: (rest :+ last)) => if (rest.isEmpty) last else Block(rest, last)
+ case t => t
+ }
+ val printed = showCode(tree) + " // : " + tree.tpe.safeToString
+ Candidates(cursor, "" :: printed :: Nil)
+ }
+ def typeAt(result: Result, start: Int, end: Int) = {
+ val tpString = result.compiler.exitingTyper(result.typedTreeAt(buf, start, end).tpe.toString)
+ Candidates(cursor, "" :: tpString :: Nil)
+ }
+ def candidates(result: Result): Candidates = {
+ import result.compiler.CompletionResult._, result.compiler.{Symbol, NoSymbol, Type, Member, NoType, Name}
+ def defStringCandidates(qualTpe: Type, matching: List[Member], name: Name): Candidates = {
+ val defStrings = for {
+ member <- matching
+ if member.symNameDropLocal == name
+ sym <- member.sym.alternatives
+ sugared = sym.sugaredSymbolOrSelf
+ } yield {
+ val tp = qualTpe match {
+ case NoType => member.tpe
+ case _ => qualTpe memberType sym
+ }
+ sugared.defStringSeenAs(tp)
+ }
+ Candidates(cursor, "" :: defStrings.distinct)
+ }
+ val found = result.completionsAt(cursor) match {
+ case NoResults => Completion.NoCandidates
+ case r => val matching = r.matchingResults()
+ val tabAfterCommonPrefixCompletion = lastCommonPrefixCompletion.contains(buf.substring(0, cursor)) && matching.exists(_.symNameDropLocal == r.name)
+ val doubleTab = tabCount > 0 && matching.forall(_.symNameDropLocal == r.name)
+ if (tabAfterCommonPrefixCompletion || doubleTab) defStringCandidates(r.qualifierType, matching, r.name)
+ else {
+ if (matching.nonEmpty && matching.forall(_.symNameDropLocal == r.name))
+ Completion.NoCandidates // don't offer completion if the only option has been fully typed already
+ else {
+ // regular completion
+ val memberCompletions: List[String] = matching.map(_.symNameDropLocal.decoded).distinct
+ Candidates(cursor + r.positionDelta, memberCompletions)
+ }
+ }
+ }
+ lastCommonPrefixCompletion =
+ if (found != Completion.NoCandidates && buf.length >= found.cursor)
+ Some(buf.substring(0, found.cursor) + StringOps.longestCommonPrefix(found.candidates))
+ else
+ None
+ found
+ }
+ val buf1 = buf.patch(cursor, Cursor, 0)
+ try {
+ intp.presentationCompile(buf1) match {
+ case Left(_) => Completion.NoCandidates
+ case Right(result) => try {
+ buf match {
+ case slashPrint() if cursor == buf.length => print(result)
+ case slashTypeAt(start, end) if cursor == buf.length => typeAt(result, start.toInt, end.toInt)
+ case _ => candidates(result)
+ }
+ } finally result.cleanup()
+ }
+ } catch {
+ case NonFatal(e) =>
+ if (isReplDebug) e.printStackTrace()
+ Completion.NoCandidates
+ }
+ }
+}
+object PresentationCompilerCompleter {
+ private case class Request(line: String, cursor: Int)
+ private val NoRequest = Request("", -1)
+}