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