Skip to content

Commit c836518

Browse files
committed
Merge branch 'main' of github.com:fwcd/kotlin-language-server into dependency-caching
2 parents 41a89dc + f693eb2 commit c836518

File tree

18 files changed

+187
-57
lines changed

18 files changed

+187
-57
lines changed

.github/workflows/build.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
name: Build
2-
on: [push, pull_request]
2+
on: [ push, pull_request ]
33

44
jobs:
55
build:
66
runs-on: ubuntu-latest
77
strategy:
88
matrix:
9-
java: ['11', '17']
9+
java: [ '11', '17' ]
1010
steps:
1111
- uses: actions/checkout@v3
1212
- name: Setup JDK
13-
uses: actions/setup-java@v2
13+
uses: actions/setup-java@v3
1414
with:
1515
distribution: 'temurin'
1616
java-version: ${{ matrix.java }}
17+
- uses: gradle/gradle-build-action@v2
1718
- name: Build
1819
run: ./gradlew :server:build :shared:build

.github/workflows/deploy.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ on:
77
jobs:
88
deploy:
99
runs-on: ubuntu-latest
10+
if: github.repository == 'fwcd/kotlin-language-server'
1011
steps:
1112
- uses: actions/checkout@v3
1213
- name: Setup JDK
13-
uses: actions/setup-java@v2
14+
uses: actions/setup-java@v3
1415
with:
1516
distribution: 'temurin'
1617
java-version: '11'
18+
- uses: gradle/gradle-build-action@v2
1719
- name: Build distribution
1820
run: ./gradlew :server:distZip :grammars:distZip
1921
- name: Create release

README.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Kotlin Language Server
2+
23
[![Release](https://img.shields.io/github/release/fwcd/kotlin-language-server)](https://github.com/fwcd/kotlin-language-server/releases)
34
[![Build](https://github.com/fwcd/kotlin-language-server/actions/workflows/build.yml/badge.svg)](https://github.com/fwcd/kotlin-language-server/actions/workflows/build.yml)
45
[![Downloads](https://img.shields.io/github/downloads/fwcd/kotlin-language-server/total)](https://github.com/fwcd/kotlin-language-server/releases)
@@ -11,6 +12,7 @@ A [language server](https://microsoft.github.io/language-server-protocol/) that
1112
Any editor conforming to LSP is supported, including [VSCode](https://github.com/fwcd/vscode-kotlin) and [Atom](https://github.com/fwcd/atom-ide-kotlin).
1213

1314
## Getting Started
15+
1416
* See [BUILDING.md](BUILDING.md) for build instructions
1517
* See [Editor Integration](EDITORS.md) for editor-specific instructions
1618
* See [Roadmap](https://github.com/fwcd/kotlin-language-server/projects/1) for features, planned additions, bugfixes and changes
@@ -19,6 +21,7 @@ Any editor conforming to LSP is supported, including [VSCode](https://github.com
1921
* See [tree-sitter-kotlin](https://github.com/fwcd/tree-sitter-kotlin) for an experimental [Tree-Sitter](https://tree-sitter.github.io/tree-sitter/) grammar
2022

2123
## This repository needs your help!
24+
2225
[The original author](https://github.com/georgewfraser) created this project while he was considering using Kotlin in his work. He ended up deciding not to and is not really using Kotlin these days though this is a pretty fully-functional language server that just needs someone to use it every day for a while and iron out the last few pesky bugs.
2326

2427
There are two hard parts of implementing a language server:
@@ -27,7 +30,23 @@ There are two hard parts of implementing a language server:
2730

2831
The project uses the internal APIs of the [Kotlin compiler](https://github.com/JetBrains/kotlin/tree/master/compiler).
2932

30-
Dependencies are determined by the [findClassPath](server/src/main/kotlin/org/javacs/kt/classpath/findClassPath.kt) function, which invokes Maven or Gradle and tells it to output a list of dependencies. Currently, both Maven and Gradle projects are supported.
33+
### Figuring out the dependencies
34+
35+
Dependencies are determined by the [DefaultClassPathResolver.kt](shared/src/main/kotlin/org/javacs/kt/classpath/DefaultClassPathResolver.kt), which invokes Maven or Gradle to get a list of classpath JARs. Alternatively, projects can also 'manually' provide a list of dependencies through a shell script, located either at `[project root]/kotlinLspClasspath.{sh,bat,cmd}` or `[config root]/KotlinLanguageServer/classpath.{sh,bat,cmd}`, which outputs a list of JARs.
36+
37+
* Example of the `~/.config/KotlinLanguageServer/classpath.sh` on Linux:
38+
```bash
39+
#!/bin/bash
40+
echo /my/path/kotlin-compiler-1.4.10/lib/kotlin-stdlib.jar:/my/path/my-lib.jar
41+
```
42+
43+
* Example of the `%HOMEPATH%\.config\KotlinLanguageServer\classpath.bat` on Windows:
44+
```cmd
45+
@echo off
46+
echo C:\my\path\kotlin-compiler-1.4.10\lib\kotlin-stdlib.jar;C:\my\path\my-lib.jar
47+
```
48+
49+
### Incrementally re-compiling as the user types
3150

3251
I get incremental compilation at the file-level by keeping the same `KotlinCoreEnvironment` alive between compilations in [Compiler.kt](server/src/main/kotlin/org/javacs/kt/compiler/Compiler.kt). There is a performance benchmark in [OneFilePerformance.kt](server/src/test/kotlin/org/javacs/kt/OneFilePerformance.kt) that verifies this works.
3352

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import org.javacs.kt.compiler.Compiler
66
import org.javacs.kt.storage.Storage
77
import org.javacs.kt.util.AsyncExecutor
88
import java.io.Closeable
9+
import java.io.File
910
import java.nio.file.FileSystems
11+
import java.nio.file.Files
1012
import java.nio.file.Path
1113

1214
/**
@@ -19,9 +21,10 @@ class CompilerClassPath(private val config: CompilerConfiguration) : Closeable {
1921
private val buildScriptClassPath = mutableSetOf<Path>()
2022
val classPath = mutableSetOf<ClassPathEntry>()
2123
var storage: Storage? = null
24+
val outputDirectory: File = Files.createTempDirectory("klsBuildOutput").toFile()
2225
val javaHome: String? = System.getProperty("java.home", null)
2326

24-
var compiler = Compiler(javaSourcePath, classPath.map { it.compiledJar }.toSet(), buildScriptClassPath)
27+
var compiler = Compiler(javaSourcePath, classPath.map { it.compiledJar }.toSet(), buildScriptClassPath, outputDirectory)
2528
private set
2629

2730
private val async = AsyncExecutor()
@@ -69,7 +72,7 @@ class CompilerClassPath(private val config: CompilerConfiguration) : Closeable {
6972
if (refreshCompiler) {
7073
LOG.info("Reinstantiating compiler")
7174
compiler.close()
72-
compiler = Compiler(javaSourcePath, classPath.map { it.compiledJar }.toSet(), buildScriptClassPath)
75+
compiler = Compiler(javaSourcePath, classPath.map { it.compiledJar }.toSet(), buildScriptClassPath, outputDirectory)
7376
updateCompilerConfiguration()
7477
}
7578

@@ -140,6 +143,7 @@ class CompilerClassPath(private val config: CompilerConfiguration) : Closeable {
140143

141144
override fun close() {
142145
compiler.close()
146+
outputDirectory.delete()
143147
}
144148
}
145149

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

Lines changed: 3 additions & 2 deletions
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

@@ -112,7 +112,7 @@ class KotlinLanguageServer : LanguageServer, LanguageClientAware, Closeable {
112112

113113
folders.forEachIndexed { i, folder ->
114114
LOG.info("Adding workspace folder {}", folder.name)
115-
val progressPrefix = "[${i + 1}/${folders.size}] ${folder.name}"
115+
val progressPrefix = "[${i + 1}/${folders.size}] ${folder.name ?: ""}"
116116
val progressPercent = (100 * i) / folders.size
117117

118118
progress?.update("$progressPrefix: Updating source path", progressPercent)
@@ -126,6 +126,7 @@ class KotlinLanguageServer : LanguageServer, LanguageClientAware, Closeable {
126126
sourcePath.refresh()
127127
}
128128
}
129+
progress?.close()
129130

130131
textDocuments.lintAll()
131132

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

0 commit comments

Comments
 (0)