Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,6 @@ public class BootLanguageServerPlugin extends AbstractUIPlugin {

public static final String BOOT_LS_DEFINITION_ID = "org.eclipse.languageserver.languages.springboot";

private BootLsState lsState = new BootLsState();

public BootLanguageServerPlugin() {
// Empty
}
Expand Down Expand Up @@ -181,10 +179,6 @@ private ImageDescriptor createStereotypeImageDescriptor(URL url, IPath p) {
return ImageDescriptor.createFromURL(url);
}

public BootLsState getLsState() {
return lsState;
}

public Image getStereotypeImage(String name) {
return getImageRegistry().get(STEREOTYPE_IMG_PREFIX + name);
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
import org.eclipse.lsp4j.ExecuteCommandParams;
import org.eclipse.lsp4j.InitializeResult;
import org.eclipse.lsp4j.jsonrpc.messages.Message;
import org.eclipse.lsp4j.jsonrpc.messages.NotificationMessage;
import org.eclipse.lsp4j.jsonrpc.messages.ResponseMessage;
import org.eclipse.lsp4j.services.LanguageServer;
import org.eclipse.ui.PlatformUI;
Expand Down Expand Up @@ -128,7 +127,6 @@ public InputStream getErrorStream() {

@Override
public void stop() {
BootLanguageServerPlugin.getDefault().getLsState().stopped();
IProxyService proxyService = PlatformUI.getWorkbench().getService(IProxyService.class);
if (proxyService != null) {
proxyService.removeProxyChangeListener(proxySettingsListener);
Expand Down Expand Up @@ -182,14 +180,6 @@ public void handleMessage(Message message, LanguageServer languageServer, URI ro
));
}

BootLanguageServerPlugin.getDefault().getLsState().initialized();
}
} else if (message instanceof NotificationMessage) {
NotificationMessage notification = (NotificationMessage) message;
// Handle spring/index/updated notification
if ("spring/index/updated".equals(notification.getMethod())) {
// Emit event to the Flux
BootLanguageServerPlugin.getDefault().getLsState().indexed();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public void run() {
GroupingDialog dialog = new GroupingDialog(UI.getActiveShell(), structureView::fetchGroups, structureView::getGroupings);
if (dialog.open() == IDialogConstants.OK_ID) {
structureView.setGroupings(dialog.getResult());
structureView.fetchStructure(false);
structureView.fetchStructure(null, false);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@
*******************************************************************************/
package org.springframework.tooling.boot.ls.views;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
Expand All @@ -26,10 +29,10 @@
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.part.ViewPart;
import org.springframework.tooling.boot.ls.BootLanguageServerPlugin;
import org.springframework.tooling.boot.ls.BootLsState;
import org.springframework.tooling.boot.ls.views.StructureClient.Groups;
import org.springframework.tooling.boot.ls.views.StructureClient.StructureParameter;
import org.springframework.tooling.ls.eclipse.commons.LanguageServerCommonsActivator;
import org.springframework.tooling.ls.eclipse.commons.SpringIndexState;


/**
Expand All @@ -47,27 +50,35 @@ public class LogicalStructureView extends ViewPart {

final private GroupingRepository groupingRepository = new GroupingRepository();

private final AtomicBoolean fetching = new AtomicBoolean();
private Consumer<Set<String>> indexStateListener = projectNames -> fetchStructure(projectNames, false);

private Consumer<BootLsState> lsStateListener = state -> {
if (state.isIndexed()) {
fetchStructure(false);
} else {
UI.getDisplay().asyncExec(() -> treeViewer.setInput(null));
}
};

void fetchStructure(boolean updateMetadata) {
if (!fetching.compareAndExchange(false, true)) {
structureClient.fetchStructure(new StructureParameter(updateMetadata, getGroupings())).thenAccept(nodes -> {
fetching.set(false);
UI.getDisplay().asyncExec(() -> {
Object[] expanded = treeViewer.getExpandedElements();
treeViewer.setInput(nodes);
treeViewer.setExpandedElements(expanded);
void fetchStructure(Set<String> affectedProjects, boolean updateMetadata) {
structureClient.fetchStructure(new StructureParameter(updateMetadata, affectedProjects, getGroupings()))
.thenAccept(nodes -> {
UI.getDisplay().asyncExec(() -> {
Object[] expanded = treeViewer.getExpandedElements();
if (affectedProjects == null || affectedProjects.isEmpty()) {
treeViewer.setInput(nodes);
} else {
@SuppressWarnings("unchecked")
List<StereotypeNode> oldNodes = (List<StereotypeNode>) treeViewer.getInput();
List<StereotypeNode> newNodes = new ArrayList<>(oldNodes.size() + nodes.size());
Map<String, Optional<StereotypeNode>> nodesMap = affectedProjects.stream().collect(Collectors.toMap(e -> e, e -> nodes.stream().filter(n -> e.equals(n.getProjectId())).findFirst()));
for (StereotypeNode n : oldNodes) {
String projectName = n.getProjectId();
if (nodesMap.containsKey(projectName)) {
nodesMap.remove(projectName).ifPresent(newNodes::add);
} else {
newNodes.add(n);
}
}
nodesMap.values().stream().filter(opt -> opt.isPresent()).map(opt -> opt.get()).forEach(newNodes::add);
treeViewer.setInput(newNodes);
}

treeViewer.setExpandedElements(expanded);
});
});
});
}
}

CompletableFuture<List<Groups>> fetchGroups() {
Expand All @@ -88,15 +99,13 @@ public void createPartControl(Composite parent) {
// Set initial input - placeholder data
treeViewer.setInput(Collections.emptyList());

BootLsState lsState = BootLanguageServerPlugin.getDefault().getLsState();
fetchStructure(null, false);

if (lsState.isIndexed()) {
fetchStructure(false);
}
final SpringIndexState springIndexState = LanguageServerCommonsActivator.getInstance().getSpringIndexState();

lsState.addStateChangedListener(lsStateListener);
springIndexState.addStateChangedListener(indexStateListener);

treeViewer.getControl().addDisposeListener(e -> lsState.removeStateChangedListener(lsStateListener));
treeViewer.getControl().addDisposeListener(e -> springIndexState.removeStateChangedListener(indexStateListener));

treeViewer.addDoubleClickListener(e -> {
Object o = ((IStructuredSelection) e.getSelection()).getFirstElement();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ class RefreshAction extends Action {

@Override
public void run() {
this.logicalStructureView.fetchStructure(true);
this.logicalStructureView.fetchStructure(null, true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,14 @@
*
* @author Alex Boyko
*/
record StereotypeNode(String id, String text, String icon, Location location, Location reference, Map<String, Object> attributes, StereotypeNode[] children) {
record StereotypeNode(
String id,
String text,
String icon,
Location location,
Location reference,
Map<String, Object> attributes,
StereotypeNode[] children) {

@Override
public boolean equals(Object obj) {
Expand All @@ -39,6 +46,11 @@ public int hashCode() {
public String toString() {
return text;
}


public String getProjectId() {
return attributes.containsKey(StereotypeNodeDeserializer.PROJECT_ID)
? (String) attributes.get(StereotypeNodeDeserializer.PROJECT_ID)
: null;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;

@SuppressWarnings({ "restriction", "serial" })
@SuppressWarnings({ "restriction" })
public class StereotypeNodeDeserializer implements com.google.gson.JsonDeserializer<StereotypeNode> {

private static final String LOCATION = "location";
Expand All @@ -30,6 +30,8 @@ public class StereotypeNodeDeserializer implements com.google.gson.JsonDeseriali
private static final String CHILDREN = "children";
private static final String REFERENCE = "reference";

static final String PROJECT_ID = "projectId";

private static final String NODE_ID = "nodeId";


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
Expand All @@ -22,12 +23,12 @@
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;

@SuppressWarnings({ "restriction", "serial" })
@SuppressWarnings({ "restriction" })
class StructureClient {

record Groups (String projectName, List<Group> groups) {}
record Group (String identifier, String displayName) {}
record StructureParameter(boolean updateMetadata, Map<String, List<String>> groups) {}
record StructureParameter(boolean updateMetadata, Collection<String> affectedProjects, Map<String, List<String>> groups) {}

private static final String FETCH_SPRING_BOOT_STRUCTURE = "sts/spring-boot/structure";
private static final String FETCH_STRUCTURE_GROUPS = "sts/spring-boot/structure/groups";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ public void propertyChange(PropertyChangeEvent event) {

private AnnotationPreference bootHintAnnotationPreference;

/*
* Singleton state. We assume there is going to be one server at most serving info about SpringIndex
*/
private SpringIndexState springIndexState = new SpringIndexState();

public LanguageServerCommonsActivator() {
}

Expand Down Expand Up @@ -131,4 +136,8 @@ public static void logInfo(String message) {
instance.getLog().log(new Status(IStatus.INFO, instance.getBundle().getSymbolicName(), message));
}

public SpringIndexState getSpringIndexState() {
return springIndexState;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.ISourceViewerExtension5;
import org.eclipse.lsp4e.LSPEclipseUtils;
import org.eclipse.lsp4e.LanguageClientImpl;
import org.eclipse.lsp4e.client.DefaultLanguageClient;
import org.eclipse.lsp4j.CodeLens;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.MarkupContent;
Expand Down Expand Up @@ -102,7 +102,7 @@
import com.google.common.collect.ImmutableMap;

@SuppressWarnings("restriction")
public class STS4LanguageClientImpl extends LanguageClientImpl implements STS4LanguageClient {
public class STS4LanguageClientImpl extends DefaultLanguageClient implements STS4LanguageClient {

private final Executor executor;

Expand Down Expand Up @@ -635,6 +635,7 @@ public void liveProcessMemoryMetricsDataUpdated(LiveProcessSummary arg0) {

@Override
public void indexUpdated(IndexUpdatedParams indexUpdateDetails) {
LanguageServerCommonsActivator.getInstance().getSpringIndexState().indexed(indexUpdateDetails.getAffectedProjects());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*******************************************************************************
* Copyright (c) 2025 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.tooling.ls.eclipse.commons;

import java.util.Collections;
import java.util.Set;
import java.util.function.Consumer;

import org.eclipse.core.runtime.ListenerList;

public final class SpringIndexState {

private ListenerList<Consumer<Set<String>>> listeners = new ListenerList<>();

public void addStateChangedListener(Consumer<Set<String>> l) {
listeners.add(l);
}

public void removeStateChangedListener(Consumer<Set<String>> l) {
listeners.remove(l);
}

/*
* Ideally does not need to be synced but it should be error prone for for more than server notifying about index changes
*/
synchronized void indexed(Set<String> projectNames) {
listeners.forEach(l -> l.accept(Collections.unmodifiableSet(projectNames)));
}
}
Loading
Loading