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 cf3937ed2..4fa157975 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 @@ -61,7 +61,6 @@ import org.rascalmpl.library.lang.json.internal.JsonValueReader; import org.rascalmpl.library.lang.json.internal.JsonValueWriter; import org.rascalmpl.library.util.PathConfig; -import org.rascalmpl.uri.URIResolverRegistry; import org.rascalmpl.values.IRascalValueFactory; import org.rascalmpl.vscode.lsp.log.LogRedirectConfiguration; import org.rascalmpl.vscode.lsp.terminal.ITerminalIDEServer.LanguageParameter; @@ -175,7 +174,7 @@ public static void startLanguageServer(ExecutorService threadPool, Function {}, threadPool, docService, wsService), threadPool)); } } catch (IOException e) { - logger.fatal("Failure to start TCP server", e); + logger.fatal("Failure to start TCP server on port {}", portNumber, e); } } } @@ -219,7 +218,6 @@ private static void startLSP(Launcher server) { } private static class ActualLanguageServer implements IBaseLanguageServerExtensions, LanguageClientAware { static final Logger logger = LogManager.getLogger(ActualLanguageServer.class); - private static final URIResolverRegistry reg = URIResolverRegistry.getInstance(); private final IBaseTextDocumentService lspDocumentService; private final BaseWorkspaceService lspWorkspaceService; private final Runnable onExit; diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/RascalLSPMonitor.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/RascalLSPMonitor.java index 5dcdc1f13..6ea706256 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/RascalLSPMonitor.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/RascalLSPMonitor.java @@ -32,7 +32,6 @@ import java.util.function.Function; import org.apache.logging.log4j.Logger; -import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.PolyNull; import org.eclipse.lsp4j.ProgressParams; 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 4bcaf347a..9cfc29eb2 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 @@ -30,6 +30,7 @@ import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiFunction; import org.apache.logging.log4j.LogManager; @@ -38,6 +39,7 @@ import org.rascalmpl.library.util.ParseErrorRecovery; import org.rascalmpl.values.IRascalValueFactory; import org.rascalmpl.values.parsetrees.ITree; +import org.rascalmpl.vscode.lsp.parametric.NoContributions.NoContributionException; import org.rascalmpl.vscode.lsp.util.Diagnostics; import org.rascalmpl.vscode.lsp.util.Versioned; import io.usethesource.vallang.ISourceLocation; @@ -195,26 +197,34 @@ public CompletableFuture>> getDiagnosticsAs } private void parse() { - parser.apply(location, content) - .whenComplete((t, e) -> { - var diagnosticsList = toDiagnosticsList(t, e); // `t` and `e` are nullable - - // Complete future to get the tree - if (t == null) { - treeAsync.completeExceptionally(e); - } else { - var tree = new Versioned<>(version, t, timestamp); - Versioned.replaceIfNewer(last, tree); - if (diagnosticsList.isEmpty()) { - Versioned.replaceIfNewer(lastWithoutErrors, tree); + try { + parser.apply(location, content) + .whenComplete((t, e) -> { + if (e instanceof CompletionException && e.getCause() != null) { + e = e.getCause(); + } + var diagnosticsList = toDiagnosticsList(t, e); // `t` and `e` are nullable + + // Complete future to get the tree + if (t == null) { + treeAsync.completeExceptionally(e); + } else { + var tree = new Versioned<>(version, t, timestamp); + Versioned.replaceIfNewer(last, tree); + if (diagnosticsList.isEmpty()) { + Versioned.replaceIfNewer(lastWithoutErrors, tree); + } + treeAsync.complete(tree); } - treeAsync.complete(tree); - } - // Complete future to get diagnostics - var diagnostics = new Versioned<>(version, diagnosticsList); - diagnosticsAsync.complete(diagnostics); - }); + // Complete future to get diagnostics + var diagnostics = new Versioned<>(version, diagnosticsList); + diagnosticsAsync.complete(diagnostics); + }); + } catch (NoContributionException e) { + logger.debug("Ignoring missing parser for {}", location, e); + treeAsync.completeOnTimeout(new Versioned<>(version, IRascalValueFactory.getInstance().character(0), timestamp), 60, TimeUnit.SECONDS); + } } private List toDiagnosticsList(@Nullable ITree tree, @Nullable Throwable excp) { @@ -241,4 +251,9 @@ private List toDiagnosticsList(@Nullable ITree tree, @Null public long getLastModified() { return unpackCurrent().getTimestamp(); } + + public TextDocumentState changeParser(BiFunction> parsing) { + var c = getCurrentContent(); + return new TextDocumentState(parsing, this.location, c.version(), c.get(), getLastModified()); + } } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/NoContributions.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/NoContributions.java new file mode 100644 index 000000000..76ee5c2a6 --- /dev/null +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/NoContributions.java @@ -0,0 +1,247 @@ +/* + * 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.parametric; + +import java.util.concurrent.CompletableFuture; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.rascalmpl.values.parsetrees.ITree; +import org.rascalmpl.vscode.lsp.util.concurrent.InterruptibleFuture; + +import io.usethesource.vallang.IConstructor; +import io.usethesource.vallang.IList; +import io.usethesource.vallang.ISet; +import io.usethesource.vallang.ISourceLocation; +import io.usethesource.vallang.ITuple; +import io.usethesource.vallang.IValue; + +/** + * An implementation of {@link ILanguageContributions} that has no contributions. + * It is intended to be used as a placeholder for files which do not have any contributions registered. + */ +public class NoContributions implements ILanguageContributions { + + private static final Logger logger = LogManager.getLogger(NoContributions.class); + private static final CompletableFuture FALSE = CompletableFuture.completedFuture(false); + + private String name; + + public class NoContributionException extends RuntimeException { + private NoContributionException(String message) { + super("Missing contribution: " + message); + } + } + + public NoContributions(String name) { + this.name = name; + } + + @Override + public String getName() { + return String.format("Empty contributions for '%s'", name); + } + + @Override + public CompletableFuture parsing(ISourceLocation loc, String input) { + throw new NoContributionException("parsing"); + } + + @Override + public InterruptibleFuture analysis(ISourceLocation loc, ITree input) { + throw new NoContributionException("analysis"); + } + + @Override + public InterruptibleFuture build(ISourceLocation loc, ITree input) { + throw new NoContributionException("build"); + } + + @Override + public InterruptibleFuture documentSymbol(ITree input) { + throw new NoContributionException("documentSymbol"); + } + + @Override + public InterruptibleFuture codeLens(ITree input) { + throw new NoContributionException("codeLens"); + } + + @Override + public InterruptibleFuture inlayHint(ITree input) { + throw new NoContributionException("inlayHint"); + } + + @Override + public InterruptibleFuture execution(String command) { + throw new NoContributionException("execution"); + } + + @Override + public InterruptibleFuture hover(IList focus) { + throw new NoContributionException("hover"); + } + + @Override + public InterruptibleFuture definition(IList focus) { + throw new NoContributionException("definition"); + } + + @Override + public InterruptibleFuture references(IList focus) { + throw new NoContributionException("references"); + } + + @Override + public InterruptibleFuture implementation(IList focus) { + throw new NoContributionException("implementation"); + } + + @Override + public InterruptibleFuture codeAction(IList focus) { + throw new NoContributionException("codeLens"); + } + + @Override + public InterruptibleFuture selectionRange(IList focus) { + throw new NoContributionException("selectionRange"); + } + + @Override + public InterruptibleFuture prepareRename(IList focus) { + throw new NoContributionException("prepareRename"); + } + + @Override + public InterruptibleFuture rename(IList focus, String name) { + throw new NoContributionException("rename"); + } + + @Override + public InterruptibleFuture didRenameFiles(IList fileRenames) { + throw new NoContributionException("didRenameFiles"); + } + + @Override + public CompletableFuture parseCodeActions(String command) { + throw new NoContributionException("parseCodeActions"); + } + + @Override + public CompletableFuture hasAnalysis() { + return FALSE; + } + + @Override + public CompletableFuture hasBuild() { + return FALSE; + } + + @Override + public CompletableFuture hasDocumentSymbol() { + return FALSE; + } + + @Override + public CompletableFuture hasCodeLens() { + return FALSE; + } + + @Override + public CompletableFuture hasInlayHint() { + return FALSE; + } + + @Override + public CompletableFuture hasRename() { + return FALSE; + } + + @Override + public CompletableFuture hasExecution() { + return FALSE; + } + + @Override + public CompletableFuture hasHover() { + return FALSE; + } + + @Override + public CompletableFuture hasDefinition() { + return FALSE; + } + + @Override + public CompletableFuture hasReferences() { + return FALSE; + } + + @Override + public CompletableFuture hasImplementation() { + return FALSE; + } + + @Override + public CompletableFuture hasCodeAction() { + return FALSE; + } + + @Override + public CompletableFuture hasDidRenameFiles() { + return FALSE; + } + + @Override + public CompletableFuture hasSelectionRange() { + return FALSE; + } + + @Override + public CompletableFuture specialCaseHighlighting() { + return FALSE; + } + + @Override + public CompletableFuture getAnalyzerSummaryConfig() { + throw new NoContributionException("getAnalyzerSummaryConfig"); + } + + @Override + public CompletableFuture getBuilderSummaryConfig() { + throw new NoContributionException("getBuilderSummaryConfig"); + } + + @Override + public CompletableFuture getOndemandSummaryConfig() { + throw new NoContributionException("getOndemandSummaryConfig"); + } + + @Override + public void cancelProgress(String progressId) { + logger.trace("Cancelling progress {} not supported on dummy contributions.", progressId); + } +} 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 d7f4ab776..72c4cb80b 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 @@ -30,10 +30,12 @@ import java.io.Reader; import java.time.Duration; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; @@ -137,6 +139,7 @@ import org.rascalmpl.vscode.lsp.util.locations.LineColumnOffsetMap; import org.rascalmpl.vscode.lsp.util.locations.Locations; import org.rascalmpl.vscode.lsp.util.locations.impl.TreeSearch; + import io.usethesource.vallang.IBool; import io.usethesource.vallang.IConstructor; import io.usethesource.vallang.IList; @@ -317,8 +320,7 @@ public void didClose(DidCloseTextDocumentParams params) { logger.debug("Did Close file: {}", params.getTextDocument()); var loc = Locations.toLoc(params.getTextDocument()); if (files.remove(loc) == null) { - throw new ResponseErrorException(new ResponseError(ResponseErrorCode.InternalError, - "Unknown file: " + loc, params)); + throw new ResponseErrorException(unknownFileError(loc, params)); } facts(loc).close(loc); } @@ -328,7 +330,7 @@ public void didDeleteFiles(DeleteFilesParams params) { ownExecuter.submit(() -> { // if a file is deleted, and we were tracking it, we remove our diagnostics for (var f : params.getFiles()) { - if (registeredExtensions.containsKey(extension(URIUtil.assumeCorrectLocation(f.getUri())))) { + if (isLanguageRegistered(URIUtil.assumeCorrectLocation(f.getUri()))) { availableClient().publishDiagnostics(new PublishDiagnosticsParams(f.getUri(), List.of())); } } @@ -339,12 +341,21 @@ public void didDeleteFiles(DeleteFilesParams params) { private void triggerAnalyzer(TextDocumentItem doc, Duration delay) { triggerAnalyzer(new VersionedTextDocumentIdentifier(doc.getUri(), doc.getVersion()), delay); } + private void triggerAnalyzer(VersionedTextDocumentIdentifier doc, Duration delay) { - logger.trace("Triggering analyzer for {}", doc.getUri()); var location = Locations.toLoc(doc); - var fileFacts = facts(location); - fileFacts.invalidateAnalyzer(location); - fileFacts.calculateAnalyzer(location, getFile(location).getCurrentTreeAsync(true), doc.getVersion(), delay); + triggerAnalyzer(location, doc.getVersion(), delay); + } + + private void triggerAnalyzer(ISourceLocation location, int version, Duration delay) { + if (isLanguageRegistered(location)) { + logger.trace("Triggering analyzer for {}", location); + var fileFacts = facts(location); + fileFacts.invalidateAnalyzer(location); + fileFacts.calculateAnalyzer(location, getFile(location).getCurrentTreeAsync(true), version, delay); + } else { + logger.debug("Not triggering analyzer, since no language is registered for {}", location); + } } private void triggerBuilder(TextDocumentIdentifier doc) { @@ -355,12 +366,11 @@ private void triggerBuilder(TextDocumentIdentifier doc) { fileFacts.calculateBuilder(location, getFile(location).getCurrentTreeAsync(true)); } - private TextDocumentState updateContents(VersionedTextDocumentIdentifier doc, String newContents, long timestamp) { - TextDocumentState file = getFile(Locations.toLoc(doc)); + private void updateContents(VersionedTextDocumentIdentifier doc, String newContents, long timestamp) { logger.trace("New contents for {}", doc); + TextDocumentState file = getFile(Locations.toLoc(doc)); columns.clear(file.getLocation()); handleParsingErrors(file, file.update(doc.getVersion(), newContents, timestamp)); - return file; } private void handleParsingErrors(TextDocumentState file, CompletableFuture>> diagnosticsAsync) { @@ -535,8 +545,9 @@ public void didRenameFiles(RenameFilesParams params, List works private Map> bundleRenamesByContribution(List allRenames) { Map> bundled = new HashMap<>(); for (FileRename rename : allRenames) { - String language = registeredExtensions.get(extension(URIUtil.assumeCorrectLocation(rename.getNewUri()))); - if (language != null) { + var l = URIUtil.assumeCorrectLocation(rename.getNewUri()); + if (isLanguageRegistered(l)) { + var language = language(l); ILanguageContributions contrib = contributions.get(language); if (contrib != null) { bundled.computeIfAbsent(contrib, k -> new ArrayList<>()).add(rename); @@ -611,17 +622,26 @@ private static T last(List l) { return l.get(l.size() - 1); } - private ILanguageContributions contributions(ISourceLocation doc) { - String language = registeredExtensions.get(extension(doc)); - if (language != null) { - ILanguageContributions contrib = contributions.get(language); + private boolean isLanguageRegistered(ISourceLocation loc) { + return registeredExtensions.containsKey(extension(loc)); + } - if (contrib != null) { - return contrib; - } - } + private Optional safeLanguage(ISourceLocation loc) { + return Optional.ofNullable(registeredExtensions.get(extension(loc))); + } - throw new UnsupportedOperationException("Rascal Parametric LSP has no support for this file: " + doc); + private String language(ISourceLocation loc) { + return safeLanguage(loc).orElseThrow(() -> + new UnsupportedOperationException(String.format("Rascal Parametric LSP has no support for this file, since no language is registered for extension '%s': %s", extension(loc), loc)) + ); + } + + private ILanguageContributions contributions(ISourceLocation doc) { + return safeLanguage(doc) + .map(contributions::get) + .map(ILanguageContributions.class::cast) + .flatMap(Optional::ofNullable) + .orElseGet(() -> new NoContributions(extension(doc))); } private static String extension(ISourceLocation doc) { @@ -629,26 +649,24 @@ private static String extension(ISourceLocation doc) { } private ParametricFileFacts facts(ISourceLocation doc) { - String language = registeredExtensions.get(extension(doc)); - if (language != null) { - ParametricFileFacts fact = facts.get(language); - if (fact != null) { - return fact; - } + ParametricFileFacts fact = facts.get(language(doc)); + + if (fact == null) { + throw new ResponseErrorException(unknownFileError(doc, doc)); } - throw new UnsupportedOperationException("Rascal Parametric LSP has no support for this file: " + doc); + + return fact; } private TextDocumentState open(TextDocumentItem doc, long timestamp) { return files.computeIfAbsent(Locations.toLoc(doc), - l -> new TextDocumentState(contributions(l)::parsing, l, doc.getVersion(), doc.getText(), timestamp) - ); + l -> new TextDocumentState(contributions(l)::parsing, l, doc.getVersion(), doc.getText(), timestamp)); } private TextDocumentState getFile(ISourceLocation loc) { TextDocumentState file = files.get(loc); if (file == null) { - throw new ResponseErrorException(new ResponseError(ResponseErrorCode.RequestFailed, "Unknown file: " + loc, loc)); + throw new ResponseErrorException(unknownFileError(loc, loc)); } return file; } @@ -832,10 +850,6 @@ public CompletableFuture> selectionRange(SelectionRangePara public synchronized void registerLanguage(LanguageParameter lang) { logger.info("registerLanguage({})", lang.getName()); - for (var extension: lang.getExtensions()) { - this.registeredExtensions.put(extension, lang.getName()); - } - var multiplexer = contributions.computeIfAbsent(lang.getName(), t -> new LanguageContributionsMultiplexer(lang.getName(), ownExecuter) ); @@ -866,6 +880,32 @@ public synchronized void registerLanguage(LanguageParameter lang) { fact.reloadContributions(); fact.setClient(clientCopy); + + for (var extension: lang.getExtensions()) { + this.registeredExtensions.put(extension, lang.getName()); + } + + // If we opened any files with this extension before, now associate them with contributions + var extensions = Arrays.asList(lang.getExtensions()); + for (var f : files.keySet()) { + if (extensions.contains(extension(f))) { + updateFileState(lang, f); + } + } + } + + private void updateFileState(LanguageParameter lang, ISourceLocation f) { + logger.trace("File of language {} - updating state: {}", lang.getName(), f); + // Since we cannot know what happened to this file before we were called, we need to be careful about races. + // It might have been closed in the meantime, so we compute the new value if the key still exists, based on the current value. + var state = files.computeIfPresent(f, (loc, currentState) -> currentState.changeParser(contributions(loc)::parsing)); + if (state == null) { + logger.debug("Updating the parser of {} failed, since it was closed.", f); + return; + } + // Update open editor + handleParsingErrors(state, state.getCurrentDiagnosticsAsync()); + triggerAnalyzer(f, state.getCurrentContent().version(), NORMAL_DEBOUNCE); } private static String buildContributionKey(LanguageParameter lang) { @@ -939,4 +979,8 @@ public void cancelProgress(String progressId) { contributions.values().forEach(plex -> plex.cancelProgress(progressId)); } + + private ResponseError unknownFileError(ISourceLocation loc, Object data) { + return new ResponseError(ResponseErrorCode.RequestFailed, "Unknown file: " + loc, data); + } } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/terminal/ITerminalIDEServer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/terminal/ITerminalIDEServer.java index e09bdd054..705e0ae8c 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/terminal/ITerminalIDEServer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/terminal/ITerminalIDEServer.java @@ -36,7 +36,6 @@ import java.util.Base64.Decoder; import java.util.Base64.Encoder; import java.util.concurrent.CompletableFuture; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.jsonrpc.services.JsonNotification; @@ -44,8 +43,8 @@ import org.rascalmpl.values.IRascalValueFactory; import io.usethesource.vallang.IConstructor; import io.usethesource.vallang.IList; -import io.usethesource.vallang.ISet; import io.usethesource.vallang.IMap; +import io.usethesource.vallang.ISet; import io.usethesource.vallang.ISourceLocation; import io.usethesource.vallang.IString; import io.usethesource.vallang.IValue; diff --git a/rascal-vscode-extension/src/lsp/ParameterizedLanguageServer.ts b/rascal-vscode-extension/src/lsp/ParameterizedLanguageServer.ts index cc951abf2..8d6147b22 100644 --- a/rascal-vscode-extension/src/lsp/ParameterizedLanguageServer.ts +++ b/rascal-vscode-extension/src/lsp/ParameterizedLanguageServer.ts @@ -102,6 +102,8 @@ export class ParameterizedLanguageServer implements vscode.Disposable { for (const editor of vscode.window.visibleTextEditors) { const ext = path.extname(editor.document.uri.path); if (ext !== "" && lang.extensions.includes(ext.substring(1))) { + // (Re)set the language ID to re-trigger contribution requests + await vscode.languages.setTextDocumentLanguage(editor.document, "plaintext"); vscode.languages.setTextDocumentLanguage(editor.document, this.languageId); } }