-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix LSP document synchronization #544
base: main
Are you sure you want to change the base?
Changes from 6 commits
8c1b469
ac5a860
e873a4a
ba53307
f4ddf7a
d202427
c2da74f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
org.rascalmpl.vscode.lsp.uri.LSPOpenFileResolver |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,33 +31,55 @@ | |
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.HashSet; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import java.util.concurrent.CompletableFuture; | ||
import java.util.concurrent.CompletionException; | ||
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; | ||
import org.rascalmpl.vscode.lsp.uri.jsonrpc.messages.IOResult; | ||
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; | ||
|
||
public static FallbackResolver getInstance() { | ||
if (instance == null) { | ||
throw new IllegalStateException("FallbackResolver accessed before initialization"); | ||
} | ||
return instance; | ||
} | ||
|
||
public FallbackResolver() { | ||
instance = this; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should add a small comment on why we're doing this |
||
} | ||
|
||
private static VSCodeUriResolverServer getServer() throws IOException { | ||
var result = VSCodeVFS.INSTANCE.getServer(); | ||
|
@@ -250,5 +272,45 @@ public void unwatch(ISourceLocation root, Consumer<ISourceLocationChanged> watch | |
getClient().removeWatcher(root, watcher, getServer()); | ||
|
||
} | ||
|
||
public boolean isFileManaged(ISourceLocation file) { | ||
for (final var service : textDocumentServices) { | ||
if (service.isManagingFile(file)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
@Override | ||
public ISourceLocation resolve(ISourceLocation input) throws IOException { | ||
if (isFileManaged(input)) { | ||
try { | ||
return URIUtil.changeScheme(input, "lsp+" + input.getScheme()).top(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why the |
||
} catch (URISyntaxException e) { | ||
// fall through | ||
} | ||
} | ||
return input; | ||
} | ||
|
||
@Override | ||
public String authority() { | ||
throw new UnsupportedOperationException("'authority' not supported by fallback resolver"); | ||
} | ||
|
||
private Set<IBaseTextDocumentService> textDocumentServices = new HashSet<>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: should be final notnit: not a threadsafe container, since we're only iterating over it, never clearing any, or using the key of it, an |
||
|
||
public void registerTextDocumentService(IBaseTextDocumentService service) { | ||
textDocumentServices.add(service); | ||
} | ||
|
||
public TextDocumentState getDocumentState(ISourceLocation file) { | ||
for (final var service : textDocumentServices) { | ||
if (service.isManagingFile(file)) { | ||
return service.getDocumentState(file); | ||
} | ||
} | ||
return null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is there an alternative to what happens to a uri that was generated while the editor was open, but refenced after it is closed? do we unresolve it to the original URI? |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
/* | ||
* 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); | ||
if (fallbackResolver.isFileManaged(uri)) { | ||
return new ByteArrayInputStream(fallbackResolver.getDocumentState(uri).getCurrentContent().get().getBytes(StandardCharsets.UTF_16)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a risk of a race between |
||
} | ||
throw new IOException("File does not exist"); | ||
} | ||
|
||
@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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can throw a NPE in case |
||
} | ||
|
||
@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 UnsupportedOperationException("Unimplemented method 'list'"); | ||
rodinaarssen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
@Override | ||
public String scheme() { | ||
return "lsp"; | ||
} | ||
|
||
@Override | ||
public boolean supportsHost() { | ||
return false; | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this not looking at documents? (just like the Parametric one, where both look at the same container?)