Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3007e54
Parametric router boilerplate.
toinehartman Apr 15, 2026
1345af6
Add parametric router tests.
toinehartman Apr 15, 2026
d08579b
First implementation of single-language server and some router methods.
toinehartman Apr 15, 2026
eea545e
Complete parametric router boilerplate.
toinehartman Apr 16, 2026
e516b87
Remove copies and unused endpoints.
toinehartman Apr 16, 2026
4baf07e
Route LSP calls by language.
toinehartman Apr 16, 2026
a80ec90
Extract open file content management.
toinehartman Apr 16, 2026
3d06f3f
Remove language on unregister.
toinehartman Apr 16, 2026
5800110
Fix accidental endless recursions.
toinehartman Apr 16, 2026
9c336ee
Fix string ambiguities.
toinehartman Apr 16, 2026
44b2aeb
Add TODO note for later.
toinehartman Apr 16, 2026
69ef3d5
Propagate language registrations.
toinehartman Apr 16, 2026
47488e2
Connect client to delegate servers.
toinehartman Apr 16, 2026
a2dc0e7
Parametric doc service has at most one multiplexer.
toinehartman Apr 16, 2026
5538293
Fix various null checks & annotations.
toinehartman Apr 16, 2026
31ed9d9
Merge remote-tracking branch 'origin/main' into feature/1010-pom-lead…
toinehartman Apr 16, 2026
1a6df0a
Remove mapping of single language to facts/extensions.
toinehartman Apr 17, 2026
df2b2a3
Use utility list function.
toinehartman Apr 17, 2026
f353ca7
Fix opaque URIs and extensionless documents.
toinehartman Apr 17, 2026
1c86272
Do not distribute renames over contributions.
toinehartman Apr 17, 2026
c92350a
Comments & synchronization.
toinehartman Apr 17, 2026
5525b59
Implement executeCommand on router side.
toinehartman Apr 17, 2026
eb22f35
Finish implementing command execution.
toinehartman Apr 20, 2026
d9112a6
Refactor command execution to simplify.
toinehartman Apr 20, 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
14 changes: 0 additions & 14 deletions rascal-lsp/src/main/checkerframework/lsp4j.astub
Original file line number Diff line number Diff line change
Expand Up @@ -63,20 +63,6 @@ public class DocumentSymbol {
}


package org.eclipse.lsp4j.services;


import org.eclipse.lsp4j.*;
import org.eclipse.lsp4j.jsonrpc.services.*;
import org.checkerframework.checker.nullness.qual.*;

@JsonSegment("textDocument")
public interface TextDocumentService {
@JsonRequest
default CompletableFuture<@Nullable Hover> hover(HoverParams params) { }
}


package org.eclipse.lsp4j;

import org.checkerframework.checker.nullness.qual.*;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
Expand Down Expand Up @@ -131,23 +130,25 @@ private static void printClassPath() {
}

@SuppressWarnings({"java:S2189", "java:S106"})
public static void startLanguageServer(ExecutorService requestPool, ExecutorService workerPool, Function<ExecutorService, IBaseTextDocumentService> docServiceProvider, BiFunction<ExecutorService, IBaseTextDocumentService, BaseWorkspaceService> workspaceServiceProvider, int portNumber) {
public static void startLanguageServer(ExecutorService requestPool, ExecutorService workerPool, Function<ExecutorService, IBaseTextDocumentService> docServiceProvider, Function<ExecutorService, BaseWorkspaceService> workspaceServiceProvider, int portNumber) {
logger.info("Starting Rascal Language Server: {}", getVersion());
printClassPath();

if (DEPLOY_MODE) {
var docService = docServiceProvider.apply(workerPool);
var wsService = workspaceServiceProvider.apply(workerPool, docService);
var wsService = workspaceServiceProvider.apply(workerPool);
docService.pair(wsService);
wsService.pair(docService);
startLSP(constructLSPClient(capturedIn, capturedOut, new ActualLanguageServer(() -> System.exit(0), workerPool, docService, wsService), requestPool));
}
else {
try (ServerSocket serverSocket = new ServerSocket(portNumber, 0, InetAddress.getByName("127.0.0.1"))) {
logger.info("Rascal LSP server listens on port number: {}", portNumber);
while (true) {
var docService = docServiceProvider.apply(workerPool);
var wsService = workspaceServiceProvider.apply(workerPool, docService);
var wsService = workspaceServiceProvider.apply(workerPool);
docService.pair(wsService);
wsService.pair(docService);
startLSP(constructLSPClient(serverSocket.accept(), new ActualLanguageServer(() -> {}, workerPool, docService, wsService), requestPool));
}
} catch (IOException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Expand All @@ -55,29 +56,27 @@
import org.eclipse.lsp4j.services.LanguageClientAware;
import org.eclipse.lsp4j.services.WorkspaceService;
import org.rascalmpl.vscode.lsp.util.Nullables;
import org.rascalmpl.vscode.lsp.util.concurrent.CompletableFutureUtils;
import org.rascalmpl.vscode.lsp.util.locations.Locations;

public abstract class BaseWorkspaceService implements WorkspaceService, LanguageClientAware {
private static final Logger logger = LogManager.getLogger(BaseWorkspaceService.class);

private @MonotonicNonNull LanguageClient client;

public static final String RASCAL_LANGUAGE = "Rascal";
public static final String RASCAL_META_COMMAND = "rascal-meta-command";
public static final String RASCAL_COMMAND = "rascal-command";
protected final ExecutorService exec;

private final ExecutorService exec;

private final IBaseTextDocumentService documentService;
private @MonotonicNonNull IBaseTextDocumentService documentService;
private final CopyOnWriteArrayList<WorkspaceFolder> workspaceFolders = new CopyOnWriteArrayList<>();


protected BaseWorkspaceService(ExecutorService exec, IBaseTextDocumentService documentService) {
this.documentService = documentService;
protected BaseWorkspaceService(ExecutorService exec) {
this.exec = exec;
}

public void pair(IBaseTextDocumentService documentService) {
this.documentService = documentService;
}

public void initialize(ClientCapabilities clientCap, @Nullable List<WorkspaceFolder> currentWorkspaceFolders, ServerCapabilities capabilities) {
this.workspaceFolders.clear();
if (currentWorkspaceFolders != null) {
Expand All @@ -93,6 +92,21 @@ public void initialize(ClientCapabilities clientCap, @Nullable List<WorkspaceFol
}
}

IBaseTextDocumentService availableDocumentService() {
if (this.documentService == null) {
throw new IllegalStateException("Document service not initialized");
}

return this.documentService;
}

protected LanguageClient availableClient() {
if (this.client == null) {
throw new IllegalStateException("Language client not initialized");
}
return this.client;
}

public List<WorkspaceFolder> workspaceFolders() {
return Collections.unmodifiableList(workspaceFolders);
}
Expand Down Expand Up @@ -126,60 +140,53 @@ public void didChangeWorkspaceFolders(DidChangeWorkspaceFoldersParams params) {
if (removed != null) {
workspaceFolders.removeAll(removed);
for (WorkspaceFolder folder : removed) {
documentService.projectRemoved(folder.getName(), Locations.toLoc(folder.getUri()));
availableDocumentService().projectRemoved(folder.getName(), Locations.toLoc(folder.getUri()));
}
}

var added = params.getEvent().getAdded();
if (added != null) {
workspaceFolders.addAll(added);
for (WorkspaceFolder folder : added) {
documentService.projectAdded(folder.getName(), Locations.toLoc(folder.getUri()));
availableDocumentService().projectAdded(folder.getName(), Locations.toLoc(folder.getUri()));
}
}
}

@Override
public void didCreateFiles(CreateFilesParams params) {
logger.debug("workspace/didCreateFiles: {}", params.getFiles());
exec.submit(() -> documentService.didCreateFiles(params));
exec.submit(() -> availableDocumentService().didCreateFiles(params));
}

@Override
public void didRenameFiles(RenameFilesParams params) {
logger.debug("workspace/didRenameFiles: {}", params.getFiles());

exec.submit(() -> documentService.didRenameFiles(params, workspaceFolders()));
exec.submit(() -> availableDocumentService().didRenameFiles(params, workspaceFolders()));

exec.submit(() -> {
// cleanup the old files (we do not get a `didDelete` event)
var oldFiles = params.getFiles().stream()
.map(f -> f.getOldUri())
.map(FileDelete::new)
.collect(Collectors.toList());
documentService.didDeleteFiles(new DeleteFilesParams(oldFiles));
availableDocumentService().didDeleteFiles(new DeleteFilesParams(oldFiles));
});
}

@Override
public void didDeleteFiles(DeleteFilesParams params) {
logger.debug("workspace/didDeleteFiles: {}", params.getFiles());
exec.submit(() -> documentService.didDeleteFiles(params));
exec.submit(() -> availableDocumentService().didDeleteFiles(params));
}

@Override
public CompletableFuture<Object> executeCommand(ExecuteCommandParams commandParams) {
logger.debug("workspace/executeCommand: {}", commandParams);
return CompletableFutureUtils.completedFuture(commandParams, exec)
.thenCompose(params -> {
if (params.getCommand().startsWith(RASCAL_META_COMMAND) || params.getCommand().startsWith(RASCAL_COMMAND)) {
String languageName = ((JsonPrimitive) params.getArguments().get(0)).getAsString();
String command = ((JsonPrimitive) params.getArguments().get(1)).getAsString();
return documentService.executeCommand(languageName, command).thenApply(v -> v);
}

return CompletableFutureUtils.completedFuture(params.getCommand() + " was ignored.", exec);
});
var language = ((JsonPrimitive) commandParams.getArguments().get(0)).getAsString();
var command = ((JsonPrimitive) commandParams.getArguments().get(1)).getAsString();
return availableDocumentService().executeCommand(language, command).thenApply(Function.identity());
}

protected final ExecutorService getExecutor() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,11 @@ public interface IBaseTextDocumentService extends TextDocumentService {
void projectAdded(String name, ISourceLocation projectRoot);
void projectRemoved(String name, ISourceLocation projectRoot);

CompletableFuture<IValue> executeCommand(String languageName, String command);
CompletableFuture<IValue> executeCommand(String language, String command);

LineColumnOffsetMap getColumnMap(ISourceLocation file);
ColumnMaps getColumnMaps();
// TODO Simplify return type to something that can be serialized over JSON-RPC
@Nullable TextDocumentState getDocumentState(ISourceLocation file);

boolean isManagingFile(ISourceLocation file);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2018-2025, NWO-I CWI and Swat.engineering
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.rascalmpl.vscode.lsp.parametric;

import java.util.concurrent.CompletableFuture;
import org.eclipse.lsp4j.services.LanguageClientAware;
import org.eclipse.lsp4j.services.TextDocumentService;
import org.eclipse.lsp4j.services.WorkspaceService;
import org.rascalmpl.vscode.lsp.parametric.LanguageRegistry.LanguageParameter;

import io.usethesource.vallang.IValue;

public interface ISingleLanguageService extends TextDocumentService, WorkspaceService, LanguageClientAware {
void cancelProgress(String progressId);
void registerLanguage(LanguageParameter lang);
void unregisterLanguage(LanguageParameter lang);
CompletableFuture<IValue> executeCommand(String command);
}
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,14 @@ public boolean removeContributor(String contribKey) {
return true;
}

/**
* Remove all contributors.
*/
public void clearContributors() {
contributions.clear();
calculateRouting();
}

private synchronized void calculateRouting() {
// after contributions have changed, we calculate the routing
// this is to avoid doing this lookup every time we get a request
Expand Down
Loading
Loading