From 09ed9c1157087815115cfb9ed84aa38f0e669d14 Mon Sep 17 00:00:00 2001 From: aboyko Date: Wed, 21 Feb 2024 17:29:55 -0500 Subject: [PATCH] Add Starters Inlay Hint in the pom file --- .../plugin.xml | 5 + .../.classpath | 2 +- .../.settings/org.eclipse.jdt.core.prefs | 14 +- .../plugin.xml | 4 + .../boot/wizard/AddBootStartersHandler2.java | 38 ++++ .../plugin.xml | 37 ++++ .../boot/ls/commands/AddStartersHandler.java | 33 ++++ .../CompositeLanguageServerComponents.java | 135 ++++++++++---- .../composable/LanguageServerComponents.java | 8 +- .../languageserver/util/InlayHintHandler.java | 23 +++ .../util/SimpleLanguageServer.java | 7 + .../util/SimpleTextDocumentService.java | 24 +++ .../app/BootLanguageServerInitializer.java | 12 +- .../BootJavaLanguageServerComponents.java | 5 +- .../boot/maven/PomInlayHintHandler.java | 170 ++++++++++++++++++ .../maven/PomLanguageServerComponents.java | 42 +++++ .../generations/GenerationsValidator.java | 4 +- 17 files changed, 513 insertions(+), 50 deletions(-) create mode 100644 eclipse-extensions/org.springframework.ide.eclipse.boot.wizard/src/org/springframework/ide/eclipse/boot/wizard/AddBootStartersHandler2.java create mode 100644 eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/AddStartersHandler.java create mode 100644 headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/InlayHintHandler.java create mode 100644 headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/maven/PomInlayHintHandler.java create mode 100644 headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/maven/PomLanguageServerComponents.java diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.launch/plugin.xml b/eclipse-extensions/org.springframework.ide.eclipse.boot.launch/plugin.xml index ed7ec551c..c09175773 100644 --- a/eclipse-extensions/org.springframework.ide.eclipse.boot.launch/plugin.xml +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.launch/plugin.xml @@ -221,6 +221,11 @@ id="org.springframework.ide.eclipse.boot.commands.editStartersCommand" name="Edit Starters"> + + - + diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.wizard/.settings/org.eclipse.jdt.core.prefs b/eclipse-extensions/org.springframework.ide.eclipse.boot.wizard/.settings/org.eclipse.jdt.core.prefs index 9f6ece88b..8aabe8df8 100644 --- a/eclipse-extensions/org.springframework.ide.eclipse.boot.wizard/.settings/org.eclipse.jdt.core.prefs +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.wizard/.settings/org.eclipse.jdt.core.prefs @@ -1,8 +1,14 @@ eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 -org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=16 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=16 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.release=disabled -org.eclipse.jdt.core.compiler.source=1.8 +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=16 diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.wizard/plugin.xml b/eclipse-extensions/org.springframework.ide.eclipse.boot.wizard/plugin.xml index 55ef78cc6..83406514d 100644 --- a/eclipse-extensions/org.springframework.ide.eclipse.boot.wizard/plugin.xml +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.wizard/plugin.xml @@ -142,6 +142,10 @@ + + diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.wizard/src/org/springframework/ide/eclipse/boot/wizard/AddBootStartersHandler2.java b/eclipse-extensions/org.springframework.ide.eclipse.boot.wizard/src/org/springframework/ide/eclipse/boot/wizard/AddBootStartersHandler2.java new file mode 100644 index 000000000..3eb99e1cd --- /dev/null +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.wizard/src/org/springframework/ide/eclipse/boot/wizard/AddBootStartersHandler2.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * 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.eclipse.boot.wizard; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IFileEditorInput; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.handlers.HandlerUtil; +import org.springframework.ide.eclipse.boot.wizard.starters.AddStartersWizard; + +public class AddBootStartersHandler2 extends AbstractHandler { + + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + try { + IEditorInput input = HandlerUtil.getActiveEditorInput(event); + if (input instanceof IFileEditorInput fileInput) { + AddStartersWizard.openFor(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), fileInput.getFile().getProject()); + return null; + } + } catch (Exception e) { + throw new ExecutionException("", e); + } + throw new ExecutionException("Current active editor is not for pom.xml"); + } + +} diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml b/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml index 9942e4a07..de1ec1509 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml @@ -82,6 +82,10 @@ --> + + @@ -173,6 +177,17 @@ file-names="spring.factories,aot.factories" file-patterns="*.factories"> + + + + + + @@ -292,6 +311,24 @@ id="org.springframework.tooling.boot.ls.modulith.metadata.refresh" name="Refresh Modulith Metadata"> + + + + + + componentsByLanguageId = new HashMap<>(); + private Map> componentsByLanguageId = new HashMap<>(); public void add(LanguageServerComponents components) { for (LanguageId language : components.getInterestingLanguages()) { - //Multiple associations to a single language id not yet supported. - Assert.isLegal(!componentsByLanguageId.containsKey(language)); - componentsByLanguageId.put(language, components); + List list = componentsByLanguageId.get(language); + if (list == null) { + list = new ArrayList<>(1); + componentsByLanguageId.put(language, list); + } + list.add(components); } } @@ -60,30 +70,33 @@ public class CompositeLanguageServerComponents implements LanguageServerComponen } } - private final Map componentsByLanguageId; + private final Map> componentsByLanguageId; private final IReconcileEngine reconcileEngine; private final HoverHandler hoverHandler; private final CodeActionHandler codeActionHandler; + private final CodeLensHandler codeLensHandler; private final DocumentSymbolHandler docSymbolHandler; + private final InlayHintHandler inlayHintHandler; public CompositeLanguageServerComponents(SimpleLanguageServer server, Builder builder) { this.componentsByLanguageId = ImmutableMap.copyOf(builder.componentsByLanguageId); //Create composite Reconcile engine - if (componentsByLanguageId.values().stream().map(LanguageServerComponents::getReconcileEngine).anyMatch(Optional::isPresent)) { + if (componentsByLanguageId.values().stream().flatMap(l -> l.stream()).map(LanguageServerComponents::getReconcileEngine).anyMatch(Optional::isPresent)) { this.reconcileEngine = new IReconcileEngine() { @Override public void reconcile(IDocument document, IProblemCollector problemCollector) { LanguageId language = document.getLanguageId(); - LanguageServerComponents subComponents = componentsByLanguageId.get(language); + List subComponents = componentsByLanguageId.get(language); if (subComponents!=null) { - Optional subEngine = subComponents.getReconcileEngine(); - if (subEngine.isPresent()) { - subEngine.get().reconcile(document, problemCollector); - return; + for (LanguageServerComponents subComponent : subComponents) { + Optional subEngine = subComponent.getReconcileEngine(); + if (subEngine.isPresent()) { + subEngine.get().reconcile(document, problemCollector); + } } } - //No applicable subEngine... but we still have to obey the IReconcileEngine contract! - IReconcileEngine.NULL.reconcile(document, problemCollector); +// //No applicable subEngine... but we still have to obey the IReconcileEngine contract! +// IReconcileEngine.NULL.reconcile(document, problemCollector); } }; } else { @@ -95,11 +108,13 @@ public class CompositeLanguageServerComponents implements LanguageServerComponen public Hover handle(CancelChecker cancelToken, HoverParams params) { TextDocument doc = server.getTextDocumentService().getLatestSnapshot(params.getTextDocument().getUri()); LanguageId language = doc.getLanguageId(); - LanguageServerComponents subComponents = componentsByLanguageId.get(language); + List subComponents = componentsByLanguageId.get(language); if (subComponents != null) { - HoverHandler subEngine = subComponents.getHoverProvider(); - if (subEngine != null) { - return subEngine.handle(cancelToken, params); + for (LanguageServerComponents subComponent : subComponents) { + HoverHandler subEngine = subComponent.getHoverProvider(); + if (subEngine != null) { + return subEngine.handle(cancelToken, params); + } } } //No applicable subEngine... @@ -113,11 +128,51 @@ public class CompositeLanguageServerComponents implements LanguageServerComponen public List> handle(CancelChecker cancelToken, CodeActionCapabilities capabilities, CodeActionContext context, TextDocument doc, IRegion region) { LanguageId language = doc.getLanguageId(); - LanguageServerComponents subComponents = componentsByLanguageId.get(language); + List subComponents = componentsByLanguageId.get(language); if (subComponents != null) { - return subComponents.getCodeActionProvider() - .map(subEngine -> subEngine.handle(cancelToken, capabilities, context, doc, region)) - .orElse(Collections.emptyList()); + return subComponents.stream() + .map(sc -> sc.getCodeActionProvider()) + .filter(provider -> provider.isPresent()) + .flatMap(se -> se.get().handle(cancelToken, capabilities, context, doc, region).stream()) + .collect(Collectors.toList()); + } + // No applicable subEngine... + return Collections.emptyList(); + } + }; + + this.codeLensHandler = new CodeLensHandler() { + + @Override + public List handle(CancelChecker cancelToken, CodeLensParams params) { + TextDocument doc = server.getTextDocumentService().getLatestSnapshot(params.getTextDocument().getUri()); + LanguageId language = doc.getLanguageId(); + List subComponents = componentsByLanguageId.get(language); + if (subComponents != null) { + return subComponents.stream() + .map(sc -> sc.getCodeLensHandler()) + .filter(h -> h.isPresent()) + .flatMap(h -> h.get().handle(cancelToken, params).stream()) + .collect(Collectors.toList()); + } + // No applicable subEngine... + return Collections.emptyList(); + } + }; + + this.inlayHintHandler = new InlayHintHandler() { + + @Override + public List handle(CancelChecker token, InlayHintParams params) { + TextDocument doc = server.getTextDocumentService().getLatestSnapshot(params.getTextDocument().getUri()); + LanguageId language = doc.getLanguageId(); + List subComponents = componentsByLanguageId.get(language); + if (subComponents != null) { + return subComponents.stream() + .map(sc -> sc.getInlayHintHandler()) + .filter(h -> h.isPresent()) + .flatMap(h -> h.get().handle(token, params).stream()) + .collect(Collectors.toList()); } // No applicable subEngine... return Collections.emptyList(); @@ -130,12 +185,13 @@ public class CompositeLanguageServerComponents implements LanguageServerComponen public List handle(DocumentSymbolParams params) { TextDocument doc = server.getTextDocumentService().getLatestSnapshot(params.getTextDocument().getUri()); LanguageId language = doc.getLanguageId(); - LanguageServerComponents subComponents = componentsByLanguageId.get(language); + List subComponents = componentsByLanguageId.get(language); if (subComponents != null) { - DocumentSymbolHandler subHandler = subComponents.getDocumentSymbolProvider().orElse(null); - if (subHandler != null) { - return subHandler.handle(params); - } + return subComponents.stream() + .map(sc -> sc.getDocumentSymbolProvider()) + .filter(ds -> ds.isPresent()) + .flatMap(ds -> ds.get().handle(params).stream()) + .collect(Collectors.toList()); } //No applicable subEngine... return Collections.emptyList(); @@ -163,14 +219,13 @@ public class CompositeLanguageServerComponents implements LanguageServerComponen return Optional.ofNullable(docSymbolHandler); } - @SuppressWarnings("unchecked") public C get(Class subComponentsType) { - for (LanguageServerComponents subcomp : this.componentsByLanguageId.values()) { - if (subComponentsType.isInstance(subcomp)) { - return (C) subcomp; - } - } - return null; + return this.componentsByLanguageId.values().stream() + .flatMap(l -> l.stream()) + .filter(subComponentsType::isInstance) + .map(subComponentsType::cast) + .findFirst() + .orElse(null); } @Override @@ -178,4 +233,14 @@ public class CompositeLanguageServerComponents implements LanguageServerComponen return Optional.of(codeActionHandler); } + @Override + public Optional getCodeLensHandler() { + return Optional.of(codeLensHandler); + } + + @Override + public Optional getInlayHintHandler() { + return Optional.of(inlayHintHandler); + } + } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/composable/LanguageServerComponents.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/composable/LanguageServerComponents.java index 9925d3113..257426106 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/composable/LanguageServerComponents.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/composable/LanguageServerComponents.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018, 2022 Pivotal, Inc. + * Copyright (c) 2018, 2024 Pivotal, 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 @@ -15,15 +15,19 @@ import java.util.Set; import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine; import org.springframework.ide.vscode.commons.languageserver.util.CodeActionHandler; +import org.springframework.ide.vscode.commons.languageserver.util.CodeLensHandler; import org.springframework.ide.vscode.commons.languageserver.util.DocumentSymbolHandler; import org.springframework.ide.vscode.commons.languageserver.util.HoverHandler; +import org.springframework.ide.vscode.commons.languageserver.util.InlayHintHandler; import org.springframework.ide.vscode.commons.util.text.LanguageId; public interface LanguageServerComponents { Set getInterestingLanguages(); default Optional getReconcileEngine() { return Optional.empty(); } - HoverHandler getHoverProvider(); + default HoverHandler getHoverProvider() { return null; } default Optional getCodeActionProvider() { return Optional.empty(); } default Optional getDocumentSymbolProvider() { return Optional.empty(); } + default Optional getCodeLensHandler() { return Optional.empty(); } + default Optional getInlayHintHandler() { return Optional.empty(); } } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/InlayHintHandler.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/InlayHintHandler.java new file mode 100644 index 000000000..6c9c09f06 --- /dev/null +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/InlayHintHandler.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * 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.commons.languageserver.util; + +import java.util.List; + +import org.eclipse.lsp4j.InlayHint; +import org.eclipse.lsp4j.InlayHintParams; +import org.eclipse.lsp4j.jsonrpc.CancelChecker; + +public interface InlayHintHandler { + + List handle(CancelChecker token, InlayHintParams params); + +} 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 4745b7c5d..adec2806c 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 @@ -521,6 +521,9 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, SpringInd codeLensOptions.setResolveProvider(hasCodeLensResolveProvider()); c.setCodeLensProvider(codeLensOptions ); } + if (hasInlayHintHandler()) { + c.setInlayHintProvider(true); + } if (hasExecuteCommandSupport && ( hasQuickFixes() || !commands.isEmpty() @@ -566,6 +569,10 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, SpringInd private boolean hasCodeLensHandler() { return getTextDocumentService().hasCodeLensHandler(); } + + private boolean hasInlayHintHandler() { + return getTextDocumentService().hasInlayHintHandler(); + } private boolean hasCodeLensResolveProvider() { return getTextDocumentService().hasCodeLensResolveProvider(); 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 06d0b4ee8..7c0f377f4 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 @@ -52,6 +52,8 @@ import org.eclipse.lsp4j.DocumentSymbol; import org.eclipse.lsp4j.DocumentSymbolParams; import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.HoverParams; +import org.eclipse.lsp4j.InlayHint; +import org.eclipse.lsp4j.InlayHintParams; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.LocationLink; import org.eclipse.lsp4j.PublishDiagnosticsParams; @@ -119,6 +121,7 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE private CodeLensHandler codeLensHandler; private CodeLensResolveHandler codeLensResolveHandler; private CodeActionHandler codeActionHandler; + private InlayHintHandler inlayHintHandler; final private ApplicationContext appContext; @@ -531,6 +534,18 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE } return CompletableFuture.completedFuture(Collections.emptyList()); } + + @Override + public CompletableFuture> inlayHint(InlayHintParams params) { + InlayHintHandler handler = this.inlayHintHandler; + + if (handler != null) { + return CompletableFutures.computeAsync(messageWorkerThreadPool, cancelToken -> { + return handler.handle(cancelToken, params); + }); + } + return CompletableFuture.completedFuture(Collections.emptyList()); + } @Override public CompletableFuture resolveCodeLens(CodeLens unresolved) { @@ -665,10 +680,19 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE Assert.isNull("A code action handler is already set, multiple handlers not supported yet", codeActionHandler); this.codeActionHandler = h; } + + public synchronized void onInlayHint(InlayHintHandler h) { + Assert.isNull("A inlay hint handler is already set, multiple handlers not supported yet", inlayHintHandler); + this.inlayHintHandler = h; + } public boolean hasCodeLensHandler() { return this.codeLensHandler != null; } + + public boolean hasInlayHintHandler() { + return this.inlayHintHandler != null; + } public synchronized void onCodeLensResolve(CodeLensResolveHandler h) { Assert.isNull("A code lens resolve handler is already set, multiple handlers not supported yet", codeLensResolveHandler); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java index 431c20678..232e27819 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java @@ -29,8 +29,10 @@ import org.springframework.ide.vscode.boot.java.links.JavaElementLocationProvide import org.springframework.ide.vscode.boot.java.links.SourceLinks; import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessLiveDataProvider; import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache; +import org.springframework.ide.vscode.boot.maven.PomLanguageServerComponents; import org.springframework.ide.vscode.boot.metadata.ProjectBasedPropertyIndexProvider; import org.springframework.ide.vscode.boot.properties.BootPropertiesLanguageServerComponents; +import org.springframework.ide.vscode.boot.validation.generations.SpringProjectsProvider; import org.springframework.ide.vscode.boot.xml.SpringXMLLanguageServerComponents; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.completion.CompositeCompletionEngine; @@ -119,7 +121,8 @@ public class BootLanguageServerInitializer implements InitializingBean { new BootPropertiesLanguageServerComponents(server, params, javaElementLocationProvider, parser, yamlStructureProvider, yamlAssistContextProvider, sourceLinks), new BootJavaLanguageServerComponents(appContext), new SpringXMLLanguageServerComponents(server, springIndexer, params, config), - new SpringFactoriesLanguageServerComponents(projectFinder, springIndexer, config) + new SpringFactoriesLanguageServerComponents(projectFinder, springIndexer, config), + new PomLanguageServerComponents(server, projectFinder, params.projectObserver, appContext.getBean(SpringProjectsProvider.class)) ); for (LanguageServerComponents c : componentsList) { @@ -144,7 +147,11 @@ public class BootLanguageServerInitializer implements InitializingBean { components.getCodeActionProvider().ifPresent(documents::onCodeAction); + components.getCodeLensHandler().ifPresent(documents::onCodeLens); + components.getDocumentSymbolProvider().ifPresent(documents::onDocumentSymbol); + + components.getInlayHintHandler().ifPresent(documents::onInlayHint); startListeningToPerformReconcile(); @@ -164,8 +171,7 @@ public class BootLanguageServerInitializer implements InitializingBean { documents.publishDiagnostics(d.getId(), Collections.emptyList()); } }); - - + } private void startListeningToPerformReconcile() { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java index b13a24927..783b6932f 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java @@ -174,7 +174,6 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent sourceLinks); codeLensHandler = createCodeLensEngine(indexer); - documents.onCodeLens(codeLensHandler); highlightsEngine = createDocumentHighlightEngine(indexer); documents.onDocumentHighlight(highlightsEngine); @@ -218,8 +217,8 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent return hoverProvider; } - public CodeLensHandler getCodeLensHandler() { - return codeLensHandler; + public Optional getCodeLensHandler() { + return Optional.of(codeLensHandler); } public DocumentHighlightHandler getDocumentHighlightHandler() { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/maven/PomInlayHintHandler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/maven/PomInlayHintHandler.java new file mode 100644 index 000000000..1fcef0135 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/maven/PomInlayHintHandler.java @@ -0,0 +1,170 @@ +/******************************************************************************* + * 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.maven; + +import java.net.URI; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.dom.DOMElement; +import org.eclipse.lemminx.dom.DOMParser; +import org.eclipse.lsp4j.Command; +import org.eclipse.lsp4j.InlayHint; +import org.eclipse.lsp4j.InlayHintKind; +import org.eclipse.lsp4j.InlayHintLabelPart; +import org.eclipse.lsp4j.InlayHintParams; +import org.eclipse.lsp4j.jsonrpc.CancelChecker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ide.vscode.boot.validation.generations.GenerationsValidator; +import org.springframework.ide.vscode.boot.validation.generations.SpringProjectsProvider; +import org.springframework.ide.vscode.boot.validation.generations.VersionValidationUtils; +import org.springframework.ide.vscode.boot.validation.generations.json.Generation; +import org.springframework.ide.vscode.boot.validation.generations.json.ResolvedSpringProject; +import org.springframework.ide.vscode.commons.Version; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.java.SpringProjectUtil; +import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver; +import org.springframework.ide.vscode.commons.languageserver.util.InlayHintHandler; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; +import org.springframework.ide.vscode.commons.util.text.TextDocument; + +public class PomInlayHintHandler implements InlayHintHandler { + + private static final String POM_XML = "pom.xml"; + + private static final Logger log = LoggerFactory.getLogger(PomInlayHintHandler.class); + + final private SimpleLanguageServer server; + final private JavaProjectFinder projectFinder; + final private SpringProjectsProvider generationsProvider; + + public PomInlayHintHandler(SimpleLanguageServer server, JavaProjectFinder projectFinder, ProjectObserver projectObserver, SpringProjectsProvider generationsProvider) { + this.server = server; + this.projectFinder = projectFinder; + this.generationsProvider = generationsProvider; + + projectObserver.addListener(new ProjectObserver.Listener() { + + private void refreshOpenDocs(IJavaProject project) { + if (project.getProjectBuild() != null && project.getProjectBuild().getBuildFile() != null ) { + URI projectBuildFileUri = project.getProjectBuild().getBuildFile(); + if (projectBuildFileUri.toString().endsWith(POM_XML)) { + server.getTextDocumentService().getAll().stream() + .map(doc -> URI.create(doc.getId().getUri())) + .filter(projectBuildFileUri::equals) + .findFirst().ifPresent(docPath -> { + log.info("Refresh inlays for: " + docPath); + server.getClient().refreshInlayHints(); + }); + } + } + } + + @Override + public void deleted(IJavaProject project) { + refreshOpenDocs(project); + } + + @Override + public void created(IJavaProject project) { + refreshOpenDocs(project); + } + + @Override + public void changed(IJavaProject project) { + refreshOpenDocs(project); + } + }); + + } + + @Override + public List handle(CancelChecker token, InlayHintParams params) { + URI uri = URI.create(params.getTextDocument().getUri()); + if ("file".equals(uri.getScheme()) && POM_XML.equals(Paths.get(uri).getFileName().toString())) { + + log.info("INLAY for " + uri); + + Optional projectOpt = projectFinder.find(params.getTextDocument()); + if (projectOpt.isPresent()) { + + URI buildFileUri = projectOpt.get().getProjectBuild().getBuildFile(); + + if (buildFileUri.equals(uri) && SpringProjectUtil.isBootProject(projectOpt.get())) { + Version currentVersion = SpringProjectUtil.getSpringBootVersion(projectOpt.get()); + if (currentVersion != null) { + try { + ResolvedSpringProject genProject = generationsProvider.getProject(SpringProjectUtil.SPRING_BOOT); + if (genProject != null) { + Generation generation = GenerationsValidator.getGenerationForJavaProject(projectOpt.get(), genProject); + if (generation != null && VersionValidationUtils.isOssValid(generation)) { + TextDocument doc = server.getTextDocumentService().getLatestSnapshot(params.getTextDocument().getUri()); + + if (doc != null) { + String content = doc.get(); + + if (!content.isEmpty()) { + // if doc is not empty, dive into the details and provide more sophisticated content assist proposals + DOMParser parser = DOMParser.getInstance(); + DOMDocument dom = parser.parse(content, "", null); + + DOMElement project = dom.getDocumentElement(); + if (project != null && "project".equals(project.getTagName())) { + for (int j = 0; j < project.getChildNodes().getLength(); j++) { + var child = project.getChildNodes().item(j); + if ("dependencies".equals(child.getNodeName()) && child instanceof DOMElement dependencies) { + try { + Command command = new Command(); + command.setTitle("Add Spring Boot Starters"); + command.setCommand("spring.initializr.addStarters"); + + InlayHintLabelPart label = new InlayHintLabelPart("Add Spring Boot Starters..."); + label.setCommand(command); + + InlayHint hint = new InlayHint(); + hint.setPosition(doc.toPosition(dependencies.getStartTagCloseOffset() + 1)); + hint.setKind(InlayHintKind.Parameter); + hint.setPaddingLeft(true); + hint.setLabel(List.of(label)); + + log.info("Sending inlay for " + uri); + return List.of(hint); + } catch (Exception e) { + log.error("", e); + break; + } + } + } + } + + + } + } + } + } + } catch (Exception e) { + log.error("", e); + } + + } + } + } + + } + return Collections.emptyList(); + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/maven/PomLanguageServerComponents.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/maven/PomLanguageServerComponents.java new file mode 100644 index 000000000..2c317a6fe --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/maven/PomLanguageServerComponents.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * 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.maven; + +import java.util.Optional; +import java.util.Set; + +import org.springframework.ide.vscode.boot.validation.generations.SpringProjectsProvider; +import org.springframework.ide.vscode.commons.languageserver.composable.LanguageServerComponents; +import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver; +import org.springframework.ide.vscode.commons.languageserver.util.InlayHintHandler; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; +import org.springframework.ide.vscode.commons.util.text.LanguageId; + +public class PomLanguageServerComponents implements LanguageServerComponents{ + + private PomInlayHintHandler inlayHintHandler; + + public PomLanguageServerComponents(SimpleLanguageServer server, JavaProjectFinder projectFinder, ProjectObserver projectObserver, SpringProjectsProvider generationsProvider) { + this.inlayHintHandler = new PomInlayHintHandler(server, projectFinder, projectObserver, generationsProvider); + } + + @Override + public Set getInterestingLanguages() { + return Set.of(LanguageId.XML); + } + + @Override + public Optional getInlayHintHandler() { + return Optional.of(inlayHintHandler); + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/GenerationsValidator.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/GenerationsValidator.java index e2b291940..dcafe77ab 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/GenerationsValidator.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/GenerationsValidator.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 VMware, Inc. + * Copyright (c) 2023, 2024 VMware, 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 @@ -33,7 +33,7 @@ public class GenerationsValidator extends AbstractDiagnosticValidator { this.provider = provider; } - private static Generation getGenerationForJavaProject(IJavaProject javaProject, ResolvedSpringProject springProject) + public static Generation getGenerationForJavaProject(IJavaProject javaProject, ResolvedSpringProject springProject) throws Exception { List genList = springProject.getGenerations(); Version javaProjectVersion = SpringProjectUtil.getDependencyVersion(javaProject, springProject.getSlug());