summaryrefslogtreecommitdiff
path: root/src/repl/scala/tools/nsc/interpreter/IMain.scala
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/scala/tools/nsc/interpreter/IMain.scala
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/scala/tools/nsc/interpreter/IMain.scala')
-rw-r--r--src/repl/scala/tools/nsc/interpreter/IMain.scala47
1 files changed, 29 insertions, 18 deletions
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)
}
}
+