diff --git a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/SimpleSymbolElement.java b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/SimpleSymbolElement.java new file mode 100644 index 000000000..b843bc6c1 --- /dev/null +++ b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/SimpleSymbolElement.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2025 Broadcom + * 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 - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.protocol.spring; + +import org.eclipse.lsp4j.DocumentSymbol; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.WorkspaceSymbol; + +public class SimpleSymbolElement extends AbstractSpringIndexElement implements SymbolElement { + + private final WorkspaceSymbol symbol; + + public SimpleSymbolElement(WorkspaceSymbol symbol) { + this.symbol = symbol; + } + + @Override + public DocumentSymbol getDocumentSymbol() { + DocumentSymbol documentSymbol = new DocumentSymbol(); + documentSymbol.setName(symbol.getName()); + documentSymbol.setKind(symbol.getKind()); + documentSymbol.setTags(symbol.getTags()); + + Range range = symbol.getLocation().isLeft() ? symbol.getLocation().getLeft().getRange() : new Range(); + documentSymbol.setRange(range); + documentSymbol.setSelectionRange(range); + + return documentSymbol; + } + +} diff --git a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/SymbolElement.java b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/SymbolElement.java new file mode 100644 index 000000000..57b11e9f7 --- /dev/null +++ b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/SymbolElement.java @@ -0,0 +1,19 @@ +/******************************************************************************* + * Copyright (c) 2025 Broadcom + * 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 - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.protocol.spring; + +import org.eclipse.lsp4j.DocumentSymbol; + +public interface SymbolElement { + + public DocumentSymbol getDocumentSymbol(); + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/SpringIndexToSymbolsConverter.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/SpringIndexToSymbolsConverter.java new file mode 100644 index 000000000..18ca5aa24 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/SpringIndexToSymbolsConverter.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2025 Broadcom + * 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 - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.index; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.lsp4j.DocumentSymbol; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.SymbolKind; +import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement; +import org.springframework.ide.vscode.commons.protocol.spring.SymbolElement; + +public class SpringIndexToSymbolsConverter { + + public static List createDocumentSymbols(List indexElements) { + List result = new ArrayList<>(); + + for (SpringIndexElement indexElement : indexElements) { + result.add(createSymbol(indexElement)); + } + + return result; + } + + private static DocumentSymbol createSymbol(SpringIndexElement indexElement) { + + DocumentSymbol symbol = null; + if (indexElement instanceof SymbolElement symbolElement) { + symbol = symbolElement.getDocumentSymbol(); + } + else { + symbol = new DocumentSymbol(indexElement.toString(), SymbolKind.String, + new Range(new Position(), new Position()), + new Range(new Position(), new Position())); + } + + List children = indexElement.getChildren(); + if (children != null && children.size() > 0) { + List childSymbols = new ArrayList<>(); + + for (SpringIndexElement child : children) { + DocumentSymbol childSymbol = createSymbol(child); + if (childSymbol != null) { + childSymbols.add(childSymbol); + } + } + + if (childSymbols.size() > 0) { + symbol.setChildren(childSymbols); + } + } + + return symbol; + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/RestrictedDefaultSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/RestrictedDefaultSymbolProvider.java index dc28c28fb..7b2936279 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/RestrictedDefaultSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/RestrictedDefaultSymbolProvider.java @@ -22,7 +22,9 @@ import org.eclipse.lsp4j.WorkspaceSymbol; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.boot.java.Annotations; +import org.springframework.ide.vscode.boot.java.beans.CachedBean; import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider; +import org.springframework.ide.vscode.commons.protocol.spring.SimpleSymbolElement; import org.springframework.ide.vscode.commons.util.text.TextDocument; /** @@ -41,6 +43,7 @@ public class RestrictedDefaultSymbolProvider extends AbstractSymbolProvider { try { WorkspaceSymbol symbol = DefaultSymbolProvider.provideDefaultSymbol(node, doc); context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), symbol)); + context.getBeans().add(new CachedBean(context.getDocURI(), new SimpleSymbolElement(symbol))); } catch (Exception e) { log.warn(e.getMessage()); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringFactoriesIndexer.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringFactoriesIndexer.java index 2f08785f7..880d70ee5 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringFactoriesIndexer.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringFactoriesIndexer.java @@ -28,10 +28,12 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.commons.codec.digest.DigestUtils; +import org.eclipse.lsp4j.DocumentSymbol; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.SymbolKind; import org.eclipse.lsp4j.WorkspaceSymbol; +import org.eclipse.lsp4j.WorkspaceSymbolLocation; import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -100,6 +102,28 @@ public class SpringFactoriesIndexer implements SpringIndexer { return computeSymbols(docURI, content); } + @Override + public List computeDocumentSymbols(IJavaProject project, String docURI, String content) throws Exception { + return computeSymbols(docURI, content).stream() + .map(workspaceSymbol -> convertToDocumentSymbol(workspaceSymbol)) + .toList(); + } + + private DocumentSymbol convertToDocumentSymbol(WorkspaceSymbol workspaceSymbol) { + Either location = workspaceSymbol.getLocation(); + + Range range = null; + if (location.isLeft()) { + Location l = location.getLeft(); + range = l.getRange(); + } + else if (location.isRight()) { + range = new Range(); + } + + return new DocumentSymbol(workspaceSymbol.getName(), workspaceSymbol.getKind(), range, range); + } + private List computeSymbols(String docURI, String content) { ImmutableList.Builder symbols = ImmutableList.builder(); PropertiesAst ast = new AntlrParser().parse(content).ast; @@ -287,5 +311,5 @@ public class SpringFactoriesIndexer implements SpringIndexer { IndexCacheKey key = getCacheKey(project); cache.removeFiles(key, files, CachedSymbol.class); } - + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java index a3ef3bfe9..8e6ce19f3 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java @@ -12,6 +12,7 @@ package org.springframework.ide.vscode.boot.java.utils; import java.util.List; +import org.eclipse.lsp4j.DocumentSymbol; import org.eclipse.lsp4j.WorkspaceSymbol; import org.springframework.ide.vscode.commons.java.IJavaProject; @@ -24,6 +25,7 @@ public interface SpringIndexer { boolean isInterestedIn(String resource); // note that this might be a document URI or a standard file path on the system List computeSymbols(IJavaProject project, String docURI, String content) throws Exception; + List computeDocumentSymbols(IJavaProject project, String docURI, String string) throws Exception; void initializeProject(IJavaProject project, boolean clean) throws Exception; void removeProject(IJavaProject project) throws Exception; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java index f0b5ca9ba..4e10fce01 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java @@ -49,9 +49,11 @@ import org.eclipse.jdt.core.dom.NormalAnnotation; import org.eclipse.jdt.core.dom.SingleMemberAnnotation; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.Diagnostic; +import org.eclipse.lsp4j.DocumentSymbol; import org.eclipse.lsp4j.WorkspaceSymbol; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.ide.vscode.boot.index.SpringIndexToSymbolsConverter; import org.springframework.ide.vscode.boot.index.cache.IndexCache; import org.springframework.ide.vscode.boot.index.cache.IndexCacheKey; import org.springframework.ide.vscode.boot.java.Annotations; @@ -71,6 +73,7 @@ import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFin import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem; import org.springframework.ide.vscode.commons.protocol.java.Classpath; +import org.springframework.ide.vscode.commons.protocol.spring.SimpleSymbolElement; import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement; import org.springframework.ide.vscode.commons.util.UriUtil; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -366,6 +369,47 @@ public class SpringIndexerJava implements SpringIndexer { return Collections.emptyList(); } + + @Override + public List computeDocumentSymbols(IJavaProject project, String docURI, String content) throws Exception { + if (content != null) { + URI uri = URI.create(docURI); + + return cuCache.withCompilationUnit(project, uri, cu -> { + List generatedSymbols = new ArrayList(); + List generatedBeans = new ArrayList(); + + IProblemCollector voidProblemCollector = new IProblemCollector() { + @Override + public void endCollecting() { + } + + @Override + public void beginCollecting() { + } + + @Override + public void accept(ReconcileProblem problem) { + } + }; + + AtomicReference docRef = new AtomicReference<>(); + String file = UriUtil.toFileString(docURI); + SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, file, + 0, docRef, content, generatedSymbols, generatedBeans, voidProblemCollector, SCAN_PASS.ONE, new ArrayList<>(), true); + + scanAST(context, false); + + List indexElements = generatedBeans.stream() + .map(cachedBean -> cachedBean.getBean()) + .toList(); + + return SpringIndexToSymbolsConverter.createDocumentSymbols(indexElements); + }); + } + + return Collections.emptyList(); + } private Set scanFilesInternally(IJavaProject project, DocumentDescriptor[] docs) throws Exception { final boolean ignoreMethodBodies = false; @@ -770,6 +814,7 @@ public class SpringIndexerJava implements SpringIndexer { WorkspaceSymbol symbol = provideDefaultSymbol(node, context); if (symbol != null) { context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), symbol)); + context.getBeans().add(new CachedBean(context.getDocURI(), new SimpleSymbolElement(symbol))); } } @@ -965,5 +1010,5 @@ public class SpringIndexerJava implements SpringIndexer { fileScanListener.fileScanned(file); } } - + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerXML.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerXML.java index ca7d350db..2a02f9969 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerXML.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerXML.java @@ -31,9 +31,11 @@ import org.apache.commons.io.FileUtils; import org.eclipse.lemminx.dom.DOMDocument; import org.eclipse.lemminx.dom.DOMNode; import org.eclipse.lemminx.dom.DOMParser; +import org.eclipse.lsp4j.DocumentSymbol; import org.eclipse.lsp4j.WorkspaceSymbol; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.ide.vscode.boot.index.SpringIndexToSymbolsConverter; import org.springframework.ide.vscode.boot.index.cache.IndexCache; import org.springframework.ide.vscode.boot.index.cache.IndexCacheKey; import org.springframework.ide.vscode.boot.java.beans.CachedBean; @@ -352,8 +354,7 @@ public class SpringIndexerXML implements SpringIndexer { } @Override - public List computeSymbols(IJavaProject project, String docURI, String content) - throws Exception { + public List computeSymbols(IJavaProject project, String docURI, String content) throws Exception { if (content != null) { List generatedSymbols = new ArrayList<>(); List generatedBeans = new ArrayList<>(); @@ -361,7 +362,24 @@ public class SpringIndexerXML implements SpringIndexer { scanFile(project, content, docURI, 0, generatedSymbols, generatedBeans); return generatedSymbols.stream().map(s -> s.getEnhancedSymbol()).collect(Collectors.toList()); } + + return Collections.emptyList(); + } + + @Override + public List computeDocumentSymbols(IJavaProject project, String docURI, String content) throws Exception { + if (content != null) { + List generatedSymbols = new ArrayList<>(); + List generatedBeans = new ArrayList<>(); + + scanFile(project, content, docURI, 0, generatedSymbols, generatedBeans); + + return SpringIndexToSymbolsConverter.createDocumentSymbols(generatedBeans.stream().map(cachedBean -> cachedBean.getBean()).toList()); + } + return Collections.emptyList(); } + + }