From 5fcd43d80b7db784192fe324760c92cfcecd32bd Mon Sep 17 00:00:00 2001 From: dingyi Date: Tue, 10 Dec 2024 23:49:22 +0800 Subject: [PATCH 1/4] refactor(language-lsp): improve signature help trigger - Remove an unnecessary logging statement from LspLanguage to reduce noise and improve performance. - Improve the signature help trigger logic by adding support for triggering it when the selection changes and the character at the selection's left boundary matches a specific condition. - Adjust thread usage in LspEditor to ensure that the `sleep` operation is performed on a background thread using `Dispatchers.IO`. - Remove the `FIXME` comment in DefaultLanguageClient, as the support for diagnostics is now implemented. - Change the `StreamProvider` interface in CustomConnectProvider to a functional interface, simplifying its usage and improving code readability. - Add logging statement in `LspCompletionItem` for debugging purposes. --- .../sora/lsp/client/DefaultLanguageClient.kt | 5 +--- .../connection/CustomConnectProvider.kt | 2 +- .../rosemoe/sora/lsp/editor/LspEditor.kt | 29 +++++++++++++++---- .../rosemoe/sora/lsp/editor/LspLanguage.kt | 2 +- .../editor/completion/LspCompletionItem.kt | 1 + .../LspEditorContentChangeEventReceiver.kt | 17 +++++++++++ 6 files changed, 45 insertions(+), 11 deletions(-) diff --git a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/client/DefaultLanguageClient.kt b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/client/DefaultLanguageClient.kt index cea7cff36..92f22f225 100644 --- a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/client/DefaultLanguageClient.kt +++ b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/client/DefaultLanguageClient.kt @@ -69,7 +69,7 @@ open class DefaultLanguageClient(protected val context: ClientContext) : } override fun registerCapability(params: RegistrationParams): CompletableFuture { - //Not prepared to support this feature + // Not prepared to support this feature return CompletableFuture.completedFuture(null) } @@ -83,8 +83,6 @@ open class DefaultLanguageClient(protected val context: ClientContext) : } override fun publishDiagnostics(publishDiagnosticsParams: PublishDiagnosticsParams) { - // FIXME: support it. - val diagnosticsContainer = context.project.diagnosticsContainer val uri = URI(publishDiagnosticsParams.uri).toFileUri() @@ -96,7 +94,6 @@ open class DefaultLanguageClient(protected val context: ClientContext) : val editor = context.getEditor(uri) editor?.onDiagnosticsUpdate() - } override fun refreshDiagnostics(): CompletableFuture { diff --git a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/client/connection/CustomConnectProvider.kt b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/client/connection/CustomConnectProvider.kt index 77d2ac495..b1115f116 100644 --- a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/client/connection/CustomConnectProvider.kt +++ b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/client/connection/CustomConnectProvider.kt @@ -62,7 +62,7 @@ class CustomConnectProvider(private val streamProvider: StreamProvider) : Stream /** * Provider of language server connection */ - interface StreamProvider { + fun interface StreamProvider { @WorkerThread fun getStreams(): Pair } diff --git a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/LspEditor.kt b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/LspEditor.kt index 491d0de1d..fb53bd08e 100644 --- a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/LspEditor.kt +++ b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/LspEditor.kt @@ -26,11 +26,13 @@ package io.github.rosemoe.sora.lsp.editor import androidx.annotation.WorkerThread import io.github.rosemoe.sora.event.ContentChangeEvent +import io.github.rosemoe.sora.event.SelectionChangeEvent import io.github.rosemoe.sora.lang.Language import io.github.rosemoe.sora.lsp.client.languageserver.requestmanager.RequestManager import io.github.rosemoe.sora.lsp.client.languageserver.serverdefinition.LanguageServerDefinition import io.github.rosemoe.sora.lsp.client.languageserver.wrapper.LanguageServerWrapper import io.github.rosemoe.sora.lsp.editor.event.LspEditorContentChangeEventReceiver +import io.github.rosemoe.sora.lsp.editor.event.LspEditorSelectionChangeEventReceiver import io.github.rosemoe.sora.lsp.editor.signature.SignatureHelpWindow import io.github.rosemoe.sora.lsp.events.EventType import io.github.rosemoe.sora.lsp.events.diagnostics.publishDiagnostics @@ -67,6 +69,8 @@ class LspEditor( private lateinit var editorContentChangeEventReceiver: LspEditorContentChangeEventReceiver + private lateinit var editorSelectionChangeEventReceiver: LspEditorSelectionChangeEventReceiver + private var currentLanguage: LspLanguage? = null private var isClose = false @@ -102,12 +106,25 @@ class LspEditor( editorContentChangeEventReceiver = LspEditorContentChangeEventReceiver(this) - val subscriptionReceipt = - currentEditor.subscribeEvent( - editorContentChangeEventReceiver + editorSelectionChangeEventReceiver = LspEditorSelectionChangeEventReceiver(this) + + val subscriptionReceipts = + mutableListOf( + currentEditor.subscribeEvent( + editorContentChangeEventReceiver + ), + currentEditor.subscribeEvent( + editorSelectionChangeEventReceiver + ) ) - unsubscribeFunction = Runnable { subscriptionReceipt.unsubscribe() } + unsubscribeFunction = + Runnable { + subscriptionReceipts.removeAll { + it.unsubscribe() + true + } + } } get() { return _currentEditor.get() @@ -207,7 +224,9 @@ class LspEditor( exception.printStackTrace(); } start = System.currentTimeMillis() - Thread.sleep((retryTime / 10).toLong()) + withContext(Dispatchers.IO) { + Thread.sleep((retryTime / 10).toLong()) + } } if (!isConnected && start > maxRetryTime) { diff --git a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/LspLanguage.kt b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/LspLanguage.kt index 7afb124c6..251b799f9 100644 --- a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/LspLanguage.kt +++ b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/LspLanguage.kt @@ -96,7 +96,7 @@ class LspLanguage(var editor: LspEditor) : Language { }*/ val prefix = computePrefix(content, position) - Log.d("prefix", prefix); + val prefixLength = prefix.length val documentChangeEvent = diff --git a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/completion/LspCompletionItem.kt b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/completion/LspCompletionItem.kt index 7c1c79a09..f108288c3 100644 --- a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/completion/LspCompletionItem.kt +++ b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/completion/LspCompletionItem.kt @@ -24,6 +24,7 @@ package io.github.rosemoe.sora.lsp.editor.completion +import android.util.Log import io.github.rosemoe.sora.lang.completion.CompletionItemKind import io.github.rosemoe.sora.lang.completion.SimpleCompletionIconDrawer.draw import io.github.rosemoe.sora.lang.completion.snippet.parser.CodeSnippetParser diff --git a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/event/LspEditorContentChangeEventReceiver.kt b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/event/LspEditorContentChangeEventReceiver.kt index e752eabf3..fad343ed2 100644 --- a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/event/LspEditorContentChangeEventReceiver.kt +++ b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/event/LspEditorContentChangeEventReceiver.kt @@ -26,6 +26,7 @@ package io.github.rosemoe.sora.lsp.editor.event import io.github.rosemoe.sora.event.ContentChangeEvent import io.github.rosemoe.sora.event.EventReceiver +import io.github.rosemoe.sora.event.SelectionChangeEvent import io.github.rosemoe.sora.event.Unsubscribe import io.github.rosemoe.sora.lsp.editor.LspEditor import io.github.rosemoe.sora.lsp.events.EventType @@ -55,3 +56,19 @@ class LspEditorContentChangeEventReceiver(private val editor: LspEditor) : } } +class LspEditorSelectionChangeEventReceiver(private val editor: LspEditor): EventReceiver { + override fun onReceive(event: SelectionChangeEvent, unsubscribe: Unsubscribe) { + + editor.coroutineScope.launch(Dispatchers.IO) { + + if (editor.hitReTrigger(event.editor.text[event.left.index].toString())) { + editor.showSignatureHelp(null) + return@launch + } + + editor.eventManager.emitAsync(EventType.signatureHelp, event.left) + } + + + } +} \ No newline at end of file From 78786b0301f0e150871c446db3d0c2c050d87d6b Mon Sep 17 00:00:00 2001 From: dingyi Date: Sat, 14 Dec 2024 19:09:02 +0800 Subject: [PATCH 2/4] refactor(language-lsp): use delay instead of thread sleepReplace `Thread.sleep` with `delay` for pausing execution during LSP connection retries. This change utilizes Kotlin coroutines for asynchronous operations and avoids blocking the thread. --- .../main/java/io/github/rosemoe/sora/lsp/editor/LspEditor.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/LspEditor.kt b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/LspEditor.kt index fb53bd08e..61e15c3d8 100644 --- a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/LspEditor.kt +++ b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/LspEditor.kt @@ -46,6 +46,7 @@ import io.github.rosemoe.sora.lsp.utils.clearVersions import io.github.rosemoe.sora.widget.CodeEditor import io.github.rosemoe.sora.widget.subscribeEvent import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.future.future import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext @@ -224,9 +225,7 @@ class LspEditor( exception.printStackTrace(); } start = System.currentTimeMillis() - withContext(Dispatchers.IO) { - Thread.sleep((retryTime / 10).toLong()) - } + delay((retryTime / 10).toLong()) } if (!isConnected && start > maxRetryTime) { From 64886a30501b1ca07c17cd6aae605571a32b58c0 Mon Sep 17 00:00:00 2001 From: dingyi Date: Sat, 14 Dec 2024 19:51:22 +0800 Subject: [PATCH 3/4] feat(language-lsp): implement basic diagnostics support This commit implements basic diagnostics support for the LSP client. - Added event listeners for content and selection changes to trigger diagnostic requests. - Implemented diagnostic request and publishing logic within the event handling mechanism. - Updated the request manager to support diagnostic requests. - Adjusted event data handling for diagnostics to improve type safety and clarity. --- .../requestmanager/DefaultRequestManager.kt | 15 +++++++++++ .../LspEditorContentChangeEventReceiver.kt | 25 ++++++++++++++++++- .../rosemoe/sora/lsp/events/EventEmitter.kt | 2 +- .../diagnostics/PublishDiagnosticsEvent.kt | 2 +- .../QueryDocumentDiagnosticsEvent.kt | 14 +---------- 5 files changed, 42 insertions(+), 16 deletions(-) diff --git a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/client/languageserver/requestmanager/DefaultRequestManager.kt b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/client/languageserver/requestmanager/DefaultRequestManager.kt index 45c77ad2e..60bfaccee 100644 --- a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/client/languageserver/requestmanager/DefaultRequestManager.kt +++ b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/client/languageserver/requestmanager/DefaultRequestManager.kt @@ -49,6 +49,8 @@ import org.eclipse.lsp4j.DidCloseTextDocumentParams import org.eclipse.lsp4j.DidOpenTextDocumentParams import org.eclipse.lsp4j.DidSaveTextDocumentParams import org.eclipse.lsp4j.DocumentColorParams +import org.eclipse.lsp4j.DocumentDiagnosticParams +import org.eclipse.lsp4j.DocumentDiagnosticReport import org.eclipse.lsp4j.DocumentFormattingParams import org.eclipse.lsp4j.DocumentHighlight import org.eclipse.lsp4j.DocumentHighlightParams @@ -491,6 +493,19 @@ class DefaultRequestManager( } else null } + override fun diagnostic(params: DocumentDiagnosticParams?): CompletableFuture? { + return if (checkStatus()) { + try { + if (serverCapabilities.diagnosticProvider != null) textDocumentService.diagnostic( + params + ) else null + } catch (e: Exception) { + crashed(e) + null + } + } else null + } + override fun definition(params: TextDocumentPositionParams): CompletableFuture, List>>? { return definition(DefinitionParams(params.textDocument, params.position)) } diff --git a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/event/LspEditorContentChangeEventReceiver.kt b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/event/LspEditorContentChangeEventReceiver.kt index fad343ed2..de477b3ad 100644 --- a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/event/LspEditorContentChangeEventReceiver.kt +++ b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/event/LspEditorContentChangeEventReceiver.kt @@ -24,16 +24,23 @@ package io.github.rosemoe.sora.lsp.editor.event +import android.util.Log import io.github.rosemoe.sora.event.ContentChangeEvent import io.github.rosemoe.sora.event.EventReceiver import io.github.rosemoe.sora.event.SelectionChangeEvent import io.github.rosemoe.sora.event.Unsubscribe import io.github.rosemoe.sora.lsp.editor.LspEditor import io.github.rosemoe.sora.lsp.events.EventType +import io.github.rosemoe.sora.lsp.events.diagnostics.publishDiagnostics +import io.github.rosemoe.sora.lsp.events.diagnostics.queryDocumentDiagnostics import io.github.rosemoe.sora.lsp.events.document.documentChange import io.github.rosemoe.sora.lsp.events.signature.signatureHelp import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import org.eclipse.lsp4j.DocumentDiagnosticReport +import org.eclipse.lsp4j.FullDocumentDiagnosticReport +import org.eclipse.lsp4j.UnchangedDocumentDiagnosticReport +import org.eclipse.lsp4j.jsonrpc.messages.Either class LspEditorContentChangeEventReceiver(private val editor: LspEditor) : @@ -50,13 +57,29 @@ class LspEditorContentChangeEventReceiver(private val editor: LspEditor) : } editor.eventManager.emitAsync(EventType.signatureHelp, event.changeStart) + + val diagnostics = + editor.eventManager.emitAsync(EventType.queryDocumentDiagnostics) + .getOrNull("diagnostics") ?: return@launch + + if (diagnostics.isRelatedUnchangedDocumentDiagnosticReport) { + // no-op + return@launch + } + + if (diagnostics.isRelatedFullDocumentDiagnosticReport) { + editor.eventManager.emit(EventType.publishDiagnostics) { + put("data", diagnostics.left.items) + } + } } } } -class LspEditorSelectionChangeEventReceiver(private val editor: LspEditor): EventReceiver { +class LspEditorSelectionChangeEventReceiver(private val editor: LspEditor) : + EventReceiver { override fun onReceive(event: SelectionChangeEvent, unsubscribe: Unsubscribe) { editor.coroutineScope.launch(Dispatchers.IO) { diff --git a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/events/EventEmitter.kt b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/events/EventEmitter.kt index a5be7a587..eda42303f 100644 --- a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/events/EventEmitter.kt +++ b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/events/EventEmitter.kt @@ -161,7 +161,7 @@ class EventContext { } fun getOrNull(key: String): T? { - return data[key] as T? + return data[key] as? T? } fun put(key: String, value: Any) { diff --git a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/events/diagnostics/PublishDiagnosticsEvent.kt b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/events/diagnostics/PublishDiagnosticsEvent.kt index 53391b789..91d305951 100644 --- a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/events/diagnostics/PublishDiagnosticsEvent.kt +++ b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/events/diagnostics/PublishDiagnosticsEvent.kt @@ -40,7 +40,7 @@ class PublishDiagnosticsEvent : EventListener { val lspEditor = context.get("lsp-editor") val originEditor = lspEditor.editor ?: return - val data = context.get>("data") + val data = context.getOrNull>("data") ?: return val diagnosticsContainer = originEditor.diagnostics ?: DiagnosticsContainer() diff --git a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/events/diagnostics/QueryDocumentDiagnosticsEvent.kt b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/events/diagnostics/QueryDocumentDiagnosticsEvent.kt index db69233f1..5271e5bb3 100644 --- a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/events/diagnostics/QueryDocumentDiagnosticsEvent.kt +++ b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/events/diagnostics/QueryDocumentDiagnosticsEvent.kt @@ -48,22 +48,10 @@ class QueryDocumentDiagnosticsEvent : AsyncEventListener() { .diagnostic( editor.uri.createDocumentDiagnosticParams() ) - .thenApply { either: DocumentDiagnosticReport -> - if (either.isRelatedFullDocumentDiagnosticReport) - either.relatedFullDocumentDiagnosticReport - .relatedDocuments - else - either.relatedUnchangedDocumentDiagnosticReport - .relatedDocuments - } this.future = future.thenAccept { } - val result = future.await() - - if (result.isEmpty()) { - return - } + val result = future.await() ?: return context.put("diagnostics", result) } From fb20092eac0acfe4a0fe3b1217bd2ef9fb47bcbf Mon Sep 17 00:00:00 2001 From: dingyi Date: Sat, 14 Dec 2024 19:56:29 +0800 Subject: [PATCH 4/4] fix(language-lsp): update diagnostics on UI thread This change addresses a potential bug where diagnostics were being updated from a background thread, potentially leading to unexpected behavior or crashes. --- .../sora/lsp/events/diagnostics/PublishDiagnosticsEvent.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/events/diagnostics/PublishDiagnosticsEvent.kt b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/events/diagnostics/PublishDiagnosticsEvent.kt index 91d305951..8abd42b3c 100644 --- a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/events/diagnostics/PublishDiagnosticsEvent.kt +++ b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/events/diagnostics/PublishDiagnosticsEvent.kt @@ -51,7 +51,10 @@ class PublishDiagnosticsEvent : EventListener { data.transformToEditorDiagnostics(originEditor) ) - originEditor.diagnostics = diagnosticsContainer + // run on ui thread + originEditor.post { + originEditor.diagnostics = diagnosticsContainer + } }