From e7e2015ac41905f890ea86987a056952daa58ce5 Mon Sep 17 00:00:00 2001 From: Martin Lippert Date: Tue, 8 Apr 2025 09:58:18 +0200 Subject: [PATCH] option added to generate workspace symbols from spring index on demand --- .../tooling/boot/ls/Constants.java | 2 + .../DelegatingStreamConnectionProvider.java | 1 + .../ls/prefs/BootJavaPreferencesPage.java | 5 +- .../boot/ls/prefs/PrefsInitializer.java | 7 +-- .../protocol/spring/SymbolElement.java | 2 +- .../ide/vscode/boot/app/BootJavaConfig.java | 5 ++ .../vscode/boot/app/SpringSymbolIndex.java | 60 +++++++++++++++++-- .../index/SpringIndexToSymbolsConverter.java | 40 +++++++++++++ .../boot/index/SpringMetamodelIndex.java | 5 ++ .../vscode-spring-boot/package.json | 7 ++- 10 files changed, 119 insertions(+), 15 deletions(-) diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/Constants.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/Constants.java index 7b6817418..aa5fedf8b 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/Constants.java +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/Constants.java @@ -54,4 +54,6 @@ public class Constants { public static final String PREF_BEANS_STRUCTURE_TREE = "boot-java.java.beans-structure-tree"; + public static final String PREF_SYMBOLS_FROM_NEW_INDEX = "boot-java.java.symbols-from-new-index"; + } diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/DelegatingStreamConnectionProvider.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/DelegatingStreamConnectionProvider.java index 26e28ff81..bc925f4b0 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/DelegatingStreamConnectionProvider.java +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/DelegatingStreamConnectionProvider.java @@ -201,6 +201,7 @@ public class DelegatingStreamConnectionProvider implements StreamConnectionProvi javaCompletionSettings.put("inject-bean", preferenceStore.getBoolean(Constants.PREF_COMPLETION_JAVA_INJECT_BEAN)); javaSettings.put("beans-structure-tree", preferenceStore.getBoolean(Constants.PREF_BEANS_STRUCTURE_TREE)); + javaSettings.put("symbols-from-new-index", preferenceStore.getBoolean(Constants.PREF_SYMBOLS_FROM_NEW_INDEX)); javaSettings.put("completions", javaCompletionSettings); javaSettings.put("reconcilers", preferenceStore.getBoolean(Constants.PREF_JAVA_RECONCILE)); diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/BootJavaPreferencesPage.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/BootJavaPreferencesPage.java index e04f64a59..5d7ed55a1 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/BootJavaPreferencesPage.java +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/BootJavaPreferencesPage.java @@ -57,7 +57,10 @@ public class BootJavaPreferencesPage extends FieldEditorPreferencePage implement addField(new BooleanFieldEditor(Constants.PREF_COMPLETION_JAVA_INJECT_BEAN, "Inject Bean completion proposals in Java editor", fieldEditorParent)); // Experimental Beans tree - addField(new BooleanFieldEditor(Constants.PREF_BEANS_STRUCTURE_TREE, "Beans structure tree in the outline view", fieldEditorParent)); + addField(new BooleanFieldEditor(Constants.PREF_BEANS_STRUCTURE_TREE, "Beans structure tree in the outline view (experimental)", fieldEditorParent)); + + // Experimental symbols from new index + addField(new BooleanFieldEditor(Constants.PREF_SYMBOLS_FROM_NEW_INDEX, "Generate workspace symbols from index (experimental)", fieldEditorParent)); Composite c = new Composite(fieldEditorParent, SWT.NONE); GridDataFactory.fillDefaults().grab(true, false).applyTo(c); diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/PrefsInitializer.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/PrefsInitializer.java index 8bcfeee09..44b0c5ee5 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/PrefsInitializer.java +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/PrefsInitializer.java @@ -59,18 +59,13 @@ public class PrefsInitializer extends AbstractPreferenceInitializer { })); preferenceStore.setDefault(Constants.PREF_MODULITH, true); - preferenceStore.setDefault(Constants.PREF_LIVE_INFORMATION_ALL_JVM_PROCESSES, false); - preferenceStore.setDefault(Constants.PREF_JPQL, true); - preferenceStore.setDefault(Constants.PREF_PROPS_COMPLETIONS_ELIDE_PREFIX, false); - preferenceStore.setDefault(Constants.PREF_CRON_INLAY_HINTS, true); - preferenceStore.setDefault(Constants.PREF_COMPLETION_JAVA_INJECT_BEAN, true); - preferenceStore.setDefault(Constants.PREF_BEANS_STRUCTURE_TREE, false); + preferenceStore.setDefault(Constants.PREF_SYMBOLS_FROM_NEW_INDEX, false); } } 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 index 57b11e9f7..bb15a70b8 100644 --- 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 @@ -12,7 +12,7 @@ package org.springframework.ide.vscode.commons.protocol.spring; import org.eclipse.lsp4j.DocumentSymbol; -public interface SymbolElement { +public interface SymbolElement extends SpringIndexElement { public DocumentSymbol getDocumentSymbol(); 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 f87e96325..8ae6df611 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 @@ -219,6 +219,11 @@ public class BootJavaConfig implements InitializingBean { return Boolean.TRUE.equals(b); } + public boolean isSymbolsFromNewIndexEnabled() { + Boolean b = settings.getBoolean("boot-java", "java", "symbols-from-new-index"); + return Boolean.TRUE.equals(b); + } + public Settings getRawSettings() { return settings; } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndex.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndex.java index 4960b3d79..c9283583e 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndex.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndex.java @@ -40,6 +40,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -86,6 +87,7 @@ import org.springframework.ide.vscode.commons.protocol.spring.Bean; import org.springframework.ide.vscode.commons.protocol.spring.BeansParams; import org.springframework.ide.vscode.commons.protocol.spring.DocumentElement; import org.springframework.ide.vscode.commons.protocol.spring.MatchingBeansParams; +import org.springframework.ide.vscode.commons.protocol.spring.ProjectElement; import org.springframework.ide.vscode.commons.protocol.spring.SpringIndex; import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement; import org.springframework.ide.vscode.commons.util.Futures; @@ -103,7 +105,6 @@ import com.google.common.collect.ImmutableList; public class SpringSymbolIndex implements InitializingBean, SpringIndex { private static final String QUERY_PARAM_LOCATION_PREFIX = "locationPrefix:"; - private static final String OUTLINE_SYMBOLS_FROM_INDEX_PROPERTY = "outlineSymbolsFromIndex"; @Autowired SimpleLanguageServer server; @Autowired BootJavaConfig config; @@ -681,6 +682,23 @@ public class SpringSymbolIndex implements InitializingBean, SpringIndex { } public List getAllSymbols(String query) { + long start = System.currentTimeMillis(); + + try { + if (config.isSymbolsFromNewIndexEnabled()) { + return getAllSymbolsFromMetamodelIndex(query); + } + else { + return getAllSymbolsFromSymbolsIndex(query); + } + } + finally { + long end = System.currentTimeMillis(); + log.info("workspace symbols computation took " + (end - start) + "ms"); + } + } + + private List getAllSymbolsFromSymbolsIndex(String query) { if (query != null && query.length() > 0) { synchronized(this.symbols) { return searchMatchingSymbols(this.symbols, query); @@ -692,6 +710,37 @@ public class SpringSymbolIndex implements InitializingBean, SpringIndex { } } + private List getAllSymbolsFromMetamodelIndex(String query) { + Collection projects = springIndex.getProjects(); + + String locationPrefix = ""; + if (query.startsWith(QUERY_PARAM_LOCATION_PREFIX)) { + + int separatorIndex = query.indexOf("?"); + if (separatorIndex > 0) { + locationPrefix = query.substring(QUERY_PARAM_LOCATION_PREFIX.length(), separatorIndex); + query = query.substring(separatorIndex + 1); + } + else { + locationPrefix = query.substring(QUERY_PARAM_LOCATION_PREFIX.length()); + query = query.substring(QUERY_PARAM_LOCATION_PREFIX.length() + locationPrefix.length()); + } + } + + if (query.startsWith("*")) { + query = query.substring(1); + } + + final String finalQuery = query; + final String finalLocationPrefix = locationPrefix; + + Predicate locationPredicate = (document) -> document.getDocURI().startsWith(finalLocationPrefix); + Predicate symbolPredicate = query != null && query.length() > 0 ? (symbol) -> StringUtil.containsCharactersCaseInsensitive(symbol.getName(), finalQuery) : (symbol) -> true; + + List workspaceSymbols = SpringIndexToSymbolsConverter.createWorkspaceSymbols(projects, locationPredicate, symbolPredicate); + return workspaceSymbols; + } + synchronized private CompletableFuture projectInitializedFuture(IJavaProject project) { if (project == null) { return CompletableFuture.completedFuture(null); @@ -702,7 +751,7 @@ public class SpringSymbolIndex implements InitializingBean, SpringIndex { } public List getSymbols(String docURI) { - if (System.getProperty(OUTLINE_SYMBOLS_FROM_INDEX_PROPERTY) != null) { + if (config.isBeanStructureTreeEnabled()) { return getWorkspaceSymbolsFromMetamodelIndex(docURI); } else { @@ -711,8 +760,7 @@ public class SpringSymbolIndex implements InitializingBean, SpringIndex { } public List getDocumentSymbols(String docURI) { - if (System.getProperty(OUTLINE_SYMBOLS_FROM_INDEX_PROPERTY) != null - || config.isBeanStructureTreeEnabled()) { + if (config.isBeanStructureTreeEnabled()) { return getDocumentSymbolsFromMetamodelIndex(docURI); } else { @@ -1095,9 +1143,9 @@ public class SpringSymbolIndex implements InitializingBean, SpringIndex { List oldSymbols = symbolsByDoc.remove(docURI); if (oldSymbols != null) { - List copy = null; + Set copy = null; synchronized(oldSymbols) { - copy = new ArrayList<>(oldSymbols); + copy = new HashSet<>(oldSymbols); // use HashSet here in order to speed up removal of symbols from global list } synchronized(this.symbols) { 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 index 4e6b81167..8942b49e1 100644 --- 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 @@ -11,9 +11,16 @@ package org.springframework.ide.vscode.boot.index; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.function.Predicate; import org.eclipse.lsp4j.DocumentSymbol; +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.WorkspaceSymbol; +import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.springframework.ide.vscode.commons.protocol.spring.DocumentElement; +import org.springframework.ide.vscode.commons.protocol.spring.ProjectElement; import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement; import org.springframework.ide.vscode.commons.protocol.spring.SymbolElement; @@ -28,7 +35,19 @@ public class SpringIndexToSymbolsConverter { return result; } + + public static List createWorkspaceSymbols(Collection projects, + Predicate documentPredicate, Predicate symbolPredicate) { + return projects.stream() + .flatMap(project -> project.getChildren().stream()) + .filter(node -> node instanceof DocumentElement) + .map(node -> (DocumentElement) node) + .filter(documentPredicate) + .flatMap(document -> createWorkspaceSymbols(document, symbolPredicate).stream()) + .toList(); + } + private static List createSymbol(SpringIndexElement indexElement) { List subTreeSymbols = new ArrayList<>(); @@ -58,5 +77,26 @@ public class SpringIndexToSymbolsConverter { return subTreeSymbols; } } + + private static List createWorkspaceSymbols(DocumentElement document, Predicate symbolPredicate) { + return SpringMetamodelIndex.getNodesOfType(SymbolElement.class, List.of(document)).stream() + .map(symbolElement -> symbolElement.getDocumentSymbol()) + .filter(symbolPredicate) + .map(documentSymbol -> createWorkspaceSymbol(documentSymbol, document.getDocURI())) + .toList(); + } + + private static WorkspaceSymbol createWorkspaceSymbol(DocumentSymbol documentSymbol, String docURI) { + WorkspaceSymbol workspaceSymbol = new WorkspaceSymbol(); + + workspaceSymbol.setName(documentSymbol.getName()); + workspaceSymbol.setKind(documentSymbol.getKind()); + workspaceSymbol.setTags(documentSymbol.getTags()); + + Location location = new Location(docURI, documentSymbol.getRange()); + workspaceSymbol.setLocation(Either.forLeft(location)); + + return workspaceSymbol; + } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/SpringMetamodelIndex.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/SpringMetamodelIndex.java index 383b24648..1fb90d1af 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/SpringMetamodelIndex.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/SpringMetamodelIndex.java @@ -13,6 +13,7 @@ package org.springframework.ide.vscode.boot.index; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -58,6 +59,10 @@ public class SpringMetamodelIndex { public void removeProject(String projectName) { projectRootElements.remove(projectName); } + + public Collection getProjects() { + return Collections.unmodifiableCollection(this.projectRootElements.values()); + } public DocumentElement getDocument(String docURI) { List rootNodes = new ArrayList(this.projectRootElements.values()); diff --git a/vscode-extensions/vscode-spring-boot/package.json b/vscode-extensions/vscode-spring-boot/package.json index 59d90e680..cf331fb63 100644 --- a/vscode-extensions/vscode-spring-boot/package.json +++ b/vscode-extensions/vscode-spring-boot/package.json @@ -366,7 +366,12 @@ "boot-java.java.beans-structure-tree": { "type": "boolean", "default": false, - "description": "Beans structure tree in the outline view" + "description": "Beans structure tree in the outline view (experimental)" + }, + "boot-java.java.symbols-from-new-index": { + "type": "boolean", + "default": false, + "description": "Generate workspace symbols from index (experimental)" }, "boot-java.remote-apps": { "type": "array",