Skip to content

Commit

Permalink
Merge pull request #337 from daplf/dependency-caching
Browse files Browse the repository at this point in the history
Cache dependencies
  • Loading branch information
fwcd authored Aug 13, 2023
2 parents 3f39416 + 8731d73 commit 394c6a3
Show file tree
Hide file tree
Showing 17 changed files with 313 additions and 21 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ There is an extensive suite of behavioral [tests](server/src/test/kotlin/org/jav

The Kotlin language server supports some non-standard requests through LSP. See [KotlinProtocolExtensions](server/src/main/kotlin/org/javacs/kt/KotlinProtocolExtensions.kt) for a description of the interface. The general syntax for these methods is `kotlin/someCustomMethod`.

## Initialization Options

The Kotlin language server supports some custom initialization options via the `initializationOptions` property in the `initialize` request parameters. See `InitializationOptions` in [Configuration](server/src/main/kotlin/org/javacs/kt/Configuration.kt) for a list of supported properties.

## Features

### Autocomplete
Expand Down
1 change: 1 addition & 0 deletions gradle/platform/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@ dependencies {
api("org.jetbrains.kotlin:kotlin-scripting-jvm-host:$kotlinVersion")
api("org.jetbrains.kotlin:kotlin-scripting-jvm-host:$kotlinVersion")
api("org.openjdk.jmh:jmh-generator-annprocess:1.20")
api("org.xerial:sqlite-jdbc:3.41.2.1")
}
}
1 change: 1 addition & 0 deletions server/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ dependencies {
implementation("com.h2database:h2")
implementation("com.github.fwcd.ktfmt:ktfmt")
implementation("com.beust:jcommander")
implementation("org.xerial:sqlite-jdbc")

testImplementation("org.hamcrest:hamcrest-all")
testImplementation("junit:junit")
Expand Down
5 changes: 3 additions & 2 deletions server/src/main/kotlin/org/javacs/kt/CompilerClassPath.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.javacs.kt
import org.javacs.kt.classpath.ClassPathEntry
import org.javacs.kt.classpath.defaultClassPathResolver
import org.javacs.kt.compiler.Compiler
import org.javacs.kt.database.DatabaseService
import org.javacs.kt.util.AsyncExecutor
import java.io.Closeable
import java.io.File
Expand All @@ -14,7 +15,7 @@ import java.nio.file.Path
* Manages the class path (compiled JARs, etc), the Java source path
* and the compiler. Note that Kotlin sources are stored in SourcePath.
*/
class CompilerClassPath(private val config: CompilerConfiguration) : Closeable {
class CompilerClassPath(private val config: CompilerConfiguration, private val databaseService: DatabaseService) : Closeable {
val workspaceRoots = mutableSetOf<Path>()

private val javaSourcePath = mutableSetOf<Path>()
Expand All @@ -39,7 +40,7 @@ class CompilerClassPath(private val config: CompilerConfiguration) : Closeable {
updateJavaSourcePath: Boolean = true
): Boolean {
// TODO: Fetch class path and build script class path concurrently (and asynchronously)
val resolver = defaultClassPathResolver(workspaceRoots)
val resolver = defaultClassPathResolver(workspaceRoots, databaseService.db)
var refreshCompiler = updateJavaSourcePath

if (updateClassPath) {
Expand Down
41 changes: 41 additions & 0 deletions server/src/main/kotlin/org/javacs/kt/Configuration.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
package org.javacs.kt

import com.google.gson.GsonBuilder
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonParseException
import org.eclipse.lsp4j.InitializeParams
import java.lang.reflect.Type
import java.nio.file.InvalidPathException
import java.nio.file.Path
import java.nio.file.Paths

public data class SnippetsConfiguration(
/** Whether code completion should return VSCode-style snippets. */
var enabled: Boolean = true
Expand Down Expand Up @@ -35,6 +46,36 @@ public data class ExternalSourcesConfiguration(
var autoConvertToKotlin: Boolean = false
)


fun getStoragePath(params: InitializeParams): Path? {
params.initializationOptions?.let { initializationOptions ->
val gson = GsonBuilder().registerTypeHierarchyAdapter(Path::class.java, GsonPathConverter()).create()
val options = gson.fromJson(initializationOptions as JsonElement, InitializationOptions::class.java)

return options?.storagePath
}

return null
}

data class InitializationOptions(
// A path to a directory used by the language server to store data. Used for caching purposes.
val storagePath: Path?
)

class GsonPathConverter : JsonDeserializer<Path?> {

@Throws(JsonParseException::class)
override fun deserialize(json: JsonElement, type: Type?, context: JsonDeserializationContext?): Path? {
return try {
Paths.get(json.asString)
} catch (ex: InvalidPathException) {
LOG.printStackTrace(ex)
null
}
}
}

public data class Configuration(
val compiler: CompilerConfiguration = CompilerConfiguration(),
val completion: CompletionConfiguration = CompletionConfiguration(),
Expand Down
18 changes: 12 additions & 6 deletions server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,28 @@ import org.eclipse.lsp4j.services.LanguageClientAware
import org.eclipse.lsp4j.services.LanguageServer
import org.eclipse.lsp4j.services.NotebookDocumentService
import org.javacs.kt.command.ALL_COMMANDS
import org.javacs.kt.externalsources.*
import org.javacs.kt.database.DatabaseService
import org.javacs.kt.progress.LanguageClientProgress
import org.javacs.kt.progress.Progress
import org.javacs.kt.semantictokens.semanticTokensLegend
import org.javacs.kt.util.AsyncExecutor
import org.javacs.kt.util.TemporaryDirectory
import org.javacs.kt.util.parseURI
import org.javacs.kt.progress.Progress
import org.javacs.kt.progress.LanguageClientProgress
import org.javacs.kt.semantictokens.semanticTokensLegend
import org.javacs.kt.externalsources.*
import org.javacs.kt.index.SymbolIndex
import java.io.Closeable
import java.nio.file.Paths
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletableFuture.completedFuture

class KotlinLanguageServer : LanguageServer, LanguageClientAware, Closeable {
val config = Configuration()
val classPath = CompilerClassPath(config.compiler)
val databaseService = DatabaseService()
val classPath = CompilerClassPath(config.compiler, databaseService)

private val tempDirectory = TemporaryDirectory()
private val uriContentProvider = URIContentProvider(ClassContentProvider(config.externalSources, classPath, tempDirectory, CompositeSourceArchiveProvider(JdkSourceArchiveProvider(classPath), ClassPathSourceArchiveProvider(classPath))))
val sourcePath = SourcePath(classPath, uriContentProvider, config.indexing)
val sourcePath = SourcePath(classPath, uriContentProvider, config.indexing, databaseService)
val sourceFiles = SourceFiles(sourcePath, uriContentProvider)

private val textDocuments = KotlinTextDocumentService(sourceFiles, sourcePath, config, tempDirectory, uriContentProvider, classPath)
Expand Down Expand Up @@ -89,6 +92,9 @@ class KotlinLanguageServer : LanguageServer, LanguageClientAware, Closeable {
serverCapabilities.executeCommandProvider = ExecuteCommandOptions(ALL_COMMANDS)
serverCapabilities.documentHighlightProvider = Either.forLeft(true)

val storagePath = getStoragePath(params)
databaseService.setup(storagePath)

val clientCapabilities = params.capabilities
config.completion.snippets.enabled = clientCapabilities?.textDocument?.completion?.completionItem?.snippetSupport ?: false

Expand Down
6 changes: 4 additions & 2 deletions server/src/main/kotlin/org/javacs/kt/SourcePath.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import org.javacs.kt.util.describeURI
import org.javacs.kt.index.SymbolIndex
import org.javacs.kt.progress.Progress
import com.intellij.lang.Language
import org.javacs.kt.database.DatabaseService
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.BindingContext
Expand All @@ -22,14 +23,15 @@ import java.util.concurrent.locks.ReentrantLock
class SourcePath(
private val cp: CompilerClassPath,
private val contentProvider: URIContentProvider,
private val indexingConfig: IndexingConfiguration
private val indexingConfig: IndexingConfiguration,
private val databaseService: DatabaseService
) {
private val files = mutableMapOf<URI, SourceFile>()
private val parseDataWriteLock = ReentrantLock()

private val indexAsync = AsyncExecutor()
var indexEnabled: Boolean by indexingConfig::enabled
val index = SymbolIndex()
val index = SymbolIndex(databaseService)

var beforeCompileCallback: () -> Unit = {}

Expand Down
14 changes: 11 additions & 3 deletions server/src/main/kotlin/org/javacs/kt/index/SymbolIndex.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.name.FqName
import org.javacs.kt.LOG
import org.javacs.kt.database.DatabaseService
import org.javacs.kt.progress.Progress
import org.jetbrains.exposed.dao.IntEntity
import org.jetbrains.exposed.dao.IntEntityClass
Expand Down Expand Up @@ -80,14 +81,18 @@ class PositionEntity(id: EntityID<Int>) : IntEntity(id) {
/**
* A global view of all available symbols across all packages.
*/
class SymbolIndex {
private val db = Database.connect("jdbc:h2:mem:symbolindex;DB_CLOSE_DELAY=-1", "org.h2.Driver")
class SymbolIndex(
private val databaseService: DatabaseService
) {
private val db: Database by lazy {
databaseService.db ?: Database.connect("jdbc:h2:mem:symbolindex;DB_CLOSE_DELAY=-1", "org.h2.Driver")
}

var progressFactory: Progress.Factory = Progress.Factory.None

init {
transaction(db) {
SchemaUtils.create(Symbols, Locations, Ranges, Positions)
SchemaUtils.createMissingTablesAndColumns(Symbols, Locations, Ranges, Positions)
}
}

Expand All @@ -99,6 +104,9 @@ class SymbolIndex {
progressFactory.create("Indexing").thenApplyAsync { progress ->
try {
transaction(db) {
// Remove everything first.
Symbols.deleteAll()
// Add new ones.
addDeclarations(allDescriptors(module, exclusions))

val finished = System.currentTimeMillis()
Expand Down
3 changes: 2 additions & 1 deletion server/src/test/kotlin/org/javacs/kt/CompiledFileTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.javacs.kt

import org.hamcrest.Matchers.equalTo
import org.javacs.kt.compiler.Compiler
import org.javacs.kt.database.DatabaseService
import org.junit.AfterClass
import org.junit.Assert.assertThat
import org.junit.Test
Expand Down Expand Up @@ -29,7 +30,7 @@ class CompiledFileTest {
val file = testResourcesRoot().resolve("compiledFile/CompiledFileExample.kt")
val content = Files.readAllLines(file).joinToString("\n")
val parse = compiler.createKtFile(content, file)
val classPath = CompilerClassPath(CompilerConfiguration())
val classPath = CompilerClassPath(CompilerConfiguration(), DatabaseService())
val sourcePath = listOf(parse)
val (context, container) = compiler.compileKtFiles(sourcePath, sourcePath)
CompiledFile(content, parse, context, container, sourcePath, classPath)
Expand Down
2 changes: 2 additions & 0 deletions shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ dependencies {
implementation(platform("dev.fwcd.kotlin-language-server:platform"))

implementation(kotlin("stdlib"))
implementation("org.jetbrains.exposed:exposed-core")
implementation("org.jetbrains.exposed:exposed-dao")
testImplementation("org.hamcrest:hamcrest-all")
testImplementation("junit:junit")
}
Loading

0 comments on commit 394c6a3

Please sign in to comment.