From 2ffe6383a566259c8b8e01d673a210d07b71a314 Mon Sep 17 00:00:00 2001 From: Zhiming Ma Date: Wed, 13 Nov 2024 21:55:45 +0800 Subject: [PATCH] feat(intellij): add support to collect declaration snippets. (#3394) * feat(intellij): add support to collect declaration snippets. * fix(intellij): add try-catch for findTargetElement. --- .../com/tabbyml/intellijtabby/ProjectExt.kt | 57 ++++++++ .../tabbyml/intellijtabby/chat/ChatBrowser.kt | 8 +- .../DefaultLanguageSupportProvider.kt | 88 +++++++++++ .../LanguageSupportProvider.kt | 51 +++++++ .../languageSupport/LanguageSupportService.kt | 48 ++++++ .../intellijtabby/lsp/LanguageClient.kt | 137 ++++++++++++++++-- .../intellijtabby/lsp/PsiManagerExt.kt | 10 -- .../intellijtabby/lsp/TextDocumentSync.kt | 6 +- .../lsp/protocol/ProtocolData.kt | 2 +- .../src/main/resources/META-INF/plugin.xml | 6 + 10 files changed, 385 insertions(+), 28 deletions(-) create mode 100644 clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/languageSupport/DefaultLanguageSupportProvider.kt create mode 100644 clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/languageSupport/LanguageSupportProvider.kt create mode 100644 clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/languageSupport/LanguageSupportService.kt delete mode 100644 clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/lsp/PsiManagerExt.kt diff --git a/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/ProjectExt.kt b/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/ProjectExt.kt index 5e46327e389d..b73e7133c6d1 100644 --- a/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/ProjectExt.kt +++ b/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/ProjectExt.kt @@ -1,6 +1,16 @@ package com.tabbyml.intellijtabby +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.editor.Document +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.fileEditor.TextEditor +import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.VirtualFileManager +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiManager import com.intellij.util.messages.Topic fun Project.safeSyncPublisher(topic: Topic): L? { @@ -16,3 +26,50 @@ fun Project.safeSyncPublisher(topic: Topic): L? { } } } + + +fun Project.findVirtualFile(fileUri: String): VirtualFile? { + val virtualFileManager = VirtualFileManager.getInstance() + return virtualFileManager.findFileByUrl(fileUri) +} + +fun Project.findDocument(fileUri: String): Document? { + return findVirtualFile(fileUri)?.let { findDocument(it) } +} + +fun Project.findDocument(virtualFile: VirtualFile): Document? { + val fileDocumentManager = FileDocumentManager.getInstance() + return runReadAction { fileDocumentManager.getDocument(virtualFile) } +} + +fun Project.findPsiFile(fileUri: String): PsiFile? { + return findVirtualFile(fileUri)?.let { findPsiFile(it) } +} + +fun Project.findPsiFile(virtualFile: VirtualFile): PsiFile? { + val psiManager = PsiManager.getInstance(this) + return runReadAction { psiManager.findFile(virtualFile) } +} + +fun Project.findEditor(fileUri: String): TextEditor? { + return findVirtualFile(fileUri)?.let { findEditor(it) } +} + +fun Project.findEditor(virtualFile: VirtualFile): TextEditor? { + val fileEditorManager = FileEditorManagerEx.getInstanceEx(this) + + return runInEdtAndWait { + fileEditorManager.getEditors(virtualFile) + }.firstOrNull { editor -> editor is TextEditor } as? TextEditor? +} + +private fun runInEdtAndWait(runnable: () -> T): T { + val app = ApplicationManager.getApplication() + if (app.isDispatchThread) { + return runnable() + } else { + var resultRef: T? = null + app.invokeAndWait { resultRef = runnable() } + @Suppress("UNCHECKED_CAST") return resultRef as T + } +} diff --git a/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/chat/ChatBrowser.kt b/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/chat/ChatBrowser.kt index 26bd6af4b2d1..665f46ed413a 100644 --- a/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/chat/ChatBrowser.kt +++ b/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/chat/ChatBrowser.kt @@ -3,8 +3,8 @@ package com.tabbyml.intellijtabby.chat import com.google.gson.Gson import com.google.gson.annotations.SerializedName import com.google.gson.reflect.TypeToken -import com.intellij.openapi.application.ReadAction import com.intellij.openapi.application.invokeLater +import com.intellij.openapi.application.runReadAction import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.components.service import com.intellij.openapi.diagnostic.Logger @@ -165,18 +165,18 @@ class ChatBrowser(private val project: Project) : JBCefBrowser( private fun getActiveFileContext(useSelectedText: Boolean = true): FileContext? { return FileEditorManager.getInstance(project).selectedTextEditor?.let { editor -> - ReadAction.compute?, Throwable> { + runReadAction { val document = editor.document if (useSelectedText) { val selectionModel = editor.selectionModel - val text = selectionModel.selectedText.takeUnless { it.isNullOrBlank() } ?: return@compute null + val text = selectionModel.selectedText.takeUnless { it.isNullOrBlank() } ?: return@runReadAction null Triple( text, document.getLineNumber(selectionModel.selectionStart) + 1, document.getLineNumber(selectionModel.selectionEnd) + 1, ) } else { - val text = document.text.takeUnless { it.isBlank() } ?: return@compute null + val text = document.text.takeUnless { it.isBlank() } ?: return@runReadAction null Triple( text, 1, diff --git a/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/languageSupport/DefaultLanguageSupportProvider.kt b/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/languageSupport/DefaultLanguageSupportProvider.kt new file mode 100644 index 000000000000..bd7face285d0 --- /dev/null +++ b/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/languageSupport/DefaultLanguageSupportProvider.kt @@ -0,0 +1,88 @@ +package com.tabbyml.intellijtabby.languageSupport + +import com.intellij.codeInsight.TargetElementUtil +import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiRecursiveElementWalkingVisitor +import com.tabbyml.intellijtabby.findEditor +import com.tabbyml.intellijtabby.languageSupport.LanguageSupportProvider.* +import org.eclipse.lsp4j.SemanticTokenTypes + +/** + * The default implementation of [LanguageSupportProvider]. + * This implementation relies on [TargetElementUtil] and tries to find the navigation target at each position in the + * editor to provide semantic tokens and declarations. + * This implementation may not work effectively for all languages. + */ +open class DefaultLanguageSupportProvider : LanguageSupportProvider { + private val logger = logger() + private val targetElementUtil = TargetElementUtil.getInstance() + + override fun provideSemanticTokensRange(project: Project, fileRange: FileRange): List? { + val psiFile = fileRange.file + val editor = project.findEditor(psiFile.virtualFile) ?: return null + + return runReadAction { + val leafElements = mutableListOf() + psiFile.accept(object : PsiRecursiveElementWalkingVisitor() { + override fun visitElement(element: PsiElement) { + if (element.children.isEmpty() && + element.text.matches(Regex("\\w+")) && + fileRange.range.contains(element.textRange) && + leafElements.none { it.textRange.intersects(element.textRange) } + ) { + leafElements.add(element) + } + if (element.textRange.intersects(fileRange.range)) { + super.visitElement(element) + } + } + }) + + leafElements.mapNotNull { + val target = try { + targetElementUtil.findTargetElement( + editor.editor, + TargetElementUtil.ELEMENT_NAME_ACCEPTED or TargetElementUtil.REFERENCED_ELEMENT_ACCEPTED, + it.textRange.startOffset + ) + } catch (e: Exception) { + logger.debug("Failed to find target element when providing semantic tokens", e) + null + } + if (target == it || target == null || target.text == null) { + null + } else { + SemanticToken( + text = it.text, + range = it.textRange, + type = SemanticTokenTypes.Type, // Default to use `Type` as the token type as we don't know the actual type + ) + } + } + } + } + + override fun provideDeclaration(project: Project, filePosition: FilePosition): List? { + val psiFile = filePosition.file + val editor = project.findEditor(psiFile.virtualFile) ?: return null + + return runReadAction { + val target = try { + targetElementUtil.findTargetElement( + editor.editor, + TargetElementUtil.ELEMENT_NAME_ACCEPTED or TargetElementUtil.REFERENCED_ELEMENT_ACCEPTED, + filePosition.offset + ) + } catch (e: Exception) { + logger.debug("Failed to find target element at ${psiFile.virtualFile.url}:${filePosition.offset}", e) + null + } + val file = target?.containingFile ?: return@runReadAction listOf() + val range = target.textRange + listOf(FileRange(file, range)) + } + } +} diff --git a/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/languageSupport/LanguageSupportProvider.kt b/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/languageSupport/LanguageSupportProvider.kt new file mode 100644 index 000000000000..49612adfaecb --- /dev/null +++ b/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/languageSupport/LanguageSupportProvider.kt @@ -0,0 +1,51 @@ +package com.tabbyml.intellijtabby.languageSupport + +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiFile + +interface LanguageSupportProvider { + data class FilePosition( + val file: PsiFile, + val offset: Int, + ) + + data class FileRange( + val file: PsiFile, + val range: TextRange, + ) + + data class SemanticToken( + val text: String, + val range: TextRange, + /** + * See [org.eclipse.lsp4j.SemanticTokenTypes] + */ + val type: String, + /** + * See [org.eclipse.lsp4j.SemanticTokenModifiers] + */ + val modifiers: List = emptyList(), + ) + + /** + * Find all semantic tokens in the given [fileRange]. + * For now, this function is only used to find tokens that reference a declaration, which will be used to invoke [provideDeclaration] later. + * So it is safe to only contain these tokens in the result, like class names, function names, etc. + * + * If no tokens are found, return an empty list. + * If the provider does not support the given document, return null. + */ + fun provideSemanticTokensRange(project: Project, fileRange: FileRange): List? { + return null + } + + /** + * Get the declaration location for the token at the given [filePosition]. + * If no declaration is found, return an empty list. + * If the provider does not support the given document, return null. + */ + fun provideDeclaration(project: Project, filePosition: FilePosition): List? { + return null + } +} diff --git a/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/languageSupport/LanguageSupportService.kt b/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/languageSupport/LanguageSupportService.kt new file mode 100644 index 000000000000..200614f305ce --- /dev/null +++ b/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/languageSupport/LanguageSupportService.kt @@ -0,0 +1,48 @@ +package com.tabbyml.intellijtabby.languageSupport + +import com.intellij.openapi.components.Service +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.extensions.ExtensionPointName +import com.intellij.openapi.project.Project +import com.tabbyml.intellijtabby.languageSupport.LanguageSupportProvider.* + + +@Service(Service.Level.PROJECT) +class LanguageSupportService(private val project: Project) { + private val logger = logger() + private val languageSupportProviderExtensionPoint: ExtensionPointName = + ExtensionPointName.create("com.tabbyml.intellij-tabby.languageSupportProvider") + private val defaultLanguageSupportProvider = DefaultLanguageSupportProvider() + + fun provideSemanticTokensRange(fileRange: FileRange): List? { + var semanticTokens: List? = null + for (provider in languageSupportProviderExtensionPoint.extensionList) { + semanticTokens = provider.provideSemanticTokensRange(project, fileRange) + if (semanticTokens != null) { + logger.trace("Semantic tokens provided by ${provider.javaClass.name}: $semanticTokens") + break + } + } + if (semanticTokens == null) { + semanticTokens = defaultLanguageSupportProvider.provideSemanticTokensRange(project, fileRange) + logger.trace("Semantic tokens provided by default provider: $semanticTokens") + } + return semanticTokens + } + + fun provideDeclaration(position: FilePosition): List? { + var declaration: List? = null + for (provider in languageSupportProviderExtensionPoint.extensionList) { + declaration = provider.provideDeclaration(project, position) + if (declaration != null) { + logger.trace("Declaration provided by ${provider.javaClass.name}: $declaration") + break + } + } + if (declaration == null) { + declaration = defaultLanguageSupportProvider.provideDeclaration(project, position) + logger.trace("Declaration provided by default provider: $declaration") + } + return declaration + } +} diff --git a/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/lsp/LanguageClient.kt b/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/lsp/LanguageClient.kt index e6f453ddfab7..8380e1db11b7 100644 --- a/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/lsp/LanguageClient.kt +++ b/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/lsp/LanguageClient.kt @@ -5,15 +5,19 @@ import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationInfo import com.intellij.openapi.components.serviceOrNull import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.editor.Document import com.intellij.openapi.extensions.PluginId import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir -import com.intellij.openapi.vfs.VirtualFileManager -import com.intellij.psi.PsiFile -import com.intellij.psi.PsiManager +import com.intellij.openapi.util.TextRange import com.intellij.psi.codeStyle.CodeStyleSettingsManager import com.intellij.util.messages.Topic +import com.tabbyml.intellijtabby.findDocument +import com.tabbyml.intellijtabby.findPsiFile +import com.tabbyml.intellijtabby.findVirtualFile import com.tabbyml.intellijtabby.git.GitProvider +import com.tabbyml.intellijtabby.languageSupport.LanguageSupportProvider +import com.tabbyml.intellijtabby.languageSupport.LanguageSupportService import com.tabbyml.intellijtabby.lsp.protocol.* import com.tabbyml.intellijtabby.lsp.protocol.ClientCapabilities import com.tabbyml.intellijtabby.lsp.protocol.ClientInfo @@ -34,9 +38,8 @@ class LanguageClient(private val project: Project) : com.tabbyml.intellijtabby.l Disposable { private val logger = Logger.getInstance(LanguageClient::class.java) private val scope = CoroutineScope(Dispatchers.IO) - private val virtualFileManager = VirtualFileManager.getInstance() - private val psiManager = PsiManager.getInstance(project) private val gitProvider = project.serviceOrNull() + private val languageSupportService = project.serviceOrNull() private val configurationSync = ConfigurationSync(project) private val textDocumentSync = TextDocumentSync(project) @@ -69,6 +72,8 @@ class LanguageClient(private val project: Project) : com.tabbyml.intellijtabby.l tabby = TabbyClientCapabilities( agent = true, gitProvider = gitProvider?.isSupported(), + workspaceFileSystem = true, + languageSupport = languageSupportService != null, editorOptions = true, ), ), workspaceFolders = getWorkspaceFolders() @@ -101,7 +106,7 @@ class LanguageClient(private val project: Project) : com.tabbyml.intellijtabby.l override fun editorOptions(params: EditorOptionsParams): CompletableFuture { val codeStyleSettingsManager = CodeStyleSettingsManager.getInstance(project) - val indentation = findPsiFile(params.uri)?.language?.let { + val indentation = project.findPsiFile(params.uri)?.language?.let { codeStyleSettingsManager.mainProjectCodeStyle?.getCommonSettings(it)?.indentOptions }?.let { if (it.USE_TAB_CHARACTER) { @@ -115,6 +120,70 @@ class LanguageClient(private val project: Project) : com.tabbyml.intellijtabby.l } } + override fun readFile(params: ReadFileParams): CompletableFuture { + val file = project.findVirtualFile(params.uri) ?: return CompletableFuture.completedFuture(null) + when (params.format) { + ReadFileParams.Format.TEXT -> { + val document = project.findDocument(file) ?: return CompletableFuture.completedFuture(null) + val text = if (params.range != null) { + document.getText( + TextRange( + offsetInDocument(document, params.range.start), + offsetInDocument(document, params.range.end) + ) + ) + } else { + document.text + } + return CompletableFuture.completedFuture(ReadFileResult(text)) + } + + else -> { + return CompletableFuture.completedFuture(null) + } + } + } + + override fun declaration(params: DeclarationParams): CompletableFuture?> { + return CompletableFuture?>().completeAsync { + val virtualFile = project.findVirtualFile(params.textDocument.uri) ?: return@completeAsync null + val document = project.findDocument(virtualFile) ?: return@completeAsync null + val psiFile = project.findPsiFile(virtualFile) ?: return@completeAsync null + val languageSupport = languageSupportService ?: return@completeAsync null + languageSupport.provideDeclaration( + LanguageSupportProvider.FilePosition(psiFile, offsetInDocument(document, params.position)) + )?.mapNotNull { + val targetUri = it.file.virtualFile.url + val targetDocument = project.findDocument(it.file.virtualFile) ?: return@mapNotNull null + val range = Range( + positionInDocument(targetDocument, it.range.startOffset), + positionInDocument(targetDocument, it.range.endOffset) + ) + LocationLink(targetUri, range, range) + } + } + } + + override fun semanticTokensRange(params: SemanticTokensRangeParams): CompletableFuture { + return CompletableFuture().completeAsync { + val virtualFile = project.findVirtualFile(params.textDocument.uri) ?: return@completeAsync null + val document = project.findDocument(virtualFile) ?: return@completeAsync null + val psiFile = project.findPsiFile(virtualFile) ?: return@completeAsync null + val languageSupport = languageSupportService ?: return@completeAsync null + languageSupport.provideSemanticTokensRange( + LanguageSupportProvider.FileRange( + psiFile, + TextRange( + offsetInDocument(document, params.range.start), + offsetInDocument(document, params.range.end) + ) + ) + )?.let { + encodeSemanticTokens(document, it) + } + } + } + override fun gitRepository(params: GitRepositoryParams): CompletableFuture { val repository = gitProvider?.getRepository(params.uri)?.let { repo -> GitRepository(root = repo.root, remotes = repo.remotes?.map { @@ -179,16 +248,64 @@ class LanguageClient(private val project: Project) : com.tabbyml.intellijtabby.l textDocumentSync.dispose() } - private fun findPsiFile(fileUri: String): PsiFile? { - return virtualFileManager.findFileByUrl(fileUri)?.let { psiManager.findFileWithReadLock(it) } - } - private fun getWorkspaceFolders(): List { return project.guessProjectDir()?.let { listOf(WorkspaceFolder(it.url, project.name)) } ?: listOf() } + private fun encodeSemanticTokens( + document: Document, + tokens: List + ): SemanticTokensRangeResult { + val tokenTypesLegend = mutableListOf() + val tokenModifiersLegend = mutableListOf() + val data = mutableListOf() + var line = 0 + var character = 0 + for (token in tokens.sortedBy { it.range.startOffset }) { + val position = positionInDocument(document, token.range.startOffset) + val deltaLine = position.line - line + line = position.line + if (deltaLine != 0) { + character = 0 + } + val deltaCharacter = position.character - character + character = position.character + val length = token.range.endOffset - token.range.startOffset + val tokenType = tokenTypesLegend.indexOf(token.type).let { + if (it == -1) { + tokenTypesLegend.add(token.type) + tokenTypesLegend.size - 1 + } else { + it + } + } + val tokenModifiers = token.modifiers.map { modifier -> + tokenModifiersLegend.indexOf(modifier).let { + if (it == -1) { + tokenModifiersLegend.add(modifier) + tokenModifiersLegend.size - 1 + } else { + it + } + } + }.fold(0) { acc, i -> + acc or (1 shl i) + } + + data.add(deltaLine) + data.add(deltaCharacter) + data.add(length) + data.add(tokenType) + data.add(tokenModifiers) + } + return SemanticTokensRangeResult( + legend = SemanticTokensRangeResult.SemanticTokensLegend(tokenTypesLegend, tokenModifiersLegend), + tokens = SemanticTokensRangeResult.SemanticTokens(data = data) + ) + } + interface AgentListener { fun agentStatusChanged(status: String) {} fun agentIssueUpdated(issueList: IssueList) {} diff --git a/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/lsp/PsiManagerExt.kt b/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/lsp/PsiManagerExt.kt deleted file mode 100644 index 2043a089b703..000000000000 --- a/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/lsp/PsiManagerExt.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.tabbyml.intellijtabby.lsp - -import com.intellij.openapi.application.ReadAction -import com.intellij.openapi.vfs.VirtualFile -import com.intellij.psi.PsiFile -import com.intellij.psi.PsiManager - -fun PsiManager.findFileWithReadLock(virtualFile: VirtualFile): PsiFile? { - return ReadAction.compute { findFile(virtualFile) } -} \ No newline at end of file diff --git a/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/lsp/TextDocumentSync.kt b/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/lsp/TextDocumentSync.kt index a4b55536deab..8468ebc39ca5 100644 --- a/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/lsp/TextDocumentSync.kt +++ b/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/lsp/TextDocumentSync.kt @@ -6,8 +6,8 @@ import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.EditorFactory import com.intellij.openapi.editor.event.DocumentEvent import com.intellij.openapi.project.Project -import com.intellij.psi.PsiManager import com.tabbyml.intellijtabby.events.DocumentListener +import com.tabbyml.intellijtabby.findPsiFile import com.tabbyml.intellijtabby.lsp.protocol.server.LanguageServer import org.eclipse.lsp4j.* @@ -48,12 +48,12 @@ class TextDocumentSync(private val project: Project) : Disposable { companion object { fun buildDidOpenTextDocumentParams(editor: Editor): DidOpenTextDocumentParams? { + val project = editor.project ?: return null val virtualFile = editor.virtualFile ?: return null - val psiManager = editor.project?.let { PsiManager.getInstance(it) } ?: return null return DidOpenTextDocumentParams( TextDocumentItem( virtualFile.url, - virtualFile.let { psiManager.findFileWithReadLock(it) }?.getLanguageId() ?: "plaintext", + project.findPsiFile(virtualFile)?.getLanguageId() ?: "plaintext", editor.document.modificationStamp.toInt(), editor.document.text, ) diff --git a/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/lsp/protocol/ProtocolData.kt b/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/lsp/protocol/ProtocolData.kt index 56ac9dfc31bc..6f274ceefc22 100644 --- a/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/lsp/protocol/ProtocolData.kt +++ b/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/lsp/protocol/ProtocolData.kt @@ -339,7 +339,7 @@ data class SemanticTokensRangeResult( ) data class SemanticTokens( - val resultId: String, + val resultId: String? = null, val data: List, ) } diff --git a/clients/intellij/src/main/resources/META-INF/plugin.xml b/clients/intellij/src/main/resources/META-INF/plugin.xml index fa6bf3f63254..687523d2592b 100644 --- a/clients/intellij/src/main/resources/META-INF/plugin.xml +++ b/clients/intellij/src/main/resources/META-INF/plugin.xml @@ -33,6 +33,12 @@ + + + +