Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
29eb813
Implement workspace file notifcation routing.
toinehartman Jun 11, 2026
9c3424e
Enable completion tests.
toinehartman Jun 11, 2026
de39d64
Improve completion tests.
toinehartman Jun 11, 2026
c9e628b
Make completion check resilient again stale elements.
toinehartman Jun 15, 2026
420e89c
Implement folding range refresh.
toinehartman Jun 24, 2026
6b46f1c
Do not disconnect development language server on unregister.
toinehartman Jun 25, 2026
d13c136
Fix dynamic capability equals.
toinehartman Jun 25, 2026
a23a2f5
Improve multiple client initialization.
toinehartman Jun 25, 2026
46ef19e
Add text document content refresh.
toinehartman Jun 25, 2026
f4fcd5c
Document renamed file routing.
toinehartman Jun 25, 2026
0edb882
Routing/logging wrapper.
toinehartman Jun 25, 2026
da7f26f
Set log level per test suite.
toinehartman Jun 25, 2026
d8d6806
Set DSL log level to trace.
toinehartman Jun 25, 2026
43e791c
Clear all notifications before loading language.
toinehartman Jun 25, 2026
3124f72
Safe, reused unregistration.
toinehartman Jun 25, 2026
89befc3
Do one capability update at a time.
toinehartman Jun 25, 2026
11296e8
Isolate failing test/registration.
toinehartman Jun 26, 2026
c718507
Isolate even more.
toinehartman Jun 26, 2026
d212f94
Revert "Isolate even more."
toinehartman Jun 26, 2026
2e4a43b
Revert "Isolate failing test/registration."
toinehartman Jun 26, 2026
00d85cc
Filter duplicate registrations from servers.
toinehartman Jun 26, 2026
5362c1f
Merge branch 'feature/1010-pom-leading-for-dsls/final' into feature/1…
toinehartman Jun 26, 2026
3244617
Enforce trace log level.
toinehartman Jun 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1013,10 +1013,14 @@ private static String buildContributionKey(LanguageParameter lang) {
return lang.getMainFunction() + "::" + lang.getMainFunction();
}

public static boolean isLanguageCompletelyRemoved(LanguageParameter lang) {
return lang.getMainModule() == null || lang.getMainModule().isEmpty();
}

@Override
public synchronized void unregisterLanguage(LanguageParameter lang) {
logger.info("unregisterLanguage({})", lang.getName());
boolean removeAll = lang.getMainModule() == null || lang.getMainModule().isEmpty();
boolean removeAll = isLanguageCompletelyRemoved(lang);
if (!removeAll) {
var contrib = contributions.get(lang.getName());
if (contrib != null && !contrib.removeContributor(buildContributionKey(lang))) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ public boolean equals(@Nullable Object obj) {
}
var other = (AbstractDynamicCapability<?>) obj;
return Objects.equals(id, other.id)
&& Objects.equals(methodName, other.methodName);
&& Objects.equals(methodName, other.methodName)
&& Objects.equals(preferStaticRegistration, other.preferStaticRegistration);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@
private final Set<AbstractDynamicCapability<?>> staticCapabilities;

private final AtomicReference<Collection<ICapabilityParams>> lastParams = new AtomicReference<>(Collections.emptyList());
private final AtomicReference<CompletableFuture<Void>> currentUpdate;

// Map of method names with current registration values
private final Map<String, Registration> currentRegistrations = new ConcurrentHashMap<>();

Expand All @@ -87,6 +89,7 @@
this.client = client;
this.exec = exec;
this.noop = CompletableFutureUtils.completedFuture(null, exec);
this.currentUpdate = new AtomicReference<>(noop);

// Check whether to register capabilities dynamically or statically
var dynamicCaps = new HashSet<AbstractDynamicCapability<?>>();
Expand Down Expand Up @@ -120,16 +123,19 @@
* @return A future that completes with a boolean that is false when any registration failed, and true otherwise.
*/
public CompletableFuture<Void> update(Collection<ICapabilityParams> languages) {
logger.debug("Updating {} dynamic capabilities for {} languages", dynamicCapabilities.size(), languages.size());
// Copy the contributions so we know we are looking at a stable collection of elements.

/*
*VERY IMPORTANT* Setting the atomic reference from the thread that called us.
We need to be sure that the `lastContributions` reference actually points to the most recently known contributions.
Therefore, we need to set this reference before delegating any work to futures, where we lose guaranteed execution order.
Additionally, this function should be called from a thread pool with predictable execution order.
*/
lastParams.set(List.copyOf(languages));
return currentUpdate.updateAndGet(current -> current.thenComposeAsync(v -> doUpdate(languages), exec));
}

private CompletableFuture<Void> doUpdate(Collection<ICapabilityParams> languages) {
logger.debug("Updating {} dynamic capabilities for {} languages", dynamicCapabilities.size(), languages.size());
return CompletableFutureUtils.reduce(dynamicCapabilities.stream().map(this::updateRegistration), exec)
.thenAccept(_v -> logger.debug("Done updating dynamic capabilities"));
}
Expand Down Expand Up @@ -200,6 +206,7 @@
result = register(registration, existingRegistration);
}

// TODO Check/Fix that if one capability fails, the rest are still registered

Check warning on line 209 in rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/capabilities/CapabilityRegistration.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Complete the task associated to this TODO comment.

See more on https://sonarcloud.io/project/issues?id=usethesource_rascal-language-servers&issues=AZ7_T3o55529WGWPwarC&open=AZ7_T3o55529WGWPwarC&pullRequest=1117
return result.handle((_v, t) -> {
if (t != null) {
// An error occurred. Inform the user and do not recurse.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public class ActualRoutingLanguageServer extends BaseLanguageServer.ActualLangua
private final Map<String, CompletableFuture<IBaseLanguageServerExtensions>> languageServers = new ConcurrentHashMap<>();
private final Map<String, String> languagesByExtension = new ConcurrentHashMap<>();

private final MultipleClientProxy client = new MultipleClientProxy();
private @MonotonicNonNull MultipleClientProxy remoteClient;
private @MonotonicNonNull InitializeParams initializeParams;
private final JsonWriter logForwarder;

Expand Down Expand Up @@ -173,7 +173,7 @@ public CompletableFuture<IBaseLanguageServerExtensions> route(ISourceLocation lo
@Override
public void connect(LanguageClient client) {
super.connect(client); // first let the super class proxy the client
this.client.connect(availableClient());
this.remoteClient = new MultipleClientProxy(availableClient(), getExecutor());
}

private static String extension(ISourceLocation doc) {
Expand Down Expand Up @@ -349,6 +349,11 @@ public void write(JsonWriter writer, ProxiedIValue proxiedValue) throws IOExcept
}

private @Nullable CompletableFuture<IBaseLanguageServerExtensions> startServer(LanguageParameter lang) {
if (remoteClient == null) {
// This should never happen, since it's initialized by `connect` before we are able to receive any `registerLanguage` requests.
throw new IllegalStateException("Remote client is not initialized");
}

var serverParams = BaseLanguageServer.DEPLOY_MODE
? startServerProcess(lang)
: connectToServer(lang)
Expand All @@ -360,7 +365,7 @@ public void write(JsonWriter writer, ProxiedIValue proxiedValue) throws IOExcept

var serverLauncher = new Launcher.Builder<IBaseLanguageServerExtensions>()
.setRemoteInterface(IBaseLanguageServerExtensions.class)
.setLocalService(client)
.setLocalService(remoteClient)
.setInput(serverParams.getLeft())
.setOutput(serverParams.getMiddle())
.configureGson(ActualRoutingLanguageServer::configureProxyGson)
Expand Down Expand Up @@ -468,27 +473,23 @@ public synchronized CompletableFuture<Void> sendRegisterLanguage(LanguageParamet
public synchronized CompletableFuture<Void> sendUnregisterLanguage(LanguageParameter lang) {
logger.debug("rascal/sendUnregisterLanguage({})", lang.getName());

var work = route(lang.getName())
.thenCompose(s -> s.sendUnregisterLanguage(lang));

// Note: this should be handled for the deployed scenario by the process onExit hook.
boolean removeAll = lang.getMainModule() == null || lang.getMainModule().isEmpty();
if (removeAll) {
// clear the whole language
logger.trace("unregisterLanguage({}) completely", lang.getName());
if (ParametricTextDocumentService.isLanguageCompletelyRemoved(lang)) {
// Do not remove the connection to the server.
// For the deployed scenario, this is handled by the process onExit hook.
// For the development scenario, we maintain the connection, since the remote server does not exit.

for (var extension : lang.getExtensions()) {
this.languagesByExtension.remove(extension);
}
var removed = languageServers.remove(lang.getName());
if (removed != null) {
work = work
.thenCompose(ignored -> removed)
.thenCompose(server -> server.shutdown().thenAccept(ignored -> server.exit()));
}
}

return work;
return route(lang.getName()).handle((s, t) -> {
if (s == null) {
// Nothing to unregister
return CompletableFutureUtils.<Void>completedFuture(null, getExecutor());
}
return s.sendUnregisterLanguage(lang);
}).thenCompose(Function.identity());
}

@Override
Expand Down
Loading
Loading