forked from scalameta/metals
-
Notifications
You must be signed in to change notification settings - Fork 2
/
WorkspaceSearchVisitor.scala
152 lines (142 loc) Β· 4.87 KB
/
WorkspaceSearchVisitor.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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package scala.meta.internal.metals
import java.nio.file.Path
import java.{util => ju}
import scala.collection.mutable
import scala.meta.internal.metals.MetalsEnrichments._
import scala.meta.internal.mtags.GlobalSymbolIndex
import scala.meta.internal.mtags.Symbol
import scala.meta.internal.mtags.SymbolDefinition
import scala.meta.internal.semanticdb.Scala.Descriptor
import scala.meta.internal.semanticdb.Scala.DescriptorParser
import scala.meta.internal.semanticdb.Scala.Symbols
import scala.meta.io.AbsolutePath
import scala.meta.pc.SymbolSearchVisitor
import org.eclipse.lsp4j.SymbolKind
import org.eclipse.lsp4j.jsonrpc.CancelChecker
import org.eclipse.{lsp4j => l}
/**
* A symbol search visitor for `workspace/symbol`.
*
* - workspace symbols are converted directly to l.SymbolInformation
* - classpath symbols are converted into "goto definition" requests,
* which creates files on disk, and then into l.SymbolInformation.
*/
class WorkspaceSearchVisitor(
workspace: AbsolutePath,
query: WorkspaceSymbolQuery,
token: CancelChecker,
index: GlobalSymbolIndex,
saveClassFileToDisk: Boolean,
)(implicit rc: ReportContext)
extends SymbolSearchVisitor {
private val fromWorkspace = new ju.ArrayList[l.SymbolInformation]()
private val fromClasspath = new ju.ArrayList[l.SymbolInformation]()
private val bufferedClasspath = new ju.ArrayList[(String, String)]()
def allResults(): Seq[l.SymbolInformation] = {
if (fromWorkspace.isEmpty) {
bufferedClasspath.forEach { case (pkg, name) =>
expandClassfile(pkg, name)
}
}
fromWorkspace.sort(byNameLength)
fromClasspath.sort(byNameLength)
val result = new ju.ArrayList[l.SymbolInformation]()
result.addAll(fromWorkspace)
result.addAll(fromClasspath)
if (!bufferedClasspath.isEmpty && fromClasspath.isEmpty) {
val dependencies = workspace.resolve(Directories.workspaceSymbol)
if (!dependencies.isFile) {
dependencies.writeText(Messages.WorkspaceSymbolDependencies.title)
}
result.add(
new l.SymbolInformation(
Messages.WorkspaceSymbolDependencies.title,
// NOTE(olafur) The "Event" symbol kind is arbitrarily picked, in VS
// Code its icon is a yellow lightning which makes it similar but
// distinct enough from the regular results. I tried the "File" kind
// but found the icon in VS Code to be ugly and its white color
// attracted too much attention.
SymbolKind.Event,
new l.Location(
dependencies.toURI.toString(),
new l.Range(new l.Position(0, 0), new l.Position(0, 0)),
),
)
)
}
result.asScala.toSeq
}
private val byNameLength = new ju.Comparator[l.SymbolInformation] {
def compare(x: l.SymbolInformation, y: l.SymbolInformation): Int = {
Integer.compare(x.getName().length(), y.getName().length())
}
}
private val isVisited: mutable.Set[AbsolutePath] =
mutable.Set.empty[AbsolutePath]
private def definition(
pkg: String,
filename: String,
index: GlobalSymbolIndex,
): Option[SymbolDefinition] = {
val nme = Classfile.name(filename)
val tpe = Symbol(Symbols.Global(pkg, Descriptor.Type(nme)))
val forTpe = index.definitions(tpe)
val defs = if (forTpe.isEmpty) {
val term = Symbol(Symbols.Global(pkg, Descriptor.Term(nme)))
index.definitions(term)
} else forTpe
defs.sortBy(_.path.toURI.toString).headOption
}
override def shouldVisitPackage(pkg: String): Boolean = true
override def visitWorkspaceSymbol(
path: Path,
symbol: String,
kind: SymbolKind,
range: l.Range,
): Int = {
val (desc, owner) = DescriptorParser(symbol)
fromWorkspace.add(
new l.SymbolInformation(
desc.name.value,
kind,
new l.Location(path.toUri.toString, range),
owner.replace('/', '.'),
)
)
1
}
override def visitClassfile(pkg: String, filename: String): Int = {
if (fromWorkspace.isEmpty || query.isClasspath) {
expandClassfile(pkg, filename)
} else {
bufferedClasspath.add(pkg -> filename)
1
}
}
override def isCancelled: Boolean = token.isCancelled
private def expandClassfile(pkg: String, filename: String): Int = {
var isHit = false
for {
defn <- definition(pkg, filename, index)
if !isVisited(defn.path)
} {
isVisited += defn.path
val input = defn.path.toInput
SemanticdbDefinition.foreach(
input,
defn.dialect,
includeMembers = false,
) { semanticDefn =>
if (query.matches(semanticDefn.info)) {
val path =
if (saveClassFileToDisk) defn.path.toFileOnDisk(workspace)
else defn.path
val uri = path.toURI.toString
fromClasspath.add(semanticDefn.toLsp(uri))
isHit = true
}
}
}
if (isHit) 1 else 0
}
}