diff --git a/docs/LSPSupport.md b/docs/LSPSupport.md index 14a38c413..5b74845ab 100644 --- a/docs/LSPSupport.md +++ b/docs/LSPSupport.md @@ -58,7 +58,7 @@ Current state of [Language Features]( https://microsoft.github.io/language-serve * ✅ [textDocument/foldingRange](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_foldingRange) (see [implementation details](#folding-range)) * ❌ [textDocument/selectionRange](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_selectionRange). * ❌ [textDocument/documentSymbol](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentSymbol). - * ❌ [textDocument/semanticTokens](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_semanticTokens). + * ✅ [textDocument/semanticTokens](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_semanticTokens) (see [implementation details](#semantic-tokens)) * ❌ [textDocument/inlineValue](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_inlineValue). * ❌ [workspace/inlineValue/refresh](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_inlineValue_refresh). * ❌ [textDocument/moniker](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_moniker). @@ -454,4 +454,43 @@ is rendered as a `Sticky balloon` notification: You can change the notification behavior of `LSP/window/showMessageRequest` by using the standard UI `Notifications` preferences : -![window/showMessageRequest Notification](./images/lsp-support/window_showMessageRequest_Notification.png) \ No newline at end of file +![window/showMessageRequest Notification](./images/lsp-support/window_showMessageRequest_Notification.png) + +#### Semantic Tokens + + * Use only textDocument/semanticTokens/full + * Use an implementation of RainbowVisitor. You need to enable semantic tokens (for the moment I don't know how to select by default this checkbox) + +![](images/lsp-support/textDocument_semanticTokens_enable.png) + * Use SemanticTokensColorsProvider API to get TextAttributesKey from tokenType, tokenModifiers: + +```java + public @Nullable TextAttributesKey getTextAttributesKey(@NotNull String tokenType, + @NotNull List tokenModifiers, + @NotNull PsiFile file); +``` + * By default uses DefaultSemanticTokensColorsProvider + * TODO: provide an extension point to use a custom SemanticTokensColorsProvider + * Provide a semantic tokens inspector view ![](images/lsp-support/textDocument_semanticTokens_inspector.png) + +##### DefaultSemanticTokensColorsProvider + +The following table lists the currently predefined mappings: + + * the `Semantic token types` column shows the standard LSP Semantic token types. + * the `Semantic modifiers types` column shows the standard LSP Semantic modifier types. + * the `SemanticTokensHighlightingColors` column defines the `TextAttributesKey` constants declared in the LSP4IJ `SemanticTokensHighlightingColors` class. + * the `DefaultLanguageHighlighterColors` column defines the default `TextAttributesKey` used by IntelliJ and show the mapping between `SemanticTokensHighlightingColors` +and `DefaultLanguageHighlighterColors`. + +| Semantic token types | Semantic modifier types | SemanticTokensHighlightingColors | DefaultLanguageHighlighterColors | +|----------------------|-------------------------|----------------------------------|----------------------------------| +| namespace | definition | NAMESPACE_DECLARATION | CLASS_REFERENCE | +| namespace | declaration | NAMESPACE_DECLARATION | CLASS_REFERENCE | +| namespace | | NAMESPACE | CLASS_NAME | +| type (TODO) | | | | + +If you need other mapping: + + * if you think it is a generic mapping, please create a contribution to define a new `SemanticTokensHighlightingColors` constants + * if the mapping is specific to your language, use the 'semanticTokensColorsProvider' extension point to defines your new mapping. \ No newline at end of file diff --git a/docs/images/lsp-support/textDocument_semanticTokens_enable.png b/docs/images/lsp-support/textDocument_semanticTokens_enable.png new file mode 100644 index 000000000..03b6673b1 Binary files /dev/null and b/docs/images/lsp-support/textDocument_semanticTokens_enable.png differ diff --git a/docs/images/lsp-support/textDocument_semanticTokens_inspector.png b/docs/images/lsp-support/textDocument_semanticTokens_inspector.png new file mode 100644 index 000000000..cc271bde1 Binary files /dev/null and b/docs/images/lsp-support/textDocument_semanticTokens_inspector.png differ diff --git a/src/main/java/com/redhat/devtools/lsp4ij/LSPFileSupport.java b/src/main/java/com/redhat/devtools/lsp4ij/LSPFileSupport.java index 4728bc5f7..d06ba28c9 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/LSPFileSupport.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/LSPFileSupport.java @@ -29,6 +29,7 @@ import com.redhat.devtools.lsp4ij.features.references.LSPReferenceSupport; import com.redhat.devtools.lsp4ij.features.rename.LSPPrepareRenameSupport; import com.redhat.devtools.lsp4ij.features.rename.LSPRenameSupport; +import com.redhat.devtools.lsp4ij.features.semanticTokens.LSPSemanticTokensSupport; import com.redhat.devtools.lsp4ij.features.signatureHelp.LSPSignatureHelpSupport; import com.redhat.devtools.lsp4ij.features.typeDefinition.LSPTypeDefinitionSupport; import org.jetbrains.annotations.ApiStatus; @@ -78,6 +79,8 @@ public class LSPFileSupport extends UserDataHolderBase implements Disposable { private final LSPTypeDefinitionSupport typeDefinitionSupport; + private final LSPSemanticTokensSupport semanticTokensSupport; + private LSPFileSupport(@NotNull PsiFile file) { this.file = file; this.codeLensSupport = new LSPCodeLensSupport(file); @@ -97,6 +100,7 @@ private LSPFileSupport(@NotNull PsiFile file) { this.referenceSupport = new LSPReferenceSupport(file); this.declarationSupport = new LSPDeclarationSupport(file); this.typeDefinitionSupport = new LSPTypeDefinitionSupport(file); + this.semanticTokensSupport = new LSPSemanticTokensSupport(file); file.putUserData(LSP_FILE_SUPPORT_KEY, this); } @@ -121,8 +125,9 @@ public void dispose() { getReferenceSupport().cancel(); getDeclarationSupport().cancel(); getTypeDefinitionSupport().cancel(); + getSemanticTokensSupport().cancel(); var map = getUserMap(); - for(var key : map.getKeys()) { + for (var key : map.getKeys()) { var value = map.get(key); if (value instanceof Disposable disposable) { disposable.dispose(); @@ -283,6 +288,15 @@ public LSPTypeDefinitionSupport getTypeDefinitionSupport() { return typeDefinitionSupport; } + /** + * Returns the LSP semantic tokens support. + * + * @return the LSP semantic tokens support. + */ + public LSPSemanticTokensSupport getSemanticTokensSupport() { + return semanticTokensSupport; + } + /** * Return the existing LSP file support for the given Psi file, or create a new one if necessary. * diff --git a/src/main/java/com/redhat/devtools/lsp4ij/LSPIJUtils.java b/src/main/java/com/redhat/devtools/lsp4ij/LSPIJUtils.java index 01dd20601..3bf8b9741 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/LSPIJUtils.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/LSPIJUtils.java @@ -484,10 +484,13 @@ public static Module getModule(@Nullable VirtualFile file, @NotNull Project proj * @return a valid offset from the given position in the given document. */ public static int toOffset(@NotNull Position position, @NotNull Document document) { + return toOffset(position.getLine(), position.getCharacter(), document); + } + + public static int toOffset(int line, int character, @NotNull Document document) { // See https://github.com/microsoft/vscode-languageserver-node/blob/8e625564b531da607859b8cb982abb7cdb2fbe2e/textDocument/src/main.ts#L304 // Adjust position line/character according to this comment https://github.com/microsoft/vscode-languageserver-node/blob/ed3cd0f78c1495913bda7318ace2be7f968008af/textDocument/src/main.ts#L26 - int line = position.getLine(); if (line >= document.getLineCount()) { // The line number is greater than the number of lines in a document, it defaults back to the number of lines in the document. return document.getTextLength(); @@ -498,7 +501,7 @@ public static int toOffset(@NotNull Position position, @NotNull Document documen int lineOffset = document.getLineStartOffset(line); int nextLineOffset = document.getLineEndOffset(line); // If the character value is greater than the line length it defaults back to the line length - return Math.max(Math.min(lineOffset + position.getCharacter(), nextLineOffset), lineOffset); + return Math.max(Math.min(lineOffset + character, nextLineOffset), lineOffset); } /** @@ -953,7 +956,7 @@ private static void doApplyEdits(@Nullable Editor editor, * @param caretOffset the current caret offset and -1 if caret must be not moved. * @return the increment (positive or negative) used to update caret offset. */ - private static int applyEdit(int start, + public static int applyEdit(int start, int end, @Nullable String newText, @NotNull Document document, diff --git a/src/main/java/com/redhat/devtools/lsp4ij/LSPRequestConstants.java b/src/main/java/com/redhat/devtools/lsp4ij/LSPRequestConstants.java index 28d07f053..94535b03e 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/LSPRequestConstants.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/LSPRequestConstants.java @@ -27,6 +27,7 @@ public class LSPRequestConstants { public static final String TEXT_DOCUMENT_DEFINITION = "textDocument/definition"; public static final String TEXT_DOCUMENT_DOCUMENT_LINK = "textDocument/documentLink"; public static final String TEXT_DOCUMENT_FOLDING_RANGE = "textDocument/foldingRange"; + public static final String TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL = "textDocument/semanticTokensFull"; public static final String TEXT_DOCUMENT_TYPE_DEFINITION = "textDocument/typeDefinition"; public static final String TEXT_DOCUMENT_CODE_ACTION = "textDocument/codeAction"; public static final String TEXT_DOCUMENT_CODE_LENS = "textDocument/codeLens"; diff --git a/src/main/java/com/redhat/devtools/lsp4ij/LanguageServerItem.java b/src/main/java/com/redhat/devtools/lsp4ij/LanguageServerItem.java index 85571149e..dd4f96b82 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/LanguageServerItem.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/LanguageServerItem.java @@ -11,6 +11,7 @@ package com.redhat.devtools.lsp4ij; import com.intellij.psi.PsiFile; +import com.redhat.devtools.lsp4ij.features.semanticTokens.SemanticTokensColorsProvider; import org.eclipse.lsp4j.*; import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.eclipse.lsp4j.services.LanguageServer; @@ -405,6 +406,16 @@ public static boolean isCodeActionResolveSupported(@Nullable ServerCapabilities return false; } + /** + * Returns true if the language server can support semantic tokens and false otherwise. + * + * @param serverCapabilities the server capabilities. + * @return true if the language server can support semantic tokens and false otherwise. + */ + public static boolean isSemanticTokensSupported(@Nullable ServerCapabilities serverCapabilities) { + return serverCapabilities != null && + serverCapabilities.getSemanticTokensProvider() != null; + } /** * Returns true if the language server can support rename and false otherwise. @@ -498,4 +509,7 @@ private static boolean hasCapability(Boolean capability) { return capability != null && capability; } + public SemanticTokensColorsProvider getSemanticTokensColorsProvider() { + return getServerWrapper().getServerDefinition().getSemanticTokensColorsProvider(); + } } \ No newline at end of file diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/LanguageClientImpl.java b/src/main/java/com/redhat/devtools/lsp4ij/client/LanguageClientImpl.java index 9e400e3ee..f0b4a9b65 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/client/LanguageClientImpl.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/client/LanguageClientImpl.java @@ -11,6 +11,7 @@ package com.redhat.devtools.lsp4ij.client; import com.google.gson.JsonObject; +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; import com.intellij.openapi.Disposable; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.editor.Editor; @@ -150,6 +151,27 @@ private void refreshInlayHintsForAllOpenedFiles() { } } + @Override + public CompletableFuture refreshSemanticTokens() { + return CompletableFuture.runAsync(() -> { + if (wrapper == null) { + return; + } + refreshSemanticTokensForAllOpenedFiles(); + }); + } + + private void refreshSemanticTokensForAllOpenedFiles() { + for (var fileData : wrapper.getConnectedFiles()) { + VirtualFile file = fileData.getFile(); + final PsiFile psiFile = LSPIJUtils.getPsiFile(file, project); + if (psiFile != null) { + // Should be enough? + DaemonCodeAnalyzer.getInstance(psiFile.getProject()).restart(psiFile); + } + } + } + @Override public CompletableFuture createProgress(WorkDoneProgressCreateParams params) { return progressManager.createProgress(params); diff --git a/src/main/java/com/redhat/devtools/lsp4ij/console/explorer/LanguageServerExplorerLifecycleListener.java b/src/main/java/com/redhat/devtools/lsp4ij/console/explorer/LanguageServerExplorerLifecycleListener.java index b1d796217..91f13c0b0 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/console/explorer/LanguageServerExplorerLifecycleListener.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/console/explorer/LanguageServerExplorerLifecycleListener.java @@ -13,6 +13,7 @@ *******************************************************************************/ package com.redhat.devtools.lsp4ij.console.explorer; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.redhat.devtools.lsp4ij.LanguageServerWrapper; import com.redhat.devtools.lsp4ij.ServerStatus; @@ -62,9 +63,12 @@ public void handleLSPMessage(Message message, MessageConsumer messageConsumer, L return; } - TracingMessageConsumer tracing = getLSPRequestCacheFor(languageServer); - String log = tracing.log(message, messageConsumer, serverTrace); - invokeLaterIfNeeded(() -> showTrace(processTreeNode, log)); + ApplicationManager.getApplication() + .executeOnPooledThread(() -> { + TracingMessageConsumer tracing = getLSPRequestCacheFor(languageServer); + String log = tracing.log(message, messageConsumer, serverTrace); + invokeLaterIfNeeded(() -> showTrace(processTreeNode, log)); + }); } @Override diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/documentation/LSPDocumentationTargetProvider.java b/src/main/java/com/redhat/devtools/lsp4ij/features/documentation/LSPDocumentationTargetProvider.java index 22448e2ff..57a831f74 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/features/documentation/LSPDocumentationTargetProvider.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/documentation/LSPDocumentationTargetProvider.java @@ -11,7 +11,6 @@ package com.redhat.devtools.lsp4ij.features.documentation; import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; @@ -83,7 +82,6 @@ public class LSPDocumentationTargetProvider implements DocumentationTargetProvid // textDocument/hover has been collected correctly List hovers = hoverFuture.getNow(null); if (hovers != null) { - Editor editor = LSPIJUtils.editorForElement(psiFile); return hovers .stream() .map(hover -> toDocumentTarget(hover, document, psiFile)) diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/DefaultSemanticTokensColorsProvider.java b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/DefaultSemanticTokensColorsProvider.java new file mode 100644 index 000000000..83c805a5a --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/DefaultSemanticTokensColorsProvider.java @@ -0,0 +1,186 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.redhat.devtools.lsp4ij.features.semanticTokens; + +import com.intellij.openapi.editor.DefaultLanguageHighlighterColors; +import com.intellij.openapi.editor.colors.TextAttributesKey; +import com.intellij.psi.PsiFile; +import org.eclipse.lsp4j.SemanticTokenModifiers; +import org.eclipse.lsp4j.SemanticTokenTypes; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class DefaultSemanticTokensColorsProvider implements SemanticTokensColorsProvider { + + + @Override + public @Nullable TextAttributesKey getTextAttributesKey(@NotNull String tokenType, + @NotNull List tokenModifiers, + @NotNull PsiFile file) { + switch (tokenType) { + + // Namespace: for identifiers that declare or reference a namespace, module, or package. + case SemanticTokenTypes.Namespace: + if (hasTokenModifiers(tokenModifiers, + SemanticTokenModifiers.Declaration, + SemanticTokenModifiers.Definition)) { + // with declaration, definition modifiers + return SemanticTokensHighlightingColors.NAMESPACE_DECLARATION; + } + // with other modifiers + return SemanticTokensHighlightingColors.NAMESPACE; + + // class: for identifiers that declare or reference a class type. + case SemanticTokenTypes.Class: + if (hasTokenModifiers(tokenModifiers, + SemanticTokenModifiers.Declaration, + SemanticTokenModifiers.Definition)) { + // with declaration, definition modifiers + return SemanticTokensHighlightingColors.CLASS_DECLARATION; + } + // with other modifiers + return SemanticTokensHighlightingColors.CLASS_REFERENCE; + + // comment + case SemanticTokenTypes.Comment: + return SemanticTokensHighlightingColors.COMMENT; + + // decorator + case SemanticTokenTypes.Decorator: + return SemanticTokensHighlightingColors.DECORATOR; + + // enum + case SemanticTokenTypes.Enum: + return SemanticTokensHighlightingColors.ENUM; + + // enum member + case SemanticTokenTypes.EnumMember: + return SemanticTokensHighlightingColors.ENUM_MEMBER; + + // event + case SemanticTokenTypes.Event: + return SemanticTokensHighlightingColors.EVENT; + + // function + case SemanticTokenTypes.Function: + if (hasTokenModifiers(tokenModifiers, + SemanticTokenModifiers.Declaration, + SemanticTokenModifiers.Definition)) { + // with declaration, definition modifiers + return SemanticTokensHighlightingColors.FUNCTION_DECLARATION; + } + // with other modifiers + return SemanticTokensHighlightingColors.FUNCTION_CALL; + + // interface + case SemanticTokenTypes.Interface: + return DefaultLanguageHighlighterColors.INTERFACE_NAME; + + // keyword + case SemanticTokenTypes.Keyword: + return SemanticTokensHighlightingColors.KEYWORD; + + // macro + case SemanticTokenTypes.Macro: + return SemanticTokensHighlightingColors.MACRO; + + // method + case SemanticTokenTypes.Method: { + if (hasTokenModifiers(tokenModifiers, + SemanticTokenModifiers.Declaration, + SemanticTokenModifiers.Definition)) { + // with declaration, definition modifiers + return SemanticTokensHighlightingColors.METHOD_DECLARATION; + } + // with other modifiers + return SemanticTokensHighlightingColors.METHOD_CALL; + } + + /*case "member": + return SemanticTokensHighlightingColors.MEMBER_ATTRIBUTES; +*/ + + // modifier + case SemanticTokenTypes.Modifier: + return SemanticTokensHighlightingColors.MODIFIER; + + // Number: for tokens that represent a number literal. + case SemanticTokenTypes.Number: + return SemanticTokensHighlightingColors.NUMBER; + + // Operator + case SemanticTokenTypes.Operator: + return DefaultLanguageHighlighterColors.OPERATION_SIGN; + + // Parameter + case SemanticTokenTypes.Parameter: + return DefaultLanguageHighlighterColors.PARAMETER; + + // Property + case SemanticTokenTypes.Property: + if (hasTokenModifiers(tokenModifiers, SemanticTokenModifiers.Static)) { + if (hasTokenModifiers(tokenModifiers, SemanticTokenModifiers.Readonly)) { + // with static, readonly modifiers + return SemanticTokensHighlightingColors.STATIC_READONLY_PROPERTY; + } + // with static readonly modifiers + return SemanticTokensHighlightingColors.STATIC_PROPERTY; + } + if (hasTokenModifiers(tokenModifiers, SemanticTokenModifiers.Readonly)) { + // with readonly modifiers + return SemanticTokensHighlightingColors.READONLY_PROPERTY; + } + // with other modifiers + return SemanticTokensHighlightingColors.PROPERTY; + + case SemanticTokenTypes.Regexp: + return DefaultLanguageHighlighterColors.REASSIGNED_LOCAL_VARIABLE; + + case SemanticTokenTypes.String: + return SemanticTokensHighlightingColors.STRING; + + case SemanticTokenTypes.Struct: + return DefaultLanguageHighlighterColors.VALID_STRING_ESCAPE; + + case SemanticTokenTypes.Type: + return DefaultLanguageHighlighterColors.CLASS_REFERENCE; + + case SemanticTokenTypes.TypeParameter: + return DefaultLanguageHighlighterColors.MARKUP_ATTRIBUTE; + + // variable + case SemanticTokenTypes.Variable: + return DefaultLanguageHighlighterColors.LOCAL_VARIABLE; + } + return null; + } + + protected boolean hasTokenModifiers(List tokenModifiers, String... checkedTokenModifiers) { + if (tokenModifiers.isEmpty()) { + return false; + } + for (var modifier : checkedTokenModifiers) { + if (tokenModifiers.contains(modifier)) { + return true; + } + } + return false; + } + + protected boolean hasTokenModifiersOrEmpty(List tokenModifiers, String... checkedTokenModifiers) { + if (tokenModifiers.isEmpty()) { + return true; + } + return hasTokenModifiers(tokenModifiers, checkedTokenModifiers); + } +} diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/LSPSemanticTokensRainbowVisitor.java b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/LSPSemanticTokensRainbowVisitor.java new file mode 100644 index 000000000..e505c76e4 --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/LSPSemanticTokensRainbowVisitor.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.redhat.devtools.lsp4ij.features.semanticTokens; + +import com.intellij.codeInsight.daemon.RainbowVisitor; +import com.intellij.codeInsight.daemon.impl.HighlightVisitor; +import com.intellij.codeInsight.daemon.impl.analysis.HighlightInfoHolder; +import com.intellij.openapi.editor.colors.TextAttributesScheme; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.redhat.devtools.lsp4ij.LSPFileSupport; +import com.redhat.devtools.lsp4ij.LSPIJUtils; +import com.redhat.devtools.lsp4ij.LanguageServersRegistry; +import org.eclipse.lsp4j.SemanticTokensParams; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import static com.redhat.devtools.lsp4ij.internal.CompletableFutures.isDoneNormally; +import static com.redhat.devtools.lsp4ij.internal.CompletableFutures.waitUntilDone; + + +/** + * LSP 'textDocument/semanticTokens support by implementing IntelliJ {@link RainbowVisitor}. + * + *

+ * Implementing {@link RainbowVisitor} gives the capability to have an existing highlighter (custom highlighter, TextMate highlighter) + * and add semantic coloration. + *

+ */ +public class LSPSemanticTokensRainbowVisitor extends RainbowVisitor { + + private static final Logger LOGGER = LoggerFactory.getLogger(LSPSemanticTokensRainbowVisitor.class); + private TextAttributesScheme colorScheme; + + @Override + public boolean suitableForFile(@NotNull PsiFile file) { + return LanguageServersRegistry.getInstance().isFileSupported(file); + } + + @Override + public boolean analyze(@NotNull PsiFile file, boolean updateWholeFile, @NotNull HighlightInfoHolder holder, @NotNull Runnable action) { + this.colorScheme = holder.getColorsScheme(); + return super.analyze(file, updateWholeFile, holder, action); + } + + @Override + public void visit(@NotNull PsiElement element) { + // Consume LSP 'textDocument/semanticTokens/full' request + PsiFile file = element.getContainingFile(); + if (!LanguageServersRegistry.getInstance().isFileSupported(file)) { + return; + } + LSPSemanticTokensSupport semanticTokensSupport = LSPFileSupport.getSupport(file).getSemanticTokensSupport(); + var params = new SemanticTokensParams(LSPIJUtils.toTextDocumentIdentifier(file.getVirtualFile())); + CompletableFuture semanticTokensFuture = semanticTokensSupport.getSemanticTokens(params); + try { + waitUntilDone(semanticTokensFuture, file); + } catch ( + ProcessCanceledException e) {//Since 2024.2 ProcessCanceledException extends CancellationException so we can't use multicatch to keep backward compatibility + //TODO delete block when minimum required version is 2024.2 + semanticTokensSupport.cancel(); + return; + } catch (CancellationException e) { + // cancel the LSP requests textDocument/semanticTokens/full + semanticTokensSupport.cancel(); + return; + } catch (ExecutionException e) { + LOGGER.error("Error while consuming LSP 'textDocument/semanticTokens/full' request", e); + return; + } + + if (isDoneNormally(semanticTokensFuture)) { + // textDocument/semanticTokens/full has been collected correctly, create list of IJ HighlightInfo from LSP SemanticTokens list + SemanticTokensData semanticTokens = semanticTokensFuture.getNow(null); + if (semanticTokens != null) { + var document = LSPIJUtils.getDocument(file.getVirtualFile()); + if (document == null) { + return; + } + semanticTokens.highlight(file, document, info -> super.addInfo(info)); + } + + } + } + + @Override + public @NotNull HighlightVisitor clone() { + return new LSPSemanticTokensRainbowVisitor(); + } +} diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/LSPSemanticTokensSupport.java b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/LSPSemanticTokensSupport.java new file mode 100644 index 000000000..0ea4aa4d3 --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/LSPSemanticTokensSupport.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.redhat.devtools.lsp4ij.features.semanticTokens; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.redhat.devtools.lsp4ij.LSPRequestConstants; +import com.redhat.devtools.lsp4ij.LanguageServerItem; +import com.redhat.devtools.lsp4ij.LanguageServiceAccessor; +import com.redhat.devtools.lsp4ij.features.AbstractLSPFeatureSupport; +import com.redhat.devtools.lsp4ij.internal.CancellationSupport; +import org.eclipse.lsp4j.SemanticTokensLegend; +import org.eclipse.lsp4j.SemanticTokensParams; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +/** + * LSP semanticTokens support which loads and caches semantic tokens by consuming: + * + *
    + *
  • LSP 'textDocument/semanticTokens' requests
  • + *
+ */ +public class LSPSemanticTokensSupport extends AbstractLSPFeatureSupport { + + public LSPSemanticTokensSupport(@NotNull PsiFile file) { + super(file); + } + + public CompletableFuture getSemanticTokens(SemanticTokensParams params) { + return super.getFeatureData(params); + } + + @Override + protected CompletableFuture doLoad(SemanticTokensParams params, CancellationSupport cancellationSupport) { + PsiFile file = super.getFile(); + return getSemanticTokens(file.getVirtualFile(), file.getProject(), params, cancellationSupport); + } + + private static @NotNull CompletableFuture getSemanticTokens(@NotNull VirtualFile file, + @NotNull Project project, + @NotNull SemanticTokensParams params, + @NotNull CancellationSupport cancellationSupport) { + + return LanguageServiceAccessor.getInstance(project) + .getLanguageServers(file, LanguageServerItem::isSemanticTokensSupported) + .thenComposeAsync(languageServers -> { + // Here languageServers is the list of language servers which matches the given file + // and which have folding range capability + if (languageServers.isEmpty()) { + return CompletableFuture.completedFuture(null); + } + + // Collect list of textDocument/semanticTokens future for each language servers + List> semanticTokensPerServerFutures = languageServers + .stream() + .map(languageServer -> getSemanticTokensFor(params, languageServer, cancellationSupport)) + .filter(Objects::nonNull) + .toList(); + + // Merge list of textDocument/foldingRange future in one future which return the list of folding ranges + return semanticTokensPerServerFutures.get(0); //CompletableFutures.mergeInOneFuture(semanticTokensPerServerFutures, cancellationSupport); + }); + } + + private static CompletableFuture getSemanticTokensFor(SemanticTokensParams params, + LanguageServerItem languageServer, + CancellationSupport cancellationSupport) { + return cancellationSupport.execute(languageServer + .getTextDocumentService() + .semanticTokensFull(params), languageServer, LSPRequestConstants.TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL) + .thenApplyAsync(semanticTokens -> { + if (semanticTokens == null) { + // textDocument/semanticTokensFull may return null + return null; + } + return new SemanticTokensData(semanticTokens, getLegend(languageServer), languageServer.getSemanticTokensColorsProvider()); + }); + } + + @Nullable + private static SemanticTokensLegend getLegend(LanguageServerItem languageServer) { + var serverCapabilities = languageServer.getServerCapabilities(); + return serverCapabilities.getSemanticTokensProvider() != null ? serverCapabilities.getSemanticTokensProvider().getLegend() : null; + } + +} diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/SemanticTokensColorSettingsPage.java b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/SemanticTokensColorSettingsPage.java new file mode 100644 index 000000000..98dbbfab9 --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/SemanticTokensColorSettingsPage.java @@ -0,0 +1,132 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.redhat.devtools.lsp4ij.features.semanticTokens; + +import com.intellij.codeHighlighting.RainbowHighlighter; +import com.intellij.lang.Language; +import com.intellij.openapi.editor.colors.TextAttributesKey; +import com.intellij.openapi.fileTypes.PlainTextLanguage; +import com.intellij.openapi.fileTypes.SyntaxHighlighter; +import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory; +import com.intellij.openapi.options.colors.AttributesDescriptor; +import com.intellij.openapi.options.colors.ColorDescriptor; +import com.intellij.openapi.options.colors.RainbowColorSettingsPage; +import com.intellij.openapi.util.NlsContexts; +import com.intellij.psi.codeStyle.DisplayPriority; +import com.intellij.psi.codeStyle.DisplayPrioritySortable; +import com.redhat.devtools.lsp4ij.LanguageServerBundle; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.util.Map; + +/** + * LSP Semantic tokens color setting page. + */ +public class SemanticTokensColorSettingsPage implements RainbowColorSettingsPage, DisplayPrioritySortable { + + private static final AttributesDescriptor[] ourDescriptors = { + // namespace + new AttributesDescriptor(LanguageServerBundle.message("options.lsp.attribute.descriptor.namespace.declaration"), SemanticTokensHighlightingColors.NAMESPACE_DECLARATION), + new AttributesDescriptor(LanguageServerBundle.message("options.lsp.attribute.descriptor.namespace"), SemanticTokensHighlightingColors.NAMESPACE), + // Class + new AttributesDescriptor(LanguageServerBundle.message("options.lsp.attribute.descriptor.class.declaration"), SemanticTokensHighlightingColors.CLASS_DECLARATION), + new AttributesDescriptor(LanguageServerBundle.message("options.lsp.attribute.descriptor.class.reference"), SemanticTokensHighlightingColors.CLASS_REFERENCE), + // Comment + new AttributesDescriptor(LanguageServerBundle.message("options.lsp.attribute.descriptor.comment"), SemanticTokensHighlightingColors.COMMENT), + // Enum + new AttributesDescriptor(LanguageServerBundle.message("options.lsp.attribute.descriptor.enum"), SemanticTokensHighlightingColors.ENUM), + new AttributesDescriptor(LanguageServerBundle.message("options.lsp.attribute.descriptor.enum.member"), SemanticTokensHighlightingColors.ENUM_MEMBER), + new AttributesDescriptor(LanguageServerBundle.message("options.lsp.attribute.descriptor.event"), SemanticTokensHighlightingColors.EVENT), + new AttributesDescriptor(LanguageServerBundle.message("options.lsp.attribute.descriptor.function.call"), SemanticTokensHighlightingColors.FUNCTION_CALL), + new AttributesDescriptor(LanguageServerBundle.message("options.lsp.attribute.descriptor.function.declaration"), SemanticTokensHighlightingColors.FUNCTION_DECLARATION), + new AttributesDescriptor(LanguageServerBundle.message("options.lsp.attribute.descriptor.keyword"), SemanticTokensHighlightingColors.KEYWORD), + new AttributesDescriptor(LanguageServerBundle.message("options.lsp.attribute.descriptor.macro"), SemanticTokensHighlightingColors.MACRO), + new AttributesDescriptor(LanguageServerBundle.message("options.lsp.attribute.descriptor.member"), SemanticTokensHighlightingColors.MEMBER_ATTRIBUTES), + // Methods + new AttributesDescriptor(LanguageServerBundle.message("options.lsp.attribute.descriptor.method.call"), SemanticTokensHighlightingColors.METHOD_CALL), + new AttributesDescriptor(LanguageServerBundle.message("options.lsp.attribute.descriptor.method.declaration"), SemanticTokensHighlightingColors.METHOD_DECLARATION), + // Modifier + new AttributesDescriptor(LanguageServerBundle.message("options.lsp.attribute.descriptor.modifier"), SemanticTokensHighlightingColors.MODIFIER), + // Number + new AttributesDescriptor(LanguageServerBundle.message("options.lsp.attribute.descriptor.number"), SemanticTokensHighlightingColors.NUMBER), + // Properties + new AttributesDescriptor(LanguageServerBundle.message("options.lsp.attribute.descriptor.property.static"), SemanticTokensHighlightingColors.STATIC_PROPERTY), + new AttributesDescriptor(LanguageServerBundle.message("options.lsp.attribute.descriptor.property.static.readonly"), SemanticTokensHighlightingColors.STATIC_READONLY_PROPERTY), + new AttributesDescriptor(LanguageServerBundle.message("options.lsp.attribute.descriptor.property"), SemanticTokensHighlightingColors.READONLY_PROPERTY), + new AttributesDescriptor(LanguageServerBundle.message("options.lsp.attribute.descriptor.property.readonly"), SemanticTokensHighlightingColors.READONLY_PROPERTY), + + new AttributesDescriptor(LanguageServerBundle.message("options.lsp.attribute.descriptor.string"), SemanticTokensHighlightingColors.STRING), + }; + + @NonNls private static final Map ourTags = RainbowHighlighter.createRainbowHLM(); + static { + for (AttributesDescriptor descriptor : ourDescriptors) { + var attributeKey = descriptor.getKey(); + ourTags.put(attributeKey.getExternalName(), attributeKey); + } + } + + @Override + public boolean isRainbowType(TextAttributesKey type) { + return false; + } + + @Override + public @Nullable Language getLanguage() { + return null; + } + + @Override + public @Nullable Icon getIcon() { + return null; + } + + @Override + public @NotNull SyntaxHighlighter getHighlighter() { + return SyntaxHighlighterFactory.getSyntaxHighlighter(PlainTextLanguage.INSTANCE, null, null); + } + + @Override + public @NonNls @NotNull String getDemoText() { + return """ + public class SomeClass<T extends Runnable> { // some comment + } + """; + } + + @Override + public @Nullable Map getAdditionalHighlightingTagToDescriptorMap() { + return ourTags; + } + + @Override + public AttributesDescriptor @NotNull [] getAttributeDescriptors() { + return ourDescriptors; + } + + @Override + public ColorDescriptor @NotNull [] getColorDescriptors() { + return ColorDescriptor.EMPTY_ARRAY; + } + + @Override + public @NotNull @NlsContexts.ConfigurableName String getDisplayName() { + return LanguageServerBundle.message("lsp.semantic.tokens.color.settings.name"); + } + + @Override + public DisplayPriority getPriority() { + return DisplayPriority.LANGUAGE_SETTINGS; + } +} diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/SemanticTokensColorsProvider.java b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/SemanticTokensColorsProvider.java new file mode 100644 index 000000000..68d1962df --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/SemanticTokensColorsProvider.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.redhat.devtools.lsp4ij.features.semanticTokens; + +import com.intellij.openapi.editor.colors.TextAttributesKey; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public interface SemanticTokensColorsProvider { + + @Nullable + TextAttributesKey getTextAttributesKey(@NotNull String tokenType, + @NotNull List tokenModifiers, + @NotNull PsiFile file); +} diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/SemanticTokensData.java b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/SemanticTokensData.java new file mode 100644 index 000000000..baa357939 --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/SemanticTokensData.java @@ -0,0 +1,133 @@ +package com.redhat.devtools.lsp4ij.features.semanticTokens; + +import com.intellij.codeInsight.daemon.impl.HighlightInfo; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.colors.TextAttributesKey; +import com.intellij.psi.PsiFile; +import com.redhat.devtools.lsp4ij.LSPIJUtils; +import com.redhat.devtools.lsp4ij.features.semanticTokens.inspector.SemanticTokensHighlightInfo; +import com.redhat.devtools.lsp4ij.features.semanticTokens.inspector.SemanticTokensInspector; +import com.redhat.devtools.lsp4ij.features.semanticTokens.inspector.SemanticTokensInspectorData; +import org.eclipse.lsp4j.SemanticTokens; +import org.eclipse.lsp4j.SemanticTokensLegend; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; + +import static com.intellij.codeHighlighting.RainbowHighlighter.RAINBOW_ELEMENT; + +public class SemanticTokensData { + + private final SemanticTokens semanticTokens; + private final SemanticTokensLegend semanticTokensLegend; + private final SemanticTokensColorsProvider semanticTokensColorsProvider; + + public SemanticTokensData(@NotNull SemanticTokens semanticTokens, + @NotNull SemanticTokensLegend semanticTokensLegend, + @NotNull SemanticTokensColorsProvider semanticTokensColorsProvider) { + this.semanticTokens = semanticTokens; + this.semanticTokensLegend = semanticTokensLegend; + this.semanticTokensColorsProvider = semanticTokensColorsProvider; + } + + public SemanticTokens getSemanticTokens() { + return semanticTokens; + } + + @Nullable + public void highlight(@NotNull PsiFile file, + @NotNull Document document, + @NotNull Consumer addInfo) { + var inspector = SemanticTokensInspector.getInstance(file.getProject()); + boolean notifyInspector = inspector.hasSemanticTokensInspectorListener(); + List highlightInfos = notifyInspector ? new ArrayList<>() : null; + + try { + var dataStream = semanticTokens.getData(); + if (dataStream == null || dataStream.isEmpty()) { + return; + } + + int idx = 0; + int prevLine = 0; + int line = 0; + int offset = 0; + int length = 0; + String tokenType = null; + for (Integer data : dataStream) { + switch (idx % 5) { + case 0: // line + line += data; + break; + case 1: // offset + if (line == prevLine) { + offset += data; + } else { + offset = LSPIJUtils.toOffset(line, data, document); + } + break; + case 2: // length + length = data; + break; + case 3: // token type + tokenType = tokenType(data, semanticTokensLegend.getTokenTypes()); + break; + case 4: // token modifier + prevLine = line; + List tokenModifiers = tokenModifiers(data, semanticTokensLegend.getTokenModifiers()); + int colorIndex = 0;//UsedColors.getOrAddColorIndex((UserDataHolderEx) context, tokenType, highlighter.getColorsCount()); + int start = offset; + int end = offset + length; + TextAttributesKey colorKey = tokenType != null ? semanticTokensColorsProvider.getTextAttributesKey(tokenType, tokenModifiers, file) : null; + if (colorKey != null) { + HighlightInfo highlightInfo = HighlightInfo + .newHighlightInfo(RAINBOW_ELEMENT) + .range(start, end) + .textAttributes(colorKey) + .create(); + //HighlightInfo highlightInfo = highlighter.getInfo(colorIndex, start, end, colorKey); + addInfo.accept(highlightInfo); + } + + if(notifyInspector) { + highlightInfos.add(new SemanticTokensHighlightInfo(tokenType, tokenModifiers, start, end, colorKey)); + } + break; + } + idx++; + } + }finally { + if (notifyInspector) { + inspector.notify(new SemanticTokensInspectorData(document, file, highlightInfos)); + } + } + } + + private List tokenModifiers(Integer data, List legend) { + if (data.intValue() == 0) { + return Collections.emptyList(); + } + final var bitSet = BitSet.valueOf(new long[]{data}); + final var tokenModifiers = new ArrayList(); + for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) { + try { + tokenModifiers.add(legend.get(i)); + } catch (IndexOutOfBoundsException e) { + // no match + } + } + return tokenModifiers; + } + + private String tokenType(Integer index, List tokenTypes) { + if (index == null || index >= tokenTypes.size()) { + return null; + } + return tokenTypes.get(index); + } +} diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/SemanticTokensHighlightingColors.java b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/SemanticTokensHighlightingColors.java new file mode 100644 index 000000000..30b70563a --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/SemanticTokensHighlightingColors.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.redhat.devtools.lsp4ij.features.semanticTokens; + +import com.intellij.openapi.editor.DefaultLanguageHighlighterColors; +import com.intellij.openapi.editor.colors.TextAttributesKey; + +public class SemanticTokensHighlightingColors { + + // Namespace + public static final TextAttributesKey NAMESPACE_DECLARATION = TextAttributesKey.createTextAttributesKey("LSP_NAMESPACE_DECLARATION", DefaultLanguageHighlighterColors.CLASS_REFERENCE); + public static final TextAttributesKey NAMESPACE = TextAttributesKey.createTextAttributesKey("LSP_NAMESPACE", DefaultLanguageHighlighterColors.CLASS_NAME); + + // Class + public static final TextAttributesKey CLASS_DECLARATION = TextAttributesKey.createTextAttributesKey("LSP_CLASS_DECLARATION", DefaultLanguageHighlighterColors.CLASS_NAME); + public static final TextAttributesKey CLASS_REFERENCE = TextAttributesKey.createTextAttributesKey("LSP_CLASS_REFERENCE", DefaultLanguageHighlighterColors.CLASS_REFERENCE); + // Comment + public static final TextAttributesKey COMMENT = TextAttributesKey.createTextAttributesKey("LSP_COMMENT", DefaultLanguageHighlighterColors.LINE_COMMENT); + public static final TextAttributesKey DECORATOR = TextAttributesKey.createTextAttributesKey("LSP_DECORATOR", DefaultLanguageHighlighterColors.METADATA); + public static final TextAttributesKey ENUM = TextAttributesKey.createTextAttributesKey("LSP_ENUM", DefaultLanguageHighlighterColors.CLASS_NAME); + public static final TextAttributesKey ENUM_MEMBER = TextAttributesKey.createTextAttributesKey("LSP_ENUM_MEMBER", DefaultLanguageHighlighterColors.STATIC_FIELD); + public static final TextAttributesKey EVENT = TextAttributesKey.createTextAttributesKey("LSP_EVENT", DefaultLanguageHighlighterColors.CLASS_NAME); + + // Function + public static final TextAttributesKey FUNCTION_CALL = TextAttributesKey.createTextAttributesKey("LSP_FUNCTION_CALL", DefaultLanguageHighlighterColors.FUNCTION_CALL); + public static final TextAttributesKey FUNCTION_DECLARATION = TextAttributesKey.createTextAttributesKey("LSP_FUNCTION_DECLARATION", DefaultLanguageHighlighterColors.FUNCTION_DECLARATION); + + public static final TextAttributesKey KEYWORD = TextAttributesKey.createTextAttributesKey("LSP_KEYWORD", DefaultLanguageHighlighterColors.KEYWORD); + public static final TextAttributesKey MACRO = TextAttributesKey.createTextAttributesKey("LSP_MACRO", DefaultLanguageHighlighterColors.KEYWORD); + public static final TextAttributesKey MEMBER_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("LSP_MEMBER_ATTRIBUTES", DefaultLanguageHighlighterColors.FUNCTION_CALL); + + // Property + public static final TextAttributesKey STATIC_PROPERTY = TextAttributesKey.createTextAttributesKey("LSP_STATIC_PROPERTY", DefaultLanguageHighlighterColors.STATIC_FIELD); + public static final TextAttributesKey STATIC_READONLY_PROPERTY = TextAttributesKey.createTextAttributesKey("LSP_STATIC_READONLY_PROPERTY", STATIC_PROPERTY); + public static final TextAttributesKey PROPERTY = TextAttributesKey.createTextAttributesKey("LSP_PROPERTY", DefaultLanguageHighlighterColors.INSTANCE_FIELD); + public static final TextAttributesKey READONLY_PROPERTY = TextAttributesKey.createTextAttributesKey("LSP_READONLY_PROPERTY", PROPERTY); + + // Method + public static final TextAttributesKey METHOD_CALL = TextAttributesKey.createTextAttributesKey("LSP_METHOD_CALL", DefaultLanguageHighlighterColors.FUNCTION_CALL); + public static final TextAttributesKey METHOD_DECLARATION = TextAttributesKey.createTextAttributesKey("LSP_METHOD_DECLARATION", DefaultLanguageHighlighterColors.FUNCTION_DECLARATION); + + public static final TextAttributesKey MODIFIER = TextAttributesKey.createTextAttributesKey("LSP_MODIFIER", DefaultLanguageHighlighterColors.KEYWORD); + public static final TextAttributesKey NUMBER = TextAttributesKey.createTextAttributesKey("LSP_NUMBER", DefaultLanguageHighlighterColors.NUMBER); + + public static final TextAttributesKey STRING = TextAttributesKey.createTextAttributesKey("LSP_STRING", DefaultLanguageHighlighterColors.STRING); + + // Variable + public static final TextAttributesKey CONSTANT = TextAttributesKey.createTextAttributesKey("LSP_CONSTANT", DefaultLanguageHighlighterColors.CONSTANT); + + public static final TextAttributesKey GLOBAL_VARIABLE = TextAttributesKey.createTextAttributesKey("LSP_GLOBAL_VARIABLE", DefaultLanguageHighlighterColors.GLOBAL_VARIABLE); + + +} diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/inspector/SemanticTokensHighlightInfo.java b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/inspector/SemanticTokensHighlightInfo.java new file mode 100644 index 000000000..472386eaf --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/inspector/SemanticTokensHighlightInfo.java @@ -0,0 +1,8 @@ +package com.redhat.devtools.lsp4ij.features.semanticTokens.inspector; + +import com.intellij.openapi.editor.colors.TextAttributesKey; + +import java.util.List; + +public record SemanticTokensHighlightInfo(String tokenType, List tokenModifiers, int start, int end, TextAttributesKey colorKey) { +} diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/inspector/SemanticTokensInspector.java b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/inspector/SemanticTokensInspector.java new file mode 100644 index 000000000..6ce27c712 --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/inspector/SemanticTokensInspector.java @@ -0,0 +1,115 @@ +package com.redhat.devtools.lsp4ij.features.semanticTokens.inspector; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.impl.DocumentImpl; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; +import com.redhat.devtools.lsp4ij.LSPIJUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.stream.Collectors; + +public class SemanticTokensInspector { + + private static @NotNull Comparator HIGHLIGHT_INFO_SORTER() { + return (a, b) -> { + int diff = b.start() - a.start(); + if (diff == 0) { + return b.end() - a.end(); + } + return diff; + }; + } + + public static SemanticTokensInspector getInstance(@NotNull Project project) { + return project.getService(SemanticTokensInspector.class); + } + + private final Collection listeners = new CopyOnWriteArrayList<>(); + + public void addSemanticTokensInspectorListener(@NotNull SemanticTokensInspectorListener listener) { + this.listeners.add(listener); + } + + public void removeSemanticTokensInspectorListener(@NotNull SemanticTokensInspectorListener listener) { + this.listeners.remove(listener); + } + + public boolean hasSemanticTokensInspectorListener() { + return !listeners.isEmpty(); + } + + public static String format(SemanticTokensInspectorData data, + boolean showTextAttributes, + boolean showTokenType, + boolean showTokenModifiers, + Project project) { + var document = data.document(); + var infos = data.highlightInfos(); + if (infos.isEmpty()) { + return document.getText(); + } + infos.sort(HIGHLIGHT_INFO_SORTER()); + if (ApplicationManager.getApplication().isWriteAccessAllowed()) { + return doFormat(infos, document, showTextAttributes, showTokenType, showTokenModifiers); + } + return WriteCommandAction.runWriteCommandAction(project, (Computable) () -> { + return doFormat(infos, document, showTextAttributes, showTokenType, showTokenModifiers); + }); + } + + private static @NotNull String doFormat(List infos, + Document document, + boolean showTextAttributes, + boolean showTokenType, + boolean showTokenModifiers) { + Document newDocument = new DocumentImpl(document.getText()); + for (var info : infos) { + String infoToShow = format(info, showTextAttributes, showTokenType, showTokenModifiers); + + int end = info.end(); + String endContent = ""; + LSPIJUtils.applyEdit(end, end, endContent, newDocument, -1); + + int start = info.start(); + String startContent = "<" + infoToShow + ">"; + LSPIJUtils.applyEdit(start, start, startContent, newDocument, -1); + } + return newDocument.getText(); + } + + @Nullable + private static String format(@NotNull SemanticTokensHighlightInfo info, + boolean showTextAttributes, + boolean showTokenType, + boolean showTokenModifiers) { + StringBuilder formattedInfo = new StringBuilder(); + if (showTextAttributes) { + formattedInfo.append(info.colorKey() != null ? info.colorKey().getExternalName() : null); + } + if (showTokenType) { + if (showTextAttributes) { + formattedInfo.append(" - "); + } + formattedInfo.append(info.tokenType()); + } + if (showTokenModifiers) { + formattedInfo.append(":"); + formattedInfo.append(info.tokenModifiers().stream().collect(Collectors.joining("."))); + } + return formattedInfo.toString(); + } + + public void notify(@NotNull SemanticTokensInspectorData data) { + for (SemanticTokensInspectorListener listener : listeners) { + listener.notify(data); + } + } +} diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/inspector/SemanticTokensInspectorData.java b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/inspector/SemanticTokensInspectorData.java new file mode 100644 index 000000000..f372df054 --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/inspector/SemanticTokensInspectorData.java @@ -0,0 +1,12 @@ +package com.redhat.devtools.lsp4ij.features.semanticTokens.inspector; + +import com.intellij.openapi.editor.Document; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public record SemanticTokensInspectorData(@NotNull Document document, + @NotNull PsiFile file, + @NotNull List highlightInfos) { +} diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/inspector/SemanticTokensInspectorDetail.java b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/inspector/SemanticTokensInspectorDetail.java new file mode 100644 index 000000000..8821d7cee --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/inspector/SemanticTokensInspectorDetail.java @@ -0,0 +1,59 @@ +package com.redhat.devtools.lsp4ij.features.semanticTokens.inspector; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.ui.SimpleToolWindowPanel; +import com.intellij.ui.components.JBCheckBox; +import com.intellij.util.ui.FormBuilder; +import com.redhat.devtools.lsp4ij.LanguageServerBundle; + +import javax.swing.*; + +public class SemanticTokensInspectorDetail extends SimpleToolWindowPanel implements Disposable { + + private final SemanticTokensInspectorToolWindowPanel panel; + + private final JBCheckBox showTextAttributesCheckBox = new JBCheckBox(LanguageServerBundle.message("lsp.semantic.tokens.inspector.show.text.attributes")); + private final JBCheckBox showTokenTypeCheckBox = new JBCheckBox(LanguageServerBundle.message("lsp.semantic.tokens.inspector.show.token.type")); + private final JBCheckBox showTokenModifiersCheckBox = new JBCheckBox(LanguageServerBundle.message("lsp.semantic.tokens.inspector.show.token.modifiers")); + + public SemanticTokensInspectorDetail(SemanticTokensInspectorToolWindowPanel panel) { + super(true, false); + this.panel = panel; + this.setContent(createUI()); + } + + private JPanel createUI() { + FormBuilder builder = FormBuilder + .createFormBuilder() + .setFormLeftIndent(10); + builder.addComponent(showTextAttributesCheckBox); + builder.addComponent(showTokenTypeCheckBox); + builder.addComponent(showTokenModifiersCheckBox); + builder + .addComponentFillVertically(new JPanel(), 50); + + showTextAttributesCheckBox.addChangeListener(e -> panel.refresh()); + showTokenTypeCheckBox.addChangeListener(e -> panel.refresh()); + showTokenModifiersCheckBox.addChangeListener(e -> panel.refresh()); + + showTextAttributesCheckBox.setSelected(true); + return builder.getPanel(); + } + + public boolean isShowTextAttributes() { + return showTextAttributesCheckBox.isSelected(); + } + + public boolean isShowTokenType() { + return showTokenTypeCheckBox.isSelected(); + } + + public boolean isShowTokenModifiers() { + return showTokenModifiersCheckBox.isSelected(); + } + + @Override + public void dispose() { + + } +} diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/inspector/SemanticTokensInspectorListener.java b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/inspector/SemanticTokensInspectorListener.java new file mode 100644 index 000000000..cd284799a --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/inspector/SemanticTokensInspectorListener.java @@ -0,0 +1,6 @@ +package com.redhat.devtools.lsp4ij.features.semanticTokens.inspector; + +public interface SemanticTokensInspectorListener { + + void notify(SemanticTokensInspectorData data); +} diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/inspector/SemanticTokensInspectorToolWindowFactory.java b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/inspector/SemanticTokensInspectorToolWindowFactory.java new file mode 100644 index 000000000..d2d7cd1e3 --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/inspector/SemanticTokensInspectorToolWindowFactory.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.redhat.devtools.lsp4ij.features.semanticTokens.inspector; + +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.wm.ToolWindow; +import com.intellij.openapi.wm.ToolWindowFactory; +import com.intellij.ui.content.Content; +import com.intellij.ui.content.ContentManager; +import com.redhat.devtools.lsp4ij.LanguageServerBundle; +import org.jetbrains.annotations.NotNull; + +/** + * Semantic Tokens Inspector factory. + * + * @author Angelo ZERR + */ +public class SemanticTokensInspectorToolWindowFactory implements ToolWindowFactory, DumbAware { + + @Override + public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) { + SemanticTokensInspectorToolWindowPanel semanticTokensInspectorView = new SemanticTokensInspectorToolWindowPanel(project); + ContentManager contentManager = toolWindow.getContentManager(); + Content content = contentManager.getFactory().createContent(semanticTokensInspectorView, + LanguageServerBundle.message("lsp.semantic.tokens.inspector.title"), false); + content.setDisposer(semanticTokensInspectorView); + contentManager.addContent(content); + } +} diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/inspector/SemanticTokensInspectorToolWindowPanel.java b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/inspector/SemanticTokensInspectorToolWindowPanel.java new file mode 100644 index 000000000..127cff8c0 --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/semanticTokens/inspector/SemanticTokensInspectorToolWindowPanel.java @@ -0,0 +1,174 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package com.redhat.devtools.lsp4ij.features.semanticTokens.inspector; + +import com.intellij.icons.AllIcons; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.SimpleToolWindowPanel; +import com.intellij.openapi.util.Key; +import com.intellij.psi.PsiFile; +import com.intellij.ui.OnePixelSplitter; +import com.intellij.ui.components.JBScrollPane; +import com.intellij.ui.components.JBTabbedPane; +import com.intellij.ui.components.JBTextArea; +import com.intellij.util.ui.JBFont; +import com.intellij.util.ui.JBUI; + +import javax.swing.*; +import java.awt.*; + +/** + * Semantic Tokens Inspector view. + */ +public class SemanticTokensInspectorToolWindowPanel extends SimpleToolWindowPanel implements Disposable { + + private static final Object CURRENT_DATA_KEY = new Object() ; + + private record SemanticTokensEditorData(int tabIndex, JBTextArea editor) { + } + + private static final Key TAB_INDEX_KEY = Key.create("lsp.semantic.tokens.tab.index"); + + private final Project project; + + private SemanticTokensInspectorDetail semanticTokensInspectorDetail; + private JBTabbedPane semanticTokenTabbedPane; + + private SemanticTokensInspectorListener listener; + + + public SemanticTokensInspectorToolWindowPanel(Project project) { + super(false, true); + this.project = project; + createUI(); + } + + private void createUI() { + semanticTokensInspectorDetail = new SemanticTokensInspectorDetail(this); + var scrollPane = new JBScrollPane(semanticTokensInspectorDetail); + semanticTokenTabbedPane = new JBTabbedPane(); + var splitPane = createSplitPanel(scrollPane, new JBScrollPane(semanticTokenTabbedPane)); + super.setContent(splitPane); + super.revalidate(); + super.repaint(); + this.listener = data -> { + ApplicationManager.getApplication() + .executeOnPooledThread(() -> { + show(data); + }); + }; + SemanticTokensInspector.getInstance(project).addSemanticTokensInspectorListener(listener); + } + + private void show(SemanticTokensInspectorData data) { + String text = SemanticTokensInspector.format(data, + semanticTokensInspectorDetail.isShowTextAttributes(), + semanticTokensInspectorDetail.isShowTokenType(), + semanticTokensInspectorDetail.isShowTokenModifiers(), + project); + ApplicationManager.getApplication().invokeLater(() -> { + var editorData = getEditorFor(data.file()); + + var editor = editorData.editor(); + editor.setText(text); + + int tabIndex = editorData.tabIndex(); + JPanel tab = (JPanel) semanticTokenTabbedPane.getTabComponentAt(tabIndex); + tab.putClientProperty(CURRENT_DATA_KEY, data); + semanticTokenTabbedPane.setSelectedIndex(tabIndex); + }); + } + + private SemanticTokensEditorData getEditorFor(PsiFile file) { + SemanticTokensEditorData editorData = file.getUserData(TAB_INDEX_KEY); + if (editorData == null) { + synchronized (semanticTokenTabbedPane) { + JBTextArea editor = createEditor(); + var scrollPane = new JBScrollPane(editor); + semanticTokenTabbedPane.addTab(file.getName(), file.getIcon(0), scrollPane); + int tabIndex = semanticTokenTabbedPane.getTabCount() - 1; + + JPanel tabHeader = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0)); + tabHeader.setOpaque(false); + + JLabel label = new JLabel(file.getName()); + label.setIcon(file.getIcon(0)); + label.setBorder(JBUI.Borders.empty(1)); + label.setFont(getFont()); + tabHeader.add(label); + + JButton closeButton = new JButton(AllIcons.Actions.Close); // Replace with a proper icon + closeButton.setPreferredSize(new Dimension(16, 16)); + closeButton.setContentAreaFilled(false); + closeButton.setBorderPainted(false); + closeButton.setFocusPainted(false); + closeButton.setBorder(BorderFactory.createEmptyBorder()); + closeButton.addActionListener(e -> { + file.putUserData(TAB_INDEX_KEY, null); + semanticTokenTabbedPane.removeTabAt(tabIndex); + }); + tabHeader.add(closeButton); + + semanticTokenTabbedPane.setTabComponentAt(tabIndex, tabHeader); + label.putClientProperty(JBTabbedPane.LABEL_FROM_TABBED_PANE, Boolean.TRUE); + + editorData = new SemanticTokensEditorData(tabIndex, editor); + file.putUserData(TAB_INDEX_KEY, editorData); + } + } + return editorData; + } + + private JBTextArea createEditor() { + JBTextArea semanticTokenEditor = new JBTextArea(5, 0); + semanticTokenEditor.setLineWrap(true); + semanticTokenEditor.setWrapStyleWord(true); + semanticTokenEditor.setFont(JBFont.regular()); + return semanticTokenEditor; + } + + private static JComponent createSplitPanel(JComponent left, JComponent right) { + OnePixelSplitter splitter = new OnePixelSplitter(false, 0.15f); + splitter.setShowDividerControls(true); + splitter.setHonorComponentsMinimumSize(true); + splitter.setFirstComponent(left); + splitter.setSecondComponent(right); + return splitter; + } + + public Project getProject() { + return project; + } + + public void refresh() { + if (semanticTokenTabbedPane == null) { + return; + } + for (int i = 0; i < semanticTokenTabbedPane.getTabCount(); i++) { + JPanel tab = (JPanel) semanticTokenTabbedPane.getTabComponentAt(i); + SemanticTokensInspectorData data = (SemanticTokensInspectorData) tab.getClientProperty(CURRENT_DATA_KEY); + if (data != null) { + show(data); + } + } + } + + + @Override + public void dispose() { + SemanticTokensInspector.getInstance(project).removeSemanticTokensInspectorListener(listener); + } +} diff --git a/src/main/java/com/redhat/devtools/lsp4ij/internal/ClientCapabilitiesFactory.java b/src/main/java/com/redhat/devtools/lsp4ij/internal/ClientCapabilitiesFactory.java index cd00cd4d8..af3fd2fac 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/internal/ClientCapabilitiesFactory.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/internal/ClientCapabilitiesFactory.java @@ -78,6 +78,9 @@ public static ClientCapabilities create(Object experimental) { // Refresh support for InlayHint workspaceClientCapabilities.setInlayHint(new InlayHintWorkspaceCapabilities(Boolean.TRUE)); + // Refresh support for SemanticTokens + workspaceClientCapabilities.setSemanticTokens(new SemanticTokensWorkspaceCapabilities(Boolean.TRUE)); + return workspaceClientCapabilities; } @@ -96,9 +99,13 @@ public static ClientCapabilities create(Object experimental) { // Code Action support final var codeAction = new CodeActionCapabilities(new CodeActionLiteralSupportCapabilities( - new CodeActionKindCapabilities(List.of(CodeActionKind.QuickFix, CodeActionKind.Refactor, - CodeActionKind.RefactorExtract, CodeActionKind.RefactorInline, - CodeActionKind.RefactorRewrite, CodeActionKind.Source, + new CodeActionKindCapabilities(List.of( + CodeActionKind.QuickFix, + CodeActionKind.Refactor, + CodeActionKind.RefactorExtract, + CodeActionKind.RefactorInline, + CodeActionKind.RefactorRewrite, + CodeActionKind.Source, CodeActionKind.SourceOrganizeImports))), Boolean.TRUE); codeAction.setDataSupport(Boolean.TRUE); @@ -203,6 +210,50 @@ public static ClientCapabilities create(Object experimental) { renameCapabilities.setPrepareSupport(true); textDocumentClientCapabilities.setRename(renameCapabilities); + // textDocument/semanticTokens + var semanticTokensCapabilities = new SemanticTokensCapabilities(); + semanticTokensCapabilities.setTokenTypes(List.of( + SemanticTokenTypes.Namespace, + SemanticTokenTypes.Type, + SemanticTokenTypes.Class, + SemanticTokenTypes.Enum, + SemanticTokenTypes.Interface, + SemanticTokenTypes.Struct, + SemanticTokenTypes.TypeParameter, + SemanticTokenTypes.Parameter, + SemanticTokenTypes.Variable, + SemanticTokenTypes.Property, + SemanticTokenTypes.EnumMember, + SemanticTokenTypes.Event, + SemanticTokenTypes.Function, + SemanticTokenTypes.Method, + SemanticTokenTypes.Macro, + SemanticTokenTypes.Keyword, + SemanticTokenTypes.Modifier, + SemanticTokenTypes.Comment, + SemanticTokenTypes.String, + SemanticTokenTypes.Number, + SemanticTokenTypes.Regexp, + SemanticTokenTypes.Operator, + SemanticTokenTypes.Decorator + )); + semanticTokensCapabilities.setTokenModifiers(List.of( + SemanticTokenModifiers.Declaration, + SemanticTokenModifiers.Definition, + SemanticTokenModifiers.Readonly, + SemanticTokenModifiers.Static + /*"deprecated", + "abstract", + "async", + "modification", + "documentation", + "defaultLibrary"*/ + )); + //semanticTokensCapabilities.setFormats(List.of("relative")); + var semanticTokensClientCapabilitiesRequests = new SemanticTokensClientCapabilitiesRequests(Boolean.TRUE, Boolean.FALSE); + semanticTokensCapabilities.setRequests(semanticTokensClientCapabilitiesRequests); + textDocumentClientCapabilities.setSemanticTokens(semanticTokensCapabilities); + // Synchronization support textDocumentClientCapabilities .setSynchronization(new SynchronizationCapabilities(Boolean.TRUE, Boolean.TRUE, Boolean.TRUE)); diff --git a/src/main/java/com/redhat/devtools/lsp4ij/server/definition/LanguageServerDefinition.java b/src/main/java/com/redhat/devtools/lsp4ij/server/definition/LanguageServerDefinition.java index 98f3bc2df..df19cf4c2 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/server/definition/LanguageServerDefinition.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/server/definition/LanguageServerDefinition.java @@ -23,6 +23,8 @@ import com.redhat.devtools.lsp4ij.LanguageServerEnablementSupport; import com.redhat.devtools.lsp4ij.LanguageServerFactory; import com.redhat.devtools.lsp4ij.client.LanguageClientImpl; +import com.redhat.devtools.lsp4ij.features.semanticTokens.DefaultSemanticTokensColorsProvider; +import com.redhat.devtools.lsp4ij.features.semanticTokens.SemanticTokensColorsProvider; import org.eclipse.lsp4j.jsonrpc.Launcher; import org.eclipse.lsp4j.services.LanguageServer; import org.jetbrains.annotations.NotNull; @@ -214,4 +216,8 @@ public boolean supportsCurrentEditMode(@NotNull Project project) { public Icon getIcon() { return AllIcons.Webreferences.Server; } + + public SemanticTokensColorsProvider getSemanticTokensColorsProvider() { + return new DefaultSemanticTokensColorsProvider(); + } } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 97ae01b9c..15719ef3f 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -218,7 +218,7 @@ order="first"/> + implementation="com.redhat.devtools.lsp4ij.features.documentation.LSPDocumentationLinkHandler"/> + + + + + + + + + + + + diff --git a/src/main/resources/messages/LanguageServerBundle.properties b/src/main/resources/messages/LanguageServerBundle.properties index 4afa144a6..326b95063 100644 --- a/src/main/resources/messages/LanguageServerBundle.properties +++ b/src/main/resources/messages/LanguageServerBundle.properties @@ -157,4 +157,35 @@ lsp.refactor.rename.progress.title=Renaming ''{0}'' file with ''{1}'' new name.. # LSP Command lsp.command.error.title=Cannot execute ''{0}'' command. lsp.command.error.with.ls.content=Missing ''{0}'' command! It was referenced by ''{1}''. It needs to be contributed by an [IntelliJ plugin](https://github.com/redhat-developer/lsp4ij/blob/main/docs/DeveloperGuide.md#lsp-commands). -lsp.command.error.without.ls.content=Missing ''{0}'' command! It needs to be contributed by an [IntelliJ plugin](https://github.com/redhat-developer/lsp4ij/blob/main/docs/DeveloperGuide.md#lsp-commands). \ No newline at end of file +lsp.command.error.without.ls.content=Missing ''{0}'' command! It needs to be contributed by an [IntelliJ plugin](https://github.com/redhat-developer/lsp4ij/blob/main/docs/DeveloperGuide.md#lsp-commands). + +# LSP colors used by LSP semanticTokens +options.lsp.attribute.descriptor.class.declaration=Classes//Class declaration +options.lsp.attribute.descriptor.class.reference=Classes//Class reference +options.lsp.attribute.descriptor.comment=Comment +options.lsp.attribute.descriptor.enum=Enum +options.lsp.attribute.descriptor.enum.member=Enum member +options.lsp.attribute.descriptor.event=Event +options.lsp.attribute.descriptor.function.call=Functions//Function call +options.lsp.attribute.descriptor.function.declaration=Functions//Function declaration +options.lsp.attribute.descriptor.keyword=Keyword +options.lsp.attribute.descriptor.member=Members//Member call +options.lsp.attribute.descriptor.macro=Macro +options.lsp.attribute.descriptor.method.call=Methods//Method call +options.lsp.attribute.descriptor.method.declaration=Methods//Method declaration +options.lsp.attribute.descriptor.modifier=Modifier +options.lsp.attribute.descriptor.namespace.declaration=Namespaces//Namespace declaration +options.lsp.attribute.descriptor.namespace=Namespaces//Namespace +options.lsp.attribute.descriptor.number=Number +options.lsp.attribute.descriptor.property.static=Properties//Static property +options.lsp.attribute.descriptor.property.static.readonly=Properties//Static readonly property +options.lsp.attribute.descriptor.property=Properties//Property +options.lsp.attribute.descriptor.property.readonly=Properties//Readonly property +options.lsp.attribute.descriptor.string=String + +# Semantic Tokens +lsp.semantic.tokens.inspector.title=Semantic Tokens Inspector +lsp.semantic.tokens.inspector.show.text.attributes=Show text attributes +lsp.semantic.tokens.inspector.show.token.type=Show token type +lsp.semantic.tokens.inspector.show.token.modifiers=Show token modifiers +lsp.semantic.tokens.color.settings.name=Language Server diff --git a/src/main/resources/templates/gopls/initializationOptions.json b/src/main/resources/templates/gopls/initializationOptions.json new file mode 100644 index 000000000..8b430b5dc --- /dev/null +++ b/src/main/resources/templates/gopls/initializationOptions.json @@ -0,0 +1,3 @@ +{ + "ui.semanticTokens": true +} \ No newline at end of file