Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
c9cc370
feat: implement LSP-based completions
S-furi Sep 8, 2025
33933bb
chore: remove unnecessary gradle files for lsp projects roots
S-furi Oct 15, 2025
a6dca44
chore: use version catalog for springboot deps
S-furi Oct 15, 2025
14beab1
refactor: bring public function above private ones
S-furi Oct 15, 2025
b45bc01
refactor: rename completion test base
S-furi Oct 16, 2025
ecbc85d
refactor: remove and move completion-related tests into appropriate p…
S-furi Oct 16, 2025
9ae355f
fix: guard sigle LSPClient creation when multiple `requires` are invoked
S-furi Oct 16, 2025
97cae39
chore: add simple REST completion retrieval extension function
S-furi Oct 16, 2025
ef840d3
chore: rebase fixes
S-furi Oct 16, 2025
9874cf4
chore: provide jackson's object mapper through a spring bean
S-furi Oct 16, 2025
9f6e8e6
refactor: move lsp exception handler in exceptions' package
S-furi Oct 16, 2025
6328221
refactor: move rest controller in rest package
S-furi Oct 16, 2025
a8fb85a
chore: make a global exception handler for completions' exceptions
S-furi Oct 16, 2025
791624b
refactor: rename WS completion request dto
S-furi Oct 16, 2025
0187604
refactor: simplify `Project` and `LspProject` logic
S-furi Oct 16, 2025
8d010b6
fix: fix failing tests
S-furi Oct 16, 2025
8c4b8e2
refactor: move `Completion` from `common` subproject to `completions`…
S-furi Oct 16, 2025
00a447e
refactor: extract `FuzzyCompletionRanking` into util package
S-furi Oct 16, 2025
9debbd7
chore: improve completion ranking
S-furi Oct 16, 2025
e78a763
fix: propagate rest endpoint refactor
S-furi Oct 17, 2025
eaf51da
chore: use JUnit assumption to ignore JS tests for LSP completions
S-furi Oct 17, 2025
f83349c
refactor: name BaseCompletionTest companion object
S-furi Oct 17, 2025
f01c7fe
refactor: use `extractCaret` function instead of manually specifying …
S-furi Oct 17, 2025
7340f17
refactor: avoid duplicating code for JVM and JS tests
S-furi Oct 17, 2025
dc8c291
build: force `linux/amd64` when running LSP integration tests
S-furi Oct 17, 2025
697db32
fix: adapt import test to LSP-based completions
S-furi Oct 17, 2025
65c01c9
chore: do not include package in brackets when completion reference i…
S-furi Oct 17, 2025
a545867
test(lsp): make use of built docker image for lsp-tests
S-furi Oct 17, 2025
cfdac8d
refactor: move `Completion` from `common` subproject to `completions`…
S-furi Oct 16, 2025
5fdfbef
chore: do not include package in brackets when completion reference i…
S-furi Oct 17, 2025
1cc62aa
perf: make restful endpoint properly enqueue requests without blockin…
S-furi Oct 17, 2025
fbb92c7
test: implement `ConcurrencyCompletionRunnerTest` for LSP-based RESTf…
S-furi Oct 17, 2025
672ad72
chore: add references to IntelliJ bug
S-furi Oct 20, 2025
2198c9b
test: test stateless (RESTful) LSP-based completion service with Impo…
S-furi Oct 20, 2025
b24418a
chore: add jackson dependency for `ImportTest`
S-furi Oct 20, 2025
1d817c4
chore: remove unused test
S-furi Oct 20, 2025
02a5b34
fix: fix completion test
S-furi Oct 20, 2025
1117658
chore: properly ignore test with related YouTrack issue
S-furi Oct 20, 2025
145c9ca
test: implement import test for WS-based completions
S-furi Oct 20, 2025
5fc82e4
chore: update backoff and reconnect parameters
S-furi Oct 20, 2025
842422f
fix: avoid using lazy initialization, prefer `@BeforeAll` annotation
S-furi Oct 20, 2025
dc20743
chore: do not use coroutines apis for containerized LSP init healthcheck
S-furi Oct 20, 2025
d50ba6b
chore: increase initial connection timeout on WS tests
S-furi Oct 20, 2025
c0bb6bf
refactor: reuse completions check logic
S-furi Oct 20, 2025
da1dfcd
build: create common conventions for spring-boot based applications
S-furi Oct 20, 2025
4ce3b74
refactor: extract test websocket client
S-furi Oct 20, 2025
9ee8910
test: test WS endpoint with high concurrency
S-furi Oct 20, 2025
865606c
chore: try increase WS timeout on concurrency test for passing in CI
S-furi Oct 20, 2025
f8b1114
chore: free warmup client when instanciating WS tests
S-furi Oct 20, 2025
e1be89f
refactor: refactor concurrency tests
S-furi Oct 21, 2025
98cedb4
chore: add missing packages that has to be excluded from completion r…
S-furi Oct 21, 2025
84f3959
docs: add KDoc for `LspCompletionQueue`
S-furi Oct 21, 2025
6a108e9
chore(build): use settings plugin to resolve version catalog instead …
S-furi Oct 21, 2025
cb01d70
refactor: use `CompletionResponse` instead of plain `Completion`
S-furi Oct 21, 2025
ca34185
refactor: make `LspCompletionParser` managed by Spring's lifecycle ma…
S-furi Oct 22, 2025
3f62093
chore: configure LSP parameters through spring's application configur…
S-furi Oct 22, 2025
2879392
build: do not checkout repository with git large file system
S-furi Oct 22, 2025
8454598
refactor: make use of `jackson`'s object mapper instead of `kotlinx.s…
S-furi Oct 22, 2025
cb0260e
chore: remove from base-project build file what's already defined in …
S-furi Oct 24, 2025
2e16ed0
fix: write user's related files into `/tmp`
S-furi Oct 24, 2025
8d3d609
feat: containerize `Completion` application
S-furi Oct 24, 2025
6cdad72
chore: reorder completion parsing logic
S-furi Oct 27, 2025
da52ece
chore: move jackson's extension in proper package
S-furi Oct 27, 2025
7c02e5e
chore: move `Icon` in enums package
S-furi Oct 27, 2025
ab4ef01
refactor: remove package declaration in build file
S-furi Oct 27, 2025
f77e871
chore: link YT issue to todo
S-furi Oct 27, 2025
ae108d9
chore: forward kotlin-lsp ports when running docker compose
S-furi Oct 27, 2025
3114371
feat: use spring's docker compose support for kotlin-lsp
S-furi Oct 27, 2025
3bb3a82
chore: pass LspConfig to clients
S-furi Oct 27, 2025
be1136a
chore: use `local` spring profile for running with local compose
S-furi Oct 27, 2025
3c22338
chore: try use websites-team `intellij-lsp` docker image
S-furi Oct 27, 2025
6ff3f95
fix: make tests use lsp configuration taken from compose
S-furi Oct 28, 2025
aaa9ee3
chore: use websites-team registry intellij-lsp docker image
S-furi Oct 28, 2025
bfc4a9e
chore: increase lsp-connection timeout in containerized environments
S-furi Oct 28, 2025
88e51fb
fix: fix typo in application-local config
S-furi Oct 28, 2025
7583164
refactor: distinguish between springdoc versions (webmvc or webflux)
S-furi Oct 28, 2025
ab77dd8
chore: set up springdoc for completions subproject
S-furi Oct 28, 2025
825c065
fix: use single project root workspace as remote for lsp
S-furi Oct 28, 2025
529d1b8
fix: do not start spring docker compose with prod config
S-furi Oct 28, 2025
abd08d9
chore: use single compose file for lsp
S-furi Oct 28, 2025
5a8ee28
refactor: rename jackson extensions
S-furi Oct 29, 2025
f104477
chore: add additional config to spring docker compose configuration
S-furi Oct 29, 2025
5d5be2d
chore: harden connection manager inFlight request avoidance logic
S-furi Oct 30, 2025
099d5ab
fix: guard against possible null data (e.g. when in dumb mode)
S-furi Oct 30, 2025
c62ad8d
chore: se kotlin-lsp tagged version
S-furi Oct 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,5 @@ out/

### VS Code ###
.vscode/

kotlin-lsp/
17 changes: 2 additions & 15 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@ version = "${libs.versions.kotlin.get()}-SNAPSHOT"
val propertyFile = "application.properties"

plugins {
alias(libs.plugins.spring.boot)
alias(libs.plugins.spring.dependency.management)
alias(libs.plugins.kotlin.plugin.spring)
id("base-kotlin-jvm-conventions")
id("base-spring-boot-conventions")
}

apply<NodeJsRootPlugin>()
Expand All @@ -40,7 +37,7 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation(libs.aws.springboot.container)
implementation(libs.springdoc)
implementation(libs.springdoc.webmvc)
implementation(libs.gson)
implementation(libs.kotlinx.serialization.json)
implementation(libs.kotlin.compiler.arguments.description)
Expand All @@ -58,9 +55,6 @@ dependencies {
implementation(project(":dependencies"))

testImplementation(libs.kotlin.test)
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
}
testImplementation(libs.kotlinx.coroutines.test)

resourceDependency(libs.skiko.js.wasm.runtime)
Expand Down Expand Up @@ -93,12 +87,6 @@ fun generateProperties(prefix: String = "") = """
""".trimIndent()

tasks.withType<KotlinCompile> {
compilerOptions {
freeCompilerArgs.addAll(
"-Xjsr305=strict",
)

}
dependsOn(":executors:jar")
buildPropertyFile()
}
Expand Down Expand Up @@ -146,7 +134,6 @@ tasks.withType<Test> {
with(rootProject.kotlinNodeJsEnvSpec) {
dependsOn(rootProject.nodeJsSetupTaskProvider)
}
useJUnitPlatform()
javaLauncher.set(javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(17))
vendor.set(JvmVendorSpec.AMAZON)
Expand Down
13 changes: 1 addition & 12 deletions buildSrc/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,7 @@ pluginManagement {
includeBuild("../build-settings-logic")
}

dependencyResolutionManagement {
// For buildSrc we need to declare a custom path to the toml file with versions' catalog.
// But for a root project we can't set `from` inside `versionCatalogs` catalog block for the default `libs` catalog.
// (see https://github.com/gradle/gradle/issues/21328)
// That is why it is not fully moved to the dependencyResolutionManagement block in the settings convention plugin.
versionCatalogs {
getByName("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}

plugins {
id("kotlin-compiler-server-version-catalog")
id("dev.panuszewski.typesafe-conventions") version "0.9.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
id("base-kotlin-jvm-conventions")

alias(libs.plugins.spring.boot)
alias(libs.plugins.spring.dependency.management)
alias(libs.plugins.kotlin.plugin.spring)
}

dependencies {
testImplementation(libs.spring.boot.starter.test) {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
}
}

tasks.withType<KotlinCompile>().configureEach {
compilerOptions { freeCompilerArgs.addAll("-Xjsr305=strict") }
}

tasks.withType<Test>().configureEach { useJUnitPlatform() }
45 changes: 0 additions & 45 deletions common/src/main/kotlin/model/Completion.kt

This file was deleted.

16 changes: 0 additions & 16 deletions common/src/main/kotlin/model/Icon.kt

This file was deleted.

26 changes: 26 additions & 0 deletions completions/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import org.springframework.boot.gradle.tasks.bundling.BootBuildImage

plugins {
id("base-spring-boot-conventions")
}

version = "${libs.versions.kotlin.get()}-SNAPSHOT"

dependencies {
implementation(libs.spring.boot.starter.webflux)
implementation(libs.spring.boot.docker.compose)
implementation(libs.springdoc.webflux)
implementation(libs.org.eclipse.lsp4j)
implementation(libs.kotlinx.coroutines.reactor)
testImplementation(libs.kotlin.test)
testImplementation(libs.bundles.testcontainers)
testImplementation(libs.rector.test)
}

tasks.named<BootBuildImage>("bootBuildImage") {
// TODO(KTL-3803):push docker image to JB registry
val baseImageName = "sfuri/kotlin-compiler-server-completions-lsp"
// publish = true
imageName = "$baseImageName:${project.version}"
tags = setOf("$baseImageName:latest")
}
17 changes: 17 additions & 0 deletions completions/build_completions_image.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash

export COMPLETIONS_SERVICE_PORT=8081

# export LSP_LOCAL_WORKSPACE_ROOT=lsp-users-projects-root
export LSP_REMOTE_WORKSPACE_ROOT=lsp-users-projects-root-test

echo "building docker image for spring application"
yes n | ../gradlew bootBuildImage

if [[ " $* " == *" --run "* ]]; then
echo "requested starting completion service"
docker compose up -d
echo "completion service up and running"
fi

exit 0
23 changes: 23 additions & 0 deletions completions/compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
services:
kotlin-lsp:
image: sfuri/intellij-lsp
ports:
- "9999:9999"
networks:
- lsp-lsp-net

lsp-completions-service:
image: sfuri/kotlin-compiler-server-completions-lsp
ports:
- "${COMPLETIONS_SERVICE_PORT}:8082"
networks:
- lsp-lsp-net
environment:
- LSP_HOST=kotlin-lsp
- LSP_PORT=9999
depends_on:
- kotlin-lsp

networks:
lsp-lsp-net:
driver: bridge
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package completions

import completions.configuration.lsp.LspProperties
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.boot.runApplication

@SpringBootApplication
@EnableConfigurationProperties(value = [LspProperties::class])
class CompletionsApplication

fun main(args: Array<String>) {
runApplication<CompletionsApplication>(*args)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package completions.configuration

import kotlinx.coroutines.asCoroutineDispatcher
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.util.concurrent.Executors
import kotlin.coroutines.CoroutineContext

@Configuration
class CoroutinesConfig {
@Bean
fun lspCoroutineContext(): CoroutineContext {
val parallelism = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(4)
return Executors.newFixedThreadPool(parallelism).asCoroutineDispatcher()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package completions.configuration

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.cors.CorsConfiguration
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource
import org.springframework.web.cors.reactive.CorsWebFilter

@Configuration
class CorsConfiguration {
@Bean
fun corsFilter(): CorsWebFilter {
val source = UrlBasedCorsConfigurationSource()
val config = CorsConfiguration().applyPermitDefaultValues()
source.registerCorsConfiguration("/**", config)
return CorsWebFilter(source)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package completions.configuration

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.kotlinModule
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class JacksonConfig {

@Bean
fun objectMapper(): ObjectMapper =
ObjectMapper().registerModule(kotlinModule())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package completions.configuration

import completions.controllers.ws.LspCompletionWebSocketHandler
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.reactive.HandlerMapping
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping
import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter

@Configuration
class WebSocketConfiguration {
@Bean
fun webSocketHandlerAdapter(): WebSocketHandlerAdapter = WebSocketHandlerAdapter()

@Bean
fun webSocketMapping(handler: LspCompletionWebSocketHandler): HandlerMapping =
SimpleUrlHandlerMapping(mapOf(WEBSOCKET_COMPLETIONS_PATH to handler), 1)

companion object {
const val WEBSOCKET_COMPLETIONS_PATH = "/api/complete"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package completions.configuration.lsp

import org.springframework.boot.context.properties.ConfigurationProperties

/**
* Simple LSP configuration properties which expose a way to be accessed
* even from non-spring managed components, i.e. [completions.lsp.client.LspConnectionManager].
*/
@ConfigurationProperties(prefix = "lsp")
data class LspProperties(
val host: String,
val port: Int,
val reconnectionRetries: Int,
val remoteWorkspaceRoot: String = "/lsp/workspaces/lsp-users-projects-root",
val localWorkspaceRoot: String = "/lsp/workspaces/lsp-users-projects-root"
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package completions.controllers.rest

import completions.dto.api.CompletionRequest
import completions.dto.api.CompletionResponse
import completions.service.lsp.LspCompletionQueue
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping(value = ["/api/compiler"])
class CompletionsRestController(
private val lspCompletionQueue: LspCompletionQueue,
) {
@PostMapping("/complete")
suspend fun complete(
@RequestBody completionRequest: CompletionRequest,
@RequestParam line: Int,
@RequestParam ch: Int,
): List<CompletionResponse> {
return lspCompletionQueue.complete(completionRequest, line, ch)
}
}
Loading