Skip to content

Commit 4aa8456

Browse files
authored
Merge pull request #334 from daplf/setup-jdt-ls-extension
Setup JDT LS extension to provide Java + Kotlin interoperability
2 parents 09dcece + dd3bd4b commit 4aa8456

File tree

10 files changed

+125
-22
lines changed

10 files changed

+125
-22
lines changed

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import org.javacs.kt.classpath.defaultClassPathResolver
55
import org.javacs.kt.compiler.Compiler
66
import org.javacs.kt.util.AsyncExecutor
77
import java.io.Closeable
8+
import java.io.File
89
import java.nio.file.FileSystems
10+
import java.nio.file.Files
911
import java.nio.file.Path
1012

1113
/**
@@ -17,9 +19,10 @@ class CompilerClassPath(private val config: CompilerConfiguration) : Closeable {
1719
private val javaSourcePath = mutableSetOf<Path>()
1820
private val buildScriptClassPath = mutableSetOf<Path>()
1921
val classPath = mutableSetOf<ClassPathEntry>()
22+
val outputDirectory: File = Files.createTempDirectory("klsBuildOutput").toFile()
2023
val javaHome: String? = System.getProperty("java.home", null)
2124

22-
var compiler = Compiler(javaSourcePath, classPath.map { it.compiledJar }.toSet(), buildScriptClassPath)
25+
var compiler = Compiler(javaSourcePath, classPath.map { it.compiledJar }.toSet(), buildScriptClassPath, outputDirectory)
2326
private set
2427

2528
private val async = AsyncExecutor()
@@ -67,7 +70,7 @@ class CompilerClassPath(private val config: CompilerConfiguration) : Closeable {
6770
if (refreshCompiler) {
6871
LOG.info("Reinstantiating compiler")
6972
compiler.close()
70-
compiler = Compiler(javaSourcePath, classPath.map { it.compiledJar }.toSet(), buildScriptClassPath)
73+
compiler = Compiler(javaSourcePath, classPath.map { it.compiledJar }.toSet(), buildScriptClassPath, outputDirectory)
7174
updateCompilerConfiguration()
7275
}
7376

@@ -138,6 +141,7 @@ class CompilerClassPath(private val config: CompilerConfiguration) : Closeable {
138141

139142
override fun close() {
140143
compiler.close()
144+
outputDirectory.delete()
141145
}
142146
}
143147

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class KotlinLanguageServer : LanguageServer, LanguageClientAware, Closeable {
3030

3131
private val textDocuments = KotlinTextDocumentService(sourceFiles, sourcePath, config, tempDirectory, uriContentProvider, classPath)
3232
private val workspaces = KotlinWorkspaceService(sourceFiles, sourcePath, classPath, textDocuments, config)
33-
private val protocolExtensions = KotlinProtocolExtensionService(uriContentProvider)
33+
private val protocolExtensions = KotlinProtocolExtensionService(uriContentProvider, classPath)
3434

3535
private lateinit var client: LanguageClient
3636

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,16 @@ import org.javacs.kt.util.parseURI
66
import java.util.concurrent.CompletableFuture
77

88
class KotlinProtocolExtensionService(
9-
private val uriContentProvider: URIContentProvider
9+
private val uriContentProvider: URIContentProvider,
10+
private val cp: CompilerClassPath
1011
) : KotlinProtocolExtensions {
1112
private val async = AsyncExecutor()
1213

1314
override fun jarClassContents(textDocument: TextDocumentIdentifier): CompletableFuture<String?> = async.compute {
1415
uriContentProvider.contentOf(parseURI(textDocument.uri))
1516
}
17+
18+
override fun buildOutputLocation(): CompletableFuture<String?> = async.compute {
19+
cp.outputDirectory.absolutePath
20+
}
1621
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,7 @@ import java.util.concurrent.CompletableFuture
99
interface KotlinProtocolExtensions {
1010
@JsonRequest
1111
fun jarClassContents(textDocument: TextDocumentIdentifier): CompletableFuture<String?>
12+
13+
@JsonRequest
14+
fun buildOutputLocation(): CompletableFuture<String?>
1215
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,9 @@ class KotlinTextDocumentService(
177177
// Lint after saving to prevent inconsistent diagnostics
178178
val uri = parseURI(params.textDocument.uri)
179179
lintNow(uri)
180+
debounceLint.schedule {
181+
sp.save(uri)
182+
}
180183
}
181184

182185
override fun signatureHelp(position: SignatureHelpParams): CompletableFuture<SignatureHelp?> = async.compute {
@@ -261,6 +264,7 @@ class KotlinTextDocumentService(
261264
fun lintAll() {
262265
debounceLint.submitImmediately {
263266
sp.compileAllFiles()
267+
sp.saveAllFiles()
264268
sp.refreshDependencyIndexes()
265269
}
266270
}

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import com.beust.jcommander.JCommander
44
import com.beust.jcommander.Parameter
55
import java.util.concurrent.Executors
66
import org.eclipse.lsp4j.launch.LSPLauncher
7-
import org.eclipse.lsp4j.ConfigurationParams
8-
import org.eclipse.lsp4j.ConfigurationItem
97
import org.javacs.kt.util.ExitingInputStream
108
import org.javacs.kt.util.tcpStartServer
119
import org.javacs.kt.util.tcpConnectToClient
@@ -43,7 +41,7 @@ fun main(argv: Array<String>) {
4341

4442
val server = KotlinLanguageServer()
4543
val threads = Executors.newSingleThreadExecutor { Thread(it, "client") }
46-
val launcher = LSPLauncher.createServerLauncher(server, ExitingInputStream(inStream), outStream, threads, { it })
44+
val launcher = LSPLauncher.createServerLauncher(server, ExitingInputStream(inStream), outStream, threads) { it }
4745

4846
server.connect(launcher.remoteProxy)
4947
launcher.startListening()

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

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ class SourcePath(
4848
var compiledContext: BindingContext? = null,
4949
var module: ModuleDescriptor? = null,
5050
val language: Language? = null,
51-
val isTemporary: Boolean = false // A temporary source file will not be returned by .all()
51+
val isTemporary: Boolean = false, // A temporary source file will not be returned by .all()
52+
var lastSavedFile: KtFile? = null,
5253
) {
5354
val extension: String? = uri.fileExtension ?: "kt" // TODO: Use language?.associatedFileType?.defaultExtension again
5455
val isScript: Boolean = extension == "kts"
@@ -160,7 +161,10 @@ class SourcePath(
160161
}
161162

162163
fun delete(uri: URI) {
163-
files[uri]?.let { refreshWorkspaceIndexes(listOf(it), listOf()) }
164+
files[uri]?.let {
165+
refreshWorkspaceIndexes(listOf(it), listOf())
166+
cp.compiler.removeGeneratedCode(listOfNotNull(it.lastSavedFile))
167+
}
164168

165169
files.remove(uri)
166170
}
@@ -248,10 +252,41 @@ class SourcePath(
248252
// TODO: Investigate the possibility of compiling all files at once, instead of iterating here
249253
// At the moment, compiling all files at once sometimes leads to an internal error from the TopDownAnalyzer
250254
files.keys.forEach {
251-
compileFiles(listOf(it))
255+
// If one of the files fails to compile, we compile the others anyway
256+
try {
257+
compileFiles(listOf(it))
258+
} catch (ex: Exception) {
259+
LOG.printStackTrace(ex)
260+
}
261+
}
262+
}
263+
264+
/**
265+
* Saves a file. This generates code for the file and deletes previously generated code for this file.
266+
*/
267+
fun save(uri: URI) {
268+
files[uri]?.let {
269+
if (!it.isScript) {
270+
// If the code generation fails for some reason, we generate code for the other files anyway
271+
try {
272+
cp.compiler.removeGeneratedCode(listOfNotNull(it.lastSavedFile))
273+
it.module?.let { module ->
274+
it.compiledContext?.let { context ->
275+
cp.compiler.generateCode(module, context, listOfNotNull(it.compiledFile))
276+
it.lastSavedFile = it.compiledFile
277+
}
278+
}
279+
} catch (ex: Exception) {
280+
LOG.printStackTrace(ex)
281+
}
282+
}
252283
}
253284
}
254285

286+
fun saveAllFiles() {
287+
files.keys.forEach { save(it) }
288+
}
289+
255290
fun refreshDependencyIndexes() {
256291
compileAllFiles()
257292

server/src/main/kotlin/org/javacs/kt/compiler/Compiler.kt

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
1111
import org.jetbrains.kotlin.cli.common.environment.setIdeaIoUseFallback
1212
import org.jetbrains.kotlin.cli.jvm.config.addJavaSourceRoots
1313
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots
14+
import org.jetbrains.kotlin.config.CommonConfigurationKeys
1415
import org.jetbrains.kotlin.config.CompilerConfiguration as KotlinCompilerConfiguration
1516
import org.jetbrains.kotlin.container.ComponentProvider
1617
import org.jetbrains.kotlin.container.get
@@ -22,6 +23,8 @@ import org.jetbrains.kotlin.resolve.BindingContext
2223
import org.jetbrains.kotlin.resolve.BindingTraceContext
2324
import org.jetbrains.kotlin.resolve.LazyTopDownAnalyzer
2425
import org.jetbrains.kotlin.resolve.TopDownAnalysisMode
26+
import org.jetbrains.kotlin.resolve.calls.components.InferenceSession
27+
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
2528
import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory
2629
import org.jetbrains.kotlin.scripting.compiler.plugin.ScriptingCompilerConfigurationComponentRegistrar
2730
import org.jetbrains.kotlin.scripting.compiler.plugin.definitions.CliScriptDefinitionProvider
@@ -32,6 +35,9 @@ import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition // Lega
3235
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
3336
import org.jetbrains.kotlin.scripting.definitions.getEnvironment
3437
import org.jetbrains.kotlin.scripting.resolve.KotlinScriptDefinitionFromAnnotatedTemplate
38+
import org.jetbrains.kotlin.types.TypeUtils
39+
import org.jetbrains.kotlin.types.expressions.ExpressionTypingServices
40+
import org.jetbrains.kotlin.util.KotlinFrontEndException
3541
import java.io.Closeable
3642
import java.nio.file.Path
3743
import java.nio.file.Paths
@@ -51,19 +57,19 @@ import org.javacs.kt.LOG
5157
import org.javacs.kt.CompilerConfiguration
5258
import org.javacs.kt.util.KotlinLSException
5359
import org.javacs.kt.util.LoggingMessageCollector
60+
import org.jetbrains.kotlin.cli.common.output.writeAllTo
61+
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
62+
import org.jetbrains.kotlin.codegen.KotlinCodegenFacade
63+
import org.jetbrains.kotlin.codegen.state.GenerationState
64+
import org.jetbrains.kotlin.container.getService
65+
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
5466
import org.jetbrains.kotlin.cli.jvm.compiler.CliBindingTrace
5567
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
5668
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
5769
import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM
5870
import org.jetbrains.kotlin.config.*
59-
import org.jetbrains.kotlin.container.getService
60-
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
61-
import org.jetbrains.kotlin.resolve.calls.components.InferenceSession
62-
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
6371
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
64-
import org.jetbrains.kotlin.types.TypeUtils
65-
import org.jetbrains.kotlin.types.expressions.ExpressionTypingServices
66-
import org.jetbrains.kotlin.util.KotlinFrontEndException
72+
import java.io.File
6773

6874
private val GRADLE_DSL_DEPENDENCY_PATTERN = Regex("^gradle-(?:kotlin-dsl|core).*\\.jar$")
6975

@@ -429,7 +435,7 @@ enum class CompilationKind {
429435
* Incrementally compiles files and expressions.
430436
* The basic strategy for compiling one file at-a-time is outlined in OneFilePerformance.
431437
*/
432-
class Compiler(javaSourcePath: Set<Path>, classPath: Set<Path>, buildScriptClassPath: Set<Path> = emptySet()) : Closeable {
438+
class Compiler(javaSourcePath: Set<Path>, classPath: Set<Path>, buildScriptClassPath: Set<Path> = emptySet(), private val outputDirectory: File) : Closeable {
433439
private var closed = false
434440
private val localFileSystem: VirtualFileSystem
435441

@@ -539,6 +545,34 @@ class Compiler(javaSourcePath: Set<Path>, classPath: Set<Path>, buildScriptClass
539545
}
540546
}
541547

548+
fun removeGeneratedCode(files: Collection<KtFile>) {
549+
files.forEach { file ->
550+
file.declarations.forEach { declaration ->
551+
outputDirectory.resolve(
552+
file.packageFqName.asString().replace(".", File.separator) + File.separator + declaration.name + ".class"
553+
).delete()
554+
}
555+
}
556+
}
557+
558+
fun generateCode(module: ModuleDescriptor, bindingContext: BindingContext, files: Collection<KtFile>) {
559+
outputDirectory.let {
560+
compileLock.withLock {
561+
val compileEnv = compileEnvironmentFor(CompilationKind.DEFAULT)
562+
val state = GenerationState.Builder(
563+
project = compileEnv.environment.project,
564+
builderFactory = ClassBuilderFactories.BINARIES,
565+
module = module,
566+
bindingContext = bindingContext,
567+
files = files.toList(),
568+
configuration = compileEnv.environment.configuration
569+
).build()
570+
KotlinCodegenFacade.compileCorrectFiles(state)
571+
state.factory.writeAllTo(it)
572+
}
573+
}
574+
}
575+
542576
override fun close() {
543577
if (!closed) {
544578
defaultCompileEnvironment.close()

server/src/test/kotlin/org/javacs/kt/CompiledFileTest.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,30 @@ package org.javacs.kt
22

33
import org.hamcrest.Matchers.equalTo
44
import org.javacs.kt.compiler.Compiler
5+
import org.junit.AfterClass
56
import org.junit.Assert.assertThat
67
import org.junit.Test
78
import org.junit.BeforeClass
9+
import java.io.File
810
import java.nio.file.Files
911

1012
class CompiledFileTest {
1113
val compiledFile = compileFile()
1214

1315
companion object {
14-
@JvmStatic @BeforeClass fun setupLogger() {
16+
lateinit var outputDirectory: File
17+
18+
@JvmStatic @BeforeClass fun setup() {
1519
LOG.connectStdioBackend()
20+
outputDirectory = Files.createTempDirectory("klsBuildOutput").toFile()
21+
}
22+
23+
@JvmStatic @AfterClass fun tearDown() {
24+
outputDirectory.delete()
1625
}
1726
}
1827

19-
fun compileFile(): CompiledFile = Compiler(setOf(), setOf()).use { compiler ->
28+
fun compileFile(): CompiledFile = Compiler(setOf(), setOf(), outputDirectory = outputDirectory).use { compiler ->
2029
val file = testResourcesRoot().resolve("compiledFile/CompiledFileExample.kt")
2130
val content = Files.readAllLines(file).joinToString("\n")
2231
val parse = compiler.createKtFile(content, file)

server/src/test/kotlin/org/javacs/kt/CompilerTest.kt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ import org.javacs.kt.compiler.Compiler
1010
import org.junit.Assert.assertThat
1111
import org.junit.Test
1212
import org.junit.After
13+
import org.junit.AfterClass
1314
import org.junit.BeforeClass
15+
import java.io.File
1416
import java.nio.file.Files
1517

1618
class CompilerTest {
17-
val compiler = Compiler(setOf(), setOf())
1819
val myTestResources = testResourcesRoot().resolve("compiler")
1920
val file = myTestResources.resolve("FileToEdit.kt")
2021
val editedText = """
@@ -23,8 +24,18 @@ private class FileToEdit {
2324
}"""
2425

2526
companion object {
26-
@JvmStatic @BeforeClass fun setupLogger() {
27+
lateinit var outputDirectory: File
28+
lateinit var compiler: Compiler
29+
30+
@JvmStatic @BeforeClass fun setup() {
2731
LOG.connectStdioBackend()
32+
outputDirectory = Files.createTempDirectory("klsBuildOutput").toFile()
33+
compiler = Compiler(setOf(), setOf(), outputDirectory = outputDirectory)
34+
}
35+
36+
@JvmStatic @AfterClass
37+
fun tearDown() {
38+
outputDirectory.delete()
2839
}
2940
}
3041

0 commit comments

Comments
 (0)