1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
|
/* 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.Candidates
import scala.util.control.NonFatal
class PresentationCompilerCompleter(intp: IMain) extends Completion {
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 }
// 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._
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 = member.prefix memberType sym
sugared.defStringSeenAs(tp)
}
Candidates(cursor, "" :: defStrings.distinct)
}
val found = result.completionsAt(cursor) match {
case NoResults => Completion.NoCandidates
case r =>
def shouldHide(m: Member): Boolean = {
val isUniversal = definitions.isUniversalMember(m.sym)
def viaUniversalExtensionMethod = m match {
case t: TypeMember if t.implicitlyAdded && t.viaView.info.params.head.info.bounds.isEmptyBounds => true
case _ => false
}
(
isUniversal && nme.isReplWrapperName(m.prefix.typeSymbol.name)
|| isUniversal && tabCount == 0 && r.name.isEmpty
|| viaUniversalExtensionMethod && tabCount == 0 && r.name.isEmpty
)
}
val matching = r.matchingResults().filterNot(shouldHide)
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(shouldHide)
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 {
// regular completion
val memberCompletions: List[String] = matching.map(_.symNameDropLocal.decoded).distinct.sorted
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)
}
|