Skip to content

Commit 448ffdc

Browse files
committed
feat: Advanced Workspace folders support
Fixes #1352 Signed-off-by: azerr <azerr@redhat.com>
1 parent 7b1c925 commit 448ffdc

36 files changed

Lines changed: 2046 additions & 23 deletions

src/main/java/com/redhat/devtools/lsp4ij/LanguageServerWrapper.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.intellij.util.Alarm;
3030
import com.intellij.util.messages.MessageBusConnection;
3131
import com.redhat.devtools.lsp4ij.client.LanguageClientImpl;
32+
import com.redhat.devtools.lsp4ij.client.WorkspaceFolderNotificationManager;
3233
import com.redhat.devtools.lsp4ij.client.features.FileUriSupport;
3334
import com.redhat.devtools.lsp4ij.client.features.LSPClientFeatures;
3435
import com.redhat.devtools.lsp4ij.console.explorer.TracingMessageConsumer;
@@ -129,6 +130,7 @@ public class LanguageServerWrapper implements Disposable {
129130
private List<String> currentProcessCommandLines;
130131
private boolean initiallySupportsWorkspaceFolders = false;
131132
private FileOperationsManager fileOperationsManager;
133+
private WorkspaceFolderNotificationManager workspaceFolderNotificationManager;
132134

133135
private LSPClientFeatures clientFeatures;
134136
// error notification displayed when server start fails.
@@ -480,6 +482,8 @@ public synchronized void start() throws LanguageServerException {
480482
fileOperationsManager = new FileOperationsManager(this);
481483
fileOperationsManager.setServerCapabilities(serverCapabilities);
482484

485+
workspaceFolderNotificationManager = new WorkspaceFolderNotificationManager(this);
486+
483487
this.lspStreamProvider = initializingContext.provider;
484488
this.languageClient = initializingContext.languageClient;
485489
this.languageServer = initializingContext.languageServer;
@@ -541,7 +545,9 @@ private CompletableFuture<InitializingContext> initServer(final VirtualFile root
541545
initParams.setClientInfo(getClientInfo());
542546
initParams.setTrace(provider.getTrace(rootURI));
543547

544-
var folders = LSPIJUtils.toWorkspaceFolders(initialProject, getClientFeatures());
548+
var folders = getClientFeatures()
549+
.getWorkspaceFolderFeature()
550+
.getInitialWorkspaceFolders(initialProject);
545551
initParams.setWorkspaceFolders(folders);
546552

547553
// Customize initialize params if needed
@@ -711,6 +717,11 @@ public boolean canOperate(Project project) {
711717
return ls2;
712718
}
713719

720+
// Check and notify workspace folder for dynamic strategies BEFORE didOpen
721+
if (workspaceFolderNotificationManager != null) {
722+
workspaceFolderNotificationManager.checkAndNotifyWorkspaceFolder(file);
723+
}
724+
714725
DocumentContentSynchronizer synchronizer = createDocumentContentSynchronizer(toUriString(fileUri), file, document, fileConnectionInfo.documentText(), fileConnectionInfo.languageId());
715726
// Synchronizer usually is disposed when document editor is closed.
716727
// But in case of closing the project, it does not close all editors,
@@ -1602,6 +1613,9 @@ private synchronized CompletableFuture<Void> stop(@Nullable InitializingContext
16021613
}
16031614
this.serverCapabilities = null;
16041615
this.dynamicRegistrations.clear();
1616+
if (clientFeatures != null) {
1617+
clientFeatures.getWorkspaceFolderFeature().reset();
1618+
}
16051619
}
16061620

16071621
if (isDisposed()) {

src/main/java/com/redhat/devtools/lsp4ij/LanguageServersRegistry.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,8 @@ private void loadServersAndMappingFromSettings() {
247247
globalSettings != null ? globalSettings.getInitializationOptionsContent() : null,
248248
globalSettings != null ? globalSettings.getExperimentalContent() : null,
249249
settings.getClientConfigurationContent(),
250-
settings.getInstallerConfigurationContent()),
250+
settings.getInstallerConfigurationContent(),
251+
settings.getWorkspaceFolderStrategyConfiguration()),
251252
mappings, false);
252253
}
253254
}
@@ -594,6 +595,7 @@ private void addServerDefinitionWithoutNotification(@NotNull LanguageServerDefin
594595
settings.setIncludeSystemEnvironmentVariables(userDefinedServer.isIncludeSystemEnvironmentVariables());
595596
settings.setMappings(toServerMappingSettings(mappings));
596597
settings.setClientConfigurationContent(userDefinedServer.getClientConfigurationContent());
598+
settings.setWorkspaceFolderStrategyConfiguration(userDefinedServer.getWorkspaceFolderStrategyConfiguration());
597599
settings.setInstallerConfigurationContent(userDefinedServer.getInstallerConfigurationContent());
598600
UserDefinedLanguageServerSettings.getInstance().setUserDefinedLanguageServerSettings(languageServerId, settings);
599601
}
@@ -930,7 +932,8 @@ private void notifyMappingsChanged(@NotNull LanguageServerDefinition definition)
930932
/* includeSystemEnvironmentVariablesChanged */ false,
931933
/* mappingsChanged */ true,
932934
/* clientConfigurationContentChanged */ false,
933-
/* installerConfigurationContentChanged */ false);
935+
/* installerConfigurationContentChanged */ false,
936+
/* workspaceFolderStrategyConfigurationChanged*/ false);
934937
handleChangeEvent(event);
935938
}
936939
}
@@ -948,6 +951,7 @@ private void notifyMappingsChanged(@NotNull LanguageServerDefinition definition)
948951
ls.setIncludeSystemEnvironmentVariables(request.includeSystemEnvironmentVariables());
949952
ls.setClientConfigurationContent(request.clientConfigurationContent());
950953
ls.setInstallerConfigurationContent(request.installerConfigurationContent());
954+
ls.setWorkspaceFolderStrategyConfiguration(request.workspaceFolderStrategyConfiguration());
951955

952956
// remove associations
953957
removeAssociationsFor(request.serverDefinition());
@@ -968,6 +972,7 @@ private void notifyMappingsChanged(@NotNull LanguageServerDefinition definition)
968972

969973
boolean clientConfigurationContentChanged = !Objects.equals(settings.getClientConfigurationContent(), request.clientConfigurationContent());
970974
boolean installerConfigurationContentChanged = !Objects.equals(settings.getInstallerConfigurationContent(), request.installerConfigurationContent());
975+
boolean workspaceFolderStrategyConfigurationChanged = !Objects.equals(settings.getWorkspaceFolderStrategyConfiguration(), request.workspaceFolderStrategyConfiguration());
971976

972977
settings.setServerName(request.name());
973978
settings.setServerUrl(request.url());
@@ -976,11 +981,13 @@ private void notifyMappingsChanged(@NotNull LanguageServerDefinition definition)
976981
settings.setIncludeSystemEnvironmentVariables(request.includeSystemEnvironmentVariables());
977982
settings.setClientConfigurationContent(request.clientConfigurationContent());
978983
settings.setInstallerConfigurationContent(request.installerConfigurationContent());
984+
settings.setWorkspaceFolderStrategyConfiguration(request.workspaceFolderStrategyConfiguration());
979985
settings.setMappings(request.mappings());
980986

981987
if (nameChanged || commandChanged || userEnvironmentVariablesChanged ||
982988
includeSystemEnvironmentVariablesChanged ||
983-
mappingsChanged || clientConfigurationContentChanged || installerConfigurationContentChanged) {
989+
mappingsChanged || clientConfigurationContentChanged || installerConfigurationContentChanged ||
990+
workspaceFolderStrategyConfigurationChanged) {
984991
// Notifications
985992
LanguageServerDefinitionListener.LanguageServerChangedEvent event = new LanguageServerDefinitionListener.LanguageServerChangedEvent(
986993
LanguageServerDefinitionListener.LanguageServerDefinitionEvent.UpdatedBy.USER,
@@ -992,7 +999,8 @@ private void notifyMappingsChanged(@NotNull LanguageServerDefinition definition)
992999
includeSystemEnvironmentVariablesChanged,
9931000
mappingsChanged,
9941001
clientConfigurationContentChanged,
995-
installerConfigurationContentChanged);
1002+
installerConfigurationContentChanged,
1003+
workspaceFolderStrategyConfigurationChanged);
9961004
if (notify) {
9971005
handleChangeEvent(event);
9981006
}
@@ -1122,7 +1130,8 @@ public record UpdateServerDefinitionRequest(@NotNull Project project,
11221130
boolean includeSystemEnvironmentVariables,
11231131
@NotNull List<ServerMappingSettings> mappings,
11241132
@Nullable String clientConfigurationContent,
1125-
@Nullable String installerConfigurationContent) {
1133+
@Nullable String installerConfigurationContent,
1134+
@Nullable String workspaceFolderStrategyConfiguration) {
11261135
}
11271136

11281137
/**

src/main/java/com/redhat/devtools/lsp4ij/LanguageServiceAccessor.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ public void handleChanged(@NotNull LanguageServerChangedEvent event) {
105105
&& (event.commandChanged ||
106106
event.userEnvironmentVariablesChanged ||
107107
event.includeSystemEnvironmentVariablesChanged ||
108-
event.mappingsChanged)) {
108+
event.mappingsChanged ||
109+
event.workspaceFolderStrategyConfigurationChanged)) {
109110
languageServerWrappers.forEach(LanguageServerWrapper::restart);
110111
}
111112
}

src/main/java/com/redhat/devtools/lsp4ij/client/LanguageClientImpl.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,11 @@ public CompletableFuture<Void> unregisterCapability(UnregistrationParams params)
149149

150150
@Override
151151
public CompletableFuture<List<WorkspaceFolder>> workspaceFolders() {
152-
return CompletableFuture.supplyAsync(() -> LSPIJUtils.toWorkspaceFolders(project, wrapper.getClientFeatures()));
152+
return CompletableFuture.supplyAsync(() ->
153+
wrapper.getClientFeatures()
154+
.getWorkspaceFolderFeature()
155+
.getInitialWorkspaceFolders(project)
156+
);
153157
}
154158

155159

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 Red Hat Inc. and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7+
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10+
*
11+
* Contributors:
12+
* Red Hat Inc. - initial API and implementation
13+
*******************************************************************************/
14+
package com.redhat.devtools.lsp4ij.client;
15+
16+
import com.intellij.openapi.vfs.VirtualFile;
17+
import com.redhat.devtools.lsp4ij.LanguageServerWrapper;
18+
import com.redhat.devtools.lsp4ij.ServerStatus;
19+
import org.eclipse.lsp4j.DidChangeWorkspaceFoldersParams;
20+
import org.eclipse.lsp4j.WorkspaceFolder;
21+
import org.eclipse.lsp4j.WorkspaceFoldersChangeEvent;
22+
import org.jetbrains.annotations.NotNull;
23+
import org.slf4j.Logger;
24+
import org.slf4j.LoggerFactory;
25+
26+
import java.util.Collections;
27+
28+
/**
29+
* Manager for sending workspace folder notifications to the language server.
30+
*/
31+
public class WorkspaceFolderNotificationManager {
32+
33+
private static final Logger LOGGER = LoggerFactory.getLogger(WorkspaceFolderNotificationManager.class);
34+
35+
private final LanguageServerWrapper serverWrapper;
36+
37+
public WorkspaceFolderNotificationManager(@NotNull LanguageServerWrapper serverWrapper) {
38+
this.serverWrapper = serverWrapper;
39+
}
40+
41+
/**
42+
* Checks if a new workspace folder needs to be notified for the given file,
43+
* and sends the notification if necessary.
44+
*
45+
* @param file the file being connected
46+
*/
47+
public void checkAndNotifyWorkspaceFolder(@NotNull VirtualFile file) {
48+
if (serverWrapper.getServerStatus() != ServerStatus.started) {
49+
return;
50+
}
51+
52+
var clientFeatures = serverWrapper.getClientFeatures();
53+
var workspaceFolderFeature = clientFeatures.getWorkspaceFolderFeature();
54+
55+
// Check if we need to notify a new workspace folder
56+
WorkspaceFolder folderToNotify = workspaceFolderFeature.getWorkspaceFolderToNotify(file);
57+
58+
if (folderToNotify != null) {
59+
// Send workspace/didChangeWorkspaceFolders notification
60+
notifyWorkspaceFolderAdded(folderToNotify);
61+
}
62+
}
63+
64+
/**
65+
* Sends a workspace/didChangeWorkspaceFolders notification to add a new folder.
66+
*
67+
* @param folder the workspace folder to add
68+
*/
69+
private void notifyWorkspaceFolderAdded(@NotNull WorkspaceFolder folder) {
70+
var languageServer = serverWrapper.getLanguageServer();
71+
if (languageServer == null) {
72+
return;
73+
}
74+
75+
try {
76+
WorkspaceFoldersChangeEvent event = new WorkspaceFoldersChangeEvent();
77+
event.setAdded(Collections.singletonList(folder));
78+
event.setRemoved(Collections.emptyList());
79+
80+
DidChangeWorkspaceFoldersParams params = new DidChangeWorkspaceFoldersParams();
81+
params.setEvent(event);
82+
83+
languageServer.getWorkspaceService().didChangeWorkspaceFolders(params);
84+
85+
LOGGER.info("Workspace folder added: " + folder.getUri());
86+
} catch (Exception e) {
87+
LOGGER.error("Error sending workspace folder notification", e);
88+
}
89+
}
90+
}

src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPClientFeatures.java

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,12 @@ public class LSPClientFeatures implements Disposable, FileUriSupport {
102102
private LSPWorkspaceSymbolFeature workspaceSymbolFeature;
103103

104104
private LSPConfigurationFeature configurationFeature;
105-
105+
106106
private EditorBehaviorFeature editorBehaviorFeature;
107107

108+
private LSPWorkspaceFolderFeature workspaceFolderFeature;
109+
private @Nullable LanguageServerDefinition serverDefinition;
110+
108111
public LSPClientFeatures() {
109112
setFileUriSupport(FileUriSupport.DEFAULT);
110113
}
@@ -357,6 +360,9 @@ public final Project getProject() {
357360
*/
358361
@NotNull
359362
public final LanguageServerDefinition getServerDefinition() {
363+
if (serverDefinition != null) {
364+
return serverDefinition;
365+
}
360366
return getServerWrapper().getServerDefinition();
361367
}
362368

@@ -1343,6 +1349,38 @@ public LSPClientFeatures setEditorBehaviorFeature(@NotNull EditorBehaviorFeature
13431349
return this;
13441350
}
13451351

1352+
/**
1353+
* Returns the LSP workspace folder feature.
1354+
*
1355+
* @return the LSP workspace folder feature.
1356+
*/
1357+
@NotNull
1358+
public final LSPWorkspaceFolderFeature getWorkspaceFolderFeature() {
1359+
if (workspaceFolderFeature == null) {
1360+
initWorkspaceFolderFeature();
1361+
}
1362+
return workspaceFolderFeature;
1363+
}
1364+
1365+
private synchronized void initWorkspaceFolderFeature() {
1366+
if (workspaceFolderFeature != null) {
1367+
return;
1368+
}
1369+
setWorkspaceFolderFeature(new LSPWorkspaceFolderFeature());
1370+
}
1371+
1372+
/**
1373+
* Initialize the LSP workspace folder feature.
1374+
*
1375+
* @param workspaceFolderFeature the LSP workspace folder feature.
1376+
* @return the LSP client features.
1377+
*/
1378+
public final LSPClientFeatures setWorkspaceFolderFeature(@NotNull LSPWorkspaceFolderFeature workspaceFolderFeature) {
1379+
workspaceFolderFeature.setClientFeatures(this);
1380+
this.workspaceFolderFeature = workspaceFolderFeature;
1381+
return this;
1382+
}
1383+
13461384
/**
13471385
* Set the language server wrapper.
13481386
*
@@ -1353,6 +1391,16 @@ public final void setServerWrapper(LanguageServerWrapper serverWrapper) {
13531391
this.serverWrapper = serverWrapper;
13541392
}
13551393

1394+
/**
1395+
* Set the language server definition.
1396+
*
1397+
* @param serverDefinition the language server definition.
1398+
*/
1399+
@ApiStatus.Internal
1400+
public final void setServerDefinition(LanguageServerDefinition serverDefinition) {
1401+
this.serverDefinition = serverDefinition;
1402+
}
1403+
13561404
/**
13571405
* Returns the language server wrapper.
13581406
*
@@ -1440,6 +1488,9 @@ public void dispose() {
14401488
if (workspaceSymbolFeature != null) {
14411489
workspaceSymbolFeature.dispose();
14421490
}
1491+
if (workspaceFolderFeature != null) {
1492+
workspaceFolderFeature.reset();
1493+
}
14431494
}
14441495

14451496
public void setServerCapabilities(@NotNull ServerCapabilities serverCapabilities) {

0 commit comments

Comments
 (0)