diff --git a/rascal-lsp/.vscode/launch.json b/rascal-lsp/.vscode/launch.json
index 361401f1e..9dde3567f 100644
--- a/rascal-lsp/.vscode/launch.json
+++ b/rascal-lsp/.vscode/launch.json
@@ -10,7 +10,8 @@
"console": "internalConsole",
"vmArgs": [
"-Dlog4j2.level=TRACE",
- "-Drascal.compilerClasspath=${workspaceFolder}/target/lib/rascal.jar"
+ "-Drascal.compilerClasspath=${workspaceFolder}/target/lib/rascal.jar",
+ "-Drascal.fallbackResolver=org.rascalmpl.vscode.lsp.uri.FallbackResolver"
]
},
{
@@ -22,7 +23,8 @@
"console": "internalConsole",
"vmArgs": [
"-Dlog4j2.level=TRACE",
- "-Drascal.compilerClasspath=${workspaceFolder}/target/lib/rascal.jar"
+ "-Drascal.compilerClasspath=${workspaceFolder}/target/lib/rascal.jar",
+ "-Drascal.fallbackResolver=org.rascalmpl.vscode.lsp.uri.FallbackResolver"
]
}
]
diff --git a/rascal-lsp/pom.xml b/rascal-lsp/pom.xml
index 857255f18..a96edd7d0 100644
--- a/rascal-lsp/pom.xml
+++ b/rascal-lsp/pom.xml
@@ -65,7 +65,7 @@
org.rascalmpl
rascal
- 0.40.17
+ 0.41.0-RC7
org.rascalmpl
diff --git a/rascal-lsp/src/main/java/org/rascalmpl/uri/resolvers.config b/rascal-lsp/src/main/java/org/rascalmpl/uri/resolvers.config
new file mode 100644
index 000000000..a7f9a3c3f
--- /dev/null
+++ b/rascal-lsp/src/main/java/org/rascalmpl/uri/resolvers.config
@@ -0,0 +1 @@
+org.rascalmpl.vscode.lsp.uri.LSPOpenFileResolver
diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseTextDocumentService.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseTextDocumentService.java
index 443e28924..6f2d6fc55 100644
--- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseTextDocumentService.java
+++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/IBaseTextDocumentService.java
@@ -48,6 +48,9 @@ public interface IBaseTextDocumentService extends TextDocumentService {
void unregisterLanguage(LanguageParameter lang);
CompletableFuture executeCommand(String languageName, String command);
LineColumnOffsetMap getColumnMap(ISourceLocation file);
+ TextDocumentState getDocumentState(ISourceLocation file);
+
+ boolean isManagingFile(ISourceLocation file);
default void didRenameFiles(RenameFilesParams params, Set workspaceFolders) {}
}
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 084a4425e..5e0c73273 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
@@ -51,6 +51,7 @@
import org.rascalmpl.uri.URIResolverRegistry;
import org.rascalmpl.uri.URIUtil;
import org.rascalmpl.values.IRascalValueFactory;
+import org.rascalmpl.vscode.lsp.util.locations.Locations;
import io.usethesource.vallang.ISourceLocation;
import io.usethesource.vallang.IValueFactory;
@@ -65,7 +66,7 @@ default CompletableFuture resolveLocation(SourceLocation loc) {
try {
ISourceLocation tmp = loc.toRascalLocation();
- ISourceLocation resolved = reg.logicalToPhysical(tmp);
+ ISourceLocation resolved = Locations.toClientLocation(tmp);
if (resolved == null) {
return loc;
@@ -77,10 +78,6 @@ default CompletableFuture resolveLocation(SourceLocation loc) {
IRascalFileSystemServices__logger.warn("Could not resolve location {} due to {}.", loc, e.getMessage());
return loc;
}
- catch (IOException e) {
- // This is normal behavior (when its not a logical scheme)
- return loc;
- }
catch (Throwable e) {
IRascalFileSystemServices__logger.warn("Could not resolve location {} due to {}.", loc, e.getMessage());
return loc;
@@ -130,7 +127,7 @@ private static boolean readonly(ISourceLocation loc) throws IOException {
return false;
}
if (reg.getRegisteredLogicalSchemes().contains(loc.getScheme())) {
- var resolved = reg.logicalToPhysical(loc);
+ var resolved = Locations.toClientLocation(loc);
if (resolved != null && resolved != loc) {
return readonly(resolved);
}
diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/LSPIDEServices.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/LSPIDEServices.java
index 8d1a2dac1..1d446d6a5 100644
--- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/LSPIDEServices.java
+++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/LSPIDEServices.java
@@ -92,7 +92,7 @@ public void browse(URI uri, String title, int viewColumn) {
@Override
public void edit(ISourceLocation path) {
try {
- ISourceLocation physical = URIResolverRegistry.getInstance().logicalToPhysical(path);
+ ISourceLocation physical = Locations.toClientLocation(path);
ShowDocumentParams params = new ShowDocumentParams(physical.getURI().toASCIIString());
params.setTakeFocus(true);
diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/TextDocumentState.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/TextDocumentState.java
index 73690847b..e143b4d27 100644
--- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/TextDocumentState.java
+++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/TextDocumentState.java
@@ -55,10 +55,10 @@ public class TextDocumentState {
@SuppressWarnings("java:S3077") // we are use volatile correctly
private volatile CompletableFuture> currentTree;
- public TextDocumentState(BiFunction> parser, ISourceLocation file, int initialVersion, String initialContent) {
+ public TextDocumentState(BiFunction> parser, ISourceLocation file, int initialVersion, String initialContent, long timestamp) {
this.parser = parser;
this.file = file;
- this.currentContent = new Versioned<>(initialVersion, initialContent);
+ this.currentContent = new Versioned<>(initialVersion, initialContent, timestamp);
this.currentTree = newTreeAsync(initialVersion, initialContent);
}
@@ -72,8 +72,8 @@ public TextDocumentState(BiFunction pair.
*/
- public CompletableFuture> update(int version, String content) {
- currentContent = new Versioned<>(version, content);
+ public CompletableFuture> update(int version, String content, long timestamp) {
+ currentContent = new Versioned<>(version, content, timestamp);
var newTree = newTreeAsync(version, content);
currentTree = newTree;
return newTree;
@@ -105,4 +105,8 @@ public ISourceLocation getLocation() {
public Versioned getCurrentContent() {
return currentContent;
}
+
+ public long getLastModified() {
+ return currentContent.getTimestamp();
+ }
}
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 7c7bbb648..31dec798c 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
@@ -106,6 +106,7 @@
import org.rascalmpl.vscode.lsp.parametric.model.ParametricSummary;
import org.rascalmpl.vscode.lsp.parametric.model.ParametricSummary.SummaryLookup;
import org.rascalmpl.vscode.lsp.terminal.ITerminalIDEServer.LanguageParameter;
+import org.rascalmpl.vscode.lsp.uri.FallbackResolver;
import org.rascalmpl.vscode.lsp.util.CodeActions;
import org.rascalmpl.vscode.lsp.util.Diagnostics;
import org.rascalmpl.vscode.lsp.util.FoldingRanges;
@@ -149,6 +150,9 @@ public class ParametricTextDocumentService implements IBaseTextDocumentService,
private final @Nullable LanguageParameter dedicatedLanguage;
public ParametricTextDocumentService(ExecutorService exec, @Nullable LanguageParameter dedicatedLanguage) {
+ // The following call ensures that URIResolverRegistry is initialized before FallbackResolver is accessed
+ URIResolverRegistry.getInstance();
+
this.ownExecuter = exec;
this.files = new ConcurrentHashMap<>();
this.columns = new ColumnMaps(this::getContents);
@@ -160,6 +164,7 @@ public ParametricTextDocumentService(ExecutorService exec, @Nullable LanguagePar
this.dedicatedLanguageName = dedicatedLanguage.getName();
this.dedicatedLanguage = dedicatedLanguage;
}
+ FallbackResolver.getInstance().registerTextDocumentService(this);
}
@Override
@@ -226,15 +231,17 @@ public void connect(LanguageClient client) {
@Override
public void didOpen(DidOpenTextDocumentParams params) {
+ var timestamp = System.currentTimeMillis();
logger.debug("Did Open file: {}", params.getTextDocument());
- handleParsingErrors(open(params.getTextDocument()));
+ handleParsingErrors(open(params.getTextDocument(), timestamp));
triggerAnalyzer(params.getTextDocument(), Duration.ofMillis(800));
}
@Override
public void didChange(DidChangeTextDocumentParams params) {
+ var timestamp = System.currentTimeMillis();
logger.debug("Did Change file: {}", params.getTextDocument().getUri());
- updateContents(params.getTextDocument(), last(params.getContentChanges()).getText());
+ updateContents(params.getTextDocument(), last(params.getContentChanges()).getText(), timestamp);
triggerAnalyzer(params.getTextDocument(), Duration.ofMillis(800));
}
@@ -275,10 +282,10 @@ private void triggerBuilder(TextDocumentIdentifier doc) {
fileFacts.calculateBuilder(location, getFile(doc).getCurrentTreeAsync());
}
- private TextDocumentState updateContents(VersionedTextDocumentIdentifier doc, String newContents) {
+ private TextDocumentState updateContents(VersionedTextDocumentIdentifier doc, String newContents, long timestamp) {
TextDocumentState file = getFile(doc);
logger.trace("New contents for {}", doc);
- handleParsingErrors(file, file.update(doc.getVersion(), newContents));
+ handleParsingErrors(file, file.update(doc.getVersion(), newContents, timestamp));
return file;
}
@@ -439,9 +446,9 @@ private ParametricFileFacts facts(String doc) {
throw new UnsupportedOperationException("Rascal Parametric LSP has no support for this file: " + doc);
}
- private TextDocumentState open(TextDocumentItem doc) {
+ private TextDocumentState open(TextDocumentItem doc, long timestamp) {
return files.computeIfAbsent(Locations.toLoc(doc),
- l -> new TextDocumentState(contributions(doc)::parsing, l, doc.getVersion(), doc.getText())
+ l -> new TextDocumentState(contributions(doc)::parsing, l, doc.getVersion(), doc.getText(), timestamp)
);
}
@@ -683,4 +690,14 @@ public CompletableFuture executeCommand(String languageName, String comm
return CompletableFuture.completedFuture(null);
}
}
+
+ @Override
+ public boolean isManagingFile(ISourceLocation file) {
+ return files.containsKey(file.top());
+ }
+
+ @Override
+ public TextDocumentState getDocumentState(ISourceLocation file) {
+ return files.get(file.top());
+ }
}
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 ea0d7e413..5daa0ae5e 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
@@ -102,6 +102,7 @@
import org.rascalmpl.library.util.PathConfig;
import org.rascalmpl.parser.gtd.exception.ParseError;
import org.rascalmpl.uri.URIResolverRegistry;
+import org.rascalmpl.uri.URIUtil;
import org.rascalmpl.values.parsetrees.ITree;
import org.rascalmpl.values.parsetrees.ProductionAdapter;
import org.rascalmpl.values.parsetrees.TreeAdapter;
@@ -113,6 +114,7 @@
import org.rascalmpl.vscode.lsp.rascal.model.FileFacts;
import org.rascalmpl.vscode.lsp.rascal.model.SummaryBridge;
import org.rascalmpl.vscode.lsp.terminal.ITerminalIDEServer.LanguageParameter;
+import org.rascalmpl.vscode.lsp.uri.FallbackResolver;
import org.rascalmpl.vscode.lsp.util.CodeActions;
import org.rascalmpl.vscode.lsp.util.Diagnostics;
import org.rascalmpl.vscode.lsp.util.DocumentChanges;
@@ -143,9 +145,13 @@ public class RascalTextDocumentService implements IBaseTextDocumentService, Lang
private @MonotonicNonNull BaseWorkspaceService workspaceService;
public RascalTextDocumentService(ExecutorService exec) {
+ // The following call ensures that URIResolverRegistry is initialized before FallbackResolver is accessed
+ URIResolverRegistry.getInstance();
+
this.ownExecuter = exec;
this.documents = new ConcurrentHashMap<>();
this.columns = new ColumnMaps(this::getContents);
+ FallbackResolver.getInstance().registerTextDocumentService(this);
}
@Override
@@ -200,15 +206,17 @@ public void connect(LanguageClient client) {
@Override
public void didOpen(DidOpenTextDocumentParams params) {
+ var timestamp = System.currentTimeMillis();
logger.debug("Open: {}", params.getTextDocument());
- TextDocumentState file = open(params.getTextDocument());
+ TextDocumentState file = open(params.getTextDocument(), timestamp);
handleParsingErrors(file);
}
@Override
public void didChange(DidChangeTextDocumentParams params) {
+ var timestamp = System.currentTimeMillis();
logger.trace("Change: {}", params.getTextDocument());
- updateContents(params.getTextDocument(), last(params.getContentChanges()).getText());
+ updateContents(params.getTextDocument(), last(params.getContentChanges()).getText(), timestamp);
}
@Override
@@ -230,10 +238,10 @@ public void didSave(DidSaveTextDocumentParams params) {
}
}
- private TextDocumentState updateContents(VersionedTextDocumentIdentifier doc, String newContents) {
+ private TextDocumentState updateContents(VersionedTextDocumentIdentifier doc, String newContents, long timestamp) {
TextDocumentState file = getFile(doc);
logger.trace("New contents for {}", doc);
- handleParsingErrors(file, file.update(doc.getVersion(), newContents));
+ handleParsingErrors(file, file.update(doc.getVersion(), newContents, timestamp));
return file;
}
@@ -419,9 +427,9 @@ private static T last(List l) {
return l.get(l.size() - 1);
}
- private TextDocumentState open(TextDocumentItem doc) {
+ private TextDocumentState open(TextDocumentItem doc, long timestamp) {
return documents.computeIfAbsent(Locations.toLoc(doc),
- l -> new TextDocumentState((loc, input) -> rascalServices.parseSourceFile(loc, input), l, doc.getVersion(), doc.getText()));
+ l -> new TextDocumentState((loc, input) -> rascalServices.parseSourceFile(loc, input), l, doc.getVersion(), doc.getText(), timestamp));
}
private TextDocumentState getFile(TextDocumentIdentifier doc) {
@@ -567,6 +575,16 @@ private static CompletableFuture recoverExceptions(CompletableFuture f
});
}
+ @Override
+ public boolean isManagingFile(ISourceLocation file) {
+ return documents.containsKey(file.top());
+ }
+
+ @Override
+ public TextDocumentState getDocumentState(ISourceLocation file) {
+ return documents.get(file.top());
+ }
+
public @MonotonicNonNull FileFacts getFileFacts() {
return facts;
}
diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/model/FileFacts.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/model/FileFacts.java
index dafb3fd04..f11da45bf 100644
--- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/model/FileFacts.java
+++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/model/FileFacts.java
@@ -50,6 +50,8 @@
import org.rascalmpl.vscode.lsp.util.concurrent.LazyUpdateableReference;
import org.rascalmpl.vscode.lsp.util.concurrent.ReplaceableFuture;
import org.rascalmpl.vscode.lsp.util.locations.ColumnMaps;
+import org.rascalmpl.vscode.lsp.util.locations.Locations;
+
import io.usethesource.vallang.ISourceLocation;
public class FileFacts {
@@ -87,7 +89,7 @@ public void reportParseErrors(ISourceLocation file, List msgs) {
private FileFact getFile(ISourceLocation l) {
ISourceLocation resolved = null;
try {
- resolved = URIResolverRegistry.getInstance().logicalToPhysical(l);
+ resolved = Locations.toClientLocation(l);
if (resolved == null) {
resolved = l;
}
diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/terminal/TerminalIDEClient.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/terminal/TerminalIDEClient.java
index a9430d9b7..a270dd5f2 100644
--- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/terminal/TerminalIDEClient.java
+++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/terminal/TerminalIDEClient.java
@@ -97,7 +97,7 @@ public void browse(URI uri, String title, int viewColumn) {
@Override
public void edit(ISourceLocation path) {
try {
- ISourceLocation physical = URIResolverRegistry.getInstance().logicalToPhysical(path);
+ ISourceLocation physical = Locations.toClientLocation(path);
ShowDocumentParams params = new ShowDocumentParams(physical.getURI().toASCIIString());
params.setTakeFocus(true);
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 ee1da2e3e..4bc53da55 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
@@ -31,21 +31,28 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
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 org.rascalmpl.uri.ILogicalSourceLocationResolver;
import org.rascalmpl.uri.ISourceLocationInputOutput;
import org.rascalmpl.uri.ISourceLocationWatcher;
import org.rascalmpl.uri.URIUtil;
+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;
@@ -53,11 +60,28 @@
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 io.usethesource.vallang.ISourceLocation;
-public class FallbackResolver implements ISourceLocationInputOutput, ISourceLocationWatcher {
+public class FallbackResolver implements ISourceLocationInputOutput, ISourceLocationWatcher, ILogicalSourceLocationResolver {
+
+ private static 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) {
+ throw new IllegalStateException("FallbackResolver accessed before initialization");
+ }
+ return instance;
+ }
+
+ public FallbackResolver() {
+ instance = this;
+ }
private static VSCodeUriResolverServer getServer() throws IOException {
var result = VSCodeVFS.INSTANCE.getServer();
@@ -250,5 +274,49 @@ public void unwatch(ISourceLocation root, Consumer watch
getClient().removeWatcher(root, watcher, getServer());
}
+
+ public boolean isFileManaged(ISourceLocation file) {
+ for (var service : textDocumentServices) {
+ if (service.isManagingFile(file)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ @Override
+ public ISourceLocation resolve(ISourceLocation input) throws IOException {
+ if (isFileManaged(input)) {
+ 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(input.top(), "lsp+" + input.getScheme());
+ } catch (URISyntaxException e) {
+ // fall through
+ }
+ }
+ return input;
+ }
+
+ @Override
+ public String authority() {
+ throw new UnsupportedOperationException("'authority' not supported by fallback resolver");
+ }
+
+ private final List textDocumentServices = new CopyOnWriteArrayList<>();
+
+ public void registerTextDocumentService(IBaseTextDocumentService service) {
+ textDocumentServices.add(service);
+ }
+
+ public TextDocumentState getDocumentState(ISourceLocation file) throws IOException {
+ for (var service : textDocumentServices) {
+ var state = service.getDocumentState(file);
+ if (state != null) {
+ return state;
+ }
+ }
+ throw new IOException("File is not managed by lsp");
+ }
}
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
new file mode 100644
index 000000000..e13a10b13
--- /dev/null
+++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/uri/LSPOpenFileResolver.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2018-2023, 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;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import org.rascalmpl.uri.ISourceLocationInput;
+import org.rascalmpl.uri.URIUtil;
+
+import io.usethesource.vallang.ISourceLocation;
+
+public class LSPOpenFileResolver implements ISourceLocationInput {
+
+ @Override
+ public InputStream getInputStream(ISourceLocation uri) throws IOException {
+ var fallbackResolver = FallbackResolver.getInstance();
+ uri = stripLspPrefix(uri);
+ return new ByteArrayInputStream(fallbackResolver.getDocumentState(uri).getCurrentContent().get().getBytes(StandardCharsets.UTF_16));
+ }
+
+ @Override
+ public Charset getCharset(ISourceLocation uri) throws IOException {
+ return StandardCharsets.UTF_16;
+ }
+
+ @Override
+ public boolean exists(ISourceLocation uri) {
+ return FallbackResolver.getInstance().isFileManaged(stripLspPrefix(uri));
+ }
+
+ @Override
+ public long lastModified(ISourceLocation uri) throws IOException {
+ return FallbackResolver.getInstance().getDocumentState(stripLspPrefix(uri)).getLastModified();
+ }
+
+ @Override
+ public boolean isDirectory(ISourceLocation uri) {
+ return false;
+ }
+
+ @Override
+ public boolean isFile(ISourceLocation uri) {
+ return exists(uri);
+ }
+
+ private static ISourceLocation stripLspPrefix(ISourceLocation uri) {
+ if (uri.getScheme().startsWith("lsp+")) {
+ try {
+ return URIUtil.changeScheme(uri, uri.getScheme().substring("lsp+".length()));
+ } catch (URISyntaxException e) {
+ // fall through
+ }
+ }
+ return uri;
+ }
+
+ @Override
+ public String[] list(ISourceLocation uri) throws IOException {
+ throw new IOException("`list` is not supported on files");
+ }
+
+ @Override
+ public String scheme() {
+ return "lsp";
+ }
+
+ @Override
+ public boolean supportsHost() {
+ return false;
+ }
+
+}
diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/Diagnostics.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/Diagnostics.java
index f6d9e07a5..a9bf4f887 100644
--- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/Diagnostics.java
+++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/Diagnostics.java
@@ -168,7 +168,7 @@ public static Map> translateMessages(IList mes
}
private static ISourceLocation getMessageLocation(IConstructor message) {
- return Locations.toPhysicalIfPossible(((ISourceLocation) message.get("at")));
+ return Locations.toClientLocationIfPossible(((ISourceLocation) message.get("at")));
}
private static boolean hasValidLocation(IConstructor d, ISourceLocation file) {
diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/Versioned.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/Versioned.java
index c9a7b7189..b4f66b357 100644
--- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/Versioned.java
+++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/Versioned.java
@@ -31,10 +31,16 @@
public class Versioned {
private final int version;
private final T object;
+ private final long timestamp;
public Versioned(int version, T object) {
+ this(version, object, System.currentTimeMillis());
+ }
+
+ public Versioned(int version, T object, long timestamp) {
this.version = version;
this.object = object;
+ this.timestamp = timestamp;
}
public int version() {
@@ -45,6 +51,10 @@ public T get() {
return object;
}
+ public long getTimestamp() {
+ return timestamp;
+ }
+
@Override
public String toString() {
return String.format("%s [version %d]", object, version);
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 00b50aadd..0b6c3bff1 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
@@ -35,12 +35,41 @@
import org.eclipse.lsp4j.TextDocumentItem;
import org.rascalmpl.uri.URIResolverRegistry;
import org.rascalmpl.uri.URIUtil;
-import org.rascalmpl.values.IRascalValueFactory;
import io.usethesource.vallang.ISourceLocation;
import io.usethesource.vallang.IValue;
+/**
+ * The Locations class provides utility methods related to source locations and VS Code locations.
+ *
+ * Source locations of files that are opened in the IDE are redirected by prefixing the scheme with "lsp+".
+ * These lsp-redirected source locations must not leak outside of the LSP server. The `toClientLocation` methods
+ * strip source locations of their "lsp+" prefix.
+ */
public class Locations {
+ public static ISourceLocation toClientLocation(ISourceLocation loc) throws IOException {
+ var result = URIResolverRegistry.getInstance().logicalToPhysical(loc);
+ if (result.getScheme().startsWith("lsp+")) {
+ try {
+ result = URIUtil.changeScheme(result, result.getScheme().substring("lsp+".length()));
+ } catch (URISyntaxException e) {
+ // fall through
+ }
+ }
+ return result;
+ }
+
+ public static ISourceLocation toClientLocationIfPossible(ISourceLocation loc) {
+ var result = toPhysicalIfPossible(loc);
+ if (result.getScheme().startsWith("lsp+")) {
+ try {
+ return URIUtil.changeScheme(result, result.getScheme().substring("lsp+".length()));
+ } catch (URISyntaxException e) {
+ // fall through
+ }
+ }
+ return result;
+ }
public static ISourceLocation toPhysicalIfPossible(ISourceLocation loc) {
ISourceLocation physical;
try {
diff --git a/rascal-vscode-extension/src/test/vscode-suite/ide.test.ts b/rascal-vscode-extension/src/test/vscode-suite/ide.test.ts
index 90b75ba3e..d5b4f4a2e 100644
--- a/rascal-vscode-extension/src/test/vscode-suite/ide.test.ts
+++ b/rascal-vscode-extension/src/test/vscode-suite/ide.test.ts
@@ -270,4 +270,15 @@ describe('IDE', function () {
await ide.revertOpenChanges();
}
});
+
+ it("editor contents used for open files", async() => {
+ const importerEditor = await ide.openModule(TestWorkspace.importerFile);
+ const importeeEditor = await ide.openModule(TestWorkspace.importeeFile);
+
+ await importeeEditor.typeTextAt(3, 1, "public str foo;");
+ await ide.openModule(TestWorkspace.importerFile);
+
+ await ide.triggerTypeChecker(importerEditor, {waitForFinish : true});
+ await ide.hasErrorSquiggly(importerEditor);
+ });
});
diff --git a/rascal-vscode-extension/src/test/vscode-suite/utils.ts b/rascal-vscode-extension/src/test/vscode-suite/utils.ts
index 27f77047b..82e615dd7 100644
--- a/rascal-vscode-extension/src/test/vscode-suite/utils.ts
+++ b/rascal-vscode-extension/src/test/vscode-suite/utils.ts
@@ -61,6 +61,9 @@ export class TestWorkspace {
public static readonly libFile = path.join(src(this.libProject), 'Lib.rsc');
public static readonly libFileTpl = path.join(target(this.libProject),'$Lib.tpl');
+ public static readonly importerFile = path.join(src(this.testProject), 'Importer.rsc');
+ public static readonly importeeFile = path.join(src(this.testProject), 'Importee.rsc');
+
public static readonly picoFile = path.join(src(this.testProject, 'pico'), 'testing.pico');
public static readonly picoNewFile = path.join(src(this.testProject, 'pico'), 'testing.pico-new');
}
diff --git a/rascal-vscode-extension/test-workspace/test-project/src/main/rascal/Importee.rsc b/rascal-vscode-extension/test-workspace/test-project/src/main/rascal/Importee.rsc
new file mode 100644
index 000000000..18e4a45b9
--- /dev/null
+++ b/rascal-vscode-extension/test-workspace/test-project/src/main/rascal/Importee.rsc
@@ -0,0 +1,3 @@
+module Importee
+
+
diff --git a/rascal-vscode-extension/test-workspace/test-project/src/main/rascal/Importer.rsc b/rascal-vscode-extension/test-workspace/test-project/src/main/rascal/Importer.rsc
new file mode 100644
index 000000000..6a24cb67b
--- /dev/null
+++ b/rascal-vscode-extension/test-workspace/test-project/src/main/rascal/Importer.rsc
@@ -0,0 +1,6 @@
+module Importer
+
+import Importee;
+
+int x = foo;
+
\ No newline at end of file