Skip to content

Commit 2f46b72

Browse files
committed
Merge branch 'main' into GH-321
2 parents cd01576 + f693eb2 commit 2f46b72

40 files changed

+496
-241
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]
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/CompiledFile.kt

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

33
import com.intellij.openapi.util.TextRange
44
import com.intellij.psi.PsiElement
5-
import com.intellij.psi.PsiIdentifier
65
import org.javacs.kt.compiler.CompilationKind
76
import org.javacs.kt.position.changedRegion
87
import org.javacs.kt.position.position
98
import org.javacs.kt.util.findParent
109
import org.javacs.kt.util.nullResult
1110
import org.javacs.kt.util.toPath
12-
import org.jetbrains.kotlin.container.ComponentProvider
1311
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
12+
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
1413
import org.jetbrains.kotlin.lexer.KtTokens
1514
import org.jetbrains.kotlin.psi.*
1615
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
@@ -23,7 +22,7 @@ class CompiledFile(
2322
val content: String,
2423
val parse: KtFile,
2524
val compile: BindingContext,
26-
val container: ComponentProvider,
25+
val module: ModuleDescriptor,
2726
val sourcePath: Collection<KtFile>,
2827
val classPath: CompilerClassPath,
2928
val isScript: Boolean = false,
@@ -33,7 +32,7 @@ class CompiledFile(
3332
* Find the type of the expression at `cursor`
3433
*/
3534
fun typeAtPoint(cursor: Int): KotlinType? {
36-
var cursorExpr = parseAtPoint(cursor, asReference = true)?.findParent<KtExpression>() ?: return nullResult("Couldn't find expression at ${describePosition(cursor)}")
35+
val cursorExpr = parseAtPoint(cursor, asReference = true)?.findParent<KtExpression>() ?: return nullResult("Couldn't find expression at ${describePosition(cursor)}")
3736
val surroundingExpr = expandForType(cursor, cursorExpr)
3837
val scope = scopeAtPoint(cursor) ?: return nullResult("Couldn't find scope at ${describePosition(cursor)}")
3938
return typeOfExpression(surroundingExpr, scope)
@@ -53,16 +52,33 @@ class CompiledFile(
5352
else return surroundingExpr
5453
}
5554

55+
/**
56+
* Looks for a reference expression at the given cursor.
57+
* This is currently used by many features in the language server.
58+
* Unfortunately, it fails to find declarations for JDK symbols.
59+
* [referenceExpressionAtPoint] provides an alternative implementation that can find JDK symbols.
60+
* It cannot, however, replace this method at the moment.
61+
* TODO: Investigate why this method doesn't find JDK symbols.
62+
*/
5663
fun referenceAtPoint(cursor: Int): Pair<KtExpression, DeclarationDescriptor>? {
5764
val element = parseAtPoint(cursor, asReference = true)
58-
var cursorExpr = element?.findParent<KtExpression>() ?: return nullResult("Couldn't find expression at ${describePosition(cursor)} (only found $element)")
65+
val cursorExpr = element?.findParent<KtExpression>() ?: return nullResult("Couldn't find expression at ${describePosition(cursor)} (only found $element)")
5966
val surroundingExpr = expandForReference(cursor, cursorExpr)
6067
val scope = scopeAtPoint(cursor) ?: return nullResult("Couldn't find scope at ${describePosition(cursor)}")
6168
val context = bindingContextOf(surroundingExpr, scope)
6269
LOG.info("Hovering {}", surroundingExpr)
6370
return referenceFromContext(cursor, context)
6471
}
6572

73+
/**
74+
* Looks for a reference expression at the given cursor.
75+
* This method is similar to [referenceAtPoint], but the latter fails to find declarations for JDK symbols.
76+
* This method should not be used for anything other than finding definitions (at least for now).
77+
*/
78+
fun referenceExpressionAtPoint(cursor: Int): Pair<KtExpression, DeclarationDescriptor>? {
79+
return referenceFromContext(cursor, compile)
80+
}
81+
6682
private fun referenceFromContext(cursor: Int, context: BindingContext): Pair<KtExpression, DeclarationDescriptor>? {
6783
val targets = context.getSliceContents(BindingContext.REFERENCE_TARGET)
6884
return targets.asSequence()
@@ -203,9 +219,3 @@ class CompiledFile(
203219
return "$file ${start.line}:${start.character + 1}-${end.line + 1}:${end.character + 1}"
204220
}
205221
}
206-
207-
private fun fileName(file: KtFile): String {
208-
val parts = file.name.split('/')
209-
210-
return parts.last()
211-
}

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

Lines changed: 7 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
/**
@@ -18,8 +20,10 @@ class CompilerClassPath(private val config: CompilerConfiguration) : Closeable {
1820
private val javaSourcePath = mutableSetOf<Path>()
1921
private val buildScriptClassPath = mutableSetOf<Path>()
2022
val classPath = mutableSetOf<ClassPathEntry>()
23+
val outputDirectory: File = Files.createTempDirectory("klsBuildOutput").toFile()
24+
val javaHome: String? = System.getProperty("java.home", null)
2125

22-
var compiler = Compiler(javaSourcePath, classPath.map { it.compiledJar }.toSet(), buildScriptClassPath)
26+
var compiler = Compiler(javaSourcePath, classPath.map { it.compiledJar }.toSet(), buildScriptClassPath, outputDirectory)
2327
private set
2428

2529
private val async = AsyncExecutor()
@@ -67,7 +71,7 @@ class CompilerClassPath(private val config: CompilerConfiguration) : Closeable {
6771
if (refreshCompiler) {
6872
LOG.info("Reinstantiating compiler")
6973
compiler.close()
70-
compiler = Compiler(javaSourcePath, classPath.map { it.compiledJar }.toSet(), buildScriptClassPath)
74+
compiler = Compiler(javaSourcePath, classPath.map { it.compiledJar }.toSet(), buildScriptClassPath, outputDirectory)
7175
updateCompilerConfiguration()
7276
}
7377

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

139143
override fun close() {
140144
compiler.close()
145+
outputDirectory.delete()
141146
}
142147
}
143148

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ public data class IndexingConfiguration(
3131
public data class ExternalSourcesConfiguration(
3232
/** Whether kls-URIs should be sent to the client to describe classes in JARs. */
3333
var useKlsScheme: Boolean = false,
34-
/** Whether external classes classes should be automatically converted to Kotlin. */
35-
var autoConvertToKotlin: Boolean = true
34+
/** Whether external classes should be automatically converted to Kotlin. */
35+
var autoConvertToKotlin: Boolean = false
3636
)
3737

3838
public data class Configuration(

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

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,13 @@ import org.eclipse.lsp4j.services.LanguageClient
77
import org.eclipse.lsp4j.services.LanguageClientAware
88
import org.eclipse.lsp4j.services.LanguageServer
99
import org.javacs.kt.command.ALL_COMMANDS
10-
import org.javacs.kt.externalsources.JarClassContentProvider
11-
import org.javacs.kt.externalsources.ClassPathSourceJarProvider
10+
import org.javacs.kt.externalsources.*
1211
import org.javacs.kt.util.AsyncExecutor
1312
import org.javacs.kt.util.TemporaryDirectory
1413
import org.javacs.kt.util.parseURI
1514
import org.javacs.kt.progress.Progress
1615
import org.javacs.kt.progress.LanguageClientProgress
1716
import org.javacs.kt.semantictokens.semanticTokensLegend
18-
import java.net.URI
1917
import java.io.Closeable
2018
import java.nio.file.Paths
2119
import java.util.concurrent.CompletableFuture
@@ -26,13 +24,13 @@ class KotlinLanguageServer : LanguageServer, LanguageClientAware, Closeable {
2624
val classPath = CompilerClassPath(config.compiler)
2725

2826
private val tempDirectory = TemporaryDirectory()
29-
private val uriContentProvider = URIContentProvider(JarClassContentProvider(config.externalSources, classPath, tempDirectory, ClassPathSourceJarProvider(classPath)))
27+
private val uriContentProvider = URIContentProvider(ClassContentProvider(config.externalSources, classPath, tempDirectory, CompositeSourceArchiveProvider(JdkSourceArchiveProvider(classPath), ClassPathSourceArchiveProvider(classPath))))
3028
val sourcePath = SourcePath(classPath, uriContentProvider, config.indexing)
3129
val sourceFiles = SourceFiles(sourcePath, uriContentProvider)
3230

33-
private val textDocuments = KotlinTextDocumentService(sourceFiles, sourcePath, config, tempDirectory, uriContentProvider)
31+
private val textDocuments = KotlinTextDocumentService(sourceFiles, sourcePath, config, tempDirectory, uriContentProvider, classPath)
3432
private val workspaces = KotlinWorkspaceService(sourceFiles, sourcePath, classPath, textDocuments, config)
35-
private val protocolExtensions = KotlinProtocolExtensionService(uriContentProvider)
33+
private val protocolExtensions = KotlinProtocolExtensionService(uriContentProvider, classPath)
3634

3735
private lateinit var client: LanguageClient
3836

@@ -110,7 +108,7 @@ class KotlinLanguageServer : LanguageServer, LanguageClientAware, Closeable {
110108

111109
folders.forEachIndexed { i, folder ->
112110
LOG.info("Adding workspace folder {}", folder.name)
113-
val progressPrefix = "[${i + 1}/${folders.size}] ${folder.name}"
111+
val progressPrefix = "[${i + 1}/${folders.size}] ${folder.name ?: ""}"
114112
val progressPercent = (100 * i) / folders.size
115113

116114
progress?.update("$progressPrefix: Updating source path", progressPercent)
@@ -124,6 +122,7 @@ class KotlinLanguageServer : LanguageServer, LanguageClientAware, Closeable {
124122
sourcePath.refresh()
125123
}
126124
}
125+
progress?.close()
127126

128127
textDocuments.lintAll()
129128

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

33
import org.eclipse.lsp4j.*
4-
import org.javacs.kt.externalsources.JarClassContentProvider
5-
import org.javacs.kt.externalsources.toKlsURI
64
import org.javacs.kt.util.AsyncExecutor
7-
import org.javacs.kt.util.noResult
85
import org.javacs.kt.util.parseURI
9-
import java.net.URI
10-
import java.net.URISyntaxException
116
import java.util.concurrent.CompletableFuture
127

138
class KotlinProtocolExtensionService(
14-
private val uriContentProvider: URIContentProvider
9+
private val uriContentProvider: URIContentProvider,
10+
private val cp: CompilerClassPath
1511
) : KotlinProtocolExtensions {
1612
private val async = AsyncExecutor()
1713

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

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: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import org.javacs.kt.codeaction.codeActions
88
import org.javacs.kt.completion.*
99
import org.javacs.kt.definition.goToDefinition
1010
import org.javacs.kt.diagnostic.convertDiagnostic
11-
import org.javacs.kt.externalsources.JarClassContentProvider
1211
import org.javacs.kt.formatting.formatKotlinCode
1312
import org.javacs.kt.hover.hoverAt
1413
import org.javacs.kt.position.offset
@@ -26,13 +25,11 @@ import org.javacs.kt.util.TemporaryDirectory
2625
import org.javacs.kt.util.parseURI
2726
import org.javacs.kt.util.describeURI
2827
import org.javacs.kt.util.describeURIs
29-
import org.javacs.kt.command.JAVA_TO_KOTLIN_COMMAND
3028
import org.javacs.kt.rename.renameSymbol
3129
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
3230
import java.net.URI
3331
import java.io.Closeable
3432
import java.nio.file.Path
35-
import java.nio.file.Paths
3633
import java.time.Duration
3734
import java.util.concurrent.CompletableFuture
3835

@@ -41,7 +38,8 @@ class KotlinTextDocumentService(
4138
private val sp: SourcePath,
4239
private val config: Configuration,
4340
private val tempDirectory: TemporaryDirectory,
44-
private val uriContentProvider: URIContentProvider
41+
private val uriContentProvider: URIContentProvider,
42+
private val cp: CompilerClassPath
4543
) : TextDocumentService, Closeable {
4644
private lateinit var client: LanguageClient
4745
private val async = AsyncExecutor()
@@ -93,7 +91,7 @@ class KotlinTextDocumentService(
9391

9492
override fun codeAction(params: CodeActionParams): CompletableFuture<List<Either<Command, CodeAction>>> = async.compute {
9593
val (file, _) = recover(params.textDocument.uri, params.range.start, Recompile.NEVER)
96-
codeActions(file, params.range, params.context)
94+
codeActions(file, sp.index, params.range, params.context)
9795
}
9896

9997
override fun hover(position: HoverParams): CompletableFuture<Hover?> = async.compute {
@@ -118,7 +116,7 @@ class KotlinTextDocumentService(
118116
LOG.info("Go-to-definition at {}", describePosition(position))
119117

120118
val (file, cursor) = recover(position, Recompile.NEVER)
121-
goToDefinition(file, cursor, uriContentProvider.jarClassContentProvider, tempDirectory, config.externalSources)
119+
goToDefinition(file, cursor, uriContentProvider.classContentProvider, tempDirectory, config.externalSources, cp)
122120
?.let(::listOf)
123121
?.let { Either.forLeft<List<Location>, List<LocationLink>>(it) }
124122
?: noResult("Couldn't find definition at ${describePosition(position)}", Either.forLeft(emptyList()))
@@ -179,6 +177,9 @@ class KotlinTextDocumentService(
179177
// Lint after saving to prevent inconsistent diagnostics
180178
val uri = parseURI(params.textDocument.uri)
181179
lintNow(uri)
180+
debounceLint.schedule {
181+
sp.save(uri)
182+
}
182183
}
183184

184185
override fun signatureHelp(position: SignatureHelpParams): CompletableFuture<SignatureHelp?> = async.compute {
@@ -263,6 +264,7 @@ class KotlinTextDocumentService(
263264
fun lintAll() {
264265
debounceLint.submitImmediately {
265266
sp.compileAllFiles()
267+
sp.saveAllFiles()
266268
sp.refreshDependencyIndexes()
267269
}
268270
}

0 commit comments

Comments
 (0)