From 06ad297a07716910814651acd60736978aa54d63 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 2 Sep 2015 12:50:23 +1000 Subject: Add an option to let pres compiler user manage threading The presentation compiler currently demands that all interaction is performed by asynchronous submission of work items, which are queued and executed on the presentation compiler thread. This is fairly inconvenient if you are a known-single-threaded client that is trying to use the compiler from your own thread. This commit adds an option to disable "assertCorrectThread" to better support this use case. --- src/interactive/scala/tools/nsc/interactive/Global.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/interactive') diff --git a/src/interactive/scala/tools/nsc/interactive/Global.scala b/src/interactive/scala/tools/nsc/interactive/Global.scala index 727bfdd510..1e9e66df64 100644 --- a/src/interactive/scala/tools/nsc/interactive/Global.scala +++ b/src/interactive/scala/tools/nsc/interactive/Global.scala @@ -120,6 +120,7 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") val debugIDE: Boolean = settings.YpresentationDebug.value val verboseIDE: Boolean = settings.YpresentationVerbose.value + private val anyThread: Boolean = settings.YpresentationAnyThread.value private def replayName = settings.YpresentationReplay.value private def logName = settings.YpresentationLog.value @@ -532,7 +533,7 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") */ @elidable(elidable.WARNING) override def assertCorrectThread() { - assert(initializing || onCompilerThread, + assert(initializing || anyThread || onCompilerThread, "Race condition detected: You are running a presentation compiler method outside the PC thread.[phase: %s]".format(globalPhase) + " Please file a ticket with the current stack trace at https://www.assembla.com/spaces/scala-ide/support/tickets") } -- cgit v1.2.3 From eca39a40d3c0158192e7364ebb1d9bd5b8deec55 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 2 Sep 2015 12:53:20 +1000 Subject: Add a higher level completion API to the pres compiler Conveniences added: - The client need not determine ahead of time whether it wants scope completions of type member completions, this is inferred from the tree at the cursor - Computes the delta from the cursor to point where a suggested completion item should be written. This includes finding the position of the name within a Select node, which is tricky. - Includes a matcher that matches results base on prefix, accessibility and suitability in the syntactic location. Type members are not offered in term positions, and term members only offered in type position if they could for a prefix for a type. E.g. `type T = glob` should offer `global` to help writing `global.Tree`. --- .../scala/tools/nsc/interactive/Global.scala | 78 +++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) (limited to 'src/interactive') diff --git a/src/interactive/scala/tools/nsc/interactive/Global.scala b/src/interactive/scala/tools/nsc/interactive/Global.scala index 1e9e66df64..8f04735ceb 100644 --- a/src/interactive/scala/tools/nsc/interactive/Global.scala +++ b/src/interactive/scala/tools/nsc/interactive/Global.scala @@ -662,7 +662,7 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") /** Make sure unit is typechecked */ - private def typeCheck(unit: RichCompilationUnit) { + private[scala] def typeCheck(unit: RichCompilationUnit) { debugLog("type checking: "+unit) parseAndEnter(unit) unit.status = PartiallyChecked @@ -1148,6 +1148,82 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") } } + sealed abstract class CompletionResult { + type M <: Member + def results: List[M] + /** The (possibly partial) detected that precedes the cursor */ + def name: Name + def positionDelta: Int + def matchingResults(matcher: (M, Name) => Boolean = CompletionResult.prefixMatcher): List[M] = { + results filter (r => matcher(r, name)) + } + def qualifierType: Type = NoType + } + object CompletionResult { + final case class ScopeMembers(positionDelta: Int, results: List[ScopeMember], name: Name) extends CompletionResult { + type M = ScopeMember + } + final case class TypeMembers(positionDelta: Int, qualifier: Tree, tree: Tree, results: List[TypeMember], name: Name) extends CompletionResult { + type M = TypeMember + override def qualifierType: Type = qualifier.tpe + } + case object NoResults extends CompletionResult { + override def results = Nil + override def name = nme.EMPTY + override def positionDelta = 0 + + } + val prefixMatcher = (member: Member, name: Name) => { + val symbol = member.sym + val prefix = if (name == nme.ERROR) nme.EMPTY else name + def isStable = member.tpe.isStable || member.sym.isStable || member.sym.getterIn(member.sym.owner).isStable + val nameMatches = !symbol.isConstructor && (prefix.isEmpty || symbol.name.startsWith(prefix) && (symbol.name.isTermName == prefix.isTermName || prefix.isTypeName && isStable)) + nameMatches && member.accessible + } + } + + final def completionsAt(pos: Position): CompletionResult = { + val focus1: Tree = typedTreeAt(pos) + def typeCompletions(tree: Tree, qual: Tree, nameStart: Int, name: Name): CompletionResult = { + val qualPos = qual.pos + val allTypeMembers = typeMembers(qualPos).toList.flatten + + val positionDelta: Int = nameStart - pos.start + val subName = name.subName(0, -positionDelta) + CompletionResult.TypeMembers(positionDelta, qual, tree, allTypeMembers, subName) + } + focus1 match { + case imp@Import(i @ Ident(name), head :: Nil) if head.name == nme.ERROR => + val allMembers = scopeMembers(pos) + val nameStart = i.pos.start + val positionDelta: Int = nameStart - pos.start + val subName = name.subName(0, pos.start - i.pos.start) + CompletionResult.ScopeMembers(positionDelta, allMembers, subName) + case imp@Import(qual, selectors) => + selectors.reverseIterator.find(_.namePos <= pos.start) match { + case None => CompletionResult.NoResults + case Some(selector) => + typeCompletions(imp, qual, selector.namePos, selector.name) + } + case sel@Select(qual, name) => + val qualPos = qual.pos + def fallback = qualPos.end + 2 + val source = pos.source + val nameStart: Int = (qualPos.end + 1 until focus1.pos.end).find(p => + source.identifier(source.position(p)).exists(_.length > 0) + ).getOrElse(fallback) + typeCompletions(sel, qual, nameStart, name) + case Ident(name) => + val allMembers = scopeMembers(pos) + val positionDelta: Int = focus1.pos.start - pos.start + val subName = name.subName(0, -positionDelta) + CompletionResult.ScopeMembers(positionDelta, allMembers, subName) + case _ => + CompletionResult.NoResults + } + } + + /** Implements CompilerControl.askLoadedTyped */ private[interactive] def waitLoadedTyped(source: SourceFile, response: Response[Tree], keepLoaded: Boolean = false, onSameThread: Boolean = true) { getUnit(source) match { -- cgit v1.2.3 From 3d7066d0a710a05b392b47077f4fe00673df2131 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 2 Sep 2015 13:06:09 +1000 Subject: Publicize method in the presentation compiler --- src/interactive/scala/tools/nsc/interactive/Global.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/interactive') diff --git a/src/interactive/scala/tools/nsc/interactive/Global.scala b/src/interactive/scala/tools/nsc/interactive/Global.scala index 8f04735ceb..d5caf28991 100644 --- a/src/interactive/scala/tools/nsc/interactive/Global.scala +++ b/src/interactive/scala/tools/nsc/interactive/Global.scala @@ -793,7 +793,7 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") } /** A fully attributed tree located at position `pos` */ - private[interactive] def typedTreeAt(pos: Position): Tree = getUnit(pos.source) match { + private[scala] def typedTreeAt(pos: Position): Tree = getUnit(pos.source) match { case None => reloadSources(List(pos.source)) try typedTreeAt(pos) -- cgit v1.2.3 From f279894a3efe84b85e9becfd0ce96aaa0c21bfbd Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 2 Sep 2015 13:23:44 +1000 Subject: 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` and you'll see the post-typer tree. `// typeAt 4 6` shows the type of the range position within the buffer. The enclosed unit test exercises most of the new functionality. --- .../scala/tools/nsc/settings/ScalaSettings.scala | 1 + .../tools/nsc/interactive/CompilerControl.scala | 1 + .../nsc/interpreter/jline/JLineDelimiter.scala | 2 +- .../tools/nsc/interpreter/jline/JLineReader.scala | 40 +++---- src/repl/scala/tools/nsc/interpreter/ILoop.scala | 27 +++-- src/repl/scala/tools/nsc/interpreter/IMain.scala | 47 ++++---- src/repl/scala/tools/nsc/interpreter/Imports.scala | 8 +- .../tools/nsc/interpreter/JLineCompletion.scala | 2 +- .../tools/nsc/interpreter/MemberHandlers.scala | 13 ++- .../nsc/interpreter/PresentationCompilation.scala | 111 +++++++++++++++++++ .../PresentationCompilerCompleter.scala | 118 +++++++++++++++++++++ .../tools/nsc/interpreter/CompletionTest.scala | 109 +++++++++++++++++++ 12 files changed, 426 insertions(+), 53 deletions(-) create mode 100644 src/repl/scala/tools/nsc/interpreter/PresentationCompilation.scala create mode 100644 src/repl/scala/tools/nsc/interpreter/PresentationCompilerCompleter.scala create mode 100644 test/junit/scala/tools/nsc/interpreter/CompletionTest.scala (limited to 'src/interactive') diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index ed6ae48890..1817cfa25a 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -175,6 +175,7 @@ trait ScalaSettings extends AbsScalaSettings val YconstOptimization = BooleanSetting ("-Yconst-opt", "Perform optimization with constant values.") val Ycompacttrees = BooleanSetting ("-Ycompact-trees", "Use compact tree printer when displaying trees.") val noCompletion = BooleanSetting ("-Yno-completion", "Disable tab-completion in the REPL.") + val completion = ChoiceSetting ("-Ycompletion", "provider", "Select tab-completion in the REPL.", List("pc","adhoc","none"), "pc") val Xdce = BooleanSetting ("-Ydead-code", "Perform dead code elimination.") val debug = BooleanSetting ("-Ydebug", "Increase the quantity of debugging output.") //val doc = BooleanSetting ("-Ydoc", "Generate documentation") diff --git a/src/interactive/scala/tools/nsc/interactive/CompilerControl.scala b/src/interactive/scala/tools/nsc/interactive/CompilerControl.scala index 9caebb711d..fc6403afa3 100644 --- a/src/interactive/scala/tools/nsc/interactive/CompilerControl.scala +++ b/src/interactive/scala/tools/nsc/interactive/CompilerControl.scala @@ -272,6 +272,7 @@ trait CompilerControl { self: Global => val tpe: Type val accessible: Boolean def implicitlyAdded = false + def symNameDropLocal: Name = sym.name.dropLocal private def accessible_s = if (accessible) "" else "[inaccessible] " def forceInfoString = { diff --git a/src/repl-jline/scala/tools/nsc/interpreter/jline/JLineDelimiter.scala b/src/repl-jline/scala/tools/nsc/interpreter/jline/JLineDelimiter.scala index c18a9809a0..89e849429d 100644 --- a/src/repl-jline/scala/tools/nsc/interpreter/jline/JLineDelimiter.scala +++ b/src/repl-jline/scala/tools/nsc/interpreter/jline/JLineDelimiter.scala @@ -11,7 +11,7 @@ import _root_.jline.console.completer.ArgumentCompleter.{ ArgumentDelimiter, Arg // implements a jline interface class JLineDelimiter extends ArgumentDelimiter { - def toJLine(args: List[String], cursor: Int) = args match { + def toJLine(args: List[String], cursor: Int): ArgumentList = args match { case Nil => new ArgumentList(new Array[String](0), 0, 0, cursor) case xs => new ArgumentList(xs.toArray, xs.size - 1, xs.last.length, cursor) } diff --git a/src/repl-jline/scala/tools/nsc/interpreter/jline/JLineReader.scala b/src/repl-jline/scala/tools/nsc/interpreter/jline/JLineReader.scala index f0fce13fe8..5082c99a76 100644 --- a/src/repl-jline/scala/tools/nsc/interpreter/jline/JLineReader.scala +++ b/src/repl-jline/scala/tools/nsc/interpreter/jline/JLineReader.scala @@ -15,7 +15,7 @@ import jconsole.history.{History => JHistory} import scala.tools.nsc.interpreter -import scala.tools.nsc.interpreter.Completion +import scala.tools.nsc.interpreter.{Completion, JLineCompletion, NoCompletion} import scala.tools.nsc.interpreter.Completion.Candidates import scala.tools.nsc.interpreter.session.History @@ -121,23 +121,27 @@ private class JLineConsoleReader extends jconsole.ConsoleReader with interpreter def initCompletion(completion: Completion): Unit = { this setBellEnabled false - if (completion ne interpreter.NoCompletion) { - val jlineCompleter = new ArgumentCompleter(new JLineDelimiter, - new Completer { - val tc = completion.completer() - def complete(_buf: String, cursor: Int, candidates: JList[CharSequence]): Int = { - val buf = if (_buf == null) "" else _buf - val Candidates(newCursor, newCandidates) = tc.complete(buf, cursor) - newCandidates foreach (candidates add _) - newCursor - } - } - ) - - jlineCompleter setStrict false - - this addCompleter jlineCompleter - this setAutoprintThreshold 400 // max completion candidates without warning + // adapt the JLine completion interface + def completer = + new Completer { + val tc = completion.completer() + def complete(_buf: String, cursor: Int, candidates: JList[CharSequence]): Int = { + val buf = if (_buf == null) "" else _buf + val Candidates(newCursor, newCandidates) = tc.complete(buf, cursor) + newCandidates foreach (candidates add _) + newCursor + } + } + + // a last bit of nastiness: parsing help depending on the flavor of completer (fixme) + completion match { + case _: JLineCompletion => + val jlineCompleter = new ArgumentCompleter(new JLineDelimiter, completer) + jlineCompleter setStrict false + this addCompleter jlineCompleter + case NoCompletion => () + case _ => this addCompleter completer } + setAutoprintThreshold(400) // max completion candidates without warning } } 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` */ + 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 + 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) +} diff --git a/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala b/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala new file mode 100644 index 0000000000..70cb2882ba --- /dev/null +++ b/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala @@ -0,0 +1,109 @@ +package scala.tools.nsc.interpreter + +import java.io.{StringWriter, PrintWriter} + +import org.junit.Assert.assertEquals +import org.junit.Test + +import scala.tools.nsc.Settings + +class CompletionTest { + val EmptyString = "" // def string results include the empty string so that JLine won't insert "def ..." at the cursor + + def newIMain(): IMain = { + val settings = new Settings() + settings.Xnojline.value = true + settings.usejavacp.value = true + + val writer = new StringWriter + val out = new PrintWriter(writer) + new IMain(settings, out) + } + @Test + def t4438_arrayCompletion(): Unit = { + val intp = newIMain() + val completer = new PresentationCompilerCompleter(intp) + assert(completer.complete("Array(1, 2, 3) rev").candidates.contains("reverseMap")) + } + + @Test + def completions(): Unit = { + val intp = newIMain() + val completer = new PresentationCompilerCompleter(intp) + checkExact(completer, "object O { def x_y_z = 1 }; import O._; x_y")("x_y_z") + checkExact(completer, "object O { private def x_y_z = 1 }; import O._; x_y")() + checkExact(completer, "object O { private def x_y_z = 1; x_y", "}")("x_y_z") + checkExact(completer, "object x_y_z; import x_y")("x_y_z") + + checkExact(completer, "object x_y_z { def a_b_c }; import x_y_z.a_b")("a_b_c") + + checkExact(completer, "object X { private[this] def definition = 0; def")("definition") + + // stable terms are offered in type completion as they might be used as a prefix + checkExact(completer, """object O { def x_y_z = 0; val x_z_y = ""; type T = x_""")("x_z_y") + checkExact(completer, """def method { def x_y_z = 0; val x_z_y = ""; type T = x_""")("x_z_y") + } + + @Test + def previousLineCompletions(): Unit = { + val intp = newIMain() + intp.interpret("class C { val x_y_z = 42 }") + intp.interpret("object O { type T = Int }") + + val completer = new PresentationCompilerCompleter(intp) + + checkExact(completer, "new C().x_y")("x_y_z") + checkExact(completer, "(1 : O.T).toCha")("toChar") + } + + @Test + def previousResultInvocation(): Unit = { + val intp = newIMain() + intp.interpret("1 + 1") + + val completer = new PresentationCompilerCompleter(intp) + + checkExact(completer, ".toCha")("toChar") + } + + @Test + def defString(): Unit = { + val intp = newIMain() + val completer = new PresentationCompilerCompleter(intp) + + // Double Tab on a fully typed selection shows the def string + checkExact(completer, "(p: {def a_b_c: Int}) => p.a_b_c")() + checkExact(completer, "(p: {def a_b_c: Int}) => p.a_b_c")(EmptyString, "def a_b_c: Int") + + // likewise for an ident + checkExact(completer, "(p: {def x_y_z: Int}) => {import p._; x_y_z")() + checkExact(completer, "(p: {def x_y_z: Int}) => {import p._; x_y_z")(EmptyString, "def x_y_z: Int") + + // If the first completion only gives one alternative + checkExact(completer, "(p: {def x_y_z: Int; def x_y_z(a: String): Int }) => p.x_y")("x_y_z") + // ... it is automatically inserted into the buffer. Hitting again is triggers the help + checkExact(completer, "(p: {def x_y_z: Int; def x_y_z(a: String): Int }) => p.x_y_z")(EmptyString, "def x_y_z(a: String): Int", "def x_y_z: Int") + + checkExact(completer, "(p: {def x_y_z: Int; def x_z_y(a: String): Int }) => p.x_")("x_y_z", "x_z_y") + // By contrast, in this case the user had to type "y_z" manually, so no def string printing just yet + checkExact(completer, "(p: {def x_y_z: Int; def x_z_y(a: String): Int }) => p.x_y_z")() + // Another , Okay, time to print. + checkExact(completer, "(p: {def x_y_z: Int; def x_z_y(a: String): Int }) => p.x_y_z")(EmptyString, "def x_y_z: Int") + + // The def string reconstructs the source-level modifiers (rather than showing the desugarings of vals), + // and performs as-seen-from with respect to the prefix + checkExact(completer, "trait T[A]{ lazy val x_y_z: A }; class C extends T[Int] { x_y_z")() + checkExact(completer, "trait T[A]{ lazy val x_y_z: A }; class C extends T[Int] { x_y_z")(EmptyString, "lazy val x_y_z: Int") + } + + @Test + def treePrint(): Unit = { + val intp = newIMain() + val completer = new PresentationCompilerCompleter(intp) + checkExact(completer, " 1.toHexString //print")(EmptyString, "scala.Predef.intWrapper(1).toHexString // : String") + } + + def checkExact(completer: PresentationCompilerCompleter, before: String, after: String = "")(expected: String*): Unit = { + assertEquals(expected.toSet, completer.complete(before, after).candidates.toSet) + } +} -- cgit v1.2.3 From a67b04079716812004b0d44ad65d48c508cf7d9e Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 3 Sep 2015 12:38:54 +1000 Subject: Add the prefix the autocompletion results (Scope-, TypeMember) This makes life easier for clients of these APIs, we use this to avoid passing this around in the wrapper result `TypeMembers`. --- .../scala/tools/nsc/interactive/CompilerControl.scala | 8 +++++++- src/interactive/scala/tools/nsc/interactive/Global.scala | 11 +++++++---- .../nsc/interpreter/PresentationCompilerCompleter.scala | 15 +++++++-------- .../scala/tools/nsc/interpreter/CompletionTest.scala | 3 +++ 4 files changed, 24 insertions(+), 13 deletions(-) (limited to 'src/interactive') diff --git a/src/interactive/scala/tools/nsc/interactive/CompilerControl.scala b/src/interactive/scala/tools/nsc/interactive/CompilerControl.scala index fc6403afa3..586f011429 100644 --- a/src/interactive/scala/tools/nsc/interactive/CompilerControl.scala +++ b/src/interactive/scala/tools/nsc/interactive/CompilerControl.scala @@ -268,6 +268,7 @@ trait CompilerControl { self: Global => /** Info given for every member found by completion */ abstract class Member { + def prefix: Type val sym: Symbol val tpe: Type val accessible: Boolean @@ -289,6 +290,8 @@ trait CompilerControl { self: Global => accessible: Boolean, inherited: Boolean, viaView: Symbol) extends Member { + // should be a case class parameter, but added as a var instead to preserve compatibility with the IDE + var prefix: Type = NoType override def implicitlyAdded = viaView != NoSymbol } @@ -296,7 +299,10 @@ trait CompilerControl { self: Global => sym: Symbol, tpe: Type, accessible: Boolean, - viaImport: Tree) extends Member + viaImport: Tree) extends Member { + // should be a case class parameter, but added as a var instead to preserve compatibility with the IDE + var prefix: Type = NoType + } // items that get sent to scheduler diff --git a/src/interactive/scala/tools/nsc/interactive/Global.scala b/src/interactive/scala/tools/nsc/interactive/Global.scala index d5caf28991..b9d06b3633 100644 --- a/src/interactive/scala/tools/nsc/interactive/Global.scala +++ b/src/interactive/scala/tools/nsc/interactive/Global.scala @@ -1031,10 +1031,12 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") def addScopeMember(sym: Symbol, pre: Type, viaImport: Tree) = locals.add(sym, pre, implicitlyAdded = false) { (s, st) => // imported val and var are always marked as inaccessible, but they could be accessed through their getters. SI-7995 - if (s.hasGetter) + val member = if (s.hasGetter) new ScopeMember(s, st, context.isAccessible(s.getter, pre, superAccess = false), viaImport) else new ScopeMember(s, st, context.isAccessible(s, pre, superAccess = false), viaImport) + member.prefix = pre + member } def localsToEnclosing() = { enclosing.addNonShadowed(locals) @@ -1101,10 +1103,13 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") def addTypeMember(sym: Symbol, pre: Type, inherited: Boolean, viaView: Symbol) = { val implicitlyAdded = viaView != NoSymbol members.add(sym, pre, implicitlyAdded) { (s, st) => - new TypeMember(s, st, + val result = new TypeMember(s, st, context.isAccessible(if (s.hasGetter) s.getter(s.owner) else s, pre, superAccess && !implicitlyAdded), inherited, viaView) + result.prefix = pre + result + } } @@ -1157,7 +1162,6 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") def matchingResults(matcher: (M, Name) => Boolean = CompletionResult.prefixMatcher): List[M] = { results filter (r => matcher(r, name)) } - def qualifierType: Type = NoType } object CompletionResult { final case class ScopeMembers(positionDelta: Int, results: List[ScopeMember], name: Name) extends CompletionResult { @@ -1165,7 +1169,6 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") } final case class TypeMembers(positionDelta: Int, qualifier: Tree, tree: Tree, results: List[TypeMember], name: Name) extends CompletionResult { type M = TypeMember - override def qualifierType: Type = qualifier.tpe } case object NoResults extends CompletionResult { override def results = Nil diff --git a/src/repl/scala/tools/nsc/interpreter/PresentationCompilerCompleter.scala b/src/repl/scala/tools/nsc/interpreter/PresentationCompilerCompleter.scala index ee901db1a0..3fb4428e76 100644 --- a/src/repl/scala/tools/nsc/interpreter/PresentationCompilerCompleter.scala +++ b/src/repl/scala/tools/nsc/interpreter/PresentationCompilerCompleter.scala @@ -54,28 +54,27 @@ class PresentationCompilerCompleter(intp: IMain) extends Completion with ScalaCo 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 = { + import result.compiler._ + import CompletionResult._ + def defStringCandidates(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 - } + val tp = member.prefix 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() + 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) + if (tabAfterCommonPrefixCompletion || doubleTab) defStringCandidates(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 diff --git a/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala b/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala index 70cb2882ba..8c72ed7b32 100644 --- a/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala +++ b/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala @@ -94,6 +94,9 @@ class CompletionTest { // and performs as-seen-from with respect to the prefix checkExact(completer, "trait T[A]{ lazy val x_y_z: A }; class C extends T[Int] { x_y_z")() checkExact(completer, "trait T[A]{ lazy val x_y_z: A }; class C extends T[Int] { x_y_z")(EmptyString, "lazy val x_y_z: Int") + + checkExact(completer, "trait T[A] { def foo: A }; (t: T[Int]) => t.foo")() + checkExact(completer, "trait T[A] { def foo: A }; (t: T[Int]) => t.foo")(EmptyString, "def foo: Int") } @Test -- cgit v1.2.3 From 0a8c4b263e979a7ed9fc9172ebc5f51e8093f3d5 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 8 Sep 2015 23:32:56 +1000 Subject: Camel Case and JavaBean completion This is just too useful to leave on the cutting room floor. ``` scala> classOf[String].enclo scala> classOf[String].getEnclosing getEnclosingClass getEnclosingConstructor getEnclosingMethod scala> classOf[String].simpl scala> classOf[String].getSimpleName type X = global.TTWD scala> type X = global.TypeTreeWithDeferredRefCheck ``` I revised the API of `matchingResults` as it was clunky to reuse the filtering on accessibility and term/type-ness while providing a custom name matcher. --- .../scala/tools/nsc/interactive/Global.scala | 34 +++++++++++++++++----- .../PresentationCompilerCompleter.scala | 23 ++++++++++----- .../tools/nsc/interpreter/CompletionTest.scala | 11 +++++++ 3 files changed, 53 insertions(+), 15 deletions(-) (limited to 'src/interactive') diff --git a/src/interactive/scala/tools/nsc/interactive/Global.scala b/src/interactive/scala/tools/nsc/interactive/Global.scala index b9d06b3633..703da0b947 100644 --- a/src/interactive/scala/tools/nsc/interactive/Global.scala +++ b/src/interactive/scala/tools/nsc/interactive/Global.scala @@ -1159,8 +1159,14 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") /** The (possibly partial) detected that precedes the cursor */ def name: Name def positionDelta: Int - def matchingResults(matcher: (M, Name) => Boolean = CompletionResult.prefixMatcher): List[M] = { - results filter (r => matcher(r, name)) + def matchingResults(nameMatcher: (Name) => Name => Boolean = entered => candidate => candidate.startsWith(entered)): List[M] = { + val enteredName = if (name == nme.ERROR) nme.EMPTY else name + val matcher = nameMatcher(enteredName) + results filter { (member: Member) => + val symbol = member.sym + def isStable = member.tpe.isStable || member.sym.isStable || member.sym.getterIn(member.sym.owner).isStable + member.accessible && !symbol.isConstructor && (name.isEmpty || matcher(member.sym.name) && (symbol.name.isTermName == name.isTermName || name.isTypeName && isStable)) + } } } object CompletionResult { @@ -1176,12 +1182,24 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") override def positionDelta = 0 } - val prefixMatcher = (member: Member, name: Name) => { - val symbol = member.sym - val prefix = if (name == nme.ERROR) nme.EMPTY else name - def isStable = member.tpe.isStable || member.sym.isStable || member.sym.getterIn(member.sym.owner).isStable - val nameMatches = !symbol.isConstructor && (prefix.isEmpty || symbol.name.startsWith(prefix) && (symbol.name.isTermName == prefix.isTermName || prefix.isTypeName && isStable)) - nameMatches && member.accessible + private val CamelRegex = "([A-Z][^A-Z]*)".r + private def camelComponents(s: String): List[String] = { + CamelRegex.findAllIn("X" + s).toList match { case head :: tail => head.drop(1) :: tail; case Nil => Nil } + } + def camelMatch(entered: Name): Name => Boolean = { + val chunks: List[String] = camelComponents(entered.toString) + + (candidate: Name) => { + val candidateChunks = camelComponents(candidate.toString) + val exactCamelMatch = + (chunks corresponds candidateChunks.take(chunks.length))((x, y) => y.startsWith(x)) + def beanCamelMatch = candidateChunks match { + case ("get" | "is") :: tail => + (chunks corresponds tail.take(chunks.length))((x, y) => y.toLowerCase.startsWith(x.toLowerCase)) + case _ => false + } + exactCamelMatch || beanCamelMatch + } } } diff --git a/src/repl/scala/tools/nsc/interpreter/PresentationCompilerCompleter.scala b/src/repl/scala/tools/nsc/interpreter/PresentationCompilerCompleter.scala index f6b6ff440b..7cff24cfa3 100644 --- a/src/repl/scala/tools/nsc/interpreter/PresentationCompilerCompleter.scala +++ b/src/repl/scala/tools/nsc/interpreter/PresentationCompilerCompleter.scala @@ -77,14 +77,23 @@ class PresentationCompilerCompleter(intp: IMain) extends Completion with ScalaCo 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(matching, r.name) + else if (matching.isEmpty) { + // Lenient matching based on camel case and on eliding JavaBean "get" / "is" boilerplate + val camelMatches: List[Member] = r.matchingResults(CompletionResult.camelMatch(_)).filterNot(isInterpreterWrapperMember) + val memberCompletions = camelMatches.map(_.symNameDropLocal.decoded).distinct.sorted + def allowCompletion = ( + (memberCompletions.size == 1) + || CompletionResult.camelMatch(r.name)(r.name.newName(StringOps.longestCommonPrefix(memberCompletions))) + ) + if (memberCompletions.isEmpty) Completion.NoCandidates + else if (allowCompletion) Candidates(cursor + r.positionDelta, memberCompletions) + else Candidates(cursor, "" :: memberCompletions) + } 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 { - 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.sorted - Candidates(cursor + r.positionDelta, memberCompletions) - } + // regular completion + val memberCompletions: List[String] = matching.map(_.symNameDropLocal.decoded).distinct.sorted + Candidates(cursor + r.positionDelta, memberCompletions) } } lastCommonPrefixCompletion = diff --git a/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala b/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala index 75ab4c605f..a55a3c66c0 100644 --- a/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala +++ b/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala @@ -51,6 +51,17 @@ class CompletionTest { assertEquals(List("prefix_aaa", "prefix_nnn", "prefix_zzz"), completer.complete("""class C { def prefix_nnn = 0; def prefix_zzz = 0; def prefix_aaa = 0; prefix_""").candidates) } + @Test + def camelCompletions(): Unit = { + val intp = newIMain() + val completer = new PresentationCompilerCompleter(intp) + checkExact(completer, "object O { def theCatSatOnTheMat = 1 }; import O._; tCSO")("theCatSatOnTheMat") + checkExact(completer, "object O { def getBlerganator = 1 }; import O._; blerga")("getBlerganator") + checkExact(completer, "object O { def xxxxYyyyyZzzz = 1; def xxxxYyZeee = 1 }; import O._; xYZ")("", "xxxxYyyyyZzzz", "xxxxYyZeee") + checkExact(completer, "object O { def xxxxYyyyyZzzz = 1; def xxxxYyyyyZeee = 1 }; import O._; xYZ")("xxxxYyyyyZzzz", "xxxxYyyyyZeee") + checkExact(completer, "object O { class AbstractMetaFactoryFactory }; new O.AMFF")("AbstractMetaFactoryFactory") + } + @Test def previousLineCompletions(): Unit = { val intp = newIMain() -- cgit v1.2.3 From 2d025fe2d0c9cd0e01e390055b0531166988f901 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 9 Sep 2015 08:52:48 +1000 Subject: Exclude and friends from REPL completion --- src/interactive/scala/tools/nsc/interactive/Global.scala | 4 +++- src/reflect/scala/reflect/internal/Chars.scala | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'src/interactive') diff --git a/src/interactive/scala/tools/nsc/interactive/Global.scala b/src/interactive/scala/tools/nsc/interactive/Global.scala index 703da0b947..ae9d816780 100644 --- a/src/interactive/scala/tools/nsc/interactive/Global.scala +++ b/src/interactive/scala/tools/nsc/interactive/Global.scala @@ -19,6 +19,7 @@ import scala.annotation.{ elidable, tailrec } import scala.language.implicitConversions import scala.tools.nsc.typechecker.Typers import scala.util.control.Breaks._ +import scala.reflect.internal.Chars.isIdentifierStart /** * This trait allows the IDE to have an instance of the PC that @@ -1165,7 +1166,8 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") results filter { (member: Member) => val symbol = member.sym def isStable = member.tpe.isStable || member.sym.isStable || member.sym.getterIn(member.sym.owner).isStable - member.accessible && !symbol.isConstructor && (name.isEmpty || matcher(member.sym.name) && (symbol.name.isTermName == name.isTermName || name.isTypeName && isStable)) + def isJunk = symbol.name.isEmpty || !isIdentifierStart(member.sym.name.charAt(0)) // e.g. + !isJunk && member.accessible && !symbol.isConstructor && (name.isEmpty || matcher(member.sym.name) && (symbol.name.isTermName == name.isTermName || name.isTypeName && isStable)) } } } diff --git a/src/reflect/scala/reflect/internal/Chars.scala b/src/reflect/scala/reflect/internal/Chars.scala index 74413fdaba..0f532a4e57 100644 --- a/src/reflect/scala/reflect/internal/Chars.scala +++ b/src/reflect/scala/reflect/internal/Chars.scala @@ -66,7 +66,7 @@ trait Chars { '0' <= c && c <= '9' || 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' /** Can character start an alphanumeric Scala identifier? */ - def isIdentifierStart(c: Char): Boolean = + def isIdentifierStart(c: Char): Boolean = (c == '_') || (c == '$') || Character.isUnicodeIdentifierStart(c) /** Can character form part of an alphanumeric Scala identifier? */ -- cgit v1.2.3 From 1c42e6bee90b7c444e973726e01b137584dfaad5 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 9 Sep 2015 12:02:31 +1000 Subject: More liberal matching in REPL autocompletion For the SHIFT-impaired: you can just write everything in lowercase, (whisper-case?) and we'll try to DWYM. We treat capital letters that you *do* enter as significant, they can't match a lower case letter in an identifier. Modelled after IntellIJ's completion. I still don't fall into this mode if you enter an exact prefix of a candidate, but we might consider changing that. ``` scala> classOf[String].typ getAnnotationsByType getComponentType getDeclaredAnnotationsByType getTypeName getTypeParameters scala> classOf[String].typN scala> classOf[String].getTypeName res3: String = java.lang.String scala> def foo(s: str scala> def foo(s: String String StringBuffer StringBuilder StringCanBuildFrom StringContext StringFormat StringIndexOutOfBoundsException scala> def foo(s: string scala> def foo(s: String String StringBuffer StringBuilder StringCanBuildFrom StringContext StringFormat StringIndexOutOfBoundsException ``` --- .../scala/tools/nsc/interactive/Global.scala | 31 +++++++++++++++------- .../tools/nsc/interpreter/CompletionTest.scala | 9 +++++++ 2 files changed, 31 insertions(+), 9 deletions(-) (limited to 'src/interactive') diff --git a/src/interactive/scala/tools/nsc/interactive/Global.scala b/src/interactive/scala/tools/nsc/interactive/Global.scala index ae9d816780..6c8719b5eb 100644 --- a/src/interactive/scala/tools/nsc/interactive/Global.scala +++ b/src/interactive/scala/tools/nsc/interactive/Global.scala @@ -1189,18 +1189,31 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") CamelRegex.findAllIn("X" + s).toList match { case head :: tail => head.drop(1) :: tail; case Nil => Nil } } def camelMatch(entered: Name): Name => Boolean = { - val chunks: List[String] = camelComponents(entered.toString) + val enteredS = entered.toString + val enteredLowercaseSet = enteredS.toLowerCase().toSet (candidate: Name) => { - val candidateChunks = camelComponents(candidate.toString) - val exactCamelMatch = - (chunks corresponds candidateChunks.take(chunks.length))((x, y) => y.startsWith(x)) - def beanCamelMatch = candidateChunks match { - case ("get" | "is") :: tail => - (chunks corresponds tail.take(chunks.length))((x, y) => y.toLowerCase.startsWith(x.toLowerCase)) - case _ => false + def candidateChunks = camelComponents(candidate.toString) + // Loosely based on IntelliJ's autocompletion: the user can just write everything in + // lowercase, as we'll let `isl` match `GenIndexedSeqLike` or `isLovely`. + def lenientMatch(entered: String, candidate: List[String], matchCount: Int): Boolean = { + candidate match { + case Nil => entered.isEmpty && matchCount > 0 + case head :: tail => + val enteredAlternatives = Set(entered, entered.capitalize) + head.inits.filter(_.length <= entered.length).exists(init => + enteredAlternatives.exists(entered => + lenientMatch(entered.stripPrefix(init), tail, matchCount + (if (init.isEmpty) 0 else 1)) + ) + ) + } + } + val containsAllEnteredChars = { + // Trying to rule out some candidates quickly before the more expensive `lenientMatch` + val candidateLowercaseSet = candidate.toString.toLowerCase().toSet + enteredLowercaseSet.diff(candidateLowercaseSet).isEmpty } - exactCamelMatch || beanCamelMatch + containsAllEnteredChars && lenientMatch(enteredS, candidateChunks, 0) } } } diff --git a/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala b/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala index a55a3c66c0..534d5ddc3a 100644 --- a/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala +++ b/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala @@ -62,6 +62,15 @@ class CompletionTest { checkExact(completer, "object O { class AbstractMetaFactoryFactory }; new O.AMFF")("AbstractMetaFactoryFactory") } + @Test + def lenientCamelCompletions(): Unit = { + val intp = newIMain() + val completer = new PresentationCompilerCompleter(intp) + checkExact(completer, "object O { def theCatSatOnTheMat = 1 }; import O._; tcso")("theCatSatOnTheMat") + checkExact(completer, "object O { def theCatSatOnTheMat = 1 }; import O._; sotm")("theCatSatOnTheMat") + checkExact(completer, "object O { def theCatSatOnTheMat = 1 }; import O._; TCSOTM")() + } + @Test def previousLineCompletions(): Unit = { val intp = newIMain() -- cgit v1.2.3 From 46211ddd07432f06d125d9926cd8b42bc7bfba40 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 9 Sep 2015 19:38:29 +1000 Subject: Fix completion for synthetic case modules and methods I'm pretty sure the `isSynthetic` call added in 854de25ee6 should instead be `isArtifact`, so that's what I've implemented here. `isSynthetic` used to also filter out error symbols, which are created with the flags `SYNTHETIC | IS_ERROR`. I've added an addition test for `isError`, which was needed to keep the output of `presentation/scope-completion-import` unchanged. The checkfile for `presentation/callcc-interpreter` is modified to add the additional completion proposals: synthetic companion objects. --- src/interactive/scala/tools/nsc/interactive/Global.scala | 2 +- test/files/presentation/callcc-interpreter.check | 11 ++++++++++- test/junit/scala/tools/nsc/interpreter/CompletionTest.scala | 10 ++++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) (limited to 'src/interactive') diff --git a/src/interactive/scala/tools/nsc/interactive/Global.scala b/src/interactive/scala/tools/nsc/interactive/Global.scala index 6c8719b5eb..d6be6331ce 100644 --- a/src/interactive/scala/tools/nsc/interactive/Global.scala +++ b/src/interactive/scala/tools/nsc/interactive/Global.scala @@ -1000,7 +1000,7 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") def add(sym: Symbol, pre: Type, implicitlyAdded: Boolean)(toMember: (Symbol, Type) => M) { if ((sym.isGetter || sym.isSetter) && sym.accessed != NoSymbol) { add(sym.accessed, pre, implicitlyAdded)(toMember) - } else if (!sym.name.decodedName.containsName("$") && !sym.isSynthetic && sym.hasRawInfo) { + } else if (!sym.name.decodedName.containsName("$") && !sym.isError && !sym.isArtifact && sym.hasRawInfo) { val symtpe = pre.memberType(sym) onTypeError ErrorType matching(sym, symtpe, this(sym.name)) match { case Some(m) => diff --git a/test/files/presentation/callcc-interpreter.check b/test/files/presentation/callcc-interpreter.check index 4bf68b3d4e..94a3d64d68 100644 --- a/test/files/presentation/callcc-interpreter.check +++ b/test/files/presentation/callcc-interpreter.check @@ -3,7 +3,7 @@ reload: CallccInterpreter.scala askTypeCompletion at CallccInterpreter.scala(51,34) ================================================================================ [response] askTypeCompletion at (51,34) -retrieved 57 members +retrieved 66 members abstract trait Term extends AnyRef abstract trait Value extends AnyRef case class Add extends callccInterpreter.Term with Product with Serializable @@ -50,6 +50,15 @@ final def synchronized[T0](x$1: T0): T0 final def wait(): Unit final def wait(x$1: Long): Unit final def wait(x$1: Long,x$2: Int): Unit +object Add +object App +object Ccc +object Con +object Fun +object Lam +object M +object Num +object Var private[this] val term0: callccInterpreter.App private[this] val term1: callccInterpreter.App private[this] val term2: callccInterpreter.Add diff --git a/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala b/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala index 5b35c37c7f..177d4922fb 100644 --- a/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala +++ b/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala @@ -81,6 +81,12 @@ class CompletionTest { checkExact(completer, "new C().x_y")("x_y_z") checkExact(completer, "(1 : O.T).toCha")("toChar") + + intp.interpret("case class X_y_z()") + val completer1 = new PresentationCompilerCompleter(intp) + checkExact(completer1, "new X_y_")("X_y_z") + checkExact(completer1, "X_y_")("X_y_z") + checkExact(completer1, "X_y_z.app")("apply") } @Test @@ -137,8 +143,8 @@ class CompletionTest { def firstCompletionWithNoPrefixHidesUniversalMethodsAndExtensionMethods(): Unit = { val intp = newIMain() val completer = new PresentationCompilerCompleter(intp) - checkExact(completer, "case class C(a: Int, b: Int) { this.")("a", "b") - assert(Set("asInstanceOf", "==").diff(completer.complete("case class C(a: Int, b: Int) { this.").candidates.toSet).isEmpty) + checkExact(completer, "class C(val a: Int, val b: Int) { this.")("a", "b") + assert(Set("asInstanceOf", "==").diff(completer.complete("class C(val a: Int, val b: Int) { this.").candidates.toSet).isEmpty) checkExact(completer, "case class D(a: Int, b: Int) { this.a")("a", "asInstanceOf") } -- cgit v1.2.3 From b89874b7c0bb9ad8e2276b652580f3aaf3e5c621 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 9 Sep 2015 21:45:04 +1000 Subject: Fix REPL completion of symbolic identifiers Recover part of the identifier that preceded the cursor from the source, rather than from the name in the `Select` node, which might contains an encoded name that differs in length from the one in source. --- src/interactive/scala/tools/nsc/interactive/Global.scala | 3 +-- test/junit/scala/tools/nsc/interpreter/CompletionTest.scala | 9 ++++++++- 2 files changed, 9 insertions(+), 3 deletions(-) (limited to 'src/interactive') diff --git a/src/interactive/scala/tools/nsc/interactive/Global.scala b/src/interactive/scala/tools/nsc/interactive/Global.scala index d6be6331ce..cc4b845b2c 100644 --- a/src/interactive/scala/tools/nsc/interactive/Global.scala +++ b/src/interactive/scala/tools/nsc/interactive/Global.scala @@ -1223,9 +1223,8 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") def typeCompletions(tree: Tree, qual: Tree, nameStart: Int, name: Name): CompletionResult = { val qualPos = qual.pos val allTypeMembers = typeMembers(qualPos).toList.flatten - val positionDelta: Int = nameStart - pos.start - val subName = name.subName(0, -positionDelta) + val subName: Name = name.newName(new String(pos.source.content, nameStart, pos.start - nameStart)).encodedName CompletionResult.TypeMembers(positionDelta, qual, tree, allTypeMembers, subName) } focus1 match { diff --git a/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala b/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala index 177d4922fb..514f30571e 100644 --- a/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala +++ b/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala @@ -48,7 +48,14 @@ class CompletionTest { checkExact(completer, """class C { asInstanceO""")("asInstanceOf") // Output is sorted - assertEquals(List("prefix_aaa", "prefix_nnn", "prefix_zzz"), completer.complete("""class C { def prefix_nnn = 0; def prefix_zzz = 0; def prefix_aaa = 0; prefix_""").candidates) + assertEquals(List("prefix_aaa", "prefix_nnn", "prefix_zzz"), completer.complete( """class C { def prefix_nnn = 0; def prefix_zzz = 0; def prefix_aaa = 0; prefix_""").candidates) + } + + @Test + def symbolically(): Unit = { + val intp = newIMain() + val completer = new PresentationCompilerCompleter(intp) + checkExact(completer, """class C { def +++(a: Any) = 0; def ---(a: Any) = 0; this.++""")("+++") } @Test -- cgit v1.2.3 From d8d8ba71236bc18d0b4c74f9ebead2973dc6e435 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 21 Sep 2015 21:46:07 +1000 Subject: Remove used param and document positionDelta --- src/interactive/scala/tools/nsc/interactive/Global.scala | 11 ++++++----- src/repl/scala/tools/nsc/interpreter/IMain.scala | 2 +- .../tools/nsc/interpreter/PresentationCompilerCompleter.scala | 4 ++-- 3 files changed, 9 insertions(+), 8 deletions(-) (limited to 'src/interactive') diff --git a/src/interactive/scala/tools/nsc/interactive/Global.scala b/src/interactive/scala/tools/nsc/interactive/Global.scala index cc4b845b2c..6600fea2d8 100644 --- a/src/interactive/scala/tools/nsc/interactive/Global.scala +++ b/src/interactive/scala/tools/nsc/interactive/Global.scala @@ -1157,8 +1157,9 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") sealed abstract class CompletionResult { type M <: Member def results: List[M] - /** The (possibly partial) detected that precedes the cursor */ + /** The (possibly partial) name detected that precedes the cursor */ def name: Name + /** Cursor Offset - positionDelta == position of the start of the name */ def positionDelta: Int def matchingResults(nameMatcher: (Name) => Name => Boolean = entered => candidate => candidate.startsWith(entered)): List[M] = { val enteredName = if (name == nme.ERROR) nme.EMPTY else name @@ -1223,7 +1224,7 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") def typeCompletions(tree: Tree, qual: Tree, nameStart: Int, name: Name): CompletionResult = { val qualPos = qual.pos val allTypeMembers = typeMembers(qualPos).toList.flatten - val positionDelta: Int = nameStart - pos.start + val positionDelta: Int = pos.start - nameStart val subName: Name = name.newName(new String(pos.source.content, nameStart, pos.start - nameStart)).encodedName CompletionResult.TypeMembers(positionDelta, qual, tree, allTypeMembers, subName) } @@ -1231,7 +1232,7 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") case imp@Import(i @ Ident(name), head :: Nil) if head.name == nme.ERROR => val allMembers = scopeMembers(pos) val nameStart = i.pos.start - val positionDelta: Int = nameStart - pos.start + val positionDelta: Int = pos.start - nameStart val subName = name.subName(0, pos.start - i.pos.start) CompletionResult.ScopeMembers(positionDelta, allMembers, subName) case imp@Import(qual, selectors) => @@ -1250,8 +1251,8 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") typeCompletions(sel, qual, nameStart, name) case Ident(name) => val allMembers = scopeMembers(pos) - val positionDelta: Int = focus1.pos.start - pos.start - val subName = name.subName(0, -positionDelta) + val positionDelta: Int = pos.start - focus1.pos.start + val subName = name.subName(0, positionDelta) CompletionResult.ScopeMembers(positionDelta, allMembers, subName) case _ => CompletionResult.NoResults diff --git a/src/repl/scala/tools/nsc/interpreter/IMain.scala b/src/repl/scala/tools/nsc/interpreter/IMain.scala index f6f391600b..ef6ab4063a 100644 --- a/src/repl/scala/tools/nsc/interpreter/IMain.scala +++ b/src/repl/scala/tools/nsc/interpreter/IMain.scala @@ -1180,7 +1180,7 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set case class Incomplete(trees: List[Tree]) extends Result case class Success(trees: List[Tree]) extends Result - def apply(line: String, forPresentation: Boolean = false): Result = debugging(s"""parse("$line")""") { + def apply(line: String): Result = debugging(s"""parse("$line")""") { var isIncomplete = false def parse = { reporter.reset() diff --git a/src/repl/scala/tools/nsc/interpreter/PresentationCompilerCompleter.scala b/src/repl/scala/tools/nsc/interpreter/PresentationCompilerCompleter.scala index 01735aed3a..0fb3236966 100644 --- a/src/repl/scala/tools/nsc/interpreter/PresentationCompilerCompleter.scala +++ b/src/repl/scala/tools/nsc/interpreter/PresentationCompilerCompleter.scala @@ -97,14 +97,14 @@ class PresentationCompilerCompleter(intp: IMain) extends Completion with ScalaCo || CompletionResult.camelMatch(r.name)(r.name.newName(StringOps.longestCommonPrefix(memberCompletions))) ) if (memberCompletions.isEmpty) Completion.NoCandidates - else if (allowCompletion) Candidates(cursor + r.positionDelta, memberCompletions) + else if (allowCompletion) Candidates(cursor - r.positionDelta, memberCompletions) else Candidates(cursor, "" :: memberCompletions) } 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.sorted - Candidates(cursor + r.positionDelta, memberCompletions) + Candidates(cursor - r.positionDelta, memberCompletions) } } lastCommonPrefixCompletion = -- cgit v1.2.3