diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java index 2c4932eaf..028a569e4 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java @@ -59,6 +59,7 @@ import org.eclipse.lsp4j.ProgressParams; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.Registration; import org.eclipse.lsp4j.RegistrationParams; +import org.eclipse.lsp4j.SemanticTokensCapabilities; import org.eclipse.lsp4j.SemanticTokensWithRegistrationOptions; import org.eclipse.lsp4j.ServerCapabilities; import org.eclipse.lsp4j.SetTraceParams; @@ -546,8 +547,12 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, SpringInd c.setWorkspaceSymbolProvider(true); } // TODO: Check if server supports all token types from the legend - SemanticTokensWithRegistrationOptions semanticTokensCapability = getTextDocumentService().getSemanticTokensWithRegistrationOptions(); - c.setSemanticTokensProvider(semanticTokensCapability); + // Eclipse LSP4E client gives `null` semantic tokens client capability yet it works up to dynamic registration + SemanticTokensCapabilities clientSemanticTokensCapability = getTextDocumentService().clientCapabilities.getSemanticTokens(); + if (clientSemanticTokensCapability == null || clientSemanticTokensCapability.getDynamicRegistration() == null || !clientSemanticTokensCapability.getDynamicRegistration().booleanValue()) { + SemanticTokensWithRegistrationOptions semanticTokensCapability = getTextDocumentService().getSemanticTokensWithRegistrationOptions(); + c.setSemanticTokensProvider(semanticTokensCapability); + } WorkspaceFoldersOptions workspaceFoldersOptions = new WorkspaceFoldersOptions(); workspaceFoldersOptions.setSupported(true); diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java index 242aea824..13f145af2 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java @@ -747,7 +747,7 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE return this.codeLensResolveHandler != null; } - final SemanticTokensWithRegistrationOptions getSemanticTokensWithRegistrationOptions() { + public final SemanticTokensWithRegistrationOptions getSemanticTokensWithRegistrationOptions() { return semanticTokensHandler == null ? null : semanticTokensHandler.getCapability(); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaConfig.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaConfig.java index 04ade53c5..4734fc0a3 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaConfig.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaConfig.java @@ -93,6 +93,11 @@ public class BootJavaConfig implements InitializingBean { return isEnabled != null && isEnabled.booleanValue(); } + public boolean isJavaEmbeddedLanguagesSyntaxHighlighting() { + Boolean isEnabled = settings.getBoolean("boot-java", "embedded-syntax-highlighting"); + return isEnabled != null && isEnabled.booleanValue(); + } + public String[] xmlBeansFoldersToScan() { String foldersStr = settings.getString("boot-java", "support-spring-xml-config", "scan-folders"); if (foldersStr != null) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/JdtConfig.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/JdtConfig.java index dbacf022f..f851910c2 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/JdtConfig.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/JdtConfig.java @@ -39,6 +39,7 @@ import org.springframework.ide.vscode.boot.java.reconcilers.PreciseBeanTypeRecon import org.springframework.ide.vscode.boot.java.reconcilers.ServerHttpSecurityLambdaDslReconciler; import org.springframework.ide.vscode.boot.java.reconcilers.UnnecessarySpringExtensionReconciler; import org.springframework.ide.vscode.boot.java.reconcilers.WebSecurityConfigurerAdapterReconciler; +import org.springframework.ide.vscode.boot.java.semantictokens.EmbeddedLanguagesSemanticTokensSupport; import org.springframework.ide.vscode.boot.java.semantictokens.JavaSemanticTokensProvider; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; @@ -128,5 +129,9 @@ public class JdtConfig { @Bean QueryJdtAstReconciler dataQueryReconciler(HqlReconciler hqlReconciler, JpqlReconciler jpqlReconciler, SqlReconciler sqlReconciler) { return new QueryJdtAstReconciler(hqlReconciler, jpqlReconciler, sqlReconciler); } + + @Bean EmbeddedLanguagesSemanticTokensSupport embbededLanguagesSyntaxHighlighting(SimpleLanguageServer server, BootJavaConfig config) { + return new EmbeddedLanguagesSemanticTokensSupport(server, config); + } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/semantictokens/EmbeddedLanguagesSemanticTokensSupport.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/semantictokens/EmbeddedLanguagesSemanticTokensSupport.java new file mode 100644 index 000000000..e624ee8ac --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/semantictokens/EmbeddedLanguagesSemanticTokensSupport.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (c) 2024 Broadcom, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Broadcom, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.semantictokens; + +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.lsp4j.DocumentFilter; +import org.eclipse.lsp4j.Registration; +import org.eclipse.lsp4j.RegistrationParams; +import org.eclipse.lsp4j.SemanticTokensWithRegistrationOptions; +import org.eclipse.lsp4j.Unregistration; +import org.eclipse.lsp4j.UnregistrationParams; +import org.springframework.ide.vscode.boot.app.BootJavaConfig; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; +import org.springframework.ide.vscode.commons.util.text.LanguageId; + +public class EmbeddedLanguagesSemanticTokensSupport { + + private static final String CAPABILITY = "textDocument/semanticTokens"; + + private final SimpleLanguageServer server; + private boolean enabled; + + private String registrationId; + private boolean initialized; + + public EmbeddedLanguagesSemanticTokensSupport(SimpleLanguageServer server, BootJavaConfig config) { + this.server = server; + this.enabled = false; + this.initialized = false; + this.registrationId = null; + config.addListener(v -> setEnabled(config.isJavaEmbeddedLanguagesSyntaxHighlighting())); + } + + public synchronized boolean isEnabled() { + return enabled; + } + + private synchronized void setEnabled(boolean enabled) { + if (!initialized || this.enabled != enabled) { + this.initialized = true; + this.enabled = enabled; + SemanticTokensWithRegistrationOptions semanticTokensCapability = server.getTextDocumentService() + .getSemanticTokensWithRegistrationOptions(); + if (semanticTokensCapability != null) { + List newDocSelectors = semanticTokensCapability.getDocumentSelector().stream() + .filter(s -> !LanguageId.JAVA.getId().equals(s.getLanguage())).toList(); + if (newDocSelectors.size() != semanticTokensCapability.getDocumentSelector().size()) { + if (!enabled) { + semanticTokensCapability.setDocumentSelector(newDocSelectors); + } + CompletableFuture unregisterFuture = CompletableFuture.completedFuture(null); + if (this.registrationId != null) { + UnregistrationParams unregisterParams = new UnregistrationParams( + List.of(new Unregistration(this.registrationId, CAPABILITY))); + unregisterFuture = server.getClient().unregisterCapability(unregisterParams) + .thenAccept(v -> this.registrationId = null); + } + + unregisterFuture.thenCompose(res -> { + final String registrationId = UUID.randomUUID().toString(); + RegistrationParams registerParams = new RegistrationParams( + List.of(new Registration(registrationId, CAPABILITY, semanticTokensCapability))); + return server.getClient().registerCapability(registerParams) + .thenAccept(v -> this.registrationId = registrationId); + }); + } + + } + } + } + +} diff --git a/vscode-extensions/vscode-spring-boot/package.json b/vscode-extensions/vscode-spring-boot/package.json index 21c14c449..67b579ca5 100644 --- a/vscode-extensions/vscode-spring-boot/package.json +++ b/vscode-extensions/vscode-spring-boot/package.json @@ -249,6 +249,11 @@ "title": "Features", "order": 100, "properties": { + "boot-java.embedded-syntax-highlighting": { + "type": "boolean", + "default": true, + "description": "Syntax Hyghlighting for Languages embedded into Java source code" + }, "boot-java.validation.java.reconcilers": { "type": "boolean", "default": true,