Skip to content

Commit 3300994

Browse files
authored
Merge pull request #32 from lstreckeisen/CMI-95-Improve-Language-Server-Command-error-handling
Cmi 95 improve language server command error handling
2 parents b619412 + b9a7a75 commit 3300994

File tree

6 files changed

+117
-21
lines changed

6 files changed

+117
-21
lines changed

build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ intellijPlatform {
4444
pluginConfiguration {
4545
id = "org.contextmapper.intellij-plugin"
4646
name = "ContextMapper"
47-
version = "0.3.0"
47+
version = "0.3.1"
4848

4949
productDescriptor {
5050
}
@@ -89,6 +89,7 @@ intellijPlatform {
8989
kotlin {
9090
compilerOptions {
9191
jvmTarget.set(JvmTarget.JVM_17)
92+
allWarningsAsErrors = true
9293
}
9394
}
9495

lsp/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
"name": "context-mapper-intellij-plugin",
33
"private": true,
44
"dependencies": {
5-
"@lstreckeisen/context-mapper-language-server": "0.4.0"
5+
"@lstreckeisen/context-mapper-language-server": "0.4.1"
66
}
77
}

src/main/kotlin/org/contextmapper/intellij/actions/PlantUMLAction.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import com.intellij.openapi.actionSystem.AnActionEvent
55
import com.intellij.openapi.actionSystem.CommonDataKeys
66
import com.intellij.openapi.diagnostic.Logger
77
import com.intellij.openapi.vfs.LocalFileSystem
8+
import com.redhat.devtools.lsp4ij.LanguageServerManager
89
import com.redhat.devtools.lsp4ij.commands.CommandExecutor
910
import org.contextmapper.intellij.actions.generators.ContextMapperGenerator
11+
import org.contextmapper.intellij.actions.generators.HandledGeneratorException
1012
import org.contextmapper.intellij.utils.showErrorNotification
1113
import org.contextmapper.intellij.utils.showInfoNotification
1214
import org.eclipse.lsp4j.Command
@@ -15,7 +17,7 @@ import kotlin.io.path.Path
1517
private val logger = Logger.getInstance(PlantUMLAction::class.java)
1618

1719
class PlantUMLAction : AnAction() {
18-
private val generator = ContextMapperGenerator { context -> CommandExecutor.executeCommand(context) }
20+
private val commandExecutor: LspCommandExecutor = { context -> CommandExecutor.executeCommand(context) }
1921

2022
override fun actionPerformed(event: AnActionEvent) {
2123
val project = event.project
@@ -31,13 +33,16 @@ class PlantUMLAction : AnAction() {
3133
val commandArgs = listOf(file.path, outDir)
3234
val command = Command("Generate PlantUML Diagrams", PLANT_UML_GENERATOR_COMMAND, commandArgs)
3335

36+
val generator = ContextMapperGenerator(commandExecutor, LanguageServerManager.getInstance(project))
3437
generator.generate(project, command)
3538
.whenComplete { result, ex ->
3639
if (ex != null || result.isFailure) {
40+
val resultError = result.exceptionOrNull()
3741
val errorMessage =
38-
if (ex != null) {
42+
if (resultError == null) {
3943
"An unexpected error occurred while generating PlantUML diagrams"
4044
} else {
45+
if (resultError is HandledGeneratorException) return@whenComplete
4146
result.exceptionOrNull()!!.message!!
4247
}
4348

@@ -51,8 +56,8 @@ class PlantUMLAction : AnAction() {
5156
showInfoNotification(project, "No PlantUML diagrams were created")
5257
} else {
5358
showInfoNotification(project, "Successfully created ${generatedFiles.size} PlantUML diagram(s)")
59+
LocalFileSystem.getInstance().refresh(true)
5460
}
55-
LocalFileSystem.getInstance().refresh(true)
5661
}
5762
}
5863
}

src/main/kotlin/org/contextmapper/intellij/actions/generators/ContextMapperGenerator.kt

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.contextmapper.intellij.actions.generators
22

33
import com.intellij.openapi.project.Project
4+
import com.redhat.devtools.lsp4ij.LanguageServerItem
5+
import com.redhat.devtools.lsp4ij.LanguageServerManager
46
import com.redhat.devtools.lsp4ij.commands.CommandExecutor
57
import com.redhat.devtools.lsp4ij.commands.LSPCommandContext
68
import kotlinx.coroutines.CoroutineScope
@@ -9,19 +11,28 @@ import kotlinx.coroutines.launch
911
import org.contextmapper.intellij.actions.LspCommandExecutor
1012
import org.contextmapper.intellij.utils.CONTEXT_MAPPER_SERVER_ID
1113
import org.eclipse.lsp4j.Command
14+
import org.eclipse.lsp4j.jsonrpc.ResponseErrorException
1215
import java.util.concurrent.CompletableFuture
1316

1417
class ContextMapperGenerator(
15-
private val commandExecutor: LspCommandExecutor
18+
private val commandExecutor: LspCommandExecutor,
19+
private val languageServerManager: LanguageServerManager
1620
) {
1721
fun generate(
1822
project: Project,
1923
command: Command
2024
): CompletableFuture<Result<GeneratorResult>> {
25+
val future = CompletableFuture<Result<GeneratorResult>>()
26+
2127
val context = LSPCommandContext(command, project)
22-
context.preferredLanguageServerId = CONTEXT_MAPPER_SERVER_ID
2328

24-
val future = CompletableFuture<Result<GeneratorResult>>()
29+
val languageServerResult = getLanguageServer()
30+
if (languageServerResult.isFailure) {
31+
future.complete(Result.failure(languageServerResult.exceptionOrNull()!!))
32+
return future
33+
}
34+
context.preferredLanguageServerId = CONTEXT_MAPPER_SERVER_ID
35+
context.preferredLanguageServer = languageServerResult.getOrNull()
2536

2637
val response = commandExecutor(context)
2738
CoroutineScope(Dispatchers.IO).launch {
@@ -40,19 +51,12 @@ class ContextMapperGenerator(
4051
try {
4152
val returnedValue = result.join()
4253

43-
val generatedFiles: List<String> =
44-
if (returnedValue is List<*>) {
45-
val files = returnedValue.filterIsInstance<String>()
46-
if (files.size != returnedValue.size) {
47-
future.complete(Result.failure(ContextMapperGeneratorException("Generator returned unexpected result type.")))
48-
return
49-
}
50-
files
51-
} else {
52-
future.complete(Result.failure(ContextMapperGeneratorException("Generator returned unexpected result type.")))
53-
return
54-
}
54+
val generatedFilesResult = extractGeneratedFiles(returnedValue)
55+
if (generatedFilesResult.isFailure) {
56+
future.complete(Result.failure(generatedFilesResult.exceptionOrNull()!!))
57+
}
5558

59+
val generatedFiles = generatedFilesResult.getOrNull()!!
5660
if (generatedFiles.isEmpty()) {
5761
future.complete(Result.success(GeneratorResult(listOf())))
5862
} else {
@@ -72,4 +76,41 @@ class ContextMapperGenerator(
7276
future.complete(Result.failure(ContextMapperGeneratorException("Generator failed without error")))
7377
}
7478
}
79+
80+
private fun getLanguageServer(): Result<LanguageServerItem?> {
81+
return try {
82+
val languageServer =
83+
languageServerManager.getLanguageServer(CONTEXT_MAPPER_SERVER_ID)
84+
.join()
85+
Result.success(languageServer)
86+
} catch (ex: Exception) {
87+
Result.failure(
88+
ContextMapperGeneratorException(
89+
"Could not find language server instance.",
90+
ex,
91+
),
92+
)
93+
}
94+
}
95+
96+
private fun extractGeneratedFiles(returnedValue: Any): Result<List<String>> {
97+
when (returnedValue) {
98+
is List<*> -> {
99+
val files = returnedValue.filterIsInstance<String>()
100+
if (files.size != returnedValue.size) {
101+
return Result.failure(ContextMapperGeneratorException("Generator returned unexpected result type."))
102+
}
103+
return Result.success(files)
104+
}
105+
106+
is ResponseErrorException -> {
107+
// ResponseErrors are already handled by LSP4IJ
108+
return Result.failure(HandledGeneratorException())
109+
}
110+
111+
else -> {
112+
return Result.failure(ContextMapperGeneratorException("Generator returned unexpected result type."))
113+
}
114+
}
115+
}
75116
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package org.contextmapper.intellij.actions.generators
2+
3+
class HandledGeneratorException : Exception()

src/test/kotlin/org/contextmapper/intellij/actions/generators/ContextMapperGeneratorTest.kt

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
package org.contextmapper.intellij.actions.generators
22

33
import com.intellij.openapi.project.Project
4+
import com.redhat.devtools.lsp4ij.LanguageServerManager
45
import com.redhat.devtools.lsp4ij.commands.CommandExecutor
56
import com.redhat.devtools.lsp4ij.settings.UserDefinedLanguageServerSettings
67
import io.mockk.CapturingSlot
78
import io.mockk.every
89
import io.mockk.mockk
910
import io.mockk.slot
1011
import org.contextmapper.intellij.actions.LspCommandExecutor
12+
import org.contextmapper.intellij.utils.CONTEXT_MAPPER_SERVER_ID
1113
import org.eclipse.lsp4j.Command
14+
import org.eclipse.lsp4j.jsonrpc.ResponseErrorException
1215
import org.junit.jupiter.api.Assertions.assertEquals
1316
import org.junit.jupiter.api.Assertions.assertNotNull
1417
import org.junit.jupiter.api.Assertions.assertTrue
@@ -20,13 +23,21 @@ class ContextMapperGeneratorTest() {
2023

2124
private lateinit var successMessage: CapturingSlot<String>
2225
private lateinit var lspCommandExecutor: LspCommandExecutor
26+
private lateinit var languageServerManager: LanguageServerManager
2327
private lateinit var project: Project
2428
private lateinit var generator: ContextMapperGenerator
2529

2630
@BeforeEach
2731
fun setup() {
2832
successMessage = slot()
2933
lspCommandExecutor = mockk(relaxed = true)
34+
languageServerManager =
35+
mockk(relaxed = true) {
36+
every { getLanguageServer(any()) } returns
37+
mockk {
38+
every { join() } returns mockk(relaxed = true)
39+
}
40+
}
3041
project =
3142
mockk {
3243
every { basePath } returns "/tmp"
@@ -35,7 +46,7 @@ class ContextMapperGeneratorTest() {
3546
relaxed = true,
3647
)
3748
}
38-
generator = ContextMapperGenerator(lspCommandExecutor)
49+
generator = ContextMapperGenerator(lspCommandExecutor, languageServerManager)
3950
}
4051

4152
@Test
@@ -110,4 +121,39 @@ class ContextMapperGeneratorTest() {
110121
assertTrue(result.isFailure)
111122
assertTrue { result.exceptionOrNull() is ContextMapperGeneratorException }
112123
}
124+
125+
@Test
126+
fun testFailedGenerationWithHandledException() {
127+
every { lspCommandExecutor.invoke(any()) } returns
128+
mockk<CommandExecutor.LSPCommandResponse> {
129+
every { response } returns
130+
mockk {
131+
every { join() } returns mockk<ResponseErrorException>()
132+
}
133+
}
134+
135+
val result =
136+
generator.generate(project, command)
137+
.join()
138+
139+
assertNotNull(result)
140+
assertTrue { result.isFailure }
141+
assertTrue { result.exceptionOrNull() is HandledGeneratorException }
142+
}
143+
144+
@Test
145+
fun testGeneratorWithMissingLanguageServer() {
146+
every { languageServerManager.getLanguageServer(eq(CONTEXT_MAPPER_SERVER_ID)) } returns
147+
mockk {
148+
every { join() } throws RuntimeException()
149+
}
150+
151+
val result =
152+
generator.generate(project, command)
153+
.join()
154+
155+
assertNotNull(result)
156+
assertTrue { result.isFailure }
157+
assertTrue { result.exceptionOrNull() is ContextMapperGeneratorException }
158+
}
113159
}

0 commit comments

Comments
 (0)