From 0d570cff9e85a0dd27013afb15022e18f1814b23 Mon Sep 17 00:00:00 2001 From: azerr Date: Thu, 26 Sep 2024 11:35:48 +0200 Subject: [PATCH] feat: provide LSP API support Signed-off-by: azerr --- docs/LSPApi.md | 92 +++++ .../lsp4ij/LanguageServerFactory.java | 4 + .../devtools/lsp4ij/LanguageServerItem.java | 6 +- .../lsp4ij/LanguageServerWrapper.java | 11 + .../lsp4ij/LanguageServiceAccessor.java | 63 ++-- .../client/features/AbstractLSPFeature.java | 53 +++ .../client/features/LSPClientFeatures.java | 339 ++++++++++++++++++ .../client/features/LSPCodeLensFeature.java | 34 ++ .../client/features/LSPColorFeature.java | 34 ++ .../client/features/LSPCompletionFeature.java | 35 ++ .../features/LSPDeclarationFeature.java | 34 ++ .../client/features/LSPDiagnosticFeature.java | 235 ++++++++++++ .../features/LSPDocumentHighlightFeature.java | 34 ++ .../features/LSPDocumentLinkFeature.java | 34 ++ .../features/LSPDocumentSymbolFeature.java | 34 ++ .../features/LSPFoldingRangeFeature.java | 34 ++ .../client/features/LSPFormattingFeature.java | 100 ++++++ .../client/features/LSPHoverFeature.java | 39 ++ .../quickfix/LSPLazyCodeActions.java | 4 +- .../features/codeLens/LSPCodeLensSupport.java | 13 +- .../features/color/LSPColorSupport.java | 13 +- .../completion/LSPCompletionSupport.java | 13 +- .../declaration/LSPDeclarationSupport.java | 13 +- .../diagnostics/LSPDiagnosticAnnotator.java | 158 +------- .../diagnostics/LSPDiagnosticsForServer.java | 8 +- .../documentLink/LSPDocumentLinkSupport.java | 13 +- .../LSPDocumentSymbolSupport.java | 14 +- .../documentation/LSPHoverSupport.java | 14 +- .../foldingRange/LSPFoldingRangeSupport.java | 13 +- .../AbstractLSPFormattingService.java | 23 +- .../LSPFormattingAndRangeBothService.java | 9 +- .../formatting/LSPFormattingOnlyService.java | 9 +- .../highlight/LSPHighlightSupport.java | 17 +- .../definition/LanguageServerDefinition.java | 23 +- .../ExtensionLanguageServerDefinition.java | 17 +- 35 files changed, 1294 insertions(+), 295 deletions(-) create mode 100644 docs/LSPApi.md create mode 100644 src/main/java/com/redhat/devtools/lsp4ij/client/features/AbstractLSPFeature.java create mode 100644 src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPClientFeatures.java create mode 100644 src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPCodeLensFeature.java create mode 100644 src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPColorFeature.java create mode 100644 src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPCompletionFeature.java create mode 100644 src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDeclarationFeature.java create mode 100644 src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDiagnosticFeature.java create mode 100644 src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDocumentHighlightFeature.java create mode 100644 src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDocumentLinkFeature.java create mode 100644 src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDocumentSymbolFeature.java create mode 100644 src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPFoldingRangeFeature.java create mode 100644 src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPFormattingFeature.java create mode 100644 src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPHoverFeature.java diff --git a/docs/LSPApi.md b/docs/LSPApi.md new file mode 100644 index 000000000..2b6a1fb2e --- /dev/null +++ b/docs/LSPApi.md @@ -0,0 +1,92 @@ +# 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 customizing the behavior of LSP features to customize: + +* [LSP completion feature](#customize-lsp-completion-feature) +* [LSP diagnostic feature](#customize-lsp-diagnostic-feature) +* [LSP formatting feature](#customize-lsp-formatting-feature) + +These custom supports are done: + +* by extending the default classes `LSP*Feature` (e.g. creating a new class `MyLSPFormattingFeature` that extends +[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 some methods to customize the behavior. +* registering your custom classes with `LanguageServerFactory#createClientFeatures(@NotNull Project)`: + +```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 + } +} +``` + +## Customize LSP completion feature + +TODO + +## Customize LSP diagnostic feature + +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); + } + +} +``` + +## Customize LSP formatting feature + +Here is an example of code that allows to execute the LSP formatter even if there is a specific formatter registered by an IntelliJ plugin + +TODO: revisit this API to manage range formatting too. + +```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 return false if it has custom formatter with psi + // returns true even if there is custom formatter + return true; + } +} +``` \ No newline at end of file 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..aa001b6a0 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/LanguageServerItem.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/LanguageServerItem.java @@ -340,12 +340,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()); } diff --git a/src/main/java/com/redhat/devtools/lsp4ij/LanguageServerWrapper.java b/src/main/java/com/redhat/devtools/lsp4ij/LanguageServerWrapper.java index 16f714eb1..fb839b14b 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); @@ -1174,4 +1177,12 @@ public boolean isSignatureTriggerCharactersSupported(String charTyped) { } return triggerCharacters.contains(charTyped); } + + public LSPClientFeatures getClientFeatures() { + if (clientFeatures == null) { + 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..c68c7bd21 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/LanguageServiceAccessor.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/LanguageServiceAccessor.java @@ -21,6 +21,7 @@ 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; @@ -35,7 +36,6 @@ import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.function.Predicate; -import java.util.stream.Collectors; /** * Language server accessor. @@ -63,11 +63,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 +81,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 +123,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, project, Set.of(definition), matchedServers, null); for (var ls : matchedServers) { ls.restart(); } @@ -144,7 +144,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 +154,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(); @@ -177,21 +176,23 @@ public boolean hasAny(@NotNull VirtualFile file, @NotNull public CompletableFuture> getLanguageServers(@NotNull VirtualFile file, - @Nullable Predicate filter) { - return getLanguageServers(file, filter, null); + @Nullable Predicate beforeStartingServerFilter, + @Nullable Predicate afterStartingServerFilter) { + return getLanguageServers(file, beforeStartingServerFilter, afterStartingServerFilter, null); } @NotNull - public CompletableFuture> getLanguageServers(@NotNull VirtualFile file, - @Nullable Predicate filter, - @Nullable LanguageServerDefinition matchServerDefinition) { + private CompletableFuture> getLanguageServers(@NotNull VirtualFile file, + @Nullable Predicate beforeStartingServerFilter, + @Nullable Predicate afterStartingServerFilter, + @Nullable LanguageServerDefinition matchServerDefinition) { URI uri = LSPIJUtils.toUri(file); if (uri == null) { return CompletableFuture.completedFuture(Collections.emptyList()); } // 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 +210,12 @@ public CompletableFuture> getLanguageServers(@NotNull V return matchedServers .thenComposeAsync(result -> CompletableFuture.allOf(result .stream() + .filter(wrapper -> !wrapper.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,7 +250,8 @@ 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 @@ -264,9 +266,9 @@ private CompletableFuture> getMatchedLanguageS if (!serverDefinitions.contains(matchServerDefinition)) { return CompletableFuture.completedFuture(Collections.emptyList()); } - collectLanguageServersFromDefinition(file, project, Set.of(matchServerDefinition), matchedServers); + collectLanguageServersFromDefinition(file, project, Set.of(matchServerDefinition), matchedServers, beforeStartingServerFilter); } else { - collectLanguageServersFromDefinition(file, project, serverDefinitions, matchedServers); + collectLanguageServersFromDefinition(file, project, serverDefinitions, matchedServers, beforeStartingServerFilter); } CompletableFuture> async = mappings.getAsyncMatched(); @@ -274,7 +276,7 @@ private CompletableFuture> getMatchedLanguageS // Collect async server definitions return async .thenApply(asyncServerDefinitions -> { - collectLanguageServersFromDefinition(file, project, asyncServerDefinitions, matchedServers); + collectLanguageServersFromDefinition(file, project, asyncServerDefinitions, matchedServers, beforeStartingServerFilter); return matchedServers; }); } @@ -288,15 +290,21 @@ private CompletableFuture> getMatchedLanguageS * @param fileProject the file project. * @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 Project fileProject, + @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; @@ -306,8 +314,10 @@ 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); + if (beforeStartingServerFilter == null || beforeStartingServerFilter.test(wrapper.getClientFeatures())) { + startedServers.add(wrapper); + matchedServers.add(wrapper); + } } } } @@ -371,9 +381,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(); @@ -479,7 +487,8 @@ public List getActiveLanguageServers(Predicate getLanguageServers(@Nullable Project project, - Predicate request, boolean onlyActiveLS) { + @Nullable Predicate request, + boolean onlyActiveLS) { List serverInfos = new ArrayList<>(); for (LanguageServerWrapper wrapper : startedServers) { if ((!onlyActiveLS || wrapper.isActive()) && (project == null || wrapper.canOperate(project))) { 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..cff942d17 --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/AbstractLSPFeature.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * 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 feature. + */ +@ApiStatus.Experimental +public abstract class AbstractLSPFeature { + + private LSPClientFeatures clientFeatures; + + /** + * Returns the LSP server support. + * + * @return the LSP server support. + */ + public @NotNull LSPClientFeatures getClientFeatures() { + return clientFeatures; + } + + public boolean isEnabled(@NotNull PsiFile file) { + return true; + } + + /** + * Set the LSP server support. + * + * @param clientFeatures the LSP client features. + */ + public void setClientFeatures(@NotNull LSPClientFeatures clientFeatures) { + this.clientFeatures = clientFeatures; + } + + public boolean isEnabled(@NotNull T context) { + // TODO: remove this method + return true; + } + + +} 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..5729e839c --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPClientFeatures.java @@ -0,0 +1,339 @@ +/******************************************************************************* + * 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.project.Project; +import com.redhat.devtools.lsp4ij.LanguageServerWrapper; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +/** + * LSP client features. + */ +@ApiStatus.Experimental +public class LSPClientFeatures { + + private LanguageServerWrapper serverWrapper; + + private LSPCodeLensFeature codeLensFeature; + + private LSPColorFeature colorFeature; + + private LSPCompletionFeature completionFeature; + + private LSPDeclarationFeature declarationFeature; + + private LSPDocumentLinkFeature documentLinkFeature; + + private LSPDocumentHighlightFeature documentHighlightFeature; + + private LSPDocumentSymbolFeature documentSymbolFeature; + + private LSPDiagnosticFeature diagnosticFeature; + + private LSPFoldingRangeFeature foldingRangeFeature; + + private LSPFormattingFeature formattingFeature; + + private LSPHoverFeature hoverFeature; + + /** + * Returns the project. + * + * @return the project. + */ + public @NotNull Project getProject() { + return serverWrapper.getProject(); + } + + /** + * 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 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 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; + } + + public void setServerWrapper(LanguageServerWrapper serverWrapper) { + this.serverWrapper = serverWrapper; + } + + LanguageServerWrapper getServerWrapper() { + return serverWrapper; + } +} 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..45d21753e --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPCodeLensFeature.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * 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 codeLens feature. + */ +@ApiStatus.Experimental +public class LSPCodeLensFeature extends AbstractLSPFeature { + + /** + * 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()); + } +} 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..dbc667d8a --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPColorFeature.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * 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 AbstractLSPFeature { + + /** + * 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 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..3b61c08bb --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPCompletionFeature.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * 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 completion feature. + */ +@ApiStatus.Experimental +public class LSPCompletionFeature extends AbstractLSPFeature { + + /** + * 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()); + } + +} 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..9e89ef366 --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDeclarationFeature.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * 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 AbstractLSPFeature { + + /** + * 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/LSPDiagnosticFeature.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDiagnosticFeature.java new file mode 100644 index 000000000..9c132ced3 --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDiagnosticFeature.java @@ -0,0 +1,235 @@ +/******************************************************************************* + * 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.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 AbstractLSPFeature { + + public static class LSPDiagnosticFeatureContext { + + } + + @Override + public boolean isEnabled(@NotNull LSPDiagnosticFeatureContext context) { + 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("
    "); + for (var item : information) { + String message = item.getMessage(); + tooltip.append("
  • "); + Location location = item.getLocation(); + if (location != null) { + String fileName = getFileName(location); + String fileUrl = LSPNavigationLinkHandler.toNavigationUrl(location); + addLink(fileName, fileUrl, tooltip); + tooltip.append(": "); + } + tooltip.append(message); + tooltip.append("
  • "); + } + 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..e611e4eb1 --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDocumentHighlightFeature.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * 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 AbstractLSPFeature { + + /** + * 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..3da1a18e1 --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDocumentLinkFeature.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * 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 AbstractLSPFeature { + + /** + * 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..36d62bfe3 --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPDocumentSymbolFeature.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * 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 documentSymbol feature. + */ +@ApiStatus.Experimental +public class LSPDocumentSymbolFeature extends AbstractLSPFeature { + + /** + * 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()); + } +} 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..9605291ef --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPFoldingRangeFeature.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * 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 AbstractLSPFeature { + + /** + * 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..ec23ab4de --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPFormattingFeature.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at 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 AbstractLSPFeature { + + public static class LSPFormattingFeatureContext { + + private final @NotNull PsiFile file; + private final @NotNull LSPFormattingFeature.LSPFormattingFeatureContext.@NotNull FormattingKind formattingKind; + + public enum FormattingKind { + ONLY_DOCUMENT, + DOCUMENT_AND_RANGE; + } + + public LSPFormattingFeatureContext(@NotNull PsiFile file, @NotNull FormattingKind formattingKind) { + this.file = file; + this.formattingKind = formattingKind; + } + + public @NotNull PsiFile getFile() { + return file; + } + + public @NotNull LSPFormattingFeature.LSPFormattingFeatureContext.@NotNull FormattingKind getFormattingKind() { + return formattingKind; + } + } + + @Override + public boolean isEnabled(@NotNull PsiFile file) { + if (!isExistingFormatterOverrideable(file) && LanguageFormatting.INSTANCE.forContext(file) != null) { + return false; + } + return true; + } + + @Override + public boolean isEnabled(@NotNull LSPFormattingFeatureContext context) { + PsiFile file = context.getFile(); + if (!isExistingFormatterOverrideable(file) && LanguageFormatting.INSTANCE.forContext(file) != null) { + return false; + } + if (context.getFormattingKind() == LSPFormattingFeatureContext.FormattingKind.DOCUMENT_AND_RANGE) { + return isDocumentRangeFormattingSupported(file); + } + return isDocumentFormattingSupported(file) && !isDocumentRangeFormattingSupported(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. + */ + protected 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. + */ + protected 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..5edee1268 --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPHoverFeature.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 hover feature. + */ +@ApiStatus.Experimental +public class LSPHoverFeature extends AbstractLSPFeature { + + @Override + public boolean isEnabled(@NotNull Object context) { + return false; + } + + /** + * 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/features/codeAction/quickfix/LSPLazyCodeActions.java b/src/main/java/com/redhat/devtools/lsp4ij/features/codeAction/quickfix/LSPLazyCodeActions.java index 6466aeccc..c91536c8d 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/features/codeAction/quickfix/LSPLazyCodeActions.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/codeAction/quickfix/LSPLazyCodeActions.java @@ -56,7 +56,7 @@ public class LSPLazyCodeActions implements LSPLazyCodeActionProvider { private final LanguageServerItem languageServer; // List of lazy code actions - private final List codeActions; + private final List codeActions; // LSP code actions request used to load code action for the diagnostic. private CompletableFuture> lspCodeActionRequest = null; @@ -181,7 +181,7 @@ private static CodeActionParams createCodeActionParams(List diagnost * * @return the list of lazy code actions. */ - public List getCodeActions() { + public List getCodeActions() { return codeActions; } diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/codeLens/LSPCodeLensSupport.java b/src/main/java/com/redhat/devtools/lsp4ij/features/codeLens/LSPCodeLensSupport.java index 1c5dabbd6..0540a105b 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/features/codeLens/LSPCodeLensSupport.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/codeLens/LSPCodeLensSupport.java @@ -10,8 +10,6 @@ ******************************************************************************/ package com.redhat.devtools.lsp4ij.features.codeLens; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiFile; import com.redhat.devtools.lsp4ij.LSPRequestConstants; import com.redhat.devtools.lsp4ij.LanguageServerItem; @@ -52,16 +50,17 @@ public CompletableFuture> getCodeLenses(CodeLensParams params @Override protected CompletableFuture> doLoad(CodeLensParams params, CancellationSupport cancellationSupport) { PsiFile file = super.getFile(); - return getCodeLenses(file.getVirtualFile(), file.getProject(), params, cancellationSupport); + return getCodeLenses(file, params, cancellationSupport); } - private static @NotNull CompletableFuture> getCodeLenses(@NotNull VirtualFile file, - @NotNull Project project, + private static @NotNull CompletableFuture> getCodeLenses(@NotNull PsiFile file, @NotNull CodeLensParams params, @NotNull CancellationSupport cancellationSupport) { - return LanguageServiceAccessor.getInstance(project) - .getLanguageServers(file, LanguageServerItem::isCodeLensSupported) + return LanguageServiceAccessor.getInstance(file.getProject()) + .getLanguageServers(file.getVirtualFile(), + f -> f.getCodeLensFeature().isEnabled(file), + f -> f.getCodeLensFeature().isCodeLensSupported(file)) .thenComposeAsync(languageServers -> { // Here languageServers is the list of language servers which matches the given file // and which have code lens capability diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/color/LSPColorSupport.java b/src/main/java/com/redhat/devtools/lsp4ij/features/color/LSPColorSupport.java index 963d09863..920ed18d9 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/features/color/LSPColorSupport.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/color/LSPColorSupport.java @@ -10,8 +10,6 @@ ******************************************************************************/ package com.redhat.devtools.lsp4ij.features.color; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiFile; import com.redhat.devtools.lsp4ij.LSPRequestConstants; import com.redhat.devtools.lsp4ij.LanguageServerItem; @@ -48,16 +46,17 @@ public CompletableFuture> getColors(DocumentColorParams params) protected CompletableFuture> doLoad(@NotNull DocumentColorParams params, @NotNull CancellationSupport cancellationSupport) { PsiFile file = super.getFile(); - return getColors(file.getVirtualFile(), file.getProject(), params, cancellationSupport); + return getColors(file, params, cancellationSupport); } - private static @NotNull CompletableFuture> getColors(@NotNull VirtualFile file, - @NotNull Project project, + private static @NotNull CompletableFuture> getColors(@NotNull PsiFile file, @NotNull DocumentColorParams params, @NotNull CancellationSupport cancellationSupport) { - return LanguageServiceAccessor.getInstance(project) - .getLanguageServers(file, LanguageServerItem::isColorSupported) + return LanguageServiceAccessor.getInstance(file.getProject()) + .getLanguageServers(file.getVirtualFile(), + f -> f.getColorFeature().isEnabled(file), + f -> f.getColorFeature().isColorSupported(file)) .thenComposeAsync(languageServers -> { // Here languageServers is the list of language servers which matches the given file // and which have color capability diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/completion/LSPCompletionSupport.java b/src/main/java/com/redhat/devtools/lsp4ij/features/completion/LSPCompletionSupport.java index e3f5105da..502064abd 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/features/completion/LSPCompletionSupport.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/completion/LSPCompletionSupport.java @@ -10,8 +10,6 @@ ******************************************************************************/ package com.redhat.devtools.lsp4ij.features.completion; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiFile; import com.redhat.devtools.lsp4ij.LSPRequestConstants; import com.redhat.devtools.lsp4ij.LanguageServerItem; @@ -54,16 +52,17 @@ public CompletableFuture> getCompletions(LSPCompletionParam protected CompletableFuture> doLoad(@NotNull LSPCompletionParams params, @NotNull CancellationSupport cancellationSupport) { PsiFile file = super.getFile(); - return getCompletions(file.getVirtualFile(), file.getProject(), params, cancellationSupport); + return getCompletions(file, params, cancellationSupport); } - private static @NotNull CompletableFuture> getCompletions(@NotNull VirtualFile file, - @NotNull Project project, + private static @NotNull CompletableFuture> getCompletions(@NotNull PsiFile file, @NotNull LSPCompletionParams params, @NotNull CancellationSupport cancellationSupport) { - return LanguageServiceAccessor.getInstance(project) - .getLanguageServers(file, LanguageServerItem::isCompletionSupported) + return LanguageServiceAccessor.getInstance(file.getProject()) + .getLanguageServers(file.getVirtualFile(), + f -> f.getCompletionFeature().isEnabled(file), + f -> f.getCompletionFeature().isCompletionSupported(file)) .thenComposeAsync(languageServers -> { // Here languageServers is the list of language servers which matches the given file // and which have completion capability diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/declaration/LSPDeclarationSupport.java b/src/main/java/com/redhat/devtools/lsp4ij/features/declaration/LSPDeclarationSupport.java index 2624d9315..df1a69566 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/features/declaration/LSPDeclarationSupport.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/declaration/LSPDeclarationSupport.java @@ -54,16 +54,17 @@ public CompletableFuture> getDeclarations(LSPDeclarationParams pa @Override protected CompletableFuture> doLoad(LSPDeclarationParams params, CancellationSupport cancellationSupport) { PsiFile file = super.getFile(); - return collectDeclarations(file.getVirtualFile(), file.getProject(), params, cancellationSupport); + return collectDeclarations(file, params, cancellationSupport); } - private static @NotNull CompletableFuture> collectDeclarations(@NotNull VirtualFile file, - @NotNull Project project, + private static @NotNull CompletableFuture> collectDeclarations(@NotNull PsiFile file, @NotNull LSPDeclarationParams params, @NotNull CancellationSupport cancellationSupport) { - var textDocumentIdentifier = LSPIJUtils.toTextDocumentIdentifier(file); - return LanguageServiceAccessor.getInstance(project) - .getLanguageServers(file, LanguageServerItem::isDeclarationSupported) + var textDocumentIdentifier = LSPIJUtils.toTextDocumentIdentifier(file.getVirtualFile()); + return LanguageServiceAccessor.getInstance(file.getProject()) + .getLanguageServers(file.getVirtualFile(), + f -> f.getDeclarationFeature().isEnabled(file), + f -> f.getDeclarationFeature().isDeclarationSupported(file)) .thenComposeAsync(languageServers -> { // Here languageServers is the list of language servers which matches the given file // and which have declaration capability diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/diagnostics/LSPDiagnosticAnnotator.java b/src/main/java/com/redhat/devtools/lsp4ij/features/diagnostics/LSPDiagnosticAnnotator.java index a1ee8724d..21270b0f2 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/features/diagnostics/LSPDiagnosticAnnotator.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/diagnostics/LSPDiagnosticAnnotator.java @@ -14,25 +14,20 @@ package com.redhat.devtools.lsp4ij.features.diagnostics; import com.intellij.codeInsight.intention.IntentionAction; -import com.intellij.codeInspection.ProblemHighlightType; -import com.intellij.lang.annotation.*; +import com.intellij.lang.annotation.AnnotationHolder; +import com.intellij.lang.annotation.ExternalAnnotator; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.util.Key; -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.LSPVirtualFileData; import com.redhat.devtools.lsp4ij.LanguageServersRegistry; import com.redhat.devtools.lsp4ij.LanguageServiceAccessor; import com.redhat.devtools.lsp4ij.features.AbstractLSPExternalAnnotator; -import com.redhat.devtools.lsp4ij.features.codeAction.LSPLazyCodeActionIntentionAction; -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 com.redhat.devtools.lsp4ij.client.features.LSPDiagnosticFeature; +import org.eclipse.lsp4j.Diagnostic; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -95,148 +90,9 @@ private static void createAnnotation(@NotNull Diagnostic diagnostic, @Nullable PsiFile file, @NotNull LSPDiagnosticsForServer diagnosticsForServer, @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; - } - - // Collect information required to create Intellij Annotations - HighlightSeverity severity = SeverityMapping.toHighlightSeverity(diagnostic.getSeverity()); - String message = diagnostic.getMessage(); - - // 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 - List fixes = diagnosticsForServer.getQuickFixesFor(diagnostic); - for (IntentionAction fix : fixes) { - builder.withFix(fix); - } - builder.create(); - } - - /** - * 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 - private static 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; - } - - /** - * Returns the annotation tooltip from the given LSP diagnostic. - * - * @param diagnostic the LSP diagnostic. - * @return the annotation tooltip from the given LSP diagnostic. - */ - private static String getToolTip(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 informations = diagnostic.getRelatedInformation(); - if (informations != null) { - tooltip.append("
    "); - for (var information : informations) { - String message = information.getMessage(); - tooltip.append("
  • "); - Location location = information.getLocation(); - if (location != null) { - String fileName = getFileName(location); - String fileUrl = LSPNavigationLinkHandler.toNavigationUrl(location); - addLink(fileName, fileUrl, tooltip); - tooltip.append(": "); - } - tooltip.append(message); - tooltip.append("
  • "); - } - 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(""); - } + LSPDiagnosticFeature diagnosticSupport = diagnosticsForServer.getServerSupport().getDiagnosticFeature(); + List fixes = diagnosticsForServer.getQuickFixesFor(diagnostic); + diagnosticSupport.createAnnotation(diagnostic, document, fixes, holder); } } \ No newline at end of file diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/diagnostics/LSPDiagnosticsForServer.java b/src/main/java/com/redhat/devtools/lsp4ij/features/diagnostics/LSPDiagnosticsForServer.java index 012c7f179..08cd2ed23 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/features/diagnostics/LSPDiagnosticsForServer.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/diagnostics/LSPDiagnosticsForServer.java @@ -13,11 +13,12 @@ *******************************************************************************/ package com.redhat.devtools.lsp4ij.features.diagnostics; +import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.openapi.vfs.VirtualFile; import com.redhat.devtools.lsp4ij.LanguageServerItem; import com.redhat.devtools.lsp4ij.LanguageServerWrapper; -import com.redhat.devtools.lsp4ij.features.codeAction.LSPLazyCodeActionIntentionAction; import com.redhat.devtools.lsp4ij.features.codeAction.quickfix.LSPLazyCodeActions; +import com.redhat.devtools.lsp4ij.client.features.LSPClientFeatures; import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; @@ -53,6 +54,9 @@ public LSPDiagnosticsForServer(LanguageServerItem languageServer, VirtualFile fi this.diagnostics = Collections.emptyMap(); } + public LSPClientFeatures getServerSupport() { + return languageServer.getServerWrapper().getClientFeatures(); + } /** * Update the new LSP published diagnosics. * @@ -134,7 +138,7 @@ public Set getDiagnostics() { * @param diagnostic the diagnostic. * @return Intellij quickfixes for the given diagnostic if there available. */ - public List getQuickFixesFor(Diagnostic diagnostic) { + public List getQuickFixesFor(Diagnostic diagnostic) { boolean codeActionSupported = isCodeActionSupported(languageServer.getServerWrapper()); if (!codeActionSupported || diagnostics.isEmpty()) { return Collections.emptyList(); diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/documentLink/LSPDocumentLinkSupport.java b/src/main/java/com/redhat/devtools/lsp4ij/features/documentLink/LSPDocumentLinkSupport.java index 9a4ec589f..3305ffa09 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/features/documentLink/LSPDocumentLinkSupport.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/documentLink/LSPDocumentLinkSupport.java @@ -10,8 +10,6 @@ ******************************************************************************/ package com.redhat.devtools.lsp4ij.features.documentLink; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiFile; import com.redhat.devtools.lsp4ij.LSPRequestConstants; import com.redhat.devtools.lsp4ij.LanguageServerItem; @@ -48,16 +46,17 @@ public CompletableFuture> getDocumentLinks(DocumentLinkPa protected CompletableFuture> doLoad(@NotNull DocumentLinkParams params, @NotNull CancellationSupport cancellationSupport) { PsiFile file = super.getFile(); - return getDocumentLinks(file.getVirtualFile(), file.getProject(), params, cancellationSupport); + return getDocumentLinks(file, params, cancellationSupport); } - private static @NotNull CompletableFuture> getDocumentLinks(@NotNull VirtualFile file, - @NotNull Project project, + private static @NotNull CompletableFuture> getDocumentLinks(@NotNull PsiFile file, @NotNull DocumentLinkParams params, @NotNull CancellationSupport cancellationSupport) { - return LanguageServiceAccessor.getInstance(project) - .getLanguageServers(file, LanguageServerItem::isDocumentLinkSupported) + return LanguageServiceAccessor.getInstance(file.getProject()) + .getLanguageServers(file.getVirtualFile(), + f -> f.getDocumentLinkFeature().isEnabled(file), + f -> f.getDocumentLinkFeature().isDocumentLinkSupported(file)) .thenComposeAsync(languageServers -> { // Here languageServers is the list of language servers which matches the given file // and which have document link capability diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/documentSymbol/LSPDocumentSymbolSupport.java b/src/main/java/com/redhat/devtools/lsp4ij/features/documentSymbol/LSPDocumentSymbolSupport.java index cde680260..5aadafdb8 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/features/documentSymbol/LSPDocumentSymbolSupport.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/documentSymbol/LSPDocumentSymbolSupport.java @@ -10,8 +10,6 @@ ******************************************************************************/ package com.redhat.devtools.lsp4ij.features.documentSymbol; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiFile; import com.redhat.devtools.lsp4ij.LSPRequestConstants; import com.redhat.devtools.lsp4ij.LanguageServerItem; @@ -51,14 +49,14 @@ protected CompletableFuture> doLoad(DocumentSymbolParam return getDocumentSymbols(file, documentSymbolParams, cancellationSupport); } - private static @NotNull CompletableFuture> getDocumentSymbols(@NotNull PsiFile psiFile, + private static @NotNull CompletableFuture> getDocumentSymbols(@NotNull PsiFile file, @NotNull DocumentSymbolParams params, @NotNull CancellationSupport cancellationSupport) { - Project project = psiFile.getProject(); - VirtualFile file = psiFile.getVirtualFile(); - return LanguageServiceAccessor.getInstance(project) - .getLanguageServers(file, LanguageServerItem::isDocumentSymbolSupported) + return LanguageServiceAccessor.getInstance(file.getProject()) + .getLanguageServers(file.getVirtualFile(), + f -> f.getDocumentSymbolFeature().isEnabled(file), + f -> f.getDocumentSymbolFeature().isDocumentSymbolSupported(file)) .thenComposeAsync(languageServers -> { // Here languageServers is the list of language servers which matches the given file // and which have document link capability @@ -69,7 +67,7 @@ protected CompletableFuture> doLoad(DocumentSymbolParam // Collect list of textDocument/documentSymbol future for each language servers List>> documentSymbolInformationPerServerFutures = languageServers .stream() - .map(languageServer -> getDocumentSymbolsFor(params, languageServer, psiFile, cancellationSupport)) + .map(languageServer -> getDocumentSymbolsFor(params, languageServer, file, cancellationSupport)) .toList(); // Merge list of textDocument/documentSymbol future in one future which return the list of document link diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/documentation/LSPHoverSupport.java b/src/main/java/com/redhat/devtools/lsp4ij/features/documentation/LSPHoverSupport.java index cf36a0156..9e73c84c6 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/features/documentation/LSPHoverSupport.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/documentation/LSPHoverSupport.java @@ -11,8 +11,6 @@ package com.redhat.devtools.lsp4ij.features.documentation; import com.intellij.openapi.editor.Document; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiFile; import com.redhat.devtools.lsp4ij.LSPIJUtils; import com.redhat.devtools.lsp4ij.LSPRequestConstants; @@ -57,16 +55,16 @@ public CompletableFuture> getHover(int offset, Document document) { @Override protected CompletableFuture> doLoad(HoverParams params, CancellationSupport cancellationSupport) { PsiFile file = super.getFile(); - return getHover(file.getVirtualFile(), file.getProject(), params, cancellationSupport); + return getHover(file, params, cancellationSupport); } - private static @NotNull CompletableFuture> getHover(@NotNull VirtualFile file, - @NotNull Project project, + private static @NotNull CompletableFuture> getHover(@NotNull PsiFile file, @NotNull HoverParams params, @NotNull CancellationSupport cancellationSupport) { - - return LanguageServiceAccessor.getInstance(project) - .getLanguageServers(file, LanguageServerItem::isHoverSupported) + return LanguageServiceAccessor.getInstance(file.getProject()) + .getLanguageServers(file.getVirtualFile(), + f -> f.getHoverFeature().isEnabled(file), + f -> f.getHoverFeature().isHoverSupported(file)) .thenComposeAsync(languageServers -> { // Here languageServers is the list of language servers which matches the given file // and which have hover capability diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/foldingRange/LSPFoldingRangeSupport.java b/src/main/java/com/redhat/devtools/lsp4ij/features/foldingRange/LSPFoldingRangeSupport.java index 1ce251366..72d4bef41 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/features/foldingRange/LSPFoldingRangeSupport.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/foldingRange/LSPFoldingRangeSupport.java @@ -10,8 +10,6 @@ ******************************************************************************/ package com.redhat.devtools.lsp4ij.features.foldingRange; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiFile; import com.redhat.devtools.lsp4ij.LSPRequestConstants; import com.redhat.devtools.lsp4ij.LanguageServerItem; @@ -48,16 +46,17 @@ public CompletableFuture> getFoldingRanges(FoldingRangeReques @Override protected CompletableFuture> doLoad(FoldingRangeRequestParams params, CancellationSupport cancellationSupport) { PsiFile file = super.getFile(); - return getFoldingRanges(file.getVirtualFile(), file.getProject(), params, cancellationSupport); + return getFoldingRanges(file, params, cancellationSupport); } - private static @NotNull CompletableFuture> getFoldingRanges(@NotNull VirtualFile file, - @NotNull Project project, + private static @NotNull CompletableFuture> getFoldingRanges(@NotNull PsiFile file, @NotNull FoldingRangeRequestParams params, @NotNull CancellationSupport cancellationSupport) { - return LanguageServiceAccessor.getInstance(project) - .getLanguageServers(file, LanguageServerItem::isFoldingSupported) + return LanguageServiceAccessor.getInstance(file.getProject()) + .getLanguageServers(file.getVirtualFile(), + f -> f.getFoldingFeature().isEnabled(file), + f -> f.getFoldingRangeFeature().isFoldingRangeSupported(file)) .thenComposeAsync(languageServers -> { // Here languageServers is the list of language servers which matches the given file // and which have folding range capability diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/formatting/AbstractLSPFormattingService.java b/src/main/java/com/redhat/devtools/lsp4ij/features/formatting/AbstractLSPFormattingService.java index bbab2ee5b..e019c729b 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/features/formatting/AbstractLSPFormattingService.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/formatting/AbstractLSPFormattingService.java @@ -12,18 +12,14 @@ import com.intellij.formatting.service.AsyncDocumentFormattingService; import com.intellij.formatting.service.AsyncFormattingRequest; -import com.intellij.lang.LanguageFormatting; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiFile; -import com.redhat.devtools.lsp4ij.LSPFileSupport; -import com.redhat.devtools.lsp4ij.LSPIJUtils; -import com.redhat.devtools.lsp4ij.LanguageServersRegistry; -import com.redhat.devtools.lsp4ij.LanguageServiceAccessor; -import org.eclipse.lsp4j.ServerCapabilities; +import com.redhat.devtools.lsp4ij.*; +import com.redhat.devtools.lsp4ij.client.features.LSPFormattingFeature; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -108,19 +104,18 @@ public boolean isRunUnderProgress() { @Override public final boolean canFormat(@NotNull PsiFile file) { - if (!LanguageFormatting.INSTANCE.allForLanguage(file.getLanguage()).isEmpty()) { - // When IJ provides formatting for the language (ex : JAVA, HTML) - // the LSP formatting support is ignored to avoid having some conflicts when formatting is done - return false; - } if (!LanguageServersRegistry.getInstance().isFileSupported(file)) { // The file is not associated to a language server return false; } - // Check if the file can support formatting / range formatting + LSPFormattingFeature.LSPFormattingFeatureContext context = new LSPFormattingFeature.LSPFormattingFeatureContext(file, getFormattingKind()); Project project = file.getProject(); return LanguageServiceAccessor.getInstance(project) - .hasAny(file.getVirtualFile(), ls -> canSupportFormatting(ls.getServerCapabilitiesSync())); + .hasAny(file.getVirtualFile(), ls -> canSupportFormatting(ls, context)); + } + + private boolean canSupportFormatting(LanguageServerWrapper ls, LSPFormattingFeature.LSPFormattingFeatureContext context) { + return ls.getClientFeatures().getFormattingFeature().isEnabled(context); } private static TextRange getFormattingRange(AsyncFormattingRequest formattingRequest) { @@ -136,5 +131,5 @@ private static TextRange getFormattingRange(AsyncFormattingRequest formattingReq return textRange; } - protected abstract boolean canSupportFormatting(@Nullable ServerCapabilities serverCapabilities); + protected abstract LSPFormattingFeature.LSPFormattingFeatureContext.@NotNull FormattingKind getFormattingKind(); } diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/formatting/LSPFormattingAndRangeBothService.java b/src/main/java/com/redhat/devtools/lsp4ij/features/formatting/LSPFormattingAndRangeBothService.java index 8edd4b344..3309b544f 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/features/formatting/LSPFormattingAndRangeBothService.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/formatting/LSPFormattingAndRangeBothService.java @@ -10,10 +10,8 @@ ******************************************************************************/ package com.redhat.devtools.lsp4ij.features.formatting; -import com.redhat.devtools.lsp4ij.LanguageServerItem; -import org.eclipse.lsp4j.ServerCapabilities; +import com.redhat.devtools.lsp4ij.client.features.LSPFormattingFeature; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.Set; @@ -30,7 +28,8 @@ public class LSPFormattingAndRangeBothService extends AbstractLSPFormattingServi } @Override - protected boolean canSupportFormatting(@Nullable ServerCapabilities serverCapabilities) { - return LanguageServerItem.isDocumentRangeFormattingSupported(serverCapabilities); + protected LSPFormattingFeature.LSPFormattingFeatureContext.@NotNull FormattingKind getFormattingKind() { + return LSPFormattingFeature.LSPFormattingFeatureContext.FormattingKind.DOCUMENT_AND_RANGE; } + } diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/formatting/LSPFormattingOnlyService.java b/src/main/java/com/redhat/devtools/lsp4ij/features/formatting/LSPFormattingOnlyService.java index 2bb435f7f..2efc47a45 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/features/formatting/LSPFormattingOnlyService.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/formatting/LSPFormattingOnlyService.java @@ -10,10 +10,8 @@ ******************************************************************************/ package com.redhat.devtools.lsp4ij.features.formatting; -import com.redhat.devtools.lsp4ij.LanguageServerItem; -import org.eclipse.lsp4j.ServerCapabilities; +import com.redhat.devtools.lsp4ij.client.features.LSPFormattingFeature; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.Collections; import java.util.Set; @@ -31,8 +29,7 @@ public class LSPFormattingOnlyService extends AbstractLSPFormattingService { } @Override - protected boolean canSupportFormatting(@Nullable ServerCapabilities serverCapabilities) { - return LanguageServerItem.isDocumentFormattingSupported(serverCapabilities) && - !LanguageServerItem.isDocumentRangeFormattingSupported(serverCapabilities); + protected LSPFormattingFeature.LSPFormattingFeatureContext.@NotNull FormattingKind getFormattingKind() { + return LSPFormattingFeature.LSPFormattingFeatureContext.FormattingKind.ONLY_DOCUMENT; } } diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/highlight/LSPHighlightSupport.java b/src/main/java/com/redhat/devtools/lsp4ij/features/highlight/LSPHighlightSupport.java index f9a31fef6..995845ceb 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/features/highlight/LSPHighlightSupport.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/highlight/LSPHighlightSupport.java @@ -10,14 +10,12 @@ ******************************************************************************/ package com.redhat.devtools.lsp4ij.features.highlight; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiFile; +import com.redhat.devtools.lsp4ij.LSPRequestConstants; import com.redhat.devtools.lsp4ij.LanguageServerItem; import com.redhat.devtools.lsp4ij.LanguageServiceAccessor; -import com.redhat.devtools.lsp4ij.internal.CancellationSupport; import com.redhat.devtools.lsp4ij.features.AbstractLSPDocumentFeatureSupport; -import com.redhat.devtools.lsp4ij.LSPRequestConstants; +import com.redhat.devtools.lsp4ij.internal.CancellationSupport; import org.eclipse.lsp4j.DocumentHighlight; import org.eclipse.lsp4j.DocumentHighlightParams; import org.jetbrains.annotations.NotNull; @@ -48,16 +46,17 @@ public CompletableFuture> getHighlights(DocumentHighligh @Override protected CompletableFuture> doLoad(DocumentHighlightParams params, CancellationSupport cancellationSupport) { PsiFile file = super.getFile(); - return getHighlights(file.getVirtualFile(), file.getProject(), params, cancellationSupport); + return getHighlights(file, params, cancellationSupport); } - private static @NotNull CompletableFuture> getHighlights(@NotNull VirtualFile file, - @NotNull Project project, + private static @NotNull CompletableFuture> getHighlights(@NotNull PsiFile file, @NotNull DocumentHighlightParams params, @NotNull CancellationSupport cancellationSupport) { - return LanguageServiceAccessor.getInstance(project) - .getLanguageServers(file, LanguageServerItem::isDocumentHighlightSupported) + return LanguageServiceAccessor.getInstance(file.getProject()) + .getLanguageServers(file.getVirtualFile(), + f -> f.getDocumentHighlightFeature().isEnabled(file), + f -> f.getDocumentHighlightFeature().isDocumentHighlightSupported(file)) .thenComposeAsync(languageServers -> { // Here languageServers is the list of language servers which matches the given file // and which have documentHighlights capability diff --git a/src/main/java/com/redhat/devtools/lsp4ij/server/definition/LanguageServerDefinition.java b/src/main/java/com/redhat/devtools/lsp4ij/server/definition/LanguageServerDefinition.java index 2e3fe61b0..0e0a898a8 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/server/definition/LanguageServerDefinition.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/server/definition/LanguageServerDefinition.java @@ -22,7 +22,6 @@ import com.intellij.openapi.util.Pair; import com.redhat.devtools.lsp4ij.LanguageServerEnablementSupport; import com.redhat.devtools.lsp4ij.LanguageServerFactory; -import com.redhat.devtools.lsp4ij.client.LanguageClientImpl; import com.redhat.devtools.lsp4ij.features.semanticTokens.DefaultSemanticTokensColorsProvider; import com.redhat.devtools.lsp4ij.features.semanticTokens.SemanticTokensColorsProvider; import org.eclipse.lsp4j.jsonrpc.Launcher; @@ -43,12 +42,10 @@ public abstract class LanguageServerDefinition implements LanguageServerFactory, private static final int DEFAULT_LAST_DOCUMENTED_DISCONNECTED_TIMEOUT = 5; - private static SemanticTokensColorsProvider DEFAULT_SEMANTIC_TOKENS_COLORS_PROVIDER = new DefaultSemanticTokensColorsProvider(); + private static final SemanticTokensColorsProvider DEFAULT_SEMANTIC_TOKENS_COLORS_PROVIDER = new DefaultSemanticTokensColorsProvider(); - private final @NotNull - String id; - private final @NotNull - String name; + private final @NotNull String id; + private final @NotNull String name; private final boolean isSingleton; private final String description; private final int lastDocumentDisconnectedTimeout; @@ -110,7 +107,7 @@ public String getId() { */ @NotNull public String getDisplayName() { - return name != null ? name : id; + return name; } /** @@ -199,22 +196,12 @@ public List, String>> getFilenameMatcherMappings() { return null; } - @Override - public @NotNull LanguageClientImpl createLanguageClient(@NotNull Project project) { - return new LanguageClientImpl(project); - } - - @Override - public @NotNull Class getServerInterface() { - return LanguageServer.class; - } - public Launcher.Builder createLauncherBuilder() { return new Launcher.Builder<>(); } public boolean supportsCurrentEditMode(@NotNull Project project) { - return project != null && (supportsLightEdit || !LightEdit.owns(project)); + return (supportsLightEdit || !LightEdit.owns(project)); } public Icon getIcon() { diff --git a/src/main/java/com/redhat/devtools/lsp4ij/server/definition/extension/ExtensionLanguageServerDefinition.java b/src/main/java/com/redhat/devtools/lsp4ij/server/definition/extension/ExtensionLanguageServerDefinition.java index daf9500a3..d1d556e70 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/server/definition/extension/ExtensionLanguageServerDefinition.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/server/definition/extension/ExtensionLanguageServerDefinition.java @@ -21,6 +21,7 @@ import com.redhat.devtools.lsp4ij.features.semanticTokens.SemanticTokensColorsProvider; import com.redhat.devtools.lsp4ij.internal.StringUtils; import com.redhat.devtools.lsp4ij.server.StreamConnectionProvider; +import com.redhat.devtools.lsp4ij.client.features.LSPClientFeatures; import com.redhat.devtools.lsp4ij.server.definition.LanguageServerDefinition; import org.eclipse.lsp4j.services.LanguageServer; import org.jetbrains.annotations.NotNull; @@ -72,6 +73,20 @@ public ExtensionLanguageServerDefinition(@NotNull ServerExtensionPointBean eleme return languageClient; } + @Override + public @NotNull LSPClientFeatures createClientFeatures() { + LSPClientFeatures clientFeatures = null; + try { + clientFeatures = getFactory().createClientFeatures(); + } catch (Exception e) { + LOGGER.warn("Exception occurred while creating an instance of the LSP client features", e); //$NON-NLS-1$ + } + if (clientFeatures == null) { + clientFeatures = super.createClientFeatures(); + } + return clientFeatures; + } + @SuppressWarnings("unchecked") @Override public @NotNull Class getServerInterface() { @@ -146,7 +161,7 @@ private synchronized Icon findIcon() { } @Override - public SemanticTokensColorsProvider getSemanticTokensColorsProvider() { + public @NotNull SemanticTokensColorsProvider getSemanticTokensColorsProvider() { return super.getSemanticTokensColorsProvider(); } }