diff --git a/docs/LSPApi.md b/docs/LSPApi.md
new file mode 100644
index 000000000..d532a2b88
--- /dev/null
+++ b/docs/LSPApi.md
@@ -0,0 +1,496 @@
+## LSP Client Features
+
+The [LSPClientFeatures](https://github.com/redhat-developer/lsp4ij/blob/main/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPClientFeatures.java) API allows you to customize the behavior of LSP features, including:
+
+- [LSP codeAction feature](#lsp-codeAction-feature)
+- [LSP codeLens feature](#lsp-codeLens-feature)
+- [LSP color feature](#lsp-color-feature)
+- [LSP completion feature](#lsp-completion-feature)
+- [LSP declaration feature](#lsp-declaration-feature)
+- [LSP definition feature](#lsp-definition-feature)
+- [LSP diagnostic feature](#lsp-diagnostic-feature)
+- [LSP documentHighlight feature](#lsp-documentHighlight-feature)
+- [LSP documentLink feature](#lsp-documentLink-feature)
+- [LSP documentSymbol feature](#lsp-documentSymbol-feature)
+- [LSP foldingRange feature](#lsp-foldingRange-feature)
+- [LSP formatting feature](#lsp-formatting-feature)
+- [LSP hover feature](#lsp-hover-feature)
+- [LSP implementation feature](#lsp-implementation-feature)
+- [LSP inlayHint feature](#lsp-inlayHint-feature)
+- [LSP references feature](#lsp-references-feature)
+- [LSP rename feature](#lsp-rename-feature)
+- [LSP semanticTokens feature](#lsp-semanticTokens-feature)
+- [LSP signatureHelp feature](#lsp-signatureHelp-feature)
+- [LSP typeDefinition feature](#lsp-typeDefinition-feature)
+- [LSP usage feature](#lsp-usage-feature)
+- [LSP workspace symbol feature](#lsp-workspace-symbol-feature)
+
+You can extend these default features by:
+
+- Creating custom classes that extend the `LSP*Feature` classes (e.g., creating a class `MyLSPFormattingFeature` that extends the [LSPFormattingFeature](https://github.com/redhat-developer/lsp4ij/blob/main/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPFormattingFeature.java) to customize formatting support) and overriding specific methods to modify behavior.
+- Registering your custom classes using `LanguageServerFactory#createClientFeatures()`.
+
+```java
+package my.language.server;
+
+import com.intellij.openapi.project.Project;
+import com.redhat.devtools.lsp4ij.LanguageServerFactory;
+import com.redhat.devtools.lsp4ij.client.features.LSPClientFeatures;
+import org.jetbrains.annotations.NotNull;
+
+public class MyLanguageServerFactory implements LanguageServerFactory {
+
+ @Override
+ public @NotNull LSPClientFeatures createClientFeatures() {
+ return new LSPClientFeatures()
+ .setCompletionFeature(new MyLSPCompletionFeature()) // customize LSP completion feature
+ .setDiagnosticFeature(new MyLSPDiagnosticFeature()) // customize LSP diagnostic feature
+ .setFormattingFeature(new MyLSPFormattingFeature()); // customize LSP formatting feature
+ }
+}
+```
+
+| API | Description | Default Behaviour |
+|--------------------------------------------------------------|-------------------------------------------------------------------------------------------------|-------------------|
+| Project getProject() | Returns the project. | |
+| LanguageServerDefinition getServerDefinition() | Returns the language server definition. | |
+| boolean isServerDefinition(@NotNull String languageServerId) | Returns true if the given language server id matches the server definition and false otherwise. | |
+| ServerStatus getServerStatus() | Returns the server status. | |
+| LanguageServer getLanguageServer() | Returns the LSP4J language server. | |
+| boolean isServerStoppingRequiredWhenFilesClosed() | Returns `true` if server stopping required when files closed and `false` otherwise. | `true` |
+
+```java
+package my.language.server;
+
+import com.redhat.devtools.lsp4ij.client.features.LSPClientFeatures;
+
+public class MyLSPClientFeatures extends LSPClientFeatures {
+
+ @Override
+ public boolean isServerStoppingRequiredWhenFilesClosed() {
+ // Don't stop the language server when all files closed
+ return false;
+ }
+}
+```
+
+```java
+package my.language.server;
+
+import com.intellij.openapi.project.Project;
+import com.redhat.devtools.lsp4ij.LanguageServerFactory;
+import com.redhat.devtools.lsp4ij.client.features.LSPClientFeatures;
+import org.jetbrains.annotations.NotNull;
+
+public class MyLanguageServerFactory implements LanguageServerFactory {
+
+ @Override
+ public @NotNull LSPClientFeatures createClientFeatures() {
+ return new MyLSPClientFeatures() // customize LSP general features
+ .setCompletionFeature(new MyLSPCompletionFeature()) // customize LSP completion feature
+ .setDiagnosticFeature(new MyLSPDiagnosticFeature()) // customize LSP diagnostic feature
+ .setFormattingFeature(new MyLSPFormattingFeature()); // customize LSP formatting feature
+ }
+}
+```
+
+## LSP Base Feature
+
+| API | Description | Default Behaviour |
+|--------------------------------------------------------------|-------------------------------------------------------------------------------------------------|-------------------|
+| Project getProject() | Returns the project. | |
+| LanguageServerDefinition getServerDefinition() | Returns the language server definition. | |
+| boolean isServerDefinition(@NotNull String languageServerId) | Returns true if the given language server id matches the server definition and false otherwise. | |
+| ServerStatus getServerStatus() | Returns the server status. | |
+| LanguageServer getLanguageServer() | Returns the LSP4J language server. | |
+
+### Disable a given LSP Feature
+
+All `LSP*Feature` classes extend [AbstractLSPDocumentFeature](https://github.com/redhat-developer/lsp4ij/blob/main/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPAbstractFeature.java), which declares the `isEnabled` method that returns `true` by default:
+
+```java
+public boolean isEnabled(@NotNull PsiFile file) {
+ return true;
+}
+```
+
+By overriding this method, you can return `false` to disable a given LSP feature for your language server. This method is called before starting the language server.
+
+### Supported LSP Feature
+
+All `LSP*Feature` classes extend [AbstractLSPDocumentFeature](https://github.com/redhat-developer/lsp4ij/blob/main/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPAbstractFeature.java), which declares the `isSupported` method that uses the server capabilities:
+
+```java
+public boolean isSupported(@NotNull PsiFile file) {
+
+}
+```
+
+This method is called after starting the language server to collect the LSP server capabilities.
+
+### Project Access
+
+If you need the `Project` instance in your LSP feature to retrieve the settings of the current project, you can use the `getProject()` getter method.
+
+```java
+public class MyLSPCodeLensFeature extends LSPCodeLensFeature {
+
+ @Override
+ public boolean isEnabled(@NotNull PsiFile file) {
+ return MySettings.getInstance(super.getProject()).isCodeLensEnabled();
+ }
+
+}
+```
+
+### Server Status
+
+If you need to check the server status, you can use the `getServerStatus()` getter method.
+
+```java
+public class MyLSPCodeLensFeature extends LSPCodeLensFeature {
+
+ @Override
+ public boolean isEnabled(@NotNull PsiFile file) {
+ // Here, code lens will be disabled if the language server is not started
+ // (the LSP CodeLens will not force the start of the language server)
+ var serverStatus = super.getServerStatus();
+ return serverStatus == ServerStatus.starting || serverStatus == ServerStatus.started;
+ }
+
+}
+```
+
+## LSP CodeAction Feature
+
+| API | Description | Default Behaviour |
+|---------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
+| boolean isEnabled(PsiFile file) | Returns `true` if the LSP feature is enabled for the given file and `false` otherwise. | `true` |
+| boolean isSupported(PsiFile file) | Returns `true` if the LSP feature is supported for the given file and `false` otherwise. This supported state is called after starting the language server, which matches the file and user with the LSP server capabilities. | Check the server capability |
+| String getText(CodeAction codeAction) | Returns the IntelliJ intention action text from the given LSP code action and `null` to ignore the code action. | `codeAction.getTitle()` |
+| String getFamilyName(CodeAction codeAction) | Returns the IntelliJ intention action family name from the given LSP code action. | Label of `CodeActionKind` |
+| String getText(Command command) | Returns the IntelliJ intention action text from the given LSP command and `null` to ignore the command. | `command.getTitle()` |
+| String getFamilyName(Command command) | Returns the IntelliJ intention action family name from the given LSP command. | "LSP Command" |
+
+## LSP CodeLens Feature
+
+| API | Description | Default Behaviour |
+|-----------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------|
+| boolean isEnabled(PsiFile file) | Returns `true` if the LSP feature is enabled for the given file and `false` otherwise. | `true` |
+| boolean isSupported(PsiFile file) | Returns `true` if the LSP feature is supported for the given file and `false` otherwise. This supported state is called after starting the language server, which matches the file and user with the LSP server capabilities. | Check the server capability |
+| CodeVisionEntry createCodeVisionEntry(CodeLens codeLens, String providerId, LSPCodeLensContext codeLensContext) | Creates an IntelliJ `CodeVisionEntry` from the given LSP `CodeLens` and `null` otherwise (to ignore the LSP `CodeLens`). | |
+| String getText(CodeLens codeLens) | Returns the code vision entry text from the LSP `CodeLens` and `null` otherwise (to ignore the LSP `CodeLens`). | `codeLens.getCommand().getTitle()` |
+
+Here is an example of code that avoids creating an IntelliJ `CodeVisionEntry` when the LSP `CodeLens` command is equal to `Run`:
+
+```java
+package my.language.server;
+
+import com.redhat.devtools.lsp4ij.client.features.LSPCodeLensFeature;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class MyLSPCodeLensFeature extends LSPCodeLensFeature {
+
+ @Override
+ @Nullable
+ public String getText(CodeLens codeLens) {
+ Command command = codeLens.getCommand();
+ if ("Run".equals(command)) {
+ return null;
+ }
+ return super.getText(codeLens);
+ }
+
+}
+```
+
+## LSP Color Feature
+
+| API | Description | Default Behaviour |
+|--------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
+| boolean isEnabled(PsiFile file) | Returns `true` if the LSP feature is enabled for the given file and `false` otherwise. | `true` |
+| boolean isSupported(PsiFile file) | Returns `true` if the LSP feature is supported for the given file and `false` otherwise. This supported state is called after starting the language server, which matches the file and user with the LSP server capabilities. | Check the server capability |
+
+## LSP Completion Feature
+
+| API | Description | Default Behaviour |
+|---------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------|
+| boolean isEnabled(PsiFile file) | Returns `true` if the LSP feature is enabled for the given file and `false` otherwise. | `true` |
+| boolean isSupported(PsiFile file) | Returns `true` if the LSP feature is supported for the given file and `false` otherwise. This supported state is called after starting the language server, which matches the file and user with the LSP server capabilities. | Check the server capability |
+| LookupElement createLookupElement(CompletionItem item, LSPCompletionContext context) | Create a completion lookup element from the given LSP completion item and context and null otherwise. | |
+| void renderLookupElement(LookupElementPresentation presentation, CompletionItem item) | Update the given IntelliJ lookup element presentation with the given LSP completion item. | |
+| String getItemText(CompletionItem item) | Returns the IntelliJ lookup item text from the given LSP completion item and null otherwise. | `item.getLabel()` |
+| String getTypeText(CompletionItem item) | Returns the IntelliJ lookup type text from the given LSP completion item and null otherwise. | `item.getDetail()` |
+| Icon getIcon(CompletionItem item) | Returns the IntelliJ lookup icon from the given LSP completion item and null otherwise. | default icon from `item.getKind()` |
+| boolean isStrikeout(CompletionItem item) | Returns true if the IntelliJ lookup is strike out and false otherwise. | use `item.getDeprecated()` or `item.getTags().contains(CompletionItemTag.Deprecated)` |
+| String getTailText(CompletionItem item) | Returns the IntelliJ lookup tail text from the given LSP completion item and null otherwise. | `item.getLabelDetails().getDetail()` |
+| boolean isItemTextBold(CompletionItem item) | Returns the IntelliJ lookup item text bold from the given LSP completion item and null otherwise. | `item.getKind() == CompletionItemKind.Keyword` |
+
+## LSP Declaration Feature
+
+| API | Description | Default Behaviour |
+|-----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
+| boolean isEnabled(PsiFile file) | Returns `true` if the LSP feature is enabled for the given file and `false` otherwise. | `true` |
+| boolean isSupported(PsiFile file) | Returns `true` if the LSP feature is supported for the given file and `false` otherwise. This supported state is called after starting the language server, which matches the file and user with the LSP server capabilities. | Check the server capability |
+
+## LSP Definition Feature
+
+| API | Description | Default Behaviour |
+|-----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
+| boolean isEnabled(PsiFile file) | Returns `true` if the LSP feature is enabled for the given file and `false` otherwise. | `true` |
+| boolean isSupported(PsiFile file) | Returns `true` if the LSP feature is supported for the given file and `false` otherwise. This supported state is called after starting the language server, which matches the file and user with the LSP server capabilities. | Check the server capability |
+
+## LSP Diagnostic Feature
+
+| API | Description | Default Behaviour |
+|-----------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------|-------------------|
+| boolean isEnabled(PsiFile file) | Returns `true` if the LSP feature is enabled for the given file and `false` otherwise. | `true` |
+| void createAnnotation(Diagnostic diagnostic, Document document, List fixes, AnnotationHolder holder) | Creates an IntelliJ annotation in the given holder using the provided LSP diagnostic and fixes. | |
+| HighlightSeverity getHighlightSeverity(Diagnostic diagnostic) | Returns the IntelliJ `HighlightSeverity` from the given diagnostic and `null` otherwise. | |
+| String getMessage(Diagnostic diagnostic) | Returns the message of the given diagnostic. | |
+| String getToolTip(Diagnostic diagnostic) | Returns the annotation tooltip from the given LSP diagnostic. | |
+| ProblemHighlightType getProblemHighlightType(List tags) | Returns the `ProblemHighlightType` from the given tags and `null` otherwise. | |
+
+Here is an example of code that avoids creating an IntelliJ annotation when the LSP diagnostic code is equal to `ignore`:
+
+```java
+package my.language.server;
+
+import com.intellij.lang.annotation.HighlightSeverity;
+import com.redhat.devtools.lsp4ij.client.features.LSPDiagnosticFeature;
+import org.eclipse.lsp4j.Diagnostic;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class MyLSPDiagnosticFeature extends LSPDiagnosticFeature {
+
+ @Override
+ public @Nullable HighlightSeverity getHighlightSeverity(@NotNull Diagnostic diagnostic) {
+ if (diagnostic.getCode() != null &&
+ diagnostic.getCode().isLeft() &&
+ "ignore".equals(diagnostic.getCode().getLeft())) {
+ // return a null HighlightSeverity when LSP diagnostic code is equals
+ // to 'ignore' to avoid creating an IntelliJ annotation
+ return null;
+ }
+ return super.getHighlightSeverity(diagnostic);
+ }
+
+}
+```
+
+## LSP DocumentHighlight Feature
+
+| API | Description | Default Behaviour |
+|-----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
+| boolean isEnabled(PsiFile file) | Returns `true` if the LSP feature is enabled for the given file and `false` otherwise. | `true` |
+| boolean isSupported(PsiFile file) | Returns `true` if the LSP feature is supported for the given file and `false` otherwise. This supported state is called after starting the language server, which matches the file and user with the LSP server capabilities. | Check the server capability |
+
+## LSP DocumentLink Feature
+
+| API | Description | Default Behaviour |
+|-----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
+| boolean isEnabled(PsiFile file) | Returns `true` if the LSP feature is enabled for the given file and `false` otherwise. | `true` |
+| boolean isSupported(PsiFile file) | Returns `true` if the LSP feature is supported for the given file and `false` otherwise. This supported state is called after starting the language server, which matches the file and user with the LSP server capabilities. | Check the server capability |
+
+## LSP DocumentSymbol Feature
+
+| API | Description | Default Behaviour |
+|-------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
+| boolean isEnabled(PsiFile file) | Returns `true` if the LSP feature is enabled for the given file and `false` otherwise. | `true` |
+| boolean isSupported(PsiFile file) | Returns `true` if the LSP feature is supported for the given file and `false` otherwise. This supported state is called after starting the language server, which matches the file and user with the LSP server capabilities. | Check the server capability |
+| StructureViewTreeElement getStructureViewTreeElement(DocumentSymbolData documentSymbol) | | |
+| String getPresentableText(DocumentSymbol documentSymbol, PsiFile psiFile) | | |
+| Icon getIcon(DocumentSymbol documentSymbol, PsiFile psiFile) | | |
+| String getPresentableText(DocumentSymbol documentSymbol, PsiFile psiFile, boolean unused) | | |
+| void navigate(DocumentSymbol documentSymbol, PsiFile psiFile, boolean requestFocus) | | | | | |
+| boolean canNavigate(DocumentSymbol documentSymbol, PsiFile psiFile) | | |
+
+Here is an example of code to customize the document symbol used in `Structure`:
+
+```java
+package com.redhat.devtools.lsp4ij.client;
+
+import com.intellij.ide.structureView.StructureViewTreeElement;
+import com.intellij.psi.PsiFile;
+import com.redhat.devtools.lsp4ij.client.features.LSPDocumentSymbolFeature;
+import com.redhat.devtools.lsp4ij.features.documentSymbol.DocumentSymbolData;
+import org.eclipse.lsp4j.DocumentSymbol;
+import org.eclipse.lsp4j.SymbolKind;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+
+public class MyLSPDocumentSymbolFeature extends LSPDocumentSymbolFeature {
+
+ @Override
+ public @Nullable StructureViewTreeElement getStructureViewTreeElement(DocumentSymbolData documentSymbol) {
+ if ("ignore".equals(documentSymbol.getDocumentSymbol().getName())) {
+ // Ignore document symbol with "ignore" name.
+ return null;
+ }
+ return super.getStructureViewTreeElement(documentSymbol);
+ }
+
+ @Override
+ public @Nullable Icon getIcon(@NotNull DocumentSymbol documentSymbol, @NotNull PsiFile psiFile, boolean unused) {
+ if (documentSymbol.getKind() == SymbolKind.Class) {
+ // Returns custom icon for 'Class' symbol kind
+ return ...;
+ }
+ return super.getIcon(documentSymbol, psiFile, unused);
+ }
+
+ @Override
+ public @Nullable String getPresentableText(@NotNull DocumentSymbol documentSymbol, @NotNull PsiFile psiFile) {
+ if (documentSymbol.getKind() == SymbolKind.Class) {
+ // Returns custom presentable text for 'Class' symbol kind
+ return ...;
+ }
+ return super.getPresentableText(documentSymbol, psiFile);
+ }
+
+ @Override
+ public @Nullable String getLocationString(@NotNull DocumentSymbol documentSymbol, @NotNull PsiFile psiFile) {
+ if (documentSymbol.getKind() == SymbolKind.Class) {
+ // Returns custom location string for 'Class' symbol kind
+ return ...;
+ }
+ return super.getLocationString(documentSymbol, psiFile);
+ }
+}
+```
+
+## LSP FoldingRange Feature
+
+| API | Description | Default Behaviour |
+|-----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
+| boolean isEnabled(PsiFile file) | Returns `true` if the LSP feature is enabled for the given file and `false` otherwise. | `true` |
+| boolean isSupported(PsiFile file) | Returns `true` if the LSP feature is supported for the given file and `false` otherwise. This supported state is called after starting the language server, which matches the file and user with the LSP server capabilities. | Check the server capability |
+
+## LSP Formatting Feature
+
+| API | Description | Default Behaviour |
+|-------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
+| boolean isEnabled(PsiFile file) | Returns `true` if the LSP feature is enabled for the given file and `false` otherwise. | `true` |
+| boolean isSupported(PsiFile file) | Returns `true` if the LSP feature is supported for the given file and `false` otherwise. This supported state is called after starting the language server, which matches the file and user with the LSP server capabilities. | Check the server capability |
+| boolean isExistingFormatterOverrideable(PsiFile file) | Returns `true` if existing formatters are overrideable and `false` otherwise. | `false` |
+
+Here is an example of code that allows executing the LSP formatter even if there is a specific formatter registered by an IntelliJ plugin:
+
+```java
+package my.language.server;
+
+import com.intellij.psi.PsiFile;
+import com.redhat.devtools.lsp4ij.client.features.LSPFormattingFeature;
+import org.jetbrains.annotations.NotNull;
+
+public class MyLSPFormattingFeature extends LSPFormattingFeature {
+
+ @Override
+ protected boolean isExistingFormatterOverrideable(@NotNull PsiFile file) {
+ // By default, isExistingFormatterOverrideable returns false if it has a custom formatter with psi
+ // returns true even if there is a custom formatter
+ return true;
+ }
+}
+```
+
+## LSP Hover Feature
+
+| API | Description | Default Behaviour |
+|--------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
+| boolean isEnabled(PsiFile file) | Returns `true` if the LSP feature is enabled for the given file and `false` otherwise. | `true` |
+| boolean isSupported(PsiFile file) | Returns `true` if the LSP feature is supported for the given file and `false` otherwise. This supported state is called after starting the language server, which matches the file and user with the LSP server capabilities. | Check the server capability |
+| String getContent(MarkupContent content, PsiFile file) | Returns the HTML content from the given LSP Markup content and null otherwise. | |
+
+## LSP Implementation Feature
+
+| API | Description | Default Behaviour |
+|-----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
+| boolean isEnabled(PsiFile file) | Returns `true` if the LSP feature is enabled for the given file and `false` otherwise. | `true` |
+| boolean isSupported(PsiFile file) | Returns `true` if the LSP feature is supported for the given file and `false` otherwise. This supported state is called after starting the language server, which matches the file and user with the LSP server capabilities. | Check the server capability |
+
+## LSP InlayHint Feature
+
+| API | Description | Default Behaviour |
+|-----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
+| boolean isEnabled(PsiFile file) | Returns `true` if the LSP feature is enabled for the given file and `false` otherwise. | `true` |
+| boolean isSupported(PsiFile file) | Returns `true` if the LSP feature is supported for the given file and `false` otherwise. This supported state is called after starting the language server, which matches the file and user with the LSP server capabilities. | Check the server capability |
+
+## LSP References Feature
+
+| API | Description | Default Behaviour |
+|-----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
+| boolean isEnabled(PsiFile file) | Returns `true` if the LSP feature is enabled for the given file and `false` otherwise. | `true` |
+| boolean isSupported(PsiFile file) | Returns `true` if the LSP feature is supported for the given file and `false` otherwise. This supported state is called after starting the language server, which matches the file and user with the LSP server capabilities. | Check the server capability |
+
+## LSP Rename Feature
+
+| API | Description | Default Behaviour |
+|-----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
+| boolean isEnabled(PsiFile file) | Returns `true` if the LSP feature is enabled for the given file and `false` otherwise. | `true` |
+| boolean isSupported(PsiFile file) | Returns `true` if the LSP feature is supported for the given file and `false` otherwise. This supported state is called after starting the language server, which matches the file and user with the LSP server capabilities. | Check the server capability |
+
+## LSP SemanticTokens Feature
+
+| API | Description | Default Behaviour |
+|--------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
+| boolean isEnabled(PsiFile file) | Returns `true` if the LSP feature is enabled for the given file and `false` otherwise. | `true` |
+| boolean isSupported(PsiFile file) | Returns `true` if the LSP feature is supported for the given file and `false` otherwise. This supported state is called after starting the language server, which matches the file and user with the LSP server capabilities. | Check the server capability |
+| TextAttributesKey getTextAttributesKey(@NotNull String tokenType, List tokenModifiers, PsiFile file) | Returns the TextAttributesKey to use for colorization for the given token type and given token modifiers and null otherwise. | |
+
+```java
+package my.language.server;
+
+import com.intellij.psi.PsiFile;
+import com.redhat.devtools.lsp4ij.client.features.LSPSemanticTokensFeature;
+import org.jetbrains.annotations.NotNull;
+
+public class MyLSPSemanticTokensFeature extends LSPSemanticTokensFeature {
+
+ @Override
+ public @Nullable TextAttributesKey getTextAttributesKey(@NotNull String tokenType,
+ @NotNull List tokenModifiers,
+ @NotNull PsiFile file) {
+ if ("myClass".equals(tokenType)) {
+ TextAttributesKey myClass = ...
+ return myClass;
+ }
+ if ("ignore".equals(tokenType)) {
+ return null;
+ }
+ return super.getTextAttributesKey(tokenType, tokenModifiers, file);
+ }
+}
+```
+
+## LSP SignatureHelp Feature
+
+| API | Description | Default Behaviour |
+|-----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
+| boolean isEnabled(PsiFile file) | Returns `true` if the LSP feature is enabled for the given file and `false` otherwise. | `true` |
+| boolean isSupported(PsiFile file) | Returns `true` if the LSP feature is supported for the given file and `false` otherwise. This supported state is called after starting the language server, which matches the file and user with the LSP server capabilities. | Check the server capability |
+
+## LSP TypeDefinition Feature
+
+| API | Description | Default Behaviour |
+|-----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
+| boolean isEnabled(PsiFile file) | Returns `true` if the LSP feature is enabled for the given file and `false` otherwise. | `true` |
+| boolean isSupported(PsiFile file) | Returns `true` if the LSP feature is supported for the given file and `false` otherwise. This supported state is called after starting the language server, which matches the file and user with the LSP server capabilities. | Check the server capability |
+
+## LSP Usage Feature
+
+| API | Description | Default Behaviour |
+|-----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
+| boolean isEnabled(PsiFile file) | Returns `true` if the LSP feature is enabled for the given file and `false` otherwise. | `true` |
+| boolean isSupported(PsiFile file) | Returns `true` if the LSP feature is supported for the given file and `false` otherwise. This supported state is called after starting the language server, which matches the file and user with the LSP server capabilities. | Check the server capability |
+
+## LSP Workspace Symbol Feature
+
+| API | Description | Default Behaviour |
+|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------|
+| boolean isEnabled() | Returns `true` if the LSP feature is enabled and `false` otherwise. | `true` when server is starting/started |
+| boolean isSupported() | Returns `true` if the LSP feature is supported and `false` otherwise. This supported state is called after starting the language server, which matches the LSP server capabilities. | Check the server capability |
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/ConnectDocumentToLanguageServerSetupParticipant.java b/src/main/java/com/redhat/devtools/lsp4ij/ConnectDocumentToLanguageServerSetupParticipant.java
index d75d67d0b..517224353 100644
--- a/src/main/java/com/redhat/devtools/lsp4ij/ConnectDocumentToLanguageServerSetupParticipant.java
+++ b/src/main/java/com/redhat/devtools/lsp4ij/ConnectDocumentToLanguageServerSetupParticipant.java
@@ -80,7 +80,7 @@ private static void connectToLanguageServer(@NotNull VirtualFile file, @NotNull
// Server capabilities filter is set to null to avoid waiting
// for the start of the server when server capabilities are checked
LanguageServiceAccessor.getInstance(project)
- .getLanguageServers(file, null);
+ .getLanguageServers(file, null, null);
}
}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/LanguageServerFactory.java b/src/main/java/com/redhat/devtools/lsp4ij/LanguageServerFactory.java
index e7831e973..320f7daef 100644
--- a/src/main/java/com/redhat/devtools/lsp4ij/LanguageServerFactory.java
+++ b/src/main/java/com/redhat/devtools/lsp4ij/LanguageServerFactory.java
@@ -13,6 +13,7 @@
import com.intellij.openapi.project.Project;
import com.redhat.devtools.lsp4ij.client.LanguageClientImpl;
import com.redhat.devtools.lsp4ij.server.StreamConnectionProvider;
+import com.redhat.devtools.lsp4ij.client.features.LSPClientFeatures;
import org.eclipse.lsp4j.services.LanguageServer;
import org.jetbrains.annotations.NotNull;
@@ -50,4 +51,7 @@ public interface LanguageServerFactory {
return LanguageServer.class;
}
+ @NotNull default LSPClientFeatures createClientFeatures() {
+ return new LSPClientFeatures();
+ }
}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/LanguageServerItem.java b/src/main/java/com/redhat/devtools/lsp4ij/LanguageServerItem.java
index 3310c7526..a885e1f39 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.client.features.LSPClientFeatures;
import com.redhat.devtools.lsp4ij.features.semanticTokens.SemanticTokensColorsProvider;
import org.eclipse.lsp4j.*;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
@@ -52,6 +53,15 @@ public LanguageServerWrapper getServerWrapper() {
return serverWrapper;
}
+ /**
+ * Returns the LSP client features.
+ *
+ * @return the LSP client features.
+ */
+ public LSPClientFeatures getClientFeatures() {
+ return getServerWrapper().getClientFeatures();
+ }
+
/**
* Returns the server capabilities of the language server.
*
@@ -340,12 +350,12 @@ public static boolean isImplementationSupported(@Nullable ServerCapabilities ser
}
/**
- * Returns true if the language server can support folding and false otherwise.
+ * Returns true if the language server can support folding range and false otherwise.
*
* @param serverCapabilities the server capabilities.
- * @return true if the language server can support folding and false otherwise.
+ * @return true if the language server can support folding range and false otherwise.
*/
- public static boolean isFoldingSupported(@Nullable ServerCapabilities serverCapabilities) {
+ public static boolean isFoldingRangeSupported(@Nullable ServerCapabilities serverCapabilities) {
return serverCapabilities != null &&
hasCapability(serverCapabilities.getFoldingRangeProvider());
}
@@ -510,7 +520,7 @@ private static boolean hasCapability(Boolean capability) {
}
public SemanticTokensColorsProvider getSemanticTokensColorsProvider() {
- return getServerWrapper().getServerDefinition().getSemanticTokensColorsProvider();
+ return getClientFeatures().getSemanticTokensFeature();
}
/**
@@ -542,4 +552,12 @@ public static boolean isWorkspaceSymbolSupported(@Nullable ServerCapabilities se
return serverCapabilities != null &&
hasCapability(serverCapabilities.getWorkspaceSymbolProvider());
}
+
+ public static boolean isUsageSupported(ServerCapabilities serverCapabilities) {
+ return LanguageServerItem.isDeclarationSupported(serverCapabilities) ||
+ LanguageServerItem.isTypeDefinitionSupported(serverCapabilities) ||
+ LanguageServerItem.isDefinitionSupported(serverCapabilities) ||
+ LanguageServerItem.isReferencesSupported(serverCapabilities) ||
+ LanguageServerItem.isImplementationSupported(serverCapabilities);
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/LanguageServerManager.java b/src/main/java/com/redhat/devtools/lsp4ij/LanguageServerManager.java
index 9a12c5b0c..035c8d552 100644
--- a/src/main/java/com/redhat/devtools/lsp4ij/LanguageServerManager.java
+++ b/src/main/java/com/redhat/devtools/lsp4ij/LanguageServerManager.java
@@ -16,6 +16,9 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+
public class LanguageServerManager {
private final Project project;
@@ -276,4 +279,20 @@ public void stop(@NotNull LanguageServerDefinition serverDefinition,
}
}
+ public CompletableFuture<@Nullable LanguageServerItem> getLanguageServer(@NotNull String languageServerId) {
+ LanguageServerDefinition serverDefinition = LanguageServersRegistry.getInstance().getServerDefinition(languageServerId);
+ if (serverDefinition == null) {
+ return CompletableFuture.completedFuture(null);
+ }
+ return LanguageServiceAccessor.getInstance(project)
+ .getLanguageServers(Set.of(serverDefinition), null, null)
+ .thenApply(servers -> {
+ if (servers.isEmpty()) {
+ return null;
+ }
+ return servers.get(0);
+ });
+
+ }
+
}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/LanguageServerWrapper.java b/src/main/java/com/redhat/devtools/lsp4ij/LanguageServerWrapper.java
index 16f714eb1..31760f244 100644
--- a/src/main/java/com/redhat/devtools/lsp4ij/LanguageServerWrapper.java
+++ b/src/main/java/com/redhat/devtools/lsp4ij/LanguageServerWrapper.java
@@ -31,6 +31,7 @@
import com.redhat.devtools.lsp4ij.lifecycle.LanguageServerLifecycleManager;
import com.redhat.devtools.lsp4ij.lifecycle.NullLanguageServerLifecycleManager;
import com.redhat.devtools.lsp4ij.server.*;
+import com.redhat.devtools.lsp4ij.client.features.LSPClientFeatures;
import com.redhat.devtools.lsp4ij.server.definition.LanguageServerDefinition;
import org.eclipse.lsp4j.*;
import org.eclipse.lsp4j.jsonrpc.Launcher;
@@ -108,6 +109,8 @@ public class LanguageServerWrapper implements Disposable {
private FileOperationsManager fileOperationsManager;
+ private LSPClientFeatures clientFeatures;
+
/* Backwards compatible constructor */
public LanguageServerWrapper(@NotNull Project project, @NotNull LanguageServerDefinition serverDefinition) {
this(project, serverDefinition, null);
@@ -226,7 +229,7 @@ public synchronized void start() throws LanguageServerException {
// Add error log
provider.addLogErrorHandler(error -> {
- ServerMessageHandler.logMessage(this, new MessageParams(MessageType.Error, error));
+ ServerMessageHandler.logMessage(this.getServerDefinition(), new MessageParams(MessageType.Error, error), getProject());
});
// Starting process...
@@ -408,6 +411,9 @@ public void dispose() {
this.disposed = true;
stop();
stopDispatcher();
+ if (clientFeatures != null) {
+ clientFeatures.dispose();
+ }
}
public boolean isDisposed() {
@@ -708,7 +714,7 @@ public void disconnect(URI path, boolean stopIfNoOpenedFiles) {
synchronizer.getDocument().removeDocumentListener(synchronizer);
synchronizer.documentClosed();
}
- if (stopIfNoOpenedFiles && this.connectedDocuments.isEmpty()) {
+ if (getClientFeatures().isServerStoppingRequiredWhenFilesClosed() && stopIfNoOpenedFiles && this.connectedDocuments.isEmpty()) {
if (this.serverDefinition.getLastDocumentDisconnectedTimeout() != 0 && !ApplicationManager.getApplication().isUnitTestMode()) {
removeStopTimer(true);
startStopTimer();
@@ -763,6 +769,10 @@ public LanguageServer getServer() {
}
}
+ public LanguageServer getLanguageServer() {
+ return languageServer;
+ }
+
/**
* Starts the language server and returns a CompletableFuture waiting for the
* server to be initialized. If done in the UI stream, a job will be created
@@ -1174,4 +1184,20 @@ public boolean isSignatureTriggerCharactersSupported(String charTyped) {
}
return triggerCharacters.contains(charTyped);
}
+
+ public LSPClientFeatures getClientFeatures() {
+ if (clientFeatures == null) {
+ clientFeatures = getOrCreateClientFeatures();
+ }
+ return clientFeatures;
+ }
+
+ private synchronized LSPClientFeatures getOrCreateClientFeatures() {
+ if (clientFeatures != null) {
+ return clientFeatures;
+ }
+ LSPClientFeatures clientFeatures = getServerDefinition().createClientFeatures();
+ clientFeatures.setServerWrapper(this);
+ return clientFeatures;
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/LanguageServiceAccessor.java b/src/main/java/com/redhat/devtools/lsp4ij/LanguageServiceAccessor.java
index 8f30de70a..adef01f58 100644
--- a/src/main/java/com/redhat/devtools/lsp4ij/LanguageServiceAccessor.java
+++ b/src/main/java/com/redhat/devtools/lsp4ij/LanguageServiceAccessor.java
@@ -21,21 +21,19 @@
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.vfs.VirtualFile;
+import com.redhat.devtools.lsp4ij.client.features.LSPClientFeatures;
import com.redhat.devtools.lsp4ij.server.definition.LanguageServerDefinition;
import com.redhat.devtools.lsp4ij.server.definition.LanguageServerDefinitionListener;
import com.redhat.devtools.lsp4ij.server.definition.LanguageServerFileAssociation;
-import org.eclipse.lsp4j.ServerCapabilities;
-import org.eclipse.lsp4j.services.LanguageServer;
+import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.net.URI;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
-import java.util.stream.Collectors;
/**
* Language server accessor.
@@ -44,6 +42,7 @@
*
* If adopters need to collect some language servers, LSP4IJ must provide an API for that.
*/
+@ApiStatus.Internal
public class LanguageServiceAccessor implements Disposable {
private static final Logger LOGGER = LoggerFactory.getLogger(LanguageServiceAccessor.class);
@@ -63,11 +62,11 @@ public void handleRemoved(@NotNull LanguageServerDefinitionListener.LanguageServ
List serversToDispose = startedServers
.stream()
.filter(server -> event.serverDefinitions.contains(server.getServerDefinition()))
- .collect(Collectors.toList());
+ .toList();
serversToDispose.forEach(LanguageServerWrapper::dispose);
// Remove all servers which are removed from the cache
synchronized (startedServers) {
- startedServers.removeAll(serversToDispose);
+ serversToDispose.forEach(startedServers::remove);
}
}
@@ -81,7 +80,7 @@ public void handleChanged(@NotNull LanguageServerChangedEvent event) {
List serversToRestart = startedServers
.stream()
.filter(server -> event.serverDefinition.equals(server.getServerDefinition()))
- .collect(Collectors.toList());
+ .toList();
serversToRestart.forEach(LanguageServerWrapper::restart);
}
}
@@ -123,7 +122,7 @@ public void findAndStartLanguageServerIfNeeded(LanguageServerDefinition definiti
if (forceStart) {
// The language server must be started even if there is no open file corresponding to it.
LinkedHashSet matchedServers = new LinkedHashSet<>();
- collectLanguageServersFromDefinition(null, project, Set.of(definition), matchedServers);
+ collectLanguageServersFromDefinition(null, Set.of(definition), matchedServers, null);
for (var ls : matchedServers) {
ls.restart();
}
@@ -144,7 +143,7 @@ public void findAndStartLanguageServerIfNeeded(LanguageServerDefinition definiti
*/
private void findAndStartLsForFile(@NotNull VirtualFile file,
@NotNull LanguageServerDefinition definition) {
- getLanguageServers(file, null, definition);
+ getLanguageServers(file, null, null, definition);
}
/**
@@ -154,7 +153,6 @@ private void findAndStartLsForFile(@NotNull VirtualFile file,
* @param filter the filter.
* @return true if the given file matches one of started language server with the given filter and false otherwise.
*/
- @NotNull
public boolean hasAny(@NotNull VirtualFile file,
@NotNull Predicate filter) {
var startedServers = getStartedServers();
@@ -176,22 +174,56 @@ public boolean hasAny(@NotNull VirtualFile file,
}
@NotNull
- public CompletableFuture> getLanguageServers(@NotNull VirtualFile file,
- @Nullable Predicate filter) {
- return getLanguageServers(file, filter, null);
+ public CompletableFuture> getLanguageServers(@Nullable Predicate beforeStartingServerFilter,
+ @Nullable Predicate afterStartingServerFilter) {
+ Set serverDefinitions = new HashSet<>(LanguageServersRegistry.getInstance().getServerDefinitions());
+ return getLanguageServers(serverDefinitions, beforeStartingServerFilter, afterStartingServerFilter);
}
@NotNull
- public CompletableFuture> getLanguageServers(@NotNull VirtualFile file,
- @Nullable Predicate filter,
- @Nullable LanguageServerDefinition matchServerDefinition) {
- URI uri = LSPIJUtils.toUri(file);
- if (uri == null) {
+ CompletableFuture> getLanguageServers(@NotNull Set serverDefinitions,
+ @Nullable Predicate beforeStartingServerFilter,
+ @Nullable Predicate afterStartingServerFilter) {
+ Set matchedServers = new LinkedHashSet<>();
+ collectLanguageServersFromDefinition(null, serverDefinitions, matchedServers, beforeStartingServerFilter);
+ final List servers = Collections.synchronizedList(new ArrayList<>());
+ try {
+ return CompletableFuture.allOf(matchedServers
+ .stream()
+ .filter(LanguageServerWrapper::isEnabled)
+ .map(wrapper ->
+ wrapper.getInitializedServer()
+ .thenComposeAsync(server -> {
+ if (server != null &&
+ (afterStartingServerFilter == null || afterStartingServerFilter.test(wrapper.getClientFeatures()))) {
+ servers.add(new LanguageServerItem(server, wrapper));
+ }
+ return CompletableFuture.completedFuture(null);
+ }))
+ .toArray(CompletableFuture[]::new))
+ .thenApply(theVoid -> servers);
+ } catch (ProcessCanceledException cancellation) {
+ throw cancellation;
+ } catch (Exception e) {
+ LOGGER.warn(e.getLocalizedMessage(), e);
return CompletableFuture.completedFuture(Collections.emptyList());
}
+ }
+
+ @NotNull
+ public CompletableFuture> getLanguageServers(@NotNull VirtualFile file,
+ @Nullable Predicate beforeStartingServerFilter,
+ @Nullable Predicate afterStartingServerFilter) {
+ return getLanguageServers(file, beforeStartingServerFilter, afterStartingServerFilter, null);
+ }
+ @NotNull
+ private CompletableFuture> getLanguageServers(@NotNull VirtualFile file,
+ @Nullable Predicate beforeStartingServerFilter,
+ @Nullable Predicate afterStartingServerFilter,
+ @Nullable LanguageServerDefinition matchServerDefinition) {
// Collect started (or not) language servers which matches the given file.
- CompletableFuture> matchedServers = getMatchedLanguageServersWrappers(file, matchServerDefinition);
+ CompletableFuture> matchedServers = getMatchedLanguageServersWrappers(file, matchServerDefinition, beforeStartingServerFilter);
if (matchedServers.isDone() && matchedServers.getNow(Collections.emptyList()).isEmpty()) {
// None language servers matches the given file
return CompletableFuture.completedFuture(Collections.emptyList());
@@ -209,12 +241,12 @@ public CompletableFuture> getLanguageServers(@NotNull V
return matchedServers
.thenComposeAsync(result -> CompletableFuture.allOf(result
.stream()
+ .filter(LanguageServerWrapper::isEnabled)
.map(wrapper ->
wrapper.getInitializedServer()
.thenComposeAsync(server -> {
if (server != null &&
- wrapper.isEnabled() &&
- (filter == null || filter.test(wrapper.getServerCapabilities()))) {
+ (afterStartingServerFilter == null || afterStartingServerFilter.test(wrapper.getClientFeatures()))) {
return wrapper.connect(file, document);
}
return CompletableFuture.completedFuture(null);
@@ -249,14 +281,15 @@ public void projectClosing(Project project) {
@NotNull
private CompletableFuture> getMatchedLanguageServersWrappers(
@NotNull VirtualFile file,
- @Nullable LanguageServerDefinition matchServerDefinition) {
+ @Nullable LanguageServerDefinition matchServerDefinition,
+ @Nullable Predicate beforeStartingServerFilter) {
MatchedLanguageServerDefinitions mappings = getMatchedLanguageServerDefinitions(file, project, false);
if (mappings == MatchedLanguageServerDefinitions.NO_MATCH) {
// There are no mapping for the given file
return CompletableFuture.completedFuture(Collections.emptyList());
}
- LinkedHashSet matchedServers = new LinkedHashSet<>();
+ Set matchedServers = new LinkedHashSet<>();
// Collect sync server definitions
var serverDefinitions = mappings.getMatched();
@@ -264,9 +297,9 @@ private CompletableFuture> getMatchedLanguageS
if (!serverDefinitions.contains(matchServerDefinition)) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
- collectLanguageServersFromDefinition(file, project, Set.of(matchServerDefinition), matchedServers);
+ collectLanguageServersFromDefinition(file, Set.of(matchServerDefinition), matchedServers, beforeStartingServerFilter);
} else {
- collectLanguageServersFromDefinition(file, project, serverDefinitions, matchedServers);
+ collectLanguageServersFromDefinition(file, serverDefinitions, matchedServers, beforeStartingServerFilter);
}
CompletableFuture> async = mappings.getAsyncMatched();
@@ -274,7 +307,7 @@ private CompletableFuture> getMatchedLanguageS
// Collect async server definitions
return async
.thenApply(asyncServerDefinitions -> {
- collectLanguageServersFromDefinition(file, project, asyncServerDefinitions, matchedServers);
+ collectLanguageServersFromDefinition(file, asyncServerDefinitions, matchedServers, beforeStartingServerFilter);
return matchedServers;
});
}
@@ -284,19 +317,23 @@ private CompletableFuture> getMatchedLanguageS
/**
* Get or create a language server wrapper for the given server definitions and add then to the given matched servers.
*
- * @param file the file.
- * @param fileProject the file project.
- * @param serverDefinitions the server definitions.
- * @param matchedServers the list to update with get/created language server.
+ * @param file the file.
+ * @param serverDefinitions the server definitions.
+ * @param matchedServers the list to update with get/created language server.
+ * @param beforeStartingServerFilter
*/
- private void collectLanguageServersFromDefinition(@Nullable VirtualFile file, @NotNull Project fileProject, @NotNull Set serverDefinitions, @NotNull Set matchedServers) {
+ private void collectLanguageServersFromDefinition(@Nullable VirtualFile file,
+ @NotNull Set serverDefinitions,
+ @NotNull Set matchedServers,
+ @Nullable Predicate beforeStartingServerFilter) {
synchronized (startedServers) {
for (var serverDefinition : serverDefinitions) {
boolean useExistingServer = false;
// Loop for started language servers
for (var startedServer : startedServers) {
if (startedServer.getServerDefinition().equals(serverDefinition)
- && (file == null || startedServer.canOperate(file))) {
+ && (file == null || startedServer.canOperate(file))
+ && (beforeStartingServerFilter == null || beforeStartingServerFilter.test(startedServer.getClientFeatures()))) {
// A started language server match the file, use it
matchedServers.add(startedServer);
useExistingServer = true;
@@ -305,9 +342,11 @@ private void collectLanguageServersFromDefinition(@Nullable VirtualFile file, @N
}
if (!useExistingServer) {
// There are none started servers which matches the file, create and add it.
- LanguageServerWrapper wrapper = new LanguageServerWrapper(fileProject, serverDefinition);
- startedServers.add(wrapper);
- matchedServers.add(wrapper);
+ LanguageServerWrapper wrapper = new LanguageServerWrapper(project, serverDefinition);
+ if (beforeStartingServerFilter == null || beforeStartingServerFilter.test(wrapper.getClientFeatures())) {
+ startedServers.add(wrapper);
+ matchedServers.add(wrapper);
+ }
}
}
}
@@ -356,9 +395,9 @@ public CompletableFuture> getAsyncMatched() {
* @param ignoreMatch true if {@link DocumentMatcher} must be ignored when mapping matches the given file and false otherwise.
* @return the matched language server definitions for the given file.
*/
- public MatchedLanguageServerDefinitions getMatchedLanguageServerDefinitions(@NotNull VirtualFile file,
- @NotNull Project fileProject,
- boolean ignoreMatch) {
+ private MatchedLanguageServerDefinitions getMatchedLanguageServerDefinitions(@NotNull VirtualFile file,
+ @NotNull Project fileProject,
+ boolean ignoreMatch) {
Set syncMatchedDefinitions = null;
Set asyncMatchedDefinitions = null;
@@ -371,9 +410,7 @@ public MatchedLanguageServerDefinitions getMatchedLanguageServerDefinitions(@Not
languages.add(language);
}
FileType fileType = file.getFileType();
- if (fileType != null) {
- languages.add(fileType);
- }
+ languages.add(fileType);
while (!languages.isEmpty()) {
Object contentType = languages.poll();
@@ -429,16 +466,14 @@ public MatchedLanguageServerDefinitions getMatchedLanguageServerDefinitions(@Not
final Set serverDefinitions = Collections.synchronizedSet(new HashSet<>());
async = CompletableFuture.allOf(asyncMatchedDefinitions
.stream()
- .map(mapping -> {
- return mapping
- .matchAsync(file, fileProject)
- .thenApply(result -> {
- if (result) {
- serverDefinitions.add(mapping.getServerDefinition());
- }
- return null;
- });
- }
+ .map(mapping -> mapping
+ .matchAsync(file, fileProject)
+ .thenApply(result -> {
+ if (result) {
+ serverDefinitions.add(mapping.getServerDefinition());
+ }
+ return null;
+ })
)
.toArray(CompletableFuture[]::new))
.thenApply(theVoid -> serverDefinitions);
@@ -456,48 +491,6 @@ private static boolean match(VirtualFile file, Project fileProject, LanguageServ
return mapping.match(file, fileProject);
}
-
- /**
- * Gets list of running LS satisfying a capability predicate. This does not
- * start any matching language servers, it returns the already running ones.
- *
- * @param request
- * @return list of Language Servers
- */
- @NotNull
- public List getActiveLanguageServers(Predicate request) {
- return getLanguageServers(null, request, true);
- }
-
- /**
- * Gets list of LS initialized for given project
- *
- * @param onlyActiveLS true if this method should return only the already running
- * language servers, otherwise previously started language servers
- * will be re-activated
- * @return list of Language Servers
- */
- @NotNull
- public List getLanguageServers(@Nullable Project project,
- Predicate request, boolean onlyActiveLS) {
- List serverInfos = new ArrayList<>();
- for (LanguageServerWrapper wrapper : startedServers) {
- if ((!onlyActiveLS || wrapper.isActive()) && (project == null || wrapper.canOperate(project))) {
- @Nullable
- LanguageServer server = wrapper.getServer();
- if (server == null) {
- continue;
- }
- if (request == null
- || wrapper.getServerCapabilities() == null /* null check is workaround for https://github.com/TypeFox/ls-api/issues/47 */
- || request.test(wrapper.getServerCapabilities())) {
- serverInfos.add(server);
- }
- }
- }
- return serverInfos;
- }
-
@Override
public void dispose() {
LanguageServersRegistry.getInstance().removeLanguageServerDefinitionListener(serverDefinitionListener);
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/ServerMessageHandler.java b/src/main/java/com/redhat/devtools/lsp4ij/ServerMessageHandler.java
index 358d28a38..34d4f6ad1 100644
--- a/src/main/java/com/redhat/devtools/lsp4ij/ServerMessageHandler.java
+++ b/src/main/java/com/redhat/devtools/lsp4ij/ServerMessageHandler.java
@@ -51,20 +51,13 @@ private ServerMessageHandler() {
/**
* Implements the LSP window/logMessage specification.
*
- * @param serverWrapper the language server wrapper
- * @param params the message request parameters
- */
- public static void logMessage(LanguageServerWrapper serverWrapper, MessageParams params) {
- logMessage(serverWrapper.getServerDefinition(), params, serverWrapper.getProject() );
- }
-
- /**
- * Implements the LSP window/logMessage specification.
- *
- * @param serverWrapper the language server wrapper
- * @param params the message request parameters
+ * @param serverDefinition the language server definition.
+ * @param params the message request parameters.
+ * @param project the project.
*/
- public static void logMessage(LanguageServerDefinition serverDefinition, MessageParams params, Project project) {
+ public static void logMessage(@NotNull LanguageServerDefinition serverDefinition,
+ @NotNull MessageParams params,
+ @NotNull Project project) {
LSPConsoleToolWindowPanel.showLog(serverDefinition, params, project );
}
@@ -107,17 +100,16 @@ public static void showMessage(@NotNull String title,
/**
* Implements the LSP window/showMessageRequest specification.
*
- * @param serverWrapper the language server wrapper
- * @param params the message request parameters
+ * @param languageServerName the language server name.
+ * @param params the message request parameters.
+ * @param project the project.
*/
- public static CompletableFuture showMessageRequest(@NotNull LanguageServerWrapper serverWrapper,
+ public static CompletableFuture showMessageRequest(@NotNull String languageServerName,
@NotNull ShowMessageRequestParams params,
@NotNull Project project) {
CompletableFuture future = new CompletableFuture<>();
ApplicationManager.getApplication()
.invokeLater(() -> {
- String languageServerName = serverWrapper.getServerDefinition().getDisplayName();
- String title = params.getMessage();
String content = MarkdownConverter.getInstance(project).toHtml(params.getMessage());
final Notification notification = new Notification(
LSP_WINDOW_SHOW_MESSAGE_REQUEST_GROUP_ID,
@@ -147,7 +139,7 @@ public void actionPerformed(@NotNull AnActionEvent e) {
}
});
- Notifications.Bus.notify(notification, serverWrapper.getProject());
+ Notifications.Bus.notify(notification, project);
var balloon= notification.getBalloon();
if (balloon != null) {
balloon.addListener(new JBPopupListener() {
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 9d9c5d5fa..e40d7eb4b 100644
--- a/src/main/java/com/redhat/devtools/lsp4ij/client/LanguageClientImpl.java
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/LanguageClientImpl.java
@@ -24,6 +24,7 @@
import com.redhat.devtools.lsp4ij.features.diagnostics.LSPDiagnosticHandler;
import com.redhat.devtools.lsp4ij.features.progress.LSPProgressManager;
import com.redhat.devtools.lsp4ij.internal.InlayHintsFactoryBridge;
+import com.redhat.devtools.lsp4ij.server.definition.LanguageServerDefinition;
import org.eclipse.lsp4j.*;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.LanguageServer;
@@ -62,7 +63,7 @@ public Project getProject() {
return project;
}
- public final void connect(LanguageServer server, LanguageServerWrapper wrapper) {
+ public final void connect(@NotNull LanguageServer server, @NotNull LanguageServerWrapper wrapper) {
this.server = server;
this.wrapper = wrapper;
this.diagnosticHandler = new LSPDiagnosticHandler(wrapper);
@@ -73,19 +74,23 @@ protected final LanguageServer getLanguageServer() {
return server;
}
+ public LanguageServerDefinition getServerDefinition() {
+ return wrapper.getServerDefinition();
+ }
+
@Override
public void telemetryEvent(Object object) {
// TODO
}
@Override
- public final CompletableFuture showMessageRequest(ShowMessageRequestParams requestParams) {
- return ServerMessageHandler.showMessageRequest(wrapper, requestParams, getProject());
+ public CompletableFuture showMessageRequest(ShowMessageRequestParams requestParams) {
+ return ServerMessageHandler.showMessageRequest(getServerDefinition().getDisplayName(), requestParams, getProject());
}
@Override
- public final void showMessage(MessageParams messageParams) {
- ServerMessageHandler.showMessage(wrapper.getServerDefinition().getDisplayName(), messageParams, getProject());
+ public void showMessage(MessageParams messageParams) {
+ ServerMessageHandler.showMessage(getServerDefinition().getDisplayName(), messageParams, getProject());
}
@Override
@@ -94,17 +99,17 @@ public CompletableFuture showDocument(ShowDocumentParams par
}
@Override
- public final void publishDiagnostics(PublishDiagnosticsParams diagnostics) {
+ public void publishDiagnostics(PublishDiagnosticsParams diagnostics) {
this.diagnosticHandler.accept(diagnostics);
}
@Override
- public final void logMessage(MessageParams message) {
- CompletableFuture.runAsync(() -> ServerMessageHandler.logMessage(wrapper, message));
+ public void logMessage(MessageParams message) {
+ CompletableFuture.runAsync(() -> ServerMessageHandler.logMessage(getServerDefinition(), message, getProject()));
}
@Override
- public final CompletableFuture applyEdit(ApplyWorkspaceEditParams params) {
+ public CompletableFuture applyEdit(ApplyWorkspaceEditParams params) {
CompletableFuture future = new CompletableFuture<>();
WriteCommandAction.runWriteCommandAction(getProject(), () -> {
LSPIJUtils.applyWorkspaceEdit(params.getEdit());
@@ -125,9 +130,7 @@ public CompletableFuture unregisterCapability(UnregistrationParams params)
@Override
public CompletableFuture> workspaceFolders() {
- return CompletableFuture.supplyAsync(() -> {
- return LSPIJUtils.toWorkspaceFolders(project);
- });
+ return CompletableFuture.supplyAsync(() -> LSPIJUtils.toWorkspaceFolders(project));
}
@Override
@@ -144,7 +147,7 @@ private void refreshInlayHintsForAllOpenedFiles() {
ReadAction.nonBlocking((Callable) () -> {
for (var fileData : wrapper.getConnectedFiles()) {
VirtualFile file = fileData.getFile();
- final PsiFile psiFile = LSPIJUtils.getPsiFile(file, project);
+ PsiFile psiFile = LSPIJUtils.getPsiFile(file, project);
if (psiFile != null) {
Editor[] editors = LSPIJUtils.editorsForFile(file, getProject());
InlayHintsFactoryBridge.refreshInlayHints(psiFile, editors, true);
@@ -170,7 +173,7 @@ private void refreshSemanticTokensForAllOpenedFiles() {
ReadAction.nonBlocking((Callable) () -> {
for (var fileData : wrapper.getConnectedFiles()) {
VirtualFile file = fileData.getFile();
- final PsiFile psiFile = LSPIJUtils.getPsiFile(file, project);
+ PsiFile psiFile = LSPIJUtils.getPsiFile(file, project);
if (psiFile != null) {
// Evict the semantic tokens cache
LSPFileSupport.getSupport(psiFile).getSemanticTokensSupport().cancel();
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/AbstractLSPDocumentFeature.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/AbstractLSPDocumentFeature.java
new file mode 100644
index 000000000..026931eda
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/AbstractLSPDocumentFeature.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * 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 https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.client.features;
+
+import com.intellij.psi.PsiFile;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Abstract class for any LSP document feature.
+ */
+@ApiStatus.Experimental
+public abstract class AbstractLSPDocumentFeature extends AbstractLSPFeature {
+
+ /**
+ * Returns true if the LSP feature is enabled for the given file and false otherwise.
+ *
+ *
+ * This enable state is called before starting the language server which matches the file.
+ *
+ *
+ * @param file the file.
+ *
+ * @return true if the LSP feature is enabled for the given file and false otherwise.
+ */
+ public boolean isEnabled(@NotNull PsiFile file) {
+ return true;
+ }
+
+ /**
+ * Returns true if the LSP feature is supported for the given file and false otherwise.
+ *
+ *
+ * This supported state is called after starting the language server which matches the file and user the LSP server capabilities.
+ *
+ *
+ * @param file the file.
+ *
+ * @return true if the LSP feature is supported for the given file and false otherwise.
+ */
+ public abstract boolean isSupported(@NotNull PsiFile file);
+
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/AbstractLSPFeature.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/AbstractLSPFeature.java
new file mode 100644
index 000000000..953546c34
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/AbstractLSPFeature.java
@@ -0,0 +1,103 @@
+/*******************************************************************************
+ * 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 https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.client.features;
+
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.project.Project;
+import com.redhat.devtools.lsp4ij.ServerStatus;
+import com.redhat.devtools.lsp4ij.server.definition.LanguageServerDefinition;
+import org.eclipse.lsp4j.services.LanguageServer;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Abstract class for any LSP feature.
+ */
+@ApiStatus.Experimental
+public abstract class AbstractLSPFeature implements Disposable {
+
+ private LSPClientFeatures clientFeatures;
+
+ /**
+ * Returns the LSP server support.
+ *
+ * @return the LSP server support.
+ */
+ public final @NotNull LSPClientFeatures getClientFeatures() {
+ return clientFeatures;
+ }
+
+ /**
+ * Returns the project.
+ *
+ * @return the project.
+ */
+ @NotNull
+ public final Project getProject() {
+ return clientFeatures.getProject();
+ }
+
+ /**
+ * Returns the server status.
+ *
+ * @return the server status.
+ */
+ @NotNull
+ public final ServerStatus getServerStatus() {
+ return getClientFeatures().getServerStatus();
+ }
+
+ /**
+ * Returns the language server definition.
+ *
+ * @return the language server definition.
+ */
+ @NotNull
+ public final LanguageServerDefinition getServerDefinition() {
+ return getClientFeatures().getServerDefinition();
+ }
+
+ /**
+ * Returns true if the given language server id matches the server definition and false otherwise.
+ *
+ * @param languageServerId the language server id.
+ * @return true if the given language server id matches the server definition and false otherwise.
+ */
+ public boolean isServerDefinition(@NotNull String languageServerId) {
+ return getClientFeatures().isServerDefinition(languageServerId);
+ }
+
+ /**
+ * Returns the LSP4J language server.
+ *
+ * @return the LSP4J language server.
+ */
+ @Nullable
+ public final LanguageServer getLanguageServer() {
+ return getClientFeatures().getLanguageServer();
+ }
+
+ /**
+ * Set the LSP server support.
+ *
+ * @param clientFeatures the LSP client features.
+ */
+ @ApiStatus.Internal
+ public final void setClientFeatures(@NotNull LSPClientFeatures clientFeatures) {
+ this.clientFeatures = clientFeatures;
+ }
+
+ @Override
+ public void dispose() {
+ clientFeatures = null;
+ }
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/AbstractLSPWorkspaceFeature.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/AbstractLSPWorkspaceFeature.java
new file mode 100644
index 000000000..561b06867
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/AbstractLSPWorkspaceFeature.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * 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 https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.client.features;
+
+import org.jetbrains.annotations.ApiStatus;
+
+/**
+ * Abstract class for any LSP workspace feature.
+ */
+@ApiStatus.Experimental
+public abstract class AbstractLSPWorkspaceFeature extends AbstractLSPFeature {
+
+ /**
+ * Returns true if the LSP feature is enabled and false otherwise.
+ *
+ *
+ * This enable state is called before starting the language server.
+ *
+ *
+ * @return true if the LSP feature is enabled and false otherwise.
+ */
+ public boolean isEnabled() {
+ return true;
+ }
+
+ /**
+ * Returns true if the LSP feature is supported false otherwise.
+ *
+ *
+ * This supported state is called after starting the language server which matches the file and user the LSP server capabilities.
+ *
+ *
+ *
+ * @return true if the LSP feature is supported and false otherwise.
+ */
+ public abstract boolean isSupported();
+
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPClientFeatures.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPClientFeatures.java
new file mode 100644
index 000000000..3add1b123
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPClientFeatures.java
@@ -0,0 +1,783 @@
+/*******************************************************************************
+ * 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 https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.client.features;
+
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.project.Project;
+import com.redhat.devtools.lsp4ij.LanguageServerWrapper;
+import com.redhat.devtools.lsp4ij.ServerStatus;
+import com.redhat.devtools.lsp4ij.server.definition.LanguageServerDefinition;
+import org.eclipse.lsp4j.services.LanguageServer;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * LSP client features.
+ */
+@ApiStatus.Experimental
+public class LSPClientFeatures implements Disposable {
+
+ private LanguageServerWrapper serverWrapper;
+
+ private LSPCodeActionFeature codeActionFeature;
+
+ private LSPCodeLensFeature codeLensFeature;
+
+ private LSPColorFeature colorFeature;
+
+ private LSPCompletionFeature completionFeature;
+
+ private LSPDeclarationFeature declarationFeature;
+
+ private LSPDefinitionFeature definitionFeature;
+
+ private LSPDocumentLinkFeature documentLinkFeature;
+
+ private LSPDocumentHighlightFeature documentHighlightFeature;
+
+ private LSPDocumentSymbolFeature documentSymbolFeature;
+
+ private LSPDiagnosticFeature diagnosticFeature;
+
+ private LSPFoldingRangeFeature foldingRangeFeature;
+
+ private LSPFormattingFeature formattingFeature;
+
+ private LSPImplementationFeature implementationFeature;
+
+ private LSPInlayHintFeature inlayHintFeature;
+
+ private LSPHoverFeature hoverFeature;
+
+ private LSPReferencesFeature referencesFeature;
+
+ private LSPRenameFeature renameFeature;
+
+ private LSPSemanticTokensFeature semanticTokensFeature;
+
+ private LSPSignatureHelpFeature signatureHelpFeature;
+
+ private LSPTypeDefinitionFeature typeDefinitionFeature;
+
+ private LSPUsageFeature usageFeature;
+
+ private LSPWorkspaceSymbolFeature workspaceSymbolFeature;
+
+ /**
+ * Returns the project.
+ *
+ * @return the project.
+ */
+ @NotNull
+ public final Project getProject() {
+ return getServerWrapper().getProject();
+ }
+
+ /**
+ * Returns the language server definition.
+ *
+ * @return the language server definition.
+ */
+ @NotNull
+ public final LanguageServerDefinition getServerDefinition() {
+ return getServerWrapper().getServerDefinition();
+ }
+
+ /**
+ * Returns true if the given language server id matches the server definition and false otherwise.
+ *
+ * @param languageServerId the language server id.
+ * @return true if the given language server id matches the server definition and false otherwise.
+ */
+ public boolean isServerDefinition(@NotNull String languageServerId) {
+ return languageServerId.equals(getServerDefinition().getId());
+ }
+
+ /**
+ * Returns the server status.
+ *
+ * @return the server status.
+ */
+ @NotNull
+ public final ServerStatus getServerStatus() {
+ return getServerWrapper().getServerStatus();
+ }
+
+ /**
+ * Returns the LSP4J language server.
+ *
+ * @return the LSP4J language server.
+ */
+ @Nullable
+ public final LanguageServer getLanguageServer() {
+ return getServerWrapper().getLanguageServer();
+ }
+
+ /**
+ * Returns true if server stopping required when files closed and false otherwise.
+ *
+ * @return true if server stopping required when files closed and false otherwise.
+ */
+ public boolean isServerStoppingRequiredWhenFilesClosed() {
+ return true;
+ }
+
+ /**
+ * Returns the LSP codeAction feature.
+ *
+ * @return the LSP codeAction feature.
+ */
+ @NotNull
+ public final LSPCodeActionFeature getCodeActionFeature() {
+ if (codeActionFeature == null) {
+ setCodeActionFeature(new LSPCodeActionFeature());
+ }
+ return codeActionFeature;
+ }
+
+ /**
+ * Initialize the LSP codeAction feature.
+ *
+ * @param codeActionFeature the LSP codeAction feature.
+ * @return the LSP client features.
+ */
+ public final LSPClientFeatures setCodeActionFeature(@NotNull LSPCodeActionFeature codeActionFeature) {
+ codeActionFeature.setClientFeatures(this);
+ this.codeActionFeature = codeActionFeature;
+ return this;
+ }
+
+ /**
+ * Returns the LSP codeLens feature.
+ *
+ * @return the LSP codeLens feature.
+ */
+ @NotNull
+ public final LSPCodeLensFeature getCodeLensFeature() {
+ if (codeLensFeature == null) {
+ setCodeLensFeature(new LSPCodeLensFeature());
+ }
+ return codeLensFeature;
+ }
+
+ /**
+ * Initialize the LSP codeLens feature.
+ *
+ * @param codeLensFeature the LSP codeLens feature.
+ * @return the LSP client features.
+ */
+ public final LSPClientFeatures setCodeLensFeature(@NotNull LSPCodeLensFeature codeLensFeature) {
+ codeLensFeature.setClientFeatures(this);
+ this.codeLensFeature = codeLensFeature;
+ return this;
+ }
+
+ /**
+ * Returns the LSP color feature.
+ *
+ * @return the LSP color feature.
+ */
+ @NotNull
+ public final LSPColorFeature getColorFeature() {
+ if (colorFeature == null) {
+ setColorFeature(new LSPColorFeature());
+ }
+ return colorFeature;
+ }
+
+ /**
+ * Initialize the LSP color feature.
+ *
+ * @param colorFeature the LSP color feature.
+ * @return the LSP client features.
+ */
+ public final LSPClientFeatures setColorFeature(@NotNull LSPColorFeature colorFeature) {
+ colorFeature.setClientFeatures(this);
+ this.colorFeature = colorFeature;
+ return this;
+ }
+
+ /**
+ * Returns the LSP completion feature.
+ *
+ * @return the LSP completion feature.
+ */
+ @NotNull
+ public final LSPCompletionFeature getCompletionFeature() {
+ if (completionFeature == null) {
+ setCompletionFeature(new LSPCompletionFeature());
+ }
+ return completionFeature;
+ }
+
+ /**
+ * Initialize the LSP completion feature.
+ *
+ * @param completionFeature the LSP completion feature.
+ * @return the LSP client features.
+ */
+ public final LSPClientFeatures setCompletionFeature(@NotNull LSPCompletionFeature completionFeature) {
+ completionFeature.setClientFeatures(this);
+ this.completionFeature = completionFeature;
+ return this;
+ }
+
+ /**
+ * Returns the LSP declaration feature.
+ *
+ * @return the LSP declaration feature.
+ */
+ @NotNull
+ public final LSPDeclarationFeature getDeclarationFeature() {
+ if (declarationFeature == null) {
+ setDeclarationFeature(new LSPDeclarationFeature());
+ }
+ return declarationFeature;
+ }
+
+ /**
+ * Initialize the LSP declaration feature.
+ *
+ * @param declarationFeature the LSP declaration feature.
+ * @return the LSP client features.
+ */
+ public final LSPClientFeatures setDeclarationFeature(@NotNull LSPDeclarationFeature declarationFeature) {
+ declarationFeature.setClientFeatures(this);
+ this.declarationFeature = declarationFeature;
+ return this;
+ }
+
+ /**
+ * Returns the LSP definition feature.
+ *
+ * @return the LSP definition feature.
+ */
+ @NotNull
+ public final LSPDefinitionFeature getDefinitionFeature() {
+ if (definitionFeature == null) {
+ setDefinitionFeature(new LSPDefinitionFeature());
+ }
+ return definitionFeature;
+ }
+
+ /**
+ * Initialize the LSP definition feature.
+ *
+ * @param definitionFeature the LSP definition feature.
+ * @return the LSP client features.
+ */
+ public final LSPClientFeatures setDefinitionFeature(@NotNull LSPDefinitionFeature definitionFeature) {
+ definitionFeature.setClientFeatures(this);
+ this.definitionFeature = definitionFeature;
+ return this;
+ }
+
+ /**
+ * Returns the LSP documentHighlight feature.
+ *
+ * @return the LSP documentHighlight feature.
+ */
+ @NotNull
+ public final LSPDocumentHighlightFeature getDocumentHighlightFeature() {
+ if (documentHighlightFeature == null) {
+ setDocumentHighlightFeature(new LSPDocumentHighlightFeature());
+ }
+ return documentHighlightFeature;
+ }
+
+ /**
+ * Initialize the LSP documentHighlight feature.
+ *
+ * @param documentHighlightFeature the LSP documentHighlight feature.
+ * @return the LSP client features.
+ */
+ public final LSPClientFeatures setDocumentHighlightFeature(@NotNull LSPDocumentHighlightFeature documentHighlightFeature) {
+ documentHighlightFeature.setClientFeatures(this);
+ this.documentHighlightFeature = documentHighlightFeature;
+ return this;
+ }
+
+ /**
+ * Returns the LSP documentLink feature.
+ *
+ * @return the LSP documentLink feature.
+ */
+ @NotNull
+ public final LSPDocumentLinkFeature getDocumentLinkFeature() {
+ if (documentLinkFeature == null) {
+ setDocumentLinkFeature(new LSPDocumentLinkFeature());
+ }
+ return documentLinkFeature;
+ }
+
+ /**
+ * Initialize the LSP documentLink feature.
+ *
+ * @param documentLinkFeature the LSP documentLink feature.
+ * @return the LSP client features.
+ */
+ public final LSPClientFeatures setDocumentLinkFeature(@NotNull LSPDocumentLinkFeature documentLinkFeature) {
+ documentLinkFeature.setClientFeatures(this);
+ this.documentLinkFeature = documentLinkFeature;
+ return this;
+ }
+
+ /**
+ * Returns the LSP documentSymbol feature.
+ *
+ * @return the LSP documentSymbol feature.
+ */
+ @NotNull
+ public final LSPDocumentSymbolFeature getDocumentSymbolFeature() {
+ if (documentSymbolFeature == null) {
+ setDocumentSymbolFeature(new LSPDocumentSymbolFeature());
+ }
+ return documentSymbolFeature;
+ }
+
+ /**
+ * Initialize the LSP documentSymbol feature.
+ *
+ * @param documentSymbolFeature the LSP documentSymbol feature.
+ * @return the LSP client features.
+ */
+ public final LSPClientFeatures setDocumentSymbolFeature(@NotNull LSPDocumentSymbolFeature documentSymbolFeature) {
+ documentSymbolFeature.setClientFeatures(this);
+ this.documentSymbolFeature = documentSymbolFeature;
+ return this;
+ }
+
+ /**
+ * Returns the LSP diagnostic feature.
+ *
+ * @return the LSP diagnostic feature.
+ */
+ @NotNull
+ public final LSPDiagnosticFeature getDiagnosticFeature() {
+ if (diagnosticFeature == null) {
+ setDiagnosticFeature(new LSPDiagnosticFeature());
+ }
+ return diagnosticFeature;
+ }
+
+ /**
+ * Initialize the LSP diagnostic feature.
+ *
+ * @param diagnosticFeature the LSP diagnostic feature.
+ * @return the LSP client feature.
+ */
+ public LSPClientFeatures setDiagnosticFeature(@NotNull LSPDiagnosticFeature diagnosticFeature) {
+ diagnosticFeature.setClientFeatures(this);
+ this.diagnosticFeature = diagnosticFeature;
+ return this;
+ }
+
+ /**
+ * Returns the LSP foldingRange feature.
+ *
+ * @return the LSP foldingRange feature.
+ */
+ @NotNull
+ public final LSPFoldingRangeFeature getFoldingRangeFeature() {
+ if (foldingRangeFeature == null) {
+ setFoldingRangeFeature(new LSPFoldingRangeFeature());
+ }
+ return foldingRangeFeature;
+ }
+
+ /**
+ * Initialize the LSP foldingRange feature.
+ *
+ * @param foldingRangeFeature the LSP foldingRange feature.
+ * @return the LSP client features.
+ */
+ public final LSPClientFeatures setFoldingRangeFeature(@NotNull LSPFoldingRangeFeature foldingRangeFeature) {
+ foldingRangeFeature.setClientFeatures(this);
+ this.foldingRangeFeature = foldingRangeFeature;
+ return this;
+ }
+
+ /**
+ * Returns the LSP formatting feature.
+ *
+ * @return the LSP formatting feature.
+ */
+ @NotNull
+ public final LSPFormattingFeature getFormattingFeature() {
+ if (formattingFeature == null) {
+ setFormattingFeature(new LSPFormattingFeature());
+ }
+ return formattingFeature;
+ }
+
+ /**
+ * Initialize the LSP formatting feature.
+ *
+ * @param formattingFeature the LSP formatting feature.
+ * @return the LSP client features.
+ */
+ public final LSPClientFeatures setFormattingFeature(@NotNull LSPFormattingFeature formattingFeature) {
+ formattingFeature.setClientFeatures(this);
+ this.formattingFeature = formattingFeature;
+ return this;
+ }
+
+ /**
+ * Returns the LSP implementation feature.
+ *
+ * @return the LSP implementation feature.
+ */
+ @NotNull
+ public final LSPImplementationFeature getImplementationFeature() {
+ if (implementationFeature == null) {
+ setImplementationFeature(new LSPImplementationFeature());
+ }
+ return implementationFeature;
+ }
+
+ /**
+ * Initialize the LSP implementation feature.
+ *
+ * @param implementationFeature the LSP implementation feature.
+ * @return the LSP client features.
+ */
+ public final LSPClientFeatures setImplementationFeature(@NotNull LSPImplementationFeature implementationFeature) {
+ implementationFeature.setClientFeatures(this);
+ this.implementationFeature = implementationFeature;
+ return this;
+ }
+
+ /**
+ * Returns the LSP inlayHint feature.
+ *
+ * @return the LSP inlayHint feature.
+ */
+ @NotNull
+ public final LSPInlayHintFeature getInlayHintFeature() {
+ if (inlayHintFeature == null) {
+ setInlayHintFeature(new LSPInlayHintFeature());
+ }
+ return inlayHintFeature;
+ }
+
+ /**
+ * Initialize the LSP inlayHint feature.
+ *
+ * @param inlayHintFeature the LSP inlayHint feature.
+ * @return the LSP client features.
+ */
+ public final LSPClientFeatures setInlayHintFeature(@NotNull LSPInlayHintFeature inlayHintFeature) {
+ inlayHintFeature.setClientFeatures(this);
+ this.inlayHintFeature = inlayHintFeature;
+ return this;
+ }
+
+ /**
+ * Returns the LSP hover feature.
+ *
+ * @return the LSP hover feature.
+ */
+ @NotNull
+ public final LSPHoverFeature getHoverFeature() {
+ if (hoverFeature == null) {
+ setHoverFeature(new LSPHoverFeature());
+ }
+ return hoverFeature;
+ }
+
+ /**
+ * Initialize the LSP hover feature.
+ *
+ * @param hoverFeature the LSP hover feature.
+ * @return the LSP client features.
+ */
+ public final LSPClientFeatures setHoverFeature(@NotNull LSPHoverFeature hoverFeature) {
+ hoverFeature.setClientFeatures(this);
+ this.hoverFeature = hoverFeature;
+ return this;
+ }
+
+ /**
+ * Returns the LSP references feature.
+ *
+ * @return the LSP references feature.
+ */
+ @NotNull
+ public final LSPReferencesFeature getReferencesFeature() {
+ if (referencesFeature == null) {
+ setReferencesFeature(new LSPReferencesFeature());
+ }
+ return referencesFeature;
+ }
+
+ /**
+ * Initialize the LSP references feature.
+ *
+ * @param referencesFeature the LSP references feature.
+ * @return the LSP client features.
+ */
+ public final LSPClientFeatures setReferencesFeature(@NotNull LSPReferencesFeature referencesFeature) {
+ referencesFeature.setClientFeatures(this);
+ this.referencesFeature = referencesFeature;
+ return this;
+ }
+
+ /**
+ * Returns the LSP rename feature.
+ *
+ * @return the LSP rename feature.
+ */
+ @NotNull
+ public final LSPRenameFeature getRenameFeature() {
+ if (renameFeature == null) {
+ setRenameFeature(new LSPRenameFeature());
+ }
+ return renameFeature;
+ }
+
+ /**
+ * Initialize the LSP rename feature.
+ *
+ * @param renameFeature the LSP rename feature.
+ * @return the LSP client features.
+ */
+ public final LSPClientFeatures setRenameFeature(@NotNull LSPRenameFeature renameFeature) {
+ renameFeature.setClientFeatures(this);
+ this.renameFeature = renameFeature;
+ return this;
+ }
+
+ /**
+ * Returns the LSP semanticTokens feature.
+ *
+ * @return the LSP semanticTokens feature.
+ */
+ @NotNull
+ public final LSPSemanticTokensFeature getSemanticTokensFeature() {
+ if (semanticTokensFeature == null) {
+ setSemanticTokensFeature(new LSPSemanticTokensFeature());
+ }
+ return semanticTokensFeature;
+ }
+
+ /**
+ * Initialize the LSP semanticTokens feature.
+ *
+ * @param semanticTokensFeature the LSP semanticTokens feature.
+ * @return the LSP client features.
+ */
+ public final LSPClientFeatures setSemanticTokensFeature(@NotNull LSPSemanticTokensFeature semanticTokensFeature) {
+ semanticTokensFeature.setClientFeatures(this);
+ this.semanticTokensFeature = semanticTokensFeature;
+ return this;
+ }
+
+ /**
+ * Returns the LSP signatureHelp feature.
+ *
+ * @return the LSP signatureHelp feature.
+ */
+ @NotNull
+ public final LSPSignatureHelpFeature getSignatureHelpFeature() {
+ if (signatureHelpFeature == null) {
+ setSignatureHelpFeature(new LSPSignatureHelpFeature());
+ }
+ return signatureHelpFeature;
+ }
+
+ /**
+ * Initialize the LSP signatureHelp feature.
+ *
+ * @param signatureHelpFeature the LSP signatureHelp feature.
+ * @return the LSP client features.
+ */
+ public final LSPClientFeatures setSignatureHelpFeature(@NotNull LSPSignatureHelpFeature signatureHelpFeature) {
+ signatureHelpFeature.setClientFeatures(this);
+ this.signatureHelpFeature = signatureHelpFeature;
+ return this;
+ }
+
+ /**
+ * Returns the LSP typeDefinition feature.
+ *
+ * @return the LSP typeDefinition feature.
+ */
+ @NotNull
+ public final LSPTypeDefinitionFeature getTypeDefinitionFeature() {
+ if (typeDefinitionFeature == null) {
+ setTypeDefinitionFeature(new LSPTypeDefinitionFeature());
+ }
+ return typeDefinitionFeature;
+ }
+
+ /**
+ * Initialize the LSP typeDefinition feature.
+ *
+ * @param typeDefinitionFeature the LSP typeDefinition feature.
+ * @return the LSP client features.
+ */
+ public final LSPClientFeatures setTypeDefinitionFeature(@NotNull LSPTypeDefinitionFeature typeDefinitionFeature) {
+ typeDefinitionFeature.setClientFeatures(this);
+ this.typeDefinitionFeature = typeDefinitionFeature;
+ return this;
+ }
+
+ /**
+ * Returns the LSP usage feature.
+ *
+ * @return the LSP usage feature.
+ */
+ @NotNull
+ public final LSPUsageFeature getUsageFeature() {
+ if (usageFeature == null) {
+ setUsageFeature(new LSPUsageFeature());
+ }
+ return usageFeature;
+ }
+
+ /**
+ * Initialize the LSP usage feature.
+ *
+ * @param usageFeature the LSP usage feature.
+ * @return the LSP client features.
+ */
+ public final LSPClientFeatures setUsageFeature(@NotNull LSPUsageFeature usageFeature) {
+ usageFeature.setClientFeatures(this);
+ this.usageFeature = usageFeature;
+ return this;
+ }
+
+ /**
+ * Returns the LSP workspaceSymbol feature.
+ *
+ * @return the LSP workspaceSymbol feature.
+ */
+ @NotNull
+ public final LSPWorkspaceSymbolFeature getWorkspaceSymbolFeature() {
+ if (workspaceSymbolFeature == null) {
+ setWorkspaceSymbolFeature(new LSPWorkspaceSymbolFeature());
+ }
+ return workspaceSymbolFeature;
+ }
+
+ /**
+ * Initialize the LSP workspaceSymbol feature.
+ *
+ * @param workspaceSymbolFeature the LSP workspaceSymbol feature.
+ * @return the LSP client features.
+ */
+ public final LSPClientFeatures setWorkspaceSymbolFeature(@NotNull LSPWorkspaceSymbolFeature workspaceSymbolFeature) {
+ workspaceSymbolFeature.setClientFeatures(this);
+ this.workspaceSymbolFeature = workspaceSymbolFeature;
+ return this;
+ }
+
+ /**
+ * Set the language server wrapper.
+ *
+ * @param serverWrapper the language server wrapper.
+ */
+ @ApiStatus.Internal
+ public final void setServerWrapper(LanguageServerWrapper serverWrapper) {
+ this.serverWrapper = serverWrapper;
+ }
+
+ /**
+ * Returns the language server wrapper.
+ *
+ * @return the language server wrapper.
+ */
+ @ApiStatus.Internal
+ final LanguageServerWrapper getServerWrapper() {
+ return serverWrapper;
+ }
+
+ /**
+ * Dispose client features called when the language server is disposed.
+ */
+ @Override
+ public void dispose() {
+ serverWrapper = null;
+ if (codeActionFeature != null) {
+ codeActionFeature.dispose();
+ }
+ if (codeLensFeature != null) {
+ codeLensFeature.dispose();
+ }
+ if (colorFeature != null) {
+ colorFeature.dispose();
+ }
+ if (completionFeature != null) {
+ completionFeature.dispose();
+ }
+ if (codeActionFeature != null) {
+ codeActionFeature.dispose();
+ }
+ if (codeActionFeature != null) {
+ codeActionFeature.dispose();
+ }
+ if (declarationFeature != null) {
+ declarationFeature.dispose();
+ }
+ if (definitionFeature != null) {
+ definitionFeature.dispose();
+ }
+ if (documentLinkFeature != null) {
+ documentLinkFeature.dispose();
+ }
+ if (documentHighlightFeature != null) {
+ documentHighlightFeature.dispose();
+ }
+ if (documentSymbolFeature != null) {
+ documentSymbolFeature.dispose();
+ }
+ if (diagnosticFeature != null) {
+ diagnosticFeature.dispose();
+ }
+ if (foldingRangeFeature != null) {
+ foldingRangeFeature.dispose();
+ }
+ if (formattingFeature != null) {
+ formattingFeature.dispose();
+ }
+ if (implementationFeature != null) {
+ implementationFeature.dispose();
+ }
+ if (inlayHintFeature != null) {
+ inlayHintFeature.dispose();
+ }
+ if (hoverFeature != null) {
+ hoverFeature.dispose();
+ }
+ if (referencesFeature != null) {
+ referencesFeature.dispose();
+ }
+ if (renameFeature != null) {
+ renameFeature.dispose();
+ }
+ if (semanticTokensFeature != null) {
+ semanticTokensFeature.dispose();
+ }
+ if (signatureHelpFeature != null) {
+ signatureHelpFeature.dispose();
+ }
+ if (typeDefinitionFeature != null) {
+ typeDefinitionFeature.dispose();
+ }
+ if (usageFeature != null) {
+ usageFeature.dispose();
+ }
+ if (workspaceSymbolFeature != null) {
+ workspaceSymbolFeature.dispose();
+ }
+ }
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPCodeActionFeature.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPCodeActionFeature.java
new file mode 100644
index 000000000..458827a51
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPCodeActionFeature.java
@@ -0,0 +1,128 @@
+/*******************************************************************************
+ * 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 https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.client.features;
+
+import com.intellij.psi.PsiFile;
+import com.redhat.devtools.lsp4ij.LanguageServerBundle;
+import com.redhat.devtools.lsp4ij.LanguageServerItem;
+import com.redhat.devtools.lsp4ij.internal.StringUtils;
+import org.eclipse.lsp4j.CodeAction;
+import org.eclipse.lsp4j.CodeActionKind;
+import org.eclipse.lsp4j.Command;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * LSP codeAction feature.
+ */
+@ApiStatus.Experimental
+public class LSPCodeActionFeature extends AbstractLSPDocumentFeature {
+
+ @Override
+ public boolean isSupported(@NotNull PsiFile file) {
+ return isCodeActionSupported(file);
+ }
+
+ /**
+ * Returns true if the file associated with a language server can support codeAction and false otherwise.
+ *
+ * @param file the file.
+ * @return true if the file associated with a language server can support codeAction and false otherwise.
+ */
+ public boolean isCodeActionSupported(@NotNull PsiFile file) {
+ // TODO implement documentSelector to use language of the given file
+ return LanguageServerItem.isCodeActionSupported(getClientFeatures().getServerWrapper().getServerCapabilitiesSync());
+ }
+
+ /**
+ * Returns true if quick fixes are enabled and false otherwise.
+ *
+ * @return true if quick fixes are enabled and false otherwise.
+ */
+ public boolean isQuickFixesEnabled(@NotNull PsiFile file) {
+ return isEnabled(file);
+ }
+
+ /**
+ * Returns true if intent action are enabled and false otherwise.
+ *
+ * @return true if intent action are enabled and false otherwise.
+ */
+ public boolean isIntentionActionsEnabled(@NotNull PsiFile file) {
+ return isEnabled(file);
+ }
+
+ /**
+ * Returns the IntelliJ intention action text from the given LSP code action and null to ignore the code action.
+ *
+ * @param codeAction the LSP code action.
+ * @return the IntelliJ intention action text from the given LSP code action and null to ignore the code action.
+ */
+ @Nullable
+ public String getText(@NotNull CodeAction codeAction) {
+ return codeAction.getTitle();
+ }
+
+ /**
+ * Returns the IntelliJ intention action family name from the given LSP code action.
+ *
+ * @param codeAction the LSP code action.
+ * @return the IntelliJ intention action family name from the given LSP code action.
+ */
+ @NotNull
+ public String getFamilyName(@NotNull CodeAction codeAction) {
+ String kind = codeAction.getKind();
+ if (StringUtils.isNotBlank(kind)) {
+ switch (kind) {
+ case CodeActionKind.QuickFix:
+ return LanguageServerBundle.message("lsp.intention.code.action.kind.quickfix");
+ case CodeActionKind.Refactor:
+ return LanguageServerBundle.message("lsp.intention.code.action.kind.refactor");
+ case CodeActionKind.RefactorExtract:
+ return LanguageServerBundle.message("lsp.intention.code.action.kind.refactor.extract");
+ case CodeActionKind.RefactorInline:
+ return LanguageServerBundle.message("lsp.intention.code.action.kind.refactor.inline");
+ case CodeActionKind.RefactorRewrite:
+ return LanguageServerBundle.message("lsp.intention.code.action.kind.refactor.rewrite");
+ case CodeActionKind.Source:
+ return LanguageServerBundle.message("lsp.intention.code.action.kind.source");
+ case CodeActionKind.SourceFixAll:
+ return LanguageServerBundle.message("lsp.intention.code.action.kind.source.fixAll");
+ case CodeActionKind.SourceOrganizeImports:
+ return LanguageServerBundle.message("lsp.intention.code.action.kind.source.organizeImports");
+ }
+ }
+ return LanguageServerBundle.message("lsp.intention.code.action.kind.empty");
+ }
+
+ /**
+ * Returns the IntelliJ intention action text from the given LSP command and null to ignore the command.
+ *
+ * @param command the LSP command.
+ * @return the IntelliJ intention action text from the given LSP command and null to ignore the command.
+ */
+ @Nullable
+ public String getText(@NotNull Command command) {
+ return command.getTitle();
+ }
+
+ /**
+ * Returns the IntelliJ intention action family name from the given LSP command.
+ *
+ * @param command the LSP command.
+ * @return the IntelliJ intention action family name from the given LSP command.
+ */
+ @NotNull
+ public String getFamilyName(@NotNull Command command) {
+ return "LSP Command";
+ }
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPCodeLensFeature.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPCodeLensFeature.java
new file mode 100644
index 000000000..c86e50e18
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPCodeLensFeature.java
@@ -0,0 +1,149 @@
+/*******************************************************************************
+ * 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 https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.client.features;
+
+import com.intellij.codeInsight.codeVision.CodeVisionEntry;
+import com.intellij.codeInsight.codeVision.ui.model.ClickableTextCodeVisionEntry;
+import com.intellij.codeInsight.codeVision.ui.model.TextCodeVisionEntry;
+import com.intellij.psi.PsiFile;
+import com.intellij.util.ui.UIUtil;
+import com.redhat.devtools.lsp4ij.LanguageServerItem;
+import com.redhat.devtools.lsp4ij.commands.CommandExecutor;
+import com.redhat.devtools.lsp4ij.commands.LSPCommandContext;
+import com.redhat.devtools.lsp4ij.internal.StringUtils;
+import org.eclipse.lsp4j.CodeLens;
+import org.eclipse.lsp4j.Command;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collections;
+
+/**
+ * LSP codeLens feature.
+ */
+@ApiStatus.Experimental
+public class LSPCodeLensFeature extends AbstractLSPDocumentFeature {
+
+ public static class LSPCodeLensContext {
+ private final @NotNull PsiFile psiFile;
+ private final @NotNull LanguageServerItem languageServer;
+ private Boolean resolveCodeLensSupported;
+
+ public LSPCodeLensContext(@NotNull PsiFile psiFile, @NotNull LanguageServerItem languageServer) {
+ this.psiFile = psiFile;
+ this.languageServer = languageServer;
+ }
+
+ public @NotNull PsiFile getPsiFile() {
+ return psiFile;
+ }
+
+ @NotNull
+ public LanguageServerItem getLanguageServer() {
+ return languageServer;
+ }
+
+ public boolean isResolveCodeLensSupported() {
+ if (resolveCodeLensSupported == null) {
+ resolveCodeLensSupported = languageServer.getClientFeatures().getCodeLensFeature().isResolveCodeLensSupported(getPsiFile());
+ }
+ return resolveCodeLensSupported;
+ }
+
+ }
+ /**
+ * Create an IntelliJ {@link CodeVisionEntry} from the given LSP CodeLens and null otherwise (to ignore the LSP CodeLens).
+ *
+ * @param codeLens the LSP codeLens.
+ * @param providerId the code vision provider Id.
+ * @param codeLensContext the LSP CodeLens context.
+ * @return an IntelliJ {@link CodeVisionEntry} from the given LSP CodeLens and null otherwise (to ignore the LSP CodeLens).
+ */
+ @Nullable
+ public CodeVisionEntry createCodeVisionEntry(@NotNull CodeLens codeLens,
+ @NotNull String providerId,
+ @NotNull LSPCodeLensContext codeLensContext) {
+ String text = getText(codeLens);
+ if (text == null) {
+ // Ignore the code vision entry
+ return null;
+ }
+ Command command = codeLens.getCommand();
+ String commandId = command.getCommand();
+ if (StringUtils.isEmpty(commandId)) {
+ // Create a simple text code vision.
+ return new TextCodeVisionEntry(text, providerId, null, text, text, Collections.emptyList());
+ }
+ // Code lens defines a command, create a clickable code vsion to execute the command.
+ return new ClickableTextCodeVisionEntry(text, providerId, (e, editor) -> {
+ LSPCommandContext context = new LSPCommandContext(command, codeLensContext.getPsiFile(), LSPCommandContext.ExecutedBy.CODE_LENS, editor, codeLensContext.getLanguageServer())
+ .setInputEvent(e);
+ if (codeLensContext.isResolveCodeLensSupported()) {
+ codeLensContext.getLanguageServer()
+ .getTextDocumentService()
+ .resolveCodeLens(codeLens)
+ .thenAcceptAsync(resolvedCodeLens -> {
+ if (resolvedCodeLens != null) {
+ UIUtil.invokeLaterIfNeeded(() ->
+ CommandExecutor.executeCommand(context));
+ }
+ }
+ );
+ } else {
+ CommandExecutor.executeCommand(context);
+ }
+ return null;
+ }, null, text, text, Collections.emptyList());
+ }
+
+ /**
+ * Returns the code vision entry text from the LSP CodeLens and null otherwise (to ignore the LSP CodeLens).
+ *
+ * @param codeLens the LSP CodeLens
+ * @return the code vision entry text from the LSP CodeLens and null otherwise (to ignore the LSP CodeLens).
+ */
+ @Nullable
+ public String getText(@NotNull CodeLens codeLens) {
+ Command command = codeLens.getCommand();
+ if (command == null || command.getTitle().isEmpty()) {
+ return null;
+ }
+ return command.getTitle();
+ }
+
+
+ @Override
+ public boolean isSupported(@NotNull PsiFile file) {
+ return isCodeLensSupported(file);
+ }
+
+ /**
+ * Returns true if the file associated with a language server can support codelens and false otherwise.
+ *
+ * @param file the file.
+ * @return true if the file associated with a language server can support codelens and false otherwise.
+ */
+ public boolean isCodeLensSupported(@NotNull PsiFile file) {
+ // TODO implement documentSelector to use language of the given file
+ return LanguageServerItem.isCodeLensSupported(getClientFeatures().getServerWrapper().getServerCapabilitiesSync());
+ }
+
+ /**
+ * Returns true if the file associated with a language server can support resolve codelens and false otherwise.
+ *
+ * @param file the file.
+ * @return true if the file associated with a language server can support resolve codelens and false otherwise.
+ */
+ public boolean isResolveCodeLensSupported(@NotNull PsiFile file) {
+ return LanguageServerItem.isResolveCodeLensSupported(getClientFeatures().getServerWrapper().getServerCapabilitiesSync());
+ }
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPColorFeature.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPColorFeature.java
new file mode 100644
index 000000000..a25671127
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPColorFeature.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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 https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.client.features;
+
+import com.intellij.psi.PsiFile;
+import com.redhat.devtools.lsp4ij.LanguageServerItem;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * LSP color feature.
+ */
+@ApiStatus.Experimental
+public class LSPColorFeature extends AbstractLSPDocumentFeature {
+
+ @Override
+ public boolean isSupported(@NotNull PsiFile file) {
+ return isColorSupported(file);
+ }
+
+ /**
+ * Returns true if the file associated with a language server can support color and false otherwise.
+ *
+ * @param file the file.
+ * @return true if the file associated with a language server can support color and false otherwise.
+ */
+ public boolean isColorSupported(@NotNull PsiFile file) {
+ // TODO implement documentSelector to use language of the given file
+ return LanguageServerItem.isColorSupported(getClientFeatures().getServerWrapper().getServerCapabilitiesSync());
+ }
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPCompletionFeature.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPCompletionFeature.java
new file mode 100644
index 000000000..697a630a4
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPCompletionFeature.java
@@ -0,0 +1,225 @@
+/*******************************************************************************
+ * 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 https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.client.features;
+
+import com.intellij.codeInsight.completion.CompletionParameters;
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.codeInsight.completion.PrioritizedLookupElement;
+import com.intellij.codeInsight.lookup.LookupElement;
+import com.intellij.codeInsight.lookup.LookupElementPresentation;
+import com.intellij.psi.PsiFile;
+import com.redhat.devtools.lsp4ij.LanguageServerItem;
+import com.redhat.devtools.lsp4ij.features.completion.CompletionPrefix;
+import com.redhat.devtools.lsp4ij.internal.StringUtils;
+import com.redhat.devtools.lsp4ij.ui.IconMapper;
+import org.eclipse.lsp4j.CompletionItem;
+import org.eclipse.lsp4j.CompletionItemKind;
+import org.eclipse.lsp4j.CompletionItemTag;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+
+/**
+ * LSP completion feature.
+ */
+@ApiStatus.Experimental
+public class LSPCompletionFeature extends AbstractLSPDocumentFeature {
+
+ public static class LSPCompletionContext {
+
+ private final @NotNull CompletionParameters parameters;
+ private final @NotNull LanguageServerItem languageServer;
+ private Boolean signatureHelpSupported;
+ private Boolean resolveCompletionSupported;
+
+ public LSPCompletionContext(@NotNull CompletionParameters parameters, @NotNull LanguageServerItem languageServer) {
+ this.parameters = parameters;
+ this.languageServer = languageServer;
+ }
+
+ public @NotNull CompletionParameters getParameters() {
+ return parameters;
+ }
+
+ public boolean isSignatureHelpSupported() {
+ if (signatureHelpSupported == null) {
+ signatureHelpSupported = languageServer.getClientFeatures().getSignatureHelpFeature().isSupported(parameters.getOriginalFile());
+ }
+ return signatureHelpSupported;
+ }
+
+ public boolean isResolveCompletionSupported() {
+ if (resolveCompletionSupported == null) {
+ resolveCompletionSupported = languageServer.getClientFeatures().getCompletionFeature().isResolveCompletionSupported(parameters.getOriginalFile());
+ }
+ return resolveCompletionSupported;
+ }
+
+ @NotNull
+ @ApiStatus.Internal
+ LanguageServerItem getLanguageServer() {
+ return languageServer;
+ }
+ }
+
+ @Override
+ public boolean isSupported(@NotNull PsiFile file) {
+ return isCompletionSupported(file);
+ }
+
+ /**
+ * Returns true if the file associated with a language server can support completion and false otherwise.
+ *
+ * @param file the file.
+ * @return true if the file associated with a language server can support completion and false otherwise.
+ */
+ public boolean isCompletionSupported(@NotNull PsiFile file) {
+ // TODO implement documentSelector to use language of the given file
+ return LanguageServerItem.isCompletionSupported(getClientFeatures().getServerWrapper().getServerCapabilitiesSync());
+ }
+
+ /**
+ * Returns true if the file associated with a language server can support resolve completion and false otherwise.
+ *
+ * @param file the file.
+ * @return true the file associated with a language server can support resolve completion and false otherwise.
+ */
+ public boolean isResolveCompletionSupported(@Nullable PsiFile file) {
+ // TODO implement documentSelector to use language of the given file
+ return LanguageServerItem.isResolveCompletionSupported(getClientFeatures().getServerWrapper().getServerCapabilitiesSync());
+ }
+
+ /**
+ * Create a completion lookup element from the given LSP completion item and context and null otherwise.
+ *
+ * @param item the LSP completion item.
+ * @param context the LSP completion context.
+ * @return a completion lookup element from the given LSP completion item and context and null otherwise.
+ */
+ @Nullable
+ public LookupElement createLookupElement(@NotNull CompletionItem item,
+ @NotNull LSPCompletionContext context) {
+ if (StringUtils.isBlank(item.getLabel())) {
+ // Invalid completion Item, ignore it
+ return null;
+ }
+ // Update text edit range, commitCharacters, ... with item defaults if needed
+ return new LSPCompletionProposal(item, context, this);
+ }
+
+ /**
+ * Update the given IntelliJ lookup element presentation with the given LSP completion item.
+ *
+ * @param presentation the lookup element presentation to update.
+ * @param item the LSP completion .
+ */
+ public void renderLookupElement(@NotNull LookupElementPresentation presentation,
+ @NotNull CompletionItem item) {
+ presentation.setItemText(this.getItemText(item));
+ presentation.setTypeText(this.getTypeText(item));
+ presentation.setIcon(this.getIcon(item));
+ presentation.setStrikeout(this.isStrikeout(item));
+ presentation.setTailText(this.getTailText(item));
+ presentation.setItemTextBold(this.isItemTextBold(item));
+ }
+
+ /**
+ * Returns the IntelliJ lookup item text from the given LSP completion item and null otherwise.
+ *
+ * @param item the LSP completion item.
+ * @return the IntelliJ lookup item text from the given LSP completion item and null otherwise.
+ */
+ @Nullable
+ public String getItemText(@NotNull CompletionItem item) {
+ return item.getLabel();
+ }
+
+ /**
+ * Returns the IntelliJ lookup type text from the given LSP completion item and null otherwise.
+ *
+ * @param item the LSP completion item.
+ * @return the IntelliJ lookup type text from the given LSP completion item and null otherwise.
+ */
+ @Nullable
+ public String getTypeText(CompletionItem item) {
+ return item.getDetail();
+ }
+
+ /**
+ * Returns the IntelliJ lookup icon from the given LSP completion item and null otherwise.
+ *
+ * @param item the LSP completion item.
+ * @return the IntelliJ lookup icon from the given LSP completion item and null otherwise.
+ */
+ @Nullable
+ public Icon getIcon(@NotNull CompletionItem item) {
+ return IconMapper.getIcon(item);
+ }
+
+ /**
+ * Returns true if the IntelliJ lookup is strike out and false otherwise.
+ *
+ * @param item
+ * @return true if the IntelliJ lookup is strike out and false otherwise.
+ */
+ public boolean isStrikeout(@NotNull CompletionItem item) {
+ return (item.getTags() != null && item.getTags().contains(CompletionItemTag.Deprecated))
+ || (item.getDeprecated() != null && item.getDeprecated().booleanValue());
+ }
+
+ /**
+ * Returns the IntelliJ lookup tail text from the given LSP completion item and null otherwise.
+ *
+ * @param item the LSP completion item.
+ * @return the IntelliJ lookup tail text from the given LSP completion item and null otherwise.
+ */
+ @Nullable
+ public String getTailText(@NotNull CompletionItem item) {
+ var labelDetails = item.getLabelDetails();
+ return labelDetails != null ? labelDetails.getDetail() : null;
+ }
+
+ /**
+ * Returns the IntelliJ lookup item text bold from the given LSP completion item and null otherwise.
+ *
+ * @param item the LSP completion item.
+ * @return the IntelliJ lookup item text bold from the given LSP completion item and null otherwise.
+ */
+ public boolean isItemTextBold(@NotNull CompletionItem item) {
+ return item.getKind() != null && item.getKind() == CompletionItemKind.Keyword;
+ }
+
+ public void addLookupItem(@NotNull CompletionPrefix completionPrefix,
+ @NotNull CompletionResultSet result,
+ @NotNull LookupElement lookupItem,
+ int priority,
+ @NotNull CompletionItem item) {
+ var prioritizedLookupItem = PrioritizedLookupElement.withPriority(lookupItem, priority);
+ // Compute the prefix
+ var textEditRange = ((LSPCompletionProposal) lookupItem).getTextEditRange();
+ String prefix = textEditRange != null ? completionPrefix.getPrefixFor(textEditRange, item) : null;
+ if (prefix != null) {
+ // Add the IJ completion item (lookup item) by using the computed prefix
+ result.withPrefixMatcher(prefix)
+ .caseInsensitive()
+ .addElement(prioritizedLookupItem);
+ } else {
+ // Should happen rarely, only when text edit is for multi-lines or if completion is triggered outside the text edit range.
+ // Add the IJ completion item (lookup item) which will use the IJ prefix
+ result.caseInsensitive()
+ .addElement(prioritizedLookupItem);
+ }
+ }
+
+
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/completion/LSPCompletionProposal.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPCompletionProposal.java
similarity index 91%
rename from src/main/java/com/redhat/devtools/lsp4ij/features/completion/LSPCompletionProposal.java
rename to src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPCompletionProposal.java
index 271f4110d..af003fb5d 100644
--- a/src/main/java/com/redhat/devtools/lsp4ij/features/completion/LSPCompletionProposal.java
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPCompletionProposal.java
@@ -8,7 +8,7 @@
* Contributors:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
-package com.redhat.devtools.lsp4ij.features.completion;
+package com.redhat.devtools.lsp4ij.client.features;
import com.intellij.codeInsight.AutoPopupController;
import com.intellij.codeInsight.completion.CodeCompletionHandlerBase;
@@ -34,10 +34,13 @@
import com.redhat.devtools.lsp4ij.LanguageServerItem;
import com.redhat.devtools.lsp4ij.commands.CommandExecutor;
import com.redhat.devtools.lsp4ij.commands.LSPCommandContext;
+import com.redhat.devtools.lsp4ij.features.completion.CompletionProposalTools;
+import com.redhat.devtools.lsp4ij.features.completion.SnippetTemplateFactory;
import com.redhat.devtools.lsp4ij.features.completion.snippet.LspSnippetIndentOptions;
import com.redhat.devtools.lsp4ij.internal.StringUtils;
import org.eclipse.lsp4j.*;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
+import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
@@ -53,47 +56,42 @@
import static com.redhat.devtools.lsp4ij.features.documentation.LSPDocumentationHelper.getValidMarkupContents;
import static com.redhat.devtools.lsp4ij.internal.CompletableFutures.isDoneNormally;
import static com.redhat.devtools.lsp4ij.internal.CompletableFutures.waitUntilDone;
-import static com.redhat.devtools.lsp4ij.ui.IconMapper.getIcon;
/**
* LSP completion lookup element.
*/
+@ApiStatus.Internal
public class LSPCompletionProposal extends LookupElement implements Pointer, Symbol, DocumentationTarget {
+
private static final Logger LOGGER = LoggerFactory.getLogger(LSPCompletionProposal.class);
private final CompletionItem item;
private final PsiFile file;
- private final Boolean supportResolveCompletion;
- private final boolean supportSignatureHelp;
- private final LSPCompletionContributor completionContributor;
// offset where completion has been triggered
// ex : string.charA|
private final int completionOffset;
+ private final LSPCompletionFeature.@NotNull LSPCompletionContext completionContext;
+ private final @NotNull LSPCompletionFeature completionFeature;
// offset where prefix completion starts
// ex : string.|charA
private int prefixStartOffset;
private final Editor editor;
- private final LanguageServerItem languageServer;
+
private CompletableFuture resolvedCompletionItemFuture;
- public LSPCompletionProposal(@NotNull PsiFile file,
- @NotNull Editor editor,
- int completionOffset,
- @NotNull CompletionItem item,
- @NotNull LanguageServerItem languageServer,
- @NotNull LSPCompletionContributor completionContributor) {
- this.file = file;
+ public LSPCompletionProposal(@NotNull CompletionItem item,
+ @NotNull LSPCompletionFeature.LSPCompletionContext completionContext,
+ @NotNull LSPCompletionFeature completionFeature) {
+ this.file = completionContext.getParameters().getOriginalFile();
this.item = item;
- this.editor = editor;
- this.languageServer = languageServer;
- this.completionContributor = completionContributor;
- this.completionOffset = completionOffset;
+ this.editor = completionContext.getParameters().getEditor();
+ this.completionContext = completionContext;
+ this.completionOffset = completionContext.getParameters().getOffset();
this.prefixStartOffset = getPrefixStartOffset(editor.getDocument(), completionOffset);
- this.supportResolveCompletion = languageServer.isResolveCompletionSupported();
- this.supportSignatureHelp = languageServer.isSignatureHelpSupported();
+ this.completionFeature = completionFeature;
putUserData(CodeCompletionHandlerBase.DIRECT_INSERTION, true);
}
@@ -115,7 +113,7 @@ public void handleInsert(@NotNull InsertionContext context) {
}
// Apply all text edits
- apply(context.getDocument(), context.getCompletionChar(), 0, context.getOffset(CompletionInitializationContext.SELECTION_END_OFFSET));
+ apply(context.getDocument(), context.getOffset(CompletionInitializationContext.SELECTION_END_OFFSET));
if (shouldStartTemplate(template)) {
// LSP completion with snippet syntax, activate the inline template
@@ -127,10 +125,10 @@ public void handleInsert(@NotNull InsertionContext context) {
// Execute custom command of the completion item if needed
Command command = item.getCommand();
if (command != null) {
- executeCustomCommand(command, context.getFile(), context.getEditor(), languageServer);
+ executeCustomCommand(command, context.getFile(), context.getEditor(), completionContext.getLanguageServer());
}
- if (supportSignatureHelp) {
+ if (completionContext.isSignatureHelpSupported()) {
// The language server supports signature help, open the parameter info popup
AutoPopupController popupController = AutoPopupController.getInstance(context.getProject());
if (popupController != null) {
@@ -213,21 +211,9 @@ public String getLookupString() {
return item.getLabel();
}
- private boolean isDeprecated() {
- return (item.getTags() != null && item.getTags().contains(CompletionItemTag.Deprecated))
- || (item.getDeprecated() != null && item.getDeprecated().booleanValue());
- }
-
@Override
public void renderElement(LookupElementPresentation presentation) {
- presentation.setItemText(item.getLabel());
- presentation.setTypeText(item.getDetail());
- presentation.setIcon(getIcon(item));
- if (isDeprecated()) {
- presentation.setStrikeout(true);
- }
- var labelDetails = item.getLabelDetails();
- presentation.setTailText(labelDetails != null ? labelDetails.getDetail() : null);
+ completionFeature.renderLookupElement(presentation, item);
}
@Override
@@ -265,10 +251,10 @@ public void renderElement(LookupElement element, LookupElementPresentation prese
* @return true if the LSP completion item 'detail' must be resolved and false otherwise.
*/
public boolean needToResolveCompletionDetail() {
- return item.getDetail() == null && supportResolveCompletion;
+ return item.getDetail() == null && completionContext.isResolveCompletionSupported();
}
- protected void apply(Document document, char trigger, int stateMask, int offset) {
+ protected void apply(Document document, int offset) {
String insertText = null;
Either eitherTextEdit = item.getTextEdit();
TextEdit textEdit = null;
@@ -334,7 +320,7 @@ protected void apply(Document document, char trigger, int stateMask, int offset)
}
List additionalEdits = item.getAdditionalTextEdits();
- if ((additionalEdits == null || additionalEdits.isEmpty()) && supportResolveCompletion) {
+ if ((additionalEdits == null || additionalEdits.isEmpty()) && completionContext.isResolveCompletionSupported()) {
// The LSP completion item 'additionalEdits' is not filled, try to resolve it.
CompletionItem resolved = getResolvedCompletionItem();
if (resolved != null) {
@@ -450,14 +436,15 @@ public CompletionItem getItem() {
*/
private CompletionItem getResolvedCompletionItem() {
if (resolvedCompletionItemFuture == null) {
- resolvedCompletionItemFuture = languageServer.getServer()
+ resolvedCompletionItemFuture = completionContext.getLanguageServer().getServer()
.getTextDocumentService()
.resolveCompletionItem(item);
}
try {
// Wait until the future is finished and stop the wait if there are some ProcessCanceledException.
waitUntilDone(resolvedCompletionItemFuture, file);
- } catch (ProcessCanceledException e) {//Since 2024.2 ProcessCanceledException extends CancellationException so we can't use multicatch to keep backward compatibility
+ } 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
return null;
} catch (CancellationException e) {
@@ -591,8 +578,8 @@ public DocumentationResult computeDocumentation() {
if (contents.isEmpty()) {
return null;
}
- return DocumentationResult.documentation(convertToHtml(contents, file));
- } else if (supportResolveCompletion) {
+ return DocumentationResult.documentation(convertToHtml(contents, null, file));
+ } else if (completionContext.isResolveCompletionSupported()) {
if (resolvedCompletionItemFuture != null && resolvedCompletionItemFuture.isDone()) {
CompletionItem resolved = getResolvedCompletionItem();
if (resolved != null) {
@@ -602,7 +589,7 @@ public DocumentationResult computeDocumentation() {
if (contents.isEmpty()) {
return null;
}
- return DocumentationResult.documentation(convertToHtml(contents, file));
+ return DocumentationResult.documentation(convertToHtml(contents, null, file));
} else {
DocumentationResult.asyncDocumentation(() -> {
// The LSP completion item 'documentation' is not filled, try to resolve it
@@ -615,7 +602,7 @@ public DocumentationResult computeDocumentation() {
if (contents.isEmpty()) {
return null;
}
- return DocumentationResult.documentation(convertToHtml(contents, file));
+ return DocumentationResult.documentation(convertToHtml(contents, null, file));
});
}
}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDeclarationFeature.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDeclarationFeature.java
new file mode 100644
index 000000000..0a1fd79fe
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDeclarationFeature.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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 https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.client.features;
+
+import com.intellij.psi.PsiFile;
+import com.redhat.devtools.lsp4ij.LanguageServerItem;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * LSP declaration feature.
+ */
+@ApiStatus.Experimental
+public class LSPDeclarationFeature extends AbstractLSPDocumentFeature {
+
+ @Override
+ public boolean isSupported(@NotNull PsiFile file) {
+ return isDeclarationSupported(file);
+ }
+
+ /**
+ * Returns true if the file associated with a language server can support codelens and false otherwise.
+ *
+ * @param file the file.
+ * @return true if the file associated with a language server can support codelens and false otherwise.
+ */
+ public boolean isDeclarationSupported(@NotNull PsiFile file) {
+ // TODO implement documentSelector to use language of the given file
+ return LanguageServerItem.isDeclarationSupported(getClientFeatures().getServerWrapper().getServerCapabilitiesSync());
+ }
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDefinitionFeature.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDefinitionFeature.java
new file mode 100644
index 000000000..5232a11b0
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDefinitionFeature.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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 https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.client.features;
+
+import com.intellij.psi.PsiFile;
+import com.redhat.devtools.lsp4ij.LanguageServerItem;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * LSP definition feature.
+ */
+@ApiStatus.Experimental
+public class LSPDefinitionFeature extends AbstractLSPDocumentFeature {
+
+ @Override
+ public boolean isSupported(@NotNull PsiFile file) {
+ return isDefinitionSupported(file);
+ }
+
+ /**
+ * Returns true if the file associated with a language server can support codelens and false otherwise.
+ *
+ * @param file the file.
+ * @return true if the file associated with a language server can support codelens and false otherwise.
+ */
+ public boolean isDefinitionSupported(@NotNull PsiFile file) {
+ // TODO implement documentSelector to use language of the given file
+ return LanguageServerItem.isDefinitionSupported(getClientFeatures().getServerWrapper().getServerCapabilitiesSync());
+ }
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDiagnosticFeature.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDiagnosticFeature.java
new file mode 100644
index 000000000..6a2ffdc8e
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDiagnosticFeature.java
@@ -0,0 +1,232 @@
+/*******************************************************************************
+ * 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 https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.client.features;
+
+import com.intellij.codeInsight.intention.IntentionAction;
+import com.intellij.codeInspection.ProblemHighlightType;
+import com.intellij.lang.annotation.AnnotationBuilder;
+import com.intellij.lang.annotation.AnnotationHolder;
+import com.intellij.lang.annotation.HighlightSeverity;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.PsiFile;
+import com.redhat.devtools.lsp4ij.LSPIJUtils;
+import com.redhat.devtools.lsp4ij.features.diagnostics.SeverityMapping;
+import com.redhat.devtools.lsp4ij.hint.LSPNavigationLinkHandler;
+import com.redhat.devtools.lsp4ij.internal.StringUtils;
+import org.eclipse.lsp4j.*;
+import org.eclipse.lsp4j.jsonrpc.messages.Either;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+/**
+ * LSP diagnostic feature.
+ */
+@ApiStatus.Experimental
+public class LSPDiagnosticFeature extends AbstractLSPDocumentFeature {
+
+ @Override
+ public boolean isSupported(@NotNull PsiFile file) {
+ return true;
+ }
+
+ /**
+ * Create an IntelliJ annotation in the given holder by using given LSP diagnostic and fixes.
+ * @param diagnostic the LSP diagnostic.
+ * @param document the document.
+ * @param fixes the fixes coming from LSP CodeAction.
+ * @param holder the annotation holder where annotation must be registered.
+ */
+ public void createAnnotation(@NotNull Diagnostic diagnostic,
+ @NotNull Document document,
+ List fixes,
+ @NotNull AnnotationHolder holder) {
+
+ // Get the text range from the given LSP diagnostic range.
+ // Since IJ cannot highlight an error when the start/end range offset are the same
+ // the method LSPIJUtils.toTextRange is called with adjust, in other words when start/end range offset are the same:
+ // - when the offset is at the end of the line, the method returns a text range with the same offset,
+ // and annotation must be created with Annotation#setAfterEndOfLine(true).
+ // - when the offset is inside the line, the end offset is incremented.
+ TextRange range = LSPIJUtils.toTextRange(diagnostic.getRange(), document, null, true);
+ if (range == null) {
+ // Language server reports invalid diagnostic, ignore it.
+ return;
+ }
+
+ HighlightSeverity severity = getHighlightSeverity(diagnostic);
+ if (severity == null) {
+ // Ignore the diagnostic
+ return;
+ }
+
+ // Collect information required to create Intellij Annotations
+ String message = getMessage(diagnostic);
+
+ // Create IntelliJ Annotation from the given LSP diagnostic
+ AnnotationBuilder builder = holder
+ .newAnnotation(severity, message)
+ .tooltip(getToolTip(diagnostic))
+ .range(range);
+ if (range.getStartOffset() == range.getEndOffset()) {
+ // Show the annotation at the end of line.
+ builder.afterEndOfLine();
+ }
+
+ // Update highlight type from the diagnostic tags
+ ProblemHighlightType highlightType = getProblemHighlightType(diagnostic.getTags());
+ if (highlightType != null) {
+ builder.highlightType(highlightType);
+ }
+
+ // Register lazy quick fixes
+ for (IntentionAction fix : fixes) {
+ builder.withFix(fix);
+ }
+ builder.create();
+ }
+
+ /**
+ * Returns the IntelliJ {@link HighlightSeverity} from the given diagnostic and null otherwise.
+ *
+ *
+ * If null is returned, the diagnostic will be ignored.
+ *
+ *
+ * @param diagnostic the LSP diagnostic.
+ * @return the IntelliJ {@link HighlightSeverity} from the given diagnostic and null otherwise.
+ */
+ @Nullable
+ public HighlightSeverity getHighlightSeverity(@NotNull Diagnostic diagnostic) {
+ return SeverityMapping.toHighlightSeverity(diagnostic.getSeverity());
+ }
+
+ /**
+ * Returns the message of the given diagnostic.
+ *
+ * @param diagnostic the LSP diagnostic.
+ * @return the message of the given diagnostic.
+ */
+ @NotNull
+ public String getMessage(@NotNull Diagnostic diagnostic) {
+ return diagnostic.getMessage();
+ }
+
+ /**
+ * Returns the annotation tooltip from the given LSP diagnostic.
+ *
+ * @param diagnostic the LSP diagnostic.
+ * @return the annotation tooltip from the given LSP diagnostic.
+ */
+ @NotNull
+ public String getToolTip(@NotNull Diagnostic diagnostic) {
+ // message
+ StringBuilder tooltip = new StringBuilder("");
+ tooltip.append(StringUtil.escapeXmlEntities(diagnostic.getMessage()));
+ // source
+ tooltip.append(" ");
+ String source = diagnostic.getSource();
+ if (StringUtils.isNotBlank(source)) {
+ tooltip.append(source);
+ }
+ // error code
+ Either code = diagnostic.getCode();
+ if (code != null) {
+ String errorCode = code.isLeft() ? code.getLeft() : code.isRight() ? String.valueOf(code.getRight()) : null;
+ if (StringUtils.isNotBlank(errorCode)) {
+ tooltip.append(" (");
+ String href = diagnostic.getCodeDescription() != null ? diagnostic.getCodeDescription().getHref() : null;
+ addLink(errorCode, href, tooltip);
+ tooltip.append(")");
+ }
+ }
+ // Diagnostic related information
+ List information = diagnostic.getRelatedInformation();
+ if (information != null) {
+ tooltip.append("
");
+ }
+ tooltip.append("");
+ return tooltip.toString();
+ }
+
+ @NotNull
+ private static String getFileName(Location location) {
+ String fileUri = location.getUri();
+ int index = fileUri.lastIndexOf('/');
+ String fileName = fileUri.substring(index + 1);
+ StringBuilder result = new StringBuilder(fileName);
+ Range range = location.getRange();
+ if (range != null) {
+ result.append("(");
+ result.append(range.getStart().getLine());
+ result.append(":");
+ result.append(range.getStart().getCharacter());
+ result.append(", ");
+ result.append(range.getEnd().getLine());
+ result.append(":");
+ result.append(range.getEnd().getCharacter());
+ result.append(")");
+ }
+ return fileName;
+ }
+
+ private static void addLink(String text, String href, StringBuilder tooltip) {
+ boolean hasHref = StringUtils.isNotBlank(href);
+ if (hasHref) {
+ tooltip.append("");
+ }
+ tooltip.append(text);
+ if (hasHref) {
+ tooltip.append("");
+ }
+ }
+
+ /**
+ * Returns the {@link ProblemHighlightType} from the given tags and null otherwise.
+ *
+ * @param tags the diagnostic tags.
+ * @return the {@link ProblemHighlightType} from the given tags and null otherwise.
+ */
+ @Nullable
+ public ProblemHighlightType getProblemHighlightType(@Nullable List tags) {
+ if (tags == null || tags.isEmpty()) {
+ return null;
+ }
+ if (tags.contains(DiagnosticTag.Unnecessary)) {
+ return ProblemHighlightType.LIKE_UNUSED_SYMBOL;
+ }
+ if (tags.contains(DiagnosticTag.Deprecated)) {
+ return ProblemHighlightType.LIKE_DEPRECATED;
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDocumentHighlightFeature.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDocumentHighlightFeature.java
new file mode 100644
index 000000000..001d742b6
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDocumentHighlightFeature.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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 https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.client.features;
+
+import com.intellij.psi.PsiFile;
+import com.redhat.devtools.lsp4ij.LanguageServerItem;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * LSP documentHighlight feature.
+ */
+@ApiStatus.Experimental
+public class LSPDocumentHighlightFeature extends AbstractLSPDocumentFeature {
+
+ @Override
+ public boolean isSupported(@NotNull PsiFile file) {
+ return isDocumentHighlightSupported(file);
+ }
+
+ /**
+ * Returns true if the file associated with a language server can support codelens and false otherwise.
+ *
+ * @param file the file.
+ * @return true if the file associated with a language server can support codelens and false otherwise.
+ */
+ public boolean isDocumentHighlightSupported(@NotNull PsiFile file) {
+ // TODO implement documentSelector to use language of the given file
+ return LanguageServerItem.isDocumentHighlightSupported(getClientFeatures().getServerWrapper().getServerCapabilitiesSync());
+ }
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDocumentLinkFeature.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDocumentLinkFeature.java
new file mode 100644
index 000000000..cccf22df8
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDocumentLinkFeature.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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 https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.client.features;
+
+import com.intellij.psi.PsiFile;
+import com.redhat.devtools.lsp4ij.LanguageServerItem;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * LSP documentLink feature.
+ */
+@ApiStatus.Experimental
+public class LSPDocumentLinkFeature extends AbstractLSPDocumentFeature {
+
+ @Override
+ public boolean isSupported(@NotNull PsiFile file) {
+ return isDocumentLinkSupported(file);
+ }
+
+ /**
+ * Returns true if the file associated with a language server can support codelens and false otherwise.
+ *
+ * @param file the file.
+ * @return true if the file associated with a language server can support codelens and false otherwise.
+ */
+ public boolean isDocumentLinkSupported(@NotNull PsiFile file) {
+ // TODO implement documentSelector to use language of the given file
+ return LanguageServerItem.isDocumentLinkSupported(getClientFeatures().getServerWrapper().getServerCapabilitiesSync());
+ }
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDocumentSymbolFeature.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDocumentSymbolFeature.java
new file mode 100644
index 000000000..a89ea5254
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDocumentSymbolFeature.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * 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 https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.client.features;
+
+import com.intellij.ide.structureView.StructureViewTreeElement;
+import com.intellij.psi.PsiFile;
+import com.redhat.devtools.lsp4ij.LSPIJUtils;
+import com.redhat.devtools.lsp4ij.LanguageServerItem;
+import com.redhat.devtools.lsp4ij.features.documentSymbol.DocumentSymbolData;
+import com.redhat.devtools.lsp4ij.features.documentSymbol.LSPDocumentSymbolStructureViewModel;
+import com.redhat.devtools.lsp4ij.ui.IconMapper;
+import org.eclipse.lsp4j.DocumentSymbol;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+
+/**
+ * LSP documentSymbol feature.
+ */
+@ApiStatus.Experimental
+public class LSPDocumentSymbolFeature extends AbstractLSPDocumentFeature {
+
+ @Override
+ public boolean isSupported(@NotNull PsiFile file) {
+ return isDocumentSymbolSupported(file);
+ }
+
+ /**
+ * Returns true if the file associated with a language server can support codelens and false otherwise.
+ *
+ * @param file the file.
+ * @return true if the file associated with a language server can support codelens and false otherwise.
+ */
+ public boolean isDocumentSymbolSupported(@NotNull PsiFile file) {
+ // TODO implement documentSelector to use language of the given file
+ return LanguageServerItem.isDocumentSymbolSupported(getClientFeatures().getServerWrapper().getServerCapabilitiesSync());
+ }
+
+ @Nullable
+ public StructureViewTreeElement getStructureViewTreeElement(@NotNull DocumentSymbolData documentSymbol) {
+ return new LSPDocumentSymbolStructureViewModel.LSPDocumentSymbolViewElement(documentSymbol);
+ }
+
+ public @Nullable String getPresentableText(@NotNull DocumentSymbol documentSymbol,
+ @NotNull PsiFile psiFile) {
+ return documentSymbol.getName();
+ }
+
+ public @Nullable Icon getIcon(@NotNull DocumentSymbol documentSymbol,
+ @NotNull PsiFile psiFile,
+ boolean unused) {
+ return IconMapper.getIcon(documentSymbol.getKind());
+ }
+
+ public @Nullable String getLocationString(@NotNull DocumentSymbol documentSymbol,
+ @NotNull PsiFile psiFile) {
+ return documentSymbol.getDetail();
+ }
+
+ public void navigate(@NotNull DocumentSymbol documentSymbol,
+ @NotNull PsiFile psiFile,
+ boolean requestFocus) {
+ var selectionRange = documentSymbol.getSelectionRange();
+ LSPIJUtils.openInEditor(psiFile.getVirtualFile(), selectionRange.getStart(), requestFocus, psiFile.getProject());
+ }
+
+ public boolean canNavigate(@NotNull DocumentSymbol documentSymbol,
+ @NotNull PsiFile psiFile) {
+ var selectionRange = documentSymbol.getSelectionRange();
+ return selectionRange != null && selectionRange.getStart() != null;
+ }
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPFoldingRangeFeature.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPFoldingRangeFeature.java
new file mode 100644
index 000000000..1fa973f19
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPFoldingRangeFeature.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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 https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.client.features;
+
+import com.intellij.psi.PsiFile;
+import com.redhat.devtools.lsp4ij.LanguageServerItem;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * LSP foldingRange feature.
+ */
+@ApiStatus.Experimental
+public class LSPFoldingRangeFeature extends AbstractLSPDocumentFeature {
+
+ @Override
+ public boolean isSupported(@NotNull PsiFile file) {
+ return isFoldingRangeSupported(file);
+ }
+
+ /**
+ * Returns true if the file associated with a language server can support folding range and false otherwise.
+ *
+ * @param file the file.
+ * @return true if the file associated with a language server can support folding range and false otherwise.
+ */
+ public boolean isFoldingRangeSupported(@NotNull PsiFile file) {
+ // TODO implement documentSelector to use language of the given file
+ return LanguageServerItem.isFoldingRangeSupported(getClientFeatures().getServerWrapper().getServerCapabilitiesSync());
+ }
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPFormattingFeature.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPFormattingFeature.java
new file mode 100644
index 000000000..028ea3a4b
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPFormattingFeature.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * 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 https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.client.features;
+
+import com.intellij.lang.LanguageFormatting;
+import com.intellij.psi.PsiFile;
+import com.redhat.devtools.lsp4ij.LanguageServerItem;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * LSP formatting feature.
+ */
+@ApiStatus.Experimental
+public class LSPFormattingFeature extends AbstractLSPDocumentFeature {
+
+ @Override
+ public boolean isEnabled(@NotNull PsiFile file) {
+ if (!isExistingFormatterOverrideable(file) && LanguageFormatting.INSTANCE.forContext(file) != null) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean isSupported(@NotNull PsiFile file) {
+ return isDocumentFormattingSupported(file);
+ }
+
+ /**
+ * Returns true if existing formatter are overrideable and false (default value) otherwise.
+ *
+ * @return true if existing formatter are overrideable and false (default value) otherwise.
+ */
+ protected boolean isExistingFormatterOverrideable(@NotNull PsiFile file) {
+ return false;
+ }
+
+ /**
+ * Returns true if the file associated with a language server can support formatting and false otherwise.
+ *
+ * @param file the file.
+ * @return true if the file associated with a language server can support formatting and false otherwise.
+ */
+ public boolean isDocumentFormattingSupported(@NotNull PsiFile file) {
+ // TODO implement documentSelector to use language of the given file
+ return LanguageServerItem.isDocumentFormattingSupported(getClientFeatures().getServerWrapper().getServerCapabilitiesSync());
+ }
+
+ /**
+ * Returns true if the file associated with a language server can support range formatting and false otherwise.
+ *
+ * @param file the file.
+ * @return true if the file associated with a language server can support range formatting and false otherwise.
+ */
+ public boolean isDocumentRangeFormattingSupported(@NotNull PsiFile file) {
+ // TODO implement documentSelector to use language of the given file
+ return LanguageServerItem.isDocumentRangeFormattingSupported(getClientFeatures().getServerWrapper().getServerCapabilitiesSync());
+ }
+
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPHoverFeature.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPHoverFeature.java
new file mode 100644
index 000000000..0beade2c4
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPHoverFeature.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * 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 https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.client.features;
+
+import com.intellij.openapi.util.text.StringUtilRt;
+import com.intellij.psi.PsiFile;
+import com.redhat.devtools.lsp4ij.LanguageServerItem;
+import com.redhat.devtools.lsp4ij.features.documentation.MarkdownConverter;
+import org.eclipse.lsp4j.MarkupContent;
+import org.eclipse.lsp4j.MarkupKind;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * LSP hover feature.
+ */
+@ApiStatus.Experimental
+public class LSPHoverFeature extends AbstractLSPDocumentFeature {
+
+ @Override
+ public boolean isSupported(@NotNull PsiFile file) {
+ return isHoverSupported(file);
+ }
+
+ /**
+ * Returns the HTML content from the given LSP Markup content and null otherwise.
+ *
+ * @param content the LSP Markup content.
+ * @param file the file.
+ * @return the HTML content from the given LSP Markup content and null otherwise.
+ */
+ @Nullable
+ public String getContent(@NotNull MarkupContent content,
+ @NotNull PsiFile file) {
+ if (MarkupKind.MARKDOWN.equals(content.getKind())) {
+ return convertMarkDownToHtml(content, file);
+ }
+ return content.getValue();
+ }
+
+ @Nullable
+ protected String convertMarkDownToHtml(@NotNull MarkupContent content,
+ @NotNull PsiFile file) {
+ var project = file.getProject();
+ return MarkdownConverter.getInstance(project)
+ .toHtml(StringUtilRt.convertLineSeparators(content.getValue()), file);
+ }
+
+ /**
+ * Returns true if the file associated with a language server can support hover and false otherwise.
+ *
+ * @param file the file.
+ * @return true if the file associated with a language server can support hover and false otherwise.
+ */
+ public boolean isHoverSupported(@NotNull PsiFile file) {
+ // TODO implement documentSelector to use language of the given file
+ return LanguageServerItem.isHoverSupported(getClientFeatures().getServerWrapper().getServerCapabilitiesSync());
+ }
+
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPImplementationFeature.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPImplementationFeature.java
new file mode 100644
index 000000000..6826312fa
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPImplementationFeature.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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 https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.client.features;
+
+import com.intellij.psi.PsiFile;
+import com.redhat.devtools.lsp4ij.LanguageServerItem;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * LSP implementation feature.
+ */
+@ApiStatus.Experimental
+public class LSPImplementationFeature extends AbstractLSPDocumentFeature {
+
+ @Override
+ public boolean isSupported(@NotNull PsiFile file) {
+ return isImplementationSupported(file);
+ }
+
+ /**
+ * Returns true if the file associated with a language server can support implementation and false otherwise.
+ *
+ * @param file the file.
+ * @return true if the file associated with a language server can support implementation and false otherwise.
+ */
+ public boolean isImplementationSupported(@NotNull PsiFile file) {
+ // TODO implement documentSelector to use language of the given file
+ return LanguageServerItem.isImplementationSupported(getClientFeatures().getServerWrapper().getServerCapabilitiesSync());
+ }
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPInlayHintFeature.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPInlayHintFeature.java
new file mode 100644
index 000000000..aba832535
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPInlayHintFeature.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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 https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.client.features;
+
+import com.intellij.psi.PsiFile;
+import com.redhat.devtools.lsp4ij.LanguageServerItem;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * LSP inlayHint feature.
+ */
+@ApiStatus.Experimental
+public class LSPInlayHintFeature extends AbstractLSPDocumentFeature {
+
+ @Override
+ public boolean isSupported(@NotNull PsiFile file) {
+ return isInlayHintSupported(file);
+ }
+
+ /**
+ * Returns true if the file associated with a language server can support codelens and false otherwise.
+ *
+ * @param file the file.
+ * @return true if the file associated with a language server can support codelens and false otherwise.
+ */
+ public boolean isInlayHintSupported(@NotNull PsiFile file) {
+ // TODO implement documentSelector to use language of the given file
+ return LanguageServerItem.isInlayHintSupported(getClientFeatures().getServerWrapper().getServerCapabilitiesSync());
+ }
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPReferencesFeature.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPReferencesFeature.java
new file mode 100644
index 000000000..0a3187a3e
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPReferencesFeature.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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 https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and references
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.client.features;
+
+import com.intellij.psi.PsiFile;
+import com.redhat.devtools.lsp4ij.LanguageServerItem;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * LSP references feature.
+ */
+@ApiStatus.Experimental
+public class LSPReferencesFeature extends AbstractLSPDocumentFeature {
+
+ @Override
+ public boolean isSupported(@NotNull PsiFile file) {
+ return isReferencesSupported(file);
+ }
+
+ /**
+ * Returns true if the file associated with a language server can support references and false otherwise.
+ *
+ * @param file the file.
+ * @return true if the file associated with a language server can support references and false otherwise.
+ */
+ public boolean isReferencesSupported(@NotNull PsiFile file) {
+ // TODO implement documentSelector to use language of the given file
+ return LanguageServerItem.isReferencesSupported(getClientFeatures().getServerWrapper().getServerCapabilitiesSync());
+ }
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPRenameFeature.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPRenameFeature.java
new file mode 100644
index 000000000..de2d6dec8
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPRenameFeature.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * 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 https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.client.features;
+
+import com.intellij.psi.PsiFile;
+import com.redhat.devtools.lsp4ij.LanguageServerItem;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * LSP rename feature.
+ */
+@ApiStatus.Experimental
+public class LSPRenameFeature extends AbstractLSPDocumentFeature {
+
+ @Override
+ public boolean isSupported(@NotNull PsiFile file) {
+ return isRenameSupported(file);
+ }
+
+ /**
+ * Returns true if the file associated with a language server can support codelens and false otherwise.
+ *
+ * @param file the file.
+ * @return true if the file associated with a language server can support codelens and false otherwise.
+ */
+ public boolean isRenameSupported(@NotNull PsiFile file) {
+ // TODO implement documentSelector to use language of the given file
+ return LanguageServerItem.isRenameSupported(getClientFeatures().getServerWrapper().getServerCapabilitiesSync());
+ }
+
+ public boolean isWillRenameFilesSupported(@NotNull PsiFile file) {
+ // TODO implement documentSelector to use language of the given file
+ return LanguageServerItem.isWillRenameFilesSupported(getClientFeatures().getServerWrapper().getServerCapabilitiesSync());
+ }
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPSemanticTokensFeature.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPSemanticTokensFeature.java
new file mode 100644
index 000000000..e59b3915e
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPSemanticTokensFeature.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * 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 https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.client.features;
+
+import com.intellij.openapi.editor.colors.TextAttributesKey;
+import com.intellij.psi.PsiFile;
+import com.redhat.devtools.lsp4ij.LanguageServerItem;
+import com.redhat.devtools.lsp4ij.features.semanticTokens.SemanticTokensColorsProvider;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+/**
+ * LSP semanticTokens feature.
+ */
+@ApiStatus.Experimental
+public class LSPSemanticTokensFeature extends AbstractLSPDocumentFeature implements SemanticTokensColorsProvider {
+
+ /**
+ * Returns the {@link TextAttributesKey} to use for colorization for the given token type and given token modifiers and null otherwise.
+ *
+ * @param tokenType the token type.
+ * @param tokenModifiers the token modifiers.
+ * @param file the Psi file.
+ * @return the {@link TextAttributesKey} to use for colorization for the given token type and given token modifiers and null otherwise.
+ */
+ @Override
+ public @Nullable TextAttributesKey getTextAttributesKey(@NotNull String tokenType,
+ @NotNull List tokenModifiers,
+ @NotNull PsiFile file) {
+ return getClientFeatures()
+ .getServerWrapper()
+ .getServerDefinition()
+ .getSemanticTokensColorsProvider()
+ .getTextAttributesKey(tokenType,tokenModifiers, file);
+ }
+
+ @Override
+ public boolean isSupported(@NotNull PsiFile file) {
+ return isSemanticTokensSupported(file);
+ }
+
+ /**
+ * Returns true if the file associated with a language server can support semanticTokens and false otherwise.
+ *
+ * @param file the file.
+ * @return true if the file associated with a language server can support semanticTokens and false otherwise.
+ */
+ public boolean isSemanticTokensSupported(@NotNull PsiFile file) {
+ // TODO implement documentSelector to use language of the given file
+ return LanguageServerItem.isSemanticTokensSupported(getClientFeatures().getServerWrapper().getServerCapabilitiesSync());
+ }
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPSignatureHelpFeature.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPSignatureHelpFeature.java
new file mode 100644
index 000000000..c74bfacf9
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPSignatureHelpFeature.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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 https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.client.features;
+
+import com.intellij.psi.PsiFile;
+import com.redhat.devtools.lsp4ij.LanguageServerItem;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * LSP signatureHelp feature.
+ */
+@ApiStatus.Experimental
+public class LSPSignatureHelpFeature extends AbstractLSPDocumentFeature {
+
+ @Override
+ public boolean isSupported(@NotNull PsiFile file) {
+ return isSignatureHelpSupported(file);
+ }
+
+ /**
+ * Returns true if the file associated with a language server can support signatureHelp and false otherwise.
+ *
+ * @param file the file.
+ * @return true if the file associated with a language server can support signatureHelp and false otherwise.
+ */
+ public boolean isSignatureHelpSupported(@NotNull PsiFile file) {
+ // TODO implement documentSelector to use language of the given file
+ return LanguageServerItem.isSignatureHelpSupported(getClientFeatures().getServerWrapper().getServerCapabilitiesSync());
+ }
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPTypeDefinitionFeature.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPTypeDefinitionFeature.java
new file mode 100644
index 000000000..f42c6479d
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPTypeDefinitionFeature.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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 https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.client.features;
+
+import com.intellij.psi.PsiFile;
+import com.redhat.devtools.lsp4ij.LanguageServerItem;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * LSP typeDefinition feature.
+ */
+@ApiStatus.Experimental
+public class LSPTypeDefinitionFeature extends AbstractLSPDocumentFeature {
+
+ @Override
+ public boolean isSupported(@NotNull PsiFile file) {
+ return isTypeDefinitionSupported(file);
+ }
+
+ /**
+ * Returns true if the file associated with a language server can support typeDefinition and false otherwise.
+ *
+ * @param file the file.
+ * @return true if the file associated with a language server can support typeDefinition and false otherwise.
+ */
+ public boolean isTypeDefinitionSupported(@NotNull PsiFile file) {
+ // TODO implement documentSelector to use language of the given file
+ return LanguageServerItem.isTypeDefinitionSupported(getClientFeatures().getServerWrapper().getServerCapabilitiesSync());
+ }
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPUsageFeature.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPUsageFeature.java
new file mode 100644
index 000000000..4c4bacdef
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPUsageFeature.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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 https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.client.features;
+
+import com.intellij.psi.PsiFile;
+import com.redhat.devtools.lsp4ij.LanguageServerItem;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * LSP usage feature.
+ */
+@ApiStatus.Experimental
+public class LSPUsageFeature extends AbstractLSPDocumentFeature {
+
+ @Override
+ public boolean isSupported(@NotNull PsiFile file) {
+ return isUsageSupported(file);
+ }
+
+ /**
+ * Returns true if the file associated with a language server can support usage and false otherwise.
+ *
+ * @param file the file.
+ * @return true if the file associated with a language server can support usage and false otherwise.
+ */
+ public boolean isUsageSupported(@NotNull PsiFile file) {
+ // TODO implement documentSelector to use language of the given file
+ return LanguageServerItem.isUsageSupported(getClientFeatures().getServerWrapper().getServerCapabilitiesSync());
+ }
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPWorkspaceSymbolFeature.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPWorkspaceSymbolFeature.java
new file mode 100644
index 000000000..cb01cc5ab
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPWorkspaceSymbolFeature.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * 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 https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.client.features;
+
+import com.redhat.devtools.lsp4ij.LanguageServerItem;
+import com.redhat.devtools.lsp4ij.ServerStatus;
+import org.jetbrains.annotations.ApiStatus;
+
+/**
+ * LSP workspace symbol feature.
+ */
+@ApiStatus.Experimental
+public class LSPWorkspaceSymbolFeature extends AbstractLSPWorkspaceFeature {
+
+ @Override
+ public boolean isEnabled() {
+ var serverStatus = getServerStatus();
+ return serverStatus == ServerStatus.starting || serverStatus == ServerStatus.started;
+ }
+
+ @Override
+ public boolean isSupported() {
+ return LanguageServerItem.isWorkspaceSymbolSupported(getClientFeatures().getServerWrapper().getServerCapabilitiesSync());
+ }
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/commands/CommandExecutor.java b/src/main/java/com/redhat/devtools/lsp4ij/commands/CommandExecutor.java
index 67887a254..807563033 100644
--- a/src/main/java/com/redhat/devtools/lsp4ij/commands/CommandExecutor.java
+++ b/src/main/java/com/redhat/devtools/lsp4ij/commands/CommandExecutor.java
@@ -74,7 +74,7 @@ public static boolean executeCommand(LSPCommandContext context) {
Command command = context.getCommand();
// 1. try to execute command on server side
- if (executeCommandServerSide(command, context.getPreferredLanguageServer())) {
+ if (executeCommandServerSide(command, context.getPreferredLanguageServer(), context.getPreferredLanguageServerId(), context.getProject())) {
return true;
}
@@ -117,7 +117,7 @@ public static boolean executeCommand(LSPCommandContext context) {
String commandId = context.getCommand().getCommand();
var preferredLanguageServer = context.getPreferredLanguageServer();
String content = preferredLanguageServer != null ?
- MarkdownConverter.getInstance(project).toHtml(LanguageServerBundle.message("lsp.command.error.with.ls.content", commandId, preferredLanguageServer.getServerWrapper().getServerDefinition().getDisplayName())):
+ MarkdownConverter.getInstance(project).toHtml(LanguageServerBundle.message("lsp.command.error.with.ls.content", commandId, preferredLanguageServer.getServerWrapper().getServerDefinition().getDisplayName())) :
MarkdownConverter.getInstance(project).toHtml(LanguageServerBundle.message("lsp.command.error.without.ls.content", commandId));
Notification notification = new Notification(LSPNotificationConstants.LSP4IJ_GENERAL_NOTIFICATIONS_ID,
@@ -134,47 +134,63 @@ public static boolean executeCommand(LSPCommandContext context) {
/**
* Execute LSP command on server side.
*
- * @param command the LSP Command to be executed. If {@code null} this method will
- * do nothing.
- * @param languageServer the language server for which the {@code command} is
- * applicable.
+ * @param command the LSP Command to be executed. If {@code null} this method will
+ * do nothing.
+ * @param languageServer the language server for which the {@code command} is
+ * applicable.
+ * @param preferredLanguageServerId
+ * @param project
* @return true if the LSP command on server side has been executed successfully and false otherwise.
*/
private static boolean executeCommandServerSide(@NotNull Command command,
- @Nullable LanguageServerItem languageServer) {
- CompletableFuture languageServerFuture = getLanguageServerForCommand(command, languageServer);
+ @Nullable LanguageServerItem languageServer,
+ @Nullable String preferredLanguageServerId,
+ @NotNull Project project) {
+ CompletableFuture languageServerFuture = getLanguageServerForCommand(command, languageServer, preferredLanguageServerId, project);
if (languageServerFuture == null) {
return false;
}
// Server can handle command
- languageServerFuture.thenAcceptAsync(server -> {
- ExecuteCommandParams params = new ExecuteCommandParams();
- params.setCommand(command.getCommand());
- params.setArguments(command.getArguments());
- server.getWorkspaceService()
- .executeCommand(params)
- .exceptionally(error -> {
- // Language server throws an error when executing a command
- // Display it with an IntelliJ notification.
- var languageServerWrapper = languageServer.getServerWrapper();
- MessageParams messageParams = new MessageParams(MessageType.Error, error.getMessage());
- var languageServerDefinition = languageServerWrapper.getServerDefinition();
- ServerMessageHandler.showMessage(languageServerDefinition.getDisplayName(), messageParams, languageServerWrapper.getProject());
- return error;
- });
- });
+ languageServerFuture
+ .thenAcceptAsync(server -> {
+ if (server == null) {
+ MessageParams messageParams = new MessageParams(MessageType.Error, "TODO");
+ ServerMessageHandler.showMessage("Unkwonw server id" + preferredLanguageServerId, messageParams, project);
+ return;
+ }
+ ExecuteCommandParams params = new ExecuteCommandParams();
+ params.setCommand(command.getCommand());
+ params.setArguments(command.getArguments());
+ server.getWorkspaceService()
+ .executeCommand(params)
+ .exceptionally(error -> {
+ // Language server throws an error when executing a command
+ // Display it with an IntelliJ notification.
+ var languageServerWrapper = languageServer.getServerWrapper();
+ MessageParams messageParams = new MessageParams(MessageType.Error, error.getMessage());
+ var languageServerDefinition = languageServerWrapper.getServerDefinition();
+ ServerMessageHandler.showMessage(languageServerDefinition.getDisplayName(), messageParams, project);
+ return error;
+ });
+ });
return true;
}
@Nullable
private static CompletableFuture getLanguageServerForCommand(@NotNull Command command,
- @Nullable LanguageServerItem languageServer) {
+ @Nullable LanguageServerItem languageServer,
+ @Nullable String preferredLanguageServerId, @NotNull Project project) {
if (languageServer != null && languageServer.supportsCommand(command)) {
return languageServer
.getServerWrapper()
.getInitializedServer();
}
+ if (preferredLanguageServerId != null) {
+ return LanguageServerManager.getInstance(project)
+ .getLanguageServer(preferredLanguageServerId)
+ .thenApply(ls -> ls != null ? ls.getServer() : null);
+ }
return null;
}
@@ -336,7 +352,7 @@ private static WorkspaceEdit createWorkspaceEdit(List