Skip to content

Commit d5b0aab

Browse files
authored
Merge pull request #323 from daplf/setup-incremental-indexing
Setup incremental indexing on file changes
2 parents 1fe7172 + 98336de commit d5b0aab

File tree

4 files changed

+219
-78
lines changed

4 files changed

+219
-78
lines changed

server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ class KotlinLanguageServer : LanguageServer, LanguageClientAware, Closeable {
125125
}
126126
}
127127

128+
textDocuments.lintAll()
129+
128130
val serverInfo = ServerInfo("Kotlin Language Server", VERSION)
129131

130132
InitializeResult(serverCapabilities, serverInfo)

server/src/main/kotlin/org/javacs/kt/KotlinTextDocumentService.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,13 @@ class KotlinTextDocumentService(
260260
debounceLint = Debouncer(Duration.ofMillis(config.linting.debounceTime))
261261
}
262262

263+
fun lintAll() {
264+
debounceLint.submitImmediately {
265+
sp.compileAllFiles()
266+
sp.refreshDependencyIndexes()
267+
}
268+
}
269+
263270
private fun clearLint(): List<URI> {
264271
val result = lintTodo.toList()
265272
lintTodo.clear()

server/src/main/kotlin/org/javacs/kt/SourcePath.kt

Lines changed: 73 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,14 @@ import org.javacs.kt.util.filePath
77
import org.javacs.kt.util.describeURI
88
import org.javacs.kt.index.SymbolIndex
99
import org.javacs.kt.progress.Progress
10-
import org.javacs.kt.IndexingConfiguration
1110
import com.intellij.lang.Language
12-
import com.intellij.psi.PsiFile
13-
import com.intellij.openapi.fileTypes.FileType
14-
import com.intellij.openapi.fileTypes.LanguageFileType
1511
import org.jetbrains.kotlin.container.ComponentProvider
1612
import org.jetbrains.kotlin.container.getService
1713
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
1814
import org.jetbrains.kotlin.psi.KtFile
1915
import org.jetbrains.kotlin.resolve.BindingContext
2016
import org.jetbrains.kotlin.resolve.CompositeBindingContext
17+
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
2118
import kotlin.concurrent.withLock
2219
import java.nio.file.Path
2320
import java.nio.file.Paths
@@ -33,7 +30,6 @@ class SourcePath(
3330
private val parseDataWriteLock = ReentrantLock()
3431

3532
private val indexAsync = AsyncExecutor()
36-
private var indexInitialized: Boolean = false
3733
var indexEnabled: Boolean by indexingConfig::enabled
3834
val index = SymbolIndex()
3935

@@ -99,14 +95,16 @@ class SourcePath(
9995
private fun doCompile() {
10096
LOG.debug("Compiling {}", path?.fileName)
10197

98+
val oldFile = clone()
99+
102100
val (context, container) = cp.compiler.compileKtFile(parsed!!, allIncludingThis(), kind)
103101
parseDataWriteLock.withLock {
104102
compiledContext = context
105103
compiledContainer = container
106104
compiledFile = parsed
107105
}
108106

109-
initializeIndexAsyncIfNeeded(container)
107+
refreshWorkspaceIndexes(listOfNotNull(oldFile), listOfNotNull(this))
110108
}
111109

112110
private fun doCompileIfChanged() {
@@ -125,6 +123,9 @@ class SourcePath(
125123
if (isTemporary) (all().asSequence() + sequenceOf(parsed!!)).toList()
126124
else all()
127125
}
126+
127+
// Creates a shallow copy
128+
fun clone(): SourceFile = SourceFile(uri, content, path, parsed, compiledFile, compiledContext, compiledContainer, language, isTemporary)
128129
}
129130

130131
private fun sourceFile(uri: URI): SourceFile {
@@ -161,6 +162,8 @@ class SourcePath(
161162
}
162163

163164
fun delete(uri: URI) {
165+
files[uri]?.let { refreshWorkspaceIndexes(listOf(it), listOf()) }
166+
164167
files.remove(uri)
165168
}
166169

@@ -195,7 +198,20 @@ class SourcePath(
195198
// Compile changed files
196199
fun compileAndUpdate(changed: List<SourceFile>, kind: CompilationKind): BindingContext? {
197200
if (changed.isEmpty()) return null
201+
202+
// Get clones of the old files, so we can remove the old declarations from the index
203+
val oldFiles = changed.mapNotNull {
204+
if (it.compiledFile?.text != it.content || it.parsed?.text != it.content) {
205+
it.clone()
206+
} else {
207+
null
208+
}
209+
}
210+
211+
// Parse the files that have changed
198212
val parse = changed.associateWith { it.apply { parseIfChanged() }.parsed!! }
213+
214+
// Get all the files. This will parse them if they changed
199215
val allFiles = all()
200216
beforeCompileCallback.invoke()
201217
val (context, container) = cp.compiler.compileKtFiles(parse.values, allFiles, kind)
@@ -214,7 +230,7 @@ class SourcePath(
214230

215231
// Only index normal files, not build files
216232
if (kind == CompilationKind.DEFAULT) {
217-
initializeIndexAsyncIfNeeded(container)
233+
refreshWorkspaceIndexes(oldFiles, parse.keys.toList())
218234
}
219235

220236
return context
@@ -230,18 +246,62 @@ class SourcePath(
230246
return CompositeBindingContext.create(combined)
231247
}
232248

249+
fun compileAllFiles() {
250+
// TODO: Investigate the possibility of compiling all files at once, instead of iterating here
251+
// At the moment, compiling all files at once sometimes leads to an internal error from the TopDownAnalyzer
252+
files.keys.forEach {
253+
compileFiles(listOf(it))
254+
}
255+
}
256+
257+
fun refreshDependencyIndexes() {
258+
compileAllFiles()
259+
260+
val container = files.values.first { it.compiledContainer != null }.compiledContainer
261+
if (container != null) {
262+
refreshDependencyIndexes(container)
263+
}
264+
}
265+
233266
/**
234-
* Initialized the symbol index asynchronously, if not
235-
* already done.
267+
* Refreshes the indexes. If already done, refreshes only the declarations in the files that were changed.
236268
*/
237-
private fun initializeIndexAsyncIfNeeded(container: ComponentProvider) = indexAsync.execute {
238-
if (indexEnabled && !indexInitialized) {
239-
indexInitialized = true
269+
private fun refreshWorkspaceIndexes(oldFiles: List<SourceFile>, newFiles: List<SourceFile>) = indexAsync.execute {
270+
if (indexEnabled) {
271+
val oldDeclarations = getDeclarationDescriptors(oldFiles)
272+
val newDeclarations = getDeclarationDescriptors(newFiles)
273+
274+
// Index the new declarations in the Kotlin source files that were just compiled, removing the old ones
275+
index.updateIndexes(oldDeclarations, newDeclarations)
276+
}
277+
}
278+
279+
/**
280+
* Refreshes the indexes. If already done, refreshes only the declarations in the files that were changed.
281+
*/
282+
private fun refreshDependencyIndexes(container: ComponentProvider) = indexAsync.execute {
283+
if (indexEnabled) {
240284
val module = container.getService(ModuleDescriptor::class.java)
241-
index.refresh(module)
285+
val declarations = getDeclarationDescriptors(files.values)
286+
index.refresh(module, declarations)
242287
}
243288
}
244289

290+
// Gets all the declaration descriptors for the collection of files
291+
private fun getDeclarationDescriptors(files: Collection<SourceFile>) =
292+
files.flatMap { file ->
293+
val compiledFile = file.compiledFile ?: file.parsed
294+
val compiledContainer = file.compiledContainer
295+
if (compiledFile != null && compiledContainer != null) {
296+
val module = compiledContainer.getService(ModuleDescriptor::class.java)
297+
module.getPackage(compiledFile.packageFqName).memberScope.getContributedDescriptors(
298+
DescriptorKindFilter.ALL
299+
) { name -> compiledFile.declarations.map { it.name }.contains(name.toString()) }
300+
} else {
301+
listOf()
302+
}
303+
}.asSequence()
304+
245305
/**
246306
* Recompiles all source files that are initialized.
247307
*/

0 commit comments

Comments
 (0)