/
InteractiveSemanticdbs.scala
144 lines (128 loc) Β· 4.47 KB
/
InteractiveSemanticdbs.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
package scala.meta.internal.metals
import java.nio.charset.Charset
import java.util.Collections
import scala.util.Success
import scala.util.Try
import scala.meta.internal.metals.MetalsEnrichments._
import scala.meta.internal.mtags.MD5
import scala.meta.internal.mtags.Semanticdbs
import scala.meta.internal.mtags.Shebang
import scala.meta.internal.mtags.TextDocumentLookup
import scala.meta.internal.{semanticdb => s}
import scala.meta.io.AbsolutePath
/**
* Produces SemanticDBs on-demand by using the presentation compiler.
*
* Only used to provide navigation inside external library sources, not used to compile
* workspace sources.
*
* Uses persistent storage to keep track of what external source file is associated
* with what build target (to determine classpath and compiler options).
*/
final class InteractiveSemanticdbs(
workspace: AbsolutePath,
buildTargets: BuildTargets,
charset: Charset,
tables: Tables,
compilers: () => Compilers,
clientConfig: ClientConfiguration,
semanticdbIndexer: () => SemanticdbIndexer,
javaInteractiveSemanticdb: Option[JavaInteractiveSemanticdb],
buffers: Buffers,
) extends Cancelable
with Semanticdbs {
private val textDocumentCache = Collections.synchronizedMap(
new java.util.HashMap[AbsolutePath, s.TextDocument]()
)
def reset(): Unit = {
textDocumentCache.clear()
}
override def cancel(): Unit = {
reset()
}
override def textDocument(
source: AbsolutePath
): TextDocumentLookup = textDocument(source, unsavedContents = None)
def onClose(path: AbsolutePath): Unit = {
textDocumentCache.remove(path)
}
def textDocument(
source: AbsolutePath,
unsavedContents: Option[String],
): TextDocumentLookup = {
def doesNotBelongToBuildTarget = buildTargets.inverseSources(source).isEmpty
lazy val sourceText =
buffers.get(source).orElse {
if (source.exists) Some(source.readText(charset))
else None
}
def shouldTryCalculateInteractiveSemanticdb = {
source.isLocalFileSystem(workspace) && (
unsavedContents.isDefined ||
source.isInReadonlyDirectory(workspace) || // dependencies
source.isSbt || // sbt files
source.isWorksheet || // worksheets
doesNotBelongToBuildTarget || // standalone files
sourceText.exists(
_.startsWith(Shebang.shebang)
) // starts with shebang
) || source.isJarFileSystem // dependencies
}
// anything aside from `*.scala`, `*.sbt`, `*.sc`, `*.java` file
def isExcludedFile = !source.isScalaFilename && !source.isJavaFilename
if (isExcludedFile || !shouldTryCalculateInteractiveSemanticdb) {
TextDocumentLookup.NotFound(source)
} else {
val result = textDocumentCache.compute(
source,
(path, existingDoc) => {
unsavedContents.orElse(sourceText) match {
case None => null
case Some(text) =>
val adjustedText =
if (text.startsWith(Shebang.shebang))
"//" + text.drop(2)
else text
val sha = MD5.compute(adjustedText)
if (existingDoc == null || existingDoc.md5 != sha) {
Try(compile(path, adjustedText)) match {
case Success(doc) if doc != null =>
if (!source.isDependencySource(workspace))
semanticdbIndexer().onChange(source, doc)
doc
case _ => null
}
} else
existingDoc
}
},
)
TextDocumentLookup.fromOption(source, Option(result))
}
}
/**
* Persist relationship between this dependency source and its enclosing build target
*/
def didDefinition(source: AbsolutePath, result: DefinitionResult): Unit = {
for {
destination <- result.definition
if destination.isDependencySource(workspace)
buildTarget = buildTargets.inverseSources(source)
} {
if (source.isWorksheet) {
tables.worksheetSources.setWorksheet(destination, source)
} else {
buildTarget.foreach { target =>
tables.dependencySources.setBuildTarget(destination, target)
}
}
}
}
private def compile(source: AbsolutePath, text: String): s.TextDocument = {
if (source.isJavaFilename)
javaInteractiveSemanticdb.fold(s.TextDocument())(
_.textDocument(source, text)
)
else compilers().semanticdbTextDocument(source, text)
}
}