Skip to content

Commit 6713347

Browse files
committed
Structure view groupings UI
1 parent 35239d0 commit 6713347

File tree

10 files changed

+153
-80
lines changed

10 files changed

+153
-80
lines changed

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/commands/JMoleculesStructureView.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
*******************************************************************************/
1111
package org.springframework.ide.vscode.boot.java.commands;
1212

13+
import java.util.Collection;
1314
import java.util.List;
1415
import java.util.function.BiConsumer;
1516

@@ -34,7 +35,7 @@ public JMoleculesStructureView(AbstractStereotypeCatalog catalog, CachedSpringMe
3435
this.springIndex = springIndex;
3536
}
3637

37-
public Node createTree(IJavaProject project, IndexBasedStereotypeFactory factory, List<String> selectedGroups) {
38+
public Node createTree(IJavaProject project, IndexBasedStereotypeFactory factory, Collection<String> selectedGroups) {
3839

3940
StereotypePackageElement mainApplicationPackage = StructureViewUtil.identifyMainApplicationPackage(project, springIndex);
4041

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/commands/ModulithStructureView.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
*******************************************************************************/
1111
package org.springframework.ide.vscode.boot.java.commands;
1212

13+
import java.util.Collection;
1314
import java.util.List;
1415
import java.util.function.BiConsumer;
1516

@@ -35,7 +36,7 @@ public ModulithStructureView(AbstractStereotypeCatalog catalog, CachedSpringMeta
3536
this.modulithService = modulithService;
3637
}
3738

38-
public Node createTree(IJavaProject project, IndexBasedStereotypeFactory factory, List<String> selectedGroups) {
39+
public Node createTree(IJavaProject project, IndexBasedStereotypeFactory factory, Collection<String> selectedGroups) {
3940

4041
var adapter = new ModulithStereotypeFactoryAdapter(factory);
4142

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/commands/SpringIndexCommands.java

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@
1010
*******************************************************************************/
1111
package org.springframework.ide.vscode.boot.java.commands;
1212

13+
import java.util.Collection;
1314
import java.util.Comparator;
1415
import java.util.List;
16+
import java.util.Map;
1517
import java.util.Objects;
18+
import java.util.Set;
1619
import java.util.stream.Collectors;
1720

1821
import org.eclipse.lsp4j.ExecuteCommandParams;
@@ -27,10 +30,11 @@
2730
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
2831
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
2932

30-
import com.google.gson.JsonArray;
33+
import com.google.gson.Gson;
3134
import com.google.gson.JsonElement;
3235
import com.google.gson.JsonObject;
3336
import com.google.gson.JsonPrimitive;
37+
import com.google.gson.reflect.TypeToken;
3438

3539
public class SpringIndexCommands {
3640

@@ -54,15 +58,27 @@ public SpringIndexCommands(SimpleLanguageServer server, SpringMetamodelIndex spr
5458
CachedSpringMetamodelIndex cachedIndex = new CachedSpringMetamodelIndex(springIndex);
5559
return projectFinder.all().stream()
5660
.sorted(Comparator.comparing(IJavaProject::getElementName))
57-
.map(project -> nodeFrom(project, cachedIndex, args.updateMetadata, args.selectedGroups))
61+
.map(project -> nodeFrom(project, cachedIndex, args.updateMetadata,
62+
args.selectedGroups == null ? null : args.selectedGroups.get(project.getElementName())))
5863
.filter(Objects::nonNull)
5964
.collect(Collectors.toList());
6065
}));
6166

6267
server.onCommand(SPRING_STRUCTURE_GROUPS_CMD, params -> server.getAsync().invoke(() -> {
63-
return projectFinder.all().stream()
64-
.map(project -> getGroups(project))
65-
.toList();
68+
if (params.getArguments().size() == 1) {
69+
Object o = params.getArguments().get(0);
70+
String name = null;
71+
if (o instanceof JsonElement) {
72+
name = ((JsonElement) o).getAsString();
73+
} else if (o instanceof String) {
74+
name = (String) o;
75+
}
76+
if (name != null) {
77+
final String projectName = name;
78+
return projectFinder.all().stream().filter(p -> projectName.equals(p.getElementName())).findFirst().map(this::getGroups).orElseThrow();
79+
}
80+
}
81+
return projectFinder.all().stream().map(this::getGroups);
6682
}));
6783
}
6884

@@ -76,7 +92,7 @@ private Groups getGroups(IJavaProject project) {
7692
return new Groups(project.getElementName(), groups);
7793
}
7894

79-
private Node nodeFrom(IJavaProject project, CachedSpringMetamodelIndex springIndex, boolean updateMetadata, List<String> selectedGroups) {
95+
private Node nodeFrom(IJavaProject project, CachedSpringMetamodelIndex springIndex, boolean updateMetadata, Collection<String> selectedGroups) {
8096
log.info("create structural view tree information for project: " + project.getElementName());
8197

8298
if (updateMetadata) {
@@ -103,11 +119,11 @@ private Node nodeFrom(IJavaProject project, CachedSpringMetamodelIndex springInd
103119
}
104120
}
105121

106-
private static record StructureCommandArgs(boolean updateMetadata, List<String> selectedGroups) {
122+
private static record StructureCommandArgs(boolean updateMetadata, Map<String, Set<String>> selectedGroups) {
107123

108124
public static StructureCommandArgs parseFrom(ExecuteCommandParams params) {
109125
boolean updateMetadata = false;
110-
List<String> selectedGroups = null;
126+
Map<String, Set<String>> selectedGroups = null;
111127

112128
List<Object> arguments = params.getArguments();
113129
if (arguments != null && arguments.size() == 1) {
@@ -119,11 +135,8 @@ public static StructureCommandArgs parseFrom(ExecuteCommandParams params) {
119135
updateMetadata = jsonElement != null && jsonElement instanceof JsonPrimitive ? jsonElement.getAsBoolean() : false;
120136

121137
JsonElement groupsElement = paramObject.get("groups");
122-
if (groupsElement instanceof JsonArray && ((JsonArray) groupsElement).size() > 0) {
123-
JsonArray groupsArray = (JsonArray) groupsElement;
124-
selectedGroups = groupsArray.asList().stream()
125-
.map(groupEntry -> groupEntry.getAsString())
126-
.toList();
138+
if (groupsElement != null) {
139+
selectedGroups = new Gson().fromJson(groupsElement, new TypeToken<Map<String, Set<String>>>() {});
127140
}
128141
}
129142
}

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/commands/StructureViewUtil.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
package org.springframework.ide.vscode.boot.java.commands;
1212

1313
import java.util.ArrayList;
14+
import java.util.Collection;
1415
import java.util.List;
1516
import java.util.Optional;
1617
import java.util.function.Function;
@@ -36,7 +37,7 @@
3637

3738
public class StructureViewUtil {
3839

39-
public static List<String[]> identifyGroupers(AbstractStereotypeCatalog catalog, List<String> selectedGroups) {
40+
public static List<String[]> identifyGroupers(AbstractStereotypeCatalog catalog, Collection<String> selectedGroups) {
4041

4142
// List<String[]> allGroupsWithSpecificOrder = Arrays.asList(
4243
// new String[] {"architecture"},

vscode-extensions/vscode-spring-boot/lib/Main.ts

Lines changed: 4 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ import * as springBootAgent from './copilot/springBootAgent';
2828
import { applyLspEdit } from "./copilot/guideApply";
2929
import { isLlmApiReady } from "./copilot/util";
3030
import CopilotRequest, { logger } from "./copilot/copilotRequest";
31-
import { ExplorerTreeProvider } from "./explorer/explorer-tree-provider";
3231
import { StructureManager } from "./explorer/structure-tree-manager";
32+
import { ExplorerTreeProvider } from "./explorer/explorer-tree-provider";
3333

3434
const PROPERTIES_LANGUAGE_ID = "spring-boot-properties";
3535
const YAML_LANGUAGE_ID = "spring-boot-properties-yaml";
@@ -159,58 +159,9 @@ export function activate(context: ExtensionContext): Thenable<ExtensionAPI> {
159159

160160
return commons.activate(options, context).then(client => {
161161

162-
// Spring structure tree in the Explorer view
163-
/*
164-
Requires the following code to be added in the `package.json` to
165-
1. Declare view:
166-
"views": {
167-
"explorer": [
168-
{
169-
"id": "explorer.spring",
170-
"name": "Spring",
171-
"when": "java:serverMode || workbenchState==empty",
172-
"contextualTitle": "Spring",
173-
"icon": "resources/logo.png"
174-
}
175-
]
176-
},
177-
178-
2. Menu item (toolbar action) on the explorer view delegating to the command
179-
"view/title": [
180-
{
181-
"command": "vscode-spring-boot.structure.refresh",
182-
"when": "view == explorer.spring",
183-
"group": "navigation@5"
184-
}
185-
],
186-
187-
*/
188-
const structureManager = new StructureManager();
189-
const explorerTreeProvider = new ExplorerTreeProvider(structureManager);
190-
const treeView = window.createTreeView('explorer.spring', { treeDataProvider: explorerTreeProvider, showCollapseAll: true });
191-
192-
// Track expansion/collapse events to preserve state across refreshes
193-
context.subscriptions.push(treeView.onDidExpandElement(e => {
194-
const nodeId = e.element.getNodeId();
195-
explorerTreeProvider.setExpansionState(nodeId, TreeItemCollapsibleState.Expanded);
196-
}));
197-
198-
context.subscriptions.push(treeView.onDidCollapseElement(e => {
199-
const nodeId = e.element.getNodeId();
200-
explorerTreeProvider.setExpansionState(nodeId, TreeItemCollapsibleState.Collapsed);
201-
}));
202-
203-
context.subscriptions.push(treeView);
204-
context.subscriptions.push(commands.registerCommand("vscode-spring-boot.structure.refresh", () => structureManager.refresh(true)));
205-
context.subscriptions.push(commands.registerCommand("vscode-spring-boot.structure.openReference", (node) => {
206-
if (node && node.getReferenceValue) {
207-
const reference = node.getReferenceValue();
208-
if (reference) {
209-
// Reference is a specific URL that should be passed to java.open.file command
210-
commands.executeCommand('java.open.file', reference);
211-
}
212-
}
213-
}));
162+
// Activation of structure explorer
163+
const structureManager = new StructureManager(context);
164+
new ExplorerTreeProvider(structureManager).createTreeView(context, 'explorer.spring');
214165

215166
context.subscriptions.push(commands.registerCommand('vscode-spring-boot.ls.start', () => client.start().then(() => {
216167
// Boot LS is fully started

vscode-extensions/vscode-spring-boot/lib/copilot/copilotRequest.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ export default class CopilotRequest {
4141
let response: string = '';
4242
messages.push(...message);
4343
try {
44-
messages.forEach(m => logger.info(m.content));
4544
response = await this.sendRequest(messages, modelOptions, cancellationToken);
4645
answer += response;
4746
logger.info(`Response: \n`, response);

vscode-extensions/vscode-spring-boot/lib/explorer/explorer-tree-provider.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Event, EventEmitter, ProviderResult, TreeDataProvider, TreeItem, TreeItemCollapsibleState } from "vscode";
1+
import { Event, EventEmitter, ExtensionContext, ProviderResult, TreeDataProvider, TreeItem, TreeItemCollapsibleState, window } from "vscode";
22
import { StructureManager } from "./structure-tree-manager";
33
import { SpringNode } from "./nodes";
44

@@ -17,6 +17,24 @@ export class ExplorerTreeProvider implements TreeDataProvider<SpringNode> {
1717
});
1818
}
1919

20+
createTreeView(context: ExtensionContext, viewId: string) {
21+
const treeView = window.createTreeView(viewId, { treeDataProvider: this, showCollapseAll: true });
22+
23+
// Track expansion/collapse events to preserve state across refreshes
24+
context.subscriptions.push(treeView.onDidExpandElement(e => {
25+
const nodeId = e.element.getNodeId();
26+
this.setExpansionState(nodeId, TreeItemCollapsibleState.Expanded);
27+
}));
28+
29+
context.subscriptions.push(treeView.onDidCollapseElement(e => {
30+
const nodeId = e.element.getNodeId();
31+
this.setExpansionState(nodeId, TreeItemCollapsibleState.Collapsed);
32+
}));
33+
34+
context.subscriptions.push(treeView);
35+
return treeView
36+
}
37+
2038
getTreeItem(element: SpringNode): TreeItem | Thenable<TreeItem> {
2139
const nodeId = element.getNodeId();
2240
const savedState = this.expansionStates.get(nodeId);
@@ -35,11 +53,11 @@ export class ExplorerTreeProvider implements TreeDataProvider<SpringNode> {
3553
}
3654

3755

38-
getExpansionState(nodeId: string): TreeItemCollapsibleState | undefined {
56+
private getExpansionState(nodeId: string): TreeItemCollapsibleState | undefined {
3957
return this.expansionStates.get(nodeId);
4058
}
4159

42-
setExpansionState(nodeId: string, state: TreeItemCollapsibleState): void {
60+
private setExpansionState(nodeId: string, state: TreeItemCollapsibleState): void {
4361
this.expansionStates.set(nodeId, state);
4462
}
4563

vscode-extensions/vscode-spring-boot/lib/explorer/nodes.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Location } from "vscode-languageclient";
33
import { LsStereoTypedNode } from "./structure-tree-manager";
44

55
export class SpringNode {
6-
constructor(public children: SpringNode[], private parent?: SpringNode) {}
6+
constructor(public children: SpringNode[], protected parent?: SpringNode) {}
77

88
getTreeItem(savedState?: TreeItemCollapsibleState): TreeItem {
99
const defaultState = savedState !== undefined ? savedState : TreeItemCollapsibleState.Collapsed;
@@ -39,7 +39,7 @@ export class StereotypedNode extends SpringNode {
3939
constructor(private n: LsStereoTypedNode, children: SpringNode[], parent?: SpringNode) {
4040
super(children, parent);
4141
}
42-
42+
4343
getTreeItem(savedState?: TreeItemCollapsibleState): TreeItem {
4444
const item = super.getTreeItem(savedState);
4545
item.label = this.n.attributes.text;
@@ -49,10 +49,14 @@ export class StereotypedNode extends SpringNode {
4949
if (this.n.attributes.reference) {
5050
item.contextValue = "stereotypedNodeWithReference";
5151
}
52+
53+
if (this.n.attributes.icon === 'project') {
54+
item.contextValue = "project";
55+
}
56+
5257

5358
if (this.n.attributes.location) {
5459
const location = this.n.attributes.location as Location;
55-
// Hard-coded range. Not present... likely not serialized correctly.
5660
item.command = {
5761
command: "vscode.open",
5862
title: "Navigate",

0 commit comments

Comments
 (0)