From 8f3f516116989ad41d6bf35b8e04fa6cdb9de5b7 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 3 Dec 2025 15:12:58 +0100 Subject: [PATCH 001/149] Eliminated URIParameter --- .../vscode/lsp/IRascalFileSystemServices.java | 43 +++++++------------ .../messages/ISourceLocationRequest.java | 6 +++ 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java index f450ed621..9ea9f1655 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java @@ -41,11 +41,12 @@ import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutorService; import java.util.stream.Stream; -import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; -import org.checkerframework.checker.nullness.qual.Nullable; + import org.apache.commons.codec.binary.Base64InputStream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; +import org.checkerframework.checker.nullness.qual.Nullable; import org.eclipse.lsp4j.jsonrpc.ResponseErrorException; import org.eclipse.lsp4j.jsonrpc.messages.ResponseError; import org.eclipse.lsp4j.jsonrpc.services.JsonNotification; @@ -58,8 +59,10 @@ import org.rascalmpl.uri.URIUtil; import org.rascalmpl.uri.UnsupportedSchemeException; import org.rascalmpl.values.IRascalValueFactory; +import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.ISourceLocationRequest; import org.rascalmpl.vscode.lsp.util.NamedThreadPool; import org.rascalmpl.vscode.lsp.util.locations.Locations; + import io.usethesource.vallang.ISourceLocation; import io.usethesource.vallang.IValueFactory; @@ -140,7 +143,7 @@ private static boolean readonly(ISourceLocation loc) throws IOException { } @JsonRequest("rascal/filesystem/stat") - default CompletableFuture stat(URIParameter uri) { + default CompletableFuture stat(ISourceLocationRequest uri) { return CompletableFuture.supplyAsync(() -> { try { ISourceLocation loc = uri.getLocation(); @@ -169,7 +172,7 @@ default CompletableFuture stat(URIParameter uri) { } @JsonRequest("rascal/filesystem/readDirectory") - default CompletableFuture readDirectory(URIParameter uri) { + default CompletableFuture readDirectory(ISourceLocationRequest uri) { return CompletableFuture.supplyAsync(() -> { try { ISourceLocation loc = uri.getLocation(); @@ -185,7 +188,7 @@ default CompletableFuture readDirectory(URIParameter uri) { } @JsonRequest("rascal/filesystem/createDirectory") - default CompletableFuture createDirectory(URIParameter uri) { + default CompletableFuture createDirectory(ISourceLocationRequest uri) { return CompletableFuture.runAsync(() -> { try { reg.mkDirectory(uri.getLocation()); @@ -196,7 +199,7 @@ default CompletableFuture createDirectory(URIParameter uri) { } @JsonRequest("rascal/filesystem/readFile") - default CompletableFuture readFile(URIParameter uri) { + default CompletableFuture readFile(ISourceLocationRequest uri) { return CompletableFuture.supplyAsync(() -> { try (InputStream source = new Base64InputStream(reg.getInputStream(uri.getLocation()), true)) { return new LocationContent(new String(source.readAllBytes(), StandardCharsets.US_ASCII)); @@ -285,7 +288,7 @@ public DeleteParameters(String uri, boolean recursive) { } public ISourceLocation getLocation() throws URISyntaxException { - return new URIParameter(uri).getLocation(); + return Locations.toCheckedLoc(uri); } public boolean isRecursive() { @@ -305,11 +308,11 @@ public RenameParameters(String oldUri, String newUri, boolean overwrite) { } public ISourceLocation getOldLocation() throws URISyntaxException { - return new URIParameter(oldUri).getLocation(); + return Locations.toCheckedLoc(oldUri); } public ISourceLocation getNewLocation() throws URISyntaxException { - return new URIParameter(newUri).getLocation(); + return Locations.toCheckedLoc(newUri); } public boolean isOverwrite() { @@ -329,7 +332,7 @@ public WatchParameters(String uri, boolean recursive, String[] excludes) { } public ISourceLocation getLocation() throws URISyntaxException { - return new URIParameter(uri).getLocation(); + return Locations.toCheckedLoc(uri); } public String[] getExcludes() { @@ -470,7 +473,7 @@ public FileChangeType getType() { } public ISourceLocation getLocation() throws URISyntaxException { - return new URIParameter(uri).getLocation(); + return Locations.toCheckedLoc(uri); } } @@ -569,22 +572,6 @@ public String getContent() { } } - public static class URIParameter { - @NonNull private final String uri; - - public URIParameter(@NonNull String uri) { - this.uri = uri; - } - - public String getUri() { - return uri; - } - - public ISourceLocation getLocation() throws URISyntaxException { - return Locations.toCheckedLoc(uri); - } - } - public static class WriteFileParameters { @NonNull private final String uri; @NonNull private final String content; @@ -603,7 +590,7 @@ public String getUri() { } public ISourceLocation getLocation() throws URISyntaxException { - return new URIParameter(uri).getLocation(); + return Locations.toCheckedLoc(uri); } public String getContent() { diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/ISourceLocationRequest.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/ISourceLocationRequest.java index f2242e93f..257be57af 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/ISourceLocationRequest.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/ISourceLocationRequest.java @@ -26,6 +26,8 @@ */ package org.rascalmpl.vscode.lsp.uri.jsonrpc.messages; +import java.net.URISyntaxException; + import org.checkerframework.checker.nullness.qual.Nullable; import org.eclipse.lsp4j.jsonrpc.validation.NonNull; import org.rascalmpl.vscode.lsp.util.locations.Locations; @@ -61,4 +63,8 @@ public String getUri() { return uri; } + public ISourceLocation getLocation() throws URISyntaxException { + return Locations.toCheckedLoc(uri); + } + } From 9c50d7cdfa33fb7161ba88b583ea8161ac1a8d75 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 5 Dec 2025 11:48:12 +0100 Subject: [PATCH 002/149] Unified delete/remove --- .../vscode/lsp/IRascalFileSystemServices.java | 22 ++----------------- .../vscode/lsp/uri/FallbackResolver.java | 2 +- .../uri/jsonrpc/VSCodeUriResolverServer.java | 2 +- .../src/fs/RascalFileSystemProviders.ts | 3 ++- .../src/fs/VSCodeURIResolver.ts | 21 +++++++++++------- 5 files changed, 19 insertions(+), 31 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java index 9ea9f1655..7cd4cd77e 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java @@ -241,11 +241,11 @@ default CompletableFuture writeFile(WriteFileParameters params) { } @JsonRequest("rascal/filesystem/delete") - default CompletableFuture delete(DeleteParameters params) { + default CompletableFuture delete(ISourceLocationRequest params, boolean recursive) { return CompletableFuture.runAsync(() -> { try { ISourceLocation loc = params.getLocation(); - reg.remove(loc, params.isRecursive()); + reg.remove(loc, recursive); } catch (IOException | URISyntaxException e) { throw new CompletionException(e); } @@ -278,24 +278,6 @@ default CompletableFuture fileSystemSchemes() { default void onDidChangeFile(FileChangeEvent event) { } - public static class DeleteParameters { - private final String uri; - private final boolean recursive; - - public DeleteParameters(String uri, boolean recursive) { - this.uri = uri; - this.recursive = recursive; - } - - public ISourceLocation getLocation() throws URISyntaxException { - return Locations.toCheckedLoc(uri); - } - - public boolean isRecursive() { - return recursive; - } - } - public static class RenameParameters { private final String oldUri; private final String newUri; diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java index a722f327c..3026298a5 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java @@ -262,7 +262,7 @@ public void mkDirectory(ISourceLocation uri) throws IOException { @Override public void remove(ISourceLocation uri) throws IOException { - call(s -> s.remove(param(uri))); + call(s -> s.remove(param(uri), false)); cachedDirectoryListing.invalidate(uri); cachedDirectoryListing.invalidate(URIUtil.getParentLocation(uri)); } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverServer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverServer.java index 722d68259..1c5bda09e 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverServer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverServer.java @@ -108,7 +108,7 @@ default CompletableFuture mkDirectory(ISourceLocationRequest req) { } @JsonRequest("rascal/vfs/output/remove") - default CompletableFuture remove(ISourceLocationRequest req) { + default CompletableFuture remove(ISourceLocationRequest req, boolean recursive) { throw new UnsupportedOperationException(); } diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts b/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts index 321287416..eb1aea79a 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts @@ -49,6 +49,7 @@ export class RascalFileSystemProvider implements vscode.FileSystemProvider { sendRequest(uri : vscode.Uri, method: string): Promise; sendRequest(uri : vscode.Uri, method: string, param: A): Promise; + sendRequest(uri : vscode.Uri, method: string, param0: A0, param1: A1): Promise; sendRequest(uri : vscode.Uri, method: string, param?: A): Promise { return this.client.sendRequest(method, param ?? { uri: uri.toString()} ) .catch((r: ResponseError) => { @@ -138,7 +139,7 @@ export class RascalFileSystemProvider implements vscode.FileSystemProvider { } delete(uri: vscode.Uri, options: { recursive: boolean; }): void | Thenable { - return this.sendRequest(uri, "rascal/filesystem/delete", {uri: uri.toString(), recursive: options.recursive}); + return this.sendRequest(uri, "rascal/filesystem/delete", uri.toString(), options.recursive); } rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean; }): void | Thenable { diff --git a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts index e776ec377..a181ea489 100644 --- a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts +++ b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts @@ -82,20 +82,25 @@ function connectInputHandler(connection: rpc.MessageConnection, handler: ISource interface ISourceLocationOutput { writeFile(req: WriteFileRequest ): Promise; mkDirectory(req: ISourceLocationRequest): Promise; - remove(req: ISourceLocationRequest): Promise; + remove(req: ISourceLocationRequest, recursive: boolean): Promise; rename(req: RenameRequest): Promise; } function connectOutputHandler(connection: rpc.MessageConnection, handler: ISourceLocationOutput, toClear: Disposable[]) { - function req (method: string, h: rpc.RequestHandler1) { + function req1 (method: string, h: rpc.RequestHandler1) { toClear.push(connection.onRequest( new rpc.RequestType1("rascal/vfs/output/" + method), h.bind(handler))); } - req("writeFile", handler.writeFile); - req("mkDirectory", handler.mkDirectory); - req("remove", handler.remove); - req("rename", handler.rename); + function req2 (method: string, h: rpc.RequestHandler2) { + toClear.push(connection.onRequest( + new rpc.RequestType2("rascal/vfs/output/" + method), + h.bind(handler))); + } + req1("writeFile", handler.writeFile); + req1("mkDirectory", handler.mkDirectory); + req2("remove", handler.remove); + req1("rename", handler.rename); } // Rascal's interface reduce to a subset we can support @@ -419,9 +424,9 @@ class ResolverClient implements VSCodeResolverServer, Disposable { this.logger.trace("[VFS] mkDirectory: ", req.uri); return asyncVoidCatcher(this.fs.createDirectory(this.toUri(req))); } - async remove(req: ISourceLocationRequest): Promise { + async remove(req: ISourceLocationRequest, recursive: boolean): Promise { this.logger.trace("[VFS] remove: ", req.uri); - return asyncVoidCatcher(this.fs.delete(this.toUri(req))); + return asyncVoidCatcher(this.fs.delete(this.toUri(req), {"recursive" : recursive})); } async rename(req: RenameRequest): Promise { this.logger.trace("[VFS] rename: ", req.from, req.to); From 698fd939c024c92b1a3b2c002d9dbc98777162f8 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 5 Dec 2025 11:51:47 +0100 Subject: [PATCH 003/149] Unified rename --- .../vscode/lsp/IRascalFileSystemServices.java | 31 +++---------------- .../uri/jsonrpc/messages/RenameRequest.java | 15 ++++++--- 2 files changed, 14 insertions(+), 32 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java index 7cd4cd77e..e1842abec 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java @@ -60,6 +60,7 @@ import org.rascalmpl.uri.UnsupportedSchemeException; import org.rascalmpl.values.IRascalValueFactory; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.ISourceLocationRequest; +import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.RenameRequest; import org.rascalmpl.vscode.lsp.util.NamedThreadPool; import org.rascalmpl.vscode.lsp.util.locations.Locations; @@ -253,11 +254,11 @@ default CompletableFuture delete(ISourceLocationRequest params, boolean re } @JsonRequest("rascal/filesystem/rename") - default CompletableFuture rename(RenameParameters params) { + default CompletableFuture rename(RenameRequest params) { return CompletableFuture.runAsync(() -> { try { - ISourceLocation oldLoc = params.getOldLocation(); - ISourceLocation newLoc = params.getNewLocation(); + ISourceLocation oldLoc = params.getFromLocation(); + ISourceLocation newLoc = params.getToLocation(); reg.rename(oldLoc, newLoc, params.isOverwrite()); } catch (IOException | URISyntaxException e) { throw new CompletionException(e); @@ -278,30 +279,6 @@ default CompletableFuture fileSystemSchemes() { default void onDidChangeFile(FileChangeEvent event) { } - public static class RenameParameters { - private final String oldUri; - private final String newUri; - private final boolean overwrite; - - public RenameParameters(String oldUri, String newUri, boolean overwrite) { - this.oldUri = oldUri; - this.newUri = newUri; - this.overwrite = overwrite; - } - - public ISourceLocation getOldLocation() throws URISyntaxException { - return Locations.toCheckedLoc(oldUri); - } - - public ISourceLocation getNewLocation() throws URISyntaxException { - return Locations.toCheckedLoc(newUri); - } - - public boolean isOverwrite() { - return overwrite; - } - } - public static class WatchParameters { private final String uri; private final boolean recursive; diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/RenameRequest.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/RenameRequest.java index f8ab7e1a5..cdb7f8541 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/RenameRequest.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/RenameRequest.java @@ -26,6 +26,7 @@ */ package org.rascalmpl.vscode.lsp.uri.jsonrpc.messages; +import java.net.URISyntaxException; import java.util.Objects; import org.checkerframework.checker.nullness.qual.Nullable; import org.eclipse.lsp4j.jsonrpc.validation.NonNull; @@ -41,10 +42,6 @@ public class RenameRequest { private boolean overwrite; - @SuppressWarnings("initialization.fields.uninitialized") - public RenameRequest() { - } - public RenameRequest(String from, String to, boolean overwrite) { this.from = from; this.to = to; @@ -65,10 +62,18 @@ public String getTo() { return to; } - public boolean getOverwrite() { + public boolean isOverwrite() { return overwrite; } + public ISourceLocation getFromLocation() throws URISyntaxException { + return Locations.toCheckedLoc(from); + } + + public ISourceLocation getToLocation() throws URISyntaxException { + return Locations.toCheckedLoc(to); + } + @Override public boolean equals(@Nullable Object obj) { if (obj instanceof RenameRequest) { From 70998943c2a6bf2464ac0ba564d4b6015ce78889 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 5 Dec 2025 11:54:40 +0100 Subject: [PATCH 004/149] Unified writeFile --- .../vscode/lsp/IRascalFileSystemServices.java | 37 +------------------ .../jsonrpc/messages/WriteFileRequest.java | 32 ++++++++++++++-- .../src/fs/RascalFileSystemProviders.ts | 1 + 3 files changed, 31 insertions(+), 39 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java index e1842abec..e1b97e6c0 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java @@ -61,6 +61,7 @@ import org.rascalmpl.values.IRascalValueFactory; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.ISourceLocationRequest; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.RenameRequest; +import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.WriteFileRequest; import org.rascalmpl.vscode.lsp.util.NamedThreadPool; import org.rascalmpl.vscode.lsp.util.locations.Locations; @@ -211,7 +212,7 @@ default CompletableFuture readFile(ISourceLocationRequest uri) } @JsonRequest("rascal/filesystem/writeFile") - default CompletableFuture writeFile(WriteFileParameters params) { + default CompletableFuture writeFile(WriteFileRequest params) { return CompletableFuture.runAsync(() -> { try { ISourceLocation loc = params.getLocation(); @@ -531,40 +532,6 @@ public String getContent() { } } - public static class WriteFileParameters { - @NonNull private final String uri; - @NonNull private final String content; - private final boolean create; - private final boolean overwrite; - - public WriteFileParameters(@NonNull String uri, @NonNull String content, boolean create, boolean overwrite) { - this.uri = uri; - this.content = content; - this.create = create; - this.overwrite = overwrite; - } - - public String getUri() { - return uri; - } - - public ISourceLocation getLocation() throws URISyntaxException { - return Locations.toCheckedLoc(uri); - } - - public String getContent() { - return content; - } - - public boolean isCreate() { - return create; - } - - public boolean isOverwrite() { - return overwrite; - } - } - /** Maps common exceptions to FileSystemError in VS Code */ public static class VSCodeFSError extends ResponseErrorException { public VSCodeFSError(Exception original) { diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/WriteFileRequest.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/WriteFileRequest.java index 969fb92cf..5fc28d1d7 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/WriteFileRequest.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/WriteFileRequest.java @@ -34,20 +34,34 @@ public class WriteFileRequest extends ISourceLocationRequest { @NonNull - private String content; + private final String content; - private boolean append; + private final boolean append; + private final boolean create; + private final boolean overwrite; - public WriteFileRequest(@NonNull String uri, @NonNull String content, @NonNull boolean append) { + public WriteFileRequest(@NonNull String uri, @NonNull String content, boolean append) { super(uri); this.content = content; this.append = append; + this.create = true; + this.overwrite = !append; } public WriteFileRequest(ISourceLocation loc, String content, boolean append) { super(loc); this.content = content; this.append = append; + this.create = true; + this.overwrite = !append; + } + + public WriteFileRequest(@NonNull String uri, @NonNull String content, boolean append, boolean create, boolean overwite) { + super(uri); + this.content = content; + this.append = append; + this.create = create; + this.overwrite = overwite; } public String getContent() { @@ -58,13 +72,23 @@ public boolean getAppend() { return append; } + public boolean isCreate() { + return create; + } + + public boolean isOverwrite() { + return overwrite; + } + @Override public boolean equals(@Nullable Object obj) { if (obj instanceof WriteFileRequest) { var other = (WriteFileRequest)obj; return super.equals(obj) && content.equals(other.content) - && append == other.append; + && append == other.append + && create == other.create + && overwrite == other.overwrite; } return false; } diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts b/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts index eb1aea79a..a38fdf4e5 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts @@ -132,6 +132,7 @@ export class RascalFileSystemProvider implements vscode.FileSystemProvider { writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean; }): void | Thenable { return this.sendRequest(uri, "rascal/filesystem/writeFile", { uri: uri.toString(), + append: false, create:options.create, overwrite:options.overwrite, content: Buffer.from(content).toString("base64") From 9f959ebaee4b9b66e6b80966ae5226b8726c9d28 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 5 Dec 2025 11:55:30 +0100 Subject: [PATCH 005/149] Towards unification of watch --- .../vscode/lsp/IRascalFileSystemServices.java | 27 ++----------------- .../uri/jsonrpc/messages/WatchRequest.java | 23 +++++++++++++--- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java index e1b97e6c0..5d4f25453 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java @@ -61,6 +61,7 @@ import org.rascalmpl.values.IRascalValueFactory; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.ISourceLocationRequest; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.RenameRequest; +import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.WatchRequest; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.WriteFileRequest; import org.rascalmpl.vscode.lsp.util.NamedThreadPool; import org.rascalmpl.vscode.lsp.util.locations.Locations; @@ -94,7 +95,7 @@ default CompletableFuture resolveLocation(SourceLocation loc) { } @JsonRequest("rascal/filesystem/watch") - default CompletableFuture watch(WatchParameters params) { + default CompletableFuture watch(WatchRequest params) { return CompletableFuture.runAsync(() -> { try { ISourceLocation loc = params.getLocation(); @@ -280,30 +281,6 @@ default CompletableFuture fileSystemSchemes() { default void onDidChangeFile(FileChangeEvent event) { } - public static class WatchParameters { - private final String uri; - private final boolean recursive; - private final String[] excludes; - - public WatchParameters(String uri, boolean recursive, String[] excludes) { - this.uri = uri; - this.recursive = recursive; - this.excludes = excludes; - } - - public ISourceLocation getLocation() throws URISyntaxException { - return Locations.toCheckedLoc(uri); - } - - public String[] getExcludes() { - return excludes; - } - - public boolean isRecursive() { - return recursive; - } - } - public static class SourceLocation { @NonNull private final String uri; private final int @Nullable[] offsetLength; diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/WatchRequest.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/WatchRequest.java index aceeabb08..5c4196b29 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/WatchRequest.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/WatchRequest.java @@ -26,6 +26,7 @@ */ package org.rascalmpl.vscode.lsp.uri.jsonrpc.messages; +import java.util.Arrays; import java.util.Objects; import org.checkerframework.checker.nullness.qual.Nullable; import org.eclipse.lsp4j.jsonrpc.validation.NonNull; @@ -38,16 +39,27 @@ public class WatchRequest extends ISourceLocationRequest { private boolean recursive; + private final String[] excludes; + public WatchRequest(ISourceLocation loc, boolean recursive, String watcher) { super(loc); this.recursive = recursive; this.watcher = watcher; + this.excludes = new String[0]; } - public WatchRequest(@NonNull String uri, @NonNull boolean recursive, @NonNull String watcher) { + public WatchRequest(@NonNull String uri, boolean recursive, @NonNull String watcher) { super(uri); this.recursive = recursive; this.watcher = watcher; + this.excludes = new String[0]; + } + + public WatchRequest(String uri, boolean recursive, String[] excludes) { + super(uri); + this.recursive = recursive; + this.watcher = ""; + this.excludes = excludes; } public String getWatcher() { @@ -58,20 +70,25 @@ public boolean isRecursive() { return recursive; } + public String[] getExcludes() { + return excludes; + } + @Override public boolean equals(@Nullable Object obj) { if (obj instanceof WatchRequest) { var other = (WatchRequest)obj; return super.equals(other) && other.recursive == recursive - && Objects.equals(watcher, other.watcher); + && Objects.equals(watcher, other.watcher) + && Arrays.equals(excludes, other.excludes); } return false; } @Override public int hashCode() { - return Objects.hash(super.hashCode(), watcher, recursive); + return Objects.hash(super.hashCode(), watcher, recursive, excludes); } } From 1049763ff76633da28cd8836f9b5eda1b1563850 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 5 Dec 2025 12:13:46 +0100 Subject: [PATCH 006/149] Unified return type of stat --- .../vscode/lsp/IRascalFileSystemServices.java | 59 ++--------------- .../messages/FileAttributesResult.java | 63 +++++++++++++++++-- 2 files changed, 64 insertions(+), 58 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java index 5d4f25453..1f2f2b9a3 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java @@ -59,6 +59,9 @@ import org.rascalmpl.uri.URIUtil; import org.rascalmpl.uri.UnsupportedSchemeException; import org.rascalmpl.values.IRascalValueFactory; +import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.FileAttributesResult; +import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.FileAttributesResult.FilePermission; +import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.FileAttributesResult.FileType; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.ISourceLocationRequest; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.RenameRequest; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.WatchRequest; @@ -146,7 +149,7 @@ private static boolean readonly(ISourceLocation loc) throws IOException { } @JsonRequest("rascal/filesystem/stat") - default CompletableFuture stat(ISourceLocationRequest uri) { + default CompletableFuture stat(ISourceLocationRequest uri) { return CompletableFuture.supplyAsync(() -> { try { ISourceLocation loc = uri.getLocation(); @@ -156,7 +159,7 @@ default CompletableFuture stat(ISourceLocationRequest uri) { var created = reg.created(loc); var lastModified = reg.lastModified(loc); if (reg.isDirectory(loc)) { - return new FileStat(FileType.Directory, created, lastModified, 0, null); + return new FileAttributesResult(true, FileType.Directory, created, lastModified, 0, null); } long size = 0; if (reg.supportsReadableFileChannel(loc)) { @@ -167,7 +170,7 @@ default CompletableFuture stat(ISourceLocationRequest uri) { else { size = Prelude.__getFileSize(IRascalValueFactory.getInstance(), loc).longValue(); } - return new FileStat(FileType.File, created, lastModified, size, readonly(loc) ? FilePermission.Readonly : null); + return new FileAttributesResult(true, FileType.File, created, lastModified, size, readonly(loc) ? FilePermission.Readonly : null); } catch (IOException | URISyntaxException | RuntimeException e) { throw new VSCodeFSError(e); } @@ -429,56 +432,6 @@ public int getValue() { } } - // The fields of are only used on the TS side - @SuppressWarnings("unused") - public static class FileStat { - private final FileType type; - private final long ctime; - private final long mtime; - private final long size; - - private @Nullable FilePermission permissions; - - public FileStat(FileType type, long ctime, long mtime, long size, @Nullable FilePermission permissions) { - this.type = type; - this.ctime = ctime; - this.mtime = mtime; - this.size = size; - this.permissions = permissions; - } - - } - - public enum FileType { - Unknown(0), File(1), Directory(2), SymbolicLink(64); - - private final int value; - - private FileType(int val) { - assert val == 0 || val == 1 || val == 2 || val == 64; - this.value = val; - } - - public int getValue() { - return value; - } - } - - // this enum models the enum inside vscode, which in the future might become an enum flag - // in that case we have to solve that - public enum FilePermission { - Readonly(1); - private final int value; - private FilePermission(int val) { - assert val == 1; - this.value = val; - } - - public int getValue() { - return value; - } - } - public static class FileWithType { @NonNull private final String name; @NonNull private final FileType type; diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/FileAttributesResult.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/FileAttributesResult.java index ee5effcf3..45299dc96 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/FileAttributesResult.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/FileAttributesResult.java @@ -33,13 +33,22 @@ public class FileAttributesResult { private boolean exists; - private int type; + private FileType type; private long ctime; private long mtime; - private int size; - private int permissions; + private long size; + @Nullable private FilePermission permissions; - public FileAttributesResult(boolean exists, int type, long ctime, long mtime, int size, int permissions) { + public FileAttributesResult(boolean exists, int type, long ctime, long mtime, long size, int permissions) { + this.exists = exists; + this.type = FileType.fromValue(type); + this.ctime = ctime; + this.mtime = mtime; + this.size = size; + this.permissions = FilePermission.fromValue(permissions); + } + + public FileAttributesResult(boolean exists, FileType type, long ctime, long mtime, long size, FilePermission permissions) { this.exists = exists; this.type = type; this.ctime = ctime; @@ -51,7 +60,7 @@ public FileAttributesResult(boolean exists, int type, long ctime, long mtime, in public FileAttributesResult() {} public FileAttributes getFileAttributes() { - return new FileAttributes(exists, (type & 1) == 1, ctime, mtime, true, (permissions & 1) == 0, size); + return new FileAttributes(exists, type == FileType.File, ctime, mtime, true, permissions != null, size); } @Override @@ -78,4 +87,48 @@ public String toString() { return "FileStatResult [exists="+ exists + " type=" + type + " ctime=" + ctime + " mtime=" + mtime + " size=" + size + " permissions=" + permissions + "]"; } + public enum FileType { + Unknown(0), File(1), Directory(2), SymbolicLink(64); + + private final int value; + + private FileType(int val) { + assert val == 0 || val == 1 || val == 2 || val == 64; + this.value = val; + } + + public int getValue() { + return value; + } + + public static FileType fromValue(int val) { + switch (val) { + case 0: return Unknown; + case 1: return File; + case 2: return Directory; + case 64: return SymbolicLink; + default: throw new IllegalArgumentException("Unknown FileType value " + val); + } + } + } + + public enum FilePermission { + Readonly(1); + private final int value; + private FilePermission(int val) { + assert val == 1; + this.value = val; + } + + public int getValue() { + return value; + } + + public static FilePermission fromValue(int val) { + switch (val) { + case 1: return Readonly; + default: return null; + } + } + } } From 51eaec6cbc402792544a76f7db5e7d52d6574116 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 5 Dec 2025 13:30:22 +0100 Subject: [PATCH 007/149] Eliminated ReadFileResult in favor of a plain string --- .../vscode/lsp/IRascalFileSystemServices.java | 4 +- .../vscode/lsp/uri/FallbackResolver.java | 2 +- .../uri/jsonrpc/VSCodeUriResolverServer.java | 3 +- .../uri/jsonrpc/messages/ReadFileResult.java | 62 ------------------- .../src/fs/VSCodeURIResolver.ts | 20 ++---- 5 files changed, 8 insertions(+), 83 deletions(-) delete mode 100644 rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/ReadFileResult.java diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java index 1f2f2b9a3..e80ff5868 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java @@ -205,10 +205,10 @@ default CompletableFuture createDirectory(ISourceLocationRequest uri) { } @JsonRequest("rascal/filesystem/readFile") - default CompletableFuture readFile(ISourceLocationRequest uri) { + default CompletableFuture readFile(ISourceLocationRequest uri) { return CompletableFuture.supplyAsync(() -> { try (InputStream source = new Base64InputStream(reg.getInputStream(uri.getLocation()), true)) { - return new LocationContent(new String(source.readAllBytes(), StandardCharsets.US_ASCII)); + return new String(source.readAllBytes(), StandardCharsets.US_ASCII); } catch (IOException | URISyntaxException | RuntimeException e) { throw new VSCodeFSError(e); } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java index 3026298a5..f4f860229 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java @@ -134,7 +134,7 @@ private ISourceLocationRequest param(ISourceLocation uri) { @Override public InputStream getInputStream(ISourceLocation uri) throws IOException { - var fileBody = call(s -> s.readFile(param(uri))).getContents(); + var fileBody = call(s -> s.readFile(param(uri))); // TODO: do the decoding in a stream, to avoid the extra intermediate // byte array diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverServer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverServer.java index 1c5bda09e..ce191174f 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverServer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverServer.java @@ -34,7 +34,6 @@ import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.FileAttributesResult; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.ISourceLocationRequest; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.NumberResult; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.ReadFileResult; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.RenameRequest; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.TimestampResult; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.WatchRequest; @@ -42,7 +41,7 @@ public interface VSCodeUriResolverServer { @JsonRequest("rascal/vfs/input/readFile") - default CompletableFuture readFile(ISourceLocationRequest req) { + default CompletableFuture readFile(ISourceLocationRequest req) { throw new UnsupportedOperationException(); } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/ReadFileResult.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/ReadFileResult.java deleted file mode 100644 index 2c8d925db..000000000 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/ReadFileResult.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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.uri.jsonrpc.messages; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.eclipse.lsp4j.jsonrpc.validation.NonNull; - -public class ReadFileResult { - - @NonNull private String contents; - - public ReadFileResult(@NonNull String contents) { - this.contents = contents; - } - - public String getContents() { - return contents; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (obj instanceof ReadFileResult) { - return contents.equals(((ReadFileResult)obj).contents); - } - return false; - } - - @Override - public int hashCode() { - return contents.hashCode(); - } - - @Override - public String toString() { - return "ReadFileResult [contents=" + contents + "]"; - } - -} diff --git a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts index a181ea489..a1f3cdac6 100644 --- a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts +++ b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts @@ -45,7 +45,7 @@ interface VSCodeResolverServer extends ISourceLocationInput, ISourceLocationOutp // Rascal's interface reduce to a subset we can support interface ISourceLocationInput { - readFile(req: ISourceLocationRequest): Promise; + readFile(req: ISourceLocationRequest): Promise; exists(req: ISourceLocationRequest): Promise; lastModified(req: ISourceLocationRequest): Promise; created(req: ISourceLocationRequest): Promise; @@ -65,7 +65,7 @@ function connectInputHandler(connection: rpc.MessageConnection, handler: ISource new rpc.RequestType1("rascal/vfs/input/" + method), h.bind(handler))); } - req("readFile", handler.readFile); + req("readFile", handler.readFile); req("exists", handler.exists); req("lastModified", handler.lastModified); req("created", handler.created); @@ -138,13 +138,6 @@ interface ISourceLocationRequest { uri: ISourceLocation; } -interface ReadFileResult { - /** - * base64 encoding of file - */ - contents: string; -} - export interface BooleanResult { result: boolean; } @@ -292,14 +285,9 @@ class ResolverClient implements VSCodeResolverServer, Disposable { - async readFile(req: ISourceLocationRequest): Promise { + async readFile(req: ISourceLocationRequest): Promise { this.logger.trace("[VFS] readFile: ", req.uri); - return asyncCatcher(async () => { - errorCode: 0, - contents: Buffer.from( - await this.fs.readFile(this.toUri(req)) - ).toString("base64") - }); + return asyncCatcher(async () => Buffer.from(await this.fs.readFile(this.toUri(req))).toString("base64")); } isRascalNative(req: ISourceLocationRequest | vscode.Uri) : boolean { From e1d701c0307c0e0b3c3b47e8ea39c5ad8ad95383 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 5 Dec 2025 14:13:49 +0100 Subject: [PATCH 008/149] Eliminated BooleanResult in favor of a plain boolean --- .../vscode/lsp/uri/FallbackResolver.java | 10 +-- .../uri/jsonrpc/VSCodeUriResolverServer.java | 11 ++-- .../uri/jsonrpc/messages/BooleanResult.java | 62 ------------------- .../src/fs/VSCodeURIResolver.ts | 47 ++++++-------- 4 files changed, 30 insertions(+), 100 deletions(-) delete mode 100644 rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/BooleanResult.java diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java index f4f860229..0d25d1748 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java @@ -146,7 +146,7 @@ public InputStream getInputStream(ISourceLocation uri) throws IOException { @Override public boolean exists(ISourceLocation uri) { try { - return call(s -> s.exists(param(uri))).getResult(); + return call(s -> s.exists(param(uri))); } catch (IOException e) { return false; } @@ -172,7 +172,7 @@ public boolean isDirectory(ISourceLocation uri) { return result; } } - return call(s -> s.isDirectory(param(uri))).getResult(); + return call(s -> s.isDirectory(param(uri))); } catch (IOException e) { return false; } @@ -188,7 +188,7 @@ public boolean isFile(ISourceLocation uri) { return !result; } } - return call(s -> s.isFile(param(uri))).getResult(); + return call(s -> s.isFile(param(uri))); } catch (IOException e) { return false; } @@ -340,11 +340,11 @@ public long size(ISourceLocation uri) throws IOException { @Override public boolean isReadable(ISourceLocation uri) throws IOException { - return call(s -> s.isReadable(param(uri))).getResult(); + return call(s -> s.isReadable(param(uri))); } @Override public boolean isWritable(ISourceLocation uri) throws IOException { - return call(s -> s.isWritable(param(uri))).getResult(); + return call(s -> s.isWritable(param(uri))); } @Override diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverServer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverServer.java index ce191174f..95a54cb84 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverServer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverServer.java @@ -29,7 +29,6 @@ import java.util.concurrent.CompletableFuture; import org.eclipse.lsp4j.jsonrpc.services.JsonRequest; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.BooleanResult; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.DirectoryListingResult; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.FileAttributesResult; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.ISourceLocationRequest; @@ -46,7 +45,7 @@ default CompletableFuture readFile(ISourceLocationRequest req) { } @JsonRequest("rascal/vfs/input/exists") - default CompletableFuture exists(ISourceLocationRequest req) { + default CompletableFuture exists(ISourceLocationRequest req) { throw new UnsupportedOperationException(); } @@ -61,12 +60,12 @@ default CompletableFuture created(ISourceLocationRequest req) { } @JsonRequest("rascal/vfs/input/isDirectory") - default CompletableFuture isDirectory(ISourceLocationRequest req) { + default CompletableFuture isDirectory(ISourceLocationRequest req) { throw new UnsupportedOperationException(); } @JsonRequest("rascal/vfs/input/isFile") - default CompletableFuture isFile(ISourceLocationRequest req) { + default CompletableFuture isFile(ISourceLocationRequest req) { throw new UnsupportedOperationException(); } @@ -86,12 +85,12 @@ default CompletableFuture stat(ISourceLocationRequest req) } @JsonRequest("rascal/vfs/input/isReadable") - default CompletableFuture isReadable(ISourceLocationRequest req) { + default CompletableFuture isReadable(ISourceLocationRequest req) { throw new UnsupportedOperationException(); } @JsonRequest("rascal/vfs/input/isWritable") - default CompletableFuture isWritable(ISourceLocationRequest req) { + default CompletableFuture isWritable(ISourceLocationRequest req) { throw new UnsupportedOperationException(); } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/BooleanResult.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/BooleanResult.java deleted file mode 100644 index 9d6cb6019..000000000 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/BooleanResult.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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.uri.jsonrpc.messages; - -import org.checkerframework.checker.nullness.qual.Nullable; - -public class BooleanResult { - private boolean result; - - public BooleanResult(boolean result) { - this.result = result; - } - - public BooleanResult() {} - - public boolean getResult() { - return result; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (obj instanceof BooleanResult) { - return result == ((BooleanResult)obj).result; - } - return false; - } - - @Override - public int hashCode() { - return result ? 11 : 3; - } - - @Override - public String toString() { - return "BooleanResult [result=" + result + "]"; - } - -} diff --git a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts index a1f3cdac6..34a199cfc 100644 --- a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts +++ b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts @@ -46,16 +46,16 @@ interface VSCodeResolverServer extends ISourceLocationInput, ISourceLocationOutp // Rascal's interface reduce to a subset we can support interface ISourceLocationInput { readFile(req: ISourceLocationRequest): Promise; - exists(req: ISourceLocationRequest): Promise; + exists(req: ISourceLocationRequest): Promise; lastModified(req: ISourceLocationRequest): Promise; created(req: ISourceLocationRequest): Promise; - isDirectory(req: ISourceLocationRequest): Promise; - isFile(req: ISourceLocationRequest): Promise; + isDirectory(req: ISourceLocationRequest): Promise; + isFile(req: ISourceLocationRequest): Promise; list(req: ISourceLocationRequest): Promise; size(req: ISourceLocationRequest): Promise; fileStat(req: ISourceLocationRequest): Promise; - isReadable(req: ISourceLocationRequest): Promise; - isWritable(req: ISourceLocationRequest): Promise; + isReadable(req: ISourceLocationRequest): Promise; + isWritable(req: ISourceLocationRequest): Promise; } @@ -66,16 +66,16 @@ function connectInputHandler(connection: rpc.MessageConnection, handler: ISource h.bind(handler))); } req("readFile", handler.readFile); - req("exists", handler.exists); + req("exists", handler.exists); req("lastModified", handler.lastModified); req("created", handler.created); - req("isDirectory", handler.isDirectory); - req("isFile", handler.isFile); + req("isDirectory", handler.isDirectory); + req("isFile", handler.isFile); req("list", handler.list); req("size", handler.size); req("stat", handler.fileStat); - req("isReadable", handler.isReadable); - req("isWritable", handler.isWritable); + req("isReadable", handler.isReadable); + req("isWritable", handler.isWritable); } // Rascal's interface reduce to a subset we can support @@ -138,11 +138,6 @@ interface ISourceLocationRequest { uri: ISourceLocation; } -export interface BooleanResult { - result: boolean; -} - - export interface TimestampResult { /** * Epoch seconds @@ -296,14 +291,14 @@ class ResolverClient implements VSCodeResolverServer, Disposable { return this.rascalNativeSchemes.has(scheme); } - async exists(req: ISourceLocationRequest): Promise { + async exists(req: ISourceLocationRequest): Promise { this.logger.trace("[VFS] exists: ", req.uri); try { await this.stat(req); - return { result: true }; + return true; } catch (_e) { - return { result: false }; + return false; } } @@ -352,30 +347,28 @@ class ResolverClient implements VSCodeResolverServer, Disposable { return this.numberResult(req, f => f.size); } - private async boolResult(req: ISourceLocationRequest, mapper: (s :vscode.FileStat) => boolean): Promise { - return asyncCatcher(async () => { - result: mapper((await this.stat(req))) - }); + private async boolResult(req: ISourceLocationRequest, mapper: (s :vscode.FileStat) => boolean): Promise { + return asyncCatcher(async () => mapper((await this.stat(req)))); } - isDirectory(req: ISourceLocationRequest): Promise { + isDirectory(req: ISourceLocationRequest): Promise { this.logger.trace("[VFS] isDirectory: ", req.uri); return this.boolResult(req, f => f.type === vscode.FileType.Directory); } - isFile(req: ISourceLocationRequest): Promise { + isFile(req: ISourceLocationRequest): Promise { this.logger.trace("[VFS] isFile: ", req.uri); // TODO: figure out how to handle vscode.FileType.Symlink return this.boolResult(req, f => f.type === vscode.FileType.File); } - isReadable(req: ISourceLocationRequest): Promise { + isReadable(req: ISourceLocationRequest): Promise { this.logger.trace("[VFS] isReadable: ", req.uri); // if we can do a stat, we can read return this.boolResult(req, _ => true); } - async isWritable(req: ISourceLocationRequest): Promise { + async isWritable(req: ISourceLocationRequest): Promise { this.logger.trace("[VFS] isWritable: ", req.uri); const scheme = this.toUri(req).scheme; const writable = this.fs.isWritableFileSystem(scheme); @@ -384,7 +377,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { } if (!writable) { // not a writable file system, so no need to check the uri - return {result : false }; + return false; } return this.boolResult(req, f => f.permissions === undefined || (f.permissions & vscode.FilePermission.Readonly) === 0); } From 7094063d6a96d16cd072e78cdea3728c02eb5f34 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 5 Dec 2025 14:19:00 +0100 Subject: [PATCH 009/149] Eliminated NumberResult and TimestampResult in favor of a plain long --- .../vscode/lsp/uri/FallbackResolver.java | 6 +- .../uri/jsonrpc/VSCodeUriResolverServer.java | 8 +-- .../uri/jsonrpc/messages/NumberResult.java | 62 ------------------ .../uri/jsonrpc/messages/TimestampResult.java | 63 ------------------- .../src/fs/VSCodeURIResolver.ts | 42 +++++-------- 5 files changed, 20 insertions(+), 161 deletions(-) delete mode 100644 rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/NumberResult.java delete mode 100644 rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/TimestampResult.java diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java index 0d25d1748..d13f6e6ee 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java @@ -154,12 +154,12 @@ public boolean exists(ISourceLocation uri) { @Override public long lastModified(ISourceLocation uri) throws IOException { - return TimeUnit.SECONDS.toMillis(call(s -> s.lastModified(param(uri))).getTimestamp()); + return TimeUnit.SECONDS.toMillis(call(s -> s.lastModified(param(uri)))); } @Override public long created(ISourceLocation uri) throws IOException { - return TimeUnit.SECONDS.toMillis(call(s -> s.created(param(uri))).getTimestamp()); + return TimeUnit.SECONDS.toMillis(call(s -> s.created(param(uri)))); } @Override @@ -335,7 +335,7 @@ public TextDocumentState getDocumentState(ISourceLocation file) throws IOExcepti @Override public long size(ISourceLocation uri) throws IOException { - return call(s -> s.size(param(uri))).getResult(); + return call(s -> s.size(param(uri))); } @Override diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverServer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverServer.java index 95a54cb84..4b7e0b602 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverServer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverServer.java @@ -32,9 +32,7 @@ import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.DirectoryListingResult; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.FileAttributesResult; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.ISourceLocationRequest; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.NumberResult; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.RenameRequest; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.TimestampResult; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.WatchRequest; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.WriteFileRequest; @@ -50,12 +48,12 @@ default CompletableFuture exists(ISourceLocationRequest req) { } @JsonRequest("rascal/vfs/input/lastModified") - default CompletableFuture lastModified(ISourceLocationRequest req) { + default CompletableFuture lastModified(ISourceLocationRequest req) { throw new UnsupportedOperationException(); } @JsonRequest("rascal/vfs/input/created") - default CompletableFuture created(ISourceLocationRequest req) { + default CompletableFuture created(ISourceLocationRequest req) { throw new UnsupportedOperationException(); } @@ -75,7 +73,7 @@ default CompletableFuture list(ISourceLocationRequest re } @JsonRequest("rascal/vfs/input/size") - default CompletableFuture size(ISourceLocationRequest req) { + default CompletableFuture size(ISourceLocationRequest req) { throw new UnsupportedOperationException(); } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/NumberResult.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/NumberResult.java deleted file mode 100644 index 4062faf0d..000000000 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/NumberResult.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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.uri.jsonrpc.messages; - -import org.checkerframework.checker.nullness.qual.Nullable; - -public class NumberResult { - private int result; - - public NumberResult(int result) { - this.result = result; - } - - public NumberResult() {} - - public int getResult() { - return result; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (obj instanceof NumberResult) { - return result == ((NumberResult)obj).result; - } - return false; - } - - @Override - public int hashCode() { - return result * 11; - } - - @Override - public String toString() { - return "NumberResult [result=" + result + "]"; - } - -} diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/TimestampResult.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/TimestampResult.java deleted file mode 100644 index b4f24d3dd..000000000 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/TimestampResult.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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.uri.jsonrpc.messages; - -import org.checkerframework.checker.nullness.qual.Nullable; - -public class TimestampResult { - private long timestamp; - - public TimestampResult(long timestamp) { - this.timestamp = timestamp; - } - - public TimestampResult() {} - - public long getTimestamp() { - return timestamp; - } - - @Override - public int hashCode() { - return Long.hashCode(timestamp); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (obj instanceof TimestampResult) { - return timestamp == ((TimestampResult)obj).timestamp; - } - return false; - } - - @Override - public String toString() { - return "TimestampResult [timestamp=" + timestamp + "]"; - } - - -} diff --git a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts index 34a199cfc..34327dc84 100644 --- a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts +++ b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts @@ -47,12 +47,12 @@ interface VSCodeResolverServer extends ISourceLocationInput, ISourceLocationOutp interface ISourceLocationInput { readFile(req: ISourceLocationRequest): Promise; exists(req: ISourceLocationRequest): Promise; - lastModified(req: ISourceLocationRequest): Promise; - created(req: ISourceLocationRequest): Promise; + lastModified(req: ISourceLocationRequest): Promise; + created(req: ISourceLocationRequest): Promise; isDirectory(req: ISourceLocationRequest): Promise; isFile(req: ISourceLocationRequest): Promise; list(req: ISourceLocationRequest): Promise; - size(req: ISourceLocationRequest): Promise; + size(req: ISourceLocationRequest): Promise; fileStat(req: ISourceLocationRequest): Promise; isReadable(req: ISourceLocationRequest): Promise; isWritable(req: ISourceLocationRequest): Promise; @@ -67,12 +67,12 @@ function connectInputHandler(connection: rpc.MessageConnection, handler: ISource } req("readFile", handler.readFile); req("exists", handler.exists); - req("lastModified", handler.lastModified); - req("created", handler.created); + req("lastModified", handler.lastModified); + req("created", handler.created); req("isDirectory", handler.isDirectory); req("isFile", handler.isFile); req("list", handler.list); - req("size", handler.size); + req("size", handler.size); req("stat", handler.fileStat); req("isReadable", handler.isReadable); req("isWritable", handler.isWritable); @@ -138,13 +138,6 @@ interface ISourceLocationRequest { uri: ISourceLocation; } -export interface TimestampResult { - /** - * Epoch seconds - */ - timestamp: number; -} - export interface DirectoryListingResult { entries: string[]; areDirectory: boolean[] @@ -321,28 +314,21 @@ class ResolverClient implements VSCodeResolverServer, Disposable { return this.fs.stat(this.toUri(req)); } - private async timeStampResult(req: ISourceLocationRequest, mapper: (s :vscode.FileStat) => number): Promise { - return asyncCatcher(async () => { - timestamp: mapper((await this.stat(req))) - }); + private async numberResult(req: ISourceLocationRequest, mapper: (s: vscode.FileStat) => number): Promise { + return asyncCatcher(async () => mapper((await this.stat(req)))); } - lastModified(req: ISourceLocationRequest): Promise { + lastModified(req: ISourceLocationRequest): Promise { this.logger.trace("[VFS] lastModified: ", req.uri); - return this.timeStampResult(req, f => f.mtime); - } - created(req: ISourceLocationRequest): Promise { - this.logger.trace("[VFS] created: ", req.uri); - return this.timeStampResult(req, f => f.ctime); + return this.numberResult(req, f => f.mtime); } - private async numberResult(req: ISourceLocationRequest, mapper: (s: vscode.FileStat) => number): Promise { - return asyncCatcher(async () => { - result: mapper((await this.stat(req))) - }); + created(req: ISourceLocationRequest): Promise { + this.logger.trace("[VFS] created: ", req.uri); + return this.numberResult(req, f => f.ctime); } - size(req: ISourceLocationRequest): Promise { + size(req: ISourceLocationRequest): Promise { this.logger.trace("[VFS] size: ", req.uri); return this.numberResult(req, f => f.size); } From a502208644cf173e7cbe2246603a5faeedec0d96 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 9 Dec 2025 16:18:23 +0100 Subject: [PATCH 010/149] Eliminated DirectoryListingResult --- .../vscode/lsp/IRascalFileSystemServices.java | 19 +------ .../vscode/lsp/uri/FallbackResolver.java | 21 ++++---- .../uri/jsonrpc/VSCodeUriResolverServer.java | 4 +- ...ryListingResult.java => FileWithType.java} | 51 +++++-------------- .../src/fs/VSCodeURIResolver.ts | 17 ++----- 5 files changed, 32 insertions(+), 80 deletions(-) rename rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/{DirectoryListingResult.java => FileWithType.java} (55%) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java index e80ff5868..59e97b41c 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java @@ -62,6 +62,7 @@ import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.FileAttributesResult; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.FileAttributesResult.FilePermission; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.FileAttributesResult.FileType; +import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.FileWithType; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.ISourceLocationRequest; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.RenameRequest; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.WatchRequest; @@ -432,24 +433,6 @@ public int getValue() { } } - public static class FileWithType { - @NonNull private final String name; - @NonNull private final FileType type; - - public FileWithType(@NonNull String name, @NonNull FileType type) { - this.name = name; - this.type = type; - } - - public String getName() { - return name; - } - - public FileType getType() { - return type; - } - } - public static class LocationContent { @NonNull private final String content; diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java index d13f6e6ee..807ca56d1 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java @@ -50,6 +50,8 @@ import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import java.util.function.Function; +import java.util.stream.Stream; + import org.checkerframework.checker.initialization.qual.UnderInitialization; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.eclipse.lsp4j.jsonrpc.ResponseErrorException; @@ -63,12 +65,16 @@ import org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeUriResolverClient; import org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeUriResolverServer; import org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeVFS; +import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.FileAttributesResult.FileType; +import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.FileWithType; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.ISourceLocationRequest; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.WriteFileRequest; import org.rascalmpl.vscode.lsp.util.Lazy; + import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import com.google.gson.JsonPrimitive; + import io.usethesource.vallang.ISourceLocation; public class FallbackResolver implements ISourceLocationInputOutput, ISourceLocationWatcher, ILogicalSourceLocationResolver { @@ -197,8 +203,8 @@ public boolean isFile(ISourceLocation uri) { /** * Rascal's current implementions sometimes ask for a directory listing * and then iterate over all the entries checking if they are a directory. - * This is super slow for this jsonrcp, so we store tha last directory listing - * and check insid + * This is super slow for this jsonrcp, so we store the last directory listing + * and check inside */ private final Cache>> cachedDirectoryListing = Caffeine.newBuilder() @@ -211,16 +217,13 @@ public String[] list(ISourceLocation uri) throws IOException { var result = call(s -> s.list(param(uri))); // we store the entries in a cache, for consecutive isDirectory/isFile calls cachedDirectoryListing.put(uri, Lazy.defer(() -> { - var entries = result.getEntries(); - var areDirs = result.getAreDirectory(); - Map lookup = new HashMap<>(entries.length); - assert entries.length == areDirs.length; - for (int i = 0; i < entries.length; i++) { - lookup.put(entries[i], areDirs[i]); + Map lookup = new HashMap<>(result.length); + for (var entry : result) { + lookup.put(entry.getName(), entry.getType().equals(FileType.Directory)); } return lookup; })); - return result.getEntries(); + return Stream.of(result).map(FileWithType::getName).toArray(String[]::new); } @Override diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverServer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverServer.java index 4b7e0b602..20406a802 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverServer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverServer.java @@ -29,8 +29,8 @@ import java.util.concurrent.CompletableFuture; import org.eclipse.lsp4j.jsonrpc.services.JsonRequest; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.DirectoryListingResult; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.FileAttributesResult; +import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.FileWithType; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.ISourceLocationRequest; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.RenameRequest; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.WatchRequest; @@ -68,7 +68,7 @@ default CompletableFuture isFile(ISourceLocationRequest req) { } @JsonRequest("rascal/vfs/input/list") - default CompletableFuture list(ISourceLocationRequest req) { + default CompletableFuture list(ISourceLocationRequest req) { throw new UnsupportedOperationException(); } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/DirectoryListingResult.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/FileWithType.java similarity index 55% rename from rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/DirectoryListingResult.java rename to rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/FileWithType.java index ae0efc5dc..4da308500 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/DirectoryListingResult.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/FileWithType.java @@ -26,48 +26,23 @@ */ package org.rascalmpl.vscode.lsp.uri.jsonrpc.messages; -import java.util.Arrays; -import java.util.Objects; -import org.checkerframework.checker.nullness.qual.Nullable; import org.eclipse.lsp4j.jsonrpc.validation.NonNull; +import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.FileAttributesResult.FileType; -public class DirectoryListingResult { +public class FileWithType { + @NonNull private final String name; + @NonNull private final FileType type; - - @NonNull private String[] entries; - @NonNull private boolean[] areDirectory; - - public DirectoryListingResult(@NonNull String [] entries, @NonNull boolean [] areDirectory) { - this.entries = entries; - this.areDirectory = areDirectory; - } - - public String [] getEntries() { - return entries; - } - - public boolean [] getAreDirectory() { - return areDirectory; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (obj instanceof DirectoryListingResult) { - return Objects.deepEquals(entries, ((DirectoryListingResult)obj).entries) - && Objects.deepEquals(areDirectory, ((DirectoryListingResult)obj).areDirectory) - ; + public FileWithType(@NonNull String name, @NonNull FileType type) { + this.name = name; + this.type = type; } - return false; - } - @Override - public int hashCode() { - return (Arrays.deepHashCode(entries) + 1) + 19 * (Arrays.hashCode(areDirectory) + 1); - } + public String getName() { + return name; + } - @Override - public String toString() { - return "DirectoryListingResult [entries=" + Arrays.toString(entries) + "areDirectory=" +Arrays.toString(areDirectory) + "]"; + public FileType getType() { + return type; + } } - -} diff --git a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts index 34327dc84..5260b02e7 100644 --- a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts +++ b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts @@ -51,7 +51,7 @@ interface ISourceLocationInput { created(req: ISourceLocationRequest): Promise; isDirectory(req: ISourceLocationRequest): Promise; isFile(req: ISourceLocationRequest): Promise; - list(req: ISourceLocationRequest): Promise; + list(req: ISourceLocationRequest): Promise<[string, vscode.FileType][]>; size(req: ISourceLocationRequest): Promise; fileStat(req: ISourceLocationRequest): Promise; isReadable(req: ISourceLocationRequest): Promise; @@ -71,7 +71,7 @@ function connectInputHandler(connection: rpc.MessageConnection, handler: ISource req("created", handler.created); req("isDirectory", handler.isDirectory); req("isFile", handler.isFile); - req("list", handler.list); + req<[string, vscode.FileType][]>("list", handler.list); req("size", handler.size); req("stat", handler.fileStat); req("isReadable", handler.isReadable); @@ -138,11 +138,6 @@ interface ISourceLocationRequest { uri: ISourceLocation; } -export interface DirectoryListingResult { - entries: string[]; - areDirectory: boolean[] -} - export interface NumberResult { result: number; } @@ -368,14 +363,10 @@ class ResolverClient implements VSCodeResolverServer, Disposable { return this.boolResult(req, f => f.permissions === undefined || (f.permissions & vscode.FilePermission.Readonly) === 0); } - async list(req: ISourceLocationRequest): Promise { + async list(req: ISourceLocationRequest): Promise<[string, vscode.FileType][]> { this.logger.trace("[VFS] list: ", req.uri); return asyncCatcher(async () => { - const entries = await this.fs.readDirectory(this.toUri(req)); - return { - entries: entries.map(([entry, _type], _index) => entry), - areDirectory: entries.map(([_entry, type], _index) => type === vscode.FileType.Directory) - }; + return await this.fs.readDirectory(this.toUri(req)); }); } From 37cfa60e735e8a9feccb7fdeb06c8ce6760b9762 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 9 Dec 2025 16:19:41 +0100 Subject: [PATCH 011/149] Removed now unreferenced interface --- rascal-vscode-extension/src/fs/VSCodeURIResolver.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts index 5260b02e7..de9729227 100644 --- a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts +++ b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts @@ -138,10 +138,6 @@ interface ISourceLocationRequest { uri: ISourceLocation; } -export interface NumberResult { - result: number; -} - export interface FileAttributesResult { exists : boolean; type: vscode.FileType; From e19e95bd572a790c766cab028925ead23e071487 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 9 Dec 2025 16:20:05 +0100 Subject: [PATCH 012/149] Nullness fix --- .../lsp/uri/jsonrpc/messages/FileAttributesResult.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/FileAttributesResult.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/FileAttributesResult.java index 45299dc96..80b9b1f80 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/FileAttributesResult.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/FileAttributesResult.java @@ -37,7 +37,7 @@ public class FileAttributesResult { private long ctime; private long mtime; private long size; - @Nullable private FilePermission permissions; + private @Nullable FilePermission permissions; public FileAttributesResult(boolean exists, int type, long ctime, long mtime, long size, int permissions) { this.exists = exists; @@ -48,7 +48,7 @@ public FileAttributesResult(boolean exists, int type, long ctime, long mtime, lo this.permissions = FilePermission.fromValue(permissions); } - public FileAttributesResult(boolean exists, FileType type, long ctime, long mtime, long size, FilePermission permissions) { + public FileAttributesResult(boolean exists, FileType type, long ctime, long mtime, long size, @Nullable FilePermission permissions) { this.exists = exists; this.type = type; this.ctime = ctime; @@ -57,8 +57,6 @@ public FileAttributesResult(boolean exists, FileType type, long ctime, long mtim this.permissions = permissions; } - public FileAttributesResult() {} - public FileAttributes getFileAttributes() { return new FileAttributes(exists, type == FileType.File, ctime, mtime, true, permissions != null, size); } @@ -124,7 +122,7 @@ public int getValue() { return value; } - public static FilePermission fromValue(int val) { + public static @Nullable FilePermission fromValue(int val) { switch (val) { case 1: return Readonly; default: return null; From b06a5dfcbf05d544d44067b38ae2be3fa438473c Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 7 Jan 2026 15:48:12 +0100 Subject: [PATCH 013/149] Ported FileAttributesResult to rascal --- .../messages/FileAttributesResult.java | 132 ------------------ 1 file changed, 132 deletions(-) delete mode 100644 rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/FileAttributesResult.java diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/FileAttributesResult.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/FileAttributesResult.java deleted file mode 100644 index 80b9b1f80..000000000 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/FileAttributesResult.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * 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.uri.jsonrpc.messages; - -import java.util.Objects; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.rascalmpl.uri.FileAttributes; - -public class FileAttributesResult { - private boolean exists; - private FileType type; - private long ctime; - private long mtime; - private long size; - private @Nullable FilePermission permissions; - - public FileAttributesResult(boolean exists, int type, long ctime, long mtime, long size, int permissions) { - this.exists = exists; - this.type = FileType.fromValue(type); - this.ctime = ctime; - this.mtime = mtime; - this.size = size; - this.permissions = FilePermission.fromValue(permissions); - } - - public FileAttributesResult(boolean exists, FileType type, long ctime, long mtime, long size, @Nullable FilePermission permissions) { - this.exists = exists; - this.type = type; - this.ctime = ctime; - this.mtime = mtime; - this.size = size; - this.permissions = permissions; - } - - public FileAttributes getFileAttributes() { - return new FileAttributes(exists, type == FileType.File, ctime, mtime, true, permissions != null, size); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (obj instanceof FileAttributesResult) { - var other = (FileAttributesResult)obj; - return exists == other.exists - && type == other.type - && ctime == other.ctime - && mtime == other.mtime - && size == other.size - && permissions == other.permissions; - } - return false; - } - - @Override - public int hashCode() { - return Objects.hash(exists, type, ctime, mtime, size, permissions); - } - - @Override - public String toString() { - return "FileStatResult [exists="+ exists + " type=" + type + " ctime=" + ctime + " mtime=" + mtime + " size=" + size + " permissions=" + permissions + "]"; - } - - public enum FileType { - Unknown(0), File(1), Directory(2), SymbolicLink(64); - - private final int value; - - private FileType(int val) { - assert val == 0 || val == 1 || val == 2 || val == 64; - this.value = val; - } - - public int getValue() { - return value; - } - - public static FileType fromValue(int val) { - switch (val) { - case 0: return Unknown; - case 1: return File; - case 2: return Directory; - case 64: return SymbolicLink; - default: throw new IllegalArgumentException("Unknown FileType value " + val); - } - } - } - - public enum FilePermission { - Readonly(1); - private final int value; - private FilePermission(int val) { - assert val == 1; - this.value = val; - } - - public int getValue() { - return value; - } - - public static @Nullable FilePermission fromValue(int val) { - switch (val) { - case 1: return Readonly; - default: return null; - } - } - } -} From fa7c652d5ca648c0e78bd1653e9f64852e6d3a23 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 7 Jan 2026 15:49:17 +0100 Subject: [PATCH 014/149] Removed VsCodeUriResolverServer --- .../uri/jsonrpc/VSCodeUriResolverServer.java | 126 ------------------ 1 file changed, 126 deletions(-) delete mode 100644 rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverServer.java diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverServer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverServer.java deleted file mode 100644 index 20406a802..000000000 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverServer.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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.uri.jsonrpc; - -import java.util.concurrent.CompletableFuture; - -import org.eclipse.lsp4j.jsonrpc.services.JsonRequest; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.FileAttributesResult; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.FileWithType; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.ISourceLocationRequest; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.RenameRequest; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.WatchRequest; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.WriteFileRequest; - -public interface VSCodeUriResolverServer { - @JsonRequest("rascal/vfs/input/readFile") - default CompletableFuture readFile(ISourceLocationRequest req) { - throw new UnsupportedOperationException(); - } - - @JsonRequest("rascal/vfs/input/exists") - default CompletableFuture exists(ISourceLocationRequest req) { - throw new UnsupportedOperationException(); - } - - @JsonRequest("rascal/vfs/input/lastModified") - default CompletableFuture lastModified(ISourceLocationRequest req) { - throw new UnsupportedOperationException(); - } - - @JsonRequest("rascal/vfs/input/created") - default CompletableFuture created(ISourceLocationRequest req) { - throw new UnsupportedOperationException(); - } - - @JsonRequest("rascal/vfs/input/isDirectory") - default CompletableFuture isDirectory(ISourceLocationRequest req) { - throw new UnsupportedOperationException(); - } - - @JsonRequest("rascal/vfs/input/isFile") - default CompletableFuture isFile(ISourceLocationRequest req) { - throw new UnsupportedOperationException(); - } - - @JsonRequest("rascal/vfs/input/list") - default CompletableFuture list(ISourceLocationRequest req) { - throw new UnsupportedOperationException(); - } - - @JsonRequest("rascal/vfs/input/size") - default CompletableFuture size(ISourceLocationRequest req) { - throw new UnsupportedOperationException(); - } - - @JsonRequest("rascal/vfs/input/stat") - default CompletableFuture stat(ISourceLocationRequest req) { - throw new UnsupportedOperationException(); - } - - @JsonRequest("rascal/vfs/input/isReadable") - default CompletableFuture isReadable(ISourceLocationRequest req) { - throw new UnsupportedOperationException(); - } - - @JsonRequest("rascal/vfs/input/isWritable") - default CompletableFuture isWritable(ISourceLocationRequest req) { - throw new UnsupportedOperationException(); - } - - - @JsonRequest("rascal/vfs/output/writeFile") - default CompletableFuture writeFile(WriteFileRequest req) { - throw new UnsupportedOperationException(); - } - - @JsonRequest("rascal/vfs/output/mkDirectory") - default CompletableFuture mkDirectory(ISourceLocationRequest req) { - throw new UnsupportedOperationException(); - } - - @JsonRequest("rascal/vfs/output/remove") - default CompletableFuture remove(ISourceLocationRequest req, boolean recursive) { - throw new UnsupportedOperationException(); - } - - @JsonRequest("rascal/vfs/output/rename") - default CompletableFuture rename(RenameRequest req) { - throw new UnsupportedOperationException(); - } - - @JsonRequest("rascal/vfs/watcher/watch") - default CompletableFuture watch(WatchRequest req) { - throw new UnsupportedOperationException(); - } - - @JsonRequest("rascal/vfs/watcher/unwatch") - default CompletableFuture unwatch(WatchRequest req) { - throw new UnsupportedOperationException(); - } - -} From f30d477a23ff5467f37c56b2752f4606cb62c55e Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 7 Jan 2026 15:53:24 +0100 Subject: [PATCH 015/149] Changed references to VSCodeUriResolverServer to the new IRemoteResolverRegistry interface --- .../rascalmpl/vscode/lsp/uri/FallbackResolver.java | 6 +++--- .../lsp/uri/jsonrpc/VSCodeUriResolverClient.java | 5 +++-- .../rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeVFS.java | 7 ++++--- .../vscode/lsp/uri/jsonrpc/impl/VSCodeVFSClient.java | 12 +++++++----- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java index 807ca56d1..f6f07adf3 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java @@ -60,10 +60,10 @@ import org.rascalmpl.uri.ISourceLocationInputOutput; import org.rascalmpl.uri.ISourceLocationWatcher; import org.rascalmpl.uri.URIUtil; +import org.rascalmpl.uri.vfs.IRemoteResolverRegistry; import org.rascalmpl.vscode.lsp.IBaseTextDocumentService; import org.rascalmpl.vscode.lsp.TextDocumentState; import org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeUriResolverClient; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeUriResolverServer; import org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeVFS; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.FileAttributesResult.FileType; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.FileWithType; @@ -94,7 +94,7 @@ public FallbackResolver() { instance = this; } - private static VSCodeUriResolverServer getServer() throws IOException { + private static IRemoteResolverRegistry getServer() throws IOException { var result = VSCodeVFS.INSTANCE.getServer(); if (result == null) { throw new IOException("Missing VFS file server"); @@ -110,7 +110,7 @@ private static VSCodeUriResolverClient getClient() throws IOException { return result; } - private static T call(Function> target) throws IOException { + private static T call(Function> target) throws IOException { try { return target.apply(getServer()).get(5, TimeUnit.MINUTES); } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverClient.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverClient.java index 15b89078d..31036b056 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverClient.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverClient.java @@ -30,6 +30,7 @@ import java.util.function.Consumer; import org.eclipse.lsp4j.jsonrpc.services.JsonNotification; import org.rascalmpl.uri.ISourceLocationWatcher; +import org.rascalmpl.uri.vfs.IRemoteResolverRegistry; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.ISourceLocationChanged; import io.usethesource.vallang.ISourceLocation; @@ -38,6 +39,6 @@ public interface VSCodeUriResolverClient { @JsonNotification("rascal/vfs/watcher/emitWatch") void emitWatch(ISourceLocationChanged event); - void addWatcher(ISourceLocation loc, boolean recursive, Consumer callback, VSCodeUriResolverServer server) throws IOException; - void removeWatcher(ISourceLocation loc, boolean recursive, Consumer callback, VSCodeUriResolverServer server) throws IOException; + void addWatcher(ISourceLocation loc, boolean recursive, Consumer callback, IRemoteResolverRegistry server) throws IOException; + void removeWatcher(ISourceLocation loc, boolean recursive, Consumer callback, IRemoteResolverRegistry server) throws IOException; } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeVFS.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeVFS.java index 4bcefbad1..84e180727 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeVFS.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeVFS.java @@ -29,6 +29,7 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import org.rascalmpl.uri.vfs.IRemoteResolverRegistry; /** * This singleton keeps track of the current VFS server instance @@ -39,15 +40,15 @@ public enum VSCodeVFS { INSTANCE; - private volatile @MonotonicNonNull VSCodeUriResolverServer server = null; + private volatile @MonotonicNonNull IRemoteResolverRegistry server = null; private volatile @MonotonicNonNull VSCodeUriResolverClient client = null; - public @Nullable VSCodeUriResolverServer getServer() { + public @Nullable IRemoteResolverRegistry getServer() { return server; } @EnsuresNonNull("this.server") - public void provideServer(VSCodeUriResolverServer server) { + public void provideServer(IRemoteResolverRegistry server) { this.server = server; } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/impl/VSCodeVFSClient.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/impl/VSCodeVFSClient.java index 02a5186fb..6018e854c 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/impl/VSCodeVFSClient.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/impl/VSCodeVFSClient.java @@ -38,18 +38,20 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.function.Consumer; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.checkerframework.checker.nullness.qual.Nullable; import org.eclipse.lsp4j.jsonrpc.Launcher; import org.rascalmpl.uri.ISourceLocationWatcher; +import org.rascalmpl.uri.vfs.IRemoteResolverRegistry; import org.rascalmpl.vscode.lsp.IRascalFileSystemServices; import org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeUriResolverClient; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeUriResolverServer; import org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeVFS; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.ISourceLocationChanged; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.WatchRequest; import org.rascalmpl.vscode.lsp.util.NamedThreadPool; + import io.usethesource.vallang.ISourceLocation; public class VSCodeVFSClient implements VSCodeUriResolverClient, AutoCloseable { @@ -82,7 +84,7 @@ public void emitWatch(ISourceLocationChanged event) { } @Override - public void addWatcher(ISourceLocation loc, boolean recursive, Consumer callback, VSCodeUriResolverServer server) throws IOException { + public void addWatcher(ISourceLocation loc, boolean recursive, Consumer callback, IRemoteResolverRegistry server) throws IOException { logger.trace("addWatcher: {}", loc); try { var watch = watchers.computeIfAbsent(new WatchSubscriptionKey(loc, recursive), k -> { @@ -103,7 +105,7 @@ public void addWatcher(ISourceLocation loc, boolean recursive, Consumer callback, - VSCodeUriResolverServer server) throws IOException { + IRemoteResolverRegistry server) throws IOException { logger.trace("removeWatcher: {}", loc); var watchKey = new WatchSubscriptionKey(loc, recursive); var watch = watchers.get(watchKey); @@ -209,8 +211,8 @@ public static void buildAndRegister(int port) { socket.setTcpNoDelay(true); @SuppressWarnings("java:S2095") // we don't have to close the client, we are passing it off to the VSCodeVFS singleton var newClient = new VSCodeVFSClient(socket); - Launcher clientLauncher = new Launcher.Builder() - .setRemoteInterface(VSCodeUriResolverServer.class) + Launcher clientLauncher = new Launcher.Builder() + .setRemoteInterface(IRemoteResolverRegistry.class) .setLocalService(newClient) .setInput(socket.getInputStream()) .setOutput(socket.getOutputStream()) From 635179ef3629b6dec7a6943a9b38095fd5f0f1d7 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 7 Jan 2026 15:54:44 +0100 Subject: [PATCH 016/149] Updated references in imports to relocated classes --- .../vscode/lsp/IRascalFileSystemServices.java | 14 ++++++-------- .../rascalmpl/vscode/lsp/uri/FallbackResolver.java | 6 ++---- .../lsp/uri/jsonrpc/impl/VSCodeVFSClient.java | 2 +- .../lsp/uri/jsonrpc/messages/FileWithType.java | 2 +- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java index 59e97b41c..c41d71b3f 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java @@ -58,15 +58,13 @@ import org.rascalmpl.uri.URIResolverRegistry; import org.rascalmpl.uri.URIUtil; import org.rascalmpl.uri.UnsupportedSchemeException; +import org.rascalmpl.uri.vfs.FileAttributesResult; +import org.rascalmpl.uri.vfs.FileAttributesResult.FilePermission; +import org.rascalmpl.uri.vfs.FileAttributesResult.FileType; +import org.rascalmpl.uri.vfs.IRemoteResolverRegistry; +import org.rascalmpl.uri.vfs.IRemoteResolverRegistry.FileChangeEvent; +import org.rascalmpl.uri.vfs.IRemoteResolverRegistry.SourceLocation; import org.rascalmpl.values.IRascalValueFactory; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.FileAttributesResult; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.FileAttributesResult.FilePermission; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.FileAttributesResult.FileType; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.FileWithType; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.ISourceLocationRequest; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.RenameRequest; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.WatchRequest; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.WriteFileRequest; import org.rascalmpl.vscode.lsp.util.NamedThreadPool; import org.rascalmpl.vscode.lsp.util.locations.Locations; diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java index f6f07adf3..103154e72 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java @@ -60,15 +60,13 @@ import org.rascalmpl.uri.ISourceLocationInputOutput; import org.rascalmpl.uri.ISourceLocationWatcher; import org.rascalmpl.uri.URIUtil; +import org.rascalmpl.uri.vfs.FileAttributesResult.FileType; import org.rascalmpl.uri.vfs.IRemoteResolverRegistry; +import org.rascalmpl.uri.vfs.IRemoteResolverRegistry.FileWithType; import org.rascalmpl.vscode.lsp.IBaseTextDocumentService; import org.rascalmpl.vscode.lsp.TextDocumentState; import org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeUriResolverClient; import org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeVFS; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.FileAttributesResult.FileType; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.FileWithType; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.ISourceLocationRequest; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.WriteFileRequest; import org.rascalmpl.vscode.lsp.util.Lazy; import com.github.benmanes.caffeine.cache.Cache; diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/impl/VSCodeVFSClient.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/impl/VSCodeVFSClient.java index 6018e854c..60590ab0b 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/impl/VSCodeVFSClient.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/impl/VSCodeVFSClient.java @@ -45,11 +45,11 @@ import org.eclipse.lsp4j.jsonrpc.Launcher; import org.rascalmpl.uri.ISourceLocationWatcher; import org.rascalmpl.uri.vfs.IRemoteResolverRegistry; +import org.rascalmpl.uri.vfs.IRemoteResolverRegistry.WatchRequest; import org.rascalmpl.vscode.lsp.IRascalFileSystemServices; import org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeUriResolverClient; import org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeVFS; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.ISourceLocationChanged; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.WatchRequest; import org.rascalmpl.vscode.lsp.util.NamedThreadPool; import io.usethesource.vallang.ISourceLocation; diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/FileWithType.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/FileWithType.java index 4da308500..dc396dac0 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/FileWithType.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/FileWithType.java @@ -27,7 +27,7 @@ package org.rascalmpl.vscode.lsp.uri.jsonrpc.messages; import org.eclipse.lsp4j.jsonrpc.validation.NonNull; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.FileAttributesResult.FileType; +import org.rascalmpl.uri.vfs.FileAttributesResult.FileType; public class FileWithType { @NonNull private final String name; From 88f68865f66e2022ea0c43a2746618197512d955 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 7 Jan 2026 15:55:02 +0100 Subject: [PATCH 017/149] Fixed indentation --- .../uri/jsonrpc/messages/FileWithType.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/FileWithType.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/FileWithType.java index dc396dac0..85d13864a 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/FileWithType.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/FileWithType.java @@ -30,19 +30,19 @@ import org.rascalmpl.uri.vfs.FileAttributesResult.FileType; public class FileWithType { - @NonNull private final String name; - @NonNull private final FileType type; + @NonNull private final String name; + @NonNull private final FileType type; - public FileWithType(@NonNull String name, @NonNull FileType type) { - this.name = name; - this.type = type; - } + public FileWithType(@NonNull String name, @NonNull FileType type) { + this.name = name; + this.type = type; + } - public String getName() { - return name; - } + public String getName() { + return name; + } - public FileType getType() { - return type; - } + public FileType getType() { + return type; } +} From 5d584328fae096df696fa608a155b4edce33d458 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Thu, 8 Jan 2026 10:15:48 +0100 Subject: [PATCH 018/149] Updated method names after interface change --- .../src/fs/RascalFileSystemProviders.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts b/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts index a38fdf4e5..eb6efbbc7 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts @@ -97,21 +97,21 @@ export class RascalFileSystemProvider implements vscode.FileSystemProvider { } watch(uri: vscode.Uri, options: { recursive: boolean; excludes: string[]; }): vscode.Disposable { - this.sendRequest(uri, "rascal/filesystem/watch", { + this.sendRequest(uri, "rascal/vfs/watcher/watch", { uri: uri.toString(), recursive:options.recursive, excludes: options.excludes }); return new vscode.Disposable(() => { - this.sendRequest(uri, "rascal/filesystem/unwatch", { + this.sendRequest(uri, "rascal/vfs/watcher/unwatch", { uri: uri.toString() }); }); } stat(uri: vscode.Uri): vscode.FileStat | Thenable { - return this.sendRequest(uri, "rascal/filesystem/stat"); + return this.sendRequest(uri, "rascal/vfs/input/stat"); } readDirectory(uri: vscode.Uri): [string, vscode.FileType][] | Thenable<[string, vscode.FileType][]> { @@ -124,8 +124,7 @@ export class RascalFileSystemProvider implements vscode.FileSystemProvider { } readFile(uri: vscode.Uri): Uint8Array | Thenable { - return this.sendRequest(uri, "rascal/filesystem/readFile") - .then(content => content.content) + return this.sendRequest(uri, "rascal/filesystem/readFile") .then(str => Buffer.from(str, "base64")); } From 6c6b13cda965c010b07526d9a6bf44eeb12fe197 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Thu, 8 Jan 2026 10:16:09 +0100 Subject: [PATCH 019/149] Removed interface that is no longer in use --- rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts b/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts index eb6efbbc7..9fe12ae27 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts @@ -150,9 +150,6 @@ export class RascalFileSystemProvider implements vscode.FileSystemProvider { function isUnknownFileSystem(scheme : string) : boolean { return vscode.workspace.fs.isWritableFileSystem(scheme) === undefined; } -interface LocationContent { - content: string; -} interface WatchParameters { uri: string; From 2a107bbdff75e2fc5bad0aabacd6d04cdbeea0a4 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Thu, 8 Jan 2026 10:20:55 +0100 Subject: [PATCH 020/149] Renamed all source location parameters from uri to loc --- .../vscode/lsp/uri/FallbackResolver.java | 76 +++++++++---------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java index 103154e72..b421c986a 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java @@ -137,8 +137,8 @@ private ISourceLocationRequest param(ISourceLocation uri) { } @Override - public InputStream getInputStream(ISourceLocation uri) throws IOException { - var fileBody = call(s -> s.readFile(param(uri))); + public InputStream getInputStream(ISourceLocation loc) throws IOException { + var fileBody = call(s -> s.readFile(param(loc))); // TODO: do the decoding in a stream, to avoid the extra intermediate // byte array @@ -148,51 +148,51 @@ public InputStream getInputStream(ISourceLocation uri) throws IOException { } @Override - public boolean exists(ISourceLocation uri) { + public boolean exists(ISourceLocation loc) { try { - return call(s -> s.exists(param(uri))); + return call(s -> s.exists(param(loc))); } catch (IOException e) { return false; } } @Override - public long lastModified(ISourceLocation uri) throws IOException { - return TimeUnit.SECONDS.toMillis(call(s -> s.lastModified(param(uri)))); + public long lastModified(ISourceLocation loc) throws IOException { + return TimeUnit.SECONDS.toMillis(call(s -> s.lastModified(param(loc)))); } @Override - public long created(ISourceLocation uri) throws IOException { - return TimeUnit.SECONDS.toMillis(call(s -> s.created(param(uri)))); + public long created(ISourceLocation loc) throws IOException { + return TimeUnit.SECONDS.toMillis(call(s -> s.created(param(loc)))); } @Override - public boolean isDirectory(ISourceLocation uri) { + public boolean isDirectory(ISourceLocation loc) { try { - var cached = cachedDirectoryListing.getIfPresent(URIUtil.getParentLocation(uri)); + var cached = cachedDirectoryListing.getIfPresent(URIUtil.getParentLocation(loc)); if (cached != null) { - var result = cached.get().get(URIUtil.getLocationName(uri)); + var result = cached.get().get(URIUtil.getLocationName(loc)); if (result != null) { return result; } } - return call(s -> s.isDirectory(param(uri))); + return call(s -> s.isDirectory(param(loc))); } catch (IOException e) { return false; } } @Override - public boolean isFile(ISourceLocation uri) { + public boolean isFile(ISourceLocation loc) { try { - var cached = cachedDirectoryListing.getIfPresent(URIUtil.getParentLocation(uri)); + var cached = cachedDirectoryListing.getIfPresent(URIUtil.getParentLocation(loc)); if (cached != null) { - var result = cached.get().get(URIUtil.getLocationName(uri)); + var result = cached.get().get(URIUtil.getLocationName(loc)); if (result != null) { return !result; } } - return call(s -> s.isFile(param(uri))); + return call(s -> s.isFile(param(loc))); } catch (IOException e) { return false; } @@ -211,10 +211,10 @@ public boolean isFile(ISourceLocation uri) { .build(); @Override - public String[] list(ISourceLocation uri) throws IOException { - var result = call(s -> s.list(param(uri))); + public String[] list(ISourceLocation loc) throws IOException { + var result = call(s -> s.list(param(loc))); // we store the entries in a cache, for consecutive isDirectory/isFile calls - cachedDirectoryListing.put(uri, Lazy.defer(() -> { + cachedDirectoryListing.put(loc, Lazy.defer(() -> { Map lookup = new HashMap<>(result.length); for (var entry : result) { lookup.put(entry.getName(), entry.getType().equals(FileType.Directory)); @@ -235,7 +235,7 @@ public boolean supportsHost() { } @Override - public OutputStream getOutputStream(ISourceLocation uri, boolean append) throws IOException { + public OutputStream getOutputStream(ISourceLocation loc, boolean append) throws IOException { // we have to collect all bytes into memory, there exist no streaming Base64 encoder in java jre // otherwise we could just store that base64 string. // when done with the outputstream, we can generate the base64 string and send it towards the LSP client @@ -249,27 +249,27 @@ public void close() throws IOException { } closed = true; var contents = Base64.getEncoder().encodeToString(this.toByteArray()); - call(s -> s.writeFile(new WriteFileRequest(uri, contents, append))); - cachedDirectoryListing.invalidate(URIUtil.getParentLocation(uri)); + call(s -> s.writeFile(param(loc), contents, append, true, true)); + cachedDirectoryListing.invalidate(URIUtil.getParentLocation(loc)); } }; } @Override - public void mkDirectory(ISourceLocation uri) throws IOException { - call(s -> s.mkDirectory(param(uri))); - cachedDirectoryListing.invalidate(URIUtil.getParentLocation(uri)); + public void mkDirectory(ISourceLocation loc) throws IOException { + call(s -> s.mkDirectory(param(loc))); + cachedDirectoryListing.invalidate(URIUtil.getParentLocation(loc)); } @Override - public void remove(ISourceLocation uri) throws IOException { - call(s -> s.remove(param(uri), false)); - cachedDirectoryListing.invalidate(uri); - cachedDirectoryListing.invalidate(URIUtil.getParentLocation(uri)); + public void remove(ISourceLocation loc) throws IOException { + call(s -> s.remove(param(loc), false)); + cachedDirectoryListing.invalidate(loc); + cachedDirectoryListing.invalidate(URIUtil.getParentLocation(loc)); } @Override - public void setLastModified(ISourceLocation uri, long timestamp) throws IOException { + public void setLastModified(ISourceLocation loc, long timestamp) throws IOException { throw new IOException("setLastModified not supported by vscode"); } @@ -335,22 +335,22 @@ public TextDocumentState getDocumentState(ISourceLocation file) throws IOExcepti } @Override - public long size(ISourceLocation uri) throws IOException { - return call(s -> s.size(param(uri))); + public long size(ISourceLocation loc) throws IOException { + return call(s -> s.size(param(loc))); } @Override - public boolean isReadable(ISourceLocation uri) throws IOException { - return call(s -> s.isReadable(param(uri))); + public boolean isReadable(ISourceLocation loc) throws IOException { + return call(s -> s.isReadable(param(loc))); } @Override - public boolean isWritable(ISourceLocation uri) throws IOException { - return call(s -> s.isWritable(param(uri))); + public boolean isWritable(ISourceLocation loc) throws IOException { + return call(s -> s.isWritable(param(loc))); } @Override - public FileAttributes stat(ISourceLocation uri) throws IOException { - return call(s -> s.stat(param(uri))).getFileAttributes(); + public FileAttributes stat(ISourceLocation loc) throws IOException { + return call(s -> s.stat(param(loc))).getFileAttributes(); } private static IOException translateException(ResponseErrorException cause) { From e4c37d63589494cd9b77dcb48d2c896b232e3b9f Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Thu, 8 Jan 2026 10:21:55 +0100 Subject: [PATCH 021/149] Removed wrapping of locations in ISourceLocationRequest as it no longer exists --- .../vscode/lsp/uri/FallbackResolver.java | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java index b421c986a..e0aadd3bc 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java @@ -131,14 +131,9 @@ private static T call(Function } } - - private ISourceLocationRequest param(ISourceLocation uri) { - return new ISourceLocationRequest(uri); - } - @Override public InputStream getInputStream(ISourceLocation loc) throws IOException { - var fileBody = call(s -> s.readFile(param(loc))); + var fileBody = call(s -> s.readFile(loc)); // TODO: do the decoding in a stream, to avoid the extra intermediate // byte array @@ -150,7 +145,7 @@ public InputStream getInputStream(ISourceLocation loc) throws IOException { @Override public boolean exists(ISourceLocation loc) { try { - return call(s -> s.exists(param(loc))); + return call(s -> s.exists(loc)); } catch (IOException e) { return false; } @@ -158,12 +153,12 @@ public boolean exists(ISourceLocation loc) { @Override public long lastModified(ISourceLocation loc) throws IOException { - return TimeUnit.SECONDS.toMillis(call(s -> s.lastModified(param(loc)))); + return TimeUnit.SECONDS.toMillis(call(s -> s.lastModified(loc))); } @Override public long created(ISourceLocation loc) throws IOException { - return TimeUnit.SECONDS.toMillis(call(s -> s.created(param(loc)))); + return TimeUnit.SECONDS.toMillis(call(s -> s.created(loc))); } @Override @@ -176,7 +171,7 @@ public boolean isDirectory(ISourceLocation loc) { return result; } } - return call(s -> s.isDirectory(param(loc))); + return call(s -> s.isDirectory(loc)); } catch (IOException e) { return false; } @@ -192,7 +187,7 @@ public boolean isFile(ISourceLocation loc) { return !result; } } - return call(s -> s.isFile(param(loc))); + return call(s -> s.isFile(loc)); } catch (IOException e) { return false; } @@ -212,7 +207,7 @@ public boolean isFile(ISourceLocation loc) { @Override public String[] list(ISourceLocation loc) throws IOException { - var result = call(s -> s.list(param(loc))); + var result = call(s -> s.list(loc)); // we store the entries in a cache, for consecutive isDirectory/isFile calls cachedDirectoryListing.put(loc, Lazy.defer(() -> { Map lookup = new HashMap<>(result.length); @@ -249,7 +244,7 @@ public void close() throws IOException { } closed = true; var contents = Base64.getEncoder().encodeToString(this.toByteArray()); - call(s -> s.writeFile(param(loc), contents, append, true, true)); + call(s -> s.writeFile(loc, contents, append, true, true)); cachedDirectoryListing.invalidate(URIUtil.getParentLocation(loc)); } }; @@ -257,13 +252,13 @@ public void close() throws IOException { @Override public void mkDirectory(ISourceLocation loc) throws IOException { - call(s -> s.mkDirectory(param(loc))); + call(s -> s.mkDirectory(loc)); cachedDirectoryListing.invalidate(URIUtil.getParentLocation(loc)); } @Override public void remove(ISourceLocation loc) throws IOException { - call(s -> s.remove(param(loc), false)); + call(s -> s.remove(loc, false)); cachedDirectoryListing.invalidate(loc); cachedDirectoryListing.invalidate(URIUtil.getParentLocation(loc)); } @@ -336,21 +331,21 @@ public TextDocumentState getDocumentState(ISourceLocation file) throws IOExcepti @Override public long size(ISourceLocation loc) throws IOException { - return call(s -> s.size(param(loc))); + return call(s -> s.size(loc)); } @Override public boolean isReadable(ISourceLocation loc) throws IOException { - return call(s -> s.isReadable(param(loc))); + return call(s -> s.isReadable(loc)); } @Override public boolean isWritable(ISourceLocation loc) throws IOException { - return call(s -> s.isWritable(param(loc))); + return call(s -> s.isWritable(loc)); } @Override public FileAttributes stat(ISourceLocation loc) throws IOException { - return call(s -> s.stat(param(loc))).getFileAttributes(); + return call(s -> s.stat(loc)).getFileAttributes(); } private static IOException translateException(ResponseErrorException cause) { From ff2db4440ec5aa50a82c5a2cc653de0679899b76 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Thu, 8 Jan 2026 10:22:21 +0100 Subject: [PATCH 022/149] Fixed layout --- .../java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java index e0aadd3bc..01b82d6ad 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java @@ -115,7 +115,7 @@ private static T call(Function catch (TimeoutException te) { throw new IOException("VSCode took too long to reply, interruption to avoid deadlocks"); } - catch(InterruptedException ie) { + catch (InterruptedException ie) { Thread.currentThread().interrupt(); throw new UnsupportedOperationException("Thread should have been interrupted"); } From 6ec530f23d02802fddd7ac7d9b9e4cf3beb50d72 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Thu, 8 Jan 2026 10:24:46 +0100 Subject: [PATCH 023/149] Updated IRascalFileSystemServices to align with the IRemoteResolverRegistry interface --- .../vscode/lsp/IRascalFileSystemServices.java | 119 ++++++++++-------- 1 file changed, 64 insertions(+), 55 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java index c41d71b3f..b2ba0d616 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java @@ -45,13 +45,9 @@ import org.apache.commons.codec.binary.Base64InputStream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; import org.checkerframework.checker.nullness.qual.Nullable; import org.eclipse.lsp4j.jsonrpc.ResponseErrorException; import org.eclipse.lsp4j.jsonrpc.messages.ResponseError; -import org.eclipse.lsp4j.jsonrpc.services.JsonNotification; -import org.eclipse.lsp4j.jsonrpc.services.JsonRequest; -import org.eclipse.lsp4j.jsonrpc.validation.NonNull; import org.rascalmpl.library.Prelude; import org.rascalmpl.uri.ISourceLocationWatcher.ISourceLocationChangeType; import org.rascalmpl.uri.ISourceLocationWatcher.ISourceLocationChanged; @@ -62,33 +58,33 @@ import org.rascalmpl.uri.vfs.FileAttributesResult.FilePermission; import org.rascalmpl.uri.vfs.FileAttributesResult.FileType; import org.rascalmpl.uri.vfs.IRemoteResolverRegistry; -import org.rascalmpl.uri.vfs.IRemoteResolverRegistry.FileChangeEvent; -import org.rascalmpl.uri.vfs.IRemoteResolverRegistry.SourceLocation; import org.rascalmpl.values.IRascalValueFactory; import org.rascalmpl.vscode.lsp.util.NamedThreadPool; import org.rascalmpl.vscode.lsp.util.locations.Locations; import io.usethesource.vallang.ISourceLocation; -import io.usethesource.vallang.IValueFactory; -public interface IRascalFileSystemServices { +public class IRascalFileSystemServices implements IRemoteResolverRegistry { static final URIResolverRegistry reg = URIResolverRegistry.getInstance(); static final Logger IRascalFileSystemServices__logger = LogManager.getLogger(IRascalFileSystemServices.class); - static final ExecutorService executor = NamedThreadPool.cachedDaemon("rascal-vfs"); + public static final ExecutorService executor = NamedThreadPool.cachedDaemon("rascal-vfs"); - @JsonRequest("rascal/filesystem/resolveLocation") - default CompletableFuture resolveLocation(SourceLocation loc) { + //@JsonRequest("rascal/filesystem/resolveLocation") + @Override + /*default */public CompletableFuture resolveLocation(ISourceLocation loc) { return CompletableFuture.supplyAsync(() -> { try { - ISourceLocation tmp = loc.toRascalLocation(); + // ISourceLocation tmp = loc.toRascalLocation(); - ISourceLocation resolved = Locations.toClientLocation(tmp); + // ISourceLocation resolved = Locations.toClientLocation(tmp); + ISourceLocation resolved = Locations.toClientLocation(URIResolverRegistry.getInstance().logicalToPhysical(loc)); if (resolved == null) { return loc; } - return SourceLocation.fromRascalLocation(resolved); + // return SourceLocation.fromRascalLocation(resolved); + return resolved; } catch (Exception e) { IRascalFileSystemServices__logger.warn("Could not resolve location {}", loc, e); return loc; @@ -96,10 +92,12 @@ default CompletableFuture resolveLocation(SourceLocation loc) { }, executor); } - @JsonRequest("rascal/filesystem/watch") - default CompletableFuture watch(WatchRequest params) { + //@JsonRequest("rascal/filesystem/watch") + @Override + /*default */public CompletableFuture watch(WatchRequest params) { return CompletableFuture.runAsync(() -> { try { + // ISourceLocation loc = Locations.toLoc(params.getURI()); ISourceLocation loc = params.getLocation(); URIResolverRegistry.getInstance().watch(loc, params.isRecursive(), changed -> { @@ -109,7 +107,7 @@ default CompletableFuture watch(WatchRequest params) { throw new RuntimeException(e); } }); - } catch (IOException | URISyntaxException | RuntimeException e) { + } catch (IOException | RuntimeException e) { throw new VSCodeFSError(e); } }, executor); @@ -147,11 +145,12 @@ private static boolean readonly(ISourceLocation loc) throws IOException { return true; } - @JsonRequest("rascal/filesystem/stat") - default CompletableFuture stat(ISourceLocationRequest uri) { + //@JsonRequest("rascal/filesystem/stat") + @Override + /*default */public CompletableFuture stat(ISourceLocation loc) { return CompletableFuture.supplyAsync(() -> { try { - ISourceLocation loc = uri.getLocation(); + // ISourceLocation loc = Locations.toLoc(uri); if (!reg.exists(loc)) { throw new FileNotFoundException(); } @@ -176,52 +175,58 @@ default CompletableFuture stat(ISourceLocationRequest uri) }, executor); } - @JsonRequest("rascal/filesystem/readDirectory") - default CompletableFuture readDirectory(ISourceLocationRequest uri) { + //@JsonRequest("rascal/filesystem/readDirectory") + @Override + /*default */public CompletableFuture list(ISourceLocation loc) { return CompletableFuture.supplyAsync(() -> { try { - ISourceLocation loc = uri.getLocation(); + // ISourceLocation loc = Locations.toLoc(uri); if (!reg.isDirectory(loc)) { throw VSCodeFSError.notADirectory(loc); } return Arrays.stream(reg.list(loc)).map(l -> new FileWithType(URIUtil.getLocationName(l), reg.isDirectory(l) ? FileType.Directory : FileType.File)).toArray(FileWithType[]::new); - } catch (IOException | URISyntaxException | RuntimeException e) { + } catch (IOException | RuntimeException e) { throw new VSCodeFSError(e); } }, executor); } - @JsonRequest("rascal/filesystem/createDirectory") - default CompletableFuture createDirectory(ISourceLocationRequest uri) { + //@JsonRequest("rascal/filesystem/createDirectory") + @Override + /*default */public CompletableFuture mkDirectory(ISourceLocation loc) { return CompletableFuture.runAsync(() -> { try { - reg.mkDirectory(uri.getLocation()); - } catch (IOException | URISyntaxException | RuntimeException e) { + // reg.mkDirectory(Locations.toLoc(uri)); + reg.mkDirectory(loc); + } catch (IOException | RuntimeException e) { throw new VSCodeFSError(e); } }, executor); } - @JsonRequest("rascal/filesystem/readFile") - default CompletableFuture readFile(ISourceLocationRequest uri) { + //@JsonRequest("rascal/filesystem/readFile") + @Override + /*default */public CompletableFuture readFile(ISourceLocation loc) { return CompletableFuture.supplyAsync(() -> { - try (InputStream source = new Base64InputStream(reg.getInputStream(uri.getLocation()), true)) { + // try (InputStream source = new Base64InputStream(reg.getInputStream(Locations.toLoc(uri)), true)) { + try (InputStream source = new Base64InputStream(reg.getInputStream(loc), true)) { return new String(source.readAllBytes(), StandardCharsets.US_ASCII); - } catch (IOException | URISyntaxException | RuntimeException e) { + } catch (IOException | RuntimeException e) { throw new VSCodeFSError(e); } }, executor); } - @JsonRequest("rascal/filesystem/writeFile") - default CompletableFuture writeFile(WriteFileRequest params) { + //@JsonRequest("rascal/filesystem/writeFile") + @Override + /*default */public CompletableFuture writeFile(ISourceLocation loc, String content, boolean append, boolean create, boolean overwrite) { return CompletableFuture.runAsync(() -> { try { - ISourceLocation loc = params.getLocation(); + // ISourceLocation loc = Locations.toLoc(uri); boolean fileExists = reg.exists(loc); - if (!fileExists && !params.isCreate()) { + if (!fileExists && !create) { throw new FileNotFoundException(loc.toString()); } if (fileExists && reg.isDirectory(loc)) { @@ -229,49 +234,53 @@ default CompletableFuture writeFile(WriteFileRequest params) { } ISourceLocation parentFolder = URIUtil.getParentLocation(loc); - if (!reg.exists(parentFolder) && params.isCreate()) { + if (!reg.exists(parentFolder) && create) { throw new FileNotFoundException(parentFolder.toString()); } - if (fileExists && params.isCreate() && !params.isOverwrite()) { + if (fileExists && create && !overwrite) { throw new FileAlreadyExistsException(loc.toString()); } try (OutputStream target = reg.getOutputStream(loc, false)) { - target.write(Base64.getDecoder().decode(params.getContent())); + target.write(Base64.getDecoder().decode(content)); } - } catch (IOException | URISyntaxException | RuntimeException e) { + } catch (IOException | RuntimeException e) { throw new VSCodeFSError(e); } }, executor); } - @JsonRequest("rascal/filesystem/delete") - default CompletableFuture delete(ISourceLocationRequest params, boolean recursive) { + //@JsonRequest("rascal/filesystem/delete") + @Override + /*default */public CompletableFuture remove(ISourceLocation loc, boolean recursive) { return CompletableFuture.runAsync(() -> { try { - ISourceLocation loc = params.getLocation(); + // ISourceLocation loc = Locations.toLoc(uri); reg.remove(loc, recursive); - } catch (IOException | URISyntaxException e) { + } catch (IOException e) { throw new CompletionException(e); } }, executor); } - @JsonRequest("rascal/filesystem/rename") - default CompletableFuture rename(RenameRequest params) { + //@JsonRequest("rascal/filesystem/rename") + @Override + /*default */public CompletableFuture rename(ISourceLocation from, ISourceLocation to, boolean overwrite) { return CompletableFuture.runAsync(() -> { try { - ISourceLocation oldLoc = params.getFromLocation(); - ISourceLocation newLoc = params.getToLocation(); - reg.rename(oldLoc, newLoc, params.isOverwrite()); - } catch (IOException | URISyntaxException e) { + // ISourceLocation oldLoc = Locations.toLoc(from); + // ISourceLocation newLoc = Locations.toLoc(to); + // reg.rename(oldLoc, newLoc, overwrite); + reg.rename(from, to, overwrite); + } catch (IOException e) { throw new CompletionException(e); } }, executor); } - @JsonRequest("rascal/filesystem/schemes") - default CompletableFuture fileSystemSchemes() { + //@JsonRequest("rascal/filesystem/schemes") + @Override + /*default */public CompletableFuture fileSystemSchemes() { Set inputs = reg.getRegisteredInputSchemes(); Set logicals = reg.getRegisteredLogicalSchemes(); @@ -279,9 +288,6 @@ default CompletableFuture fileSystemSchemes() { .completedFuture(Stream.concat(inputs.stream(), logicals.stream()).toArray(String[]::new)); } - @JsonNotification("rascal/filesystem/onDidChangeFile") - default void onDidChangeFile(FileChangeEvent event) { } - public static class SourceLocation { @NonNull private final String uri; @@ -442,6 +448,9 @@ public String getContent() { return content; } } + // @JsonNotification("rascal/filesystem/onDidChangeFile") + @Override + /*default */public void onDidChangeFile(FileChangeEvent event) { } /** Maps common exceptions to FileSystemError in VS Code */ public static class VSCodeFSError extends ResponseErrorException { From ed28b45cd1c9d66249bf2314cae82a0e2d9a317b Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Thu, 8 Jan 2026 10:25:16 +0100 Subject: [PATCH 024/149] Eliminated SourceLocation class --- .../vscode/lsp/IRascalFileSystemServices.java | 116 ------------------ 1 file changed, 116 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java index b2ba0d616..b8c76ee7c 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java @@ -288,122 +288,6 @@ private static boolean readonly(ISourceLocation loc) throws IOException { .completedFuture(Stream.concat(inputs.stream(), logicals.stream()).toArray(String[]::new)); } - - public static class SourceLocation { - @NonNull private final String uri; - private final int @Nullable[] offsetLength; - private final int @Nullable[] beginLineColumn; - private final int @Nullable[] endLineColumn; - - public static SourceLocation fromRascalLocation(ISourceLocation loc) { - if (loc.hasOffsetLength()) { - if (loc.hasLineColumn()) { - return new SourceLocation(Locations.toUri(loc).toString(), loc.getOffset(), loc.getLength(), loc.getBeginLine(), loc.getBeginColumn(), loc.getEndLine(), loc.getEndColumn()); - } - else { - return new SourceLocation(Locations.toUri(loc).toString(), loc.getOffset(), loc.getLength()); - } - } - else { - return new SourceLocation(Locations.toUri(loc).toString()); - } - } - - public ISourceLocation toRascalLocation() throws URISyntaxException { - final IValueFactory VF = IRascalValueFactory.getInstance(); - ISourceLocation tmp = Locations.toCheckedLoc(uri); - - if (hasOffsetLength()) { - if (hasLineColumn()) { - tmp = VF.sourceLocation(tmp,getOffset(), getLength(), getBeginLine(), getEndLine(), getBeginColumn(), getEndColumn()); - } - else { - tmp = VF.sourceLocation(tmp, getOffset(), getLength()); - } - } - - return tmp; - } - - private SourceLocation(String uri, int offset, int length, int beginLine, int beginColumn, int endLine, int endColumn) { - this.uri = uri; - this.offsetLength = new int[] {offset, length}; - this.beginLineColumn = new int [] {beginLine, beginColumn}; - this.endLineColumn = new int [] {endLine, endColumn}; - } - - private SourceLocation(String uri, int offset, int length) { - this.uri = uri; - this.offsetLength = new int[] {offset, length}; - this.beginLineColumn = null; - this.endLineColumn = null; - } - - private SourceLocation(String uri) { - this.uri = uri; - this.offsetLength = null; - this.beginLineColumn = null; - this.endLineColumn = null; - } - - public String getUri() { - return uri; - } - - @EnsuresNonNullIf(expression = "this.offsetLength", result = true) - public boolean hasOffsetLength() { - return offsetLength != null; - } - - @EnsuresNonNullIf(expression = "this.endLineColumn", result = true) - @EnsuresNonNullIf(expression = "this.beginLineColumn", result = true) - public boolean hasLineColumn() { - return beginLineColumn != null && endLineColumn != null; - } - - public int getOffset() { - if (!hasOffsetLength()) { - throw new IllegalStateException("This location has no offset"); - } - return offsetLength[0]; - } - - public int getLength() { - if (!hasOffsetLength()) { - throw new IllegalStateException("This location has no length"); - } - return offsetLength[1]; - } - - public int getBeginLine() { - if (!hasLineColumn()) { - throw new IllegalStateException("This location has no line and columns"); - } - return beginLineColumn[0]; - } - - public int getBeginColumn() { - if (!hasLineColumn()) { - throw new IllegalStateException("This location has no line and columns"); - } - return beginLineColumn[1]; - } - - public int getEndLine() { - if (!hasLineColumn()) { - throw new IllegalStateException("This location has no line and columns"); - } - return endLineColumn[0]; - } - - public int getEndColumn() { - if (!hasLineColumn()) { - throw new IllegalStateException("This location has no line and columns"); - } - return endLineColumn[1]; - } - } - public static class FileChangeEvent { @NonNull private final FileChangeType type; @NonNull private final String uri; From e59cc0db9f291b9e710ea5506a583316f9b9af0e Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Thu, 8 Jan 2026 10:25:31 +0100 Subject: [PATCH 025/149] Eliminated LocationContent class --- .../vscode/lsp/IRascalFileSystemServices.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java index b8c76ee7c..1481e397f 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java @@ -320,18 +320,6 @@ public int getValue() { return value; } } - - public static class LocationContent { - @NonNull private final String content; - - public LocationContent(@NonNull String content) { - this.content = content; - } - - public String getContent() { - return content; - } - } // @JsonNotification("rascal/filesystem/onDidChangeFile") @Override /*default */public void onDidChangeFile(FileChangeEvent event) { } From 506c839020947958a8d4219ce21e7aaa385c1d70 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Thu, 8 Jan 2026 10:26:13 +0100 Subject: [PATCH 026/149] Removed FileChangeEvent and FileChangeType as these have moved to Rascal --- .../vscode/lsp/IRascalFileSystemServices.java | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java index 1481e397f..2b39ce79a 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java @@ -288,38 +288,6 @@ private static boolean readonly(ISourceLocation loc) throws IOException { .completedFuture(Stream.concat(inputs.stream(), logicals.stream()).toArray(String[]::new)); } - public static class FileChangeEvent { - @NonNull private final FileChangeType type; - @NonNull private final String uri; - - public FileChangeEvent(FileChangeType type, @NonNull String uri) { - this.type = type; - this.uri = uri; - } - - public FileChangeType getType() { - return type; - } - - public ISourceLocation getLocation() throws URISyntaxException { - return Locations.toCheckedLoc(uri); - } - } - - public enum FileChangeType { - Changed(1), Created(2), Deleted(3); - - private final int value; - - private FileChangeType(int val) { - assert val == 1 || val == 2 || val == 3; - this.value = val; - } - - public int getValue() { - return value; - } - } // @JsonNotification("rascal/filesystem/onDidChangeFile") @Override /*default */public void onDidChangeFile(FileChangeEvent event) { } From ae7f9f3f01711aafcd647905930eb5c6bd6f570c Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 23 Jan 2026 12:58:44 +0100 Subject: [PATCH 027/149] Using new GsonUtil config methods --- .../main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java | 2 +- .../rascalmpl/vscode/lsp/terminal/RemoteIDEServicesThread.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java index a0b6c62a1..5ef8e9abf 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java @@ -117,7 +117,7 @@ private static Launcher constructLSPClient(InputStream in, .setRemoteInterface(IBaseLanguageClient.class) .setInput(in) .setOutput(out) - .configureGson(b -> GsonUtils.configureGson(b, GsonUtils.ComplexTypeMode.ENCODE_AS_JSON_OBJECT)) + .configureGson(GsonUtils.complexAsJsonObject()) .setExecutorService(threadPool) .create(); diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/terminal/RemoteIDEServicesThread.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/terminal/RemoteIDEServicesThread.java index d7ccfdfcd..f23994cef 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/terminal/RemoteIDEServicesThread.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/terminal/RemoteIDEServicesThread.java @@ -74,7 +74,7 @@ public void run() { .setRemoteInterface(IRemoteIDEServices.class) .setInput(connection.getInputStream()) .setOutput(connection.getOutputStream()) - .configureGson(GsonUtils::configureGson) + .configureGson(GsonUtils.complexAsJsonObject()) .setExecutorService(exec) .setExceptionHandler(e -> { logger.error(e); From 2c6c7af083499004614ebdec62e9dac794cd1458 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 23 Jan 2026 13:03:05 +0100 Subject: [PATCH 028/149] Eliminating FallbackResolver --- .../vscode/lsp/uri/FallbackResolver.java | 223 +----------------- .../vscode/lsp/uri/LSPOpenFileResolver.java | 3 +- .../src/RascalExtension.ts | 2 +- 3 files changed, 7 insertions(+), 221 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java index 01b82d6ad..c58af2d15 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java @@ -26,47 +26,24 @@ */ package org.rascalmpl.vscode.lsp.uri; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; import java.nio.file.AccessDeniedException; import java.nio.file.FileAlreadyExistsException; import java.nio.file.NoSuchFileException; import java.nio.file.NotDirectoryException; import java.time.Duration; -import java.util.Base64; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.stream.Stream; import org.checkerframework.checker.initialization.qual.UnderInitialization; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.eclipse.lsp4j.jsonrpc.ResponseErrorException; -import org.rascalmpl.uri.FileAttributes; import org.rascalmpl.uri.ILogicalSourceLocationResolver; -import org.rascalmpl.uri.ISourceLocationInputOutput; -import org.rascalmpl.uri.ISourceLocationWatcher; import org.rascalmpl.uri.URIUtil; -import org.rascalmpl.uri.vfs.FileAttributesResult.FileType; -import org.rascalmpl.uri.vfs.IRemoteResolverRegistry; -import org.rascalmpl.uri.vfs.IRemoteResolverRegistry.FileWithType; import org.rascalmpl.vscode.lsp.IBaseTextDocumentService; import org.rascalmpl.vscode.lsp.TextDocumentState; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeUriResolverClient; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeVFS; import org.rascalmpl.vscode.lsp.util.Lazy; import com.github.benmanes.caffeine.cache.Cache; @@ -75,7 +52,7 @@ import io.usethesource.vallang.ISourceLocation; -public class FallbackResolver implements ISourceLocationInputOutput, ISourceLocationWatcher, ILogicalSourceLocationResolver { +public class FallbackResolver implements ILogicalSourceLocationResolver { private static @MonotonicNonNull FallbackResolver instance = null; @@ -83,116 +60,16 @@ public class FallbackResolver implements ISourceLocationInputOutput, ISourceLoca // making it avaible through this method, we allow the IBaseTextDocumentService implementations to interact with it. public static FallbackResolver getInstance() { if (instance == null) { - throw new IllegalStateException("FallbackResolver accessed before initialization"); + instance = new FallbackResolver(); + //throw new IllegalStateException("FallbackResolver accessed before initialization"); } return instance; } - public FallbackResolver() { + private FallbackResolver() { instance = this; } - private static IRemoteResolverRegistry getServer() throws IOException { - var result = VSCodeVFS.INSTANCE.getServer(); - if (result == null) { - throw new IOException("Missing VFS file server"); - } - return result; - } - - private static VSCodeUriResolverClient getClient() throws IOException { - var result = VSCodeVFS.INSTANCE.getClient(); - if (result == null) { - throw new IOException("Missing VFS file client"); - } - return result; - } - - private static T call(Function> target) throws IOException { - try { - return target.apply(getServer()).get(5, TimeUnit.MINUTES); - } - catch (TimeoutException te) { - throw new IOException("VSCode took too long to reply, interruption to avoid deadlocks"); - } - catch (InterruptedException ie) { - Thread.currentThread().interrupt(); - throw new UnsupportedOperationException("Thread should have been interrupted"); - } - catch (CompletionException | ExecutionException ce) { - var cause = ce.getCause(); - if (cause != null) { - if (cause instanceof ResponseErrorException) { - throw translateException((ResponseErrorException)cause); - } - throw new IOException(cause); - } - throw new IOException(ce); - } - } - - @Override - public InputStream getInputStream(ISourceLocation loc) throws IOException { - var fileBody = call(s -> s.readFile(loc)); - - // TODO: do the decoding in a stream, to avoid the extra intermediate - // byte array - return Base64.getDecoder().wrap( - new ByteArrayInputStream( - fileBody.getBytes(StandardCharsets.ISO_8859_1))); - } - - @Override - public boolean exists(ISourceLocation loc) { - try { - return call(s -> s.exists(loc)); - } catch (IOException e) { - return false; - } - } - - @Override - public long lastModified(ISourceLocation loc) throws IOException { - return TimeUnit.SECONDS.toMillis(call(s -> s.lastModified(loc))); - } - - @Override - public long created(ISourceLocation loc) throws IOException { - return TimeUnit.SECONDS.toMillis(call(s -> s.created(loc))); - } - - @Override - public boolean isDirectory(ISourceLocation loc) { - try { - var cached = cachedDirectoryListing.getIfPresent(URIUtil.getParentLocation(loc)); - if (cached != null) { - var result = cached.get().get(URIUtil.getLocationName(loc)); - if (result != null) { - return result; - } - } - return call(s -> s.isDirectory(loc)); - } catch (IOException e) { - return false; - } - } - - @Override - public boolean isFile(ISourceLocation loc) { - try { - var cached = cachedDirectoryListing.getIfPresent(URIUtil.getParentLocation(loc)); - if (cached != null) { - var result = cached.get().get(URIUtil.getLocationName(loc)); - if (result != null) { - return !result; - } - } - return call(s -> s.isFile(loc)); - } catch (IOException e) { - return false; - } - } - /** * Rascal's current implementions sometimes ask for a directory listing * and then iterate over all the entries checking if they are a directory. @@ -205,84 +82,11 @@ public boolean isFile(ISourceLocation loc) { .maximumSize(1000) .build(); - @Override - public String[] list(ISourceLocation loc) throws IOException { - var result = call(s -> s.list(loc)); - // we store the entries in a cache, for consecutive isDirectory/isFile calls - cachedDirectoryListing.put(loc, Lazy.defer(() -> { - Map lookup = new HashMap<>(result.length); - for (var entry : result) { - lookup.put(entry.getName(), entry.getType().equals(FileType.Directory)); - } - return lookup; - })); - return Stream.of(result).map(FileWithType::getName).toArray(String[]::new); - } - @Override public String scheme() { throw new UnsupportedOperationException("Scheme not supported on fallback resolver"); } - @Override - public boolean supportsHost() { - return false; - } - - @Override - public OutputStream getOutputStream(ISourceLocation loc, boolean append) throws IOException { - // we have to collect all bytes into memory, there exist no streaming Base64 encoder in java jre - // otherwise we could just store that base64 string. - // when done with the outputstream, we can generate the base64 string and send it towards the LSP client - return new ByteArrayOutputStream() { - private boolean closed = false; - - @Override - public void close() throws IOException { - if (closed) { - return; - } - closed = true; - var contents = Base64.getEncoder().encodeToString(this.toByteArray()); - call(s -> s.writeFile(loc, contents, append, true, true)); - cachedDirectoryListing.invalidate(URIUtil.getParentLocation(loc)); - } - }; - } - - @Override - public void mkDirectory(ISourceLocation loc) throws IOException { - call(s -> s.mkDirectory(loc)); - cachedDirectoryListing.invalidate(URIUtil.getParentLocation(loc)); - } - - @Override - public void remove(ISourceLocation loc) throws IOException { - call(s -> s.remove(loc, false)); - cachedDirectoryListing.invalidate(loc); - cachedDirectoryListing.invalidate(URIUtil.getParentLocation(loc)); - } - - @Override - public void setLastModified(ISourceLocation loc, long timestamp) throws IOException { - throw new IOException("setLastModified not supported by vscode"); - } - - @Override - public void watch(ISourceLocation root, Consumer watcher, boolean recursive) throws IOException { - getClient().addWatcher(root, recursive, watcher, getServer()); - } - - @Override - public void unwatch(ISourceLocation root, Consumer watcher, boolean recursive) throws IOException { - getClient().removeWatcher(root, recursive, watcher, getServer()); - } - - @Override - public boolean supportsRecursiveWatch() { - return true; - } - public boolean isFileManaged(ISourceLocation file) { for (var service : textDocumentServices) { if (service.isManagingFile(file)) { @@ -329,25 +133,6 @@ public TextDocumentState getDocumentState(ISourceLocation file) throws IOExcepti throw new IOException("File is not managed by lsp"); } - @Override - public long size(ISourceLocation loc) throws IOException { - return call(s -> s.size(loc)); - } - - @Override - public boolean isReadable(ISourceLocation loc) throws IOException { - return call(s -> s.isReadable(loc)); - } - @Override - public boolean isWritable(ISourceLocation loc) throws IOException { - return call(s -> s.isWritable(loc)); - } - - @Override - public FileAttributes stat(ISourceLocation loc) throws IOException { - return call(s -> s.stat(loc)).getFileAttributes(); - } - private static IOException translateException(ResponseErrorException cause) { var error = cause.getResponseError(); switch (error.getCode()) { diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/LSPOpenFileResolver.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/LSPOpenFileResolver.java index 00efecc12..d0a7e4360 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/LSPOpenFileResolver.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/LSPOpenFileResolver.java @@ -34,6 +34,7 @@ import org.rascalmpl.uri.FileAttributes; import org.rascalmpl.uri.ISourceLocationInput; +import org.rascalmpl.uri.URIResolverRegistry; import org.rascalmpl.uri.URIUtil; import org.rascalmpl.vscode.lsp.TextDocumentState; import org.rascalmpl.vscode.lsp.util.Versioned; @@ -122,7 +123,7 @@ public FileAttributes stat(ISourceLocation uri) throws IOException { } var current = getEditorState(uri).getCurrentContent(); var modified = current.getTimestamp(); - var isWritable = FallbackResolver.getInstance().isWritable(stripLspPrefix(uri)); + var isWritable = URIResolverRegistry.getInstance().externalRegistry.isWritable(stripLspPrefix(uri)); return new FileAttributes( true, true, modified, modified, //We fix the creation timestamp to be equal to the last modified time diff --git a/rascal-vscode-extension/src/RascalExtension.ts b/rascal-vscode-extension/src/RascalExtension.ts index 23af93e0d..553bff4db 100644 --- a/rascal-vscode-extension/src/RascalExtension.ts +++ b/rascal-vscode-extension/src/RascalExtension.ts @@ -268,7 +268,7 @@ export class RascalExtension implements vscode.Disposable { shellArgs.push(); shellArgs.push( '-Dfile.encoding=UTF8' - , '-Drascal.fallbackResolver=org.rascalmpl.vscode.lsp.uri.FallbackResolver' + // , '-Drascal.fallbackResolver=org.rascalmpl.vscode.lsp.uri.FallbackResolver' , `-Drascal.languageRegistryPort=${await this.rascal.languageRegistry.serverPort}` , 'org.rascalmpl.vscode.lsp.terminal.LSPTerminalREPL' , '--remoteIDEServicesPort' From d1c77cfe912c04728427a3a6eadf30a58df936b3 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 23 Jan 2026 13:03:33 +0100 Subject: [PATCH 029/149] Moved schemes endpoint --- .../rascalmpl/vscode/lsp/BaseLanguageServer.java | 13 +++++++++++++ .../vscode/lsp/IBaseLanguageServerExtensions.java | 6 +++++- .../vscode/lsp/IRascalFileSystemServices.java | 10 ---------- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java index 5ef8e9abf..2b899ad50 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java @@ -37,12 +37,15 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.Properties; +import java.util.Set; import java.util.concurrent.CompletableFuture; 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 java.util.stream.Stream; + import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -60,6 +63,7 @@ import org.eclipse.lsp4j.services.LanguageClientAware; import org.rascalmpl.ideservices.GsonUtils; import org.rascalmpl.library.util.PathConfig; +import org.rascalmpl.uri.URIResolverRegistry; import org.rascalmpl.vscode.lsp.log.LogRedirectConfiguration; import org.rascalmpl.vscode.lsp.parametric.LanguageRegistry.LanguageParameter; import org.rascalmpl.vscode.lsp.terminal.RemoteIDEServicesThread; @@ -326,5 +330,14 @@ public void setMinimumLogLevel(String level) { final var l = Level.toLevel(level, Level.DEBUG); // fall back to debug when the string cannot be mapped Configurator.setRootLevel(l); } + + @Override + public CompletableFuture fileSystemSchemes() { + var reg = URIResolverRegistry.getInstance(); + Set inputs = reg.getRegisteredInputSchemes(); + Set logicals = reg.getRegisteredLogicalSchemes(); + + return CompletableFuture.completedFuture(Stream.concat(inputs.stream(), logicals.stream()).toArray(String[]::new)); + } } } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseLanguageServerExtensions.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseLanguageServerExtensions.java index 47693efef..743cabc7e 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseLanguageServerExtensions.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseLanguageServerExtensions.java @@ -27,6 +27,7 @@ package org.rascalmpl.vscode.lsp; import java.net.URI; +import java.util.List; import java.util.concurrent.CompletableFuture; import org.eclipse.lsp4j.jsonrpc.messages.Tuple.Two; @@ -37,7 +38,7 @@ import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.PathConfigParameter; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.VFSRegister; -public interface IBaseLanguageServerExtensions extends LanguageServer, IRascalFileSystemServices { +public interface IBaseLanguageServerExtensions extends LanguageServer { @JsonNotification("rascal/supplyRemoteIDEServicesConfiguration") default CompletableFuture supplyRemoteIDEServicesConfiguration() { throw new UnsupportedOperationException(); @@ -63,4 +64,7 @@ default CompletableFuture[]> supplyPathConfig(PathConfigParam @JsonNotification("rascal/logLevel") void setMinimumLogLevel(String level); + + @JsonRequest("rascal/vfs/schemes") + CompletableFuture fileSystemSchemes(); } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java index 2b39ce79a..0f81dc140 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java @@ -278,16 +278,6 @@ private static boolean readonly(ISourceLocation loc) throws IOException { }, executor); } - //@JsonRequest("rascal/filesystem/schemes") - @Override - /*default */public CompletableFuture fileSystemSchemes() { - Set inputs = reg.getRegisteredInputSchemes(); - Set logicals = reg.getRegisteredLogicalSchemes(); - - return CompletableFuture - .completedFuture(Stream.concat(inputs.stream(), logicals.stream()).toArray(String[]::new)); - } - // @JsonNotification("rascal/filesystem/onDidChangeFile") @Override /*default */public void onDidChangeFile(FileChangeEvent event) { } From 04cce0253bf71061fad55c67ac6a55b8818ef02e Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 23 Jan 2026 13:04:46 +0100 Subject: [PATCH 030/149] Removed qualifiers and annotations from IRascalFileSystemServices --- .../vscode/lsp/IRascalFileSystemServices.java | 45 +++++-------------- 1 file changed, 10 insertions(+), 35 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java index 0f81dc140..766f7dced 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java @@ -69,21 +69,16 @@ public class IRascalFileSystemServices implements IRemoteResolverRegistry { static final Logger IRascalFileSystemServices__logger = LogManager.getLogger(IRascalFileSystemServices.class); public static final ExecutorService executor = NamedThreadPool.cachedDaemon("rascal-vfs"); - //@JsonRequest("rascal/filesystem/resolveLocation") @Override - /*default */public CompletableFuture resolveLocation(ISourceLocation loc) { + public CompletableFuture resolveLocation(ISourceLocation loc) { return CompletableFuture.supplyAsync(() -> { try { - // ISourceLocation tmp = loc.toRascalLocation(); - - // ISourceLocation resolved = Locations.toClientLocation(tmp); ISourceLocation resolved = Locations.toClientLocation(URIResolverRegistry.getInstance().logicalToPhysical(loc)); if (resolved == null) { return loc; } - // return SourceLocation.fromRascalLocation(resolved); return resolved; } catch (Exception e) { IRascalFileSystemServices__logger.warn("Could not resolve location {}", loc, e); @@ -92,12 +87,10 @@ public class IRascalFileSystemServices implements IRemoteResolverRegistry { }, executor); } - //@JsonRequest("rascal/filesystem/watch") @Override - /*default */public CompletableFuture watch(WatchRequest params) { + public CompletableFuture watch(WatchRequest params) { return CompletableFuture.runAsync(() -> { try { - // ISourceLocation loc = Locations.toLoc(params.getURI()); ISourceLocation loc = params.getLocation(); URIResolverRegistry.getInstance().watch(loc, params.isRecursive(), changed -> { @@ -145,12 +138,10 @@ private static boolean readonly(ISourceLocation loc) throws IOException { return true; } - //@JsonRequest("rascal/filesystem/stat") @Override - /*default */public CompletableFuture stat(ISourceLocation loc) { + public CompletableFuture stat(ISourceLocation loc) { return CompletableFuture.supplyAsync(() -> { try { - // ISourceLocation loc = Locations.toLoc(uri); if (!reg.exists(loc)) { throw new FileNotFoundException(); } @@ -175,12 +166,10 @@ private static boolean readonly(ISourceLocation loc) throws IOException { }, executor); } - //@JsonRequest("rascal/filesystem/readDirectory") @Override - /*default */public CompletableFuture list(ISourceLocation loc) { + public CompletableFuture list(ISourceLocation loc) { return CompletableFuture.supplyAsync(() -> { try { - // ISourceLocation loc = Locations.toLoc(uri); if (!reg.isDirectory(loc)) { throw VSCodeFSError.notADirectory(loc); } @@ -192,12 +181,10 @@ private static boolean readonly(ISourceLocation loc) throws IOException { }, executor); } - //@JsonRequest("rascal/filesystem/createDirectory") @Override - /*default */public CompletableFuture mkDirectory(ISourceLocation loc) { + public CompletableFuture mkDirectory(ISourceLocation loc) { return CompletableFuture.runAsync(() -> { try { - // reg.mkDirectory(Locations.toLoc(uri)); reg.mkDirectory(loc); } catch (IOException | RuntimeException e) { throw new VSCodeFSError(e); @@ -205,11 +192,9 @@ private static boolean readonly(ISourceLocation loc) throws IOException { }, executor); } - //@JsonRequest("rascal/filesystem/readFile") @Override - /*default */public CompletableFuture readFile(ISourceLocation loc) { + public CompletableFuture readFile(ISourceLocation loc) { return CompletableFuture.supplyAsync(() -> { - // try (InputStream source = new Base64InputStream(reg.getInputStream(Locations.toLoc(uri)), true)) { try (InputStream source = new Base64InputStream(reg.getInputStream(loc), true)) { return new String(source.readAllBytes(), StandardCharsets.US_ASCII); } catch (IOException | RuntimeException e) { @@ -218,13 +203,10 @@ private static boolean readonly(ISourceLocation loc) throws IOException { }, executor); } - //@JsonRequest("rascal/filesystem/writeFile") @Override - /*default */public CompletableFuture writeFile(ISourceLocation loc, String content, boolean append, boolean create, boolean overwrite) { + public CompletableFuture writeFile(ISourceLocation loc, String content, boolean append, boolean create, boolean overwrite) { return CompletableFuture.runAsync(() -> { try { - // ISourceLocation loc = Locations.toLoc(uri); - boolean fileExists = reg.exists(loc); if (!fileExists && !create) { throw new FileNotFoundException(loc.toString()); @@ -250,12 +232,10 @@ private static boolean readonly(ISourceLocation loc) throws IOException { }, executor); } - //@JsonRequest("rascal/filesystem/delete") @Override - /*default */public CompletableFuture remove(ISourceLocation loc, boolean recursive) { + public CompletableFuture remove(ISourceLocation loc, boolean recursive) { return CompletableFuture.runAsync(() -> { try { - // ISourceLocation loc = Locations.toLoc(uri); reg.remove(loc, recursive); } catch (IOException e) { throw new CompletionException(e); @@ -263,14 +243,10 @@ private static boolean readonly(ISourceLocation loc) throws IOException { }, executor); } - //@JsonRequest("rascal/filesystem/rename") @Override - /*default */public CompletableFuture rename(ISourceLocation from, ISourceLocation to, boolean overwrite) { + public CompletableFuture rename(ISourceLocation from, ISourceLocation to, boolean overwrite) { return CompletableFuture.runAsync(() -> { try { - // ISourceLocation oldLoc = Locations.toLoc(from); - // ISourceLocation newLoc = Locations.toLoc(to); - // reg.rename(oldLoc, newLoc, overwrite); reg.rename(from, to, overwrite); } catch (IOException e) { throw new CompletionException(e); @@ -278,9 +254,8 @@ private static boolean readonly(ISourceLocation loc) throws IOException { }, executor); } - // @JsonNotification("rascal/filesystem/onDidChangeFile") @Override - /*default */public void onDidChangeFile(FileChangeEvent event) { } + public void onDidChangeFile(FileChangeEvent event) { } /** Maps common exceptions to FileSystemError in VS Code */ public static class VSCodeFSError extends ResponseErrorException { From 57340243894f7e87dc6180374b476ca401231449 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 23 Jan 2026 13:07:01 +0100 Subject: [PATCH 031/149] Removed LSPTerminalREPL entirely --- .../vscode/lsp/terminal/LSPTerminalREPL.java | 62 ------------------- 1 file changed, 62 deletions(-) delete mode 100644 rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/terminal/LSPTerminalREPL.java diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/terminal/LSPTerminalREPL.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/terminal/LSPTerminalREPL.java deleted file mode 100644 index eda3eba58..000000000 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/terminal/LSPTerminalREPL.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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.terminal; - -import java.io.IOException; -import java.util.ArrayList; - -import org.rascalmpl.repl.rascal.RascalInterpreterREPL; -import org.rascalmpl.shell.RascalShell; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.impl.VSCodeVFSClient; - -/** - * This class runs a Rascal terminal REPL that - * connects to a running LSP server instance to - * provide IDE feature to the user of a terminal instance. - */ -public class LSPTerminalREPL extends RascalInterpreterREPL { - public static void main(String[] args) throws IOException { - int vfsPort = -1; - - var rascalShellArgs = new ArrayList(); - - for (int i = 0; i < args.length; i++) { - if (args[i].equals("--vfsPort")) { - vfsPort = Integer.parseInt(args[++i]); - } else { - rascalShellArgs.add(args[i]); - } - } - - if (vfsPort != -1) { - VSCodeVFSClient.buildAndRegister(vfsPort); - } - - RascalShell.main(rascalShellArgs.toArray(new String[rascalShellArgs.size()])); - } -} - From 77a9934fc94b6c4f5317326bb3d11284f79a2ca7 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 23 Jan 2026 13:07:39 +0100 Subject: [PATCH 032/149] Updated setup of language servers in RascalExtension.ts --- rascal-vscode-extension/src/RascalExtension.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rascal-vscode-extension/src/RascalExtension.ts b/rascal-vscode-extension/src/RascalExtension.ts index 553bff4db..3265e231f 100644 --- a/rascal-vscode-extension/src/RascalExtension.ts +++ b/rascal-vscode-extension/src/RascalExtension.ts @@ -266,15 +266,16 @@ export class RascalExtension implements vscode.Disposable { ); } shellArgs.push(); + const vfsServerPort = await this.vfsServer.serverPort; shellArgs.push( '-Dfile.encoding=UTF8' - // , '-Drascal.fallbackResolver=org.rascalmpl.vscode.lsp.uri.FallbackResolver' , `-Drascal.languageRegistryPort=${await this.rascal.languageRegistry.serverPort}` - , 'org.rascalmpl.vscode.lsp.terminal.LSPTerminalREPL' + , `-Drascal.remoteResolverRegistryPort=${vfsServerPort}` + , 'org.rascalmpl.shell.RascalShell' , '--remoteIDEServicesPort' , '' + remoteIDEServicesConfiguration.port , '--vfsPort' - , '' + await this.vfsServer.serverPort + , '' + vfsServerPort ); return shellArgs.concat(extraArgs || []); } From 8edee2c5c07745714745ef035f3705556d2f7670 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 23 Jan 2026 13:08:27 +0100 Subject: [PATCH 033/149] Changed jsonrpc method names on the TS side --- .../src/fs/RascalFileSystemProviders.ts | 8 ++++---- rascal-vscode-extension/src/lsp/RascalLSPConnection.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts b/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts index 9fe12ae27..d8f9c7b8d 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts @@ -115,7 +115,7 @@ export class RascalFileSystemProvider implements vscode.FileSystemProvider { } readDirectory(uri: vscode.Uri): [string, vscode.FileType][] | Thenable<[string, vscode.FileType][]> { - return this.sendRequest(uri, "rascal/filesystem/readDirectory") + return this.sendRequest(uri, "rascal/vfs/input/list") .then(c => c.map(ft => [ft.name, ft.type])); } @@ -124,12 +124,12 @@ export class RascalFileSystemProvider implements vscode.FileSystemProvider { } readFile(uri: vscode.Uri): Uint8Array | Thenable { - return this.sendRequest(uri, "rascal/filesystem/readFile") + return this.sendRequest(uri, "rascal/vfs/output/mkDirectory") .then(str => Buffer.from(str, "base64")); } writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean; }): void | Thenable { - return this.sendRequest(uri, "rascal/filesystem/writeFile", { + return this.sendRequest(uri, "rascal/vfs/output/writeFile", { uri: uri.toString(), append: false, create:options.create, @@ -139,7 +139,7 @@ export class RascalFileSystemProvider implements vscode.FileSystemProvider { } delete(uri: vscode.Uri, options: { recursive: boolean; }): void | Thenable { - return this.sendRequest(uri, "rascal/filesystem/delete", uri.toString(), options.recursive); + return this.sendRequest(uri, "rascal/vfs/output/remove", uri.toString(), options.recursive); } rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean; }): void | Thenable { diff --git a/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts b/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts index e22f3f4d3..fead367ef 100644 --- a/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts +++ b/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts @@ -67,7 +67,7 @@ export async function activateLanguageClient( openEditor(uri, range, viewColumn); }); - const schemesReply = client.sendRequest("rascal/filesystem/schemes"); + const schemesReply = client.sendRequest("rascal/vfs/schemes"); schemesReply.then( schemes => { vfsServer.ignoreSchemes(schemes); From c816297c5fa03bd5253b431f2aa5a79640241cc4 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 23 Jan 2026 13:09:17 +0100 Subject: [PATCH 034/149] Fixed several typo's --- rascal-vscode-extension/src/fs/VSCodeURIResolver.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts index de9729227..261425939 100644 --- a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts +++ b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts @@ -33,7 +33,7 @@ import { JsonRpcServer } from "../util/JsonRpcServer"; declare type ISourceLocation = URI; /** - * VSCode implements this and offers it to the rascal-lsp server + * VS Code implements this and offers it to the rascal-lsp server */ interface VSCodeResolverServer extends ISourceLocationInput, ISourceLocationOutput, ISourceLocationWatcher { } @@ -43,7 +43,7 @@ interface VSCodeResolverServer extends ISourceLocationInput, ISourceLocationOutp //interface VSCodeResolverClient extends WatchEventReceiver {} -// Rascal's interface reduce to a subset we can support +// Rascal's interface reduced to a subset we can support interface ISourceLocationInput { readFile(req: ISourceLocationRequest): Promise; exists(req: ISourceLocationRequest): Promise; @@ -78,7 +78,7 @@ function connectInputHandler(connection: rpc.MessageConnection, handler: ISource req("isWritable", handler.isWritable); } -// Rascal's interface reduce to a subset we can support +// Rascal's interface reduced to a subset we can support interface ISourceLocationOutput { writeFile(req: WriteFileRequest ): Promise; mkDirectory(req: ISourceLocationRequest): Promise; @@ -103,7 +103,7 @@ function connectOutputHandler(connection: rpc.MessageConnection, handler: ISourc req1("rename", handler.rename); } -// Rascal's interface reduce to a subset we can support +// Rascal's interface reduced to a subset we can support interface ISourceLocationWatcher { watch(newWatch: WatchRequest): Promise; unwatch(removeWatch: WatchRequest): Promise; From 7c345ff46afcebf4f9b71f2e281e1a7b03bdcee5 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 23 Jan 2026 13:11:10 +0100 Subject: [PATCH 035/149] Removed ISourceLocationRequest and updated all references --- .../src/fs/VSCodeURIResolver.ts | 157 ++++++++---------- 1 file changed, 70 insertions(+), 87 deletions(-) diff --git a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts index 261425939..9a139dae4 100644 --- a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts +++ b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts @@ -45,24 +45,23 @@ interface VSCodeResolverServer extends ISourceLocationInput, ISourceLocationOutp // Rascal's interface reduced to a subset we can support interface ISourceLocationInput { - readFile(req: ISourceLocationRequest): Promise; - exists(req: ISourceLocationRequest): Promise; - lastModified(req: ISourceLocationRequest): Promise; - created(req: ISourceLocationRequest): Promise; - isDirectory(req: ISourceLocationRequest): Promise; - isFile(req: ISourceLocationRequest): Promise; - list(req: ISourceLocationRequest): Promise<[string, vscode.FileType][]>; - size(req: ISourceLocationRequest): Promise; - fileStat(req: ISourceLocationRequest): Promise; - isReadable(req: ISourceLocationRequest): Promise; - isWritable(req: ISourceLocationRequest): Promise; + readFile(req: ISourceLocation): Promise; + exists(req: ISourceLocation): Promise; + lastModified(req: ISourceLocation): Promise; + created(req: ISourceLocation): Promise; + isDirectory(req: ISourceLocation): Promise; + isFile(req: ISourceLocation): Promise; + list(req: ISourceLocation): Promise<[string, vscode.FileType][]>; + size(req: ISourceLocation): Promise; + fileStat(req: ISourceLocation): Promise; + isReadable(req: ISourceLocation): Promise; + isWritable(req: ISourceLocation): Promise; } - function connectInputHandler(connection: rpc.MessageConnection, handler: ISourceLocationInput, toClear: Disposable[]) { - function req (method: string, h: rpc.RequestHandler1) { + function req (method: string, h: rpc.RequestHandler1) { toClear.push(connection.onRequest( - new rpc.RequestType1("rascal/vfs/input/" + method), + new rpc.RequestType1("rascal/vfs/input/" + method), h.bind(handler))); } req("readFile", handler.readFile); @@ -81,8 +80,8 @@ function connectInputHandler(connection: rpc.MessageConnection, handler: ISource // Rascal's interface reduced to a subset we can support interface ISourceLocationOutput { writeFile(req: WriteFileRequest ): Promise; - mkDirectory(req: ISourceLocationRequest): Promise; - remove(req: ISourceLocationRequest, recursive: boolean): Promise; + mkDirectory(req: ISourceLocation): Promise; + remove(req: ISourceLocation, recursive: boolean): Promise; rename(req: RenameRequest): Promise; } @@ -134,10 +133,6 @@ function buildWatchReceiver(connection: rpc.MessageConnection) : WatchEventRecei // Messages (requests and responses) -interface ISourceLocationRequest { - uri: ISourceLocation; -} - export interface FileAttributesResult { exists : boolean; type: vscode.FileType; @@ -147,7 +142,8 @@ export interface FileAttributesResult { permissions: vscode.FilePermission; } -export interface WriteFileRequest extends ISourceLocationRequest { +export interface WriteFileRequest { + uri: ISourceLocation; content: string; append: boolean; } @@ -158,8 +154,8 @@ export interface RenameRequest { overwrite: boolean; } - -export interface WatchRequest extends ISourceLocationRequest { +export interface WatchRequest { + uri: ISourceLocation; /** * subscription id, this helps the calling in linking up to the original request * as the watches are recursive @@ -168,7 +164,6 @@ export interface WatchRequest extends ISourceLocationRequest { recursive: boolean; } - export enum ISourceLocationChangeType { created = 1, deleted = 2, @@ -185,10 +180,8 @@ enum ErrorCodes { generic = -1, fileSystem = -2, nativeRascal = -3 - } - export class VSCodeUriResolverServer extends JsonRpcServer { private rascalNativeSchemes: Set = new Set(); constructor(debug: boolean, private readonly logger: vscode.LogOutputChannel) { @@ -200,7 +193,6 @@ export class VSCodeUriResolverServer extends JsonRpcServer { } } - async function asyncCatcher(build: () => Thenable): Promise { try { return await build(); @@ -227,7 +219,6 @@ async function asyncVoidCatcher(run: (() => Promise) | Thenable): Pr }); } - class ResolverClient implements VSCodeResolverServer, Disposable { private readonly connection: rpc.MessageConnection; private readonly watchListener: WatchEventReceiver; @@ -249,36 +240,32 @@ class ResolverClient implements VSCodeResolverServer, Disposable { connectInputHandler(connection, this, this.toClear); connectOutputHandler(connection, this, this.toClear); connectWatchHandler(connection, this, this.toClear); + connectLogicalResolver(connection, this, this.toClear); } - toUri(req: ISourceLocationRequest | ISourceLocation): vscode.Uri { - if (typeof req !== 'string') { - req = req.uri; - } - const uri = vscode.Uri.parse(req); + toUri(loc: ISourceLocation): vscode.Uri { + const uri = vscode.Uri.parse(loc); if (this.isRascalNative(uri)) { - throw new rpc.ResponseError(ErrorCodes.nativeRascal, "Cannot request VFS jobs on native rascal URIs: " + req); + throw new rpc.ResponseError(ErrorCodes.nativeRascal, "Cannot request VFS jobs on native rascal URIs: " + loc); } return uri; } - - - async readFile(req: ISourceLocationRequest): Promise { - this.logger.trace("[VFS] readFile: ", req.uri); - return asyncCatcher(async () => Buffer.from(await this.fs.readFile(this.toUri(req))).toString("base64")); + async readFile(loc: ISourceLocation): Promise { + this.logger.trace("[VFS] readFile: ", loc); + return asyncCatcher(async () => Buffer.from(await this.fs.readFile(this.toUri(loc))).toString("base64")); } - isRascalNative(req: ISourceLocationRequest | vscode.Uri) : boolean { + isRascalNative(loc: ISourceLocation | vscode.Uri) : boolean { //this.rascalNativeSchemes.has(uri.scheme) - const scheme = "scheme" in req ? req.scheme : req.uri.substring(0, req.uri.indexOf(":")); + const scheme = typeof(loc) === "string" ? loc.substring(0, loc.indexOf(":")) : loc.scheme; return this.rascalNativeSchemes.has(scheme); } - async exists(req: ISourceLocationRequest): Promise { - this.logger.trace("[VFS] exists: ", req.uri); + async exists(loc: ISourceLocation): Promise { + this.logger.trace("[VFS] exists: ", loc); try { - await this.stat(req); + await this.stat(loc); return true; } catch (_e) { @@ -286,9 +273,9 @@ class ResolverClient implements VSCodeResolverServer, Disposable { } } - async fileStat(req: ISourceLocationRequest): Promise { + async fileStat(loc: ISourceLocation): Promise { return asyncCatcher(async () => { - const fileInfo = await this.stat(req); + const fileInfo = await this.stat(loc); return { exists: true, type: fileInfo.type.valueOf(), @@ -300,54 +287,54 @@ class ResolverClient implements VSCodeResolverServer, Disposable { }); } - private async stat(req: ISourceLocationRequest): Promise { - this.logger.trace("[VFS] stat: ", req.uri); - return this.fs.stat(this.toUri(req)); + private async stat(loc: ISourceLocation): Promise { + this.logger.trace("[VFS] stat: ", loc); + return this.fs.stat(this.toUri(loc)); } - private async numberResult(req: ISourceLocationRequest, mapper: (s: vscode.FileStat) => number): Promise { - return asyncCatcher(async () => mapper((await this.stat(req)))); + private async numberResult(loc: ISourceLocation, mapper: (s: vscode.FileStat) => number): Promise { + return asyncCatcher(async () => mapper((await this.stat(loc)))); } - lastModified(req: ISourceLocationRequest): Promise { - this.logger.trace("[VFS] lastModified: ", req.uri); - return this.numberResult(req, f => f.mtime); + lastModified(loc: ISourceLocation): Promise { + this.logger.trace("[VFS] lastModified: ", loc); + return this.numberResult(loc, f => f.mtime); } - created(req: ISourceLocationRequest): Promise { - this.logger.trace("[VFS] created: ", req.uri); - return this.numberResult(req, f => f.ctime); + created(loc: ISourceLocation): Promise { + this.logger.trace("[VFS] created: ", loc); + return this.numberResult(loc, f => f.ctime); } - size(req: ISourceLocationRequest): Promise { - this.logger.trace("[VFS] size: ", req.uri); - return this.numberResult(req, f => f.size); + size(loc: ISourceLocation): Promise { + this.logger.trace("[VFS] size: ", loc); + return this.numberResult(loc, f => f.size); } - private async boolResult(req: ISourceLocationRequest, mapper: (s :vscode.FileStat) => boolean): Promise { - return asyncCatcher(async () => mapper((await this.stat(req)))); + private async boolResult(loc: ISourceLocation, mapper: (s :vscode.FileStat) => boolean): Promise { + return asyncCatcher(async () => mapper((await this.stat(loc)))); } - isDirectory(req: ISourceLocationRequest): Promise { - this.logger.trace("[VFS] isDirectory: ", req.uri); - return this.boolResult(req, f => f.type === vscode.FileType.Directory); + isDirectory(loc: ISourceLocation): Promise { + this.logger.trace("[VFS] isDirectory: ", loc); + return this.boolResult(loc, f => f.type === vscode.FileType.Directory); } - isFile(req: ISourceLocationRequest): Promise { - this.logger.trace("[VFS] isFile: ", req.uri); + isFile(loc: ISourceLocation): Promise { + this.logger.trace("[VFS] isFile: ", loc); // TODO: figure out how to handle vscode.FileType.Symlink - return this.boolResult(req, f => f.type === vscode.FileType.File); + return this.boolResult(loc, f => f.type === vscode.FileType.File); } - isReadable(req: ISourceLocationRequest): Promise { - this.logger.trace("[VFS] isReadable: ", req.uri); + isReadable(loc: ISourceLocation): Promise { + this.logger.trace("[VFS] isReadable: ", loc); // if we can do a stat, we can read - return this.boolResult(req, _ => true); + return this.boolResult(loc, _ => true); } - async isWritable(req: ISourceLocationRequest): Promise { - this.logger.trace("[VFS] isWritable: ", req.uri); - const scheme = this.toUri(req).scheme; + async isWritable(loc: ISourceLocation): Promise { + this.logger.trace("[VFS] isWritable: ", loc); + const scheme = this.toUri(loc).scheme; const writable = this.fs.isWritableFileSystem(scheme); if (writable === undefined) { throw new rpc.ResponseError(ErrorCodes.fileSystem, "Unsupported scheme: " + scheme, "Unsupported file system"); @@ -356,30 +343,28 @@ class ResolverClient implements VSCodeResolverServer, Disposable { // not a writable file system, so no need to check the uri return false; } - return this.boolResult(req, f => f.permissions === undefined || (f.permissions & vscode.FilePermission.Readonly) === 0); + return this.boolResult(loc, f => f.permissions === undefined || (f.permissions & vscode.FilePermission.Readonly) === 0); } - async list(req: ISourceLocationRequest): Promise<[string, vscode.FileType][]> { - this.logger.trace("[VFS] list: ", req.uri); + async list(loc: ISourceLocation): Promise<[string, vscode.FileType][]> { + this.logger.trace("[VFS] list: ", loc); return asyncCatcher(async () => { - return await this.fs.readDirectory(this.toUri(req)); + return await this.fs.readDirectory(this.toUri(loc)); }); } - - async writeFile(req: WriteFileRequest): Promise { this.logger.trace("[VFS] writeFile: ", req.uri); return asyncVoidCatcher( - this.fs.writeFile(this.toUri(req), Buffer.from(req.content, "base64")) + this.fs.writeFile(this.toUri(req.uri), Buffer.from(req.content, "base64")) ); } - async mkDirectory(req: ISourceLocationRequest): Promise { - this.logger.trace("[VFS] mkDirectory: ", req.uri); + async mkDirectory(req: ISourceLocation): Promise { + this.logger.trace("[VFS] mkDirectory: ", req); return asyncVoidCatcher(this.fs.createDirectory(this.toUri(req))); } - async remove(req: ISourceLocationRequest, recursive: boolean): Promise { - this.logger.trace("[VFS] remove: ", req.uri); + async remove(req: ISourceLocation, recursive: boolean): Promise { + this.logger.trace("[VFS] remove: ", req); return asyncVoidCatcher(this.fs.delete(this.toUri(req), {"recursive" : recursive})); } async rename(req: RenameRequest): Promise { @@ -403,8 +388,6 @@ class ResolverClient implements VSCodeResolverServer, Disposable { throw new rpc.ResponseError(ErrorCodes.fileSystem, 'Watch already defined for: ' + newWatch.uri, 'AlreadyDefined'); } - - async unwatch(removeWatch: WatchRequest): Promise { this.logger.trace("[VFS] unwatch: ", removeWatch.uri); const watchKey = removeWatch.uri + removeWatch.recursive; From f8d432248e7a9edec6e3fa55514ba4f8b42053c5 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 23 Jan 2026 13:11:25 +0100 Subject: [PATCH 036/149] VS Code now implements logical resolution --- .../src/fs/VSCodeURIResolver.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts index 9a139dae4..1e8a85f0e 100644 --- a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts +++ b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts @@ -118,6 +118,16 @@ function connectWatchHandler(connection: rpc.MessageConnection, handler: ISource req("unwatch", handler.unwatch); } +interface ILogicalSourceLocationResolver { + resolve(req: ISourceLocation) : Promise +} + +function connectLogicalResolver(connection: rpc.MessageConnection, handler: ILogicalSourceLocationResolver, toClear: Disposable[]) { + toClear.push(connection.onRequest( + new rpc.RequestType1("rascal/vfs/logical/resolveLocation"), handler.resolve.bind(handler) + )); +} + // client side implementation receiving watch events export interface WatchEventReceiver { emitWatch(event: ISourceLocationChanged): void; @@ -404,6 +414,10 @@ class ResolverClient implements VSCodeResolverServer, Disposable { throw new rpc.ResponseError(ErrorCodes.fileSystem, 'Watch not defined for: ' + removeWatch.uri, 'NotDefined'); } + async resolve(req: ISourceLocation): Promise { + return req; + } + dispose() { this.activeWatches.clear(); this.toClear.forEach(c => c.dispose()); @@ -413,8 +427,6 @@ class ResolverClient implements VSCodeResolverServer, Disposable { // ignore errors here, ase we are disposing anyway } } - - } class WatcherCallbacks implements Disposable { From 0782638244e486bb8f25a71abd73c116cc38bbfc Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 23 Jan 2026 13:11:58 +0100 Subject: [PATCH 037/149] Temporarily using a snapshot dependency for Rascal --- rascal-lsp/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rascal-lsp/pom.xml b/rascal-lsp/pom.xml index 7af3eabd8..2da865926 100644 --- a/rascal-lsp/pom.xml +++ b/rascal-lsp/pom.xml @@ -64,7 +64,7 @@ org.rascalmpl rascal - 0.41.0-RC78 + 0.41.3-SNAPSHOT From 6c2a2b5bf1a5cb5239c7f1e5340b2652787d83f3 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 10 Feb 2026 09:45:51 +0100 Subject: [PATCH 038/149] Removed NamedThreadPool after porting it to Rascal --- .../vscode/lsp/util/NamedThreadPool.java | 63 ------------------- 1 file changed, 63 deletions(-) delete mode 100644 rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/NamedThreadPool.java diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/NamedThreadPool.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/NamedThreadPool.java deleted file mode 100644 index c37b7d0c7..000000000 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/NamedThreadPool.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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.util; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicInteger; - -public class NamedThreadPool { - private NamedThreadPool() {} - - public static ExecutorService single(String name) { - return Executors.newSingleThreadExecutor(factory(name, false)); - } - - public static ExecutorService singleDaemon(String name) { - return Executors.newSingleThreadExecutor(factory(name, true)); - } - - public static ExecutorService cached(String name) { - return Executors.newCachedThreadPool(factory(name, false)); - } - - public static ExecutorService cachedDaemon(String name) { - return Executors.newCachedThreadPool(factory(name, true)); - } - - private static ThreadFactory factory(String name, boolean daemon) { - AtomicInteger counter = new AtomicInteger(0); - ThreadGroup group = new ThreadGroup(name); - return r -> { - var t = new Thread(group, r, name + "-" + counter.incrementAndGet()); - t.setDaemon(daemon); - return t; - }; - } - -} From 42f9f5e025cdcac5af0908357d87cfc7c33d5f92 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 10 Feb 2026 09:46:30 +0100 Subject: [PATCH 039/149] Updated references to NamedThreadPool --- .../org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java | 2 +- .../vscode/lsp/parametric/ParametricLanguageServer.java | 2 +- .../org/rascalmpl/vscode/lsp/parametric/RascalInterface.java | 2 +- .../org/rascalmpl/vscode/lsp/rascal/RascalLanguageServer.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java index 766f7dced..db51e0b9b 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java @@ -57,9 +57,9 @@ import org.rascalmpl.uri.vfs.FileAttributesResult; import org.rascalmpl.uri.vfs.FileAttributesResult.FilePermission; import org.rascalmpl.uri.vfs.FileAttributesResult.FileType; +import org.rascalmpl.util.NamedThreadPool; import org.rascalmpl.uri.vfs.IRemoteResolverRegistry; import org.rascalmpl.values.IRascalValueFactory; -import org.rascalmpl.vscode.lsp.util.NamedThreadPool; import org.rascalmpl.vscode.lsp.util.locations.Locations; import io.usethesource.vallang.ISourceLocation; diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricLanguageServer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricLanguageServer.java index 5913f1a12..dec7c378c 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricLanguageServer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricLanguageServer.java @@ -27,9 +27,9 @@ package org.rascalmpl.vscode.lsp.parametric; +import org.rascalmpl.util.NamedThreadPool; import org.rascalmpl.vscode.lsp.BaseLanguageServer; import org.rascalmpl.vscode.lsp.parametric.LanguageRegistry.LanguageParameter; -import org.rascalmpl.vscode.lsp.util.NamedThreadPool; import com.google.gson.GsonBuilder; diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/RascalInterface.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/RascalInterface.java index e9fb65553..5a303d22d 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/RascalInterface.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/RascalInterface.java @@ -37,9 +37,9 @@ import org.rascalmpl.debug.IRascalMonitor; import org.rascalmpl.exceptions.RuntimeExceptionFactory; import org.rascalmpl.uri.URIUtil; +import org.rascalmpl.util.NamedThreadPool; import org.rascalmpl.values.parsetrees.ITree; import org.rascalmpl.vscode.lsp.parametric.LanguageRegistry.LanguageParameter; -import org.rascalmpl.vscode.lsp.util.NamedThreadPool; import org.rascalmpl.vscode.lsp.util.locations.impl.TreeSearch; import io.usethesource.vallang.IConstructor; diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServer.java index e36353df8..8cb387529 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServer.java @@ -29,8 +29,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.rascalmpl.util.NamedThreadPool; import org.rascalmpl.vscode.lsp.BaseLanguageServer; -import org.rascalmpl.vscode.lsp.util.NamedThreadPool; public class RascalLanguageServer extends BaseLanguageServer { public static void main(String[] args) { From 12d2841903d217d113841a36f9623e8c39ffb01c Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 10 Feb 2026 09:47:40 +0100 Subject: [PATCH 040/149] Removed defunct VFS classes and their uses --- .../vscode/lsp/BaseLanguageServer.java | 7 - .../lsp/IBaseLanguageServerExtensions.java | 5 - .../lsp/uri/jsonrpc/impl/VSCodeVFSClient.java | 230 ------------------ .../lsp/uri/jsonrpc/messages/VFSRegister.java | 60 ----- 4 files changed, 302 deletions(-) delete mode 100644 rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/impl/VSCodeVFSClient.java delete mode 100644 rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/VFSRegister.java diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java index bf0aaf906..c9ed8986c 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java @@ -67,9 +67,7 @@ import org.rascalmpl.vscode.lsp.log.LogRedirectConfiguration; import org.rascalmpl.vscode.lsp.parametric.LanguageRegistry.LanguageParameter; import org.rascalmpl.vscode.lsp.terminal.RemoteIDEServicesThread; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.impl.VSCodeVFSClient; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.PathConfigParameter; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.VFSRegister; import org.rascalmpl.vscode.lsp.util.concurrent.CompletableFutureUtils; import org.rascalmpl.vscode.lsp.util.locations.Locations; @@ -315,11 +313,6 @@ public void connect(LanguageClient client) { logger.debug("Remote IDE Services Port {}", remoteIDEServicesConfiguration); } - @Override - public void registerVFS(VFSRegister registration) { - VSCodeVFSClient.buildAndRegister(registration.getPort()); - } - @Override public void cancelProgress(WorkDoneProgressCancelParams params) { lspDocumentService.cancelProgress(params.getToken().getLeft()); diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseLanguageServerExtensions.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseLanguageServerExtensions.java index 743cabc7e..b8746689b 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseLanguageServerExtensions.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseLanguageServerExtensions.java @@ -27,7 +27,6 @@ package org.rascalmpl.vscode.lsp; import java.net.URI; -import java.util.List; import java.util.concurrent.CompletableFuture; import org.eclipse.lsp4j.jsonrpc.messages.Tuple.Two; @@ -36,7 +35,6 @@ import org.eclipse.lsp4j.services.LanguageServer; import org.rascalmpl.vscode.lsp.parametric.LanguageRegistry.LanguageParameter; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.PathConfigParameter; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.VFSRegister; public interface IBaseLanguageServerExtensions extends LanguageServer { @JsonNotification("rascal/supplyRemoteIDEServicesConfiguration") @@ -59,9 +57,6 @@ default CompletableFuture[]> supplyPathConfig(PathConfigParam throw new UnsupportedOperationException(); } - @JsonNotification("rascal/vfs/register") - void registerVFS(VFSRegister registration); - @JsonNotification("rascal/logLevel") void setMinimumLogLevel(String level); diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/impl/VSCodeVFSClient.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/impl/VSCodeVFSClient.java deleted file mode 100644 index 60590ab0b..000000000 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/impl/VSCodeVFSClient.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * 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.uri.jsonrpc.impl; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.Socket; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; -import java.util.concurrent.CompletionException; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.ExecutorService; -import java.util.function.Consumer; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.eclipse.lsp4j.jsonrpc.Launcher; -import org.rascalmpl.uri.ISourceLocationWatcher; -import org.rascalmpl.uri.vfs.IRemoteResolverRegistry; -import org.rascalmpl.uri.vfs.IRemoteResolverRegistry.WatchRequest; -import org.rascalmpl.vscode.lsp.IRascalFileSystemServices; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeUriResolverClient; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeVFS; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.ISourceLocationChanged; -import org.rascalmpl.vscode.lsp.util.NamedThreadPool; - -import io.usethesource.vallang.ISourceLocation; - -public class VSCodeVFSClient implements VSCodeUriResolverClient, AutoCloseable { - private static final Logger logger = LogManager.getLogger(VSCodeVFSClient.class); - - private final Map watchers = new ConcurrentHashMap<>(); - private final Map watchersById = new ConcurrentHashMap<>(); - private final Socket socket; - - private VSCodeVFSClient(Socket socket) { - this.socket = socket; - } - - @Override - public void close() { - try { - this.socket.close(); - } catch (IOException e) { - logger.debug("Closing failed", e); - } - } - - @Override - public void emitWatch(ISourceLocationChanged event) { - logger.trace("emitWatch: {}", event); - var watch = watchersById.get(event.getWatchId()); - if (watch != null) { - watch.publish(event.translate()); - } - } - - @Override - public void addWatcher(ISourceLocation loc, boolean recursive, Consumer callback, IRemoteResolverRegistry server) throws IOException { - logger.trace("addWatcher: {}", loc); - try { - var watch = watchers.computeIfAbsent(new WatchSubscriptionKey(loc, recursive), k -> { - logger.trace("Fresh watch, setting up request to server"); - var result = new Watchers(); - result.addNewWatcher(callback); - watchersById.put(result.id, result); - server.watch(new WatchRequest(loc, recursive, result.id)).join(); - return result; - }); - watch.addNewWatcher(callback); - } catch (CompletionException ce) { - logger.error("Error setting up watch", ce.getCause()); - throw new IOException(ce.getCause()); - } - } - - @Override - public void removeWatcher(ISourceLocation loc, boolean recursive, - Consumer callback, - IRemoteResolverRegistry server) throws IOException { - logger.trace("removeWatcher: {}", loc); - var watchKey = new WatchSubscriptionKey(loc, recursive); - var watch = watchers.get(watchKey); - if (watch != null && watch.removeWatcher(callback)) { - logger.trace("No other watchers registered, so unregistering at server"); - watchers.remove(watchKey); - if (!watch.callbacks.isEmpty()) { - logger.trace("Raced by another thread, canceling unregister"); - watchers.put(watchKey, watch); - return; - } - watchersById.remove(watch.id); - try { - server.unwatch(new WatchRequest(loc, recursive, watch.id)).join(); - } catch (CompletionException ce) { - logger.error("Error removing watch", ce.getCause()); - throw new IOException(ce.getCause()); - } - } - } - - private static class WatchSubscriptionKey { - private final ISourceLocation loc; - private final boolean recursive; - public WatchSubscriptionKey(ISourceLocation loc, boolean recursive) { - this.loc = loc; - this.recursive = recursive; - } - - @Override - public int hashCode() { - return Objects.hash(loc, recursive); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if ((obj instanceof WatchSubscriptionKey)) { - WatchSubscriptionKey other = (WatchSubscriptionKey) obj; - return recursive == other.recursive - && Objects.equals(loc, other.loc) - ; - } - return false; - } - - } - - - - private static final ExecutorService exec = NamedThreadPool.cachedDaemon("FallbackResolver-watcher"); - - /** - * The watch api in rascal uses closures identity to keep track of watches. - * Since we cannot share the instance via the json-rpc bridge, we keep the - * closure around in this collection class. - * If there are no more callbacks registered, we unregister the watch at the - * VSCode side. - */ - private static class Watchers { - private final String id; - private final List> callbacks = new CopyOnWriteArrayList<>(); - - public Watchers() { - this.id = UUID.randomUUID().toString(); - } - - public void addNewWatcher(Consumer watcher) { - this.callbacks.add(watcher); - } - - public boolean removeWatcher(Consumer watcher) { - this.callbacks.remove(watcher); - return this.callbacks.isEmpty(); - } - - public void publish(ISourceLocationWatcher.ISourceLocationChanged changed) { - for (Consumer c : callbacks) { - // schedule callbacks on different thread - exec.submit(() -> c.accept(changed)); - } - } - - - } - - public static void buildAndRegister(int port) { - try { - var existingClient = VSCodeVFS.INSTANCE.getClient(); - if (existingClient instanceof AutoCloseable) { - try { - ((AutoCloseable)existingClient).close(); - } catch (Exception e) { - logger.error("Error closing old client", e); - } - } - - logger.debug("Connecting to VFS: {}", port); - @SuppressWarnings("java:S2095") // we don't have to close the socket, we are passing it off to the lsp4j framework - var socket = new Socket(InetAddress.getLoopbackAddress(), port); - socket.setTcpNoDelay(true); - @SuppressWarnings("java:S2095") // we don't have to close the client, we are passing it off to the VSCodeVFS singleton - var newClient = new VSCodeVFSClient(socket); - Launcher clientLauncher = new Launcher.Builder() - .setRemoteInterface(IRemoteResolverRegistry.class) - .setLocalService(newClient) - .setInput(socket.getInputStream()) - .setOutput(socket.getOutputStream()) - .setExecutorService(IRascalFileSystemServices.executor) - .create(); - - clientLauncher.startListening(); - - VSCodeVFS.INSTANCE.provideServer(clientLauncher.getRemoteProxy()); - VSCodeVFS.INSTANCE.provideClient(newClient); - } catch (Throwable e) { - logger.error("Error setting up VFS connection", e); - } - } -} diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/VFSRegister.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/VFSRegister.java deleted file mode 100644 index fddafd73e..000000000 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/VFSRegister.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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.uri.jsonrpc.messages; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.eclipse.lsp4j.jsonrpc.validation.NonNull; - -public class VFSRegister { - @NonNull - private int port; - - public VFSRegister() { - } - - public VFSRegister(int port) { - this.port = port; - } - - public int getPort() { - return port; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (obj instanceof VFSRegister) { - return port == ((VFSRegister)obj).port; - } - return false; - } - - @Override - public int hashCode() { - return port; - } - -} From 9e425f2f4119d85525249fb753ad5fcc075fe6fb Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 10 Feb 2026 09:49:06 +0100 Subject: [PATCH 041/149] Removed WatchParameters interface and updated watch rpc calls --- .../src/fs/RascalFileSystemProviders.ts | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts b/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts index d8f9c7b8d..5c9411af1 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts @@ -97,15 +97,15 @@ export class RascalFileSystemProvider implements vscode.FileSystemProvider { } watch(uri: vscode.Uri, options: { recursive: boolean; excludes: string[]; }): vscode.Disposable { - this.sendRequest(uri, "rascal/vfs/watcher/watch", { - uri: uri.toString(), - recursive:options.recursive, - excludes: options.excludes + this.sendRequest(uri, "rascal/vfs/watcher/watch", { + loc: uri.toString(), + recursive: options.recursive }); return new vscode.Disposable(() => { this.sendRequest(uri, "rascal/vfs/watcher/unwatch", { - uri: uri.toString() + loc: uri.toString(), + recursive: options.recursive }); }); } @@ -151,12 +151,6 @@ function isUnknownFileSystem(scheme : string) : boolean { return vscode.workspace.fs.isWritableFileSystem(scheme) === undefined; } -interface WatchParameters { - uri: string; - recursive: boolean; - excludes:Array; -} - interface FileWithType { name: string; type: vscode.FileType From 4e398d2b2cb0ee97d6deb087aeb394552275a391 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 10 Feb 2026 09:50:20 +0100 Subject: [PATCH 042/149] Renamed uri to loc --- .../src/fs/VSCodeURIResolver.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts index 1e8a85f0e..187f0716b 100644 --- a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts +++ b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts @@ -165,7 +165,7 @@ export interface RenameRequest { } export interface WatchRequest { - uri: ISourceLocation; + loc: ISourceLocation; /** * subscription id, this helps the calling in linking up to the original request * as the watches are recursive @@ -387,20 +387,20 @@ class ResolverClient implements VSCodeResolverServer, Disposable { private readonly activeWatches = new Map(); async watch(newWatch: WatchRequest): Promise { - this.logger.trace("[VFS] watch: ", newWatch.uri); - const watchKey = newWatch.uri + newWatch.recursive; + this.logger.trace("[VFS] watch: ", newWatch.loc); + const watchKey = newWatch.loc + newWatch.recursive; if (!this.activeWatches.has(watchKey)) { - const watcher = new WatcherCallbacks(this.toUri(newWatch.uri), newWatch.recursive, this.watchListener, newWatch.watcher); + const watcher = new WatcherCallbacks(this.toUri(newWatch.loc), newWatch.recursive, this.watchListener, newWatch.watcher); this.activeWatches.set(watchKey, watcher); this.toClear.push(watcher); return; } - throw new rpc.ResponseError(ErrorCodes.fileSystem, 'Watch already defined for: ' + newWatch.uri, 'AlreadyDefined'); + throw new rpc.ResponseError(ErrorCodes.fileSystem, 'Watch already defined for: ' + newWatch.loc, 'AlreadyDefined'); } async unwatch(removeWatch: WatchRequest): Promise { - this.logger.trace("[VFS] unwatch: ", removeWatch.uri); - const watchKey = removeWatch.uri + removeWatch.recursive; + this.logger.trace("[VFS] unwatch: ", removeWatch.loc); + const watchKey = removeWatch.loc + removeWatch.recursive; const watcher = this.activeWatches.get(watchKey); if (watcher) { this.activeWatches.delete(watchKey); @@ -411,7 +411,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { } return; } - throw new rpc.ResponseError(ErrorCodes.fileSystem, 'Watch not defined for: ' + removeWatch.uri, 'NotDefined'); + throw new rpc.ResponseError(ErrorCodes.fileSystem, 'Watch not defined for: ' + removeWatch.loc, 'NotDefined'); } async resolve(req: ISourceLocation): Promise { From cf1208eed11701c02287bddb5b81655acfd2f058 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 10 Feb 2026 09:50:40 +0100 Subject: [PATCH 043/149] Removed vfs registration call from the extension --- rascal-vscode-extension/src/lsp/RascalLSPConnection.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts b/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts index 41c4ed403..4f370cb78 100644 --- a/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts +++ b/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts @@ -54,9 +54,6 @@ export async function activateLanguageClient( await client.start(); logger.setClient(client); - client.sendNotification("rascal/vfs/register", { - port: await vfsServer.serverPort - }); client.onNotification("rascal/showContent", (uri: string, title: string, viewColumn: integer) => { showContentPanel(uri, title, viewColumn); From c64c6670fb2e420daf083c784e1ad8b9ec8ceebf Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 11 Feb 2026 15:12:56 +0100 Subject: [PATCH 044/149] Ported Lazy to rascal --- .../parametric/model/ParametricSummary.java | 2 +- .../lsp/rascal/model/SummaryBridge.java | 2 +- .../vscode/lsp/uri/FallbackResolver.java | 1 - .../org/rascalmpl/vscode/lsp/util/Lazy.java | 50 ------------------- 4 files changed, 2 insertions(+), 53 deletions(-) delete mode 100644 rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/Lazy.java diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/model/ParametricSummary.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/model/ParametricSummary.java index 6ec7c57f7..bbfd6adb4 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/model/ParametricSummary.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/model/ParametricSummary.java @@ -43,6 +43,7 @@ import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.rascalmpl.util.Lazy; import org.rascalmpl.util.locations.ColumnMaps; import org.rascalmpl.values.IRascalValueFactory; import org.rascalmpl.values.parsetrees.ITree; @@ -54,7 +55,6 @@ import org.rascalmpl.vscode.lsp.parametric.model.RascalADTs.SummaryFields; import org.rascalmpl.vscode.lsp.rascal.conversion.Diagnostics; import org.rascalmpl.vscode.lsp.rascal.conversion.KeywordParameter; -import org.rascalmpl.vscode.lsp.util.Lazy; import org.rascalmpl.vscode.lsp.util.Versioned; import org.rascalmpl.vscode.lsp.util.concurrent.InterruptibleFuture; import org.rascalmpl.vscode.lsp.util.locations.IRangeMap; diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/model/SummaryBridge.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/model/SummaryBridge.java index a5021c7c7..f9ee2103e 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/model/SummaryBridge.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/model/SummaryBridge.java @@ -35,11 +35,11 @@ import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; +import org.rascalmpl.util.Lazy; import org.rascalmpl.util.locations.ColumnMaps; import org.rascalmpl.values.IRascalValueFactory; import org.rascalmpl.values.ValueFactoryFactory; import org.rascalmpl.vscode.lsp.rascal.conversion.KeywordParameter; -import org.rascalmpl.vscode.lsp.util.Lazy; import org.rascalmpl.vscode.lsp.util.locations.IRangeMap; import org.rascalmpl.vscode.lsp.util.locations.Locations; import org.rascalmpl.vscode.lsp.util.locations.impl.TreeMapLookup; diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java index c58af2d15..a9230b9d3 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java @@ -44,7 +44,6 @@ import org.rascalmpl.uri.URIUtil; import org.rascalmpl.vscode.lsp.IBaseTextDocumentService; import org.rascalmpl.vscode.lsp.TextDocumentState; -import org.rascalmpl.vscode.lsp.util.Lazy; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/Lazy.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/Lazy.java deleted file mode 100644 index 2f6622a95..000000000 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/Lazy.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.util; - -import java.util.function.Supplier; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.NonNull; - -public interface Lazy extends Supplier { - public static Lazy defer(Supplier generator) { - return new Lazy(){ - private volatile @MonotonicNonNull T result = null; - - @Override - public T get() { - if (result == null) { - result = generator.get(); - } - return result; - } - - }; - - } - -} From 7b144ad603f5871035caaa2cb568198525a9f952 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 11 Feb 2026 15:13:32 +0100 Subject: [PATCH 045/149] Removed defunct directory listing cache from FallbackResolver --- .../vscode/lsp/uri/FallbackResolver.java | 60 ------------------- 1 file changed, 60 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java index a9230b9d3..877f8b750 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java @@ -28,39 +28,25 @@ import java.io.IOException; import java.net.URISyntaxException; -import java.nio.file.AccessDeniedException; -import java.nio.file.FileAlreadyExistsException; -import java.nio.file.NoSuchFileException; -import java.nio.file.NotDirectoryException; -import java.time.Duration; import java.util.List; -import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import org.checkerframework.checker.initialization.qual.UnderInitialization; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.eclipse.lsp4j.jsonrpc.ResponseErrorException; import org.rascalmpl.uri.ILogicalSourceLocationResolver; import org.rascalmpl.uri.URIUtil; import org.rascalmpl.vscode.lsp.IBaseTextDocumentService; import org.rascalmpl.vscode.lsp.TextDocumentState; -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.google.gson.JsonPrimitive; - import io.usethesource.vallang.ISourceLocation; public class FallbackResolver implements ILogicalSourceLocationResolver { private static @MonotonicNonNull FallbackResolver instance = null; - // The FallbackResolver is dynamically instantiated by URIResolverRegistry. By implementing it as a singleton and - // making it avaible through this method, we allow the IBaseTextDocumentService implementations to interact with it. public static FallbackResolver getInstance() { if (instance == null) { instance = new FallbackResolver(); - //throw new IllegalStateException("FallbackResolver accessed before initialization"); } return instance; } @@ -69,18 +55,6 @@ private FallbackResolver() { instance = this; } - /** - * Rascal's current implementions sometimes ask for a directory listing - * and then iterate over all the entries checking if they are a directory. - * This is super slow for this jsonrcp, so we store the last directory listing - * and check inside - */ - private final Cache>> cachedDirectoryListing - = Caffeine.newBuilder() - .expireAfterWrite(Duration.ofSeconds(5)) - .maximumSize(1000) - .build(); - @Override public String scheme() { throw new UnsupportedOperationException("Scheme not supported on fallback resolver"); @@ -131,38 +105,4 @@ public TextDocumentState getDocumentState(ISourceLocation file) throws IOExcepti } throw new IOException("File is not managed by lsp"); } - - private static IOException translateException(ResponseErrorException cause) { - var error = cause.getResponseError(); - switch (error.getCode()) { - case -1: return new IOException("Generic error: " + error.getMessage()); - case -2: { - if (error.getData() instanceof JsonPrimitive) { - var data = (JsonPrimitive)error.getData(); - if (data.isString()) { - switch (data.getAsString()) { - case "FileExists": // fall-through - case "EntryExists": - return new FileAlreadyExistsException(error.getMessage()); - case "FileNotFound": // fall-through - case "EntryNotFound": - return new NoSuchFileException(error.getMessage()); - case "FileNotADirectory": // fall-through - case "EntryNotADirectory": - return new NotDirectoryException(error.getMessage()); - case "FileIsADirectory": // fall-through - case "EntryIsADirectory": - return new IOException("File is a directory: " + error.getMessage()); - case "NoPermissions": - return new AccessDeniedException(error.getMessage()); - } - } - } - return new IOException("File system error: " + error.getMessage() + " data: " + error.getData()); - } - case -3: return new IOException("Rascal native scheme's should not be forwarded to VS Code"); - default: return new IOException("Missing case for: " + error); - } - } - } From f19df9f7924a5eebfcfad8e407eb14a2b6924d53 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 17 Mar 2026 13:28:58 +0100 Subject: [PATCH 046/149] Layout --- .../org/rascalmpl/vscode/lsp/rascal/conversion/Diagnostics.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/conversion/Diagnostics.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/conversion/Diagnostics.java index c5ba8b86f..e6decfa78 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/conversion/Diagnostics.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/conversion/Diagnostics.java @@ -277,7 +277,7 @@ private static boolean hasValidLocation(IConstructor d) { return isValidLocation(getMessageLocation(d), d); } - private static boolean isValidLocation( ISourceLocation loc, IValue m) { + private static boolean isValidLocation(ISourceLocation loc, IValue m) { if (loc == null || loc.getScheme().equals("unknown")) { logger.trace("Dropping diagnostic due to incorrect location on message: {}", m); return false; From 780e930f62ad8bdc7188f83935db6cb4ebc6811e Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 17 Mar 2026 13:29:07 +0100 Subject: [PATCH 047/149] Improved logging message --- .../org/rascalmpl/vscode/lsp/rascal/conversion/Diagnostics.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/conversion/Diagnostics.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/conversion/Diagnostics.java index e6decfa78..39e5c2f64 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/conversion/Diagnostics.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/conversion/Diagnostics.java @@ -291,7 +291,7 @@ private static boolean isValidLocation(ISourceLocation loc, IValue m) { if (loc.getPath().endsWith("/pom.xml")) { return true; } - logger.error("Filtering diagnostic as it's an unsupported file to report diagnostics on: {}", m); + logger.error("Filtering diagnostic as it's an unsupported file to report diagnostics on ({}): {}", loc, m); return false; } } From f18a935905392bbe0610744f703cf47d1f080042 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 17 Mar 2026 13:29:35 +0100 Subject: [PATCH 048/149] Fixed constructor logic for FallbackResolver --- .../java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java index 877f8b750..bc6ecd496 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java @@ -51,9 +51,7 @@ public static FallbackResolver getInstance() { return instance; } - private FallbackResolver() { - instance = this; - } + private FallbackResolver() { } @Override public String scheme() { From 7960949f37309c835c50db1863c846f9d0de4468 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 17 Mar 2026 13:35:55 +0100 Subject: [PATCH 049/149] Updated json-rpc method names --- rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts b/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts index 5c9411af1..2b67bf490 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts @@ -42,7 +42,7 @@ export class RascalFileSystemProvider implements vscode.FileSystemProvider { constructor (client:BaseLanguageClient, private readonly logger: vscode.LogOutputChannel) { this.client = client; - client.onNotification("rascal/filesystem/onDidChangeFile", (event:vscode.FileChangeEvent) => { + client.onNotification("rascal/vfs/watcher/fileChanged", (event:vscode.FileChangeEvent) => { this._emitter.fire([event]); }); } @@ -120,11 +120,11 @@ export class RascalFileSystemProvider implements vscode.FileSystemProvider { } createDirectory(uri: vscode.Uri): void | Thenable { - return this.sendRequest(uri, "rascal/filesystem/createDirectory", {uri: uri.toString()}); + return this.sendRequest(uri, "rascal/vfs/output/mkDirectory"); } readFile(uri: vscode.Uri): Uint8Array | Thenable { - return this.sendRequest(uri, "rascal/vfs/output/mkDirectory") + return this.sendRequest(uri, "rascal/vfs/input/readFile") .then(str => Buffer.from(str, "base64")); } From 9ae44e2b38684bbb784e31bbe4e2031e51ce1a89 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 17 Mar 2026 13:38:07 +0100 Subject: [PATCH 050/149] JsonRpcServer now has a debug mode for second level purposes --- rascal-vscode-extension/src/fs/VSCodeURIResolver.ts | 2 +- rascal-vscode-extension/src/lsp/LanguageRegistry.ts | 2 +- rascal-vscode-extension/src/util/JsonRpcServer.ts | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts index 187f0716b..5a7c5274c 100644 --- a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts +++ b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts @@ -195,7 +195,7 @@ enum ErrorCodes { export class VSCodeUriResolverServer extends JsonRpcServer { private rascalNativeSchemes: Set = new Set(); constructor(debug: boolean, private readonly logger: vscode.LogOutputChannel) { - super("VFS", connection => new ResolverClient(connection, debug, this.rascalNativeSchemes, this.logger), logger); + super("VFS", debug, connection => new ResolverClient(connection, debug, this.rascalNativeSchemes, this.logger), logger); } ignoreSchemes(toIgnore: string[]) { diff --git a/rascal-vscode-extension/src/lsp/LanguageRegistry.ts b/rascal-vscode-extension/src/lsp/LanguageRegistry.ts index 53e565a41..c93f266bd 100644 --- a/rascal-vscode-extension/src/lsp/LanguageRegistry.ts +++ b/rascal-vscode-extension/src/lsp/LanguageRegistry.ts @@ -34,7 +34,7 @@ import { JsonRpcServer } from '../util/JsonRpcServer'; */ export class LanguageRegistry extends JsonRpcServer { constructor(dslLSP: ParameterizedLanguageServer, logger: LogOutputChannel) { - super("LanguageRegistry", connection => Disposable.from( + super("LanguageRegistry", false, connection => Disposable.from( connection.onRequest(new rpc.RequestType1("rascal/receiveRegisterLanguage"), lang => { logger.info("LanguageRegistry: registerLanguage", lang); return dslLSP.registerLanguage(lang); diff --git a/rascal-vscode-extension/src/util/JsonRpcServer.ts b/rascal-vscode-extension/src/util/JsonRpcServer.ts index 3b11130b4..18861ffe0 100644 --- a/rascal-vscode-extension/src/util/JsonRpcServer.ts +++ b/rascal-vscode-extension/src/util/JsonRpcServer.ts @@ -36,7 +36,7 @@ export class JsonRpcServer implements Disposable { private readonly server: net.Server; private activeClients: net.Socket[] = []; - constructor(name: string, connectHandlers: (connection: rpc.MessageConnection) => Disposable, logger: LogOutputChannel) { + constructor(name: string, debug: boolean, connectHandlers: (connection: rpc.MessageConnection) => Disposable, logger: LogOutputChannel) { this.server = net.createServer({noDelay: true}, newClient => { logger.info(`${name}: new connection ${JSON.stringify(newClient.address())}`); newClient.setNoDelay(true); @@ -64,12 +64,13 @@ export class JsonRpcServer implements Disposable { }); this.server.on('error', e => logger.error(`${name} (server): ${e}`)); this.serverPort = new Promise((r, e) => { - this.server.listen(0, "localhost", undefined, () => { + this.server.listen(debug ? 8889 : 0, "localhost", undefined, () => { const address = this.server.address(); if (address && typeof(address) !== "string" && 'port' in address) { logger.debug(`${name}: listening on ${JSON.stringify(address)}`); r(address.port); } else { + logger.error(`${name}: failed to start listening on ${JSON.stringify(address)}`); e(new Error(`${name}: server address not valid: ${JSON.stringify(address)}`)); } }); From 579bb690d32a0ae3fe5fa2c51ae3564b49af13bc Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 17 Mar 2026 13:42:04 +0100 Subject: [PATCH 051/149] Layout --- rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts b/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts index 2b67bf490..e7a142df7 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts @@ -25,7 +25,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ import * as vscode from 'vscode'; -import {BaseLanguageClient, ResponseError } from 'vscode-languageclient'; +import { BaseLanguageClient, ResponseError } from 'vscode-languageclient'; export class RascalFileSystemProvider implements vscode.FileSystemProvider { readonly client: BaseLanguageClient; From 853d09f6a5c04e36c594b624159dc423abe3e226 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 17 Mar 2026 13:46:26 +0100 Subject: [PATCH 052/149] Added FileAttributes interface that models Rascal's stat result and using in when doing a remote stat --- .../src/fs/RascalFileSystemProviders.ts | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts b/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts index e7a142df7..dd26d2a21 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts @@ -111,7 +111,15 @@ export class RascalFileSystemProvider implements vscode.FileSystemProvider { } stat(uri: vscode.Uri): vscode.FileStat | Thenable { - return this.sendRequest(uri, "rascal/vfs/input/stat"); + return this.sendRequest(uri, "rascal/vfs/input/stat").then(a => + { + type: a.isFile ? vscode.FileType.File : vscode.FileType.Directory, + ctime: a.created, + mtime: a.lastModified, + size: a.size, + permissions: a.isWritable ? undefined : vscode.FilePermission.Readonly + } + ); } readDirectory(uri: vscode.Uri): [string, vscode.FileType][] | Thenable<[string, vscode.FileType][]> { @@ -155,3 +163,13 @@ interface FileWithType { name: string; type: vscode.FileType } + +interface FileAttributes { + exists: boolean; + isFile: boolean; + created: number; + lastModified: number; + isWritable: boolean; + isReadable: boolean; + size: number; +} From 381347d59fca704d18e718785d3452465ae87476 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 17 Mar 2026 13:49:09 +0100 Subject: [PATCH 053/149] Added a translation function that makes sure VS Code uris are mapped to (Rascal-supported) uris with three slashes. VS Code models non-file URIs without an authority with a single slash character after the colon. Rascal does not have (full) support for this, so these uris are rewritten to contain three slashes before sending them to Rascal --- .../src/fs/RascalFileSystemProviders.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts b/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts index dd26d2a21..fccd35c1f 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts @@ -47,6 +47,17 @@ export class RascalFileSystemProvider implements vscode.FileSystemProvider { }); } + // VS Code omits the leading two slashes from URIs if the autority is empty *and* the scheme is not equals to "file" + // Rascal does not support this style of URIs, so we add the slashes before sending the URI over + toRascalUri(uri: vscode.Uri) : string { + const uriString = uri.toString(); + if (uri.authority === "" && uri.scheme !== "file") { + const colon = uri.scheme.length + 1; + return `${uriString.slice(0, colon)}//${uriString.slice(colon)}`; + } + return uriString; + } + sendRequest(uri : vscode.Uri, method: string): Promise; sendRequest(uri : vscode.Uri, method: string, param: A): Promise; sendRequest(uri : vscode.Uri, method: string, param0: A0, param1: A1): Promise; From 9fbcd2c58cf3aa293919175b66ccf90f654f1988 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 17 Mar 2026 13:49:50 +0100 Subject: [PATCH 054/149] Using URI translation function --- .../src/fs/RascalFileSystemProviders.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts b/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts index fccd35c1f..9cc675654 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts @@ -62,7 +62,7 @@ export class RascalFileSystemProvider implements vscode.FileSystemProvider { sendRequest(uri : vscode.Uri, method: string, param: A): Promise; sendRequest(uri : vscode.Uri, method: string, param0: A0, param1: A1): Promise; sendRequest(uri : vscode.Uri, method: string, param?: A): Promise { - return this.client.sendRequest(method, param ?? { uri: uri.toString()} ) + return this.client.sendRequest(method, param ?? this.toRascalUri(uri) ) .catch((r: ResponseError) => { if (r !== undefined) { this.logger.debug("Got response error from the file system: ", r); @@ -109,13 +109,13 @@ export class RascalFileSystemProvider implements vscode.FileSystemProvider { watch(uri: vscode.Uri, options: { recursive: boolean; excludes: string[]; }): vscode.Disposable { this.sendRequest(uri, "rascal/vfs/watcher/watch", { - loc: uri.toString(), + loc: this.toRascalUri(uri), recursive: options.recursive }); return new vscode.Disposable(() => { this.sendRequest(uri, "rascal/vfs/watcher/unwatch", { - loc: uri.toString(), + loc: this.toRascalUri(uri), recursive: options.recursive }); }); @@ -158,11 +158,11 @@ export class RascalFileSystemProvider implements vscode.FileSystemProvider { } delete(uri: vscode.Uri, options: { recursive: boolean; }): void | Thenable { - return this.sendRequest(uri, "rascal/vfs/output/remove", uri.toString(), options.recursive); + return this.sendRequest(uri, "rascal/vfs/output/remove", {uri: this.toRascalUri(uri), recursive: options.recursive}); } rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean; }): void | Thenable { - return this.sendRequest(oldUri, "rascal/filesystem/rename", {oldUri: oldUri.toString(), newUri: newUri.toString(), overwrite: options.overwrite}); + return this.sendRequest(oldUri, "rascal/filesystem/rename", {oldUri: this.toRascalUri(oldUri), newUri: this.toRascalUri(newUri), overwrite: options.overwrite}); } } From d5590cc21e3d4921734303665c9148666eab1e9b Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 17 Mar 2026 13:50:52 +0100 Subject: [PATCH 055/149] Handling VS Code's create and overwrite flags for writeFile on the TS side --- .../src/fs/RascalFileSystemProviders.ts | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts b/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts index 9cc675654..255c73276 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts @@ -148,12 +148,26 @@ export class RascalFileSystemProvider implements vscode.FileSystemProvider { } writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean; }): void | Thenable { - return this.sendRequest(uri, "rascal/vfs/output/writeFile", { - uri: uri.toString(), - append: false, - create:options.create, - overwrite:options.overwrite, - content: Buffer.from(content).toString("base64") + // The `create` and `overwrite` options are handled on this side + this.sendRequest(uri, "rascal/vfs/input/stat").then(s => { + if (!s.exists && !options.create) { + throw vscode.FileSystemError.FileNotFound(`File ${uri} does not exist and \`create\` was not set`); + } + this.sendRequest(uri,"rascal/vfs/input/stat").then(p => { + if (!p.exists && options.create) { + throw vscode.FileSystemError.FileNotFound(`Parent of ${uri} does not exist but \`create\` was set`); + } + if (s.exists && options.create && !options.overwrite) { + throw vscode.FileSystemError.FileExists(`File ${uri} exists and \`create\` was set, but \`override\` was not set`); + } + return this.sendRequest(uri, "rascal/vfs/output/writeFile", { + uri: this.toRascalUri(uri), + append: false, + create: options.create, + overwrite: options.overwrite, + content: Buffer.from(content).toString("base64") + }); + }); }); } From 4b0e506b09ab88261055b79dfb08bfb60f97e754 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 17 Mar 2026 13:54:28 +0100 Subject: [PATCH 056/149] Changed FileAttributesResult to FileAttributes to match the Rascal abstraction --- .../src/fs/VSCodeURIResolver.ts | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts index 5a7c5274c..0c10010b6 100644 --- a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts +++ b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts @@ -53,7 +53,7 @@ interface ISourceLocationInput { isFile(req: ISourceLocation): Promise; list(req: ISourceLocation): Promise<[string, vscode.FileType][]>; size(req: ISourceLocation): Promise; - fileStat(req: ISourceLocation): Promise; + fileStat(req: ISourceLocation): Promise; isReadable(req: ISourceLocation): Promise; isWritable(req: ISourceLocation): Promise; } @@ -72,7 +72,7 @@ function connectInputHandler(connection: rpc.MessageConnection, handler: ISource req("isFile", handler.isFile); req<[string, vscode.FileType][]>("list", handler.list); req("size", handler.size); - req("stat", handler.fileStat); + req("stat", handler.fileStat); req("isReadable", handler.isReadable); req("isWritable", handler.isWritable); } @@ -143,13 +143,14 @@ function buildWatchReceiver(connection: rpc.MessageConnection) : WatchEventRecei // Messages (requests and responses) -export interface FileAttributesResult { +export interface FileAttributes { exists : boolean; - type: vscode.FileType; - ctime: number; - mtime: number; + isFile: boolean; + created: number; + lastModified: number; + isReadable: boolean; + isWritable: boolean; size: number; - permissions: vscode.FilePermission; } export interface WriteFileRequest { @@ -283,16 +284,18 @@ class ResolverClient implements VSCodeResolverServer, Disposable { } } - async fileStat(loc: ISourceLocation): Promise { + async fileStat(loc: ISourceLocation): Promise { return asyncCatcher(async () => { const fileInfo = await this.stat(loc); return { exists: true, - type: fileInfo.type.valueOf(), - ctime: fileInfo.ctime, - mtime: fileInfo.mtime, + isFile: (fileInfo.type | vscode.FileType.File) > 0, + created: fileInfo.ctime, + lastModified: fileInfo.mtime, + isReadable: true, + isWritable: true, + permissions: fileInfo.permissions && (fileInfo.permissions | vscode.FilePermission.Readonly) > 0, size: fileInfo.size, - permissions: fileInfo.permissions ? fileInfo.permissions.valueOf() : 0 }; }); } From 61d86fe82296ffd301f216586a685a3bca811bda Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 17 Mar 2026 13:55:05 +0100 Subject: [PATCH 057/149] Removed WriteFileRequest and RenameRequest --- rascal-vscode-extension/src/fs/VSCodeURIResolver.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts index 0c10010b6..4621553de 100644 --- a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts +++ b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts @@ -153,18 +153,6 @@ export interface FileAttributes { size: number; } -export interface WriteFileRequest { - uri: ISourceLocation; - content: string; - append: boolean; -} - -export interface RenameRequest { - from: ISourceLocation; - to: ISourceLocation; - overwrite: boolean; -} - export interface WatchRequest { loc: ISourceLocation; /** From fa8ddb416e58ffc1e78e79e850e8124872a811e0 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 17 Mar 2026 13:56:12 +0100 Subject: [PATCH 058/149] Rewrite writeFile and rename after removal of the wrapper interfaces --- .../src/fs/VSCodeURIResolver.ts | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts index 4621553de..fa9dac6e4 100644 --- a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts +++ b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts @@ -79,10 +79,10 @@ function connectInputHandler(connection: rpc.MessageConnection, handler: ISource // Rascal's interface reduced to a subset we can support interface ISourceLocationOutput { - writeFile(req: WriteFileRequest ): Promise; - mkDirectory(req: ISourceLocation): Promise; - remove(req: ISourceLocation, recursive: boolean): Promise; - rename(req: RenameRequest): Promise; + writeFile(uri: ISourceLocation, content: string, append: boolean): Promise; + mkDirectory(uri: ISourceLocation): Promise; + remove(uri: ISourceLocation, recursive: boolean): Promise; + rename(from: ISourceLocation, to: ISourceLocation, overwrite: boolean): Promise; } function connectOutputHandler(connection: rpc.MessageConnection, handler: ISourceLocationOutput, toClear: Disposable[]) { @@ -96,10 +96,15 @@ function connectOutputHandler(connection: rpc.MessageConnection, handler: ISourc new rpc.RequestType2("rascal/vfs/output/" + method), h.bind(handler))); } - req1("writeFile", handler.writeFile); + function req3 (method: string, h: rpc.RequestHandler3) { + toClear.push(connection.onRequest( + new rpc.RequestType3("rascal/vfs/output/" + method), + h.bind(handler))); + } + req3("writeFile", handler.writeFile); req1("mkDirectory", handler.mkDirectory); req2("remove", handler.remove); - req1("rename", handler.rename); + req3("rename", handler.rename); } // Rascal's interface reduced to a subset we can support @@ -354,10 +359,14 @@ class ResolverClient implements VSCodeResolverServer, Disposable { }); } - async writeFile(req: WriteFileRequest): Promise { - this.logger.trace("[VFS] writeFile: ", req.uri); + async writeFile(uri: ISourceLocation, content: string, append: boolean): Promise { + this.logger.trace("[VFS] writeFile: ", uri); + let prefix:Buffer = Buffer.of(); + if (await this.exists(uri) && append) { + prefix = Buffer.from(await this.fs.readFile(this.toUri(uri))); + } return asyncVoidCatcher( - this.fs.writeFile(this.toUri(req.uri), Buffer.from(req.content, "base64")) + this.fs.writeFile(this.toUri(uri), Buffer.concat([prefix, Buffer.from(content, "base64")])) ); } async mkDirectory(req: ISourceLocation): Promise { @@ -368,11 +377,9 @@ class ResolverClient implements VSCodeResolverServer, Disposable { this.logger.trace("[VFS] remove: ", req); return asyncVoidCatcher(this.fs.delete(this.toUri(req), {"recursive" : recursive})); } - async rename(req: RenameRequest): Promise { - this.logger.trace("[VFS] rename: ", req.from, req.to); - const from = this.toUri(req.from); - const to = this.toUri(req.to); - return asyncVoidCatcher(this.fs.rename(from, to, { overwrite: req.overwrite })); + async rename(from: ISourceLocation, to: ISourceLocation, overwrite: boolean): Promise { + this.logger.trace("[VFS] rename: ", from, to); + return asyncVoidCatcher(this.fs.rename(this.toUri(from), this.toUri(to), { overwrite: overwrite })); } private readonly activeWatches = new Map(); From cc813db4adc9812f5bcead5808a500c120810339 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 17 Mar 2026 14:08:18 +0100 Subject: [PATCH 059/149] Added mapping functions from and to the source location abstraction in TerminalLinkProvider. The artificial wrapping class on the Java side has been removed --- .../src/RascalTerminalLinkProvider.ts | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/rascal-vscode-extension/src/RascalTerminalLinkProvider.ts b/rascal-vscode-extension/src/RascalTerminalLinkProvider.ts index 16f87cb09..e1c755982 100644 --- a/rascal-vscode-extension/src/RascalTerminalLinkProvider.ts +++ b/rascal-vscode-extension/src/RascalTerminalLinkProvider.ts @@ -74,7 +74,7 @@ export class RascalTerminalLinkProvider implements TerminalLinkProvider"; + ret += "<" + sloc.endLineColumn![0] + "," + sloc.endLineColumn![1] + ">"; + } + ret += ")"; + } + return ret; + } + + fromRascalLocationString(loc: string): SourceLocation { + const match: RegExpExecArray | null = this.linkDetector().exec(loc); + if (match !== null) { + return buildLocation(match); + } + throw Error(`Invalid location ${loc}`); + } } function translateRange(sloc: SourceLocation, td: vscode.TextDocument): vscode.Range | undefined { @@ -112,9 +133,8 @@ function translateRange(sloc: SourceLocation, td: vscode.TextDocument): vscode.R return undefined; } -function buildLink(match: RegExpExecArray): ExtendedLink { +function buildLocation(match: RegExpExecArray): SourceLocation { const linkMatch = match[0]; - const linkOffset = match.index + 1; const linkLength = linkMatch.indexOf('|', 2); const sloc = { uri: linkMatch.substring(1, linkLength) }; const numbers = linkMatch.substring(linkLength).match(/\d+/g,); @@ -126,10 +146,14 @@ function buildLink(match: RegExpExecArray): ExtendedLink { sloc.endLineColumn = [Number(numbers[4]), Number(numbers[5])]; } } + return sloc; +} +function buildLink(match: RegExpExecArray): ExtendedLink { + const sloc = buildLocation(match); return { - startIndex: linkOffset - 1, - length: linkMatch.length, + startIndex: match.index, + length: match[0].length, loc: sloc }; } From 02fc92ab7db5188dfeec680453136e9bbf6f6d04 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 17 Mar 2026 14:09:16 +0100 Subject: [PATCH 060/149] Removed FileWithType after the move to Rascal --- .../uri/jsonrpc/messages/FileWithType.java | 48 ------------------- 1 file changed, 48 deletions(-) delete mode 100644 rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/FileWithType.java diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/FileWithType.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/FileWithType.java deleted file mode 100644 index 85d13864a..000000000 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/FileWithType.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.uri.jsonrpc.messages; - -import org.eclipse.lsp4j.jsonrpc.validation.NonNull; -import org.rascalmpl.uri.vfs.FileAttributesResult.FileType; - -public class FileWithType { - @NonNull private final String name; - @NonNull private final FileType type; - - public FileWithType(@NonNull String name, @NonNull FileType type) { - this.name = name; - this.type = type; - } - - public String getName() { - return name; - } - - public FileType getType() { - return type; - } -} From dd74c0dc2ad9c63d300d1c65d15f7ff903c053cf Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 17 Mar 2026 14:10:19 +0100 Subject: [PATCH 061/149] Removed IRascalFileSystemServices from rascal-lsp. Its functionality has been moved to Rascal --- .../vscode/lsp/IRascalFileSystemServices.java | 320 ------------------ 1 file changed, 320 deletions(-) delete mode 100644 rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java deleted file mode 100644 index db51e0b9b..000000000 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IRascalFileSystemServices.java +++ /dev/null @@ -1,320 +0,0 @@ -/* - * 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; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.nio.file.FileAlreadyExistsException; -import java.nio.file.NotDirectoryException; -import java.util.Arrays; -import java.util.Base64; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.ExecutorService; -import java.util.stream.Stream; - -import org.apache.commons.codec.binary.Base64InputStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.eclipse.lsp4j.jsonrpc.ResponseErrorException; -import org.eclipse.lsp4j.jsonrpc.messages.ResponseError; -import org.rascalmpl.library.Prelude; -import org.rascalmpl.uri.ISourceLocationWatcher.ISourceLocationChangeType; -import org.rascalmpl.uri.ISourceLocationWatcher.ISourceLocationChanged; -import org.rascalmpl.uri.URIResolverRegistry; -import org.rascalmpl.uri.URIUtil; -import org.rascalmpl.uri.UnsupportedSchemeException; -import org.rascalmpl.uri.vfs.FileAttributesResult; -import org.rascalmpl.uri.vfs.FileAttributesResult.FilePermission; -import org.rascalmpl.uri.vfs.FileAttributesResult.FileType; -import org.rascalmpl.util.NamedThreadPool; -import org.rascalmpl.uri.vfs.IRemoteResolverRegistry; -import org.rascalmpl.values.IRascalValueFactory; -import org.rascalmpl.vscode.lsp.util.locations.Locations; - -import io.usethesource.vallang.ISourceLocation; - -public class IRascalFileSystemServices implements IRemoteResolverRegistry { - static final URIResolverRegistry reg = URIResolverRegistry.getInstance(); - static final Logger IRascalFileSystemServices__logger = LogManager.getLogger(IRascalFileSystemServices.class); - public static final ExecutorService executor = NamedThreadPool.cachedDaemon("rascal-vfs"); - - @Override - public CompletableFuture resolveLocation(ISourceLocation loc) { - return CompletableFuture.supplyAsync(() -> { - try { - ISourceLocation resolved = Locations.toClientLocation(URIResolverRegistry.getInstance().logicalToPhysical(loc)); - - if (resolved == null) { - return loc; - } - - return resolved; - } catch (Exception e) { - IRascalFileSystemServices__logger.warn("Could not resolve location {}", loc, e); - return loc; - } - }, executor); - } - - @Override - public CompletableFuture watch(WatchRequest params) { - return CompletableFuture.runAsync(() -> { - try { - ISourceLocation loc = params.getLocation(); - - URIResolverRegistry.getInstance().watch(loc, params.isRecursive(), changed -> { - try { - onDidChangeFile(convertChangeEvent(changed)); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - } catch (IOException | RuntimeException e) { - throw new VSCodeFSError(e); - } - }, executor); - } - - static FileChangeEvent convertChangeEvent(ISourceLocationChanged changed) throws IOException { - return new FileChangeEvent(convertFileChangeType(changed.getChangeType()), - Locations.toUri(changed.getLocation()).toASCIIString()); - } - - static FileChangeType convertFileChangeType(ISourceLocationChangeType changeType) throws IOException { - switch (changeType) { - case CREATED: - return FileChangeType.Created; - case DELETED: - return FileChangeType.Deleted; - case MODIFIED: - return FileChangeType.Changed; - default: - throw new IOException("unknown change type: " + changeType); - } - } - - private static boolean readonly(ISourceLocation loc) throws IOException { - if (reg.getRegisteredOutputSchemes().contains(loc.getScheme())) { - return false; - } - if (reg.getRegisteredLogicalSchemes().contains(loc.getScheme())) { - var resolved = Locations.toClientLocation(loc); - if (resolved != null && resolved != loc) { - return readonly(resolved); - } - return true; - } - return true; - } - - @Override - public CompletableFuture stat(ISourceLocation loc) { - return CompletableFuture.supplyAsync(() -> { - try { - if (!reg.exists(loc)) { - throw new FileNotFoundException(); - } - var created = reg.created(loc); - var lastModified = reg.lastModified(loc); - if (reg.isDirectory(loc)) { - return new FileAttributesResult(true, FileType.Directory, created, lastModified, 0, null); - } - long size = 0; - if (reg.supportsReadableFileChannel(loc)) { - try (var c = reg.getReadableFileChannel(loc)) { - size = c.size(); - } - } - else { - size = Prelude.__getFileSize(IRascalValueFactory.getInstance(), loc).longValue(); - } - return new FileAttributesResult(true, FileType.File, created, lastModified, size, readonly(loc) ? FilePermission.Readonly : null); - } catch (IOException | URISyntaxException | RuntimeException e) { - throw new VSCodeFSError(e); - } - }, executor); - } - - @Override - public CompletableFuture list(ISourceLocation loc) { - return CompletableFuture.supplyAsync(() -> { - try { - if (!reg.isDirectory(loc)) { - throw VSCodeFSError.notADirectory(loc); - } - return Arrays.stream(reg.list(loc)).map(l -> new FileWithType(URIUtil.getLocationName(l), - reg.isDirectory(l) ? FileType.Directory : FileType.File)).toArray(FileWithType[]::new); - } catch (IOException | RuntimeException e) { - throw new VSCodeFSError(e); - } - }, executor); - } - - @Override - public CompletableFuture mkDirectory(ISourceLocation loc) { - return CompletableFuture.runAsync(() -> { - try { - reg.mkDirectory(loc); - } catch (IOException | RuntimeException e) { - throw new VSCodeFSError(e); - } - }, executor); - } - - @Override - public CompletableFuture readFile(ISourceLocation loc) { - return CompletableFuture.supplyAsync(() -> { - try (InputStream source = new Base64InputStream(reg.getInputStream(loc), true)) { - return new String(source.readAllBytes(), StandardCharsets.US_ASCII); - } catch (IOException | RuntimeException e) { - throw new VSCodeFSError(e); - } - }, executor); - } - - @Override - public CompletableFuture writeFile(ISourceLocation loc, String content, boolean append, boolean create, boolean overwrite) { - return CompletableFuture.runAsync(() -> { - try { - boolean fileExists = reg.exists(loc); - if (!fileExists && !create) { - throw new FileNotFoundException(loc.toString()); - } - if (fileExists && reg.isDirectory(loc)) { - throw VSCodeFSError.isADirectory(loc); - } - - ISourceLocation parentFolder = URIUtil.getParentLocation(loc); - if (!reg.exists(parentFolder) && create) { - throw new FileNotFoundException(parentFolder.toString()); - } - - if (fileExists && create && !overwrite) { - throw new FileAlreadyExistsException(loc.toString()); - } - try (OutputStream target = reg.getOutputStream(loc, false)) { - target.write(Base64.getDecoder().decode(content)); - } - } catch (IOException | RuntimeException e) { - throw new VSCodeFSError(e); - } - }, executor); - } - - @Override - public CompletableFuture remove(ISourceLocation loc, boolean recursive) { - return CompletableFuture.runAsync(() -> { - try { - reg.remove(loc, recursive); - } catch (IOException e) { - throw new CompletionException(e); - } - }, executor); - } - - @Override - public CompletableFuture rename(ISourceLocation from, ISourceLocation to, boolean overwrite) { - return CompletableFuture.runAsync(() -> { - try { - reg.rename(from, to, overwrite); - } catch (IOException e) { - throw new CompletionException(e); - } - }, executor); - } - - @Override - public void onDidChangeFile(FileChangeEvent event) { } - - /** Maps common exceptions to FileSystemError in VS Code */ - public static class VSCodeFSError extends ResponseErrorException { - public VSCodeFSError(Exception original) { - super(translate(original)); - } - - private static ResponseError fileExists(Object data) { - return new ResponseError(-1, "File exists", data); - } - private static ResponseError fileIsADirectory(Object data) { - return new ResponseError(-2, "File is a directory", data); - } - private static ResponseError fileNotADirectory(Object data) { - return new ResponseError(-3, "File is not a directory", data); - } - private static ResponseError fileNotFound(Object data) { - return new ResponseError(-4, "File is not found", data); - } - private static ResponseError noPermissions(Object data) { - return new ResponseError(-5, "No permissions", data); - } - @SuppressWarnings("unused") - private static ResponseError unavailable(Object data) { - return new ResponseError(-6, "Unavailable", data); - } - - private static ResponseError generic(@Nullable String message, Object data) { - return new ResponseError(-99, message == null ? "no error message was provided" : message, data); - } - - public static ResponseErrorException notADirectory(Object data) { - return new ResponseErrorException(fileNotADirectory(data)); - } - - public static ResponseErrorException isADirectory(Object data) { - return new ResponseErrorException(fileIsADirectory(data)); - } - - private static ResponseError translate(Exception original) { - if (original instanceof FileNotFoundException - || original instanceof UnsupportedSchemeException - || original instanceof URISyntaxException - ) { - return fileNotFound(original); - } - else if (original instanceof FileAlreadyExistsException) { - return fileExists(original); - } - else if (original instanceof NotDirectoryException) { - return fileNotADirectory(original); - } - else if (original instanceof SecurityException) { - return noPermissions(original); - } - else if (original instanceof ResponseErrorException) { - return ((ResponseErrorException)original).getResponseError(); - } - return generic(original.getMessage(), original); - } - } -} From 070a36d4e026c41883427d6d590ff383701c3716 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 17 Mar 2026 14:11:35 +0100 Subject: [PATCH 062/149] Added RascalFileSystemInVsCode class which implements Rascal's IRascalFileSystemServices to provide LSP- or VS Code-specific checks and translations --- .../uri/jsonrpc/RascalFileSystemInVsCode.java | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java new file mode 100644 index 000000000..7bac62782 --- /dev/null +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java @@ -0,0 +1,104 @@ +/* + * 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.uri.jsonrpc; + +import java.util.concurrent.CompletableFuture; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.rascalmpl.uri.FileAttributes; +import org.rascalmpl.uri.UnsupportedSchemeException; +import org.rascalmpl.uri.remote.IRascalFileSystemServices; +import org.rascalmpl.vscode.lsp.util.locations.Locations; + +import io.usethesource.vallang.ISourceLocation; + +public class RascalFileSystemInVsCode implements IRascalFileSystemServices { + static final Logger logger = LogManager.getLogger(IRascalFileSystemServices.class); + + @Override + public CompletableFuture resolveLocation(ISourceLocation loc) { + logger.trace("resolveLocation: {}", loc); + return IRascalFileSystemServices.super.resolveLocation(Locations.toClientLocation(loc)); + } + + @Override + public CompletableFuture watch(WatchRequest params) { + logger.trace("watch: {}", params.getLocation()); + if (Locations.isWrappedOpaque(params.getLocation())) { + throw new VSCodeFSError(new UnsupportedSchemeException("Opaque locations are not supported by Rascal")); + } + return IRascalFileSystemServices.super.watch(params); + } + + @Override + public CompletableFuture stat(ISourceLocation loc) { + logger.trace("stat: {}", loc); + return IRascalFileSystemServices.super.stat(Locations.toClientLocation(loc)); + } + + @Override + public CompletableFuture list(ISourceLocation loc) { + logger.trace("list: {}", loc); + return IRascalFileSystemServices.super.list(Locations.toClientLocation(loc)); + } + + @Override + public CompletableFuture mkDirectory(ISourceLocation loc) { + logger.trace("mkDirectory: {}", loc); + return IRascalFileSystemServices.super.mkDirectory(Locations.toClientLocation(loc)); + } + + @Override + public CompletableFuture readFile(ISourceLocation loc) { + logger.trace("readFile: {}", loc); + return IRascalFileSystemServices.super.readFile(Locations.toClientLocation(loc)); + } + + @Override + public CompletableFuture writeFile(ISourceLocation loc, String content, boolean append) { + logger.info("writeFile: {}", loc); + return IRascalFileSystemServices.super.writeFile(Locations.toClientLocation(loc), content, append); + } + + @Override + public CompletableFuture remove(ISourceLocation loc, boolean recursive) { + logger.trace("remove: {}", loc); + return IRascalFileSystemServices.super.remove(Locations.toClientLocation(loc), recursive); + } + + @Override + public CompletableFuture rename(ISourceLocation from, ISourceLocation to, boolean overwrite) { + logger.trace("rename: {} to {}", from, to); + return IRascalFileSystemServices.super.rename(Locations.toClientLocation(from), Locations.toClientLocation(to), overwrite); + } + + @Override + public void onDidChangeFile(ISourceLocation loc, int type, String watchId) { + logger.trace("onDidChangeFile: {}", loc); + } +} From bd350101c7ddf77ef4a78b42d719c04d86aebe6a Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 17 Mar 2026 14:12:55 +0100 Subject: [PATCH 063/149] Added a function to Locations.java to check whether a source location is a wrapped opaque location --- .../org/rascalmpl/vscode/lsp/util/locations/Locations.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/locations/Locations.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/locations/Locations.java index 0724749f6..234c026c6 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/locations/Locations.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/locations/Locations.java @@ -29,6 +29,7 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; + import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -169,6 +170,10 @@ public static URI toUri(ISourceLocation loc) { return uri; } + public static boolean isWrappedOpaque(ISourceLocation loc) { + return OPAQUE_SCHEME.equals(loc.getURI().getScheme()); + } + public static Location mapValueToLocation(IValue v, ColumnMaps cm) { if (v instanceof ISourceLocation) { return Locations.toLocation((ISourceLocation)v, cm); From 03bb5fbacbba37ea72c193a9c47c89d41cd9f57b Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 17 Mar 2026 14:15:34 +0100 Subject: [PATCH 064/149] Changed how the remote resolver registry port is propagated from TS to Java --- rascal-vscode-extension/src/RascalExtension.ts | 2 -- rascal-vscode-extension/src/lsp/RascalLSPConnection.ts | 5 +++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/rascal-vscode-extension/src/RascalExtension.ts b/rascal-vscode-extension/src/RascalExtension.ts index 3265e231f..e776f9351 100644 --- a/rascal-vscode-extension/src/RascalExtension.ts +++ b/rascal-vscode-extension/src/RascalExtension.ts @@ -274,8 +274,6 @@ export class RascalExtension implements vscode.Disposable { , 'org.rascalmpl.shell.RascalShell' , '--remoteIDEServicesPort' , '' + remoteIDEServicesConfiguration.port - , '--vfsPort' - , '' + vfsServerPort ); return shellArgs.concat(extraArgs || []); } diff --git a/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts b/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts index 4f370cb78..09a3c15a1 100644 --- a/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts +++ b/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts @@ -41,7 +41,7 @@ export async function activateLanguageClient( : Promise { const logger = new JsonParserOutputChannel(title); const serverOptions: ServerOptions = deployMode - ? await buildRascalServerOptions(jarPath, isParametricServer, dedicated, lspArg, logger) + ? await buildRascalServerOptions(jarPath, isParametricServer, dedicated, lspArg, await vfsServer.serverPort, logger) : () => connectToRascalLanguageServerSocket(devPort) // we assume a server is running in debug mode .then((socket) => { writer: socket, reader: socket}); @@ -152,7 +152,7 @@ function loadURLintoPanel(panel:vscode.WebviewPanel, url:string): void { `; } -async function buildRascalServerOptions(jarPath: string, isParametricServer: boolean, dedicated: boolean, lspArg: string | undefined, logger: vscode.LogOutputChannel): Promise { +async function buildRascalServerOptions(jarPath: string, isParametricServer: boolean, dedicated: boolean, lspArg: string | undefined, remoteResolverRegistryPort : number, logger: vscode.LogOutputChannel): Promise { const classpath = buildCompilerJVMPath(jarPath); const commandArgs = [ '-Dlog4j2.configurationFactory=org.rascalmpl.vscode.lsp.log.LogJsonConfiguration' @@ -160,6 +160,7 @@ async function buildRascalServerOptions(jarPath: string, isParametricServer: boo , '-Drascal.fallbackResolver=org.rascalmpl.vscode.lsp.uri.FallbackResolver' , '-Drascal.lsp.deploy=true' , '-Drascal.compilerClasspath=' + classpath + , '-Drascal.remoteResolverRegistryPort=' + remoteResolverRegistryPort ]; let mainClass: string; if (isParametricServer) { From cda289bfb4f1ee0fa0b86bd35d4023ced7a214b8 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 17 Mar 2026 14:16:14 +0100 Subject: [PATCH 065/149] Added second-level debug port for remote resolver registry to launch.json --- rascal-lsp/.vscode/launch.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rascal-lsp/.vscode/launch.json b/rascal-lsp/.vscode/launch.json index 9dde3567f..69ab86561 100644 --- a/rascal-lsp/.vscode/launch.json +++ b/rascal-lsp/.vscode/launch.json @@ -11,7 +11,8 @@ "vmArgs": [ "-Dlog4j2.level=TRACE", "-Drascal.compilerClasspath=${workspaceFolder}/target/lib/rascal.jar", - "-Drascal.fallbackResolver=org.rascalmpl.vscode.lsp.uri.FallbackResolver" + "-Drascal.fallbackResolver=org.rascalmpl.vscode.lsp.uri.FallbackResolver", + "-Drascal.remoteResolverRegistryPort=8889" ] }, { @@ -24,7 +25,8 @@ "vmArgs": [ "-Dlog4j2.level=TRACE", "-Drascal.compilerClasspath=${workspaceFolder}/target/lib/rascal.jar", - "-Drascal.fallbackResolver=org.rascalmpl.vscode.lsp.uri.FallbackResolver" + "-Drascal.fallbackResolver=org.rascalmpl.vscode.lsp.uri.FallbackResolver", + "-Drascal.remoteResolverRegistryPort=8889" ] } ] From d286efec34016f9a498fcfe3c9b77c4d44e385e2 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 17 Mar 2026 14:17:23 +0100 Subject: [PATCH 066/149] Removed fallback resolver as an environment variable setting, as this is no longer in use. --- rascal-lsp/.vscode/launch.json | 2 -- rascal-vscode-extension/src/lsp/RascalLSPConnection.ts | 1 - 2 files changed, 3 deletions(-) diff --git a/rascal-lsp/.vscode/launch.json b/rascal-lsp/.vscode/launch.json index 69ab86561..08690b632 100644 --- a/rascal-lsp/.vscode/launch.json +++ b/rascal-lsp/.vscode/launch.json @@ -11,7 +11,6 @@ "vmArgs": [ "-Dlog4j2.level=TRACE", "-Drascal.compilerClasspath=${workspaceFolder}/target/lib/rascal.jar", - "-Drascal.fallbackResolver=org.rascalmpl.vscode.lsp.uri.FallbackResolver", "-Drascal.remoteResolverRegistryPort=8889" ] }, @@ -25,7 +24,6 @@ "vmArgs": [ "-Dlog4j2.level=TRACE", "-Drascal.compilerClasspath=${workspaceFolder}/target/lib/rascal.jar", - "-Drascal.fallbackResolver=org.rascalmpl.vscode.lsp.uri.FallbackResolver", "-Drascal.remoteResolverRegistryPort=8889" ] } diff --git a/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts b/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts index 09a3c15a1..7c01c504a 100644 --- a/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts +++ b/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts @@ -157,7 +157,6 @@ async function buildRascalServerOptions(jarPath: string, isParametricServer: boo const commandArgs = [ '-Dlog4j2.configurationFactory=org.rascalmpl.vscode.lsp.log.LogJsonConfiguration' , '-Dlog4j2.level=DEBUG' - , '-Drascal.fallbackResolver=org.rascalmpl.vscode.lsp.uri.FallbackResolver' , '-Drascal.lsp.deploy=true' , '-Drascal.compilerClasspath=' + classpath , '-Drascal.remoteResolverRegistryPort=' + remoteResolverRegistryPort From 6c4785688c77004a5cb9775197fc91476c1bdac5 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 17 Mar 2026 14:18:37 +0100 Subject: [PATCH 067/149] BaseLanguageServer now implements IRascalFileSystemServices --- .../main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java index 243b24088..5dee25057 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java @@ -195,7 +195,7 @@ private static void startLSP(Launcher server) { } } } - private static class ActualLanguageServer implements IBaseLanguageServerExtensions, LanguageClientAware { + private static class ActualLanguageServer implements IRascalFileSystemServices, IBaseLanguageServerExtensions, LanguageClientAware { static final Logger logger = LogManager.getLogger(ActualLanguageServer.class); private final IBaseTextDocumentService lspDocumentService; private final BaseWorkspaceService lspWorkspaceService; From 66ed527dd7ed1111108544f40cec70dfc5e659b3 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 17 Mar 2026 14:24:58 +0100 Subject: [PATCH 068/149] Added trace logging messages --- .../src/fs/RascalFileSystemProviders.ts | 8 ++++++++ rascal-vscode-extension/src/fs/VSCodeURIResolver.ts | 1 + rascal-vscode-extension/src/lsp/RascalLSPConnection.ts | 2 ++ 3 files changed, 11 insertions(+) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts b/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts index 255c73276..a8e4525dc 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts @@ -108,6 +108,7 @@ export class RascalFileSystemProvider implements vscode.FileSystemProvider { } watch(uri: vscode.Uri, options: { recursive: boolean; excludes: string[]; }): vscode.Disposable { + this.logger.trace(`[RascalFileSystemProvider] watch: ${uri}`); this.sendRequest(uri, "rascal/vfs/watcher/watch", { loc: this.toRascalUri(uri), recursive: options.recursive @@ -122,6 +123,7 @@ export class RascalFileSystemProvider implements vscode.FileSystemProvider { } stat(uri: vscode.Uri): vscode.FileStat | Thenable { + this.logger.trace(`[RascalFileSystemProvider] stat: ${uri}`); return this.sendRequest(uri, "rascal/vfs/input/stat").then(a => { type: a.isFile ? vscode.FileType.File : vscode.FileType.Directory, @@ -134,21 +136,25 @@ export class RascalFileSystemProvider implements vscode.FileSystemProvider { } readDirectory(uri: vscode.Uri): [string, vscode.FileType][] | Thenable<[string, vscode.FileType][]> { + this.logger.trace(`[RascalFileSystemProvider] readDirectory: ${uri}`); return this.sendRequest(uri, "rascal/vfs/input/list") .then(c => c.map(ft => [ft.name, ft.type])); } createDirectory(uri: vscode.Uri): void | Thenable { + this.logger.trace(`[RascalFileSystemProvider] createDirectory: ${uri}`); return this.sendRequest(uri, "rascal/vfs/output/mkDirectory"); } readFile(uri: vscode.Uri): Uint8Array | Thenable { + this.logger.trace(`[RascalFileSystemProvider] readFile: ${uri}`); return this.sendRequest(uri, "rascal/vfs/input/readFile") .then(str => Buffer.from(str, "base64")); } writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean; }): void | Thenable { // The `create` and `overwrite` options are handled on this side + this.logger.trace(`[RascalFileSystemProvider] writeFile: ${uri}`); this.sendRequest(uri, "rascal/vfs/input/stat").then(s => { if (!s.exists && !options.create) { throw vscode.FileSystemError.FileNotFound(`File ${uri} does not exist and \`create\` was not set`); @@ -172,10 +178,12 @@ export class RascalFileSystemProvider implements vscode.FileSystemProvider { } delete(uri: vscode.Uri, options: { recursive: boolean; }): void | Thenable { + this.logger.trace(`[RascalFileSystemProvider] delete: ${uri}`); return this.sendRequest(uri, "rascal/vfs/output/remove", {uri: this.toRascalUri(uri), recursive: options.recursive}); } rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean; }): void | Thenable { + this.logger.trace(`[RascalFileSystemProvider] rename: ${oldUri} to ${newUri}`); return this.sendRequest(oldUri, "rascal/filesystem/rename", {oldUri: this.toRascalUri(oldUri), newUri: this.toRascalUri(newUri), overwrite: options.overwrite}); } } diff --git a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts index fa9dac6e4..db7871d79 100644 --- a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts +++ b/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts @@ -413,6 +413,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { } async resolve(req: ISourceLocation): Promise { + this.logger.trace("[VFS] resolve: ", req); return req; } diff --git a/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts b/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts index 7c01c504a..4fa21a49c 100644 --- a/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts +++ b/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts @@ -56,11 +56,13 @@ export async function activateLanguageClient( logger.setClient(client); client.onNotification("rascal/showContent", (uri: string, title: string, viewColumn: integer) => { + logger.trace(`[RascalLSPConnection] showContent: ${uri}`); showContentPanel(uri, title, viewColumn); }); client.onNotification("rascal/editDocument", (uri: string, viewColumn: integer, range: vscode.Range) => { + logger.trace(`[RascalLSPConnection] editDocument: ${uri}`); openEditor(uri, range, viewColumn); }); From dac6221baf42425bf5bde8e7b79fa3392ed1740f Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 17 Mar 2026 16:33:43 +0100 Subject: [PATCH 069/149] Renamed VSCodeURIResolverServer to VSCodeFileSystemInRascal --- rascal-vscode-extension/src/RascalExtension.ts | 6 +++--- .../{VSCodeURIResolver.ts => VSCodeFileSystemInRascal.ts} | 2 +- .../src/lsp/ParameterizedLanguageServer.ts | 4 ++-- rascal-vscode-extension/src/lsp/RascalLSPConnection.ts | 4 ++-- rascal-vscode-extension/src/lsp/RascalLanguageServer.ts | 4 ++-- rascal-vscode-extension/src/lsp/library.ts | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) rename rascal-vscode-extension/src/fs/{VSCodeURIResolver.ts => VSCodeFileSystemInRascal.ts} (99%) diff --git a/rascal-vscode-extension/src/RascalExtension.ts b/rascal-vscode-extension/src/RascalExtension.ts index e776f9351..0107d9eff 100644 --- a/rascal-vscode-extension/src/RascalExtension.ts +++ b/rascal-vscode-extension/src/RascalExtension.ts @@ -33,20 +33,20 @@ import { checkForJVMUpdate, getJavaExecutable } from './auto-jvm/JavaLookup'; import { RascalLanguageServer } from './lsp/RascalLanguageServer'; import { LanguageParameter, ParameterizedLanguageServer } from './lsp/ParameterizedLanguageServer'; import { RascalTerminalLinkProvider } from './RascalTerminalLinkProvider'; -import { VSCodeUriResolverServer } from './fs/VSCodeURIResolver'; +import { VSCodeFileSystemInRascal } from './fs/VSCodeFileSystemInRascal'; import { RascalLibraryProvider } from './ux/LibraryNavigator'; import { FileType } from 'vscode'; import { RascalDebugViewProvider } from './dap/RascalDebugView'; export class RascalExtension implements vscode.Disposable { - private readonly vfsServer: VSCodeUriResolverServer; + private readonly vfsServer: VSCodeFileSystemInRascal; private readonly dsls:ParameterizedLanguageServer; private readonly rascal: RascalLanguageServer; private readonly log: vscode.LogOutputChannel = vscode.window.createOutputChannel("Rascal Extension", {log: true}); constructor(private readonly context: vscode.ExtensionContext, private readonly jarRootPath: string, private readonly icon: vscode.Uri, private readonly isDeploy = true) { - this.vfsServer = new VSCodeUriResolverServer(!isDeploy, this.log); + this.vfsServer = new VSCodeFileSystemInRascal(!isDeploy, this.log); this.dsls = new ParameterizedLanguageServer(context, this.vfsServer, jarRootPath, isDeploy); this.rascal = new RascalLanguageServer(context, this.vfsServer, jarRootPath, this.dsls, this.log, isDeploy); diff --git a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts similarity index 99% rename from rascal-vscode-extension/src/fs/VSCodeURIResolver.ts rename to rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts index db7871d79..7d8f1bf1f 100644 --- a/rascal-vscode-extension/src/fs/VSCodeURIResolver.ts +++ b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts @@ -186,7 +186,7 @@ enum ErrorCodes { nativeRascal = -3 } -export class VSCodeUriResolverServer extends JsonRpcServer { +export class VSCodeFileSystemInRascal extends JsonRpcServer { private rascalNativeSchemes: Set = new Set(); constructor(debug: boolean, private readonly logger: vscode.LogOutputChannel) { super("VFS", debug, connection => new ResolverClient(connection, debug, this.rascalNativeSchemes, this.logger), logger); diff --git a/rascal-vscode-extension/src/lsp/ParameterizedLanguageServer.ts b/rascal-vscode-extension/src/lsp/ParameterizedLanguageServer.ts index 81105fb93..a36c7f667 100644 --- a/rascal-vscode-extension/src/lsp/ParameterizedLanguageServer.ts +++ b/rascal-vscode-extension/src/lsp/ParameterizedLanguageServer.ts @@ -29,7 +29,7 @@ import * as vscode from 'vscode'; import { BaseLanguageClient } from 'vscode-languageclient'; import { activateLanguageClient } from './RascalLSPConnection'; -import { VSCodeUriResolverServer } from '../fs/VSCodeURIResolver'; +import { VSCodeFileSystemInRascal } from '../fs/VSCodeFileSystemInRascal'; export class ParameterizedLanguageServer implements vscode.Disposable { private readonly registeredFileExtensions:Map> = new Map(); @@ -37,7 +37,7 @@ export class ParameterizedLanguageServer implements vscode.Disposable { constructor( context: vscode.ExtensionContext, - private readonly vfsServer: VSCodeUriResolverServer, + private readonly vfsServer: VSCodeFileSystemInRascal, private readonly absoluteJarPath: string, private readonly deployMode = true, private readonly languageId = 'parametric-rascalmpl', diff --git a/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts b/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts index 4fa21a49c..f44b77c7a 100644 --- a/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts +++ b/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts @@ -32,12 +32,12 @@ import * as vscode from 'vscode'; import { integer, LanguageClient, LanguageClientOptions, ServerOptions, StreamInfo } from 'vscode-languageclient/node'; import { getJavaExecutable } from '../auto-jvm/JavaLookup'; import { RascalFileSystemProvider } from '../fs/RascalFileSystemProviders'; -import { VSCodeUriResolverServer } from '../fs/VSCodeURIResolver'; +import { VSCodeFileSystemInRascal } from '../fs/VSCodeFileSystemInRascal'; import { JsonParserOutputChannel } from './JsonOutputChannel'; export async function activateLanguageClient( { language, title, jarPath, vfsServer, isParametricServer = false, deployMode = true, devPort = -1, dedicated = false, lspArg = "" } : - {language: string, title: string, jarPath: string, vfsServer: VSCodeUriResolverServer, isParametricServer: boolean, deployMode: boolean, devPort: integer, dedicated: boolean, lspArg: string | undefined} ) + {language: string, title: string, jarPath: string, vfsServer: VSCodeFileSystemInRascal, isParametricServer: boolean, deployMode: boolean, devPort: integer, dedicated: boolean, lspArg: string | undefined} ) : Promise { const logger = new JsonParserOutputChannel(title); const serverOptions: ServerOptions = deployMode diff --git a/rascal-vscode-extension/src/lsp/RascalLanguageServer.ts b/rascal-vscode-extension/src/lsp/RascalLanguageServer.ts index de40a85c4..fbb77e1f2 100644 --- a/rascal-vscode-extension/src/lsp/RascalLanguageServer.ts +++ b/rascal-vscode-extension/src/lsp/RascalLanguageServer.ts @@ -27,7 +27,7 @@ import * as vscode from 'vscode'; import { BaseLanguageClient } from 'vscode-languageclient'; -import { VSCodeUriResolverServer } from '../fs/VSCodeURIResolver'; +import { VSCodeFileSystemInRascal } from '../fs/VSCodeFileSystemInRascal'; import { activateLanguageClient } from './RascalLSPConnection'; import { ParameterizedLanguageServer } from './ParameterizedLanguageServer'; import { RascalDebugClient } from '../dap/RascalDebugClient'; @@ -41,7 +41,7 @@ export class RascalLanguageServer implements vscode.Disposable { constructor( _context: vscode.ExtensionContext, - vfsServer: VSCodeUriResolverServer, + vfsServer: VSCodeFileSystemInRascal, absoluteJarPath: string, dslLSP: ParameterizedLanguageServer, logger: vscode.LogOutputChannel, diff --git a/rascal-vscode-extension/src/lsp/library.ts b/rascal-vscode-extension/src/lsp/library.ts index aad7e839f..2120becca 100644 --- a/rascal-vscode-extension/src/lsp/library.ts +++ b/rascal-vscode-extension/src/lsp/library.ts @@ -27,4 +27,4 @@ // make sure that everything you might need to run `ParameterizedLanguageServer` is exported in this file export { ParameterizedLanguageServer } from "./ParameterizedLanguageServer"; export type { LanguageParameter, ParserSpecification } from "./ParameterizedLanguageServer"; -export { VSCodeUriResolverServer } from "../fs/VSCodeURIResolver"; +export { VSCodeFileSystemInRascal as VSCodeFileSystemInRascal } from "../fs/VSCodeFileSystemInRascal"; From f9307c7b842c867abdd4d35f0a206552a781ca3b Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 17 Mar 2026 16:35:24 +0100 Subject: [PATCH 070/149] Renamed RascalFileSystemProvider to RascalFileSystemInVSCode --- ...oviders.ts => RascalFileSystemInVSCode.ts} | 22 +++++++++++-------- .../src/lsp/RascalLSPConnection.ts | 4 ++-- 2 files changed, 15 insertions(+), 11 deletions(-) rename rascal-vscode-extension/src/fs/{RascalFileSystemProviders.ts => RascalFileSystemInVSCode.ts} (91%) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts similarity index 91% rename from rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts rename to rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts index a8e4525dc..b6b6b4afd 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemProviders.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts @@ -27,7 +27,7 @@ import * as vscode from 'vscode'; import { BaseLanguageClient, ResponseError } from 'vscode-languageclient'; -export class RascalFileSystemProvider implements vscode.FileSystemProvider { +export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { readonly client: BaseLanguageClient; private readonly _emitter = new vscode.EventEmitter(); readonly onDidChangeFile: vscode.Event = this._emitter.event; @@ -108,13 +108,15 @@ export class RascalFileSystemProvider implements vscode.FileSystemProvider { } watch(uri: vscode.Uri, options: { recursive: boolean; excludes: string[]; }): vscode.Disposable { - this.logger.trace(`[RascalFileSystemProvider] watch: ${uri}`); + this.logger.trace(`[RascalFileSystemInVSCode] watch: ${uri}`); + //TODO (Rodin): removed "excludes", is that ok? this.sendRequest(uri, "rascal/vfs/watcher/watch", { loc: this.toRascalUri(uri), recursive: options.recursive }); return new vscode.Disposable(() => { + //TODO (Rodin): aanpassen, checken this.sendRequest(uri, "rascal/vfs/watcher/unwatch", { loc: this.toRascalUri(uri), recursive: options.recursive @@ -123,7 +125,7 @@ export class RascalFileSystemProvider implements vscode.FileSystemProvider { } stat(uri: vscode.Uri): vscode.FileStat | Thenable { - this.logger.trace(`[RascalFileSystemProvider] stat: ${uri}`); + this.logger.trace(`[RascalFileSystemInVSCode] stat: ${uri}`); return this.sendRequest(uri, "rascal/vfs/input/stat").then(a => { type: a.isFile ? vscode.FileType.File : vscode.FileType.Directory, @@ -136,25 +138,27 @@ export class RascalFileSystemProvider implements vscode.FileSystemProvider { } readDirectory(uri: vscode.Uri): [string, vscode.FileType][] | Thenable<[string, vscode.FileType][]> { - this.logger.trace(`[RascalFileSystemProvider] readDirectory: ${uri}`); + this.logger.trace(`[RascalFileSystemInVSCode] readDirectory: ${uri}`); + //TODO (Rodin): return type is not yet consistent with the Java side return this.sendRequest(uri, "rascal/vfs/input/list") .then(c => c.map(ft => [ft.name, ft.type])); } createDirectory(uri: vscode.Uri): void | Thenable { - this.logger.trace(`[RascalFileSystemProvider] createDirectory: ${uri}`); + this.logger.trace(`[RascalFileSystemInVSCode] createDirectory: ${uri}`); return this.sendRequest(uri, "rascal/vfs/output/mkDirectory"); } readFile(uri: vscode.Uri): Uint8Array | Thenable { - this.logger.trace(`[RascalFileSystemProvider] readFile: ${uri}`); + this.logger.trace(`[RascalFileSystemInVSCode] readFile: ${uri}`); return this.sendRequest(uri, "rascal/vfs/input/readFile") .then(str => Buffer.from(str, "base64")); } writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean; }): void | Thenable { // The `create` and `overwrite` options are handled on this side - this.logger.trace(`[RascalFileSystemProvider] writeFile: ${uri}`); + this.logger.trace(`[RascalFileSystemInVSCode] writeFile: ${uri}`); + //TODO (RA): refactoren: wat als `stat` een error gooit (x2) this.sendRequest(uri, "rascal/vfs/input/stat").then(s => { if (!s.exists && !options.create) { throw vscode.FileSystemError.FileNotFound(`File ${uri} does not exist and \`create\` was not set`); @@ -178,12 +182,12 @@ export class RascalFileSystemProvider implements vscode.FileSystemProvider { } delete(uri: vscode.Uri, options: { recursive: boolean; }): void | Thenable { - this.logger.trace(`[RascalFileSystemProvider] delete: ${uri}`); + this.logger.trace(`[RascalFileSystemInVSCode] delete: ${uri}`); return this.sendRequest(uri, "rascal/vfs/output/remove", {uri: this.toRascalUri(uri), recursive: options.recursive}); } rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean; }): void | Thenable { - this.logger.trace(`[RascalFileSystemProvider] rename: ${oldUri} to ${newUri}`); + this.logger.trace(`[RascalFileSystemInVSCode] rename: ${oldUri} to ${newUri}`); return this.sendRequest(oldUri, "rascal/filesystem/rename", {oldUri: this.toRascalUri(oldUri), newUri: this.toRascalUri(newUri), overwrite: options.overwrite}); } } diff --git a/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts b/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts index f44b77c7a..45a38a1cb 100644 --- a/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts +++ b/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts @@ -31,7 +31,7 @@ import * as vscode from 'vscode'; import { integer, LanguageClient, LanguageClientOptions, ServerOptions, StreamInfo } from 'vscode-languageclient/node'; import { getJavaExecutable } from '../auto-jvm/JavaLookup'; -import { RascalFileSystemProvider } from '../fs/RascalFileSystemProviders'; +import { RascalFileSystemInVSCode } from '../fs/RascalFileSystemInVSCode'; import { VSCodeFileSystemInRascal } from '../fs/VSCodeFileSystemInRascal'; import { JsonParserOutputChannel } from './JsonOutputChannel'; @@ -70,7 +70,7 @@ export async function activateLanguageClient( schemesReply.then( schemes => { vfsServer.ignoreSchemes(schemes); - new RascalFileSystemProvider(client, logger).tryRegisterSchemes(schemes); + new RascalFileSystemInVSCode(client, logger).tryRegisterSchemes(schemes); }); return client; From 5c5f56077ac956b0ff576d3b37ba4752946d6130 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 18 Mar 2026 14:50:34 +0100 Subject: [PATCH 071/149] Implemented VS Code-specific exception handling and logic in RascalFileSystemInVSCode --- .../uri/jsonrpc/RascalFileSystemInVsCode.java | 83 +++++++++++++++++-- 1 file changed, 74 insertions(+), 9 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java index 7bac62782..ee25530ae 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java @@ -26,10 +26,18 @@ */ package org.rascalmpl.vscode.lsp.uri.jsonrpc; +import java.io.FileNotFoundException; +import java.net.URISyntaxException; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.NotDirectoryException; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.eclipse.lsp4j.jsonrpc.ResponseErrorException; +import org.eclipse.lsp4j.jsonrpc.messages.ResponseError; import org.rascalmpl.uri.FileAttributes; import org.rascalmpl.uri.UnsupportedSchemeException; import org.rascalmpl.uri.remote.IRascalFileSystemServices; @@ -43,14 +51,14 @@ public class RascalFileSystemInVsCode implements IRascalFileSystemServices { @Override public CompletableFuture resolveLocation(ISourceLocation loc) { logger.trace("resolveLocation: {}", loc); - return IRascalFileSystemServices.super.resolveLocation(Locations.toClientLocation(loc)); + return IRascalFileSystemServices.super.resolveLocation(Locations.toClientLocation(loc)).exceptionally(this::handleException); } @Override public CompletableFuture watch(WatchRequest params) { logger.trace("watch: {}", params.getLocation()); if (Locations.isWrappedOpaque(params.getLocation())) { - throw new VSCodeFSError(new UnsupportedSchemeException("Opaque locations are not supported by Rascal")); + throw new ResponseErrorException(translate(new UnsupportedSchemeException("Opaque locations are not supported by Rascal"))); } return IRascalFileSystemServices.super.watch(params); } @@ -58,47 +66,104 @@ public CompletableFuture watch(WatchRequest params) { @Override public CompletableFuture stat(ISourceLocation loc) { logger.trace("stat: {}", loc); - return IRascalFileSystemServices.super.stat(Locations.toClientLocation(loc)); + return IRascalFileSystemServices.super.stat(Locations.toClientLocation(loc)).exceptionally(this::handleException); } @Override public CompletableFuture list(ISourceLocation loc) { logger.trace("list: {}", loc); - return IRascalFileSystemServices.super.list(Locations.toClientLocation(loc)); + return IRascalFileSystemServices.super.list(Locations.toClientLocation(loc)).exceptionally(this::handleException); } @Override public CompletableFuture mkDirectory(ISourceLocation loc) { logger.trace("mkDirectory: {}", loc); - return IRascalFileSystemServices.super.mkDirectory(Locations.toClientLocation(loc)); + return IRascalFileSystemServices.super.mkDirectory(Locations.toClientLocation(loc)).exceptionally(this::handleException); } @Override public CompletableFuture readFile(ISourceLocation loc) { logger.trace("readFile: {}", loc); - return IRascalFileSystemServices.super.readFile(Locations.toClientLocation(loc)); + return IRascalFileSystemServices.super.readFile(Locations.toClientLocation(loc)).exceptionally(this::handleException); } @Override public CompletableFuture writeFile(ISourceLocation loc, String content, boolean append) { logger.info("writeFile: {}", loc); - return IRascalFileSystemServices.super.writeFile(Locations.toClientLocation(loc), content, append); + if (reg.exists(loc) && reg.isDirectory(loc)) { + return CompletableFuture.failedFuture(new ResponseErrorException(fileIsADirectory(loc))); + } + return IRascalFileSystemServices.super.writeFile(Locations.toClientLocation(loc), content, append).exceptionally(this::handleException); } @Override public CompletableFuture remove(ISourceLocation loc, boolean recursive) { logger.trace("remove: {}", loc); - return IRascalFileSystemServices.super.remove(Locations.toClientLocation(loc), recursive); + return IRascalFileSystemServices.super.remove(Locations.toClientLocation(loc), recursive).exceptionally(this::handleException); } @Override public CompletableFuture rename(ISourceLocation from, ISourceLocation to, boolean overwrite) { logger.trace("rename: {} to {}", from, to); - return IRascalFileSystemServices.super.rename(Locations.toClientLocation(from), Locations.toClientLocation(to), overwrite); + return IRascalFileSystemServices.super.rename(Locations.toClientLocation(from), Locations.toClientLocation(to), overwrite).exceptionally(this::handleException); } @Override public void onDidChangeFile(ISourceLocation loc, int type, String watchId) { logger.trace("onDidChangeFile: {}", loc); } + + private static ResponseError fileExists(Object data) { + return new ResponseError(-1, "File exists", data); + } + + private static ResponseError fileIsADirectory(Object data) { + return new ResponseError(-2, "File is a directory", data); + } + + private static ResponseError fileNotADirectory(Object data) { + return new ResponseError(-3, "File is not a directory", data); + } + + private static ResponseError fileNotFound(Object data) { + return new ResponseError(-4, "File is not found", data); + } + + private static ResponseError noPermissions(Object data) { + return new ResponseError(-5, "No permissions", data); + } + + @SuppressWarnings("unused") + private static ResponseError unavailable(Object data) { + return new ResponseError(-6, "Unavailable", data); + } + + private static ResponseError generic(@Nullable String message, Object data) { + return new ResponseError(-99, message == null ? "no error message was provided" : message, data); + } + + public static ResponseError translate(Throwable original) { + if (original instanceof CompletionException) { + var cause = original.getCause(); + + if (cause instanceof FileNotFoundException || cause instanceof UnsupportedSchemeException || cause instanceof URISyntaxException) { + return fileNotFound(cause); + } else if (cause instanceof FileAlreadyExistsException) { + return fileExists(cause); + } else if (cause instanceof NotDirectoryException) { + return fileNotADirectory(cause); + } else if (cause instanceof SecurityException) { + return noPermissions(cause); + } else if (cause instanceof ResponseErrorException) { + return ((ResponseErrorException) cause).getResponseError(); + } else { + return generic(cause.getMessage(), original); + } + } + return generic(original.getMessage(), original); + } + + private T handleException(Throwable t) throws ResponseErrorException { + throw new ResponseErrorException(translate(t)); + } } From a296c7297bc3ef06925305bc1a3cfdd99ad44675 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 18 Mar 2026 14:51:04 +0100 Subject: [PATCH 072/149] Deduplication of two FileAttributes interfaces --- .../src/fs/RascalFileSystemInVSCode.ts | 2 +- .../src/fs/VSCodeFileSystemInRascal.ts | 11 +---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts index b6b6b4afd..46885807a 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts @@ -201,7 +201,7 @@ interface FileWithType { type: vscode.FileType } -interface FileAttributes { +export interface FileAttributes { exists: boolean; isFile: boolean; created: number; diff --git a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts index 7d8f1bf1f..45b561935 100644 --- a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts +++ b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts @@ -29,6 +29,7 @@ import { Disposable } from "vscode"; import * as rpc from 'vscode-jsonrpc/node'; import { URI } from "vscode-languageclient"; import { JsonRpcServer } from "../util/JsonRpcServer"; +import { FileAttributes } from './RascalFileSystemInVSCode'; declare type ISourceLocation = URI; @@ -148,16 +149,6 @@ function buildWatchReceiver(connection: rpc.MessageConnection) : WatchEventRecei // Messages (requests and responses) -export interface FileAttributes { - exists : boolean; - isFile: boolean; - created: number; - lastModified: number; - isReadable: boolean; - isWritable: boolean; - size: number; -} - export interface WatchRequest { loc: ISourceLocation; /** From b5c2cea4c656da0d000cbb5c1c5257742fd146d4 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 18 Mar 2026 14:51:31 +0100 Subject: [PATCH 073/149] BaseLanguageServer reuses mapping from Throwable to ResponseError --- .../java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java index 5dee25057..148f7dde0 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java @@ -64,9 +64,11 @@ import org.rascalmpl.ideservices.GsonUtils; import org.rascalmpl.library.util.PathConfig; import org.rascalmpl.uri.URIResolverRegistry; +import org.rascalmpl.uri.remote.IRascalFileSystemServices; import org.rascalmpl.vscode.lsp.log.LogRedirectConfiguration; import org.rascalmpl.vscode.lsp.parametric.LanguageRegistry.LanguageParameter; import org.rascalmpl.vscode.lsp.terminal.RemoteIDEServicesThread; +import org.rascalmpl.vscode.lsp.uri.jsonrpc.RascalFileSystemInVsCode; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.PathConfigParameter; import org.rascalmpl.vscode.lsp.util.concurrent.CompletableFutureUtils; import org.rascalmpl.vscode.lsp.util.locations.Locations; @@ -121,6 +123,9 @@ private static Launcher constructLSPClient(InputStream in, .setOutput(out) .configureGson(GsonUtils.complexAsBase64String()) .setExecutorService(threadPool) + .setExceptionHandler(t -> { + return RascalFileSystemInVsCode.translate((Exception) t); + }) .create(); server.connect(clientLauncher.getRemoteProxy()); From e81bc259aa49b161bf2ce0a892a806034f907dea Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 18 Mar 2026 15:04:20 +0100 Subject: [PATCH 074/149] No longer accessing formerly public field --- .../java/org/rascalmpl/vscode/lsp/uri/LSPOpenFileResolver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/LSPOpenFileResolver.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/LSPOpenFileResolver.java index d0a7e4360..f81227ecb 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/LSPOpenFileResolver.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/LSPOpenFileResolver.java @@ -123,7 +123,7 @@ public FileAttributes stat(ISourceLocation uri) throws IOException { } var current = getEditorState(uri).getCurrentContent(); var modified = current.getTimestamp(); - var isWritable = URIResolverRegistry.getInstance().externalRegistry.isWritable(stripLspPrefix(uri)); + var isWritable = URIResolverRegistry.getInstance().isWritable(stripLspPrefix(uri)); return new FileAttributes( true, true, modified, modified, //We fix the creation timestamp to be equal to the last modified time From 5d86fc0f6db8681a447fd624ef896ad8244fb2b0 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Thu, 19 Mar 2026 14:33:57 +0100 Subject: [PATCH 075/149] Aligned signature of list with the Rascal side --- .../src/fs/RascalFileSystemInVSCode.ts | 2 +- .../src/fs/VSCodeFileSystemInRascal.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts index 46885807a..af983a6b3 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts @@ -196,7 +196,7 @@ function isUnknownFileSystem(scheme : string) : boolean { return vscode.workspace.fs.isWritableFileSystem(scheme) === undefined; } -interface FileWithType { +export interface FileWithType { name: string; type: vscode.FileType } diff --git a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts index 45b561935..be1803d0a 100644 --- a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts +++ b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts @@ -29,7 +29,7 @@ import { Disposable } from "vscode"; import * as rpc from 'vscode-jsonrpc/node'; import { URI } from "vscode-languageclient"; import { JsonRpcServer } from "../util/JsonRpcServer"; -import { FileAttributes } from './RascalFileSystemInVSCode'; +import { FileAttributes, FileWithType } from './RascalFileSystemInVSCode'; declare type ISourceLocation = URI; @@ -52,7 +52,7 @@ interface ISourceLocationInput { created(req: ISourceLocation): Promise; isDirectory(req: ISourceLocation): Promise; isFile(req: ISourceLocation): Promise; - list(req: ISourceLocation): Promise<[string, vscode.FileType][]>; + list(req: ISourceLocation): Promise; size(req: ISourceLocation): Promise; fileStat(req: ISourceLocation): Promise; isReadable(req: ISourceLocation): Promise; @@ -71,7 +71,7 @@ function connectInputHandler(connection: rpc.MessageConnection, handler: ISource req("created", handler.created); req("isDirectory", handler.isDirectory); req("isFile", handler.isFile); - req<[string, vscode.FileType][]>("list", handler.list); + req("list", handler.list); req("size", handler.size); req("stat", handler.fileStat); req("isReadable", handler.isReadable); @@ -343,10 +343,10 @@ class ResolverClient implements VSCodeResolverServer, Disposable { return this.boolResult(loc, f => f.permissions === undefined || (f.permissions & vscode.FilePermission.Readonly) === 0); } - async list(loc: ISourceLocation): Promise<[string, vscode.FileType][]> { + async list(loc: ISourceLocation): Promise { this.logger.trace("[VFS] list: ", loc); return asyncCatcher(async () => { - return await this.fs.readDirectory(this.toUri(loc)); + return (await this.fs.readDirectory(this.toUri(loc))).map(entry => {name: entry[0], type: entry[1]}); }); } From 1278c1cb3199aa7415a5020984ac54d774e13217 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Thu, 19 Mar 2026 14:34:24 +0100 Subject: [PATCH 076/149] Removed call to nonexistent extension command from UI test --- rascal-vscode-extension/src/test/vscode-suite/repl.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/rascal-vscode-extension/src/test/vscode-suite/repl.test.ts b/rascal-vscode-extension/src/test/vscode-suite/repl.test.ts index 987a44fad..011c95487 100644 --- a/rascal-vscode-extension/src/test/vscode-suite/repl.test.ts +++ b/rascal-vscode-extension/src/test/vscode-suite/repl.test.ts @@ -98,7 +98,6 @@ describe('REPL', function () { }); it("VFS works", async() => { - await bench.executeCommand("rascalmpl.registerTestVFS"); const repl = new RascalREPL(bench, driver); await repl.start(); const baseLoc = '|rascal-vscode-test:///'; From 3335a8c66e9796e4b074727843e93d00cc31c9c3 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 24 Mar 2026 09:50:45 +0100 Subject: [PATCH 077/149] Using RascalFileSystemServices and IRemoteResolverRegistryClient --- .../vscode/lsp/BaseLanguageServer.java | 5 +-- .../vscode/lsp/IBaseLanguageClient.java | 3 +- .../uri/jsonrpc/RascalFileSystemInVsCode.java | 31 +++++++++---------- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java index 1b07328f2..bc12b27f0 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java @@ -64,7 +64,7 @@ import org.rascalmpl.ideservices.GsonUtils; import org.rascalmpl.library.util.PathConfig; import org.rascalmpl.uri.URIResolverRegistry; -import org.rascalmpl.uri.remote.IRascalFileSystemServices; +import org.rascalmpl.uri.remote.RascalFileSystemServices; import org.rascalmpl.vscode.lsp.log.LogRedirectConfiguration; import org.rascalmpl.vscode.lsp.parametric.LanguageRegistry.LanguageParameter; import org.rascalmpl.vscode.lsp.terminal.RemoteIDEServicesThread; @@ -200,7 +200,7 @@ private static void startLSP(Launcher server) { } } } - private static class ActualLanguageServer implements IRascalFileSystemServices, IBaseLanguageServerExtensions, LanguageClientAware { + private static class ActualLanguageServer extends RascalFileSystemServices implements IBaseLanguageServerExtensions, LanguageClientAware { static final Logger logger = LogManager.getLogger(ActualLanguageServer.class); private final IBaseTextDocumentService lspDocumentService; private final BaseWorkspaceService lspWorkspaceService; @@ -314,6 +314,7 @@ public void connect(LanguageClient client) { var actualClient = (IBaseLanguageClient) client; lspDocumentService.connect(actualClient); lspWorkspaceService.connect(actualClient); + provideClient(actualClient); remoteIDEServicesConfiguration = RemoteIDEServicesThread.startRemoteIDEServicesServer(client, lspDocumentService, executor); logger.debug("Remote IDE Services Port {}", remoteIDEServicesConfiguration); } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseLanguageClient.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseLanguageClient.java index 5a437a739..eeeece01e 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseLanguageClient.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseLanguageClient.java @@ -32,12 +32,13 @@ import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.jsonrpc.services.JsonNotification; import org.eclipse.lsp4j.services.LanguageClient; +import org.rascalmpl.uri.vfs.IRemoteResolverRegistryClient; import org.rascalmpl.vscode.lsp.parametric.LanguageRegistry.LanguageParameter; import io.usethesource.vallang.IInteger; import io.usethesource.vallang.IString; -public interface IBaseLanguageClient extends LanguageClient { +public interface IBaseLanguageClient extends LanguageClient, IRemoteResolverRegistryClient { @JsonNotification("rascal/showContent") void showContent(URI uri, IString title, IInteger viewColumn); diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java index ee25530ae..3b54bf880 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java @@ -39,19 +39,21 @@ import org.eclipse.lsp4j.jsonrpc.ResponseErrorException; import org.eclipse.lsp4j.jsonrpc.messages.ResponseError; import org.rascalmpl.uri.FileAttributes; +import org.rascalmpl.uri.URIResolverRegistry; import org.rascalmpl.uri.UnsupportedSchemeException; -import org.rascalmpl.uri.remote.IRascalFileSystemServices; +import org.rascalmpl.uri.remote.RascalFileSystemServices; import org.rascalmpl.vscode.lsp.util.locations.Locations; import io.usethesource.vallang.ISourceLocation; -public class RascalFileSystemInVsCode implements IRascalFileSystemServices { - static final Logger logger = LogManager.getLogger(IRascalFileSystemServices.class); +public class RascalFileSystemInVsCode extends RascalFileSystemServices { + private static final Logger logger = LogManager.getLogger(RascalFileSystemServices.class); + private static final URIResolverRegistry reg = URIResolverRegistry.getInstance(); @Override public CompletableFuture resolveLocation(ISourceLocation loc) { logger.trace("resolveLocation: {}", loc); - return IRascalFileSystemServices.super.resolveLocation(Locations.toClientLocation(loc)).exceptionally(this::handleException); + return super.resolveLocation(Locations.toClientLocation(loc)).exceptionally(this::handleException); } @Override @@ -60,31 +62,31 @@ public CompletableFuture watch(WatchRequest params) { if (Locations.isWrappedOpaque(params.getLocation())) { throw new ResponseErrorException(translate(new UnsupportedSchemeException("Opaque locations are not supported by Rascal"))); } - return IRascalFileSystemServices.super.watch(params); + return super.watch(params); } @Override public CompletableFuture stat(ISourceLocation loc) { logger.trace("stat: {}", loc); - return IRascalFileSystemServices.super.stat(Locations.toClientLocation(loc)).exceptionally(this::handleException); + return super.stat(Locations.toClientLocation(loc)).exceptionally(this::handleException); } @Override public CompletableFuture list(ISourceLocation loc) { logger.trace("list: {}", loc); - return IRascalFileSystemServices.super.list(Locations.toClientLocation(loc)).exceptionally(this::handleException); + return super.list(Locations.toClientLocation(loc)).exceptionally(this::handleException); } @Override public CompletableFuture mkDirectory(ISourceLocation loc) { logger.trace("mkDirectory: {}", loc); - return IRascalFileSystemServices.super.mkDirectory(Locations.toClientLocation(loc)).exceptionally(this::handleException); + return super.mkDirectory(Locations.toClientLocation(loc)).exceptionally(this::handleException); } @Override public CompletableFuture readFile(ISourceLocation loc) { logger.trace("readFile: {}", loc); - return IRascalFileSystemServices.super.readFile(Locations.toClientLocation(loc)).exceptionally(this::handleException); + return super.readFile(Locations.toClientLocation(loc)).exceptionally(this::handleException); } @Override @@ -93,24 +95,19 @@ public CompletableFuture writeFile(ISourceLocation loc, String content, bo if (reg.exists(loc) && reg.isDirectory(loc)) { return CompletableFuture.failedFuture(new ResponseErrorException(fileIsADirectory(loc))); } - return IRascalFileSystemServices.super.writeFile(Locations.toClientLocation(loc), content, append).exceptionally(this::handleException); + return super.writeFile(Locations.toClientLocation(loc), content, append).exceptionally(this::handleException); } @Override public CompletableFuture remove(ISourceLocation loc, boolean recursive) { logger.trace("remove: {}", loc); - return IRascalFileSystemServices.super.remove(Locations.toClientLocation(loc), recursive).exceptionally(this::handleException); + return super.remove(Locations.toClientLocation(loc), recursive).exceptionally(this::handleException); } @Override public CompletableFuture rename(ISourceLocation from, ISourceLocation to, boolean overwrite) { logger.trace("rename: {} to {}", from, to); - return IRascalFileSystemServices.super.rename(Locations.toClientLocation(from), Locations.toClientLocation(to), overwrite).exceptionally(this::handleException); - } - - @Override - public void onDidChangeFile(ISourceLocation loc, int type, String watchId) { - logger.trace("onDidChangeFile: {}", loc); + return super.rename(Locations.toClientLocation(from), Locations.toClientLocation(to), overwrite).exceptionally(this::handleException); } private static ResponseError fileExists(Object data) { From da199de0845f720f56697a2d78fec681c65fbee2 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 24 Mar 2026 09:51:00 +0100 Subject: [PATCH 078/149] Fixed potential NPE --- .../vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java index 3b54bf880..61565a245 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java @@ -140,6 +140,9 @@ private static ResponseError generic(@Nullable String message, Object data) { } public static ResponseError translate(Throwable original) { + if (original == null) { + return generic("Unknown error occurred", null); + } if (original instanceof CompletionException) { var cause = original.getCause(); From bcb70eb6823b49232de1334458e2e3922a6648b0 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 24 Mar 2026 13:37:05 +0100 Subject: [PATCH 079/149] Removed VSCodeUriResolverClient and VSCodeVFS classes. They are no longer in use --- .../uri/jsonrpc/VSCodeUriResolverClient.java | 44 ------------- .../vscode/lsp/uri/jsonrpc/VSCodeVFS.java | 65 ------------------- 2 files changed, 109 deletions(-) delete mode 100644 rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverClient.java delete mode 100644 rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeVFS.java diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverClient.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverClient.java deleted file mode 100644 index 31036b056..000000000 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeUriResolverClient.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.uri.jsonrpc; - -import java.io.IOException; -import java.util.function.Consumer; -import org.eclipse.lsp4j.jsonrpc.services.JsonNotification; -import org.rascalmpl.uri.ISourceLocationWatcher; -import org.rascalmpl.uri.vfs.IRemoteResolverRegistry; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.ISourceLocationChanged; -import io.usethesource.vallang.ISourceLocation; - -public interface VSCodeUriResolverClient { - - @JsonNotification("rascal/vfs/watcher/emitWatch") - void emitWatch(ISourceLocationChanged event); - - void addWatcher(ISourceLocation loc, boolean recursive, Consumer callback, IRemoteResolverRegistry server) throws IOException; - void removeWatcher(ISourceLocation loc, boolean recursive, Consumer callback, IRemoteResolverRegistry server) throws IOException; -} diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeVFS.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeVFS.java deleted file mode 100644 index 84e180727..000000000 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeVFS.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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.uri.jsonrpc; - -import org.checkerframework.checker.nullness.qual.EnsuresNonNull; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.rascalmpl.uri.vfs.IRemoteResolverRegistry; - -/** - * This singleton keeps track of the current VFS server instance - * - * The FallbackResolvers uses this, and the LSP client should make sure to setup - * the right connection - */ -public enum VSCodeVFS { - INSTANCE; - - private volatile @MonotonicNonNull IRemoteResolverRegistry server = null; - private volatile @MonotonicNonNull VSCodeUriResolverClient client = null; - - public @Nullable IRemoteResolverRegistry getServer() { - return server; - } - - @EnsuresNonNull("this.server") - public void provideServer(IRemoteResolverRegistry server) { - this.server = server; - } - - public @Nullable VSCodeUriResolverClient getClient() { - return client; - } - - @EnsuresNonNull("this.client") - public void provideClient(VSCodeUriResolverClient client) { - this.client = client; - } - - -} From e17b92aa9fb350c515407f297113f885ed9514ef Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 24 Mar 2026 15:01:28 +0100 Subject: [PATCH 080/149] LanguageServer now initializes a subclass of remote external resolver to be able to serve as an interface layer between VS Code and Rascal --- .../vscode/lsp/BaseLanguageServer.java | 6 +++ .../uri/jsonrpc/VSCodeFileSystemInRascal.java | 37 +++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeFileSystemInRascal.java diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java index bc12b27f0..aad504825 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java @@ -69,6 +69,7 @@ import org.rascalmpl.vscode.lsp.parametric.LanguageRegistry.LanguageParameter; import org.rascalmpl.vscode.lsp.terminal.RemoteIDEServicesThread; import org.rascalmpl.vscode.lsp.uri.jsonrpc.RascalFileSystemInVsCode; +import org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeFileSystemInRascal; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.PathConfigParameter; import org.rascalmpl.vscode.lsp.util.concurrent.CompletableFutureUtils; import org.rascalmpl.vscode.lsp.util.locations.Locations; @@ -317,6 +318,11 @@ public void connect(LanguageClient client) { provideClient(actualClient); remoteIDEServicesConfiguration = RemoteIDEServicesThread.startRemoteIDEServicesServer(client, lspDocumentService, executor); logger.debug("Remote IDE Services Port {}", remoteIDEServicesConfiguration); + + var remoteResolverRegistryPort = URIResolverRegistry.getRemoteResolverRegistryPort(); + if (remoteResolverRegistryPort != null) { + URIResolverRegistry.getInstance().registerRemoteResolverRegistry(new VSCodeFileSystemInRascal(remoteResolverRegistryPort)); + } } @Override diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeFileSystemInRascal.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeFileSystemInRascal.java new file mode 100644 index 000000000..efeb7dbe0 --- /dev/null +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeFileSystemInRascal.java @@ -0,0 +1,37 @@ +/* + * 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.uri.jsonrpc; + +import org.rascalmpl.uri.remote.RemoteExternalResolverRegistry; + +public class VSCodeFileSystemInRascal extends RemoteExternalResolverRegistry { + + public VSCodeFileSystemInRascal(int remoteResolverRegistryPort) { + super(remoteResolverRegistryPort); + } + +} From 242845e6eb448511ad915615a99a7a1764aa0f3b Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 25 Mar 2026 15:54:00 +0100 Subject: [PATCH 081/149] Changed interface order and names to conform to the Rascal side --- .../src/fs/VSCodeFileSystemInRascal.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts index be1803d0a..d44d0205d 100644 --- a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts +++ b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts @@ -166,9 +166,9 @@ export enum ISourceLocationChangeType { } export interface ISourceLocationChanged { + root: ISourceLocation; + type: ISourceLocationChangeType; watchId: string; - location: ISourceLocation; - changeType: ISourceLocationChangeType; } enum ErrorCodes { @@ -438,9 +438,9 @@ class WatcherCallbacks implements Disposable { private async sendWatchEvent(uri: vscode.Uri, changeType: ISourceLocationChangeType) { this.watchListener.emitWatch({ - watchId: this.watchId, - changeType: changeType, - location: uri.toString() + root: uri.toString(), + type: changeType, + watchId: this.watchId }); } From 8aed2d5f8d30b84f1f6d77e7fb9b8fc6b6a403bc Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 25 Mar 2026 15:55:00 +0100 Subject: [PATCH 082/149] Fixed jsonrpc method name --- .../uri/jsonrpc/VSCodeFileSystemInRascal.java | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeFileSystemInRascal.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeFileSystemInRascal.java index efeb7dbe0..31e74ef11 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeFileSystemInRascal.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeFileSystemInRascal.java @@ -26,12 +26,34 @@ */ package org.rascalmpl.vscode.lsp.uri.jsonrpc; +import java.io.IOException; +import java.net.URISyntaxException; + +import org.rascalmpl.uri.URIUtil; import org.rascalmpl.uri.remote.RemoteExternalResolverRegistry; +import org.rascalmpl.vscode.lsp.uri.FallbackResolver; -public class VSCodeFileSystemInRascal extends RemoteExternalResolverRegistry { +import io.usethesource.vallang.ISourceLocation; +public class VSCodeFileSystemInRascal extends RemoteExternalResolverRegistry { public VSCodeFileSystemInRascal(int remoteResolverRegistryPort) { super(remoteResolverRegistryPort); } + + @Override + public ISourceLocation resolve(ISourceLocation input) throws IOException { + var resolved = super.resolve(input); + if (FallbackResolver.getInstance().isFileManaged(resolved)) { + try { + // The offset/length part of the source location is stripped off here. + // This is reinstated by `URIResolverRegistry::resolveAndFixOffsets` + // during logical resolution + return URIUtil.changeScheme(resolved.top(), "lsp+" + resolved.getScheme()); + } catch (URISyntaxException e) { + // fall through + } + } + return resolved; + } } From 7ae9a7288e4d3fdc615827e5bd7014849f9841de Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 25 Mar 2026 15:55:13 +0100 Subject: [PATCH 083/149] Reinstated lsp+ during logical resolution --- rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts index d44d0205d..5693d8aa1 100644 --- a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts +++ b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts @@ -142,7 +142,7 @@ export interface WatchEventReceiver { function buildWatchReceiver(connection: rpc.MessageConnection) : WatchEventReceiver { return { emitWatch : (e) => { - connection.sendNotification(new rpc.NotificationType1("rascal/vfs/watcher/emitWatch"), e); + connection.sendNotification(new rpc.NotificationType1("rascal/vfs/watcher/sourceLocationChanged"), e); } }; } From c2e52244f7bc3c438e784a4b53a2238c4f227ccb Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Thu, 26 Mar 2026 12:36:27 +0100 Subject: [PATCH 084/149] Removed comments --- rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts index af983a6b3..c758ee68f 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts @@ -109,14 +109,12 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { watch(uri: vscode.Uri, options: { recursive: boolean; excludes: string[]; }): vscode.Disposable { this.logger.trace(`[RascalFileSystemInVSCode] watch: ${uri}`); - //TODO (Rodin): removed "excludes", is that ok? this.sendRequest(uri, "rascal/vfs/watcher/watch", { loc: this.toRascalUri(uri), recursive: options.recursive }); return new vscode.Disposable(() => { - //TODO (Rodin): aanpassen, checken this.sendRequest(uri, "rascal/vfs/watcher/unwatch", { loc: this.toRascalUri(uri), recursive: options.recursive @@ -139,7 +137,6 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { readDirectory(uri: vscode.Uri): [string, vscode.FileType][] | Thenable<[string, vscode.FileType][]> { this.logger.trace(`[RascalFileSystemInVSCode] readDirectory: ${uri}`); - //TODO (Rodin): return type is not yet consistent with the Java side return this.sendRequest(uri, "rascal/vfs/input/list") .then(c => c.map(ft => [ft.name, ft.type])); } From 3c201ffb4e5a7d97b28cf4f966f2831f58e34866 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 27 Mar 2026 15:02:56 +0100 Subject: [PATCH 085/149] Added jsonrpc request and notification classes for modularity and changed the signature and implementation to use them. Removed defunct classes --- .../uri/jsonrpc/RascalFileSystemInVsCode.java | 48 +++-- .../messages/ISourceLocationChangeType.java | 71 ------- .../messages/ISourceLocationChanged.java | 97 --------- .../messages/ISourceLocationRequest.java | 70 ------- .../uri/jsonrpc/messages/RenameRequest.java | 94 --------- .../uri/jsonrpc/messages/WatchRequest.java | 94 --------- .../jsonrpc/messages/WriteFileRequest.java | 101 ---------- .../src/fs/RascalFileSystemInVSCode.ts | 1 - .../src/fs/VSCodeFileSystemInRascal.ts | 185 ++++++++++-------- 9 files changed, 127 insertions(+), 634 deletions(-) delete mode 100644 rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/ISourceLocationChangeType.java delete mode 100644 rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/ISourceLocationChanged.java delete mode 100644 rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/ISourceLocationRequest.java delete mode 100644 rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/RenameRequest.java delete mode 100644 rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/WatchRequest.java delete mode 100644 rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/WriteFileRequest.java diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java index 61565a245..08085f0f6 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java @@ -42,6 +42,11 @@ import org.rascalmpl.uri.URIResolverRegistry; import org.rascalmpl.uri.UnsupportedSchemeException; import org.rascalmpl.uri.remote.RascalFileSystemServices; +import org.rascalmpl.uri.remote.jsonrpc.ISourceLocationRequest; +import org.rascalmpl.uri.remote.jsonrpc.RemoveRequest; +import org.rascalmpl.uri.remote.jsonrpc.RenameRequest; +import org.rascalmpl.uri.remote.jsonrpc.WatchRequest; +import org.rascalmpl.uri.remote.jsonrpc.WriteFileRequest; import org.rascalmpl.vscode.lsp.util.locations.Locations; import io.usethesource.vallang.ISourceLocation; @@ -51,9 +56,9 @@ public class RascalFileSystemInVsCode extends RascalFileSystemServices { private static final URIResolverRegistry reg = URIResolverRegistry.getInstance(); @Override - public CompletableFuture resolveLocation(ISourceLocation loc) { - logger.trace("resolveLocation: {}", loc); - return super.resolveLocation(Locations.toClientLocation(loc)).exceptionally(this::handleException); + public CompletableFuture resolveLocation(ISourceLocationRequest req) { + logger.trace("resolveLocation: {}", req.getLocation()); + return super.resolveLocation(new ISourceLocationRequest(Locations.toClientLocation(req.getLocation()))).exceptionally(this::handleException); } @Override @@ -66,48 +71,53 @@ public CompletableFuture watch(WatchRequest params) { } @Override - public CompletableFuture stat(ISourceLocation loc) { + public CompletableFuture stat(ISourceLocationRequest req) { + var loc = req.getLocation(); logger.trace("stat: {}", loc); - return super.stat(Locations.toClientLocation(loc)).exceptionally(this::handleException); + return super.stat(new ISourceLocationRequest(Locations.toClientLocation(loc))).exceptionally(this::handleException); } @Override - public CompletableFuture list(ISourceLocation loc) { - logger.trace("list: {}", loc); - return super.list(Locations.toClientLocation(loc)).exceptionally(this::handleException); + public CompletableFuture list(ISourceLocationRequest req) { + logger.trace("list: {}", req.getLocation()); + return super.list(new ISourceLocationRequest(Locations.toClientLocation(req.getLocation()))).exceptionally(this::handleException); } @Override - public CompletableFuture mkDirectory(ISourceLocation loc) { + public CompletableFuture mkDirectory(ISourceLocationRequest req) { + var loc = req.getLocation(); logger.trace("mkDirectory: {}", loc); - return super.mkDirectory(Locations.toClientLocation(loc)).exceptionally(this::handleException); + return super.mkDirectory(new ISourceLocationRequest(Locations.toClientLocation(loc))).exceptionally(this::handleException); } @Override - public CompletableFuture readFile(ISourceLocation loc) { + public CompletableFuture readFile(ISourceLocationRequest req) { + var loc = req.getLocation(); logger.trace("readFile: {}", loc); - return super.readFile(Locations.toClientLocation(loc)).exceptionally(this::handleException); + return super.readFile(new ISourceLocationRequest(Locations.toClientLocation(loc))).exceptionally(this::handleException); } @Override - public CompletableFuture writeFile(ISourceLocation loc, String content, boolean append) { + public CompletableFuture writeFile(WriteFileRequest req) { + var loc = req.getLocation(); logger.info("writeFile: {}", loc); if (reg.exists(loc) && reg.isDirectory(loc)) { return CompletableFuture.failedFuture(new ResponseErrorException(fileIsADirectory(loc))); } - return super.writeFile(Locations.toClientLocation(loc), content, append).exceptionally(this::handleException); + return super.writeFile(new WriteFileRequest(Locations.toClientLocation(loc), req.getContent(), req.isAppend())).exceptionally(this::handleException); } @Override - public CompletableFuture remove(ISourceLocation loc, boolean recursive) { + public CompletableFuture remove(RemoveRequest req) { + var loc = req.getLocation(); logger.trace("remove: {}", loc); - return super.remove(Locations.toClientLocation(loc), recursive).exceptionally(this::handleException); + return super.remove(new RemoveRequest(Locations.toClientLocation(loc), req.isRecursive())).exceptionally(this::handleException); } @Override - public CompletableFuture rename(ISourceLocation from, ISourceLocation to, boolean overwrite) { - logger.trace("rename: {} to {}", from, to); - return super.rename(Locations.toClientLocation(from), Locations.toClientLocation(to), overwrite).exceptionally(this::handleException); + public CompletableFuture rename(RenameRequest req) { + logger.trace("rename: {} to {}", req.getFrom(), req.getTo()); + return super.rename(new RenameRequest(Locations.toClientLocation(req.getFrom()), Locations.toClientLocation(req.getTo()), req.isOverwrite())).exceptionally(this::handleException); } private static ResponseError fileExists(Object data) { diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/ISourceLocationChangeType.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/ISourceLocationChangeType.java deleted file mode 100644 index 6258b89a0..000000000 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/ISourceLocationChangeType.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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.uri.jsonrpc.messages; - -import org.rascalmpl.uri.ISourceLocationWatcher; - -/** - * @see ISourceLocationWatcher.ISourceLocationChangeType this code is mirroring this type for serialization purposes - */ -public enum ISourceLocationChangeType { - CREATED(1), - DELETED(2), - MODIFIED(3); - - - private final int value; - - ISourceLocationChangeType(int value) { - this.value = value; - } - - public int getValue() { - return value; - } - - public static ISourceLocationChangeType forValue(int value) { - var allValues = ISourceLocationChangeType.values(); - if (value < 1 || value > allValues.length) { - throw new IllegalArgumentException("Illegal enum value: " + value); - } - return allValues[value - 1]; - } - - public static ISourceLocationWatcher.ISourceLocationChangeType translate( - ISourceLocationChangeType lsp) { - switch (lsp) { - case CREATED: - return ISourceLocationWatcher.ISourceLocationChangeType.CREATED; - case DELETED: - return ISourceLocationWatcher.ISourceLocationChangeType.DELETED; - case MODIFIED: - return ISourceLocationWatcher.ISourceLocationChangeType.MODIFIED; - default: - throw new RuntimeException("Forgotten type: " + lsp); - } - } -} diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/ISourceLocationChanged.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/ISourceLocationChanged.java deleted file mode 100644 index 89a61cbf2..000000000 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/ISourceLocationChanged.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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.uri.jsonrpc.messages; - -import java.util.Objects; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.eclipse.lsp4j.jsonrpc.validation.NonNull; -import org.rascalmpl.uri.ISourceLocationWatcher; -import org.rascalmpl.vscode.lsp.util.locations.Locations; -import io.usethesource.vallang.ISourceLocation; - -public class ISourceLocationChanged { - @NonNull - private String watchId; - @NonNull - private String location; - @NonNull - private ISourceLocationChangeType changeType; - - public ISourceLocationChanged(@NonNull String watchId, @NonNull String location, @NonNull ISourceLocationChangeType changeType) { - this.watchId = watchId; - this.location = location; - this.changeType = changeType; - } - - public ISourceLocationChangeType getChangeType() { - return changeType; - } - public String getLocation() { - return location; - } - public ISourceLocation getSourceLocation() { - return Locations.toLoc(location); - } - - public String getWatchId() { - return watchId; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (obj instanceof ISourceLocationChanged) { - var other = (ISourceLocationChanged)obj; - return Objects.equals(watchId, other.watchId) - && Objects.equals(location, other.location) - && Objects.equals(changeType, other.changeType) - ; - } - return false; - } - - @Override - public int hashCode() { - return Objects.hash(watchId, location, changeType); - } - - public ISourceLocationWatcher.ISourceLocationChanged translate() { - return ISourceLocationWatcher.makeChange( - getSourceLocation(), - ISourceLocationChangeType.translate(changeType) - ); - } - - @Override - public String toString() { - return "ISourceLocationChanged [changeType=" + changeType + ", location=" + location + ", watchId=" + watchId + "]"; - } - - - - - -} diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/ISourceLocationRequest.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/ISourceLocationRequest.java deleted file mode 100644 index 257be57af..000000000 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/ISourceLocationRequest.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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.uri.jsonrpc.messages; - -import java.net.URISyntaxException; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.eclipse.lsp4j.jsonrpc.validation.NonNull; -import org.rascalmpl.vscode.lsp.util.locations.Locations; - -import io.usethesource.vallang.ISourceLocation; - -public class ISourceLocationRequest { - @NonNull - private String uri; - - public ISourceLocationRequest(@NonNull String uri) { - this.uri = uri; - } - - public ISourceLocationRequest(ISourceLocation loc) { - this(Locations.toUri(loc).toString()); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (obj instanceof ISourceLocationRequest) { - return uri.equals(((ISourceLocationRequest)obj).uri); - } - return false; - } - - @Override - public int hashCode() { - return 7 * uri.hashCode(); - } - - public String getUri() { - return uri; - } - - public ISourceLocation getLocation() throws URISyntaxException { - return Locations.toCheckedLoc(uri); - } - -} diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/RenameRequest.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/RenameRequest.java deleted file mode 100644 index cdb7f8541..000000000 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/RenameRequest.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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.uri.jsonrpc.messages; - -import java.net.URISyntaxException; -import java.util.Objects; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.eclipse.lsp4j.jsonrpc.validation.NonNull; -import org.rascalmpl.vscode.lsp.util.locations.Locations; - -import io.usethesource.vallang.ISourceLocation; - -public class RenameRequest { - @NonNull - private String from; - @NonNull - private String to; - - private boolean overwrite; - - public RenameRequest(String from, String to, boolean overwrite) { - this.from = from; - this.to = to; - this.overwrite = overwrite; - } - - public RenameRequest(ISourceLocation from, ISourceLocation to, boolean overwrite) { - this.from = Locations.toUri(from).toString(); - this.to = Locations.toUri(to).toString(); - this.overwrite = overwrite; - } - - public String getFrom() { - return from; - } - - public String getTo() { - return to; - } - - public boolean isOverwrite() { - return overwrite; - } - - public ISourceLocation getFromLocation() throws URISyntaxException { - return Locations.toCheckedLoc(from); - } - - public ISourceLocation getToLocation() throws URISyntaxException { - return Locations.toCheckedLoc(to); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (obj instanceof RenameRequest) { - var other = (RenameRequest)obj; - return Objects.equals(from, other.from) - && Objects.equals(to, other.to) - && overwrite == other.overwrite - ; - } - return false; - } - - @Override - public int hashCode() { - return Objects.hash(from, to, overwrite); - } - -} diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/WatchRequest.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/WatchRequest.java deleted file mode 100644 index 5c4196b29..000000000 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/WatchRequest.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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.uri.jsonrpc.messages; - -import java.util.Arrays; -import java.util.Objects; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.eclipse.lsp4j.jsonrpc.validation.NonNull; -import io.usethesource.vallang.ISourceLocation; - -public class WatchRequest extends ISourceLocationRequest { - - @NonNull - private String watcher; - - private boolean recursive; - - private final String[] excludes; - - public WatchRequest(ISourceLocation loc, boolean recursive, String watcher) { - super(loc); - this.recursive = recursive; - this.watcher = watcher; - this.excludes = new String[0]; - } - - public WatchRequest(@NonNull String uri, boolean recursive, @NonNull String watcher) { - super(uri); - this.recursive = recursive; - this.watcher = watcher; - this.excludes = new String[0]; - } - - public WatchRequest(String uri, boolean recursive, String[] excludes) { - super(uri); - this.recursive = recursive; - this.watcher = ""; - this.excludes = excludes; - } - - public String getWatcher() { - return watcher; - } - - public boolean isRecursive() { - return recursive; - } - - public String[] getExcludes() { - return excludes; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (obj instanceof WatchRequest) { - var other = (WatchRequest)obj; - return super.equals(other) - && other.recursive == recursive - && Objects.equals(watcher, other.watcher) - && Arrays.equals(excludes, other.excludes); - } - return false; - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), watcher, recursive, excludes); - } - -} diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/WriteFileRequest.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/WriteFileRequest.java deleted file mode 100644 index 5fc28d1d7..000000000 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/messages/WriteFileRequest.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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.uri.jsonrpc.messages; - -import java.util.Objects; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.eclipse.lsp4j.jsonrpc.validation.NonNull; -import io.usethesource.vallang.ISourceLocation; - -public class WriteFileRequest extends ISourceLocationRequest { - - @NonNull - private final String content; - - private final boolean append; - private final boolean create; - private final boolean overwrite; - - public WriteFileRequest(@NonNull String uri, @NonNull String content, boolean append) { - super(uri); - this.content = content; - this.append = append; - this.create = true; - this.overwrite = !append; - } - - public WriteFileRequest(ISourceLocation loc, String content, boolean append) { - super(loc); - this.content = content; - this.append = append; - this.create = true; - this.overwrite = !append; - } - - public WriteFileRequest(@NonNull String uri, @NonNull String content, boolean append, boolean create, boolean overwite) { - super(uri); - this.content = content; - this.append = append; - this.create = create; - this.overwrite = overwite; - } - - public String getContent() { - return content; - } - - public boolean getAppend() { - return append; - } - - public boolean isCreate() { - return create; - } - - public boolean isOverwrite() { - return overwrite; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (obj instanceof WriteFileRequest) { - var other = (WriteFileRequest)obj; - return super.equals(obj) - && content.equals(other.content) - && append == other.append - && create == other.create - && overwrite == other.overwrite; - } - return false; - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), content, append); - } - -} diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts index c758ee68f..3b755bc4e 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts @@ -60,7 +60,6 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { sendRequest(uri : vscode.Uri, method: string): Promise; sendRequest(uri : vscode.Uri, method: string, param: A): Promise; - sendRequest(uri : vscode.Uri, method: string, param0: A0, param1: A1): Promise; sendRequest(uri : vscode.Uri, method: string, param?: A): Promise { return this.client.sendRequest(method, param ?? this.toRascalUri(uri) ) .catch((r: ResponseError) => { diff --git a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts index 5693d8aa1..273b6f1f4 100644 --- a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts +++ b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts @@ -46,23 +46,23 @@ interface VSCodeResolverServer extends ISourceLocationInput, ISourceLocationOutp // Rascal's interface reduced to a subset we can support interface ISourceLocationInput { - readFile(req: ISourceLocation): Promise; - exists(req: ISourceLocation): Promise; - lastModified(req: ISourceLocation): Promise; - created(req: ISourceLocation): Promise; - isDirectory(req: ISourceLocation): Promise; - isFile(req: ISourceLocation): Promise; - list(req: ISourceLocation): Promise; - size(req: ISourceLocation): Promise; - fileStat(req: ISourceLocation): Promise; - isReadable(req: ISourceLocation): Promise; - isWritable(req: ISourceLocation): Promise; + readFile(req: ISourceLocationRequest): Promise; + exists(req: ISourceLocationRequest): Promise; + lastModified(req: ISourceLocationRequest): Promise; + created(req: ISourceLocationRequest): Promise; + isDirectory(req: ISourceLocationRequest): Promise; + isFile(req: ISourceLocationRequest): Promise; + list(req: ISourceLocationRequest): Promise; + size(req: ISourceLocationRequest): Promise; + fileStat(req: ISourceLocationRequest): Promise; + isReadable(req: ISourceLocationRequest): Promise; + isWritable(req: ISourceLocationRequest): Promise; } function connectInputHandler(connection: rpc.MessageConnection, handler: ISourceLocationInput, toClear: Disposable[]) { - function req (method: string, h: rpc.RequestHandler1) { + function req (method: string, h: rpc.RequestHandler1) { toClear.push(connection.onRequest( - new rpc.RequestType1("rascal/vfs/input/" + method), + new rpc.RequestType1("rascal/vfs/input/" + method), h.bind(handler))); } req("readFile", handler.readFile); @@ -80,32 +80,23 @@ function connectInputHandler(connection: rpc.MessageConnection, handler: ISource // Rascal's interface reduced to a subset we can support interface ISourceLocationOutput { - writeFile(uri: ISourceLocation, content: string, append: boolean): Promise; - mkDirectory(uri: ISourceLocation): Promise; - remove(uri: ISourceLocation, recursive: boolean): Promise; - rename(from: ISourceLocation, to: ISourceLocation, overwrite: boolean): Promise; + writeFile(req: WriteFileRequest): Promise; + mkDirectory(req: ISourceLocationRequest): Promise; + remove(req: RemoveRequest): Promise; + rename(req: RenameRequest): Promise; } function connectOutputHandler(connection: rpc.MessageConnection, handler: ISourceLocationOutput, toClear: Disposable[]) { - function req1 (method: string, h: rpc.RequestHandler1) { + function req (method: string, h: rpc.RequestHandler1) { toClear.push(connection.onRequest( new rpc.RequestType1("rascal/vfs/output/" + method), h.bind(handler))); } - function req2 (method: string, h: rpc.RequestHandler2) { - toClear.push(connection.onRequest( - new rpc.RequestType2("rascal/vfs/output/" + method), - h.bind(handler))); - } - function req3 (method: string, h: rpc.RequestHandler3) { - toClear.push(connection.onRequest( - new rpc.RequestType3("rascal/vfs/output/" + method), - h.bind(handler))); - } - req3("writeFile", handler.writeFile); - req1("mkDirectory", handler.mkDirectory); - req2("remove", handler.remove); - req3("rename", handler.rename); + + req("writeFile", handler.writeFile); + req("mkDirectory", handler.mkDirectory); + req("remove", handler.remove); + req("rename", handler.rename); } // Rascal's interface reduced to a subset we can support @@ -115,9 +106,9 @@ interface ISourceLocationWatcher { } function connectWatchHandler(connection: rpc.MessageConnection, handler: ISourceLocationWatcher, toClear: Disposable[]) { - function req (method: string, h: rpc.RequestHandler1) { + function req (method: string, h: rpc.RequestHandler1) { toClear.push(connection.onRequest( - new rpc.RequestType1("rascal/vfs/watcher/" + method), + new rpc.RequestType1("rascal/vfs/watcher/" + method), h.bind(handler))); } req("watch", handler.watch); @@ -125,12 +116,12 @@ function connectWatchHandler(connection: rpc.MessageConnection, handler: ISource } interface ILogicalSourceLocationResolver { - resolve(req: ISourceLocation) : Promise + resolve(req: ISourceLocationRequest) : Promise } function connectLogicalResolver(connection: rpc.MessageConnection, handler: ILogicalSourceLocationResolver, toClear: Disposable[]) { toClear.push(connection.onRequest( - new rpc.RequestType1("rascal/vfs/logical/resolveLocation"), handler.resolve.bind(handler) + new rpc.RequestType1("rascal/vfs/logical/resolveLocation"), handler.resolve.bind(handler) )); } @@ -149,6 +140,25 @@ function buildWatchReceiver(connection: rpc.MessageConnection) : WatchEventRecei // Messages (requests and responses) +interface ISourceLocationRequest { + loc: ISourceLocation; +} + +interface WriteFileRequest extends ISourceLocationRequest { + content: string; + append: boolean; +} + +interface RenameRequest { + from: ISourceLocation; + to: ISourceLocation; + overwrite: boolean; +} + +interface RemoveRequest extends ISourceLocationRequest { + recursive: boolean; +} + export interface WatchRequest { loc: ISourceLocation; /** @@ -246,9 +256,9 @@ class ResolverClient implements VSCodeResolverServer, Disposable { return uri; } - async readFile(loc: ISourceLocation): Promise { - this.logger.trace("[VFS] readFile: ", loc); - return asyncCatcher(async () => Buffer.from(await this.fs.readFile(this.toUri(loc))).toString("base64")); + async readFile(req: ISourceLocationRequest): Promise { + this.logger.trace("[VFS] readFile: ", req.loc); + return asyncCatcher(async () => Buffer.from(await this.fs.readFile(this.toUri(req.loc))).toString("base64")); } isRascalNative(loc: ISourceLocation | vscode.Uri) : boolean { @@ -257,10 +267,10 @@ class ResolverClient implements VSCodeResolverServer, Disposable { return this.rascalNativeSchemes.has(scheme); } - async exists(loc: ISourceLocation): Promise { - this.logger.trace("[VFS] exists: ", loc); + async exists(req: ISourceLocationRequest): Promise { + this.logger.trace("[VFS] exists: ", req.loc); try { - await this.stat(loc); + await this.stat(req.loc); return true; } catch (_e) { @@ -268,9 +278,9 @@ class ResolverClient implements VSCodeResolverServer, Disposable { } } - async fileStat(loc: ISourceLocation): Promise { + async fileStat(req: ISourceLocationRequest): Promise { return asyncCatcher(async () => { - const fileInfo = await this.stat(loc); + const fileInfo = await this.stat(req.loc); return { exists: true, isFile: (fileInfo.type | vscode.FileType.File) > 0, @@ -293,45 +303,45 @@ class ResolverClient implements VSCodeResolverServer, Disposable { return asyncCatcher(async () => mapper((await this.stat(loc)))); } - lastModified(loc: ISourceLocation): Promise { - this.logger.trace("[VFS] lastModified: ", loc); - return this.numberResult(loc, f => f.mtime); + lastModified(req: ISourceLocationRequest): Promise { + this.logger.trace("[VFS] lastModified: ", req.loc); + return this.numberResult(req.loc, f => f.mtime); } - created(loc: ISourceLocation): Promise { - this.logger.trace("[VFS] created: ", loc); - return this.numberResult(loc, f => f.ctime); + created(req: ISourceLocationRequest): Promise { + this.logger.trace("[VFS] created: ", req.loc); + return this.numberResult(req.loc, f => f.ctime); } - size(loc: ISourceLocation): Promise { - this.logger.trace("[VFS] size: ", loc); - return this.numberResult(loc, f => f.size); + size(req: ISourceLocationRequest): Promise { + this.logger.trace("[VFS] size: ", req.loc); + return this.numberResult(req.loc, f => f.size); } private async boolResult(loc: ISourceLocation, mapper: (s :vscode.FileStat) => boolean): Promise { return asyncCatcher(async () => mapper((await this.stat(loc)))); } - isDirectory(loc: ISourceLocation): Promise { - this.logger.trace("[VFS] isDirectory: ", loc); - return this.boolResult(loc, f => f.type === vscode.FileType.Directory); + isDirectory(req: ISourceLocationRequest): Promise { + this.logger.trace("[VFS] isDirectory: ", req.loc); + return this.boolResult(req.loc, f => f.type === vscode.FileType.Directory); } - isFile(loc: ISourceLocation): Promise { - this.logger.trace("[VFS] isFile: ", loc); + isFile(req: ISourceLocationRequest): Promise { + this.logger.trace("[VFS] isFile: ", req.loc); // TODO: figure out how to handle vscode.FileType.Symlink - return this.boolResult(loc, f => f.type === vscode.FileType.File); + return this.boolResult(req.loc, f => f.type === vscode.FileType.File); } - isReadable(loc: ISourceLocation): Promise { - this.logger.trace("[VFS] isReadable: ", loc); + isReadable(req: ISourceLocationRequest): Promise { + this.logger.trace("[VFS] isReadable: ", req.loc); // if we can do a stat, we can read - return this.boolResult(loc, _ => true); + return this.boolResult(req.loc, _ => true); } - async isWritable(loc: ISourceLocation): Promise { - this.logger.trace("[VFS] isWritable: ", loc); - const scheme = this.toUri(loc).scheme; + async isWritable(req: ISourceLocationRequest): Promise { + this.logger.trace("[VFS] isWritable: ", req.loc); + const scheme = this.toUri(req.loc).scheme; const writable = this.fs.isWritableFileSystem(scheme); if (writable === undefined) { throw new rpc.ResponseError(ErrorCodes.fileSystem, "Unsupported scheme: " + scheme, "Unsupported file system"); @@ -340,37 +350,38 @@ class ResolverClient implements VSCodeResolverServer, Disposable { // not a writable file system, so no need to check the uri return false; } - return this.boolResult(loc, f => f.permissions === undefined || (f.permissions & vscode.FilePermission.Readonly) === 0); + return this.boolResult(req.loc, f => f.permissions === undefined || (f.permissions & vscode.FilePermission.Readonly) === 0); } - async list(loc: ISourceLocation): Promise { - this.logger.trace("[VFS] list: ", loc); + async list(req: ISourceLocationRequest): Promise { + this.logger.trace("[VFS] list: ", req.loc); return asyncCatcher(async () => { - return (await this.fs.readDirectory(this.toUri(loc))).map(entry => {name: entry[0], type: entry[1]}); + return (await this.fs.readDirectory(this.toUri(req.loc))).map(entry => {name: entry[0], type: entry[1]}); }); } - async writeFile(uri: ISourceLocation, content: string, append: boolean): Promise { - this.logger.trace("[VFS] writeFile: ", uri); - let prefix:Buffer = Buffer.of(); - if (await this.exists(uri) && append) { - prefix = Buffer.from(await this.fs.readFile(this.toUri(uri))); + async writeFile(req: WriteFileRequest): Promise { + this.logger.trace("[VFS] writeFile: ", req.loc); + const loc = this.toUri(req.loc); + let prefix : Buffer = Buffer.of(); + if (await this.exists(req) && req.append) { + prefix = Buffer.from(await this.fs.readFile(loc)); } return asyncVoidCatcher( - this.fs.writeFile(this.toUri(uri), Buffer.concat([prefix, Buffer.from(content, "base64")])) + this.fs.writeFile(loc, Buffer.concat([prefix, Buffer.from(req.content, "base64")])) ); } - async mkDirectory(req: ISourceLocation): Promise { - this.logger.trace("[VFS] mkDirectory: ", req); - return asyncVoidCatcher(this.fs.createDirectory(this.toUri(req))); + async mkDirectory(req: ISourceLocationRequest): Promise { + this.logger.trace("[VFS] mkDirectory: ", req.loc); + return asyncVoidCatcher(this.fs.createDirectory(this.toUri(req.loc))); } - async remove(req: ISourceLocation, recursive: boolean): Promise { - this.logger.trace("[VFS] remove: ", req); - return asyncVoidCatcher(this.fs.delete(this.toUri(req), {"recursive" : recursive})); + async remove(req: RemoveRequest): Promise { + this.logger.trace("[VFS] remove: ", req.loc); + return asyncVoidCatcher(this.fs.delete(this.toUri(req.loc), {"recursive" : req.recursive})); } - async rename(from: ISourceLocation, to: ISourceLocation, overwrite: boolean): Promise { - this.logger.trace("[VFS] rename: ", from, to); - return asyncVoidCatcher(this.fs.rename(this.toUri(from), this.toUri(to), { overwrite: overwrite })); + async rename(req: RenameRequest): Promise { + this.logger.trace("[VFS] rename: ", req.from, req.to); + return asyncVoidCatcher(this.fs.rename(this.toUri(req.from), this.toUri(req.to), { overwrite: req.overwrite })); } private readonly activeWatches = new Map(); @@ -403,9 +414,9 @@ class ResolverClient implements VSCodeResolverServer, Disposable { throw new rpc.ResponseError(ErrorCodes.fileSystem, 'Watch not defined for: ' + removeWatch.loc, 'NotDefined'); } - async resolve(req: ISourceLocation): Promise { - this.logger.trace("[VFS] resolve: ", req); - return req; + async resolve(req: ISourceLocationRequest): Promise { + this.logger.trace("[VFS] resolve: ", req.loc); + return req.loc; } dispose() { From c9af911df715e54fa462c6ca5013a4d76b85f30e Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 27 Mar 2026 15:03:08 +0100 Subject: [PATCH 086/149] Removed superfluous arguments --- rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts index 3b755bc4e..80a3508d8 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts @@ -169,8 +169,6 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { return this.sendRequest(uri, "rascal/vfs/output/writeFile", { uri: this.toRascalUri(uri), append: false, - create: options.create, - overwrite: options.overwrite, content: Buffer.from(content).toString("base64") }); }); From 18e3b310be70802c10823b969656c5438ed25ad3 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 27 Mar 2026 15:03:34 +0100 Subject: [PATCH 087/149] Incorporated upstream package after a Rascal update --- .../vscode/lsp/parametric/ParserOnlyContribution.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParserOnlyContribution.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParserOnlyContribution.java index 494cf6bba..9cdef62d9 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParserOnlyContribution.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParserOnlyContribution.java @@ -32,11 +32,12 @@ import java.io.Writer; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.checkerframework.checker.nullness.qual.Nullable; import org.eclipse.lsp4j.jsonrpc.messages.Either; -import org.rascalmpl.interpreter.NullRascalMonitor; +import org.rascalmpl.debug.NullRascalMonitor; import org.rascalmpl.shell.ShellEvaluatorFactory; import org.rascalmpl.values.IRascalValueFactory; import org.rascalmpl.values.RascalFunctionValueFactory; From d0cb51a6e16348d4c123c939609e068a3be37c64 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 27 Mar 2026 15:03:49 +0100 Subject: [PATCH 088/149] Fixed potential NPE --- .../uri/jsonrpc/RascalFileSystemInVsCode.java | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java index 08085f0f6..0836dc207 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java @@ -151,23 +151,24 @@ private static ResponseError generic(@Nullable String message, Object data) { public static ResponseError translate(Throwable original) { if (original == null) { - return generic("Unknown error occurred", null); + return generic("Unknown error occurred", "Unknown error occurred"); } if (original instanceof CompletionException) { var cause = original.getCause(); - - if (cause instanceof FileNotFoundException || cause instanceof UnsupportedSchemeException || cause instanceof URISyntaxException) { - return fileNotFound(cause); - } else if (cause instanceof FileAlreadyExistsException) { - return fileExists(cause); - } else if (cause instanceof NotDirectoryException) { - return fileNotADirectory(cause); - } else if (cause instanceof SecurityException) { - return noPermissions(cause); - } else if (cause instanceof ResponseErrorException) { - return ((ResponseErrorException) cause).getResponseError(); - } else { - return generic(cause.getMessage(), original); + if (cause != null) { + if (cause instanceof FileNotFoundException || cause instanceof UnsupportedSchemeException || cause instanceof URISyntaxException) { + return fileNotFound(cause); + } else if (cause instanceof FileAlreadyExistsException) { + return fileExists(cause); + } else if (cause instanceof NotDirectoryException) { + return fileNotADirectory(cause); + } else if (cause instanceof SecurityException) { + return noPermissions(cause); + } else if (cause instanceof ResponseErrorException) { + return ((ResponseErrorException) cause).getResponseError(); + } else { + return generic(cause.getMessage(), original); + } } } return generic(original.getMessage(), original); From ed90ae398225c7821e3860ce2490893395c13a86 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 31 Mar 2026 09:42:36 +0200 Subject: [PATCH 089/149] Added jsonrpc response classes for modularity and changed the signature and implementation to use them --- .../uri/jsonrpc/RascalFileSystemInVsCode.java | 6 +- .../src/fs/RascalFileSystemInVSCode.ts | 5 +- .../src/fs/VSCodeFileSystemInRascal.ts | 103 +++++++++++------- 3 files changed, 71 insertions(+), 43 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java index 0836dc207..d9918c730 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java @@ -43,8 +43,10 @@ import org.rascalmpl.uri.UnsupportedSchemeException; import org.rascalmpl.uri.remote.RascalFileSystemServices; import org.rascalmpl.uri.remote.jsonrpc.ISourceLocationRequest; +import org.rascalmpl.uri.remote.jsonrpc.LocationContentResponse; import org.rascalmpl.uri.remote.jsonrpc.RemoveRequest; import org.rascalmpl.uri.remote.jsonrpc.RenameRequest; +import org.rascalmpl.uri.remote.jsonrpc.SourceLocationResponse; import org.rascalmpl.uri.remote.jsonrpc.WatchRequest; import org.rascalmpl.uri.remote.jsonrpc.WriteFileRequest; import org.rascalmpl.vscode.lsp.util.locations.Locations; @@ -56,7 +58,7 @@ public class RascalFileSystemInVsCode extends RascalFileSystemServices { private static final URIResolverRegistry reg = URIResolverRegistry.getInstance(); @Override - public CompletableFuture resolveLocation(ISourceLocationRequest req) { + public CompletableFuture resolveLocation(ISourceLocationRequest req) { logger.trace("resolveLocation: {}", req.getLocation()); return super.resolveLocation(new ISourceLocationRequest(Locations.toClientLocation(req.getLocation()))).exceptionally(this::handleException); } @@ -91,7 +93,7 @@ public CompletableFuture mkDirectory(ISourceLocationRequest req) { } @Override - public CompletableFuture readFile(ISourceLocationRequest req) { + public CompletableFuture readFile(ISourceLocationRequest req) { var loc = req.getLocation(); logger.trace("readFile: {}", loc); return super.readFile(new ISourceLocationRequest(Locations.toClientLocation(loc))).exceptionally(this::handleException); diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts index 80a3508d8..c21357d6a 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts @@ -26,6 +26,7 @@ */ import * as vscode from 'vscode'; import { BaseLanguageClient, ResponseError } from 'vscode-languageclient'; +import { LocationContentResponse } from './VSCodeFileSystemInRascal'; export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { readonly client: BaseLanguageClient; @@ -147,8 +148,8 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { readFile(uri: vscode.Uri): Uint8Array | Thenable { this.logger.trace(`[RascalFileSystemInVSCode] readFile: ${uri}`); - return this.sendRequest(uri, "rascal/vfs/input/readFile") - .then(str => Buffer.from(str, "base64")); + return this.sendRequest(uri, "rascal/vfs/input/readFile") + .then(r => Buffer.from(r.content, "base64")); } writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean; }): void | Thenable { diff --git a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts index 273b6f1f4..3e9e10d4d 100644 --- a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts +++ b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts @@ -46,17 +46,17 @@ interface VSCodeResolverServer extends ISourceLocationInput, ISourceLocationOutp // Rascal's interface reduced to a subset we can support interface ISourceLocationInput { - readFile(req: ISourceLocationRequest): Promise; - exists(req: ISourceLocationRequest): Promise; - lastModified(req: ISourceLocationRequest): Promise; - created(req: ISourceLocationRequest): Promise; - isDirectory(req: ISourceLocationRequest): Promise; - isFile(req: ISourceLocationRequest): Promise; + readFile(req: ISourceLocationRequest): Promise; + exists(req: ISourceLocationRequest): Promise; + lastModified(req: ISourceLocationRequest): Promise; + created(req: ISourceLocationRequest): Promise; + isDirectory(req: ISourceLocationRequest): Promise; + isFile(req: ISourceLocationRequest): Promise; list(req: ISourceLocationRequest): Promise; - size(req: ISourceLocationRequest): Promise; + size(req: ISourceLocationRequest): Promise; fileStat(req: ISourceLocationRequest): Promise; - isReadable(req: ISourceLocationRequest): Promise; - isWritable(req: ISourceLocationRequest): Promise; + isReadable(req: ISourceLocationRequest): Promise; + isWritable(req: ISourceLocationRequest): Promise; } function connectInputHandler(connection: rpc.MessageConnection, handler: ISourceLocationInput, toClear: Disposable[]) { @@ -65,17 +65,17 @@ function connectInputHandler(connection: rpc.MessageConnection, handler: ISource new rpc.RequestType1("rascal/vfs/input/" + method), h.bind(handler))); } - req("readFile", handler.readFile); - req("exists", handler.exists); - req("lastModified", handler.lastModified); - req("created", handler.created); - req("isDirectory", handler.isDirectory); - req("isFile", handler.isFile); + req("readFile", handler.readFile); + req("exists", handler.exists); + req("lastModified", handler.lastModified); + req("created", handler.created); + req("isDirectory", handler.isDirectory); + req("isFile", handler.isFile); req("list", handler.list); - req("size", handler.size); + req("size", handler.size); req("stat", handler.fileStat); - req("isReadable", handler.isReadable); - req("isWritable", handler.isWritable); + req("isReadable", handler.isReadable); + req("isWritable", handler.isWritable); } // Rascal's interface reduced to a subset we can support @@ -116,12 +116,12 @@ function connectWatchHandler(connection: rpc.MessageConnection, handler: ISource } interface ILogicalSourceLocationResolver { - resolve(req: ISourceLocationRequest) : Promise + resolve(req: ISourceLocationRequest) : Promise } function connectLogicalResolver(connection: rpc.MessageConnection, handler: ILogicalSourceLocationResolver, toClear: Disposable[]) { toClear.push(connection.onRequest( - new rpc.RequestType1("rascal/vfs/logical/resolveLocation"), handler.resolve.bind(handler) + new rpc.RequestType1("rascal/vfs/logical/resolveLocation"), handler.resolve.bind(handler) )); } @@ -181,6 +181,29 @@ export interface ISourceLocationChanged { watchId: string; } +export interface LocationContentResponse { + /** + * Base64-encoded content of a location + */ + content: string +} + +interface BooleanResponse { + value: boolean +} + +interface NumberResponse { + value: number +} + +interface TimestampResponse { + value: number +} + +interface SourceLocationResponse { + loc: ISourceLocation +} + enum ErrorCodes { generic = -1, fileSystem = -2, @@ -256,9 +279,11 @@ class ResolverClient implements VSCodeResolverServer, Disposable { return uri; } - async readFile(req: ISourceLocationRequest): Promise { + async readFile(req: ISourceLocationRequest): Promise { this.logger.trace("[VFS] readFile: ", req.loc); - return asyncCatcher(async () => Buffer.from(await this.fs.readFile(this.toUri(req.loc))).toString("base64")); + return asyncCatcher(async () => { + content: Buffer.from(await this.fs.readFile(this.toUri(req.loc))).toString("base64") + }); } isRascalNative(loc: ISourceLocation | vscode.Uri) : boolean { @@ -267,14 +292,14 @@ class ResolverClient implements VSCodeResolverServer, Disposable { return this.rascalNativeSchemes.has(scheme); } - async exists(req: ISourceLocationRequest): Promise { + async exists(req: ISourceLocationRequest): Promise { this.logger.trace("[VFS] exists: ", req.loc); try { await this.stat(req.loc); - return true; + return { value : true }; } catch (_e) { - return false; + return { value: false }; } } @@ -299,47 +324,47 @@ class ResolverClient implements VSCodeResolverServer, Disposable { return this.fs.stat(this.toUri(loc)); } - private async numberResult(loc: ISourceLocation, mapper: (s: vscode.FileStat) => number): Promise { - return asyncCatcher(async () => mapper((await this.stat(loc)))); + private async numberResult(loc: ISourceLocation, mapper: (s: vscode.FileStat) => number): Promise { + return asyncCatcher(async () => { value: mapper((await this.stat(loc))) }); } - lastModified(req: ISourceLocationRequest): Promise { + lastModified(req: ISourceLocationRequest): Promise { this.logger.trace("[VFS] lastModified: ", req.loc); return this.numberResult(req.loc, f => f.mtime); } - created(req: ISourceLocationRequest): Promise { + created(req: ISourceLocationRequest): Promise { this.logger.trace("[VFS] created: ", req.loc); return this.numberResult(req.loc, f => f.ctime); } - size(req: ISourceLocationRequest): Promise { + size(req: ISourceLocationRequest): Promise { this.logger.trace("[VFS] size: ", req.loc); return this.numberResult(req.loc, f => f.size); } - private async boolResult(loc: ISourceLocation, mapper: (s :vscode.FileStat) => boolean): Promise { - return asyncCatcher(async () => mapper((await this.stat(loc)))); + private async boolResult(loc: ISourceLocation, mapper: (s :vscode.FileStat) => boolean): Promise { + return asyncCatcher(async () => { value: mapper((await this.stat(loc))) }); } - isDirectory(req: ISourceLocationRequest): Promise { + isDirectory(req: ISourceLocationRequest): Promise { this.logger.trace("[VFS] isDirectory: ", req.loc); return this.boolResult(req.loc, f => f.type === vscode.FileType.Directory); } - isFile(req: ISourceLocationRequest): Promise { + isFile(req: ISourceLocationRequest): Promise { this.logger.trace("[VFS] isFile: ", req.loc); // TODO: figure out how to handle vscode.FileType.Symlink return this.boolResult(req.loc, f => f.type === vscode.FileType.File); } - isReadable(req: ISourceLocationRequest): Promise { + isReadable(req: ISourceLocationRequest): Promise { this.logger.trace("[VFS] isReadable: ", req.loc); // if we can do a stat, we can read return this.boolResult(req.loc, _ => true); } - async isWritable(req: ISourceLocationRequest): Promise { + async isWritable(req: ISourceLocationRequest): Promise { this.logger.trace("[VFS] isWritable: ", req.loc); const scheme = this.toUri(req.loc).scheme; const writable = this.fs.isWritableFileSystem(scheme); @@ -348,7 +373,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { } if (!writable) { // not a writable file system, so no need to check the uri - return false; + return { value: false }; } return this.boolResult(req.loc, f => f.permissions === undefined || (f.permissions & vscode.FilePermission.Readonly) === 0); } @@ -414,9 +439,9 @@ class ResolverClient implements VSCodeResolverServer, Disposable { throw new rpc.ResponseError(ErrorCodes.fileSystem, 'Watch not defined for: ' + removeWatch.loc, 'NotDefined'); } - async resolve(req: ISourceLocationRequest): Promise { + async resolve(req: ISourceLocationRequest): Promise { this.logger.trace("[VFS] resolve: ", req.loc); - return req.loc; + return { loc: req.loc }; } dispose() { From 2ff7a463a1debf6d00f6f547c1b67fc20eef5eae Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 31 Mar 2026 14:33:04 +0200 Subject: [PATCH 090/149] Using request/response classes in RascalFileSystemInVSCode.ts as well --- .../src/fs/RascalFileSystemInVSCode.ts | 29 ++++++++++--------- .../src/fs/VSCodeFileSystemInRascal.ts | 10 +++---- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts index c21357d6a..4b6a0d876 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts @@ -26,7 +26,7 @@ */ import * as vscode from 'vscode'; import { BaseLanguageClient, ResponseError } from 'vscode-languageclient'; -import { LocationContentResponse } from './VSCodeFileSystemInRascal'; +import { ISourceLocationRequest, LocationContentResponse, RemoveRequest, RenameRequest, WatchRequest, WriteFileRequest } from './VSCodeFileSystemInRascal'; export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { readonly client: BaseLanguageClient; @@ -50,7 +50,10 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { // VS Code omits the leading two slashes from URIs if the autority is empty *and* the scheme is not equals to "file" // Rascal does not support this style of URIs, so we add the slashes before sending the URI over - toRascalUri(uri: vscode.Uri) : string { + toRascalUri(uri: vscode.Uri | string) : string { + if (typeof(uri) === "string") { + return uri; + } const uriString = uri.toString(); if (uri.authority === "" && uri.scheme !== "file") { const colon = uri.scheme.length + 1; @@ -59,10 +62,10 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { return uriString; } - sendRequest(uri : vscode.Uri, method: string): Promise; - sendRequest(uri : vscode.Uri, method: string, param: A): Promise; - sendRequest(uri : vscode.Uri, method: string, param?: A): Promise { - return this.client.sendRequest(method, param ?? this.toRascalUri(uri) ) + sendRequest(uri : vscode.Uri | string, method: string): Promise; + sendRequest(uri : vscode.Uri | string, method: string, param: A): Promise; + sendRequest(uri : vscode.Uri | string, method: string, param?: A): Promise { + return this.client.sendRequest(method, param ?? { loc: this.toRascalUri(uri) } ) .catch((r: ResponseError) => { if (r !== undefined) { this.logger.debug("Got response error from the file system: ", r); @@ -109,7 +112,7 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { watch(uri: vscode.Uri, options: { recursive: boolean; excludes: string[]; }): vscode.Disposable { this.logger.trace(`[RascalFileSystemInVSCode] watch: ${uri}`); - this.sendRequest(uri, "rascal/vfs/watcher/watch", { + this.sendRequest(uri, "rascal/vfs/watcher/watch", { loc: this.toRascalUri(uri), recursive: options.recursive }); @@ -167,10 +170,10 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { if (s.exists && options.create && !options.overwrite) { throw vscode.FileSystemError.FileExists(`File ${uri} exists and \`create\` was set, but \`override\` was not set`); } - return this.sendRequest(uri, "rascal/vfs/output/writeFile", { - uri: this.toRascalUri(uri), - append: false, - content: Buffer.from(content).toString("base64") + return this.sendRequest(uri, "rascal/vfs/output/writeFile", { + loc: this.toRascalUri(uri), + content: Buffer.from(content).toString("base64"), + append: false }); }); }); @@ -178,12 +181,12 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { delete(uri: vscode.Uri, options: { recursive: boolean; }): void | Thenable { this.logger.trace(`[RascalFileSystemInVSCode] delete: ${uri}`); - return this.sendRequest(uri, "rascal/vfs/output/remove", {uri: this.toRascalUri(uri), recursive: options.recursive}); + return this.sendRequest(uri, "rascal/vfs/output/remove", { loc: this.toRascalUri(uri), recursive: options.recursive }); } rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean; }): void | Thenable { this.logger.trace(`[RascalFileSystemInVSCode] rename: ${oldUri} to ${newUri}`); - return this.sendRequest(oldUri, "rascal/filesystem/rename", {oldUri: this.toRascalUri(oldUri), newUri: this.toRascalUri(newUri), overwrite: options.overwrite}); + return this.sendRequest(oldUri, "rascal/filesystem/rename", { from: this.toRascalUri(oldUri), to: this.toRascalUri(newUri), overwrite: options.overwrite }); } } diff --git a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts index 3e9e10d4d..7c9ec00b7 100644 --- a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts +++ b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts @@ -31,7 +31,7 @@ import { URI } from "vscode-languageclient"; import { JsonRpcServer } from "../util/JsonRpcServer"; import { FileAttributes, FileWithType } from './RascalFileSystemInVSCode'; -declare type ISourceLocation = URI; +export declare type ISourceLocation = URI; /** * VS Code implements this and offers it to the rascal-lsp server @@ -140,22 +140,22 @@ function buildWatchReceiver(connection: rpc.MessageConnection) : WatchEventRecei // Messages (requests and responses) -interface ISourceLocationRequest { +export interface ISourceLocationRequest { loc: ISourceLocation; } -interface WriteFileRequest extends ISourceLocationRequest { +export interface WriteFileRequest extends ISourceLocationRequest { content: string; append: boolean; } -interface RenameRequest { +export interface RenameRequest { from: ISourceLocation; to: ISourceLocation; overwrite: boolean; } -interface RemoveRequest extends ISourceLocationRequest { +export interface RemoveRequest extends ISourceLocationRequest { recursive: boolean; } From b204fac45a4f43ddefa193023a1ede2ba84d1762 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 31 Mar 2026 15:02:10 +0200 Subject: [PATCH 091/149] Unified trace logging --- .../src/fs/RascalFileSystemInVSCode.ts | 16 ++++---- .../src/fs/VSCodeFileSystemInRascal.ts | 38 +++++++++---------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts index 4b6a0d876..eece1f00b 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts @@ -111,7 +111,7 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { } watch(uri: vscode.Uri, options: { recursive: boolean; excludes: string[]; }): vscode.Disposable { - this.logger.trace(`[RascalFileSystemInVSCode] watch: ${uri}`); + this.logger.trace("[RascalFileSystemInVSCode] watch: ", uri); this.sendRequest(uri, "rascal/vfs/watcher/watch", { loc: this.toRascalUri(uri), recursive: options.recursive @@ -126,7 +126,7 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { } stat(uri: vscode.Uri): vscode.FileStat | Thenable { - this.logger.trace(`[RascalFileSystemInVSCode] stat: ${uri}`); + this.logger.trace("[RascalFileSystemInVSCode] stat: ", uri); return this.sendRequest(uri, "rascal/vfs/input/stat").then(a => { type: a.isFile ? vscode.FileType.File : vscode.FileType.Directory, @@ -139,25 +139,25 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { } readDirectory(uri: vscode.Uri): [string, vscode.FileType][] | Thenable<[string, vscode.FileType][]> { - this.logger.trace(`[RascalFileSystemInVSCode] readDirectory: ${uri}`); + this.logger.trace("[RascalFileSystemInVSCode] readDirectory: ", uri); return this.sendRequest(uri, "rascal/vfs/input/list") .then(c => c.map(ft => [ft.name, ft.type])); } createDirectory(uri: vscode.Uri): void | Thenable { - this.logger.trace(`[RascalFileSystemInVSCode] createDirectory: ${uri}`); + this.logger.trace("[RascalFileSystemInVSCode] createDirectory: ", uri); return this.sendRequest(uri, "rascal/vfs/output/mkDirectory"); } readFile(uri: vscode.Uri): Uint8Array | Thenable { - this.logger.trace(`[RascalFileSystemInVSCode] readFile: ${uri}`); + this.logger.trace("[RascalFileSystemInVSCode] readFile: ", uri); return this.sendRequest(uri, "rascal/vfs/input/readFile") .then(r => Buffer.from(r.content, "base64")); } writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean; }): void | Thenable { // The `create` and `overwrite` options are handled on this side - this.logger.trace(`[RascalFileSystemInVSCode] writeFile: ${uri}`); + this.logger.trace("[RascalFileSystemInVSCode] writeFile: ", uri); //TODO (RA): refactoren: wat als `stat` een error gooit (x2) this.sendRequest(uri, "rascal/vfs/input/stat").then(s => { if (!s.exists && !options.create) { @@ -180,12 +180,12 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { } delete(uri: vscode.Uri, options: { recursive: boolean; }): void | Thenable { - this.logger.trace(`[RascalFileSystemInVSCode] delete: ${uri}`); + this.logger.trace("[RascalFileSystemInVSCode] delete: ", uri); return this.sendRequest(uri, "rascal/vfs/output/remove", { loc: this.toRascalUri(uri), recursive: options.recursive }); } rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean; }): void | Thenable { - this.logger.trace(`[RascalFileSystemInVSCode] rename: ${oldUri} to ${newUri}`); + this.logger.trace("[RascalFileSystemInVSCode] rename: , ${oldUri}, ${newUri}"); return this.sendRequest(oldUri, "rascal/filesystem/rename", { from: this.toRascalUri(oldUri), to: this.toRascalUri(newUri), overwrite: options.overwrite }); } } diff --git a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts index 7c9ec00b7..3d3a8e86b 100644 --- a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts +++ b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts @@ -260,7 +260,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { if (debug) { connection.trace(rpc.Trace.Verbose, { log: (a) => { - this.logger.debug("[VFS]: " + a); + this.logger.debug("[VSCodeFileSystemInRascal]: " + a); } }); } @@ -280,7 +280,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { } async readFile(req: ISourceLocationRequest): Promise { - this.logger.trace("[VFS] readFile: ", req.loc); + this.logger.trace("[VSCodeFileSystemInRascal] readFile: ", req.loc); return asyncCatcher(async () => { content: Buffer.from(await this.fs.readFile(this.toUri(req.loc))).toString("base64") }); @@ -293,7 +293,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { } async exists(req: ISourceLocationRequest): Promise { - this.logger.trace("[VFS] exists: ", req.loc); + this.logger.trace("[VSCodeFileSystemInRascal] exists: ", req.loc); try { await this.stat(req.loc); return { value : true }; @@ -320,7 +320,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { } private async stat(loc: ISourceLocation): Promise { - this.logger.trace("[VFS] stat: ", loc); + this.logger.trace("[VSCodeFileSystemInRascal] stat: ", loc); return this.fs.stat(this.toUri(loc)); } @@ -329,17 +329,17 @@ class ResolverClient implements VSCodeResolverServer, Disposable { } lastModified(req: ISourceLocationRequest): Promise { - this.logger.trace("[VFS] lastModified: ", req.loc); + this.logger.trace("[VSCodeFileSystemInRascal] lastModified: ", req.loc); return this.numberResult(req.loc, f => f.mtime); } created(req: ISourceLocationRequest): Promise { - this.logger.trace("[VFS] created: ", req.loc); + this.logger.trace("[VSCodeFileSystemInRascal] created: ", req.loc); return this.numberResult(req.loc, f => f.ctime); } size(req: ISourceLocationRequest): Promise { - this.logger.trace("[VFS] size: ", req.loc); + this.logger.trace("[VSCodeFileSystemInRascal] size: ", req.loc); return this.numberResult(req.loc, f => f.size); } @@ -348,24 +348,24 @@ class ResolverClient implements VSCodeResolverServer, Disposable { } isDirectory(req: ISourceLocationRequest): Promise { - this.logger.trace("[VFS] isDirectory: ", req.loc); + this.logger.trace("[VSCodeFileSystemInRascal] isDirectory: ", req.loc); return this.boolResult(req.loc, f => f.type === vscode.FileType.Directory); } isFile(req: ISourceLocationRequest): Promise { - this.logger.trace("[VFS] isFile: ", req.loc); + this.logger.trace("[VSCodeFileSystemInRascal] isFile: ", req.loc); // TODO: figure out how to handle vscode.FileType.Symlink return this.boolResult(req.loc, f => f.type === vscode.FileType.File); } isReadable(req: ISourceLocationRequest): Promise { - this.logger.trace("[VFS] isReadable: ", req.loc); + this.logger.trace("[VSCodeFileSystemInRascal] isReadable: ", req.loc); // if we can do a stat, we can read return this.boolResult(req.loc, _ => true); } async isWritable(req: ISourceLocationRequest): Promise { - this.logger.trace("[VFS] isWritable: ", req.loc); + this.logger.trace("[VSCodeFileSystemInRascal] isWritable: ", req.loc); const scheme = this.toUri(req.loc).scheme; const writable = this.fs.isWritableFileSystem(scheme); if (writable === undefined) { @@ -379,14 +379,14 @@ class ResolverClient implements VSCodeResolverServer, Disposable { } async list(req: ISourceLocationRequest): Promise { - this.logger.trace("[VFS] list: ", req.loc); + this.logger.trace("[VSCodeFileSystemInRascal] list: ", req.loc); return asyncCatcher(async () => { return (await this.fs.readDirectory(this.toUri(req.loc))).map(entry => {name: entry[0], type: entry[1]}); }); } async writeFile(req: WriteFileRequest): Promise { - this.logger.trace("[VFS] writeFile: ", req.loc); + this.logger.trace("[VSCodeFileSystemInRascal] writeFile: ", req.loc); const loc = this.toUri(req.loc); let prefix : Buffer = Buffer.of(); if (await this.exists(req) && req.append) { @@ -397,22 +397,22 @@ class ResolverClient implements VSCodeResolverServer, Disposable { ); } async mkDirectory(req: ISourceLocationRequest): Promise { - this.logger.trace("[VFS] mkDirectory: ", req.loc); + this.logger.trace("[VSCodeFileSystemInRascal] mkDirectory: ", req.loc); return asyncVoidCatcher(this.fs.createDirectory(this.toUri(req.loc))); } async remove(req: RemoveRequest): Promise { - this.logger.trace("[VFS] remove: ", req.loc); + this.logger.trace("[VSCodeFileSystemInRascal] remove: ", req.loc); return asyncVoidCatcher(this.fs.delete(this.toUri(req.loc), {"recursive" : req.recursive})); } async rename(req: RenameRequest): Promise { - this.logger.trace("[VFS] rename: ", req.from, req.to); + this.logger.trace("[VSCodeFileSystemInRascal] rename: ", req.from, req.to); return asyncVoidCatcher(this.fs.rename(this.toUri(req.from), this.toUri(req.to), { overwrite: req.overwrite })); } private readonly activeWatches = new Map(); async watch(newWatch: WatchRequest): Promise { - this.logger.trace("[VFS] watch: ", newWatch.loc); + this.logger.trace("[VSCodeFileSystemInRascal] watch: ", newWatch.loc); const watchKey = newWatch.loc + newWatch.recursive; if (!this.activeWatches.has(watchKey)) { const watcher = new WatcherCallbacks(this.toUri(newWatch.loc), newWatch.recursive, this.watchListener, newWatch.watcher); @@ -424,7 +424,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { } async unwatch(removeWatch: WatchRequest): Promise { - this.logger.trace("[VFS] unwatch: ", removeWatch.loc); + this.logger.trace("[VSCodeFileSystemInRascal] unwatch: ", removeWatch.loc); const watchKey = removeWatch.loc + removeWatch.recursive; const watcher = this.activeWatches.get(watchKey); if (watcher) { @@ -440,7 +440,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { } async resolve(req: ISourceLocationRequest): Promise { - this.logger.trace("[VFS] resolve: ", req.loc); + this.logger.trace("[VSCodeFileSystemInRascal] resolve: ", req.loc); return { loc: req.loc }; } From 7bee3313144bfa570622334ad0dcf391f1a168ad Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 31 Mar 2026 15:02:32 +0200 Subject: [PATCH 092/149] Added UI test --- .../src/test/vscode-suite/repl.test.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rascal-vscode-extension/src/test/vscode-suite/repl.test.ts b/rascal-vscode-extension/src/test/vscode-suite/repl.test.ts index 011c95487..be0b6eff7 100644 --- a/rascal-vscode-extension/src/test/vscode-suite/repl.test.ts +++ b/rascal-vscode-extension/src/test/vscode-suite/repl.test.ts @@ -97,6 +97,14 @@ describe('REPL', function () { await driver.wait(async () => await (await bench.getEditorView().getActiveTab())?.getTitle() === "LanguageServer.rsc", Delays.slow, "LanguageServer should be opened"); }); + it("edit call stdlib module via repl", async() => { + const repl = new RascalREPL(bench, driver); + await repl.start(); + await repl.execute(":edit IO", true, Delays.extremelySlow); + + await driver.wait(async () => await (await bench.getEditorView().getActiveTab())?.getTitle() === "IO.rsc", Delays.slow, "IO should be opened"); + }); + it("VFS works", async() => { const repl = new RascalREPL(bench, driver); await repl.start(); From 43a9dde144ece48eb2647c474383e27acb988f14 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 1 Apr 2026 13:17:38 +0200 Subject: [PATCH 093/149] Apply suggestions from code review Co-authored-by: Toine Hartman --- rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts | 2 +- rascal-vscode-extension/src/test/vscode-suite/repl.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts index eece1f00b..6093f7438 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts @@ -48,7 +48,7 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { }); } - // VS Code omits the leading two slashes from URIs if the autority is empty *and* the scheme is not equals to "file" + // VS Code omits the leading two slashes from URIs if the autority is empty *and* the scheme is not equal to "file" // Rascal does not support this style of URIs, so we add the slashes before sending the URI over toRascalUri(uri: vscode.Uri | string) : string { if (typeof(uri) === "string") { diff --git a/rascal-vscode-extension/src/test/vscode-suite/repl.test.ts b/rascal-vscode-extension/src/test/vscode-suite/repl.test.ts index be0b6eff7..6ce4cde8b 100644 --- a/rascal-vscode-extension/src/test/vscode-suite/repl.test.ts +++ b/rascal-vscode-extension/src/test/vscode-suite/repl.test.ts @@ -89,7 +89,7 @@ describe('REPL', function () { expect(repl.lastOutput).is.equal("5\nint: 0"); }).timeout(Delays.extremelySlow * 3); - it("edit call module via repl", async() => { + it("open module editor via repl", async() => { const repl = new RascalREPL(bench, driver); await repl.start(); await repl.execute(":edit demo::lang::pico::LanguageServer", true, Delays.extremelySlow); @@ -97,7 +97,7 @@ describe('REPL', function () { await driver.wait(async () => await (await bench.getEditorView().getActiveTab())?.getTitle() === "LanguageServer.rsc", Delays.slow, "LanguageServer should be opened"); }); - it("edit call stdlib module via repl", async() => { + it("open stdlib module editor via repl", async() => { const repl = new RascalREPL(bench, driver); await repl.start(); await repl.execute(":edit IO", true, Delays.extremelySlow); From 23587dd27e506fdfb93b3c98e1fdbafe06a1fdb6 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 1 Apr 2026 13:59:51 +0200 Subject: [PATCH 094/149] Update rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java Co-authored-by: Toine Hartman --- .../vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java index d9918c730..ed3ade449 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java @@ -176,7 +176,7 @@ public static ResponseError translate(Throwable original) { return generic(original.getMessage(), original); } - private T handleException(Throwable t) throws ResponseErrorException { + private T handleException(Throwable t) throws ResponseErrorException { throw new ResponseErrorException(translate(t)); } } From c20043eead2fa914649e0e44331f9d1ad3c8d23f Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 1 Apr 2026 13:14:47 +0200 Subject: [PATCH 095/149] Using @JsonSegment for cleaner jsonrpc interface defintions --- .../rascalmpl/vscode/lsp/IBaseLanguageClient.java | 14 ++++++++------ .../vscode/lsp/IBaseLanguageServerExtensions.java | 14 ++++++++------ .../vscode/lsp/parametric/LanguageRegistry.java | 6 ++++-- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseLanguageClient.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseLanguageClient.java index eeeece01e..a5a0d068b 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseLanguageClient.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseLanguageClient.java @@ -31,6 +31,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.jsonrpc.services.JsonNotification; +import org.eclipse.lsp4j.jsonrpc.services.JsonSegment; import org.eclipse.lsp4j.services.LanguageClient; import org.rascalmpl.uri.vfs.IRemoteResolverRegistryClient; import org.rascalmpl.vscode.lsp.parametric.LanguageRegistry.LanguageParameter; @@ -38,29 +39,30 @@ import io.usethesource.vallang.IInteger; import io.usethesource.vallang.IString; +@JsonSegment("rascal") public interface IBaseLanguageClient extends LanguageClient, IRemoteResolverRegistryClient { - @JsonNotification("rascal/showContent") + @JsonNotification void showContent(URI uri, IString title, IInteger viewColumn); - @JsonNotification("rascal/receiveRegisterLanguage") + @JsonNotification void receiveRegisterLanguage(LanguageParameter lang); - @JsonNotification("rascal/receiveUnregisterLanguage") + @JsonNotification void receiveUnregisterLanguage(LanguageParameter lang); - @JsonNotification("rascal/editDocument") + @JsonNotification void editDocument(URI uri, @Nullable Range range, int viewColumn); /** * Notification sent to the vscode client to start a debugging session on the given debug adapter port */ - @JsonNotification("rascal/startDebuggingSession") + @JsonNotification void startDebuggingSession(int serverPort); /** * Notification sent to the vscode client to register the port on which the debug adapter server is listening * It is then used to make the link between a terminal process ID and the corresponding debug server port */ - @JsonNotification("rascal/registerDebugServerPort") + @JsonNotification void registerDebugServerPort(int processID, int serverPort); } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseLanguageServerExtensions.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseLanguageServerExtensions.java index b8746689b..c9790e831 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseLanguageServerExtensions.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseLanguageServerExtensions.java @@ -32,34 +32,36 @@ import org.eclipse.lsp4j.jsonrpc.messages.Tuple.Two; import org.eclipse.lsp4j.jsonrpc.services.JsonNotification; import org.eclipse.lsp4j.jsonrpc.services.JsonRequest; +import org.eclipse.lsp4j.jsonrpc.services.JsonSegment; import org.eclipse.lsp4j.services.LanguageServer; import org.rascalmpl.vscode.lsp.parametric.LanguageRegistry.LanguageParameter; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.PathConfigParameter; +@JsonSegment("rascal") public interface IBaseLanguageServerExtensions extends LanguageServer { - @JsonNotification("rascal/supplyRemoteIDEServicesConfiguration") + @JsonNotification default CompletableFuture supplyRemoteIDEServicesConfiguration() { throw new UnsupportedOperationException(); } - @JsonRequest("rascal/sendRegisterLanguage") + @JsonRequest default CompletableFuture sendRegisterLanguage(LanguageParameter lang) { throw new UnsupportedOperationException(); } - @JsonRequest("rascal/sendUnregisterLanguage") + @JsonRequest default CompletableFuture sendUnregisterLanguage(LanguageParameter lang) { throw new UnsupportedOperationException(); } - @JsonRequest("rascal/supplyPathConfig") + @JsonRequest default CompletableFuture[]> supplyPathConfig(PathConfigParameter projectFolder) { throw new UnsupportedOperationException(); } - @JsonNotification("rascal/logLevel") + @JsonNotification("logLevel") void setMinimumLogLevel(String level); - @JsonRequest("rascal/vfs/schemes") + @JsonRequest("vfs/schemes") CompletableFuture fileSystemSchemes(); } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageRegistry.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageRegistry.java index 27720ff3b..8df1b9786 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageRegistry.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageRegistry.java @@ -33,6 +33,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.eclipse.lsp4j.jsonrpc.services.JsonRequest; +import org.eclipse.lsp4j.jsonrpc.services.JsonSegment; import org.rascalmpl.values.IRascalValueFactory; import io.usethesource.vallang.IConstructor; @@ -46,11 +47,12 @@ /** * Interface of the Language Registry */ +@JsonSegment("rascal") public interface LanguageRegistry { - @JsonRequest("rascal/receiveRegisterLanguage") + @JsonRequest("receiveRegisterLanguage") CompletableFuture registerLanguage(LanguageParameter lang); - @JsonRequest("rascal/receiveUnregisterLanguage") + @JsonRequest("receiveUnregisterLanguage") CompletableFuture unregisterLanguage(LanguageParameter lang); public static class LanguageParameter { From 110e5685ed23a8203c4c698a9072a0661ccdb3a4 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 1 Apr 2026 13:23:30 +0200 Subject: [PATCH 096/149] Added comment --- rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts index 3d3a8e86b..c2b868f91 100644 --- a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts +++ b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts @@ -287,7 +287,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { } isRascalNative(loc: ISourceLocation | vscode.Uri) : boolean { - //this.rascalNativeSchemes.has(uri.scheme) + // Note that `ISourceLocation` === `URI` === `string` !== `vscode.Uri` const scheme = typeof(loc) === "string" ? loc.substring(0, loc.indexOf(":")) : loc.scheme; return this.rascalNativeSchemes.has(scheme); } From 0760b067bd6ad966a81d466e5fc8e5d650377dce Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 1 Apr 2026 14:32:28 +0200 Subject: [PATCH 097/149] Avoiding intermediate streams by merging sets using Sets.union --- .../java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java index aad504825..e9dd79dd6 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java @@ -71,6 +71,7 @@ import org.rascalmpl.vscode.lsp.uri.jsonrpc.RascalFileSystemInVsCode; import org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeFileSystemInRascal; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.PathConfigParameter; +import org.rascalmpl.vscode.lsp.util.Sets; import org.rascalmpl.vscode.lsp.util.concurrent.CompletableFutureUtils; import org.rascalmpl.vscode.lsp.util.locations.Locations; @@ -339,10 +340,10 @@ public void setMinimumLogLevel(String level) { @Override public CompletableFuture fileSystemSchemes() { var reg = URIResolverRegistry.getInstance(); - Set inputs = reg.getRegisteredInputSchemes(); - Set logicals = reg.getRegisteredLogicalSchemes(); + var inputs = reg.getRegisteredInputSchemes(); + var logicals = reg.getRegisteredLogicalSchemes(); - return CompletableFuture.completedFuture(Stream.concat(inputs.stream(), logicals.stream()).toArray(String[]::new)); + return CompletableFuture.completedFuture(Sets.union(inputs, logicals).toArray(String[]::new)); } } } From 49796a0e578f56153cfe817f36beac696ff0aec9 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 1 Apr 2026 14:32:47 +0200 Subject: [PATCH 098/149] Removed unused import --- .../vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java index ed3ade449..47c73bc25 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java @@ -51,8 +51,6 @@ import org.rascalmpl.uri.remote.jsonrpc.WriteFileRequest; import org.rascalmpl.vscode.lsp.util.locations.Locations; -import io.usethesource.vallang.ISourceLocation; - public class RascalFileSystemInVsCode extends RascalFileSystemServices { private static final Logger logger = LogManager.getLogger(RascalFileSystemServices.class); private static final URIResolverRegistry reg = URIResolverRegistry.getInstance(); From 5584a2d2bc4362b46dd11e4b90fdba48bed93d2f Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 1 Apr 2026 14:58:23 +0200 Subject: [PATCH 099/149] Finished implementation of remote stat from the TS side --- .../src/fs/RascalFileSystemInVSCode.ts | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts index 6093f7438..07c2b63d8 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts @@ -27,6 +27,7 @@ import * as vscode from 'vscode'; import { BaseLanguageClient, ResponseError } from 'vscode-languageclient'; import { ISourceLocationRequest, LocationContentResponse, RemoveRequest, RenameRequest, WatchRequest, WriteFileRequest } from './VSCodeFileSystemInRascal'; +import path from 'path'; export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { readonly client: BaseLanguageClient; @@ -158,23 +159,26 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean; }): void | Thenable { // The `create` and `overwrite` options are handled on this side this.logger.trace("[RascalFileSystemInVSCode] writeFile: ", uri); - //TODO (RA): refactoren: wat als `stat` een error gooit (x2) - this.sendRequest(uri, "rascal/vfs/input/stat").then(s => { - if (!s.exists && !options.create) { + const parentUri = uri.with({path : path.dirname(uri.path)}); + Promise.all([ + this.sendRequest(uri, "rascal/vfs/input/stat"), + this.sendRequest(parentUri, "rascal/vfs/input/stat")] + ).then(r => { + const fileStat = r[0]; + const parentStat = r[1]; + if (!fileStat.exists && !options.create) { throw vscode.FileSystemError.FileNotFound(`File ${uri} does not exist and \`create\` was not set`); } - this.sendRequest(uri,"rascal/vfs/input/stat").then(p => { - if (!p.exists && options.create) { - throw vscode.FileSystemError.FileNotFound(`Parent of ${uri} does not exist but \`create\` was set`); - } - if (s.exists && options.create && !options.overwrite) { - throw vscode.FileSystemError.FileExists(`File ${uri} exists and \`create\` was set, but \`override\` was not set`); - } - return this.sendRequest(uri, "rascal/vfs/output/writeFile", { - loc: this.toRascalUri(uri), - content: Buffer.from(content).toString("base64"), - append: false - }); + if (!parentStat.exists && options.create) { + throw vscode.FileSystemError.FileNotFound(`Parent of ${uri} does not exist but \`create\` was set`); + } + if (fileStat.exists && options.create && !options.overwrite) { + throw vscode.FileSystemError.FileExists(`File ${uri} exists and \`create\` was set, but \`override\` was not set`); + } + return this.sendRequest(uri, "rascal/vfs/output/writeFile", { + loc: this.toRascalUri(uri), + content: Buffer.from(content).toString("base64"), + append: false }); }); } From 4d7e3c9c1459f82b2244fa0134074d8a8e5f3033 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 1 Apr 2026 17:08:15 +0200 Subject: [PATCH 100/149] Changed case of file for uniformity --- ...ascalFileSystemInVsCode.java => RascalFileSystemInVSCode.java} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/{RascalFileSystemInVsCode.java => RascalFileSystemInVSCode.java} (100%) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java similarity index 100% rename from rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVsCode.java rename to rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java From 06db259fb468d22ccec7e55620a3a0ebc61153f3 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 1 Apr 2026 17:08:49 +0200 Subject: [PATCH 101/149] Updated code references to changed case --- .../java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java | 4 ++-- .../vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java index e9dd79dd6..8653e33f1 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java @@ -68,7 +68,7 @@ import org.rascalmpl.vscode.lsp.log.LogRedirectConfiguration; import org.rascalmpl.vscode.lsp.parametric.LanguageRegistry.LanguageParameter; import org.rascalmpl.vscode.lsp.terminal.RemoteIDEServicesThread; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.RascalFileSystemInVsCode; +import org.rascalmpl.vscode.lsp.uri.jsonrpc.RascalFileSystemInVSCode; import org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeFileSystemInRascal; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.PathConfigParameter; import org.rascalmpl.vscode.lsp.util.Sets; @@ -126,7 +126,7 @@ private static Launcher constructLSPClient(InputStream in, .configureGson(GsonUtils.complexAsJsonObject()) .setExecutorService(threadPool) .setExceptionHandler(t -> { - return RascalFileSystemInVsCode.translate((Exception) t); + return RascalFileSystemInVSCode.translate((Exception) t); }) .create(); diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java index 47c73bc25..ba7c8fee6 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java @@ -51,7 +51,7 @@ import org.rascalmpl.uri.remote.jsonrpc.WriteFileRequest; import org.rascalmpl.vscode.lsp.util.locations.Locations; -public class RascalFileSystemInVsCode extends RascalFileSystemServices { +public class RascalFileSystemInVSCode extends RascalFileSystemServices { private static final Logger logger = LogManager.getLogger(RascalFileSystemServices.class); private static final URIResolverRegistry reg = URIResolverRegistry.getInstance(); From 8253b0f83e6fc05514b05c52d8ed0ab0d79118c6 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 1 Apr 2026 18:34:35 +0200 Subject: [PATCH 102/149] Removed unused imports --- .../main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java index 8653e33f1..7550afd17 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java @@ -37,14 +37,12 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.Properties; -import java.util.Set; import java.util.concurrent.CompletableFuture; 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 java.util.stream.Stream; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; From e1c558afcd68e31e7428f162d717d43b3aaef13e Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Thu, 2 Apr 2026 11:33:06 +0200 Subject: [PATCH 103/149] Javadoc --- .../vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java | 4 ++++ .../vscode/lsp/uri/jsonrpc/VSCodeFileSystemInRascal.java | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java index ba7c8fee6..97c8e98c7 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java @@ -51,6 +51,10 @@ import org.rascalmpl.uri.remote.jsonrpc.WriteFileRequest; import org.rascalmpl.vscode.lsp.util.locations.Locations; +/** + * Wrapper for RascalFileSystemServices handling LSP-specifics. + * In particular, locations from LSP are mapped to Rascal-friendly locations, and Rascal exceptions are mapped to exceptions LSP expects. + */ public class RascalFileSystemInVSCode extends RascalFileSystemServices { private static final Logger logger = LogManager.getLogger(RascalFileSystemServices.class); private static final URIResolverRegistry reg = URIResolverRegistry.getInstance(); diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeFileSystemInRascal.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeFileSystemInRascal.java index 31e74ef11..72db43029 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeFileSystemInRascal.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeFileSystemInRascal.java @@ -35,6 +35,10 @@ import io.usethesource.vallang.ISourceLocation; +/** + * Wrapper for RemoteExternalResolverRegistry handling LSP-specifics. + * In particular, locations from LSP are mapped to Rascal-friendly locations. + */ public class VSCodeFileSystemInRascal extends RemoteExternalResolverRegistry { public VSCodeFileSystemInRascal(int remoteResolverRegistryPort) { super(remoteResolverRegistryPort); From a842898abceabe2e01a1142035e217a247afbedb Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 8 Apr 2026 15:29:03 +0200 Subject: [PATCH 104/149] Layout --- .../vscode/lsp/uri/jsonrpc/VSCodeFileSystemInRascal.java | 1 - 1 file changed, 1 deletion(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeFileSystemInRascal.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeFileSystemInRascal.java index 72db43029..6f444ffd1 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeFileSystemInRascal.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeFileSystemInRascal.java @@ -59,5 +59,4 @@ public ISourceLocation resolve(ISourceLocation input) throws IOException { } return resolved; } - } From 6843e3c57607c08f73d9d5b156aa60ccd5695d74 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 8 Apr 2026 15:29:35 +0200 Subject: [PATCH 105/149] BaseLanguageServer no longer registers external resolver registry --- .../java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java index 7550afd17..1b3bc12e8 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java @@ -317,11 +317,6 @@ public void connect(LanguageClient client) { provideClient(actualClient); remoteIDEServicesConfiguration = RemoteIDEServicesThread.startRemoteIDEServicesServer(client, lspDocumentService, executor); logger.debug("Remote IDE Services Port {}", remoteIDEServicesConfiguration); - - var remoteResolverRegistryPort = URIResolverRegistry.getRemoteResolverRegistryPort(); - if (remoteResolverRegistryPort != null) { - URIResolverRegistry.getInstance().registerRemoteResolverRegistry(new VSCodeFileSystemInRascal(remoteResolverRegistryPort)); - } } @Override From 10343bb8fc243e5da036fcbd0cefd337559419e8 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 8 Apr 2026 15:30:02 +0200 Subject: [PATCH 106/149] Implemented remote copy in both directions --- .../src/fs/RascalFileSystemInVSCode.ts | 13 +++++++++---- .../src/fs/VSCodeFileSystemInRascal.ts | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts index 07c2b63d8..073aa313b 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts @@ -24,10 +24,10 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ +import path from 'path'; import * as vscode from 'vscode'; import { BaseLanguageClient, ResponseError } from 'vscode-languageclient'; -import { ISourceLocationRequest, LocationContentResponse, RemoveRequest, RenameRequest, WatchRequest, WriteFileRequest } from './VSCodeFileSystemInRascal'; -import path from 'path'; +import { CopyRequest, ISourceLocationRequest, LocationContentResponse, RemoveRequest, RenameRequest, WatchRequest, WriteFileRequest } from './VSCodeFileSystemInRascal'; export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { readonly client: BaseLanguageClient; @@ -189,8 +189,13 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { } rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean; }): void | Thenable { - this.logger.trace("[RascalFileSystemInVSCode] rename: , ${oldUri}, ${newUri}"); - return this.sendRequest(oldUri, "rascal/filesystem/rename", { from: this.toRascalUri(oldUri), to: this.toRascalUri(newUri), overwrite: options.overwrite }); + this.logger.trace("[RascalFileSystemInVSCode] rename: ", oldUri, newUri); + return this.sendRequest(oldUri, "rascal/vfs/output/rename", { from: this.toRascalUri(oldUri), to: this.toRascalUri(newUri), overwrite: options.overwrite }); + } + + copy(source: vscode.Uri, target: vscode.Uri, options?: { overwrite?: boolean; }) : Thenable { + this.logger.trace("[RascalFileSystemInVSCode] copy: ", source, target); + return this.sendRequest(source, "rascal/vfs/output/copy", { from: this.toRascalUri(source), to: this.toRascalUri(target), recursive: true, overwrite: options ? options.overwrite ? true : false : false }); } } diff --git a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts index c2b868f91..23492577b 100644 --- a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts +++ b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts @@ -84,6 +84,7 @@ interface ISourceLocationOutput { mkDirectory(req: ISourceLocationRequest): Promise; remove(req: RemoveRequest): Promise; rename(req: RenameRequest): Promise; + copy(req: CopyRequest): Promise; } function connectOutputHandler(connection: rpc.MessageConnection, handler: ISourceLocationOutput, toClear: Disposable[]) { @@ -155,6 +156,13 @@ export interface RenameRequest { overwrite: boolean; } +export interface CopyRequest { + from: ISourceLocation; + to: ISourceLocation; + recursive: boolean; + overwrite: boolean; +} + export interface RemoveRequest extends ISourceLocationRequest { recursive: boolean; } @@ -409,6 +417,14 @@ class ResolverClient implements VSCodeResolverServer, Disposable { return asyncVoidCatcher(this.fs.rename(this.toUri(req.from), this.toUri(req.to), { overwrite: req.overwrite })); } + async copy(req: CopyRequest): Promise { + this.logger.trace("[VSCodeFileSystemInRascal] copy: ", req.from, req.to); + if (req.recursive && await this.isDirectory({ loc: req.from })) { + throw new rpc.ResponseError(ErrorCodes.fileSystem, 'Non-recursive watch requested on a directory', req); + } + return asyncVoidCatcher(this.fs.copy(this.toUri(req.from), this.toUri(req.to), { overwrite: req.overwrite })); + } + private readonly activeWatches = new Map(); async watch(newWatch: WatchRequest): Promise { From 751d413b537a15cc646f242ba058f04de755f881 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 8 Apr 2026 15:30:19 +0200 Subject: [PATCH 107/149] Spelling --- rascal-vscode-extension/src/lsp/LanguageRegistry.ts | 2 +- rascal-vscode-extension/src/util/JsonRpcServer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rascal-vscode-extension/src/lsp/LanguageRegistry.ts b/rascal-vscode-extension/src/lsp/LanguageRegistry.ts index c93f266bd..8ea8f9454 100644 --- a/rascal-vscode-extension/src/lsp/LanguageRegistry.ts +++ b/rascal-vscode-extension/src/lsp/LanguageRegistry.ts @@ -30,7 +30,7 @@ import { Disposable, LogOutputChannel } from 'vscode'; import { JsonRpcServer } from '../util/JsonRpcServer'; /** - * Json-rpc server that handles registration and unregistration of languages + * JSON-RPC server that handles registration and unregistration of languages */ export class LanguageRegistry extends JsonRpcServer { constructor(dslLSP: ParameterizedLanguageServer, logger: LogOutputChannel) { diff --git a/rascal-vscode-extension/src/util/JsonRpcServer.ts b/rascal-vscode-extension/src/util/JsonRpcServer.ts index 18861ffe0..91f9e9af3 100644 --- a/rascal-vscode-extension/src/util/JsonRpcServer.ts +++ b/rascal-vscode-extension/src/util/JsonRpcServer.ts @@ -29,7 +29,7 @@ import * as net from 'net'; import { Disposable, LogOutputChannel } from 'vscode'; /** - * Json-rpc server that starts a server on a dynamic port + * JSON-RPC server that starts a server on a dynamic port */ export class JsonRpcServer implements Disposable { readonly serverPort: Promise; From 3f78daf71153d2e5b4449ae457dde7ff346fdd04 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 8 Apr 2026 15:36:47 +0200 Subject: [PATCH 108/149] Added copy to RascalFileSystemInVSCode wrapper --- .../vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java index 97c8e98c7..a1034b3cd 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java @@ -42,6 +42,7 @@ import org.rascalmpl.uri.URIResolverRegistry; import org.rascalmpl.uri.UnsupportedSchemeException; import org.rascalmpl.uri.remote.RascalFileSystemServices; +import org.rascalmpl.uri.remote.jsonrpc.CopyRequest; import org.rascalmpl.uri.remote.jsonrpc.ISourceLocationRequest; import org.rascalmpl.uri.remote.jsonrpc.LocationContentResponse; import org.rascalmpl.uri.remote.jsonrpc.RemoveRequest; @@ -124,6 +125,12 @@ public CompletableFuture rename(RenameRequest req) { return super.rename(new RenameRequest(Locations.toClientLocation(req.getFrom()), Locations.toClientLocation(req.getTo()), req.isOverwrite())).exceptionally(this::handleException); } + @Override + public CompletableFuture copy(CopyRequest req) { + logger.trace("copy: {} to {}", req.getFrom(), req.getTo()); + return super.copy(new CopyRequest(Locations.toClientLocation(req.getFrom()), Locations.toClientLocation(req.getTo()), req.isRecursive(), req.isOverwrite())).exceptionally(this::handleException); + } + private static ResponseError fileExists(Object data) { return new ResponseError(-1, "File exists", data); } From 07ee0716243662e5a73d35077c56c9ff50ea2767 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 8 Apr 2026 17:19:45 +0200 Subject: [PATCH 109/149] Moved isWritable to ISourceLocationOutput --- rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts index 23492577b..70f67a92b 100644 --- a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts +++ b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts @@ -56,7 +56,6 @@ interface ISourceLocationInput { size(req: ISourceLocationRequest): Promise; fileStat(req: ISourceLocationRequest): Promise; isReadable(req: ISourceLocationRequest): Promise; - isWritable(req: ISourceLocationRequest): Promise; } function connectInputHandler(connection: rpc.MessageConnection, handler: ISourceLocationInput, toClear: Disposable[]) { @@ -75,7 +74,6 @@ function connectInputHandler(connection: rpc.MessageConnection, handler: ISource req("size", handler.size); req("stat", handler.fileStat); req("isReadable", handler.isReadable); - req("isWritable", handler.isWritable); } // Rascal's interface reduced to a subset we can support @@ -85,6 +83,7 @@ interface ISourceLocationOutput { remove(req: RemoveRequest): Promise; rename(req: RenameRequest): Promise; copy(req: CopyRequest): Promise; + isWritable(req: ISourceLocationRequest): Promise; } function connectOutputHandler(connection: rpc.MessageConnection, handler: ISourceLocationOutput, toClear: Disposable[]) { @@ -98,6 +97,7 @@ function connectOutputHandler(connection: rpc.MessageConnection, handler: ISourc req("mkDirectory", handler.mkDirectory); req("remove", handler.remove); req("rename", handler.rename); + req("isWritable", handler.isWritable); } // Rascal's interface reduced to a subset we can support From bd90c664a89d6b965f40ec5e47308c3b98943c00 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 8 Apr 2026 17:19:57 +0200 Subject: [PATCH 110/149] Added explicit type --- rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts index 70f67a92b..c9deb0b8c 100644 --- a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts +++ b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts @@ -457,7 +457,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { async resolve(req: ISourceLocationRequest): Promise { this.logger.trace("[VSCodeFileSystemInRascal] resolve: ", req.loc); - return { loc: req.loc }; + return { loc: req.loc }; } dispose() { From 789e88a32ee832ac480c22eb5d7ae926a94c752c Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 8 Apr 2026 17:20:15 +0200 Subject: [PATCH 111/149] Renamed watcher to watchId --- rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts index c9deb0b8c..afd8a1a53 100644 --- a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts +++ b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts @@ -173,7 +173,7 @@ export interface WatchRequest { * subscription id, this helps the calling in linking up to the original request * as the watches are recursive */ - watcher: string; + watchId: string; recursive: boolean; } @@ -431,7 +431,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { this.logger.trace("[VSCodeFileSystemInRascal] watch: ", newWatch.loc); const watchKey = newWatch.loc + newWatch.recursive; if (!this.activeWatches.has(watchKey)) { - const watcher = new WatcherCallbacks(this.toUri(newWatch.loc), newWatch.recursive, this.watchListener, newWatch.watcher); + const watcher = new WatcherCallbacks(this.toUri(newWatch.loc), newWatch.recursive, this.watchListener, newWatch.watchId); this.activeWatches.set(watchKey, watcher); this.toClear.push(watcher); return; From 692a57d2b1fc6afc0f314025a0c72ce53b95ac16 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Wed, 8 Apr 2026 17:20:24 +0200 Subject: [PATCH 112/149] Unused import --- .../main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java | 1 - 1 file changed, 1 deletion(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java index 1b3bc12e8..527d2ef4a 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java @@ -67,7 +67,6 @@ import org.rascalmpl.vscode.lsp.parametric.LanguageRegistry.LanguageParameter; import org.rascalmpl.vscode.lsp.terminal.RemoteIDEServicesThread; import org.rascalmpl.vscode.lsp.uri.jsonrpc.RascalFileSystemInVSCode; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeFileSystemInRascal; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.PathConfigParameter; import org.rascalmpl.vscode.lsp.util.Sets; import org.rascalmpl.vscode.lsp.util.concurrent.CompletableFutureUtils; From 9776c7b9606919ebc4c3478f0266ef90a1772c36 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 14 Apr 2026 09:39:23 +0200 Subject: [PATCH 113/149] Removed exception handling/mapping in favor of Rascal's RemoteIOErrors --- .../vscode/lsp/BaseLanguageServer.java | 6 +- .../uri/jsonrpc/RascalFileSystemInVSCode.java | 99 ++++--------------- 2 files changed, 21 insertions(+), 84 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java index 527d2ef4a..c2a88aa3e 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java @@ -63,10 +63,10 @@ import org.rascalmpl.library.util.PathConfig; import org.rascalmpl.uri.URIResolverRegistry; import org.rascalmpl.uri.remote.RascalFileSystemServices; +import org.rascalmpl.uri.remote.jsonrpc.RemoteIOError; import org.rascalmpl.vscode.lsp.log.LogRedirectConfiguration; import org.rascalmpl.vscode.lsp.parametric.LanguageRegistry.LanguageParameter; import org.rascalmpl.vscode.lsp.terminal.RemoteIDEServicesThread; -import org.rascalmpl.vscode.lsp.uri.jsonrpc.RascalFileSystemInVSCode; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.PathConfigParameter; import org.rascalmpl.vscode.lsp.util.Sets; import org.rascalmpl.vscode.lsp.util.concurrent.CompletableFutureUtils; @@ -122,9 +122,7 @@ private static Launcher constructLSPClient(InputStream in, .setOutput(out) .configureGson(GsonUtils.complexAsJsonObject()) .setExecutorService(threadPool) - .setExceptionHandler(t -> { - return RascalFileSystemInVSCode.translate((Exception) t); - }) + .setExceptionHandler(t -> RemoteIOError.translate((Exception) t).getResponseError()) .create(); server.connect(clientLauncher.getRemoteProxy()); diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java index a1034b3cd..6ea03a4c4 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java @@ -26,16 +26,10 @@ */ package org.rascalmpl.vscode.lsp.uri.jsonrpc; -import java.io.FileNotFoundException; -import java.net.URISyntaxException; -import java.nio.file.FileAlreadyExistsException; -import java.nio.file.NotDirectoryException; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.checkerframework.checker.nullness.qual.Nullable; import org.eclipse.lsp4j.jsonrpc.ResponseErrorException; import org.eclipse.lsp4j.jsonrpc.messages.ResponseError; import org.rascalmpl.uri.FileAttributes; @@ -43,8 +37,10 @@ import org.rascalmpl.uri.UnsupportedSchemeException; import org.rascalmpl.uri.remote.RascalFileSystemServices; import org.rascalmpl.uri.remote.jsonrpc.CopyRequest; +import org.rascalmpl.uri.remote.jsonrpc.DirectoryListingResponse; import org.rascalmpl.uri.remote.jsonrpc.ISourceLocationRequest; import org.rascalmpl.uri.remote.jsonrpc.LocationContentResponse; +import org.rascalmpl.uri.remote.jsonrpc.RemoteIOError; import org.rascalmpl.uri.remote.jsonrpc.RemoveRequest; import org.rascalmpl.uri.remote.jsonrpc.RenameRequest; import org.rascalmpl.uri.remote.jsonrpc.SourceLocationResponse; @@ -63,43 +59,44 @@ public class RascalFileSystemInVSCode extends RascalFileSystemServices { @Override public CompletableFuture resolveLocation(ISourceLocationRequest req) { logger.trace("resolveLocation: {}", req.getLocation()); - return super.resolveLocation(new ISourceLocationRequest(Locations.toClientLocation(req.getLocation()))).exceptionally(this::handleException); + return super.resolveLocation(new ISourceLocationRequest(Locations.toClientLocation(req.getLocation()))); } @Override - public CompletableFuture watch(WatchRequest params) { - logger.trace("watch: {}", params.getLocation()); - if (Locations.isWrappedOpaque(params.getLocation())) { - throw new ResponseErrorException(translate(new UnsupportedSchemeException("Opaque locations are not supported by Rascal"))); + public CompletableFuture watch(WatchRequest req) { + var loc = req.getLocation(); + logger.trace("watch: {}", loc); + if (Locations.isWrappedOpaque(loc)) { + throw RemoteIOError.translate(new UnsupportedSchemeException("Opaque locations are not supported by Rascal: " + loc.getScheme())); } - return super.watch(params); + return super.watch(req); } @Override public CompletableFuture stat(ISourceLocationRequest req) { var loc = req.getLocation(); logger.trace("stat: {}", loc); - return super.stat(new ISourceLocationRequest(Locations.toClientLocation(loc))).exceptionally(this::handleException); + return super.stat(new ISourceLocationRequest(Locations.toClientLocation(loc))); } @Override - public CompletableFuture list(ISourceLocationRequest req) { + public CompletableFuture list(ISourceLocationRequest req) { logger.trace("list: {}", req.getLocation()); - return super.list(new ISourceLocationRequest(Locations.toClientLocation(req.getLocation()))).exceptionally(this::handleException); + return super.list(new ISourceLocationRequest(Locations.toClientLocation(req.getLocation()))); } @Override public CompletableFuture mkDirectory(ISourceLocationRequest req) { var loc = req.getLocation(); logger.trace("mkDirectory: {}", loc); - return super.mkDirectory(new ISourceLocationRequest(Locations.toClientLocation(loc))).exceptionally(this::handleException); + return super.mkDirectory(new ISourceLocationRequest(Locations.toClientLocation(loc))); } @Override public CompletableFuture readFile(ISourceLocationRequest req) { var loc = req.getLocation(); logger.trace("readFile: {}", loc); - return super.readFile(new ISourceLocationRequest(Locations.toClientLocation(loc))).exceptionally(this::handleException); + return super.readFile(new ISourceLocationRequest(Locations.toClientLocation(loc))); } @Override @@ -107,85 +104,27 @@ public CompletableFuture writeFile(WriteFileRequest req) { var loc = req.getLocation(); logger.info("writeFile: {}", loc); if (reg.exists(loc) && reg.isDirectory(loc)) { - return CompletableFuture.failedFuture(new ResponseErrorException(fileIsADirectory(loc))); + throw new ResponseErrorException(new ResponseError(RemoteIOError.IsADirectory.code, "Is a directory: " + loc, null)); } - return super.writeFile(new WriteFileRequest(Locations.toClientLocation(loc), req.getContent(), req.isAppend())).exceptionally(this::handleException); + return super.writeFile(new WriteFileRequest(Locations.toClientLocation(loc), req.getContent(), req.isAppend())); } @Override public CompletableFuture remove(RemoveRequest req) { var loc = req.getLocation(); logger.trace("remove: {}", loc); - return super.remove(new RemoveRequest(Locations.toClientLocation(loc), req.isRecursive())).exceptionally(this::handleException); + return super.remove(new RemoveRequest(Locations.toClientLocation(loc), req.isRecursive())); } @Override public CompletableFuture rename(RenameRequest req) { logger.trace("rename: {} to {}", req.getFrom(), req.getTo()); - return super.rename(new RenameRequest(Locations.toClientLocation(req.getFrom()), Locations.toClientLocation(req.getTo()), req.isOverwrite())).exceptionally(this::handleException); + return super.rename(new RenameRequest(Locations.toClientLocation(req.getFrom()), Locations.toClientLocation(req.getTo()), req.isOverwrite())); } @Override public CompletableFuture copy(CopyRequest req) { logger.trace("copy: {} to {}", req.getFrom(), req.getTo()); - return super.copy(new CopyRequest(Locations.toClientLocation(req.getFrom()), Locations.toClientLocation(req.getTo()), req.isRecursive(), req.isOverwrite())).exceptionally(this::handleException); - } - - private static ResponseError fileExists(Object data) { - return new ResponseError(-1, "File exists", data); - } - - private static ResponseError fileIsADirectory(Object data) { - return new ResponseError(-2, "File is a directory", data); - } - - private static ResponseError fileNotADirectory(Object data) { - return new ResponseError(-3, "File is not a directory", data); - } - - private static ResponseError fileNotFound(Object data) { - return new ResponseError(-4, "File is not found", data); - } - - private static ResponseError noPermissions(Object data) { - return new ResponseError(-5, "No permissions", data); - } - - @SuppressWarnings("unused") - private static ResponseError unavailable(Object data) { - return new ResponseError(-6, "Unavailable", data); - } - - private static ResponseError generic(@Nullable String message, Object data) { - return new ResponseError(-99, message == null ? "no error message was provided" : message, data); - } - - public static ResponseError translate(Throwable original) { - if (original == null) { - return generic("Unknown error occurred", "Unknown error occurred"); - } - if (original instanceof CompletionException) { - var cause = original.getCause(); - if (cause != null) { - if (cause instanceof FileNotFoundException || cause instanceof UnsupportedSchemeException || cause instanceof URISyntaxException) { - return fileNotFound(cause); - } else if (cause instanceof FileAlreadyExistsException) { - return fileExists(cause); - } else if (cause instanceof NotDirectoryException) { - return fileNotADirectory(cause); - } else if (cause instanceof SecurityException) { - return noPermissions(cause); - } else if (cause instanceof ResponseErrorException) { - return ((ResponseErrorException) cause).getResponseError(); - } else { - return generic(cause.getMessage(), original); - } - } - } - return generic(original.getMessage(), original); - } - - private T handleException(Throwable t) throws ResponseErrorException { - throw new ResponseErrorException(translate(t)); + return super.copy(new CopyRequest(Locations.toClientLocation(req.getFrom()), Locations.toClientLocation(req.getTo()), req.isRecursive(), req.isOverwrite())); } } From 7a0cb5e420859f3adef45458c91ff8310e2300ef Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 14 Apr 2026 09:39:33 +0200 Subject: [PATCH 114/149] Fixed typo --- .../main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java index c2a88aa3e..7f95c04fd 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java @@ -76,7 +76,7 @@ import io.usethesource.vallang.ISourceLocation; /** -* The main language server class for Rascal is build on top of the Eclipse lsp4j library +* The main language server class for Rascal is built on top of the Eclipse lsp4j library */ @SuppressWarnings("java:S106") // we are using system.in/system.out correctly in this class public abstract class BaseLanguageServer { From 0b881de22e0e7a604ef2111a5a4c5d2d2e9344fb Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 14 Apr 2026 09:40:43 +0200 Subject: [PATCH 115/149] Updated signature of remote list after changes in Rascal --- .../src/fs/RascalFileSystemInVSCode.ts | 21 ++-------- .../src/fs/VSCodeFileSystemInRascal.ts | 38 +++++++++++++++---- 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts index 073aa313b..6f5876660 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts @@ -27,7 +27,7 @@ import path from 'path'; import * as vscode from 'vscode'; import { BaseLanguageClient, ResponseError } from 'vscode-languageclient'; -import { CopyRequest, ISourceLocationRequest, LocationContentResponse, RemoveRequest, RenameRequest, WatchRequest, WriteFileRequest } from './VSCodeFileSystemInRascal'; +import { CopyRequest, DirectoryListingResponse, FileAttributes, ISourceLocationRequest, LocationContentResponse, RemoveRequest, RenameRequest, WatchRequest, WriteFileRequest } from './VSCodeFileSystemInRascal'; export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { readonly client: BaseLanguageClient; @@ -141,8 +141,8 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { readDirectory(uri: vscode.Uri): [string, vscode.FileType][] | Thenable<[string, vscode.FileType][]> { this.logger.trace("[RascalFileSystemInVSCode] readDirectory: ", uri); - return this.sendRequest(uri, "rascal/vfs/input/list") - .then(c => c.map(ft => [ft.name, ft.type])); + return this.sendRequest(uri, "rascal/vfs/input/list") + .then(c => c.entries.map(ft => [ft.name, ft.types.reduce((a, i) => a | i)])); } createDirectory(uri: vscode.Uri): void | Thenable { @@ -202,18 +202,3 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { function isUnknownFileSystem(scheme : string) : boolean { return vscode.workspace.fs.isWritableFileSystem(scheme) === undefined; } - -export interface FileWithType { - name: string; - type: vscode.FileType -} - -export interface FileAttributes { - exists: boolean; - isFile: boolean; - created: number; - lastModified: number; - isWritable: boolean; - isReadable: boolean; - size: number; -} diff --git a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts index afd8a1a53..12e2bf30b 100644 --- a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts +++ b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts @@ -29,7 +29,6 @@ import { Disposable } from "vscode"; import * as rpc from 'vscode-jsonrpc/node'; import { URI } from "vscode-languageclient"; import { JsonRpcServer } from "../util/JsonRpcServer"; -import { FileAttributes, FileWithType } from './RascalFileSystemInVSCode'; export declare type ISourceLocation = URI; @@ -52,7 +51,7 @@ interface ISourceLocationInput { created(req: ISourceLocationRequest): Promise; isDirectory(req: ISourceLocationRequest): Promise; isFile(req: ISourceLocationRequest): Promise; - list(req: ISourceLocationRequest): Promise; + list(req: ISourceLocationRequest): Promise; size(req: ISourceLocationRequest): Promise; fileStat(req: ISourceLocationRequest): Promise; isReadable(req: ISourceLocationRequest): Promise; @@ -70,7 +69,7 @@ function connectInputHandler(connection: rpc.MessageConnection, handler: ISource req("created", handler.created); req("isDirectory", handler.isDirectory); req("isFile", handler.isFile); - req("list", handler.list); + req("list", handler.list); req("size", handler.size); req("stat", handler.fileStat); req("isReadable", handler.isReadable); @@ -177,6 +176,16 @@ export interface WatchRequest { recursive: boolean; } +export interface FileAttributes { + exists: boolean; + isFile: boolean; + created: number; + lastModified: number; + isWritable: boolean; + isReadable: boolean; + size: number; +} + export enum ISourceLocationChangeType { created = 1, deleted = 2, @@ -212,6 +221,15 @@ interface SourceLocationResponse { loc: ISourceLocation } +export interface DirectoryListingResponse { + entries: DirectoryEntry[] +} + +export interface DirectoryEntry { + name: string; + types: vscode.FileType[] +} + enum ErrorCodes { generic = -1, fileSystem = -2, @@ -357,13 +375,13 @@ class ResolverClient implements VSCodeResolverServer, Disposable { isDirectory(req: ISourceLocationRequest): Promise { this.logger.trace("[VSCodeFileSystemInRascal] isDirectory: ", req.loc); - return this.boolResult(req.loc, f => f.type === vscode.FileType.Directory); + return this.boolResult(req.loc, f => (f.type & vscode.FileType.Directory) !== 0); } isFile(req: ISourceLocationRequest): Promise { this.logger.trace("[VSCodeFileSystemInRascal] isFile: ", req.loc); // TODO: figure out how to handle vscode.FileType.Symlink - return this.boolResult(req.loc, f => f.type === vscode.FileType.File); + return this.boolResult(req.loc, f => (f.type & vscode.FileType.File) !== 0); } isReadable(req: ISourceLocationRequest): Promise { @@ -386,13 +404,17 @@ class ResolverClient implements VSCodeResolverServer, Disposable { return this.boolResult(req.loc, f => f.permissions === undefined || (f.permissions & vscode.FilePermission.Readonly) === 0); } - async list(req: ISourceLocationRequest): Promise { + async list(req: ISourceLocationRequest): Promise { this.logger.trace("[VSCodeFileSystemInRascal] list: ", req.loc); - return asyncCatcher(async () => { - return (await this.fs.readDirectory(this.toUri(req.loc))).map(entry => {name: entry[0], type: entry[1]}); + return asyncCatcher(async () => { entries: + (await this.fs.readDirectory(this.toUri(req.loc))).map(entry => {name: entry[0], types: this.decodeFileTypeBitmask(entry[1])}) }); } + decodeFileTypeBitmask(input: number) : vscode.FileType[] { + return input === 0 ? [vscode.FileType.Unknown] : [vscode.FileType.File, vscode.FileType.Directory, vscode.FileType.SymbolicLink].filter(t => t === (t & input)); + } + async writeFile(req: WriteFileRequest): Promise { this.logger.trace("[VSCodeFileSystemInRascal] writeFile: ", req.loc); const loc = this.toUri(req.loc); From 8c66018403880e6769d79fc07ae33d8a7d913e4f Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 14 Apr 2026 14:30:47 +0200 Subject: [PATCH 116/149] Added RemoteIOError.ts modeled after the Rascal counterpart --- .../src/fs/RemoteIOError.ts | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 rascal-vscode-extension/src/fs/RemoteIOError.ts diff --git a/rascal-vscode-extension/src/fs/RemoteIOError.ts b/rascal-vscode-extension/src/fs/RemoteIOError.ts new file mode 100644 index 000000000..61e3813b7 --- /dev/null +++ b/rascal-vscode-extension/src/fs/RemoteIOError.ts @@ -0,0 +1,93 @@ +/* + * 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. + */ +import * as vscode from 'vscode'; +import * as rpc from 'vscode-jsonrpc/node'; +import { ResponseError } from "vscode-languageclient"; + +export class RemoteIOError { + static readonly fileExists = -1; + static readonly fileNotFound = -2; + static readonly isADirectory = -3; + static readonly isNotADirectory = -4; + static readonly directoryIsNotEmpty = -5; + static readonly permissionDenied = -6; + static readonly unsupportedScheme = -7; + static readonly illegalSyntax = -8; + + static readonly watchAlreadyDefined = -10; + static readonly watchNotDefined = -11; + + static readonly fileSystemError = -20; + + static readonly isRascalNative = -30; + + static readonly jsonRpcError = -40; + + static readonly unknown = -100; + + static translateResponseError(r: ResponseError | undefined, uri: vscode.Uri | string, logger: vscode.LogOutputChannel) : vscode.FileSystemError | undefined { + if (r !== undefined) { + logger.debug("Received error from Rascal file system", r); + switch (r.code) { + case RemoteIOError.fileExists: return vscode.FileSystemError.FileExists(uri); + case RemoteIOError.isADirectory: return vscode.FileSystemError.FileIsADirectory(uri); + case RemoteIOError.isNotADirectory: return vscode.FileSystemError.FileNotADirectory(uri); + case RemoteIOError.fileNotFound: return vscode.FileSystemError.FileNotFound(uri); + case RemoteIOError.permissionDenied: return vscode.FileSystemError.NoPermissions(uri); + default: return new vscode.FileSystemError(uri); + } + } + return r; + } + + static translateFileSystemError(e: vscode.FileSystemError | ResponseError | unknown, logger: vscode.LogOutputChannel) : ResponseError { + logger.debug("Received error from VS Code file system", e); + if (e instanceof vscode.FileSystemError) { + switch (e.code) { + case "FileExists": + case "EntryExists": + return new rpc.ResponseError(RemoteIOError.fileExists, e.message); + case "FileNotFound": + case "EntryNotFound": + return new rpc.ResponseError(RemoteIOError.fileNotFound, e.message); + case "FileNotADirectory": + case "EntryNotADirectory": + return new rpc.ResponseError(RemoteIOError.isADirectory, e.message); + case "FileIsADirectory": + case "EntryIsADirectory": + return new rpc.ResponseError(RemoteIOError.isNotADirectory, e.message); + case "NoPermissions": + return new rpc.ResponseError(RemoteIOError.permissionDenied, e.message); + } + return new rpc.ResponseError(RemoteIOError.fileSystemError, e.message); + } + if (e instanceof rpc.ResponseError) { + return e; + } + return new rpc.ResponseError(RemoteIOError.unknown, "Unknown error occurred"); + } +} From a7ae2594882291b8d4c99166d296e5923b29d0fe Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 14 Apr 2026 14:32:20 +0200 Subject: [PATCH 117/149] Moved asyncCatcher methods inside ResolverClient to have a logger available --- .../src/fs/VSCodeFileSystemInRascal.ts | 65 +++++++++---------- 1 file changed, 30 insertions(+), 35 deletions(-) diff --git a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts index 12e2bf30b..ebf58ca5f 100644 --- a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts +++ b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts @@ -29,6 +29,7 @@ import { Disposable } from "vscode"; import * as rpc from 'vscode-jsonrpc/node'; import { URI } from "vscode-languageclient"; import { JsonRpcServer } from "../util/JsonRpcServer"; +import { RemoteIOError } from './RemoteIOError'; export declare type ISourceLocation = URI; @@ -247,32 +248,6 @@ export class VSCodeFileSystemInRascal extends JsonRpcServer { } } -async function asyncCatcher(build: () => Thenable): Promise { - try { - return await build(); - } - catch (e: unknown) { - if (e instanceof vscode.FileSystemError) { - throw new rpc.ResponseError(ErrorCodes.fileSystem, e.message, e.code); - } - if (e instanceof rpc.ResponseError) { - throw e; - } - throw new rpc.ResponseError(ErrorCodes.generic, "" + e); - } -} - -async function asyncVoidCatcher(run: (() => Promise) | Thenable): Promise { - return asyncCatcher(() => { - if (typeof run === "function") { - return run(); - } - else { - return run; - } - }); -} - class ResolverClient implements VSCodeResolverServer, Disposable { private readonly connection: rpc.MessageConnection; private readonly watchListener: WatchEventReceiver; @@ -297,6 +272,26 @@ class ResolverClient implements VSCodeResolverServer, Disposable { connectLogicalResolver(connection, this, this.toClear); } + async asyncCatcher(build: () => Thenable): Promise { + try { + return await build(); + } + catch (e: unknown) { + throw RemoteIOError.translateFileSystemError(e, this.logger); + } + } + + async asyncVoidCatcher(run: (() => Promise) | Thenable): Promise { + return this.asyncCatcher(() => { + if (typeof run === "function") { + return run(); + } + else { + return run; + } + }); + } + toUri(loc: ISourceLocation): vscode.Uri { const uri = vscode.Uri.parse(loc); if (this.isRascalNative(uri)) { @@ -330,7 +325,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { } async fileStat(req: ISourceLocationRequest): Promise { - return asyncCatcher(async () => { + return this.asyncCatcher(async () => { const fileInfo = await this.stat(req.loc); return { exists: true, @@ -351,7 +346,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { } private async numberResult(loc: ISourceLocation, mapper: (s: vscode.FileStat) => number): Promise { - return asyncCatcher(async () => { value: mapper((await this.stat(loc))) }); + return this.asyncCatcher(async () => { value: mapper((await this.stat(loc))) }); } lastModified(req: ISourceLocationRequest): Promise { @@ -370,7 +365,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { } private async boolResult(loc: ISourceLocation, mapper: (s :vscode.FileStat) => boolean): Promise { - return asyncCatcher(async () => { value: mapper((await this.stat(loc))) }); + return this.asyncCatcher(async () => { value: mapper((await this.stat(loc))) }); } isDirectory(req: ISourceLocationRequest): Promise { @@ -406,7 +401,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { async list(req: ISourceLocationRequest): Promise { this.logger.trace("[VSCodeFileSystemInRascal] list: ", req.loc); - return asyncCatcher(async () => { entries: + return this.asyncCatcher(async () => { entries: (await this.fs.readDirectory(this.toUri(req.loc))).map(entry => {name: entry[0], types: this.decodeFileTypeBitmask(entry[1])}) }); } @@ -422,21 +417,21 @@ class ResolverClient implements VSCodeResolverServer, Disposable { if (await this.exists(req) && req.append) { prefix = Buffer.from(await this.fs.readFile(loc)); } - return asyncVoidCatcher( + return this.asyncVoidCatcher( this.fs.writeFile(loc, Buffer.concat([prefix, Buffer.from(req.content, "base64")])) ); } async mkDirectory(req: ISourceLocationRequest): Promise { this.logger.trace("[VSCodeFileSystemInRascal] mkDirectory: ", req.loc); - return asyncVoidCatcher(this.fs.createDirectory(this.toUri(req.loc))); + return this.asyncVoidCatcher(this.fs.createDirectory(this.toUri(req.loc))); } async remove(req: RemoveRequest): Promise { this.logger.trace("[VSCodeFileSystemInRascal] remove: ", req.loc); - return asyncVoidCatcher(this.fs.delete(this.toUri(req.loc), {"recursive" : req.recursive})); + return this.asyncVoidCatcher(this.fs.delete(this.toUri(req.loc), {"recursive" : req.recursive})); } async rename(req: RenameRequest): Promise { this.logger.trace("[VSCodeFileSystemInRascal] rename: ", req.from, req.to); - return asyncVoidCatcher(this.fs.rename(this.toUri(req.from), this.toUri(req.to), { overwrite: req.overwrite })); + return this.asyncVoidCatcher(this.fs.rename(this.toUri(req.from), this.toUri(req.to), { overwrite: req.overwrite })); } async copy(req: CopyRequest): Promise { @@ -444,7 +439,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { if (req.recursive && await this.isDirectory({ loc: req.from })) { throw new rpc.ResponseError(ErrorCodes.fileSystem, 'Non-recursive watch requested on a directory', req); } - return asyncVoidCatcher(this.fs.copy(this.toUri(req.from), this.toUri(req.to), { overwrite: req.overwrite })); + return this.asyncVoidCatcher(this.fs.copy(this.toUri(req.from), this.toUri(req.to), { overwrite: req.overwrite })); } private readonly activeWatches = new Map(); From 713d902dfd424d3adc7f10767eb458798513a252 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Tue, 14 Apr 2026 14:32:54 +0200 Subject: [PATCH 118/149] Mapping ResponsErrors to FileSystemErrors in both directions through RemoteIOError's methods --- .../src/fs/RascalFileSystemInVSCode.ts | 15 ++------------- .../src/fs/VSCodeFileSystemInRascal.ts | 18 ++++++------------ 2 files changed, 8 insertions(+), 25 deletions(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts index 6f5876660..a1bbb7f97 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts @@ -28,6 +28,7 @@ import path from 'path'; import * as vscode from 'vscode'; import { BaseLanguageClient, ResponseError } from 'vscode-languageclient'; import { CopyRequest, DirectoryListingResponse, FileAttributes, ISourceLocationRequest, LocationContentResponse, RemoveRequest, RenameRequest, WatchRequest, WriteFileRequest } from './VSCodeFileSystemInRascal'; +import { RemoteIOError } from './RemoteIOError'; export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { readonly client: BaseLanguageClient; @@ -68,19 +69,7 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { sendRequest(uri : vscode.Uri | string, method: string, param?: A): Promise { return this.client.sendRequest(method, param ?? { loc: this.toRascalUri(uri) } ) .catch((r: ResponseError) => { - if (r !== undefined) { - this.logger.debug("Got response error from the file system: ", r); - switch (r.code) { - case -1: throw vscode.FileSystemError.FileExists(uri); - case -2: throw vscode.FileSystemError.FileIsADirectory(uri); - case -3: throw vscode.FileSystemError.FileNotADirectory(uri); - case -4: throw vscode.FileSystemError.FileNotFound(uri); - case -5: throw vscode.FileSystemError.NoPermissions(uri); - case -6: throw vscode.FileSystemError.Unavailable(uri); - default: throw new vscode.FileSystemError(uri); - } - } - throw r; + throw RemoteIOError.translateResponseError(r, uri, this.logger); }); } diff --git a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts index ebf58ca5f..4231722cc 100644 --- a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts +++ b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts @@ -231,12 +231,6 @@ export interface DirectoryEntry { types: vscode.FileType[] } -enum ErrorCodes { - generic = -1, - fileSystem = -2, - nativeRascal = -3 -} - export class VSCodeFileSystemInRascal extends JsonRpcServer { private rascalNativeSchemes: Set = new Set(); constructor(debug: boolean, private readonly logger: vscode.LogOutputChannel) { @@ -295,14 +289,14 @@ class ResolverClient implements VSCodeResolverServer, Disposable { toUri(loc: ISourceLocation): vscode.Uri { const uri = vscode.Uri.parse(loc); if (this.isRascalNative(uri)) { - throw new rpc.ResponseError(ErrorCodes.nativeRascal, "Cannot request VFS jobs on native rascal URIs: " + loc); + throw new rpc.ResponseError(RemoteIOError.isRascalNative, `Cannot request VS jobs on native Rascal locations: ${loc}`); } return uri; } async readFile(req: ISourceLocationRequest): Promise { this.logger.trace("[VSCodeFileSystemInRascal] readFile: ", req.loc); - return asyncCatcher(async () => { + return this.asyncCatcher(async () => { content: Buffer.from(await this.fs.readFile(this.toUri(req.loc))).toString("base64") }); } @@ -390,7 +384,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { const scheme = this.toUri(req.loc).scheme; const writable = this.fs.isWritableFileSystem(scheme); if (writable === undefined) { - throw new rpc.ResponseError(ErrorCodes.fileSystem, "Unsupported scheme: " + scheme, "Unsupported file system"); + throw new rpc.ResponseError(RemoteIOError.unsupportedScheme, `Unsupported scheme: ${scheme}`); } if (!writable) { // not a writable file system, so no need to check the uri @@ -437,7 +431,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { async copy(req: CopyRequest): Promise { this.logger.trace("[VSCodeFileSystemInRascal] copy: ", req.from, req.to); if (req.recursive && await this.isDirectory({ loc: req.from })) { - throw new rpc.ResponseError(ErrorCodes.fileSystem, 'Non-recursive watch requested on a directory', req); + throw new rpc.ResponseError(RemoteIOError.isADirectory, 'Non-recursive copy requested on a directory', req); } return this.asyncVoidCatcher(this.fs.copy(this.toUri(req.from), this.toUri(req.to), { overwrite: req.overwrite })); } @@ -453,7 +447,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { this.toClear.push(watcher); return; } - throw new rpc.ResponseError(ErrorCodes.fileSystem, 'Watch already defined for: ' + newWatch.loc, 'AlreadyDefined'); + throw new rpc.ResponseError(RemoteIOError.watchAlreadyDefined, `Watch already defined: ${newWatch.loc}`); } async unwatch(removeWatch: WatchRequest): Promise { @@ -469,7 +463,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { } return; } - throw new rpc.ResponseError(ErrorCodes.fileSystem, 'Watch not defined for: ' + removeWatch.loc, 'NotDefined'); + throw new rpc.ResponseError(RemoteIOError.watchNotDefined, `Watch not defined: ${removeWatch.loc}`); } async resolve(req: ISourceLocationRequest): Promise { From d5e7c9c0aa914193971d000b44bae18d52a4fd01 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Thu, 16 Apr 2026 14:25:56 +0200 Subject: [PATCH 119/149] ActualLanguageServer now correctly extends the wrapper class --- .../java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java index 7f95c04fd..eb77b2cf7 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java @@ -62,11 +62,11 @@ import org.rascalmpl.ideservices.GsonUtils; import org.rascalmpl.library.util.PathConfig; import org.rascalmpl.uri.URIResolverRegistry; -import org.rascalmpl.uri.remote.RascalFileSystemServices; import org.rascalmpl.uri.remote.jsonrpc.RemoteIOError; import org.rascalmpl.vscode.lsp.log.LogRedirectConfiguration; import org.rascalmpl.vscode.lsp.parametric.LanguageRegistry.LanguageParameter; import org.rascalmpl.vscode.lsp.terminal.RemoteIDEServicesThread; +import org.rascalmpl.vscode.lsp.uri.jsonrpc.RascalFileSystemInVSCode; import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.PathConfigParameter; import org.rascalmpl.vscode.lsp.util.Sets; import org.rascalmpl.vscode.lsp.util.concurrent.CompletableFutureUtils; @@ -197,7 +197,7 @@ private static void startLSP(Launcher server) { } } } - private static class ActualLanguageServer extends RascalFileSystemServices implements IBaseLanguageServerExtensions, LanguageClientAware { + private static class ActualLanguageServer extends RascalFileSystemInVSCode implements IBaseLanguageServerExtensions, LanguageClientAware { static final Logger logger = LogManager.getLogger(ActualLanguageServer.class); private final IBaseTextDocumentService lspDocumentService; private final BaseWorkspaceService lspWorkspaceService; From 2ce6b9cac3887942303c15ec317b3db2f6d7881f Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Thu, 16 Apr 2026 14:26:45 +0200 Subject: [PATCH 120/149] Configuring the language servers to use the specialized remote resolver registry class --- rascal-lsp/.vscode/launch.json | 6 ++++-- rascal-vscode-extension/src/lsp/RascalLSPConnection.ts | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/rascal-lsp/.vscode/launch.json b/rascal-lsp/.vscode/launch.json index 08690b632..b1df648ea 100644 --- a/rascal-lsp/.vscode/launch.json +++ b/rascal-lsp/.vscode/launch.json @@ -11,7 +11,8 @@ "vmArgs": [ "-Dlog4j2.level=TRACE", "-Drascal.compilerClasspath=${workspaceFolder}/target/lib/rascal.jar", - "-Drascal.remoteResolverRegistryPort=8889" + "-Drascal.remoteResolverRegistryPort=8889", + "-Drascal.specializedRemoteResolverRegistryClass=org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeFileSystemInRascal" ] }, { @@ -24,7 +25,8 @@ "vmArgs": [ "-Dlog4j2.level=TRACE", "-Drascal.compilerClasspath=${workspaceFolder}/target/lib/rascal.jar", - "-Drascal.remoteResolverRegistryPort=8889" + "-Drascal.remoteResolverRegistryPort=8889", + "-Drascal.specializedRemoteResolverRegistryClass=org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeFileSystemInRascal" ] } ] diff --git a/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts b/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts index 45a38a1cb..9c8c99416 100644 --- a/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts +++ b/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts @@ -162,6 +162,7 @@ async function buildRascalServerOptions(jarPath: string, isParametricServer: boo , '-Drascal.lsp.deploy=true' , '-Drascal.compilerClasspath=' + classpath , '-Drascal.remoteResolverRegistryPort=' + remoteResolverRegistryPort + , '-Drascal.specializedRemoteResolverRegistryClass=org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeFileSystemInRascal' ]; let mainClass: string; if (isParametricServer) { From 587a6248203d76034e249d47b25bbe624bb2da40 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Thu, 16 Apr 2026 15:11:31 +0200 Subject: [PATCH 121/149] Deduplicated code --- .../lsp/uri/jsonrpc/VSCodeFileSystemInRascal.java | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeFileSystemInRascal.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeFileSystemInRascal.java index 6f444ffd1..d699f1e1a 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeFileSystemInRascal.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeFileSystemInRascal.java @@ -27,9 +27,7 @@ package org.rascalmpl.vscode.lsp.uri.jsonrpc; import java.io.IOException; -import java.net.URISyntaxException; -import org.rascalmpl.uri.URIUtil; import org.rascalmpl.uri.remote.RemoteExternalResolverRegistry; import org.rascalmpl.vscode.lsp.uri.FallbackResolver; @@ -46,17 +44,6 @@ public VSCodeFileSystemInRascal(int remoteResolverRegistryPort) { @Override public ISourceLocation resolve(ISourceLocation input) throws IOException { - var resolved = super.resolve(input); - if (FallbackResolver.getInstance().isFileManaged(resolved)) { - try { - // The offset/length part of the source location is stripped off here. - // This is reinstated by `URIResolverRegistry::resolveAndFixOffsets` - // during logical resolution - return URIUtil.changeScheme(resolved.top(), "lsp+" + resolved.getScheme()); - } catch (URISyntaxException e) { - // fall through - } - } - return resolved; + return FallbackResolver.getInstance().resolve(super.resolve(input)); } } From f0ec4eb3a14c6f562d98c2dfa803a813433d175f Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Thu, 16 Apr 2026 15:17:45 +0200 Subject: [PATCH 122/149] Renamed FallbackResolver to LSPOpenFileRedirector --- .../ParametricTextDocumentService.java | 4 ++-- .../lsp/rascal/RascalTextDocumentService.java | 4 ++-- ...solver.java => LSPOpenFileRedirector.java} | 23 ++++--------------- .../vscode/lsp/uri/LSPOpenFileResolver.java | 6 ++--- .../uri/jsonrpc/VSCodeFileSystemInRascal.java | 4 ++-- 5 files changed, 14 insertions(+), 27 deletions(-) rename rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/{FallbackResolver.java => LSPOpenFileRedirector.java} (84%) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java index 97e85b6e6..f08f22b44 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java @@ -156,7 +156,7 @@ import org.rascalmpl.vscode.lsp.rascal.conversion.KeywordParameter; import org.rascalmpl.vscode.lsp.rascal.conversion.SelectionRanges; import org.rascalmpl.vscode.lsp.rascal.conversion.SemanticTokenizer; -import org.rascalmpl.vscode.lsp.uri.FallbackResolver; +import org.rascalmpl.vscode.lsp.uri.LSPOpenFileRedirector; import org.rascalmpl.vscode.lsp.util.Maps; import org.rascalmpl.vscode.lsp.util.Versioned; import org.rascalmpl.vscode.lsp.util.concurrent.CompletableFutureUtils; @@ -224,7 +224,7 @@ public ParametricTextDocumentService(ExecutorService exec, @Nullable LanguagePar this.dedicatedLanguageName = dedicatedLanguage.getName(); this.dedicatedLanguage = dedicatedLanguage; } - FallbackResolver.getInstance().registerTextDocumentService(this); + LSPOpenFileRedirector.getInstance().registerTextDocumentService(this); } @Override diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalTextDocumentService.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalTextDocumentService.java index a13d62b58..a77c1c06f 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalTextDocumentService.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalTextDocumentService.java @@ -130,7 +130,7 @@ import org.rascalmpl.vscode.lsp.rascal.conversion.SemanticTokenizer; import org.rascalmpl.vscode.lsp.rascal.model.FileFacts; import org.rascalmpl.vscode.lsp.rascal.model.SummaryBridge; -import org.rascalmpl.vscode.lsp.uri.FallbackResolver; +import org.rascalmpl.vscode.lsp.uri.LSPOpenFileRedirector; import org.rascalmpl.vscode.lsp.util.Versioned; import org.rascalmpl.vscode.lsp.util.concurrent.CompletableFutureUtils; import org.rascalmpl.vscode.lsp.util.locations.Locations; @@ -167,7 +167,7 @@ public RascalTextDocumentService(ExecutorService exec) { this.exec = exec; this.documents = new ConcurrentHashMap<>(); this.columns = new ColumnMaps(this::getContents); - FallbackResolver.getInstance().registerTextDocumentService(this); + LSPOpenFileRedirector.getInstance().registerTextDocumentService(this); } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/LSPOpenFileRedirector.java similarity index 84% rename from rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java rename to rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/LSPOpenFileRedirector.java index bc6ecd496..b255e22d5 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/FallbackResolver.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/LSPOpenFileRedirector.java @@ -33,30 +33,23 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.rascalmpl.uri.ILogicalSourceLocationResolver; import org.rascalmpl.uri.URIUtil; import org.rascalmpl.vscode.lsp.IBaseTextDocumentService; import org.rascalmpl.vscode.lsp.TextDocumentState; import io.usethesource.vallang.ISourceLocation; -public class FallbackResolver implements ILogicalSourceLocationResolver { +public class LSPOpenFileRedirector { + private static @MonotonicNonNull LSPOpenFileRedirector instance = null; - private static @MonotonicNonNull FallbackResolver instance = null; - - public static FallbackResolver getInstance() { + public static LSPOpenFileRedirector getInstance() { if (instance == null) { - instance = new FallbackResolver(); + instance = new LSPOpenFileRedirector(); } return instance; } - private FallbackResolver() { } - - @Override - public String scheme() { - throw new UnsupportedOperationException("Scheme not supported on fallback resolver"); - } + private LSPOpenFileRedirector() { } public boolean isFileManaged(ISourceLocation file) { for (var service : textDocumentServices) { @@ -67,7 +60,6 @@ public boolean isFileManaged(ISourceLocation file) { return false; } - @Override public ISourceLocation resolve(ISourceLocation input) throws IOException { if (isFileManaged(input)) { try { @@ -82,11 +74,6 @@ public ISourceLocation resolve(ISourceLocation input) throws IOException { return input; } - @Override - public String authority() { - throw new UnsupportedOperationException("'authority' not supported by fallback resolver"); - } - private final List textDocumentServices = new CopyOnWriteArrayList<>(); @SuppressWarnings({"initialization", "argument"}) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/LSPOpenFileResolver.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/LSPOpenFileResolver.java index f81227ecb..67a2070e0 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/LSPOpenFileResolver.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/LSPOpenFileResolver.java @@ -43,7 +43,7 @@ public class LSPOpenFileResolver implements ISourceLocationInput { private TextDocumentState getEditorState(ISourceLocation uri) throws IOException { - return FallbackResolver.getInstance().getDocumentState(stripLspPrefix(uri)); + return LSPOpenFileRedirector.getInstance().getDocumentState(stripLspPrefix(uri)); } @Override @@ -58,7 +58,7 @@ public Charset getCharset(ISourceLocation uri) throws IOException { @Override public boolean exists(ISourceLocation uri) { - return FallbackResolver.getInstance().isFileManaged(stripLspPrefix(uri)); + return LSPOpenFileRedirector.getInstance().isFileManaged(stripLspPrefix(uri)); } @Override @@ -113,7 +113,7 @@ private int size(Versioned s) { @Override public boolean isReadable(ISourceLocation uri) throws IOException { - return FallbackResolver.getInstance().isFileManaged(stripLspPrefix(uri)); + return LSPOpenFileRedirector.getInstance().isFileManaged(stripLspPrefix(uri)); } @Override diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeFileSystemInRascal.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeFileSystemInRascal.java index d699f1e1a..93f37f763 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeFileSystemInRascal.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/VSCodeFileSystemInRascal.java @@ -29,7 +29,7 @@ import java.io.IOException; import org.rascalmpl.uri.remote.RemoteExternalResolverRegistry; -import org.rascalmpl.vscode.lsp.uri.FallbackResolver; +import org.rascalmpl.vscode.lsp.uri.LSPOpenFileRedirector; import io.usethesource.vallang.ISourceLocation; @@ -44,6 +44,6 @@ public VSCodeFileSystemInRascal(int remoteResolverRegistryPort) { @Override public ISourceLocation resolve(ISourceLocation input) throws IOException { - return FallbackResolver.getInstance().resolve(super.resolve(input)); + return LSPOpenFileRedirector.getInstance().resolve(super.resolve(input)); } } From ef6724b7ca6213693646908ded1f69098aac2f5a Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Thu, 16 Apr 2026 15:18:30 +0200 Subject: [PATCH 123/149] Removed artificial calls to URIResolverRegistry.getInstance() that fixed a race that can no longer happen --- .../vscode/lsp/parametric/ParametricTextDocumentService.java | 3 --- .../rascalmpl/vscode/lsp/rascal/RascalTextDocumentService.java | 3 --- 2 files changed, 6 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java index f08f22b44..7515e1ba0 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java @@ -210,9 +210,6 @@ public class ParametricTextDocumentService implements IBaseTextDocumentService, @SuppressWarnings({"initialization", "methodref.receiver.bound"}) // this::getContents public ParametricTextDocumentService(ExecutorService exec, @Nullable LanguageParameter dedicatedLanguage) { - // The following call ensures that URIResolverRegistry is initialized before FallbackResolver is accessed - URIResolverRegistry.getInstance(); - this.exec = exec; this.files = new ConcurrentHashMap<>(); this.columns = new ColumnMaps(this::getContents); diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalTextDocumentService.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalTextDocumentService.java index a77c1c06f..ed0c1daef 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalTextDocumentService.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalTextDocumentService.java @@ -161,9 +161,6 @@ public class RascalTextDocumentService implements IBaseTextDocumentService, Lang @SuppressWarnings({"initialization", "methodref.receiver.bound"}) // this::getContents public RascalTextDocumentService(ExecutorService exec) { - // The following call ensures that URIResolverRegistry is initialized before FallbackResolver is accessed - URIResolverRegistry.getInstance(); - this.exec = exec; this.documents = new ConcurrentHashMap<>(); this.columns = new ColumnMaps(this::getContents); From 665da3397f99d9bcaa2786bf596a7da5b421c14a Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Thu, 16 Apr 2026 15:45:23 +0200 Subject: [PATCH 124/149] Correctly calculating overwrite using nullish coalescing operator --- rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts index a1bbb7f97..6a639ed1d 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts @@ -184,7 +184,7 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { copy(source: vscode.Uri, target: vscode.Uri, options?: { overwrite?: boolean; }) : Thenable { this.logger.trace("[RascalFileSystemInVSCode] copy: ", source, target); - return this.sendRequest(source, "rascal/vfs/output/copy", { from: this.toRascalUri(source), to: this.toRascalUri(target), recursive: true, overwrite: options ? options.overwrite ? true : false : false }); + return this.sendRequest(source, "rascal/vfs/output/copy", { from: this.toRascalUri(source), to: this.toRascalUri(target), recursive: true, overwrite: (options && options.overwrite) ?? false }); } } From 859453fe220cee72f530dbaa2a87fe60f4722636 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Thu, 16 Apr 2026 15:46:03 +0200 Subject: [PATCH 125/149] Layout unification around colons and curly braces --- .../src/RascalTerminalLinkProvider.ts | 2 +- .../src/fs/RascalFileSystemInVSCode.ts | 20 +++++++++---------- .../src/fs/VSCodeFileSystemInRascal.ts | 20 +++++++++---------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/rascal-vscode-extension/src/RascalTerminalLinkProvider.ts b/rascal-vscode-extension/src/RascalTerminalLinkProvider.ts index e1c755982..1dbab6208 100644 --- a/rascal-vscode-extension/src/RascalTerminalLinkProvider.ts +++ b/rascal-vscode-extension/src/RascalTerminalLinkProvider.ts @@ -74,7 +74,7 @@ export class RascalTerminalLinkProvider implements TerminalLinkProvider(); readonly onDidChangeFile: vscode.Event = this._emitter.event; - private readonly protectedSchemes:string[] = ["file", "http", "https", "unknown"]; + private readonly protectedSchemes: string[] = ["file", "http", "https", "unknown"]; /** * Registers a single FileSystemProvider for every URI scheme that Rascal supports, except @@ -42,17 +42,17 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { * * @param client to use as a server for the file system provider methods */ - constructor (client:BaseLanguageClient, private readonly logger: vscode.LogOutputChannel) { + constructor (client: BaseLanguageClient, private readonly logger: vscode.LogOutputChannel) { this.client = client; - client.onNotification("rascal/vfs/watcher/fileChanged", (event:vscode.FileChangeEvent) => { + client.onNotification("rascal/vfs/watcher/fileChanged", (event: vscode.FileChangeEvent) => { this._emitter.fire([event]); }); } // VS Code omits the leading two slashes from URIs if the autority is empty *and* the scheme is not equal to "file" // Rascal does not support this style of URIs, so we add the slashes before sending the URI over - toRascalUri(uri: vscode.Uri | string) : string { + toRascalUri(uri: vscode.Uri | string): string { if (typeof(uri) === "string") { return uri; } @@ -64,9 +64,9 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { return uriString; } - sendRequest(uri : vscode.Uri | string, method: string): Promise; - sendRequest(uri : vscode.Uri | string, method: string, param: A): Promise; - sendRequest(uri : vscode.Uri | string, method: string, param?: A): Promise { + sendRequest(uri: vscode.Uri | string, method: string): Promise; + sendRequest(uri: vscode.Uri | string, method: string, param: A): Promise; + sendRequest(uri: vscode.Uri | string, method: string, param?: A): Promise { return this.client.sendRequest(method, param ?? { loc: this.toRascalUri(uri) } ) .catch((r: ResponseError) => { throw RemoteIOError.translateResponseError(r, uri, this.logger); @@ -148,7 +148,7 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean; }): void | Thenable { // The `create` and `overwrite` options are handled on this side this.logger.trace("[RascalFileSystemInVSCode] writeFile: ", uri); - const parentUri = uri.with({path : path.dirname(uri.path)}); + const parentUri = uri.with({ path: path.dirname(uri.path) }); Promise.all([ this.sendRequest(uri, "rascal/vfs/input/stat"), this.sendRequest(parentUri, "rascal/vfs/input/stat")] @@ -182,12 +182,12 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { return this.sendRequest(oldUri, "rascal/vfs/output/rename", { from: this.toRascalUri(oldUri), to: this.toRascalUri(newUri), overwrite: options.overwrite }); } - copy(source: vscode.Uri, target: vscode.Uri, options?: { overwrite?: boolean; }) : Thenable { + copy(source: vscode.Uri, target: vscode.Uri, options?: { overwrite?: boolean; }): Thenable { this.logger.trace("[RascalFileSystemInVSCode] copy: ", source, target); return this.sendRequest(source, "rascal/vfs/output/copy", { from: this.toRascalUri(source), to: this.toRascalUri(target), recursive: true, overwrite: (options && options.overwrite) ?? false }); } } -function isUnknownFileSystem(scheme : string) : boolean { +function isUnknownFileSystem(scheme: string): boolean { return vscode.workspace.fs.isWritableFileSystem(scheme) === undefined; } diff --git a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts index 4231722cc..f69b04423 100644 --- a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts +++ b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts @@ -117,7 +117,7 @@ function connectWatchHandler(connection: rpc.MessageConnection, handler: ISource } interface ILogicalSourceLocationResolver { - resolve(req: ISourceLocationRequest) : Promise + resolve(req: ISourceLocationRequest): Promise } function connectLogicalResolver(connection: rpc.MessageConnection, handler: ILogicalSourceLocationResolver, toClear: Disposable[]) { @@ -131,9 +131,9 @@ export interface WatchEventReceiver { emitWatch(event: ISourceLocationChanged): void; } -function buildWatchReceiver(connection: rpc.MessageConnection) : WatchEventReceiver { +function buildWatchReceiver(connection: rpc.MessageConnection): WatchEventReceiver { return { - emitWatch : (e) => { + emitWatch: (e) => { connection.sendNotification(new rpc.NotificationType1("rascal/vfs/watcher/sourceLocationChanged"), e); } }; @@ -301,7 +301,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { }); } - isRascalNative(loc: ISourceLocation | vscode.Uri) : boolean { + isRascalNative(loc: ISourceLocation | vscode.Uri): boolean { // Note that `ISourceLocation` === `URI` === `string` !== `vscode.Uri` const scheme = typeof(loc) === "string" ? loc.substring(0, loc.indexOf(":")) : loc.scheme; return this.rascalNativeSchemes.has(scheme); @@ -311,7 +311,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { this.logger.trace("[VSCodeFileSystemInRascal] exists: ", req.loc); try { await this.stat(req.loc); - return { value : true }; + return { value: true }; } catch (_e) { return { value: false }; @@ -358,7 +358,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { return this.numberResult(req.loc, f => f.size); } - private async boolResult(loc: ISourceLocation, mapper: (s :vscode.FileStat) => boolean): Promise { + private async boolResult(loc: ISourceLocation, mapper: (s: vscode.FileStat) => boolean): Promise { return this.asyncCatcher(async () => { value: mapper((await this.stat(loc))) }); } @@ -396,18 +396,18 @@ class ResolverClient implements VSCodeResolverServer, Disposable { async list(req: ISourceLocationRequest): Promise { this.logger.trace("[VSCodeFileSystemInRascal] list: ", req.loc); return this.asyncCatcher(async () => { entries: - (await this.fs.readDirectory(this.toUri(req.loc))).map(entry => {name: entry[0], types: this.decodeFileTypeBitmask(entry[1])}) + (await this.fs.readDirectory(this.toUri(req.loc))).map(entry => { name: entry[0], types: this.decodeFileTypeBitmask(entry[1]) }) }); } - decodeFileTypeBitmask(input: number) : vscode.FileType[] { + decodeFileTypeBitmask(input: number): vscode.FileType[] { return input === 0 ? [vscode.FileType.Unknown] : [vscode.FileType.File, vscode.FileType.Directory, vscode.FileType.SymbolicLink].filter(t => t === (t & input)); } async writeFile(req: WriteFileRequest): Promise { this.logger.trace("[VSCodeFileSystemInRascal] writeFile: ", req.loc); const loc = this.toUri(req.loc); - let prefix : Buffer = Buffer.of(); + let prefix: Buffer = Buffer.of(); if (await this.exists(req) && req.append) { prefix = Buffer.from(await this.fs.readFile(loc)); } @@ -421,7 +421,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { } async remove(req: RemoveRequest): Promise { this.logger.trace("[VSCodeFileSystemInRascal] remove: ", req.loc); - return this.asyncVoidCatcher(this.fs.delete(this.toUri(req.loc), {"recursive" : req.recursive})); + return this.asyncVoidCatcher(this.fs.delete(this.toUri(req.loc), { recursive: req.recursive })); } async rename(req: RenameRequest): Promise { this.logger.trace("[VSCodeFileSystemInRascal] rename: ", req.from, req.to); From fd86faeef2aa9ba64c0bc8a3ae6ff67badc1449a Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Thu, 16 Apr 2026 16:19:58 +0200 Subject: [PATCH 126/149] Replaced null literal --- .../vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java index 6ea03a4c4..13de77144 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java @@ -104,7 +104,7 @@ public CompletableFuture writeFile(WriteFileRequest req) { var loc = req.getLocation(); logger.info("writeFile: {}", loc); if (reg.exists(loc) && reg.isDirectory(loc)) { - throw new ResponseErrorException(new ResponseError(RemoteIOError.IsADirectory.code, "Is a directory: " + loc, null)); + throw new ResponseErrorException(new ResponseError(RemoteIOError.IsADirectory.code, "Is a directory: " + loc, req)); } return super.writeFile(new WriteFileRequest(Locations.toClientLocation(loc), req.getContent(), req.isAppend())); } From 74533e652ade433a76bc9a2b6a7dd187d47201a9 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 17 Apr 2026 09:19:46 +0200 Subject: [PATCH 127/149] Renamed toClear to disposables --- .../src/fs/VSCodeFileSystemInRascal.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts index f69b04423..51a40a4b7 100644 --- a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts +++ b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts @@ -247,7 +247,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { private readonly watchListener: WatchEventReceiver; private readonly fs: vscode.FileSystem; private readonly rascalNativeSchemes: Set; - private toClear: Disposable[] = []; + private disposables: Disposable[] = []; constructor(connection: rpc.MessageConnection, debug: boolean, rascalNativeSchemes: Set, private readonly logger: vscode.LogOutputChannel){ this.rascalNativeSchemes = rascalNativeSchemes; this.fs = vscode.workspace.fs; @@ -260,10 +260,10 @@ class ResolverClient implements VSCodeResolverServer, Disposable { }); } this.watchListener = buildWatchReceiver(connection); - connectInputHandler(connection, this, this.toClear); - connectOutputHandler(connection, this, this.toClear); - connectWatchHandler(connection, this, this.toClear); - connectLogicalResolver(connection, this, this.toClear); + connectInputHandler(connection, this, this.disposables); + connectOutputHandler(connection, this, this.disposables); + connectWatchHandler(connection, this, this.disposables); + connectLogicalResolver(connection, this, this.disposables); } async asyncCatcher(build: () => Thenable): Promise { @@ -444,7 +444,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { if (!this.activeWatches.has(watchKey)) { const watcher = new WatcherCallbacks(this.toUri(newWatch.loc), newWatch.recursive, this.watchListener, newWatch.watchId); this.activeWatches.set(watchKey, watcher); - this.toClear.push(watcher); + this.disposables.push(watcher); return; } throw new rpc.ResponseError(RemoteIOError.watchAlreadyDefined, `Watch already defined: ${newWatch.loc}`); @@ -457,9 +457,9 @@ class ResolverClient implements VSCodeResolverServer, Disposable { if (watcher) { this.activeWatches.delete(watchKey); watcher.dispose(); - const index = this.toClear.indexOf(watcher); + const index = this.disposables.indexOf(watcher); if (index >= 0) { - this.toClear.splice(index, 1); + this.disposables.splice(index, 1); } return; } @@ -473,7 +473,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { dispose() { this.activeWatches.clear(); - this.toClear.forEach(c => c.dispose()); + this.disposables.forEach(c => c.dispose()); try { this.connection.end(); } catch (_e: unknown) { From 78b3fb5a39fbfc404ad79a89e83e7f541edc7322 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 17 Apr 2026 09:19:56 +0200 Subject: [PATCH 128/149] Added empty lines between functions --- rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts index 51a40a4b7..a3849e7b6 100644 --- a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts +++ b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts @@ -419,10 +419,12 @@ class ResolverClient implements VSCodeResolverServer, Disposable { this.logger.trace("[VSCodeFileSystemInRascal] mkDirectory: ", req.loc); return this.asyncVoidCatcher(this.fs.createDirectory(this.toUri(req.loc))); } + async remove(req: RemoveRequest): Promise { this.logger.trace("[VSCodeFileSystemInRascal] remove: ", req.loc); return this.asyncVoidCatcher(this.fs.delete(this.toUri(req.loc), { recursive: req.recursive })); } + async rename(req: RenameRequest): Promise { this.logger.trace("[VSCodeFileSystemInRascal] rename: ", req.from, req.to); return this.asyncVoidCatcher(this.fs.rename(this.toUri(req.from), this.toUri(req.to), { overwrite: req.overwrite })); From 72a67f433b6bb20307ef70f9089ffaa16fdca9bf Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 17 Apr 2026 09:22:19 +0200 Subject: [PATCH 129/149] Changed jsonrpc method name from logLevel to setMinimumLogLevel --- .../org/rascalmpl/vscode/lsp/IBaseLanguageServerExtensions.java | 2 +- rascal-vscode-extension/src/lsp/JsonOutputChannel.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseLanguageServerExtensions.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseLanguageServerExtensions.java index c9790e831..451b557c4 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseLanguageServerExtensions.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseLanguageServerExtensions.java @@ -59,7 +59,7 @@ default CompletableFuture[]> supplyPathConfig(PathConfigParam throw new UnsupportedOperationException(); } - @JsonNotification("logLevel") + @JsonNotification void setMinimumLogLevel(String level); @JsonRequest("vfs/schemes") diff --git a/rascal-vscode-extension/src/lsp/JsonOutputChannel.ts b/rascal-vscode-extension/src/lsp/JsonOutputChannel.ts index 346dba6e4..806ecc5bd 100644 --- a/rascal-vscode-extension/src/lsp/JsonOutputChannel.ts +++ b/rascal-vscode-extension/src/lsp/JsonOutputChannel.ts @@ -114,7 +114,7 @@ export class JsonParserOutputChannel implements vscode.LogOutputChannel { // Do nothing for now. this::setClient will take care of this once the client is available. return; } - this.client.sendNotification("rascal/logLevel", newLevel); + this.client.sendNotification("rascal/setMinimumLogLevel", newLevel); } private printLogOutput(loglevel: LogLevel, message: string) { From 78dc9a86c4941e80fcf7955549eb9b52ffa10e5b Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 17 Apr 2026 09:26:45 +0200 Subject: [PATCH 130/149] Directly setting language client in RascalFileSystemServices from BaseLanguageServer --- .../main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java index eb77b2cf7..f8cdd9668 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java @@ -311,7 +311,7 @@ public void connect(LanguageClient client) { var actualClient = (IBaseLanguageClient) client; lspDocumentService.connect(actualClient); lspWorkspaceService.connect(actualClient); - provideClient(actualClient); + this.client = actualClient; remoteIDEServicesConfiguration = RemoteIDEServicesThread.startRemoteIDEServicesServer(client, lspDocumentService, executor); logger.debug("Remote IDE Services Port {}", remoteIDEServicesConfiguration); } From 1e9e5fcd5639fdfcc3b5dc8ad03f9446848bdf6d Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 17 Apr 2026 09:27:00 +0200 Subject: [PATCH 131/149] Update rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts Co-authored-by: Toine Hartman --- rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts index 46ee4d0c3..bda03aad5 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts @@ -74,7 +74,7 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { } /** - * Attemptes to register all schemes. + * Attempts to register all schemes. * @param schemes The list of schemes to register for this provider */ tryRegisterSchemes(schemes: string[]) { From db09ebcf28e4d9f5a753de18a459bccda706b3c5 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 17 Apr 2026 10:34:52 +0200 Subject: [PATCH 132/149] Renamed specializedRemoteResolverRegistry to customRemoteResolverRegistry --- rascal-lsp/.vscode/launch.json | 4 ++-- rascal-vscode-extension/src/lsp/RascalLSPConnection.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rascal-lsp/.vscode/launch.json b/rascal-lsp/.vscode/launch.json index b1df648ea..efd1b70eb 100644 --- a/rascal-lsp/.vscode/launch.json +++ b/rascal-lsp/.vscode/launch.json @@ -12,7 +12,7 @@ "-Dlog4j2.level=TRACE", "-Drascal.compilerClasspath=${workspaceFolder}/target/lib/rascal.jar", "-Drascal.remoteResolverRegistryPort=8889", - "-Drascal.specializedRemoteResolverRegistryClass=org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeFileSystemInRascal" + "-Drascal.customRemoteResolverRegistryClass=org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeFileSystemInRascal" ] }, { @@ -26,7 +26,7 @@ "-Dlog4j2.level=TRACE", "-Drascal.compilerClasspath=${workspaceFolder}/target/lib/rascal.jar", "-Drascal.remoteResolverRegistryPort=8889", - "-Drascal.specializedRemoteResolverRegistryClass=org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeFileSystemInRascal" + "-Drascal.customRemoteResolverRegistryClass=org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeFileSystemInRascal" ] } ] diff --git a/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts b/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts index 714c56b90..e35ce3b35 100644 --- a/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts +++ b/rascal-vscode-extension/src/lsp/RascalLSPConnection.ts @@ -164,7 +164,7 @@ async function buildRascalServerOptions(jarPath: string, isParametricServer: boo , '-Drascal.lsp.deploy=true' , '-Drascal.compilerClasspath=' + classpath , '-Drascal.remoteResolverRegistryPort=' + remoteResolverRegistryPort - , '-Drascal.specializedRemoteResolverRegistryClass=org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeFileSystemInRascal' + , '-Drascal.customRemoteResolverRegistryClass=org.rascalmpl.vscode.lsp.uri.jsonrpc.VSCodeFileSystemInRascal' ]; let mainClass: string; if (isParametricServer) { From 9a01c31062187d2f8e4a890c8d6eb76c8d6d34c2 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 17 Apr 2026 12:31:57 +0200 Subject: [PATCH 133/149] Using template strings in toRascalLocationString --- rascal-vscode-extension/src/RascalTerminalLinkProvider.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rascal-vscode-extension/src/RascalTerminalLinkProvider.ts b/rascal-vscode-extension/src/RascalTerminalLinkProvider.ts index 1dbab6208..5e1c59819 100644 --- a/rascal-vscode-extension/src/RascalTerminalLinkProvider.ts +++ b/rascal-vscode-extension/src/RascalTerminalLinkProvider.ts @@ -91,12 +91,12 @@ export class RascalTerminalLinkProvider implements TerminalLinkProvider"; - ret += "<" + sloc.endLineColumn![0] + "," + sloc.endLineColumn![1] + ">"; + ret += `,<${sloc.beginLineColumn[0]},${sloc.beginLineColumn[1]}>`; + ret += `,<${sloc.endLineColumn![0]},${sloc.endLineColumn![1]}>`; } ret += ")"; } From d1feff4718de29a2ea7cf96305a9670b02ab51d6 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 17 Apr 2026 12:33:28 +0200 Subject: [PATCH 134/149] Flipped conditions --- .../src/fs/VSCodeFileSystemInRascal.ts | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts index a3849e7b6..ba29215b7 100644 --- a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts +++ b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts @@ -443,29 +443,27 @@ class ResolverClient implements VSCodeResolverServer, Disposable { async watch(newWatch: WatchRequest): Promise { this.logger.trace("[VSCodeFileSystemInRascal] watch: ", newWatch.loc); const watchKey = newWatch.loc + newWatch.recursive; - if (!this.activeWatches.has(watchKey)) { - const watcher = new WatcherCallbacks(this.toUri(newWatch.loc), newWatch.recursive, this.watchListener, newWatch.watchId); - this.activeWatches.set(watchKey, watcher); - this.disposables.push(watcher); - return; + if (this.activeWatches.has(watchKey)) { + throw new rpc.ResponseError(RemoteIOError.watchAlreadyDefined, `Watch already defined: ${newWatch.loc}`); } - throw new rpc.ResponseError(RemoteIOError.watchAlreadyDefined, `Watch already defined: ${newWatch.loc}`); + const watcher = new WatcherCallbacks(this.toUri(newWatch.loc), newWatch.recursive, this.watchListener, newWatch.watchId); + this.activeWatches.set(watchKey, watcher); + this.disposables.push(watcher); } async unwatch(removeWatch: WatchRequest): Promise { this.logger.trace("[VSCodeFileSystemInRascal] unwatch: ", removeWatch.loc); const watchKey = removeWatch.loc + removeWatch.recursive; const watcher = this.activeWatches.get(watchKey); - if (watcher) { - this.activeWatches.delete(watchKey); - watcher.dispose(); - const index = this.disposables.indexOf(watcher); - if (index >= 0) { - this.disposables.splice(index, 1); - } - return; + if (watcher === undefined) { + throw new rpc.ResponseError(RemoteIOError.watchNotDefined, `Watch not defined: ${removeWatch.loc}`); + } + this.activeWatches.delete(watchKey); + watcher.dispose(); + const index = this.disposables.indexOf(watcher); + if (index >= 0) { + this.disposables.splice(index, 1); } - throw new rpc.ResponseError(RemoteIOError.watchNotDefined, `Watch not defined: ${removeWatch.loc}`); } async resolve(req: ISourceLocationRequest): Promise { From 23f5c53fd261bb42d7c8e22abde8ed51cd8d9966 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 17 Apr 2026 12:37:13 +0200 Subject: [PATCH 135/149] Fixed condition in copy --- rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts index ba29215b7..9b17c37a2 100644 --- a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts +++ b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts @@ -432,7 +432,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { async copy(req: CopyRequest): Promise { this.logger.trace("[VSCodeFileSystemInRascal] copy: ", req.from, req.to); - if (req.recursive && await this.isDirectory({ loc: req.from })) { + if (!req.recursive && await this.isDirectory({ loc: req.from })) { throw new rpc.ResponseError(RemoteIOError.isADirectory, 'Non-recursive copy requested on a directory', req); } return this.asyncVoidCatcher(this.fs.copy(this.toUri(req.from), this.toUri(req.to), { overwrite: req.overwrite })); From 1472ebe97615b67e1330ddb60cf9d40111561536 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 17 Apr 2026 12:38:36 +0200 Subject: [PATCH 136/149] Flipped disjunction to prevent spurious stats --- rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts index 9b17c37a2..1d7c5cc76 100644 --- a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts +++ b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts @@ -408,7 +408,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { this.logger.trace("[VSCodeFileSystemInRascal] writeFile: ", req.loc); const loc = this.toUri(req.loc); let prefix: Buffer = Buffer.of(); - if (await this.exists(req) && req.append) { + if (req.append && await this.exists(req)) { prefix = Buffer.from(await this.fs.readFile(loc)); } return this.asyncVoidCatcher( From 6bcb63b74efe82fb58f46a677e386343738bbee5 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 17 Apr 2026 12:44:37 +0200 Subject: [PATCH 137/149] Fixed calculation of writable in remote stat. Improved calculation of isFile as well --- rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts index 1d7c5cc76..22c6c4bd8 100644 --- a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts +++ b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts @@ -323,12 +323,11 @@ class ResolverClient implements VSCodeResolverServer, Disposable { const fileInfo = await this.stat(req.loc); return { exists: true, - isFile: (fileInfo.type | vscode.FileType.File) > 0, + isFile: (fileInfo.type | vscode.FileType.File) === vscode.FileType.File, created: fileInfo.ctime, lastModified: fileInfo.mtime, isReadable: true, - isWritable: true, - permissions: fileInfo.permissions && (fileInfo.permissions | vscode.FilePermission.Readonly) > 0, + isWritable: !fileInfo.permissions || (fileInfo.permissions | vscode.FilePermission.Readonly) === 0, size: fileInfo.size, }; }); From f1036b535fdb03c6fe7ca8b1ff26265358955d6e Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 17 Apr 2026 12:47:39 +0200 Subject: [PATCH 138/149] Improved error message --- rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts index 22c6c4bd8..0826fb10b 100644 --- a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts +++ b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts @@ -289,7 +289,7 @@ class ResolverClient implements VSCodeResolverServer, Disposable { toUri(loc: ISourceLocation): vscode.Uri { const uri = vscode.Uri.parse(loc); if (this.isRascalNative(uri)) { - throw new rpc.ResponseError(RemoteIOError.isRascalNative, `Cannot request VS jobs on native Rascal locations: ${loc}`); + throw new rpc.ResponseError(RemoteIOError.isRascalNative, `Cannot perform VS Code file system operations on native Rascal locations: ${loc}`); } return uri; } From 939e60c2af81e7688006130a2da533bd84e94f60 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 17 Apr 2026 12:51:38 +0200 Subject: [PATCH 139/149] Improved local variable definition --- rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts index 0826fb10b..dc6babd9b 100644 --- a/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts +++ b/rascal-vscode-extension/src/fs/VSCodeFileSystemInRascal.ts @@ -243,15 +243,11 @@ export class VSCodeFileSystemInRascal extends JsonRpcServer { } class ResolverClient implements VSCodeResolverServer, Disposable { - private readonly connection: rpc.MessageConnection; private readonly watchListener: WatchEventReceiver; private readonly fs: vscode.FileSystem; - private readonly rascalNativeSchemes: Set; private disposables: Disposable[] = []; - constructor(connection: rpc.MessageConnection, debug: boolean, rascalNativeSchemes: Set, private readonly logger: vscode.LogOutputChannel){ - this.rascalNativeSchemes = rascalNativeSchemes; + constructor(private readonly connection: rpc.MessageConnection, debug: boolean, private readonly rascalNativeSchemes: Set, private readonly logger: vscode.LogOutputChannel){ this.fs = vscode.workspace.fs; - this.connection = connection; if (debug) { connection.trace(rpc.Trace.Verbose, { log: (a) => { From f3bdf177de91e373dd0419bb25470f9e139bab6f Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 17 Apr 2026 12:51:51 +0200 Subject: [PATCH 140/149] Added comment to RemoteIOError.ts --- rascal-vscode-extension/src/fs/RemoteIOError.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rascal-vscode-extension/src/fs/RemoteIOError.ts b/rascal-vscode-extension/src/fs/RemoteIOError.ts index 61e3813b7..bc7915398 100644 --- a/rascal-vscode-extension/src/fs/RemoteIOError.ts +++ b/rascal-vscode-extension/src/fs/RemoteIOError.ts @@ -28,6 +28,10 @@ import * as vscode from 'vscode'; import * as rpc from 'vscode-jsonrpc/node'; import { ResponseError } from "vscode-languageclient"; +/** + * This class mirrors the `RemoteIOError` enum in Rascal. + * The values must be kept in sync. + */ export class RemoteIOError { static readonly fileExists = -1; static readonly fileNotFound = -2; From 9b644ae0118969f68847d9f4dd68b44bf60d4221 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 17 Apr 2026 12:54:06 +0200 Subject: [PATCH 141/149] Simplified expression to calculate overwrite --- rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts index bda03aad5..80e8764fe 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts @@ -184,7 +184,7 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { copy(source: vscode.Uri, target: vscode.Uri, options?: { overwrite?: boolean; }): Thenable { this.logger.trace("[RascalFileSystemInVSCode] copy: ", source, target); - return this.sendRequest(source, "rascal/vfs/output/copy", { from: this.toRascalUri(source), to: this.toRascalUri(target), recursive: true, overwrite: (options && options.overwrite) ?? false }); + return this.sendRequest(source, "rascal/vfs/output/copy", { from: this.toRascalUri(source), to: this.toRascalUri(target), recursive: true, overwrite: options?.overwrite ?? false }); } } From 77c432104dc48a1d1d8bb612e894cf7a23329f5b Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 17 Apr 2026 13:01:50 +0200 Subject: [PATCH 142/149] Improved local variable definition --- rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts index 80e8764fe..6090458fd 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts @@ -31,7 +31,6 @@ import { CopyRequest, DirectoryListingResponse, FileAttributes, ISourceLocationR import { RemoteIOError } from './RemoteIOError'; export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { - readonly client: BaseLanguageClient; private readonly _emitter = new vscode.EventEmitter(); readonly onDidChangeFile: vscode.Event = this._emitter.event; private readonly protectedSchemes: string[] = ["file", "http", "https", "unknown"]; @@ -42,9 +41,7 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { * * @param client to use as a server for the file system provider methods */ - constructor (client: BaseLanguageClient, private readonly logger: vscode.LogOutputChannel) { - this.client = client; - + constructor (private readonly client: BaseLanguageClient, private readonly logger: vscode.LogOutputChannel) { client.onNotification("rascal/vfs/watcher/fileChanged", (event: vscode.FileChangeEvent) => { this._emitter.fire([event]); }); From 09db956ea21f1d0b9be1eb867bc030729221100a Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 17 Apr 2026 14:44:10 +0200 Subject: [PATCH 143/149] sendRequest is now async --- .../src/fs/RascalFileSystemInVSCode.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts index 6090458fd..56168ce0c 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts @@ -61,13 +61,14 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { return uriString; } - sendRequest(uri: vscode.Uri | string, method: string): Promise; - sendRequest(uri: vscode.Uri | string, method: string, param: A): Promise; - sendRequest(uri: vscode.Uri | string, method: string, param?: A): Promise { - return this.client.sendRequest(method, param ?? { loc: this.toRascalUri(uri) } ) - .catch((r: ResponseError) => { - throw RemoteIOError.translateResponseError(r, uri, this.logger); - }); + async sendRequest(uri: vscode.Uri | string, method: string): Promise; + async sendRequest(uri: vscode.Uri | string, method: string, param: A): Promise; + async sendRequest(uri: vscode.Uri | string, method: string, param?: A): Promise { + try { + return await this.client.sendRequest(method, param ?? { loc: this.toRascalUri(uri) }); + } catch (r) { + throw RemoteIOError.translateResponseError(r, uri, this.logger); + } } /** From 426472a0fc0ed5a71d2924046fae424b1bf836b7 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 17 Apr 2026 14:44:41 +0200 Subject: [PATCH 144/149] Using source location transformation of JsonRpcRequest classes to avoid code duplication --- .../uri/jsonrpc/RascalFileSystemInVSCode.java | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java index 13de77144..ee7b8e0ef 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/jsonrpc/RascalFileSystemInVSCode.java @@ -39,6 +39,7 @@ import org.rascalmpl.uri.remote.jsonrpc.CopyRequest; import org.rascalmpl.uri.remote.jsonrpc.DirectoryListingResponse; import org.rascalmpl.uri.remote.jsonrpc.ISourceLocationRequest; +import org.rascalmpl.uri.remote.jsonrpc.JsonRpcRequest; import org.rascalmpl.uri.remote.jsonrpc.LocationContentResponse; import org.rascalmpl.uri.remote.jsonrpc.RemoteIOError; import org.rascalmpl.uri.remote.jsonrpc.RemoveRequest; @@ -50,16 +51,20 @@ /** * Wrapper for RascalFileSystemServices handling LSP-specifics. - * In particular, locations from LSP are mapped to Rascal-friendly locations, and Rascal exceptions are mapped to exceptions LSP expects. + * In particular, locations from LSP are mapped to Rascal-friendly locations. */ public class RascalFileSystemInVSCode extends RascalFileSystemServices { private static final Logger logger = LogManager.getLogger(RascalFileSystemServices.class); private static final URIResolverRegistry reg = URIResolverRegistry.getInstance(); + + private T transformLocations(T req) { + return req.transformLocations(Locations::toClientLocation); + } @Override public CompletableFuture resolveLocation(ISourceLocationRequest req) { logger.trace("resolveLocation: {}", req.getLocation()); - return super.resolveLocation(new ISourceLocationRequest(Locations.toClientLocation(req.getLocation()))); + return super.resolveLocation(transformLocations(req)); } @Override @@ -69,34 +74,34 @@ public CompletableFuture watch(WatchRequest req) { if (Locations.isWrappedOpaque(loc)) { throw RemoteIOError.translate(new UnsupportedSchemeException("Opaque locations are not supported by Rascal: " + loc.getScheme())); } - return super.watch(req); + return super.watch(transformLocations(req)); } @Override public CompletableFuture stat(ISourceLocationRequest req) { var loc = req.getLocation(); logger.trace("stat: {}", loc); - return super.stat(new ISourceLocationRequest(Locations.toClientLocation(loc))); + return super.stat(transformLocations(req)); } @Override public CompletableFuture list(ISourceLocationRequest req) { logger.trace("list: {}", req.getLocation()); - return super.list(new ISourceLocationRequest(Locations.toClientLocation(req.getLocation()))); + return super.list(transformLocations(req)); } @Override public CompletableFuture mkDirectory(ISourceLocationRequest req) { var loc = req.getLocation(); logger.trace("mkDirectory: {}", loc); - return super.mkDirectory(new ISourceLocationRequest(Locations.toClientLocation(loc))); + return super.mkDirectory(transformLocations(req)); } @Override public CompletableFuture readFile(ISourceLocationRequest req) { var loc = req.getLocation(); logger.trace("readFile: {}", loc); - return super.readFile(new ISourceLocationRequest(Locations.toClientLocation(loc))); + return super.readFile(transformLocations(req)); } @Override @@ -106,25 +111,25 @@ public CompletableFuture writeFile(WriteFileRequest req) { if (reg.exists(loc) && reg.isDirectory(loc)) { throw new ResponseErrorException(new ResponseError(RemoteIOError.IsADirectory.code, "Is a directory: " + loc, req)); } - return super.writeFile(new WriteFileRequest(Locations.toClientLocation(loc), req.getContent(), req.isAppend())); + return super.writeFile(transformLocations(req)); } @Override public CompletableFuture remove(RemoveRequest req) { var loc = req.getLocation(); logger.trace("remove: {}", loc); - return super.remove(new RemoveRequest(Locations.toClientLocation(loc), req.isRecursive())); + return super.remove(transformLocations(req)); } @Override public CompletableFuture rename(RenameRequest req) { logger.trace("rename: {} to {}", req.getFrom(), req.getTo()); - return super.rename(new RenameRequest(Locations.toClientLocation(req.getFrom()), Locations.toClientLocation(req.getTo()), req.isOverwrite())); + return super.rename(transformLocations(req)); } @Override public CompletableFuture copy(CopyRequest req) { logger.trace("copy: {} to {}", req.getFrom(), req.getTo()); - return super.copy(new CopyRequest(Locations.toClientLocation(req.getFrom()), Locations.toClientLocation(req.getTo()), req.isRecursive(), req.isOverwrite())); + return super.copy(transformLocations(req)); } } From 2dedac2712675a055b7535f3389260eeaad57ed2 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 17 Apr 2026 15:01:50 +0200 Subject: [PATCH 145/149] Documented LSPOpenFileRedirector --- .../org/rascalmpl/vscode/lsp/uri/LSPOpenFileRedirector.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/LSPOpenFileRedirector.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/LSPOpenFileRedirector.java index b255e22d5..b0059c6d8 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/LSPOpenFileRedirector.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/LSPOpenFileRedirector.java @@ -39,6 +39,10 @@ import io.usethesource.vallang.ISourceLocation; +/** + * According to the LSP specification, for files that are opened in the IDE, all IO operations must be carried out over LSP. + * In particular, a "read" should yield the contents in the IDE editor, not the state on disk. + */ public class LSPOpenFileRedirector { private static @MonotonicNonNull LSPOpenFileRedirector instance = null; From 83904b1c592eb6c8f503c3ca3fc78b21fce4e5ad Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 17 Apr 2026 15:03:24 +0200 Subject: [PATCH 146/149] Running completed future on executor --- .../main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java index f8cdd9668..64c292190 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/BaseLanguageServer.java @@ -333,7 +333,7 @@ public CompletableFuture fileSystemSchemes() { var inputs = reg.getRegisteredInputSchemes(); var logicals = reg.getRegisteredLogicalSchemes(); - return CompletableFuture.completedFuture(Sets.union(inputs, logicals).toArray(String[]::new)); + return CompletableFutureUtils.completedFuture(Sets.union(inputs, logicals).toArray(String[]::new), executor); } } } From 83e0bd8efbf458cf5450fc335a06a2080f0c833f Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 17 Apr 2026 15:06:13 +0200 Subject: [PATCH 147/149] Expanded comment --- rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts index 56168ce0c..545405fe0 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts @@ -144,7 +144,8 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { } writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean; }): void | Thenable { - // The `create` and `overwrite` options are handled on this side + // The `create` and `overwrite` options are handled on this side, as this function should comply with the requirements + // as specified by vscode.FileSystemProvider.writeFile; ISourceLocationOutput does not support `create` and `overwrite`. this.logger.trace("[RascalFileSystemInVSCode] writeFile: ", uri); const parentUri = uri.with({ path: path.dirname(uri.path) }); Promise.all([ From 49cb1b9d09165c6d5f5cdef5605bf6178f92ad39 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 17 Apr 2026 15:28:51 +0200 Subject: [PATCH 148/149] Moved function inside class --- .../src/fs/RascalFileSystemInVSCode.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts index 545405fe0..b793d20a2 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts @@ -71,6 +71,10 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { } } + private isUnknownFileSystem(scheme: string): boolean { + return vscode.workspace.fs.isWritableFileSystem(scheme) === undefined; + } + /** * Attempts to register all schemes. * @param schemes The list of schemes to register for this provider @@ -82,16 +86,15 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { .concat(schemes .filter(s => s !== "jar" && s !== "zip" && s !== "compressed") .map(s => "jar+" + s)) - .filter(isUnknownFileSystem) + .filter(this.isUnknownFileSystem) .forEach(s => { try { vscode.workspace.registerFileSystemProvider(s, this); this.logger.debug(`Rascal VFS registered scheme: ${s}`); } catch (error) { - if (isUnknownFileSystem(s)) { + if (this.isUnknownFileSystem(s)) { this.logger.error(`Unable to register scheme: ${s}\n${error}`); - } - else { + } else { this.logger.debug(`Rascal VFS lost the race to register scheme: ${s}, which in most cases is fine`); } } @@ -186,7 +189,3 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { return this.sendRequest(source, "rascal/vfs/output/copy", { from: this.toRascalUri(source), to: this.toRascalUri(target), recursive: true, overwrite: options?.overwrite ?? false }); } } - -function isUnknownFileSystem(scheme: string): boolean { - return vscode.workspace.fs.isWritableFileSystem(scheme) === undefined; -} From 596f079edb8f6c8114dd3dd1d9b8fa1edf1bf680 Mon Sep 17 00:00:00 2001 From: Rodin Aarssen Date: Fri, 17 Apr 2026 15:29:18 +0200 Subject: [PATCH 149/149] Made functions in RascalFileSystemInVsCode async --- .../src/fs/RascalFileSystemInVSCode.ts | 75 +++++++++---------- 1 file changed, 35 insertions(+), 40 deletions(-) diff --git a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts index b793d20a2..0f932552b 100644 --- a/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts +++ b/rascal-vscode-extension/src/fs/RascalFileSystemInVSCode.ts @@ -116,75 +116,70 @@ export class RascalFileSystemInVSCode implements vscode.FileSystemProvider { }); } - stat(uri: vscode.Uri): vscode.FileStat | Thenable { + async stat(uri: vscode.Uri): Promise { this.logger.trace("[RascalFileSystemInVSCode] stat: ", uri); - return this.sendRequest(uri, "rascal/vfs/input/stat").then(a => - { - type: a.isFile ? vscode.FileType.File : vscode.FileType.Directory, - ctime: a.created, - mtime: a.lastModified, - size: a.size, - permissions: a.isWritable ? undefined : vscode.FilePermission.Readonly - } - ); + const stat = await this.sendRequest(uri, "rascal/vfs/input/stat"); + return { + type: stat.isFile ? vscode.FileType.File : vscode.FileType.Directory, + ctime: stat.created, + mtime: stat.lastModified, + size: stat.size, + permissions: stat.isWritable ? undefined : vscode.FilePermission.Readonly + }; } - readDirectory(uri: vscode.Uri): [string, vscode.FileType][] | Thenable<[string, vscode.FileType][]> { + async readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> { this.logger.trace("[RascalFileSystemInVSCode] readDirectory: ", uri); - return this.sendRequest(uri, "rascal/vfs/input/list") - .then(c => c.entries.map(ft => [ft.name, ft.types.reduce((a, i) => a | i)])); + return (await this.sendRequest(uri, "rascal/vfs/input/list")) + .entries.map(ft => [ft.name, ft.types.reduce((a, i) => a | i)]); } - createDirectory(uri: vscode.Uri): void | Thenable { + async createDirectory(uri: vscode.Uri): Promise { this.logger.trace("[RascalFileSystemInVSCode] createDirectory: ", uri); return this.sendRequest(uri, "rascal/vfs/output/mkDirectory"); } - readFile(uri: vscode.Uri): Uint8Array | Thenable { + async readFile(uri: vscode.Uri): Promise> { this.logger.trace("[RascalFileSystemInVSCode] readFile: ", uri); - return this.sendRequest(uri, "rascal/vfs/input/readFile") - .then(r => Buffer.from(r.content, "base64")); + return Buffer.from((await this.sendRequest(uri, "rascal/vfs/input/readFile")).content, "base64"); } - writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean; }): void | Thenable { + async writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean; }): Promise { // The `create` and `overwrite` options are handled on this side, as this function should comply with the requirements // as specified by vscode.FileSystemProvider.writeFile; ISourceLocationOutput does not support `create` and `overwrite`. this.logger.trace("[RascalFileSystemInVSCode] writeFile: ", uri); const parentUri = uri.with({ path: path.dirname(uri.path) }); - Promise.all([ + const [fileStat, parentStat] = await Promise.all([ this.sendRequest(uri, "rascal/vfs/input/stat"), - this.sendRequest(parentUri, "rascal/vfs/input/stat")] - ).then(r => { - const fileStat = r[0]; - const parentStat = r[1]; - if (!fileStat.exists && !options.create) { - throw vscode.FileSystemError.FileNotFound(`File ${uri} does not exist and \`create\` was not set`); - } - if (!parentStat.exists && options.create) { - throw vscode.FileSystemError.FileNotFound(`Parent of ${uri} does not exist but \`create\` was set`); - } - if (fileStat.exists && options.create && !options.overwrite) { - throw vscode.FileSystemError.FileExists(`File ${uri} exists and \`create\` was set, but \`override\` was not set`); - } - return this.sendRequest(uri, "rascal/vfs/output/writeFile", { - loc: this.toRascalUri(uri), - content: Buffer.from(content).toString("base64"), - append: false - }); + this.sendRequest(parentUri, "rascal/vfs/input/stat") + ]); + if (!fileStat.exists && !options.create) { + throw vscode.FileSystemError.FileNotFound(`File ${uri} does not exist and \`create\` was not set`); + } + if (!parentStat.exists && options.create) { + throw vscode.FileSystemError.FileNotFound(`Parent of ${uri} does not exist but \`create\` was set`); + } + if (fileStat.exists && options.create && !options.overwrite) { + throw vscode.FileSystemError.FileExists(`File ${uri} exists and \`create\` was set, but \`override\` was not set`); + } + return this.sendRequest(uri, "rascal/vfs/output/writeFile", { + loc: this.toRascalUri(uri), + content: Buffer.from(content).toString("base64"), + append: false }); } - delete(uri: vscode.Uri, options: { recursive: boolean; }): void | Thenable { + async delete(uri: vscode.Uri, options: { recursive: boolean; }): Promise { this.logger.trace("[RascalFileSystemInVSCode] delete: ", uri); return this.sendRequest(uri, "rascal/vfs/output/remove", { loc: this.toRascalUri(uri), recursive: options.recursive }); } - rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean; }): void | Thenable { + async rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean; }): Promise { this.logger.trace("[RascalFileSystemInVSCode] rename: ", oldUri, newUri); return this.sendRequest(oldUri, "rascal/vfs/output/rename", { from: this.toRascalUri(oldUri), to: this.toRascalUri(newUri), overwrite: options.overwrite }); } - copy(source: vscode.Uri, target: vscode.Uri, options?: { overwrite?: boolean; }): Thenable { + async copy(source: vscode.Uri, target: vscode.Uri, options?: { overwrite?: boolean; }): Promise { this.logger.trace("[RascalFileSystemInVSCode] copy: ", source, target); return this.sendRequest(source, "rascal/vfs/output/copy", { from: this.toRascalUri(source), to: this.toRascalUri(target), recursive: true, overwrite: options?.overwrite ?? false }); }