Skip to content

Commit 2fdc776

Browse files
committed
Merge remote-tracking branch 'origin/main' into feature/1010-pom-leading-for-dsls/final
2 parents 45b8638 + 1ea0248 commit 2fdc776

39 files changed

Lines changed: 1129 additions & 863 deletions

.github/workflows/build.yaml

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ jobs:
4343
working-directory: ./rascal-vscode-extension
4444
run: |
4545
npm ci
46-
npm run compile-tests
47-
npm run normalTest
46+
npm run compile:tests
47+
npm run test:normal
4848
4949
ui-test:
5050
strategy:
@@ -80,7 +80,7 @@ jobs:
8080
working-directory: ./rascal-vscode-extension
8181
run: |
8282
npm ci
83-
npm run compile-tests
83+
npm run compile:tests
8484
8585
- name: Cache vscode downloads
8686
id: cache-vscode
@@ -182,15 +182,16 @@ jobs:
182182
sed -i "1i⚠️ This is pre-release build, based on [CI build $GITHUB_RUN_NUMBER](https://github.com/usethesource/rascal-language-servers/actions/runs/$GITHUB_RUN_ID). ⚠️\n\n" README.md
183183
sed -i "s/\\(\"version\":.*\\)\\-head/\\1-head$GITHUB_RUN_NUMBER/" package.json
184184
185-
- name: Package & compile extension
185+
- name: Compile extension
186186
working-directory: ./rascal-vscode-extension
187187
run: |
188188
npm ci
189-
npm run license-check
190-
npm run esbuild
189+
npm run license:check
191190
npm run lint
191+
npm run check:types
192+
npm run compile
192193
193-
- name: package extension
194+
- name: Package extension
194195
working-directory: rascal-vscode-extension
195196
run: |
196197
npm run lsp4j:package

rascal-lsp/src/main/checkerframework/lsp4j.astub

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,3 +451,20 @@ public class SemanticTokensCapabilities extends DynamicRegistrationCapabilities
451451
public @Nullable Boolean getServerCancelSupport() {}
452452
public @Nullable Boolean getAugmentsSyntaxTokens() {}
453453
}
454+
455+
package org.eclipse.lsp4j;
456+
457+
import org.checkerframework.checker.nullness.qual.*;
458+
459+
public class DocumentFilter {
460+
public DocumentFilter(@Nullable String language, @Nullable String scheme, @Nullable Either<String, RelativePattern> pattern) {}
461+
}
462+
463+
package org.eclipse.lsp4j;
464+
465+
import org.checkerframework.checker.nullness.qual.*;
466+
467+
public class Registration {
468+
public Registration(String id, String method) {}
469+
public Registration(String id, String method, @Nullable Object registerOptions) {}
470+
}

rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/TextDocumentState.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.apache.logging.log4j.Logger;
3939
import org.checkerframework.checker.nullness.qual.Nullable;
4040
import org.rascalmpl.library.util.ParseErrorRecovery;
41+
import org.rascalmpl.uri.FileAttributes;
4142
import org.rascalmpl.values.IRascalValueFactory;
4243
import org.rascalmpl.values.parsetrees.ITree;
4344
import org.rascalmpl.vscode.lsp.parametric.NoContributions.NoContributionException;
@@ -64,6 +65,7 @@ public class TextDocumentState {
6465
private final BiFunction<ISourceLocation, String, CompletableFuture<ITree>> parser;
6566
private final ISourceLocation location;
6667
private final ExecutorService exec;
68+
private final FileAttributes attributesOnDisk;
6769

6870
private final AtomicReference<Versioned<Update>> current;
6971
private final AtomicReference<@Nullable Versioned<ITree>> lastWithoutErrors;
@@ -73,10 +75,11 @@ public TextDocumentState(
7375
BiFunction<ISourceLocation, String, CompletableFuture<ITree>> parser,
7476
ISourceLocation location,
7577
int initialVersion, String initialContent, long initialTimestamp,
76-
ExecutorService exec) {
78+
ExecutorService exec, FileAttributes attributesOnDisk) {
7779

7880
this.parser = parser;
7981
this.location = location;
82+
this.attributesOnDisk = attributesOnDisk;
8083
this.lastWithoutErrors = new AtomicReference<>();
8184
this.last = new AtomicReference<>();
8285
this.exec = exec;
@@ -89,6 +92,13 @@ public ISourceLocation getLocation() {
8992
return location;
9093
}
9194

95+
/**
96+
* The file attributes (aka stat) of the location on disk, before it was opened by VS Code
97+
*/
98+
public FileAttributes getAttributesOnDisk() {
99+
return attributesOnDisk;
100+
}
101+
92102
public CompletableFuture<Versioned<List<Diagnostics.Template>>> update(int version, String content, long timestamp) {
93103
var u = new Update(version, content, timestamp);
94104
Versioned.replaceIfNewer(current, new Versioned<>(version, u));
@@ -266,6 +276,6 @@ public long getLastModified() {
266276

267277
public TextDocumentState changeParser(BiFunction<ISourceLocation, String, CompletableFuture<ITree>> parsing) {
268278
var c = getCurrentContent();
269-
return new TextDocumentState(parsing, this.location, c.version(), c.get(), getLastModified(), exec);
279+
return new TextDocumentState(parsing, this.location, c.version(), c.get(), getLastModified(), exec, getAttributesOnDisk());
270280
}
271281
}

rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/TextDocumentStateManager.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import org.eclipse.lsp4j.jsonrpc.ResponseErrorException;
5050
import org.eclipse.lsp4j.jsonrpc.messages.ResponseError;
5151
import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;
52+
import org.rascalmpl.uri.FileAttributes;
5253
import org.rascalmpl.uri.URIResolverRegistry;
5354
import org.rascalmpl.util.locations.ColumnMaps;
5455
import org.rascalmpl.util.locations.LineColumnOffsetMap;
@@ -58,7 +59,6 @@
5859
import org.rascalmpl.vscode.lsp.util.Lists;
5960
import org.rascalmpl.vscode.lsp.util.Versioned;
6061
import org.rascalmpl.vscode.lsp.util.locations.Locations;
61-
6262
import io.usethesource.vallang.ISourceLocation;
6363

6464
/**
@@ -153,9 +153,25 @@ protected TextDocumentState getFile(ISourceLocation loc) {
153153
}
154154
}
155155

156+
private FileAttributes safeStat(ISourceLocation loc, long timestamp) {
157+
try {
158+
return URIResolverRegistry.getInstance().stat(loc);
159+
} catch (IOException e) {
160+
// generate a fallback
161+
// for example for untitled files, text document content provided,
162+
// or other files that are not accessible to our VFS
163+
return new FileAttributes(
164+
true, true,
165+
timestamp, timestamp,
166+
true, false,
167+
0
168+
);
169+
}
170+
171+
}
172+
156173
protected TextDocumentState openFile(TextDocumentItem doc, Function<ISourceLocation, BiFunction<ISourceLocation, String, CompletableFuture<ITree>>> parserGetter, long timestamp, ExecutorService exec) {
157-
return files.computeIfAbsent(Locations.toLoc(doc),
158-
l -> new TextDocumentState(parserGetter.apply(l), l, doc.getVersion(), doc.getText(), timestamp, exec));
174+
return files.computeIfAbsent(Locations.toLoc(doc), l -> new TextDocumentState(parserGetter.apply(l), l, doc.getVersion(), doc.getText(), timestamp, exec, safeStat(l, timestamp)));
159175
}
160176

161177
private void invalidateColumnMaps(ISourceLocation loc) {
@@ -191,12 +207,13 @@ protected void closeFile(ISourceLocation loc) {
191207
return files.keySet();
192208
}
193209

194-
protected void updateContents(DidChangeTextDocumentParams change, long timestamp) {
210+
protected TextDocumentState updateContents(DidChangeTextDocumentParams change, long timestamp) {
195211
var doc = change.getTextDocument();
196212
logger.trace("New contents for {}", doc);
197213
TextDocumentState file = getFile(Locations.toLoc(doc));
198214
invalidateColumnMaps(file.getLocation());
199215
handleParsingErrors(file, file.update(doc.getVersion(), Lists.last(change.getContentChanges()).getText(), timestamp));
216+
return file;
200217
}
201218

202219
protected void handleParsingErrors(TextDocumentState file, CompletableFuture<Versioned<List<Diagnostics.Template>>> diagnosticsAsync) {

rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java

Lines changed: 63 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
*/
2727
package org.rascalmpl.vscode.lsp.parametric;
2828

29+
import java.net.URI;
2930
import java.time.Duration;
3031
import java.util.ArrayList;
3132
import java.util.Arrays;
@@ -39,12 +40,14 @@
3940
import java.util.Set;
4041
import java.util.concurrent.CompletableFuture;
4142
import java.util.concurrent.ConcurrentHashMap;
43+
import java.util.concurrent.CopyOnWriteArraySet;
4244
import java.util.concurrent.ExecutorService;
4345
import java.util.function.BiFunction;
4446
import java.util.function.Function;
4547
import java.util.function.Supplier;
4648
import java.util.stream.Collectors;
4749
import java.util.stream.Stream;
50+
4851
import org.apache.logging.log4j.Level;
4952
import org.apache.logging.log4j.LogManager;
5053
import org.apache.logging.log4j.Logger;
@@ -112,7 +115,6 @@
112115
import org.eclipse.lsp4j.TextDocumentIdentifier;
113116
import org.eclipse.lsp4j.TextDocumentItem;
114117
import org.eclipse.lsp4j.TextDocumentSyncKind;
115-
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
116118
import org.eclipse.lsp4j.WorkspaceEdit;
117119
import org.eclipse.lsp4j.WorkspaceFolder;
118120
import org.eclipse.lsp4j.jsonrpc.ResponseErrorException;
@@ -138,6 +140,7 @@
138140
import org.rascalmpl.vscode.lsp.parametric.capabilities.CompletionCapability;
139141
import org.rascalmpl.vscode.lsp.parametric.capabilities.FileOperationCapability;
140142
import org.rascalmpl.vscode.lsp.parametric.capabilities.ICapabilityParams;
143+
import org.rascalmpl.vscode.lsp.parametric.capabilities.SemanticTokensCapability;
141144
import org.rascalmpl.vscode.lsp.parametric.model.ParametricFileFacts;
142145
import org.rascalmpl.vscode.lsp.parametric.model.ParametricSummary;
143146
import org.rascalmpl.vscode.lsp.parametric.model.ParametricSummary.SummaryLookup;
@@ -180,6 +183,7 @@ public class ParametricTextDocumentService extends TextDocumentStateManager impl
180183

181184
private final String dedicatedLanguageName;
182185
private final SemanticTokenizer tokenizer = new SemanticTokenizer();
186+
private final Set<String> extensionLessSchemes = new CopyOnWriteArraySet<>();
183187
private final boolean exitWhenEmpty;
184188

185189
private @MonotonicNonNull LanguageClient client;
@@ -228,6 +232,7 @@ public void initializeServerCapabilities(ClientCapabilities clientCapabilities,
228232
// Since the initialize request is the very first request after connecting, we can initialize the capabilities here
229233
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize
230234
dynamicCapabilities = new CapabilityRegistration(availableClient(), exec, clientCapabilities
235+
, new SemanticTokensCapability()
231236
, new CompletionCapability()
232237
, /* new FileOperationCapability.DidCreateFiles(exec), */ new FileOperationCapability.DidRenameFiles(exec), new FileOperationCapability.DidDeleteFiles(exec)
233238
);
@@ -246,7 +251,6 @@ private static void setStaticServerCapabilities(String dedicatedLanguageName, Se
246251
result.setReferencesProvider(true);
247252
result.setDocumentSymbolProvider(true);
248253
result.setImplementationProvider(true);
249-
result.setSemanticTokensProvider(SemanticTokenizer.options());
250254
result.setCodeActionProvider(true);
251255
result.setCodeLensProvider(new CodeLensOptions(false));
252256
result.setRenameProvider(new RenameOptions(true));
@@ -313,15 +317,31 @@ public void didOpen(DidOpenTextDocumentParams params) {
313317
logger.debug("Did Open file: {}", params.getTextDocument());
314318
TextDocumentState file = open(params.getTextDocument(), timestamp);
315319
handleParsingErrors(file, file.getCurrentDiagnosticsAsync());
316-
triggerAnalyzer(params.getTextDocument(), NORMAL_DEBOUNCE);
320+
triggerAnalyzer(file, NORMAL_DEBOUNCE);
321+
322+
// Discover capabilities
323+
discoverExtensionLessScheme(URIUtil.assumeCorrect(params.getTextDocument().getUri()));
324+
}
325+
326+
/**
327+
* Captures extensions-less schemes and updates dynamic capabilities to apply to them.
328+
* @param uri A URI that should be associated with the parametric server.
329+
*/
330+
private void discoverExtensionLessScheme(URI uri) {
331+
var ext = URIUtil.getExtension(Locations.toLoc(uri));
332+
// We want the original scheme, not the one possibly modified when converting URI -> loc
333+
if (ext.equals("") && extensionLessSchemes.add(uri.getScheme())) {
334+
// Should be called from the main, single-threaded request pool
335+
updateCapabilities();
336+
}
317337
}
318338

319339
@Override
320340
public void didChange(DidChangeTextDocumentParams params) {
321341
var timestamp = System.currentTimeMillis();
322342
logger.debug("Did Change file: {}", params.getTextDocument().getUri());
323-
updateContents(params, timestamp);
324-
triggerAnalyzer(params.getTextDocument(), NORMAL_DEBOUNCE);
343+
var state = updateContents(params, timestamp);
344+
triggerAnalyzer(state, NORMAL_DEBOUNCE);
325345
}
326346

327347
@Override
@@ -364,21 +384,13 @@ public void didDeleteFiles(DeleteFilesParams params) {
364384
}
365385
}
366386

367-
private void triggerAnalyzer(TextDocumentItem doc, Duration delay) {
368-
triggerAnalyzer(new VersionedTextDocumentIdentifier(doc.getUri(), doc.getVersion()), delay);
369-
}
370-
371-
private void triggerAnalyzer(VersionedTextDocumentIdentifier doc, Duration delay) {
372-
var location = Locations.toLoc(doc);
373-
triggerAnalyzer(location, doc.getVersion(), delay);
374-
}
375-
376-
private void triggerAnalyzer(ISourceLocation location, int version, Duration delay) {
377-
if (safeLanguage(location).isPresent()) {
387+
private void triggerAnalyzer(TextDocumentState state, Duration delay) {
388+
var location = state.getLocation();
389+
if (safeLanguage(state.getLocation()).isPresent()) {
378390
logger.trace("Triggering analyzer for {}", location);
379391
var fileFacts = facts(location);
380392
fileFacts.invalidateAnalyzer(location);
381-
fileFacts.calculateAnalyzer(location, getFile(location).getCurrentTreeAsync(true), version, delay);
393+
fileFacts.calculateAnalyzer(location, state.getCurrentTreeAsync(true), state.getCurrentContent(), delay);
382394
} else {
383395
logger.debug("Not triggering analyzer, since no language is registered for {}", location);
384396
}
@@ -941,11 +953,6 @@ public synchronized void registerLanguage(LanguageParameter lang) {
941953
this.registeredExtensions.put(extension, lang.getName());
942954
}
943955

944-
// `CapabilityRegistration::update` should never be called asynchronously, since that might re-order incoming updates.
945-
// Since `registerLanguage` is called from a single-threaded pool, calling it here is safe.
946-
// Note: `CapabilityRegistration::update` returns a void future, which we do not have to wait on.
947-
availableCapabilities().update(buildLanguageParams());
948-
949956
// If we opened any files with this extension before, now associate them with contributions
950957
var extensions = Arrays.asList(lang.getExtensions());
951958
for (var f : getOpenFiles()) {
@@ -954,6 +961,32 @@ public synchronized void registerLanguage(LanguageParameter lang) {
954961
refreshFileState(f);
955962
}
956963
}
964+
965+
// Do this last, after the parser for open editors has been updated,
966+
// since this might trigger new requests on the editor contents.
967+
// `updateCapabilities`/`CapabilityRegistration::update` should never be called asynchronously, since that might re-order incoming updates.
968+
// Since `registerLanguage` is called from a single-threaded pool, calling it here is safe.
969+
// Note: `updateCapabilities` returns a void future, which we do not have to wait on.
970+
updateCapabilities();
971+
}
972+
973+
private CompletableFuture<Void> refreshEditors() {
974+
var client = availableClient();
975+
// Do all in parallel, and return a future that completes when each of these completes
976+
return CompletableFutureUtils.reduce(List.of(
977+
client.refreshCodeLenses(),
978+
client.refreshDiagnostics(),
979+
client.refreshFoldingRanges(),
980+
client.refreshInlayHints(),
981+
client.refreshInlineValues(),
982+
client.refreshSemanticTokens()
983+
), (v1, v2) -> null);
984+
}
985+
986+
private synchronized CompletableFuture<Void> updateCapabilities() {
987+
return availableCapabilities()
988+
.update(buildLanguageParams())
989+
.thenCompose(v -> refreshEditors());
957990
}
958991

959992
/**
@@ -972,6 +1005,11 @@ public ILanguageContributions contributions() {
9721005
public Set<String> fileExtensions() {
9731006
return extensionsByLang.getOrDefault(e.getKey(), Collections.emptySet());
9741007
}
1008+
1009+
@Override
1010+
public Set<String> extensionLessSchemes() {
1011+
return extensionLessSchemes;
1012+
}
9751013
}).collect(Collectors.toSet());
9761014
}
9771015

@@ -986,7 +1024,7 @@ private void refreshFileState(ISourceLocation f) {
9861024
}
9871025
// Update open editor
9881026
handleParsingErrors(state, state.getCurrentDiagnosticsAsync());
989-
triggerAnalyzer(f, state.getCurrentContent().version(), NORMAL_DEBOUNCE);
1027+
triggerAnalyzer(state, NORMAL_DEBOUNCE);
9901028
}
9911029

9921030
private static String buildContributionKey(LanguageParameter lang) {
@@ -1026,7 +1064,8 @@ public synchronized void unregisterLanguage(LanguageParameter lang) {
10261064
System.exit(0);
10271065
}
10281066

1029-
availableCapabilities().update(buildLanguageParams());
1067+
// Should be called from the main, single-threaded request pool
1068+
updateCapabilities();
10301069
}
10311070

10321071
@Override

0 commit comments

Comments
 (0)