Skip to content

Commit ff181c6

Browse files
Support formatting using ktlint (#52)
* Support formatting using ktlint * use ktlint binary for now * detekt * Default to none * Update config * Make ktlint default * Log error
1 parent fc7773c commit ff181c6

File tree

6 files changed

+67
-3
lines changed

6 files changed

+67
-3
lines changed

server/build.gradle.kts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ dependencies {
6969
annotationProcessor(libs.org.openjdk.jmh.generator.annprocess)
7070
}
7171

72-
configurations.forEach { config -> config.resolutionStrategy { preferProjectModules() } }
72+
configurations.forEach { config -> config.resolutionStrategy {
73+
preferProjectModules()
74+
} }
7375

7476
tasks.startScripts { applicationName = "kotlin-language-server" }
7577

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,15 @@ data class KtfmtConfiguration(
7272
var removeUnusedImports: Boolean = true,
7373
)
7474

75+
data class KtlintConfiguration(
76+
var editorConfigPath: String? = null,
77+
var ktlintPath: String? = "ktlint",
78+
)
79+
7580
data class FormattingConfiguration(
76-
var formatter: String = "ktfmt",
77-
var ktfmt: KtfmtConfiguration = KtfmtConfiguration()
81+
var formatter: String = "ktlint",
82+
var ktfmt: KtfmtConfiguration = KtfmtConfiguration(),
83+
var ktlint: KtlintConfiguration = KtlintConfiguration(),
7884
)
7985

8086
fun getInitializationOptions(params: InitializeParams): InitializationOptions? {
@@ -95,6 +101,8 @@ data class InitializationOptions(
95101
val lazyCompilation: Boolean = false,
96102
// The JVM configuration, which encapsulates the Java version used by the Kotlin compiler
97103
val jvmConfiguration: JVMConfiguration? = JVMConfiguration(),
104+
// The formatting configuration
105+
val formattingConfiguration: FormattingConfiguration? = FormattingConfiguration(),
98106
)
99107

100108
class GsonPathConverter : JsonDeserializer<Path?> {

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ class KotlinLanguageServer(
7373
@JsonDelegate
7474
fun getProtocolExtensionService(): KotlinProtocolExtensions = protocolExtensions
7575

76+
@Suppress("CyclomaticComplexMethod", "LongMethod")
7677
override fun initialize(params: InitializeParams): CompletableFuture<InitializeResult> = async.compute {
7778
val serverCapabilities = ServerCapabilities()
7879
serverCapabilities.setTextDocumentSync(TextDocumentSyncKind.Incremental)
@@ -107,6 +108,15 @@ class KotlinLanguageServer(
107108
LOG.info("JVM Target - ${jvmConfig.target}")
108109
config.compiler.jvm.target = jvmConfig.target
109110
}
111+
it.formattingConfiguration?.let { formattingConfig ->
112+
config.formatting.formatter = formattingConfig.formatter
113+
formattingConfig.ktlint.ktlintPath?.let { ktlintPath ->
114+
config.formatting.ktlint.ktlintPath = ktlintPath
115+
}
116+
formattingConfig.ktlint.editorConfigPath?.let { editorConfigPath ->
117+
config.formatting.ktlint.editorConfigPath = editorConfigPath
118+
}
119+
}
110120
}
111121

112122

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,11 @@ class KotlinWorkspaceService(
134134
get("continuationIndent")?.asInt?.let { ktfmt.continuationIndent = it }
135135
get("removeUnusedImports")?.asBoolean?.let { ktfmt.removeUnusedImports = it }
136136
}
137+
get("ktlint")?.asJsonObject?.apply {
138+
val ktlint = formatting.ktlint
139+
get("ktlintPath")?.asString?.let { ktlint.ktlintPath = it }
140+
get("editorConfigPath")?.asString?.let { ktlint.editorConfigPath = it }
141+
}
137142
}
138143

139144
// Update options for inlay hints

server/src/main/kotlin/org/javacs/kt/formatting/FormattingService.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class FormattingService(private val config: FormattingConfiguration) {
1010

1111
private val formatter: Formatter get() = when (config.formatter) {
1212
"ktfmt" -> KtfmtFormatter(config.ktfmt)
13+
"ktlint" -> KtlintFormatter(config.ktlint)
1314
"none" -> NopFormatter
1415
else -> KtfmtFormatter(config.ktfmt)
1516
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package org.javacs.kt.formatting
2+
3+
import org.eclipse.lsp4j.FormattingOptions
4+
import org.javacs.kt.KtlintConfiguration
5+
6+
/**
7+
* KtlintFormatter formats the code using ktlint API
8+
*/
9+
class KtlintFormatter(private val ktlintConfig: KtlintConfiguration): Formatter {
10+
override fun format(code: String, options: FormattingOptions): String {
11+
requireNotNull(ktlintConfig.ktlintPath)
12+
val ktlintArgs = mutableListOf<String>("--stdin", "--format", "--log-level=none")
13+
ktlintConfig.editorConfigPath?.let {
14+
ktlintArgs.add("--editorconfig=${ktlintConfig.editorConfigPath}")
15+
}
16+
val process = ProcessBuilder(listOf(ktlintConfig.ktlintPath) + ktlintArgs)
17+
.start()
18+
19+
process.outputStream.bufferedWriter().use { writer ->
20+
writer.write(code)
21+
writer.flush()
22+
writer.close() // important: signal EOF to stdin
23+
}
24+
25+
val exitCode = process.waitFor()
26+
val formatted = process.inputStream.bufferedReader().readText()
27+
28+
if (exitCode != 0) {
29+
val error = process.errorStream.bufferedReader().readText()
30+
throw KtlintException("ktlint failed with exit code $exitCode. Output: $formatted, Error: $error")
31+
}
32+
33+
return formatted
34+
}
35+
}
36+
37+
38+
class KtlintException(message: String) : Exception(message)

0 commit comments

Comments
 (0)