2626 */
2727package org .rascalmpl .vscode .lsp .parametric ;
2828
29+ import java .net .URI ;
2930import java .time .Duration ;
3031import java .util .ArrayList ;
3132import java .util .Arrays ;
3940import java .util .Set ;
4041import java .util .concurrent .CompletableFuture ;
4142import java .util .concurrent .ConcurrentHashMap ;
43+ import java .util .concurrent .CopyOnWriteArraySet ;
4244import java .util .concurrent .ExecutorService ;
4345import java .util .function .BiFunction ;
4446import java .util .function .Function ;
4547import java .util .function .Supplier ;
4648import java .util .stream .Collectors ;
4749import java .util .stream .Stream ;
50+
4851import org .apache .logging .log4j .Level ;
4952import org .apache .logging .log4j .LogManager ;
5053import org .apache .logging .log4j .Logger ;
112115import org .eclipse .lsp4j .TextDocumentIdentifier ;
113116import org .eclipse .lsp4j .TextDocumentItem ;
114117import org .eclipse .lsp4j .TextDocumentSyncKind ;
115- import org .eclipse .lsp4j .VersionedTextDocumentIdentifier ;
116118import org .eclipse .lsp4j .WorkspaceEdit ;
117119import org .eclipse .lsp4j .WorkspaceFolder ;
118120import org .eclipse .lsp4j .jsonrpc .ResponseErrorException ;
138140import org .rascalmpl .vscode .lsp .parametric .capabilities .CompletionCapability ;
139141import org .rascalmpl .vscode .lsp .parametric .capabilities .FileOperationCapability ;
140142import org .rascalmpl .vscode .lsp .parametric .capabilities .ICapabilityParams ;
143+ import org .rascalmpl .vscode .lsp .parametric .capabilities .SemanticTokensCapability ;
141144import org .rascalmpl .vscode .lsp .parametric .model .ParametricFileFacts ;
142145import org .rascalmpl .vscode .lsp .parametric .model .ParametricSummary ;
143146import 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