/
WorkspaceSymbolSearch.scala
128 lines (115 loc) Β· 3.48 KB
/
WorkspaceSymbolSearch.scala
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
package scala.meta.internal.pc
import java.nio.file.Path
import scala.annotation.tailrec
import scala.util.control.NonFatal
import scala.meta.pc.SymbolSearchVisitor
import org.eclipse.{lsp4j => l}
trait WorkspaceSymbolSearch { compiler: MetalsGlobal =>
def searchOutline(
visitMember: Symbol => Boolean,
query: String
) {
def traverseUnit(unit: RichCompilationUnit) = {
@tailrec
def loop(trees: List[Tree]): Unit = {
trees match {
case Nil =>
case tree :: tail =>
val sym = tree.symbol
def matches = if (sym.isType)
CompletionFuzzy.matchesSubCharacters(query, sym.name.toString())
else CompletionFuzzy.matches(query, sym.name.toString())
if (sym != null && sym.exists && matches) {
try {
visitMember(sym)
} catch {
case _: Throwable =>
// with outline compiler there might be situations when things fail
}
}
loop(tree.children ++ tail)
}
}
loop(List(unit.body))
}
compiler.richCompilationCache.values.foreach(traverseUnit)
}
class CompilerSearchVisitor(
context: Context,
visitMember: Symbol => Boolean
) extends SymbolSearchVisitor {
def visit(top: SymbolSearchCandidate): Int = {
var added = 0
for {
sym <- loadSymbolFromClassfile(top)
if context.lookupSymbol(sym.name, _ => true).symbol != sym
} {
if (visitMember(sym)) {
added += 1
}
}
added
}
def visitClassfile(pkg: String, filename: String): Int = {
visit(SymbolSearchCandidate.Classfile(pkg, filename))
}
def visitWorkspaceSymbol(
path: Path,
symbol: String,
kind: l.SymbolKind,
range: l.Range
): Int = {
visit(SymbolSearchCandidate.Workspace(symbol, path))
}
def shouldVisitPackage(pkg: String): Boolean =
packageSymbolFromString(pkg).isDefined
override def isCancelled: Boolean = {
false
}
}
private def loadSymbolFromClassfile(
classfile: SymbolSearchCandidate
): List[Symbol] = {
def isAccessible(sym: Symbol): Boolean = {
sym != NoSymbol && {
sym.info // needed to fill complete symbol
sym.isPublic
}
}
try {
classfile match {
case SymbolSearchCandidate.Classfile(pkgString, filename) =>
val pkg = packageSymbolFromString(pkgString).getOrElse(
throw new NoSuchElementException(pkgString)
)
val names = filename
.stripSuffix(".class")
.split('$')
.iterator
.filterNot(_.isEmpty)
.toList
val members = names.foldLeft(List[Symbol](pkg)) {
case (accum, name) =>
accum.flatMap { sym =>
if (!isAccessible(sym) || !sym.isModuleOrModuleClass) Nil
else {
sym.info.member(TermName(name)) ::
sym.info.member(TypeName(name)) ::
Nil
}
}
}
members.filter(sym => isAccessible(sym))
case SymbolSearchCandidate.Workspace(symbol, path)
if !compiler.isOutlinedFile(path) =>
val gsym = inverseSemanticdbSymbol(symbol)
if (isAccessible(gsym)) gsym :: Nil
else Nil
case _ =>
Nil
}
} catch {
case NonFatal(_) => Nil
}
}
}