diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ILanguageContributions.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ILanguageContributions.java index d3df7a242..256593af6 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ILanguageContributions.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ILanguageContributions.java @@ -46,49 +46,50 @@ public interface ILanguageContributions { public String getName(); - public CompletableFuture parseSourceFile(ISourceLocation loc, String input); - public InterruptibleFuture outline(ITree input); - public InterruptibleFuture analyze(ISourceLocation loc, ITree input); + public CompletableFuture parsing(ISourceLocation loc, String input); + public InterruptibleFuture analysis(ISourceLocation loc, ITree input); public InterruptibleFuture build(ISourceLocation loc, ITree input); - public InterruptibleFuture lenses(ITree input); - public InterruptibleFuture<@Nullable IValue> executeCommand(String command); - public CompletableFuture parseCodeActions(String command); + public InterruptibleFuture documentSymbol(ITree input); + public InterruptibleFuture codeLens(ITree input); public InterruptibleFuture inlayHint(@Nullable ITree input); - public InterruptibleFuture documentation(IList focus); - public InterruptibleFuture definitions(IList focus); + public InterruptibleFuture<@Nullable IValue> execution(String command); + public InterruptibleFuture hover(IList focus); + public InterruptibleFuture definition(IList focus); public InterruptibleFuture references(IList focus); - public InterruptibleFuture implementations(IList focus); - public InterruptibleFuture codeActions(IList focus); - - public CompletableFuture hasAnalyzer(); - public CompletableFuture hasBuilder(); - public CompletableFuture hasOutliner(); - public CompletableFuture hasLensDetector(); - public CompletableFuture hasInlayHinter(); - public CompletableFuture hasCommandExecutor(); - public CompletableFuture hasDocumenter(); - public CompletableFuture hasDefiner(); - public CompletableFuture hasReferrer(); - public CompletableFuture hasImplementer(); - public CompletableFuture hasCodeActionsContributor(); + public InterruptibleFuture implementation(IList focus); + public InterruptibleFuture codeAction(IList focus); + + public CompletableFuture parseCodeActions(String command); + + public CompletableFuture hasAnalysis(); + public CompletableFuture hasBuild(); + public CompletableFuture hasDocumentSymbol(); + public CompletableFuture hasCodeLens(); + public CompletableFuture hasInlayHint(); + public CompletableFuture hasExecution(); + public CompletableFuture hasHover(); + public CompletableFuture hasDefinition(); + public CompletableFuture hasReferences(); + public CompletableFuture hasImplementation(); + public CompletableFuture hasCodeAction(); public CompletableFuture getAnalyzerSummaryConfig(); public CompletableFuture getBuilderSummaryConfig(); public CompletableFuture getOndemandSummaryConfig(); public static class SummaryConfig { - public final boolean providesDocumentation; + public final boolean providesHovers; public final boolean providesDefinitions; public final boolean providesReferences; public final boolean providesImplementations; public SummaryConfig( - boolean providesDocumentation, + boolean providesHovers, boolean providesDefinitions, boolean providesReferences, boolean providesImplementations) { - this.providesDocumentation = providesDocumentation; + this.providesHovers = providesHovers; this.providesDefinitions = providesDefinitions; this.providesReferences = providesReferences; this.providesImplementations = providesImplementations; @@ -98,14 +99,13 @@ public SummaryConfig( public static SummaryConfig or(SummaryConfig a, SummaryConfig b) { return new SummaryConfig( - a.providesDocumentation || b.providesDocumentation, + a.providesHovers || b.providesHovers, a.providesDefinitions || b.providesDefinitions, a.providesReferences || b.providesReferences, a.providesImplementations || b.providesImplementations); } } - @FunctionalInterface // Type alias to conveniently pass methods `analyze`and `build` as parameters public static interface ScheduledCalculator extends BiFunction> {} diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/InterpretedLanguageContributions.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/InterpretedLanguageContributions.java index 034c91034..14cc02637 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/InterpretedLanguageContributions.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/InterpretedLanguageContributions.java @@ -74,30 +74,31 @@ public class InterpretedLanguageContributions implements ILanguageContributions private final CompletableFuture eval; private final CompletableFuture store; - private final CompletableFuture parser; - private final CompletableFuture<@Nullable IFunction> outliner; - private final CompletableFuture<@Nullable IFunction> analyzer; - private final CompletableFuture<@Nullable IFunction> builder; - private final CompletableFuture<@Nullable IFunction> lenses; - private final CompletableFuture<@Nullable IFunction> commandExecutor; - private final CompletableFuture<@Nullable IFunction> inlayHinter; - private final CompletableFuture<@Nullable IFunction> documenter; - private final CompletableFuture<@Nullable IFunction> definer; - private final CompletableFuture<@Nullable IFunction> referrer; - private final CompletableFuture<@Nullable IFunction> implementer; - private final CompletableFuture<@Nullable IFunction> codeActionContributor; - - private final CompletableFuture hasOutliner; - private final CompletableFuture hasAnalyzer; - private final CompletableFuture hasBuilder; - private final CompletableFuture hasLensDetector; - private final CompletableFuture hasCommandExecutor; - private final CompletableFuture hasInlayHinter; - private final CompletableFuture hasDocumenter; - private final CompletableFuture hasDefiner; - private final CompletableFuture hasReferrer; - private final CompletableFuture hasImplementer; - private final CompletableFuture hasCodeActionContributor; + + private final CompletableFuture parsing; + private final CompletableFuture<@Nullable IFunction> analysis; + private final CompletableFuture<@Nullable IFunction> build; + private final CompletableFuture<@Nullable IFunction> documentSymbol; + private final CompletableFuture<@Nullable IFunction> codeLens; + private final CompletableFuture<@Nullable IFunction> inlayHint; + private final CompletableFuture<@Nullable IFunction> execution; + private final CompletableFuture<@Nullable IFunction> hover; + private final CompletableFuture<@Nullable IFunction> definition; + private final CompletableFuture<@Nullable IFunction> references; + private final CompletableFuture<@Nullable IFunction> implementation; + private final CompletableFuture<@Nullable IFunction> codeAction; + + private final CompletableFuture hasAnalysis; + private final CompletableFuture hasBuild; + private final CompletableFuture hasDocumentSymbol; + private final CompletableFuture hasCodeLens; + private final CompletableFuture hasInlayHint; + private final CompletableFuture hasExecution; + private final CompletableFuture hasHover; + private final CompletableFuture hasDefinition; + private final CompletableFuture hasReferences; + private final CompletableFuture hasImplementation; + private final CompletableFuture hasCodeAction; private final CompletableFuture analyzerSummaryConfig; private final CompletableFuture builderSummaryConfig; @@ -121,35 +122,37 @@ public InterpretedLanguageContributions(LanguageParameter lang, IBaseTextDocumen e -> loadContributions(e, lang), ValueFactoryFactory.getValueFactory().set(), exec, true, client).get(); + this.store = eval.thenApply(e -> ((ModuleEnvironment)e.getModule(mainModule)).getStore()); - this.parser = getFunctionFor(contributions, LanguageContributions.PARSER); - this.outliner = getFunctionFor(contributions, LanguageContributions.OUTLINER); - this.analyzer = getFunctionFor(contributions, LanguageContributions.ANALYZER); - this.builder = getFunctionFor(contributions, LanguageContributions.BUILDER); - this.lenses = getFunctionFor(contributions, LanguageContributions.LENS_DETECTOR); - this.commandExecutor = getFunctionFor(contributions, LanguageContributions.COMMAND_EXECUTOR); - this.inlayHinter = getFunctionFor(contributions, LanguageContributions.INLAY_HINTER); - this.documenter = getFunctionFor(contributions, LanguageContributions.DOCUMENTER); - this.definer = getFunctionFor(contributions, LanguageContributions.DEFINER); - this.referrer = getFunctionFor(contributions, LanguageContributions.REFERRER); - this.implementer = getFunctionFor(contributions, LanguageContributions.IMPLEMENTER); - this.codeActionContributor = getFunctionFor(contributions, LanguageContributions.CODE_ACTION_CONTRIBUTOR); + + this.parsing = getFunctionFor(contributions, LanguageContributions.PARSING); + this.analysis = getFunctionFor(contributions, LanguageContributions.ANALYSIS); + this.build = getFunctionFor(contributions, LanguageContributions.BUILD); + this.documentSymbol = getFunctionFor(contributions, LanguageContributions.DOCUMENT_SYMBOL); + this.codeLens = getFunctionFor(contributions, LanguageContributions.CODE_LENS); + this.inlayHint = getFunctionFor(contributions, LanguageContributions.INLAY_HINT); + this.execution = getFunctionFor(contributions, LanguageContributions.EXECUTION); + this.hover = getFunctionFor(contributions, LanguageContributions.HOVER); + this.definition = getFunctionFor(contributions, LanguageContributions.DEFINITION); + this.references = getFunctionFor(contributions, LanguageContributions.REFERENCES); + this.implementation = getFunctionFor(contributions, LanguageContributions.IMPLEMENTATION); + this.codeAction = getFunctionFor(contributions, LanguageContributions.CODE_ACTION); // assign boolean properties once instead of wasting futures all the time - this.hasOutliner = nonNull(this.outliner); - this.hasAnalyzer = nonNull(this.analyzer); - this.hasBuilder = nonNull(this.builder); - this.hasLensDetector = nonNull(this.lenses); - this.hasCommandExecutor = nonNull(this.commandExecutor); - this.hasInlayHinter = nonNull(this.inlayHinter); - this.hasDocumenter = nonNull(this.documenter); - this.hasDefiner = nonNull(this.definer); - this.hasReferrer = nonNull(this.referrer); - this.hasImplementer = nonNull(this.implementer); - this.hasCodeActionContributor = nonNull(this.codeActionContributor); - - this.analyzerSummaryConfig = scheduledSummaryConfig(contributions, LanguageContributions.ANALYZER); - this.builderSummaryConfig = scheduledSummaryConfig(contributions, LanguageContributions.BUILDER); + this.hasAnalysis = nonNull(this.analysis); + this.hasBuild = nonNull(this.build); + this.hasDocumentSymbol = nonNull(this.documentSymbol); + this.hasCodeLens = nonNull(this.codeLens); + this.hasInlayHint = nonNull(this.inlayHint); + this.hasExecution = nonNull(this.execution); + this.hasHover = nonNull(this.hover); + this.hasDefinition = nonNull(this.definition); + this.hasReferences = nonNull(this.references); + this.hasImplementation = nonNull(this.implementation); + this.hasCodeAction = nonNull(this.codeAction); + + this.analyzerSummaryConfig = scheduledSummaryConfig(contributions, LanguageContributions.ANALYSIS); + this.builderSummaryConfig = scheduledSummaryConfig(contributions, LanguageContributions.BUILD); this.ondemandSummaryConfig = ondemandSummaryConfig(contributions); } catch (IOException e1) { @@ -167,7 +170,7 @@ private static CompletableFuture scheduledSummaryConfig(Completab var constructor = getContribution(c, summarizer); if (constructor != null) { return new SummaryConfig( - isTrue(constructor, LanguageContributions.Summarizers.PROVIDES_DOCUMENTATION), + isTrue(constructor, LanguageContributions.Summarizers.PROVIDES_HOVERS), isTrue(constructor, LanguageContributions.Summarizers.PROVIDES_DEFINITIONS), isTrue(constructor, LanguageContributions.Summarizers.PROVIDES_REFERENCES), isTrue(constructor, LanguageContributions.Summarizers.PROVIDES_IMPLEMENTATIONS)); @@ -180,10 +183,10 @@ private static CompletableFuture scheduledSummaryConfig(Completab private static CompletableFuture ondemandSummaryConfig(CompletableFuture contributions) { return contributions.thenApply(c -> new SummaryConfig( - hasContribution(c, LanguageContributions.DOCUMENTER), - hasContribution(c, LanguageContributions.DEFINER), - hasContribution(c, LanguageContributions.REFERRER), - hasContribution(c, LanguageContributions.IMPLEMENTER))); + hasContribution(c, LanguageContributions.HOVER), + hasContribution(c, LanguageContributions.DEFINITION), + hasContribution(c, LanguageContributions.REFERENCES), + hasContribution(c, LanguageContributions.IMPLEMENTATION))); } private static @Nullable IConstructor getContribution(ISet contributions, String name) { @@ -257,69 +260,69 @@ public String getName() { } @Override - public CompletableFuture parseSourceFile(ISourceLocation loc, String input) { - debug(LanguageContributions.PARSER, loc, input); - return parser.thenApplyAsync(p -> p.call(VF.string(input), loc), exec); + public CompletableFuture parsing(ISourceLocation loc, String input) { + debug(LanguageContributions.PARSING, loc, input); + return parsing.thenApplyAsync(p -> p.call(VF.string(input), loc), exec); } @Override - public InterruptibleFuture outline(ITree input) { - debug(LanguageContributions.OUTLINER, TreeAdapter.getLocation(input)); - return execFunction(LanguageContributions.OUTLINER, outliner, VF.list(), input); + public InterruptibleFuture documentSymbol(ITree input) { + debug(LanguageContributions.DOCUMENT_SYMBOL, TreeAdapter.getLocation(input)); + return execFunction(LanguageContributions.DOCUMENT_SYMBOL, documentSymbol, VF.list(), input); } @Override - public InterruptibleFuture analyze(ISourceLocation src, ITree input) { - debug(LanguageContributions.ANALYZER, src); - return execFunction(LanguageContributions.ANALYZER, analyzer, EmptySummary.newInstance(src), src, input); + public InterruptibleFuture analysis(ISourceLocation src, ITree input) { + debug(LanguageContributions.ANALYSIS, src); + return execFunction(LanguageContributions.ANALYSIS, analysis, EmptySummary.newInstance(src), src, input); } @Override public InterruptibleFuture build(ISourceLocation src, ITree input) { - debug(LanguageContributions.BUILDER, src); - return execFunction(LanguageContributions.BUILDER, builder, EmptySummary.newInstance(src), src, input); + debug(LanguageContributions.BUILD, src); + return execFunction(LanguageContributions.BUILD, build, EmptySummary.newInstance(src), src, input); } @Override - public InterruptibleFuture lenses(ITree input) { - debug(LanguageContributions.LENS_DETECTOR, TreeAdapter.getLocation(input)); - return execFunction(LanguageContributions.LENS_DETECTOR, lenses, VF.list(), input); + public InterruptibleFuture codeLens(ITree input) { + debug(LanguageContributions.CODE_LENS, TreeAdapter.getLocation(input)); + return execFunction(LanguageContributions.CODE_LENS, codeLens, VF.list(), input); } @Override public InterruptibleFuture inlayHint(@Nullable ITree input) { - debug(LanguageContributions.INLAY_HINTER, input != null ? TreeAdapter.getLocation(input) : null); - return execFunction(LanguageContributions.INLAY_HINTER, inlayHinter, VF.list(), input); + debug(LanguageContributions.INLAY_HINT, input != null ? TreeAdapter.getLocation(input) : null); + return execFunction(LanguageContributions.INLAY_HINT, inlayHint, VF.list(), input); } @Override - public InterruptibleFuture documentation(IList focus) { - debug(LanguageContributions.DOCUMENTER, focus.length()); - return execFunction(LanguageContributions.DOCUMENTER, documenter, VF.set(), focus); + public InterruptibleFuture hover(IList focus) { + debug(LanguageContributions.HOVER, focus.length()); + return execFunction(LanguageContributions.HOVER, hover, VF.set(), focus); } @Override - public InterruptibleFuture definitions(IList focus) { - debug(LanguageContributions.DEFINER, focus.length()); - return execFunction(LanguageContributions.DEFINER, definer, VF.set(), focus); + public InterruptibleFuture definition(IList focus) { + debug(LanguageContributions.DEFINITION, focus.length()); + return execFunction(LanguageContributions.DEFINITION, definition, VF.set(), focus); } @Override - public InterruptibleFuture implementations(IList focus) { - debug(LanguageContributions.IMPLEMENTER, focus.length()); - return execFunction(LanguageContributions.IMPLEMENTER, implementer, VF.set(), focus); + public InterruptibleFuture implementation(IList focus) { + debug(LanguageContributions.IMPLEMENTATION, focus.length()); + return execFunction(LanguageContributions.IMPLEMENTATION, implementation, VF.set(), focus); } @Override public InterruptibleFuture references(IList focus) { - debug(LanguageContributions.REFERRER, focus.length()); - return execFunction(LanguageContributions.REFERRER, referrer, VF.set(), focus); + debug(LanguageContributions.REFERENCES, focus.length()); + return execFunction(LanguageContributions.REFERENCES, references, VF.set(), focus); } @Override - public InterruptibleFuture codeActions(IList focus) { - debug(LanguageContributions.CODE_ACTION_CONTRIBUTOR, focus.length()); - return execFunction(LanguageContributions.CODE_ACTION_CONTRIBUTOR, codeActionContributor, VF.list(), focus); + public InterruptibleFuture codeAction(IList focus) { + debug(LanguageContributions.CODE_ACTION, focus.length()); + return execFunction(LanguageContributions.CODE_ACTION, codeAction, VF.list(), focus); } private void debug(String name, Object param) { @@ -331,58 +334,58 @@ private void debug(String name, Object param1, Object param2) { } @Override - public CompletableFuture hasDefiner() { - return hasDefiner; + public CompletableFuture hasDefinition() { + return hasDefinition; } @Override - public CompletableFuture hasReferrer() { - return hasReferrer; + public CompletableFuture hasReferences() { + return hasReferences; } @Override - public CompletableFuture hasImplementer() { - return hasImplementer; + public CompletableFuture hasImplementation() { + return hasImplementation; } @Override - public CompletableFuture hasDocumenter() { - return hasDocumenter; + public CompletableFuture hasHover() { + return hasHover; } @Override - public CompletableFuture hasCommandExecutor() { - return hasCommandExecutor; + public CompletableFuture hasExecution() { + return hasExecution; } @Override - public CompletableFuture hasInlayHinter() { - return hasInlayHinter; + public CompletableFuture hasInlayHint() { + return hasInlayHint; } @Override - public CompletableFuture hasLensDetector() { - return hasLensDetector; + public CompletableFuture hasCodeLens() { + return hasCodeLens; } @Override - public CompletableFuture hasOutliner() { - return hasOutliner; + public CompletableFuture hasDocumentSymbol() { + return hasDocumentSymbol; } @Override - public CompletableFuture hasCodeActionsContributor() { - return hasCodeActionContributor; + public CompletableFuture hasCodeAction() { + return hasCodeAction; } @Override - public CompletableFuture hasAnalyzer() { - return hasAnalyzer; + public CompletableFuture hasAnalysis() { + return hasAnalysis; } @Override - public CompletableFuture hasBuilder() { - return hasBuilder; + public CompletableFuture hasBuild() { + return hasBuild; } @Override @@ -401,12 +404,12 @@ public CompletableFuture getOndemandSummaryConfig() { } @Override - public InterruptibleFuture<@Nullable IValue> executeCommand(String command) { + public InterruptibleFuture<@Nullable IValue> execution(String command) { logger.debug("executeCommand({}...) (full command value in TRACE level)", () -> command.substring(0, Math.min(10, command.length()))); logger.trace("Full command: {}", command); return InterruptibleFuture.flatten(parseCommand(command).thenCombine( - commandExecutor, + execution, (cons, func) -> { if (func == null) { diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java index 1dc864736..8fb52e459 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java @@ -51,31 +51,31 @@ private static final CompletableFuture failedInitialization() { return CompletableFuture.failedFuture(new RuntimeException("No contributions registered")); } - private volatile @MonotonicNonNull ILanguageContributions parser = null; - private volatile CompletableFuture outliner = failedInitialization(); - private volatile CompletableFuture analyzer = failedInitialization(); - private volatile CompletableFuture builder = failedInitialization(); - private volatile CompletableFuture lensDetector = failedInitialization(); - private volatile CompletableFuture commandExecutor = failedInitialization(); - private volatile CompletableFuture inlayHinter = failedInitialization(); - private volatile CompletableFuture definer = failedInitialization(); - private volatile CompletableFuture documenter = failedInitialization(); - private volatile CompletableFuture referrer = failedInitialization(); - private volatile CompletableFuture implementer = failedInitialization(); - private volatile CompletableFuture codeActionContributor = failedInitialization(); - - private volatile CompletableFuture hasDocumenter = failedInitialization(); - private volatile CompletableFuture hasDefiner = failedInitialization(); - private volatile CompletableFuture hasReferrer = failedInitialization(); - private volatile CompletableFuture hasImplementer = failedInitialization(); - - private volatile CompletableFuture hasOutliner = failedInitialization(); - private volatile CompletableFuture hasAnalyzer = failedInitialization(); - private volatile CompletableFuture hasBuilder = failedInitialization(); - private volatile CompletableFuture hasLensDetector = failedInitialization(); - private volatile CompletableFuture hasCommandExecutor = failedInitialization(); - private volatile CompletableFuture hasInlayHinter = failedInitialization(); - private volatile CompletableFuture hasCodeActionContributor = failedInitialization(); + private volatile @MonotonicNonNull ILanguageContributions parsing = null; + + private volatile CompletableFuture analysis = failedInitialization(); + private volatile CompletableFuture build = failedInitialization(); + private volatile CompletableFuture documentSymbol = failedInitialization(); + private volatile CompletableFuture codeLens = failedInitialization(); + private volatile CompletableFuture inlayHint = failedInitialization(); + private volatile CompletableFuture execution = failedInitialization(); + private volatile CompletableFuture hover = failedInitialization(); + private volatile CompletableFuture definition = failedInitialization(); + private volatile CompletableFuture references = failedInitialization(); + private volatile CompletableFuture implementation = failedInitialization(); + private volatile CompletableFuture codeAction = failedInitialization(); + + private volatile CompletableFuture hasAnalysis = failedInitialization(); + private volatile CompletableFuture hasBuild = failedInitialization(); + private volatile CompletableFuture hasDocumentSymbol = failedInitialization(); + private volatile CompletableFuture hasCodeLens = failedInitialization(); + private volatile CompletableFuture hasInlayHint = failedInitialization(); + private volatile CompletableFuture hasExecution = failedInitialization(); + private volatile CompletableFuture hasHover = failedInitialization(); + private volatile CompletableFuture hasDefinition = failedInitialization(); + private volatile CompletableFuture hasReferences = failedInitialization(); + private volatile CompletableFuture hasImplementation = failedInitialization(); + private volatile CompletableFuture hasCodeAction = failedInitialization(); private volatile CompletableFuture analyzerSummaryConfig; private volatile CompletableFuture builderSummaryConfig; @@ -134,30 +134,30 @@ private synchronized void calculateRouting() { // this is to avoid doing this lookup every time we get a request // we calculate the "route" once, and then just chain onto the completed // future - parser = firstOrFail(); - outliner = findFirstOrDefault(ILanguageContributions::hasOutliner); - analyzer = findFirstOrDefault(ILanguageContributions::hasAnalyzer); - builder = findFirstOrDefault(ILanguageContributions::hasBuilder); - lensDetector = findFirstOrDefault(ILanguageContributions::hasLensDetector); - commandExecutor = findFirstOrDefault(ILanguageContributions::hasCommandExecutor); - inlayHinter = findFirstOrDefault(ILanguageContributions::hasInlayHinter); - definer = findFirstOrDefault(ILanguageContributions::hasDefiner); - documenter = findFirstOrDefault(ILanguageContributions::hasDocumenter); - referrer = findFirstOrDefault(ILanguageContributions::hasReferrer); - implementer = findFirstOrDefault(ILanguageContributions::hasImplementer); - codeActionContributor = findFirstOrDefault(ILanguageContributions::hasCodeActionsContributor); - - hasDocumenter = anyTrue(ILanguageContributions::hasDocumenter); - hasDefiner = anyTrue(ILanguageContributions::hasDefiner); - hasReferrer = anyTrue(ILanguageContributions::hasReferrer); - hasImplementer = anyTrue(ILanguageContributions::hasImplementer); - - hasOutliner = anyTrue(ILanguageContributions::hasOutliner); - hasAnalyzer = anyTrue(ILanguageContributions::hasAnalyzer); - hasBuilder = anyTrue(ILanguageContributions::hasBuilder); - hasLensDetector = anyTrue(ILanguageContributions::hasLensDetector); - hasCommandExecutor = anyTrue(ILanguageContributions::hasCommandExecutor); - hasInlayHinter = anyTrue(ILanguageContributions::hasInlayHinter); + parsing = firstOrFail(); + + analysis = findFirstOrDefault(ILanguageContributions::hasAnalysis); + build = findFirstOrDefault(ILanguageContributions::hasBuild); + documentSymbol = findFirstOrDefault(ILanguageContributions::hasDocumentSymbol); + codeLens = findFirstOrDefault(ILanguageContributions::hasCodeLens); + inlayHint = findFirstOrDefault(ILanguageContributions::hasInlayHint); + execution = findFirstOrDefault(ILanguageContributions::hasExecution); + hover = findFirstOrDefault(ILanguageContributions::hasHover); + definition = findFirstOrDefault(ILanguageContributions::hasDefinition); + references = findFirstOrDefault(ILanguageContributions::hasReferences); + implementation = findFirstOrDefault(ILanguageContributions::hasImplementation); + codeAction = findFirstOrDefault(ILanguageContributions::hasCodeAction); + + hasAnalysis = anyTrue(ILanguageContributions::hasAnalysis); + hasBuild = anyTrue(ILanguageContributions::hasBuild); + hasDocumentSymbol = anyTrue(ILanguageContributions::hasDocumentSymbol); + hasCodeLens = anyTrue(ILanguageContributions::hasCodeLens); + hasInlayHint = anyTrue(ILanguageContributions::hasInlayHint); + hasExecution = anyTrue(ILanguageContributions::hasExecution); + hasHover = anyTrue(ILanguageContributions::hasHover); + hasDefinition = anyTrue(ILanguageContributions::hasDefinition); + hasReferences = anyTrue(ILanguageContributions::hasReferences); + hasImplementation = anyTrue(ILanguageContributions::hasImplementation); analyzerSummaryConfig = anyTrue(ILanguageContributions::getAnalyzerSummaryConfig, SummaryConfig.FALSY, SummaryConfig::or); builderSummaryConfig = anyTrue(ILanguageContributions::getBuilderSummaryConfig, SummaryConfig.FALSY, SummaryConfig::or); @@ -172,8 +172,6 @@ private ILanguageContributions firstOrFail() { return it.next().contrib; } - - private CompletableFuture findFirstOrDefault(Function> filter) { return CompletableFuture.supplyAsync(() -> { for (var c : contributions) { @@ -218,132 +216,131 @@ public String getName() { } @Override - public CompletableFuture parseSourceFile(ISourceLocation loc, String input) { - var p = parser; + public CompletableFuture parsing(ISourceLocation loc, String input) { + var p = parsing; if (p == null) { return failedInitialization(); } - return p.parseSourceFile(loc, input); + return p.parsing(loc, input); } - private InterruptibleFuture flatten(CompletableFuture target, Function> call) { return InterruptibleFuture.flatten(target.thenApply(call), ownExecuter); } @Override - public InterruptibleFuture outline(ITree input) { - return flatten(outliner, c -> c.outline(input)); + public InterruptibleFuture documentSymbol(ITree input) { + return flatten(documentSymbol, c -> c.documentSymbol(input)); } @Override - public InterruptibleFuture analyze(ISourceLocation loc, ITree input) { - return flatten(analyzer, c -> c.analyze(loc, input)); + public InterruptibleFuture analysis(ISourceLocation loc, ITree input) { + return flatten(analysis, c -> c.analysis(loc, input)); } @Override public InterruptibleFuture build(ISourceLocation loc, ITree input) { - return flatten(builder, c -> c.build(loc, input)); + return flatten(build, c -> c.build(loc, input)); } @Override - public InterruptibleFuture lenses(ITree input) { - return flatten(lensDetector, c -> c.lenses(input)); + public InterruptibleFuture codeLens(ITree input) { + return flatten(codeLens, c -> c.codeLens(input)); } @Override - public InterruptibleFuture<@Nullable IValue> executeCommand(String command) { - return flatten(commandExecutor, c -> c.executeCommand(command)); + public InterruptibleFuture<@Nullable IValue> execution(String command) { + return flatten(execution, c -> c.execution(command)); } @Override public CompletableFuture parseCodeActions(String command) { - return commandExecutor.thenApply(c -> c.parseCodeActions(command)).thenCompose(Function.identity()); + return execution.thenApply(c -> c.parseCodeActions(command)).thenCompose(Function.identity()); } @Override public InterruptibleFuture inlayHint(@Nullable ITree input) { - return flatten(inlayHinter, c -> c.inlayHint(input)); + return flatten(inlayHint, c -> c.inlayHint(input)); } @Override - public InterruptibleFuture documentation(IList focus) { - return flatten(documenter, c -> c.documentation(focus)); + public InterruptibleFuture hover(IList focus) { + return flatten(hover, c -> c.hover(focus)); } @Override - public InterruptibleFuture definitions(IList focus) { - return flatten(definer, c -> c.definitions(focus)); + public InterruptibleFuture definition(IList focus) { + return flatten(definition, c -> c.definition(focus)); } @Override public InterruptibleFuture references(IList focus) { - return flatten(referrer, c -> c.references(focus)); + return flatten(references, c -> c.references(focus)); } @Override - public InterruptibleFuture implementations(IList focus) { - return flatten(implementer, c -> c.implementations(focus)); + public InterruptibleFuture implementation(IList focus) { + return flatten(implementation, c -> c.implementation(focus)); } @Override - public InterruptibleFuture codeActions(IList focus) { - return flatten(codeActionContributor, c -> c.codeActions(focus)); + public InterruptibleFuture codeAction(IList focus) { + return flatten(codeAction, c -> c.codeAction(focus)); } @Override - public CompletableFuture hasCodeActionsContributor() { - return hasCodeActionContributor; + public CompletableFuture hasCodeAction() { + return hasCodeAction; } @Override - public CompletableFuture hasDocumenter() { - return hasDocumenter; + public CompletableFuture hasHover() { + return hasHover; } @Override - public CompletableFuture hasDefiner() { - return hasDefiner; + public CompletableFuture hasDefinition() { + return hasDefinition; } @Override - public CompletableFuture hasReferrer() { - return hasReferrer; + public CompletableFuture hasReferences() { + return hasReferences; } @Override - public CompletableFuture hasImplementer() { - return hasImplementer; + public CompletableFuture hasImplementation() { + return hasImplementation; } @Override - public CompletableFuture hasOutliner() { - return hasOutliner; + public CompletableFuture hasDocumentSymbol() { + return hasDocumentSymbol; } @Override - public CompletableFuture hasAnalyzer() { - return hasAnalyzer; + public CompletableFuture hasAnalysis() { + return hasAnalysis; } @Override - public CompletableFuture hasBuilder() { - return hasBuilder; + public CompletableFuture hasBuild() { + return hasBuild; } @Override - public CompletableFuture hasLensDetector() { - return hasLensDetector; + public CompletableFuture hasCodeLens() { + return hasCodeLens; } @Override - public CompletableFuture hasCommandExecutor() { - return hasCommandExecutor; + public CompletableFuture hasExecution() { + return hasExecution; } @Override - public CompletableFuture hasInlayHinter() { - return hasInlayHinter; + public CompletableFuture hasInlayHint() { + return hasInlayHint; } @Override 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 ba5f53e55..83da701f2 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 @@ -114,7 +114,7 @@ import org.rascalmpl.vscode.lsp.util.Diagnostics; import org.rascalmpl.vscode.lsp.util.DocumentChanges; import org.rascalmpl.vscode.lsp.util.FoldingRanges; -import org.rascalmpl.vscode.lsp.util.Outline; +import org.rascalmpl.vscode.lsp.util.DocumentSymbols; import org.rascalmpl.vscode.lsp.util.SemanticTokenizer; import org.rascalmpl.vscode.lsp.util.Versioned; import org.rascalmpl.vscode.lsp.util.concurrent.InterruptibleFuture; @@ -328,7 +328,7 @@ public CompletableFuture> codeLens(CodeLensParams param return recoverExceptions(file.getCurrentTreeAsync() .thenApply(Versioned::get) - .thenApply(contrib::lenses) + .thenApply(contrib::codeLens) .thenCompose(InterruptibleFuture::get) .thenApply(s -> s.stream() .map(e -> locCommandTupleToCodeLense(contrib.getName(), e)) @@ -370,7 +370,6 @@ private InlayHint rowToInlayHint(IValue v) { var toolTip = (IString)t.asWithKeywordParameters().getParameter("toolTip"); var atEnd = (IBool)t.asWithKeywordParameters().getParameter("atEnd"); - // translate to lsp var result = new InlayHint(Locations.toPosition(loc, columns, atEnd.getValue()), Either.forLeft(label.trim())); result.setKind(kind.getName().equals("type") ? InlayHintKind.Type : InlayHintKind.Parameter); @@ -520,7 +519,7 @@ private ParametricFileFacts facts(String doc) { private TextDocumentState open(TextDocumentItem doc) { return files.computeIfAbsent(Locations.toLoc(doc), - l -> new TextDocumentState(contributions(doc)::parseSourceFile, l, doc.getVersion(), doc.getText()) + l -> new TextDocumentState(contributions(doc)::parsing, l, doc.getVersion(), doc.getText()) ); } @@ -571,15 +570,15 @@ public CompletableFuture semanticTokensRange(SemanticTokensRange @Override public CompletableFuture>>documentSymbol(DocumentSymbolParams params) { - logger.debug("Outline/documentSymbols: {}", params.getTextDocument()); + logger.debug("Outline/documentSymbol: {}", params.getTextDocument()); final TextDocumentState file = getFile(params.getTextDocument()); ILanguageContributions contrib = contributions(params.getTextDocument()); return recoverExceptions(file.getCurrentTreeAsync() .thenApply(Versioned::get) - .thenApply(contrib::outline) + .thenApply(contrib::documentSymbol) .thenCompose(InterruptibleFuture::get) - .thenApply(c -> Outline.buildOutline(c, columns.get(file.getLocation()))) + .thenApply(documentSymbols -> DocumentSymbols.toLSP(documentSymbols, columns.get(file.getLocation()))) , Collections::emptyList); } @@ -637,9 +636,9 @@ public CompletableFuture>> codeAction(CodeActio private CompletableFuture computeCodeActions(final ILanguageContributions contribs, final int startLine, final int startColumn, ITree tree) { IList focus = TreeSearch.computeFocusList(tree, startLine, startColumn); - + if (!focus.isEmpty()) { - return contribs.codeActions(focus).get(); + return contribs.codeAction(focus).get(); } else { logger.log(Level.DEBUG, "no tree focus found at {}:{}", startLine, startColumn); @@ -678,7 +677,7 @@ public CompletableFuture, List> references(ReferenceParams params) { - logger.debug("Implementation: {} at {}", params.getTextDocument(), params.getPosition()); + logger.debug("References: {} at {}", params.getTextDocument(), params.getPosition()); return recoverExceptions( lookup(ParametricSummary::references, params.getTextDocument(), params.getPosition()) .thenApply(l -> l) // hack to help compiler see type @@ -689,14 +688,14 @@ public CompletableFuture> references(ReferenceParams pa public CompletableFuture hover(HoverParams params) { logger.debug("Hover: {} at {}", params.getTextDocument(), params.getPosition()); return recoverExceptions( - lookup(ParametricSummary::documentation, params.getTextDocument(), params.getPosition()) + lookup(ParametricSummary::hovers, params.getTextDocument(), params.getPosition()) .thenApply(Hover::new) , () -> null); } @Override public CompletableFuture> foldingRange(FoldingRangeRequestParams params) { - logger.debug("textDocument/foldingRange: {}", params.getTextDocument()); + logger.debug("Folding range: {}", params.getTextDocument()); TextDocumentState file = getFile(params.getTextDocument()); return recoverExceptions(file.getCurrentTreeAsync().thenApply(Versioned::get).thenApplyAsync(FoldingRanges::getFoldingRanges) .whenComplete((r, e) -> @@ -778,12 +777,11 @@ public CompletableFuture executeCommand(String languageName, String comm ILanguageContributions contribs = contributions.get(languageName); if (contribs != null) { - return contribs.executeCommand(command).get(); + return contribs.execution(command).get(); } else { logger.warn("ignoring command execution (no contributor configured for this language): {}, {} ", languageName, command); return CompletableFuture.completedFuture(null); } } - } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParserOnlyContribution.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParserOnlyContribution.java index 91a68d57a..3ba38ca3a 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParserOnlyContribution.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParserOnlyContribution.java @@ -75,7 +75,7 @@ public String getName() { } @Override - public CompletableFuture parseSourceFile(ISourceLocation loc, String input) { + public CompletableFuture parsing(ISourceLocation loc, String input) { if (loadingParserError != null || parser == null) { return CompletableFuture.failedFuture(new RuntimeException("Parser function did not load", loadingParserError)); } @@ -114,12 +114,12 @@ private static IConstructor makeReifiedType(ParserSpecification spec, IRascalVal } @Override - public InterruptibleFuture outline(ITree input) { + public InterruptibleFuture documentSymbol(ITree input) { return InterruptibleFuture.completedFuture(VF.list()); } @Override - public InterruptibleFuture analyze(ISourceLocation loc, ITree input) { + public InterruptibleFuture analysis(ISourceLocation loc, ITree input) { return InterruptibleFuture.completedFuture(EmptySummary.newInstance(loc)); } @@ -129,12 +129,12 @@ public InterruptibleFuture build(ISourceLocation loc, ITree input) } @Override - public InterruptibleFuture lenses(ITree input) { + public InterruptibleFuture codeLens(ITree input) { return InterruptibleFuture.completedFuture(VF.list()); } @Override - public InterruptibleFuture<@Nullable IValue> executeCommand(String command) { + public InterruptibleFuture<@Nullable IValue> execution(String command) { return InterruptibleFuture.completedFuture(VF.bool(false)); } @@ -149,12 +149,12 @@ public InterruptibleFuture inlayHint(@Nullable ITree input) { } @Override - public InterruptibleFuture documentation(IList focus) { + public InterruptibleFuture hover(IList focus) { return InterruptibleFuture.completedFuture(VF.set()); } @Override - public InterruptibleFuture definitions(IList focus) { + public InterruptibleFuture definition(IList focus) { return InterruptibleFuture.completedFuture(VF.set()); } @@ -164,67 +164,67 @@ public InterruptibleFuture references(IList focus) { } @Override - public InterruptibleFuture codeActions(IList focus) { + public InterruptibleFuture codeAction(IList focus) { return InterruptibleFuture.completedFuture(VF.list()); } @Override - public InterruptibleFuture implementations(IList focus) { + public InterruptibleFuture implementation(IList focus) { return InterruptibleFuture.completedFuture(VF.set()); } @Override - public CompletableFuture hasDocumenter() { + public CompletableFuture hasHover() { return CompletableFuture.completedFuture(false); } @Override - public CompletableFuture hasDefiner() { + public CompletableFuture hasDefinition() { return CompletableFuture.completedFuture(false); } @Override - public CompletableFuture hasReferrer() { + public CompletableFuture hasReferences() { return CompletableFuture.completedFuture(false); } @Override - public CompletableFuture hasImplementer() { + public CompletableFuture hasImplementation() { return CompletableFuture.completedFuture(false); } @Override - public CompletableFuture hasOutliner() { + public CompletableFuture hasDocumentSymbol() { return CompletableFuture.completedFuture(false); } @Override - public CompletableFuture hasAnalyzer() { + public CompletableFuture hasAnalysis() { return CompletableFuture.completedFuture(false); } @Override - public CompletableFuture hasBuilder() { + public CompletableFuture hasBuild() { return CompletableFuture.completedFuture(false); } @Override - public CompletableFuture hasCodeActionsContributor() { + public CompletableFuture hasCodeAction() { return CompletableFuture.completedFuture(false); } @Override - public CompletableFuture hasLensDetector() { + public CompletableFuture hasCodeLens() { return CompletableFuture.completedFuture(false); } @Override - public CompletableFuture hasCommandExecutor() { + public CompletableFuture hasExecution() { return CompletableFuture.completedFuture(false); } @Override - public CompletableFuture hasInlayHinter() { + public CompletableFuture hasInlayHint() { return CompletableFuture.completedFuture(false); } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/RascalInterface.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/RascalInterface.java index 99849f449..afaf6569b 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/RascalInterface.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/RascalInterface.java @@ -27,8 +27,12 @@ package org.rascalmpl.vscode.lsp.parametric; import org.rascalmpl.ideservices.IDEServices; +import org.rascalmpl.values.parsetrees.ITree; +import org.rascalmpl.vscode.lsp.util.locations.impl.TreeSearch; import io.usethesource.vallang.IConstructor; +import io.usethesource.vallang.IInteger; +import io.usethesource.vallang.IList; import io.usethesource.vallang.ISourceLocation; /** @@ -53,4 +57,8 @@ public void unregisterLanguage(IConstructor lang) { public ISourceLocation resolveProjectLocation(ISourceLocation project) { return services.resolveProjectLocation(project); } + + public IList computeFocusList(IConstructor input, IInteger line, IInteger column) { + return TreeSearch.computeFocusList((ITree) input, line.intValue(), column.intValue()); + } } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/model/ParametricFileFacts.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/model/ParametricFileFacts.java index be1ab0eaf..113100fb8 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/model/ParametricFileFacts.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/model/ParametricFileFacts.java @@ -114,7 +114,7 @@ private FileFact getFile(ISourceLocation l) { public void reloadContributions() { analyzerSummaryFactory = contrib.getAnalyzerSummaryConfig().thenApply(config -> - new ScheduledSummaryFactory(config, exec, columns, contrib::analyze)); + new ScheduledSummaryFactory(config, exec, columns, contrib::analysis)); builderSummaryFactory = contrib.getBuilderSummaryConfig().thenApply(config -> new ScheduledSummaryFactory(config, exec, columns, contrib::build)); ondemandSummaryFactory = contrib.getOndemandSummaryConfig().thenApply(config -> diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/model/ParametricSummary.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/model/ParametricSummary.java index 54b004ebb..69956d6e3 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/model/ParametricSummary.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/model/ParametricSummary.java @@ -72,7 +72,7 @@ /** * The purpose of this interface is to provide a general abstraction for - * `Position`-based look-ups of documentation, definitions, references, and + * `Position`-based look-ups of hovers, definitions, references, and * implementations, regardless of which component calculates the requested * information. There are two implementations: * @@ -96,7 +96,7 @@ public interface ParametricSummary { // this happens when no on-demand summarizer exists for the requested // information. @SuppressWarnings("deprecation") // For `MarkedString` - @Nullable InterruptibleFuture>> getDocumentation(Position cursor); + @Nullable InterruptibleFuture>> getHovers(Position cursor); @Nullable InterruptibleFuture> getDefinitions(Position cursor); @Nullable InterruptibleFuture> getReferences(Position cursor); @Nullable InterruptibleFuture> getImplementations(Position cursor); @@ -113,8 +113,8 @@ public static interface SummaryLookup extends BiFunction>> documentation(ParametricSummary summary, Position position) { - return summary.getDocumentation(position); + public static @Nullable InterruptibleFuture>> hovers(ParametricSummary summary, Position position) { + return summary.getHovers(position); } public static @Nullable InterruptibleFuture> definitions(ParametricSummary summary, Position position) { return summary.getDefinitions(position); @@ -139,7 +139,7 @@ public static InterruptibleFuture> getMessages(CompletableFutur class NullSummary implements ParametricSummary { @Override @SuppressWarnings("deprecation") // For `MarkedString` - public @Nullable InterruptibleFuture>> getDocumentation(Position cursor) { + public @Nullable InterruptibleFuture>> getHovers(Position cursor) { return null; } @@ -260,15 +260,25 @@ private InterruptibleFuture>> extractMessages(Interruptibl public class FullScheduledSummary extends MessagesOnlyScheduledSummary { @SuppressWarnings("deprecation") // For `MarkedString` - private final @Nullable InterruptibleFuture>>>> documentation; + private final @Nullable InterruptibleFuture>>>> hovers; private final @Nullable InterruptibleFuture>>> definitions; private final @Nullable InterruptibleFuture>>> references; private final @Nullable InterruptibleFuture>>> implementations; public FullScheduledSummary(InterruptibleFuture calculation) { super(calculation); - this.documentation = config.providesDocumentation ? - mapCalculation(SummaryFields.DOCUMENTATION, calculation, SummaryFields.DOCUMENTATION, ParametricSummaryFactory::mapValueToString) : null; + + // for temporary backward compatibility between SummaryFields.HOVERS and SummaryFields.DEPRECATED_DOCUMENTATION + calculation = calculation.thenApply(summary -> { + var kws = summary.asWithKeywordParameters(); + if (kws.hasParameter(SummaryFields.DEPRECATED_DOCUMENTATION) && !kws.hasParameter(SummaryFields.HOVERS)) { + return kws.setParameter(SummaryFields.HOVERS, kws.getParameter(SummaryFields.DEPRECATED_DOCUMENTATION)); + } + return summary; + }); + + this.hovers = config.providesHovers ? + mapCalculation(SummaryFields.HOVERS, calculation, SummaryFields.HOVERS, ParametricSummaryFactory::mapValueToString) : null; this.definitions = config.providesDefinitions ? mapCalculation(SummaryFields.DEFINITIONS, calculation, SummaryFields.DEFINITIONS, locationMapper(columns)) : null; this.references = config.providesReferences ? @@ -279,8 +289,8 @@ public FullScheduledSummary(InterruptibleFuture calculation) { @Override @SuppressWarnings("deprecation") // For `MarkedString` - public @Nullable InterruptibleFuture>> getDocumentation(Position cursor) { - return get(documentation, cursor); + public @Nullable InterruptibleFuture>> getHovers(Position cursor) { + return get(hovers, cursor); } @Override @@ -301,7 +311,7 @@ public FullScheduledSummary(InterruptibleFuture calculation) { @Override public void invalidate() { super.invalidate(); - documentation.interrupt(); + hovers.interrupt(); definitions.interrupt(); references.interrupt(); implementations.interrupt(); @@ -424,13 +434,13 @@ public OndemandSummary(ISourceLocation file, Versioned tree, Position cur @Override @SuppressWarnings("deprecation") // For `MarkedString` - public @Nullable InterruptibleFuture>> getDocumentation(Position cursor) { - return get(config.providesDocumentation, cursor, contrib::documentation, ParametricSummaryFactory::mapValueToString, SummaryFields.DOCUMENTATION); + public @Nullable InterruptibleFuture>> getHovers(Position cursor) { + return get(config.providesHovers, cursor, contrib::hover, ParametricSummaryFactory::mapValueToString, SummaryFields.HOVERS); } @Override public @Nullable InterruptibleFuture> getDefinitions(Position cursor) { - return get(config.providesDefinitions, cursor, contrib::definitions, locationMapper(columns), SummaryFields.DEFINITIONS); + return get(config.providesDefinitions, cursor, contrib::definition, locationMapper(columns), SummaryFields.DEFINITIONS); } @Override @@ -440,7 +450,7 @@ public OndemandSummary(ISourceLocation file, Versioned tree, Position cur @Override public @Nullable InterruptibleFuture> getImplementations(Position cursor) { - return get(config.providesImplementations, cursor, contrib::implementations, locationMapper(columns), SummaryFields.IMPLEMENTATIONS); + return get(config.providesImplementations, cursor, contrib::implementation, locationMapper(columns), SummaryFields.IMPLEMENTATIONS); } @Override diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/model/RascalADTs.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/model/RascalADTs.java index b9c4400da..837a3c2c2 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/model/RascalADTs.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/model/RascalADTs.java @@ -33,37 +33,39 @@ public class RascalADTs { private RascalADTs() {} public static class LanguageContributions { private LanguageContributions () {} - public static final String PARSER = "parser"; - public static final String ANALYZER = "analyzer"; - public static final String BUILDER = "builder"; - public static final String OUTLINER = "outliner"; - public static final String LENS_DETECTOR = "lenses"; - public static final String INLAY_HINTER = "inlayHinter"; - public static final String COMMAND_EXECUTOR = "executor"; - public static final String DOCUMENTER = "documenter"; - public static final String DEFINER = "definer"; - public static final String REFERRER = "referrer"; - public static final String IMPLEMENTER = "implementer"; - public static final String CODE_ACTION_CONTRIBUTOR = "actions"; + + public static final String PARSING = "parsing"; + public static final String ANALYSIS = "analysis"; + public static final String BUILD = "build"; + public static final String DOCUMENT_SYMBOL = "documentSymbol"; + public static final String CODE_LENS = "codeLens"; + public static final String INLAY_HINT = "inlayHint"; + public static final String EXECUTION = "execution"; + public static final String HOVER = "hover"; + public static final String DEFINITION = "definition"; + public static final String REFERENCES = "references"; + public static final String IMPLEMENTATION = "implementation"; + public static final String CODE_ACTION = "codeAction"; public static class Summarizers { private Summarizers() {} - public static final String PROVIDES_IMPLEMENTATIONS = "providesImplementations"; - public static final String PROVIDES_REFERENCES = "providesReferences"; + public static final String PROVIDES_HOVERS = "providesHovers"; public static final String PROVIDES_DEFINITIONS = "providesDefinitions"; - public static final String PROVIDES_DOCUMENTATION = "providesDocumentation"; + public static final String PROVIDES_REFERENCES = "providesReferences"; + public static final String PROVIDES_IMPLEMENTATIONS = "providesImplementations"; } } public static class SummaryFields { private SummaryFields() {} - public static final String DOCUMENTATION = "documentation"; + public static final String DEPRECATED_DOCUMENTATION = "documentation"; + + public static final String HOVERS = "hovers"; public static final String DEFINITIONS = "definitions"; public static final String REFERENCES = "references"; public static final String IMPLEMENTATIONS = "implementations"; - } public static class CommandFields { @@ -80,5 +82,4 @@ private CodeActionFields() { } public static final String TITLE = "title"; public static final String KIND = "kind"; } - } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServices.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServices.java index 2ace1ba1d..eed62f975 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServices.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalLanguageServices.java @@ -79,7 +79,7 @@ public class RascalLanguageServices { private static final Logger logger = LogManager.getLogger(RascalLanguageServices.class); - private final CompletableFuture outlineEvaluator; + private final CompletableFuture documentSymbolEvaluator; private final CompletableFuture semanticEvaluator; private final CompletableFuture compilerEvaluator; @@ -97,7 +97,7 @@ public RascalLanguageServices(RascalTextDocumentService docService, BaseWorkspac var monitor = new RascalLSPMonitor(client, logger); - outlineEvaluator = makeFutureEvaluator(exec, docService, workspaceService, client, "Rascal outline", monitor, null, false, "lang::rascal::lsp::Outline"); + documentSymbolEvaluator = makeFutureEvaluator(exec, docService, workspaceService, client, "Rascal document symbols", monitor, null, false, "lang::rascal::lsp::DocumentSymbols"); semanticEvaluator = makeFutureEvaluator(exec, docService, workspaceService, client, "Rascal semantics", monitor, null, true, "lang::rascalcore::check::Summary", "lang::rascal::lsp::refactor::Rename"); compilerEvaluator = makeFutureEvaluator(exec, docService, workspaceService, client, "Rascal compiler", monitor, null, true, "lang::rascalcore::check::Checker"); } @@ -181,14 +181,14 @@ private ISourceLocation getFileLoc(ITree moduleTree) { } - public InterruptibleFuture getOutline(IConstructor module) { + public InterruptibleFuture getDocumentSymbols(IConstructor module) { ISourceLocation loc = getFileLoc((ITree) module); if (loc == null) { return new InterruptibleFuture<>(CompletableFuture.completedFuture(VF.list()), () -> { }); } - return runEvaluator("Rascal outline", outlineEvaluator, eval -> (IList) eval.call("outlineRascalModule", module), + return runEvaluator("Rascal Document Symbols", documentSymbolEvaluator, eval -> (IList) eval.call("documentRascalSymbols", module), VF.list(), exec, false, client); } 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 5e41f55ad..6de9d8b44 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 @@ -98,7 +98,7 @@ import org.rascalmpl.vscode.lsp.util.Diagnostics; import org.rascalmpl.vscode.lsp.util.DocumentChanges; import org.rascalmpl.vscode.lsp.util.FoldingRanges; -import org.rascalmpl.vscode.lsp.util.Outline; +import org.rascalmpl.vscode.lsp.util.DocumentSymbols; import org.rascalmpl.vscode.lsp.util.SemanticTokenizer; import org.rascalmpl.vscode.lsp.util.Versioned; import org.rascalmpl.vscode.lsp.util.locations.ColumnMaps; @@ -158,6 +158,7 @@ public void initializeServerCapabilities(ServerCapabilities result) { result.setFoldingRangeProvider(true); result.setRenameProvider(true); } + @Override public void pair(BaseWorkspaceService workspaceService) { this.workspaceService = workspaceService; @@ -176,14 +177,14 @@ public void connect(LanguageClient client) { @Override public void didOpen(DidOpenTextDocumentParams params) { - logger.debug("Open file: {}", params.getTextDocument()); + logger.debug("Open: {}", params.getTextDocument()); TextDocumentState file = open(params.getTextDocument()); handleParsingErrors(file); } @Override public void didChange(DidChangeTextDocumentParams params) { - logger.trace("Change contents: {}", params.getTextDocument()); + logger.trace("Change: {}", params.getTextDocument()); updateContents(params.getTextDocument(), last(params.getContentChanges()).getText()); } @@ -244,10 +245,9 @@ private void handleParsingErrors(TextDocumentState file) { handleParsingErrors(file,file.getCurrentTreeAsync()); } - @Override public CompletableFuture, List>> definition(DefinitionParams params) { - logger.debug("Definition: {} at {}", params.getTextDocument(), params.getPosition()); + logger.debug("textDocument/definition: {} at {}", params.getTextDocument(), params.getPosition()); if (facts != null) { return facts.getSummary(Locations.toLoc(params.getTextDocument())) @@ -263,13 +263,13 @@ public CompletableFuture, List>> documentSymbol(DocumentSymbolParams params) { - logger.debug("Outline/documentSymbols: {}", params.getTextDocument()); + logger.debug("textDocument/documentSymbol: {}", params.getTextDocument()); TextDocumentState file = getFile(params.getTextDocument()); return file.getCurrentTreeAsync() .thenApply(Versioned::get) .handle((t, r) -> (t == null ? (file.getMostRecentTree().get()) : t)) - .thenCompose(tr -> rascalServices.getOutline(tr).get()) - .thenApply(c -> Outline.buildOutline(c, columns.get(file.getLocation()))) + .thenCompose(tr -> rascalServices.getDocumentSymbols(tr).get()) + .thenApply(documentSymbols -> DocumentSymbols.toLSP(documentSymbols, columns.get(file.getLocation()))) ; } @@ -429,6 +429,4 @@ public CompletableFuture executeCommand(String extension, String command logger.warn("ignoring execute command in Rascal LSP: {}, {}", extension, command); return CompletableFuture.completedFuture(null); } - - } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/Outline.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/DocumentSymbols.java similarity index 89% rename from rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/Outline.java rename to rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/DocumentSymbols.java index 21aeff439..93ad73f9b 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/Outline.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/DocumentSymbols.java @@ -42,9 +42,9 @@ import io.usethesource.vallang.IString; import io.usethesource.vallang.IWithKeywordParameters; -public class Outline { +public class DocumentSymbols { // hide constructor for static class - private Outline() {} + private DocumentSymbols() {} private static String capitalize(String kindName) { return kindName.substring(0,1).toUpperCase() + kindName.substring(1); @@ -53,29 +53,29 @@ private static String capitalize(String kindName) { /** * Converts a list of Rascal DocumentSymbols (@see util::IDE) to LSP DocumentSymbols * @param symbols list of Rascal DocumentSymbols - * @param om line/column offset map + * @param om line/column offset map * @return list of LSP DocumentSymbols */ - public static List> buildOutline(IList symbols, LineColumnOffsetMap om) { + public static List> toLSP(IList symbols, LineColumnOffsetMap om) { return symbols.stream() - .map(s -> buildParametricOutline((IConstructor) s, om)) + .map(s -> toLSP((IConstructor) s, om)) .map(Either::forRight) .collect(Collectors.toList()); } /** - * Converts a constructor tree of Rascal type DocumentSymbol from util::IDE to an LSP DocumentSymbol + * Converts a constructor tree of Rascal type DocumentSymbol (@see util::IDE) to an LSP DocumentSymbol * @param symbol IConstructor of Rascal type DocumentSymbol * @param om line/column offset map - * @return a DocumentSymbol + * @return an LSP DocumentSymbol */ - public static DocumentSymbol buildParametricOutline(IConstructor symbol, final LineColumnOffsetMap om) { + public static DocumentSymbol toLSP(IConstructor symbol, final LineColumnOffsetMap om) { IWithKeywordParameters kwp = symbol.asWithKeywordParameters(); List children = kwp.hasParameter("children") ? ((IList) kwp.getParameter("children")) .stream() - .map(c -> buildParametricOutline((IConstructor) c, om)) + .map(c -> toLSP((IConstructor) c, om)) .collect(Collectors.toList()) : Collections.emptyList(); diff --git a/rascal-lsp/src/main/rascal/demo/lang/pico/LanguageServer.rsc b/rascal-lsp/src/main/rascal/demo/lang/pico/LanguageServer.rsc index 39eca22f6..5e0eae424 100644 --- a/rascal-lsp/src/main/rascal/demo/lang/pico/LanguageServer.rsc +++ b/rascal-lsp/src/main/rascal/demo/lang/pico/LanguageServer.rsc @@ -24,6 +24,13 @@ 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. } +@synopsis{Demonstrates the API for defining and registering IDE language services for Programming Languages and Domain Specific Languages.} +@description{ +The core functionality of this module is built upon these concepts: +* ((registerLanguage)) for enabling your language services for a given file extension _in the current IDE_. +* ((Language)) is the data-type for defining a language, with meta-data for starting a new LSP server. +* A ((LanguageService)) is a specific feature for an IDE. Each service comes with one Rascal function that implements it. +} module demo::lang::pico::LanguageServer import util::LanguageServer; @@ -32,36 +39,49 @@ import ParseTree; import util::Reflective; import lang::pico::\syntax::Main; -@synopsis{Provides each contribution (IDE feature) as a callback element of the set of LanguageServices.} -set[LanguageService] picoLanguageContributor() = { - parser(parser(#start[Program])), - outliner(picoOutliner), - lenses(picoLenses), - executor(picoCommands), - inlayHinter(picoHinter), - definer(lookupDef), - actions(picoActions) +@synopsis{A language server is simply a set of ((LanguageService))s.} +@description{ +Each ((LanguageService)) for pico is implemented as a function. +Here we group all services such that the LSP server can link them +with the ((Language)) definition later. +} +set[LanguageService] picoLanguageServer() = { + parsing(parser(#start[Program])), + documentSymbol(picoDocumentSymbolService), + codeLens(picoCodeLenseService), + execution(picoExecutionService), + inlayHint(picoInlayHintService), + definition(picoDefinitionService), + codeAction(picoCodeActionService) }; @synopsis{This set of contributions runs slower but provides more detail.} -set[LanguageService] picoLanguageContributorSlowSummary() = { - parser(parser(#start[Program])), - analyzer(picoAnalyzer, providesImplementations = false), - builder(picoBuilder) +@description{ +((LanguageService))s can be registered asynchronously and incrementally, +such that quicky loaded features can be made available while slower to load +tools come in later. +} +set[LanguageService] picoLanguageServerSlowSummary() = { + parsing(parser(#start[Program])), + analysis(picoAnalysisService, providesImplementations = false), + build(picoBuildService) }; -@synopsis{The outliner maps pico syntax trees to lists of DocumentSymbols.} -list[DocumentSymbol] picoOutliner(start[Program] input) +@synopsis{The documentSymbol service maps pico syntax trees to lists of DocumentSymbols.} +@description{ +Here we list the symbols we want in the outline view, and which can be searched using +symbol search in the editor. +} +list[DocumentSymbol] picoDocumentSymbolService(start[Program] input) = [symbol("", DocumentSymbolKind::\file(), input.src, children=[ *[symbol("", \variable(), var.src) | /IdType var := input] ])]; - @synopsis{The analyzer maps pico syntax trees to error messages and references} -Summary picoAnalyzer(loc l, start[Program] input) = picoSummarizer(l, input, analyze()); +Summary picoAnalysisService(loc l, start[Program] input) = picoSummaryService(l, input, analyze()); @synopsis{The builder does a more thorough analysis then the analyzer, providing more detail} -Summary picoBuilder(loc l, start[Program] input) = picoSummarizer(l, input, build()); +Summary picoBuildService(loc l, start[Program] input) = picoSummaryService(l, input, build()); @synopsis{A simple "enum" data type for switching between analysis modes} data PicoSummarizerMode @@ -70,7 +90,7 @@ data PicoSummarizerMode ; @synopsis{Translates a pico syntax tree to a model (Summary) of everything we need to know about the program in the IDE.} -Summary picoSummarizer(loc l, start[Program] input, PicoSummarizerMode mode) { +Summary picoSummaryService(loc l, start[Program] input, PicoSummarizerMode mode) { Summary s = summary(l); // definitions of variables @@ -104,19 +124,21 @@ Summary picoSummarizer(loc l, start[Program] input, PicoSummarizerMode mode) { return s; } -@synopsis{Looks up the declaration for any variable use using the / deep match} -set[loc] lookupDef(loc _, start[Program] input, Tree cursor) = - { d.src | /IdType d := input, cursor := d.id}; +@synopsis{Looks up the declaration for any variable use using a list match into a ((Focus))} +@pitfalls{ +This demo actually finds the declaration rather than the definition of a variable in Pico. +} +set[loc] picoDefinitionService([*_, Id use, *_, start[Program] input]) = { def.src | /IdType def := input, use := def.id}; @synopsis{If a variable is not defined, we list a fix of fixes to replace it with a defined variable instead.} list[CodeAction] prepareNotDefinedFixes(loc src, rel[str, loc] defs) = [action(title="Change to >", edits=[changed(src.top, [replace(src, existing<0>)])]) | existing <- defs]; @synopsis{Finds a declaration that the cursor is on and proposes to remove it.} -list[CodeAction] picoActions([*_, IdType x, *_, start[Program] program]) +list[CodeAction] picoCodeActionService([*_, IdType x, *_, start[Program] program]) = [action(command=removeDecl(program, x, title="remove "))]; -default list[CodeAction] picoActions(Focus _focus) = []; +default list[CodeAction] picoCodeActionService(Focus _focus) = []; @synsopsis{Defines three example commands that can be triggered by the user (from a code lens, from a diagnostic, or just from the cursor position)} data Command @@ -125,11 +147,11 @@ data Command ; @synopsis{Adds an example lense to the entire program.} -rel[loc,Command] picoLenses(start[Program] input) - = {}; +lrel[loc,Command] picoCodeLenseService(start[Program] input) + = []; @synopsis{Generates inlay hints that explain the type of each variable usage.} -list[InlayHint] picoHinter(start[Program] input) { +list[InlayHint] picoInlayHintService(start[Program] input) { typeLookup = ( "" : "" | /(IdType)` : ` := input); return [ @@ -144,13 +166,13 @@ list[DocumentEdit] getAtoBEdits(start[Program] input) = [changed(input@\loc.top, [replace(id@\loc, "b") | /id:(Id) `a` := input])]; @synopsis{Command handler for the renameAtoB command} -value picoCommands(renameAtoB(start[Program] input)) { +value picoExecutionService(renameAtoB(start[Program] input)) { applyDocumentsEdits(getAtoBEdits(input)); return ("result": true); } @synopsis{Command handler for the removeDecl command} -value picoCommands(removeDecl(start[Program] program, IdType toBeRemoved)) { +value picoExecutionService(removeDecl(start[Program] program, IdType toBeRemoved)) { applyDocumentsEdits([changed(program@\loc.top, [replace(toBeRemoved@\loc, "")])]); return ("result": true); } @@ -173,8 +195,8 @@ void main() { pathConfig(), "Pico", {"pico", "pico-new"}, - "demo::lang::pico::LanguageServer", - "picoLanguageContributor" + "demo::lang::pico::NewLanguageServer", + "picoLanguageServer" ) ); registerLanguage( @@ -182,8 +204,8 @@ void main() { pathConfig(), "Pico", {"pico", "pico-new"}, - "demo::lang::pico::LanguageServer", - "picoLanguageContributorSlowSummary" + "demo::lang::pico::NewLanguageServer", + "picoLanguageServerSlowSummary" ) ); } diff --git a/rascal-lsp/src/main/rascal/demo/lang/pico/OldStyleLanguageServer.rsc b/rascal-lsp/src/main/rascal/demo/lang/pico/OldStyleLanguageServer.rsc new file mode 100644 index 000000000..e9e3dbb5e --- /dev/null +++ b/rascal-lsp/src/main/rascal/demo/lang/pico/OldStyleLanguageServer.rsc @@ -0,0 +1,194 @@ +@license{ +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. +} +@synopsis{Demonstrating all of the LSP services for the demo language Pico.} +@deprecated{This demo has been superseded by ((LanguageServer)) which avoids the use of deprecated API.} +@description{ +This module is here to test the backward compatibility layer over the new ((util::LanguageServer)) API. +For learning how to build a new set of lanuage services, please go [here]((util::LanguageServer)). +} +module demo::lang::pico::OldStyleLanguageServer + +import util::LanguageServer; +import util::IDEServices; +import ParseTree; +import util::Reflective; +import lang::pico::\syntax::Main; + +@synopsis{Provides each contribution (IDE feature) as a callback element of the set of LanguageServices.} +set[LanguageService] picoLanguageContributor() = { + parser(parser(#start[Program])), + outliner(picoOutliner), + lenses(picoLenses), + executor(picoCommands), + inlayHinter(picoHinter), + definer(lookupDef), + actions(picoActions) +}; + +@synopsis{This set of contributions runs slower but provides more detail.} +set[LanguageService] picoLanguageContributorSlowSummary() = { + parser(parser(#start[Program])), + analyzer(picoAnalyzer, providesImplementations = false), + builder(picoBuilder) +}; + +@synopsis{The outliner maps pico syntax trees to lists of DocumentSymbols.} +list[DocumentSymbol] picoOutliner(start[Program] input) + = [symbol("", DocumentSymbolKind::\file(), input.src, children=[ + *[symbol("", \variable(), var.src) | /IdType var := input] + ])]; + +@synopsis{The analyzer maps pico syntax trees to error messages and references} +Summary picoAnalyzer(loc l, start[Program] input) = picoSummarizer(l, input, analyze()); + +@synopsis{The builder does a more thorough analysis then the analyzer, providing more detail} +Summary picoBuilder(loc l, start[Program] input) = picoSummarizer(l, input, build()); + +@synopsis{A simple "enum" data type for switching between analysis modes} +data PicoSummarizerMode + = analyze() + | build() + ; + +@synopsis{Translates a pico syntax tree to a model (Summary) of everything we need to know about the program in the IDE.} +Summary picoSummarizer(loc l, start[Program] input, PicoSummarizerMode mode) { + Summary s = summary(l); + + // definitions of variables + rel[str, loc] defs = {<"", var.src> | /IdType var := input}; + + // uses of identifiers + rel[loc, str] uses = {"> | /Id id := input}; + + // documentation strings for identifier uses + rel[loc, str] docs = {"> | /IdType var := input}; + + // Provide errors (cheap to compute) both in analyze mode and in build mode. + s.messages += { is not defined", src, fixes=prepareNotDefinedFixes(src, defs))> + | <- uses, id notin defs<0>}; + + // "references" are links for loc to loc (from def to use) + s.references += (uses o defs)<1,0>; + + // "definitions" are also links from loc to loc (from use to def) + s.definitions += uses o defs; + + // "documentation" maps locations to strs + s.documentation += (uses o defs) o docs; + + // Provide warnings (expensive to compute) only in build mode + if (build() := mode) { + rel[loc, str] asgn = {"> | /Statement stmt := input, (Statement) ` := ` := stmt}; + s.messages += { is not assigned", src)> | <- defs, id notin asgn<1>}; + } + + return s; +} + +@synopsis{Looks up the declaration for any variable use using the / deep match} +set[loc] lookupDef(loc _, start[Program] input, Tree cursor) = + {d.src | /IdType d := input, cursor := d.id}; + +@synopsis{If a variable is not defined, we list a fix of fixes to replace it with a defined variable instead.} +list[CodeAction] prepareNotDefinedFixes(loc src, rel[str, loc] defs) + = [action(title="Change to >", edits=[changed(src.top, [replace(src, existing<0>)])]) | existing <- defs]; + +@synopsis{Finds a declaration that the cursor is on and proposes to remove it.} +list[CodeAction] picoActions([*Tree _, IdType x, *Tree _, start[Program] program]) + = [action(command=removeDecl(program, x, title="remove "))]; + +default list[CodeAction] picoActions(Focus _focus) = []; + +@synsopsis{Defines three example commands that can be triggered by the user (from a code lens, from a diagnostic, or just from the cursor position)} +data Command + = renameAtoB(start[Program] program) + | removeDecl(start[Program] program, IdType toBeRemoved) + ; + +@synopsis{Adds an example lense to the entire program.} +rel[loc,Command] picoLenses(start[Program] input) + = {}; + +@synopsis{Generates inlay hints that explain the type of each variable usage.} +list[InlayHint] picoHinter(start[Program] input) { + typeLookup = ( "" : "" | /(IdType)` : ` := input); + + return [ + hint(name.src, " : "]>", \type(), atEnd = true) + | /(Expression)`` := input + , "" in typeLookup + ]; +} + +@synopsis{Helper function to generate actual edit actions for the renameAtoB command} +list[DocumentEdit] getAtoBEdits(start[Program] input) + = [changed(input@\loc.top, [replace(id@\loc, "b") | /id:(Id) `a` := input])]; + +@synopsis{Command handler for the renameAtoB command} +value picoCommands(renameAtoB(start[Program] input)) { + applyDocumentsEdits(getAtoBEdits(input)); + return ("result": true); +} + +@synopsis{Command handler for the removeDecl command} +value picoCommands(removeDecl(start[Program] program, IdType toBeRemoved)) { + applyDocumentsEdits([changed(program@\loc.top, [replace(toBeRemoved@\loc, "")])]); + return ("result": true); +} + +@synopsis{The main function registers the Pico language with the IDE} +@description{ +Register the Pico language and the contributions that supply the IDE with features. + +((registerLanguage)) is called twice here: +1. first for fast and cheap contributions +2. asynchronously for the full monty that loads slower +} +@benefits{ +* You can run each contribution on an example in the terminal to test it first. +Any feedback (errors and exceptions) is faster and more clearly printed in the terminal. +} +void main() { + registerLanguage( + language( + pathConfig(), + "Pico", + {"pico", "pico-new"}, + "demo::lang::pico::OldStyleLanguageServer", + "picoLanguageContributor" + ) + ); + registerLanguage( + language( + pathConfig(), + "Pico", + {"pico", "pico-new"}, + "demo::lang::pico::OldStyleLanguageServer", + "picoLanguageContributorSlowSummary" + ) + ); +} diff --git a/rascal-lsp/src/main/rascal/lang/rascal/lsp/Outline.rsc b/rascal-lsp/src/main/rascal/lang/rascal/lsp/DocumentSymbols.rsc similarity index 97% rename from rascal-lsp/src/main/rascal/lang/rascal/lsp/Outline.rsc rename to rascal-lsp/src/main/rascal/lang/rascal/lsp/DocumentSymbols.rsc index d7c6a91c4..55f8a63f9 100644 --- a/rascal-lsp/src/main/rascal/lang/rascal/lsp/Outline.rsc +++ b/rascal-lsp/src/main/rascal/lang/rascal/lsp/DocumentSymbols.rsc @@ -25,14 +25,14 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. } @bootstrapParser -module lang::rascal::lsp::Outline +module lang::rascal::lsp::DocumentSymbols import String; import ParseTree; import lang::rascal::\syntax::Rascal; import util::LanguageServer; -list[DocumentSymbol] outlineRascalModule(start[Module] \mod) { +list[DocumentSymbol] documentRascalSymbols(start[Module] \mod) { m= \mod.top; children = []; diff --git a/rascal-lsp/src/main/rascal/util/LanguageServer.rsc b/rascal-lsp/src/main/rascal/util/LanguageServer.rsc index 78e6d7e52..869491254 100644 --- a/rascal-lsp/src/main/rascal/util/LanguageServer.rsc +++ b/rascal-lsp/src/main/rascal/util/LanguageServer.rsc @@ -45,14 +45,24 @@ import IO; import ParseTree; import Message; -@synopsis{Definition of a language server by its meta-data} +@synopsis{Definition of a language server by its meta-data.} @description{ The ((registerLanguage)) function takes this as its parameter to generate and run -a fresh language protocol server. +a fresh language protocol server. Every language server is run in its own Rascal execution +environment. The ((Language)) data-type defines the parameters of this run-time, such +that ((registerLanguage)) can boot and initialize new instances. + +* `pcfg` sets up search paths for Rascal modules and libraries required to run the language server +* `name` is the name of the language +* `extensions` are the file extensions that bind this server to editors of files with these extensions. +* `mainModule` is the Rascal module to load to start the language server +* `mainFunction` is a function of type `set[LanguageService] ()` that produces the implementation of the language server +as a set of collaborating ((LanguageService))s. } @benefits{ * each registered language is run in its own Rascal run-time environment. * reloading a language is always done in a fresh environment. +* instances of ((Language)) can be easily serialized and communicated in interactive language engineering environments. } @pitfalls{ * even though ((registerLanguage)) is called in a given run-time environment, @@ -73,27 +83,11 @@ This parse tree is then used for both syntax highlighting and other language ser @pitfalls { * use `ParseTree::parser` instead of writing your own function to ensure syntax highlighting is fast } +@deprecated{Used only in deprecated functions} alias Parser = Tree (str _input, loc _origin); @synopsis{Function profile for summarizer contributions to a language server} -@description{ -Summarizers provide information about the declarations and uses in the current file -which can be used to populate the information needed to implement interactive IDE -features. - -There are two places a Summarizer can be called: -* Summarizers can be called after _file save_, in this case we use ((builder))s. Builders typically also have side-effects on disk (leaving generated code or API descriptions in the target folder), and they may run whole-program analysis and compilation steps. -* Or they can be called while typing, in this case we use ((analyzer))s. Analyzers typically use stored or cached information from other files, but focus their own analysis on their own file. Analyzers may use incremental techniques. - -A summarizer provides the same information as the following contributors combined: -* ((documenter)) -* ((definer)) -* ((referrer)) -* ((implementer)) - -The difference is that these contributions are executed on-demand (pulled), while Summarizers -are executed after build or after typing (push). -} +@deprecated{Used only in deprecated functions} alias Summarizer = Summary (loc _origin, Tree _input); @synopsis{A focus provides the currently selected language constructs around the cursor.} @@ -125,221 +119,200 @@ of the user. alias Focus = list[Tree]; @synopsis{Function profile for outliner contributions to a language server} +@deprecated{Only in use in deprecated functions.} alias Outliner = list[DocumentSymbol] (Tree _input); @synopsis{Function profile for lenses contributions to a language server} -@deprecated{The ((OrderedLensDetector)) has replaced this type} +@deprecated{Only in use in deprecated functions.} alias LensDetector = rel[loc src, Command lens] (Tree _input); @synopsis{Function profile for lenses contributions to a language server} alias OrderedLensDetector = lrel[loc src, Command lens] (Tree _input); @synopsis{Function profile for executor contributions to a language server} +@deprecated{Only in use in deprecated functions.} alias CommandExecutor = value (Command _command); @synopsis{Function profile for inlay contributions to a language server} +@deprecated{Only in use in deprecated functions.} alias InlayHinter = list[InlayHint] (Tree _input); -@synopsis{Function profile for documentation contributions to a language server} -@description{ -A documenter is called on-demand, when documentation is requested by the IDE user. -} -@benefits{ -* is focused on a single documentation request, so does not need full program analysis. -} -@pitfalls{ -* should be extremely fast in order to provide interactive access. -* careful use of `@memo` may help to cache dependencies, but this is tricky! -} -@deprecated{The ((FocusDocumenter)) has replaced this type.} +@deprecated{Only in use in deprecated functions} alias Documenter = set[str] (loc _origin, Tree _fullTree, Tree _lexicalAtCursor); -@synopsis{Function profile for documentation contributions to a language server} -@description{ -A ((FocusDocumenter)) is called on-demand, when documentation is requested by the IDE user. -The current selection is used to create a ((Focus)) that we can use to select the right -functionality with. It is possible several constructs are in "focus", and then we can -provide several pieces of documentation. -} -@benefits{ -* is focused on a single documentation request, so does not need full program analysis. -} -@pitfalls{ -* should be extremely fast in order to provide interactive access. -* careful use of `@memo` may help to cache dependencies, but this is tricky! -} -alias FocusDocumenter = set[str] (Focus _focus); - -@synopsis{Function profile for retrieving code actions focused around the current cursor} -@description{ -Next to the quickfix commands that may be attached to diagnostic ((Message))s, the LSP -can produce refactoring and quickfix or visualization actions specific for what is near -or under the current cursor. - -An action contributor is called on demand when a user presses a light-bulb or asks for quick-fixes. -The implementor is asked to produce only actions that pertain what is under the current cursor. -} +@deprecated{Only in use in deprecated functions} alias CodeActionContributor = list[CodeAction] (Focus _focus); @synopsis{Function profile for definer contributions to a language server} -@description{ -A definer is called on-demand, when a definition is requested by the IDE user. -} -@benefits{ -* is focused on a single definition request, so does not need full program analysis. -} -@pitfalls{ -* should be extremely fast in order to provide interactive access. -* careful use of `@memo` may help to cache dependencies, but this is tricky! -} @deprecated{Use ((FocusDefiner)) instead.} alias Definer = set[loc] (loc _origin, Tree _fullTree, Tree _lexicalAtCursor); -@synopsis{Function profile for definer contributions to a language server} -@description{ -A definer is called on-demand, when a definition is requested by the IDE user. -} -@benefits{ -* is focused on a single definition request, so does not need full program analysis. -} -@pitfalls{ -* should be extremely fast in order to provide interactive access. -* careful use of `@memo` may help to cache dependencies, but this is tricky! -} -alias FocusDefiner = set[loc] (Focus _focus); - @synopsis{Function profile for referrer contributions to a language server} -@description{ -A referrer is called on-demand, when a reference is requested by the IDE user. -} -@benefits{ -* is focused on a single reference request, so does not need full program analysis. -} -@pitfalls{ -* should be extremely fast in order to provide interactive access. -* careful use of `@memo` may help to cache dependencies, but this is tricky! -} @deprecated{Use ((FocusReferrer)) instead} alias Referrer = set[loc] (loc _origin, Tree _fullTree, Tree _lexicalAtCursor); -@synopsis{Function profile for referrer contributions to a language server} -@description{ -A referrer is called on-demand, when a reference is requested by the IDE user. -} -@benefits{ -* is focused on a single reference request, so does not need full program analysis. -} -@pitfalls{ -* should be extremely fast in order to provide interactive access. -* careful use of `@memo` may help to cache dependencies, but this is tricky! -} -alias FocusReferrer = set[loc] (list[Tree] _focus); - @synopsis{Function profile for implementer contributions to a language server} -@description{ -An implementer is called on-demand, when an implementation is requested by the IDE user. -} -@benefits{ -* is focused on a single implementation request, so does not need full program analysis. -} -@pitfalls{ -* should be extremely fast in order to provide interactive access. -* careful use of `@memo` may help to cache dependencies, but this is tricky! -} @deprecated{Use ((FocusImplementer)) instead.} alias Implementer = set[loc] (loc _origin, Tree _fullTree, Tree _lexicalAtCursor); -@synopsis{Function profile for implementer contributions to a language server} +@synopsis{Each kind of service contibutes the implementation of one (or several) IDE features.} @description{ -An implementer is called on-demand, when an implementation is requested by the IDE user. +Each LanguageService constructor provides one aspect of definining the language server protocol (LSP). +Their names coincide exactly with the services which are documented [here](https://microsoft.github.io/language-server-protocol/). + +* The ((parsing)) service that maps source code strings to a ((Tree)) is essential and non-optional. +All other other services are optional. + * By providing a parser which produces annotated parse ((Tree))s, editor features such as parse error locations, syntax highlighting and +selection assistance are immediately enabled. + * The ((parsing)) service is activated after every change in an editor document (when a suitable pause has occurred) + * All downstream services are based on the ((Tree)) that is produced here. In +particular downstream services make use of the `src` origin fields that the parser must produce. + * Parsers can be obtained automatically using the ((ParseTree::parser)) or ((ParseTree::parsers)) functions, like so `parser(#start[Program])`. +Like this a fast parser is obtained that does not require a global interpreter lock. If you pass in a normal Rascal function, which is fine, the global +interpreter lock will make the editor services less responsive. +* The ((analysis)) service indexes a file as a ((Summary)), offering precomputed relations for looking up +hover documentation, definition with uses, references to declarations, implementations of types and compiler errors and warnings. + * ((analysis)) focuses on their own file, but may reuse cached or stored indices from other files. + * ((analysis)) has to be quick since they run in an interactive editor setting. + * ((analysis)) may store previous results (in memory) for incremental updates. + * ((analysis)) is triggered on-demand during typing, in a short typing pause. So you have to provide a reasonable fast function (0.5 seconds is a good target response time). + * ((analysis)) pushes their result on a local stack; which is efficiently queried by the LSP features on-demand. +* The ((util::LanguageServer::build)) service is similar to an ((analysis)), but it may perform computation-heavier additional checks or take time generate source code and binary code that makes the code in the editor executable. + * ((util::LanguageServer::build))s typically run whole-program analyses and compilation steps. + * ((util::LanguageServer::build))s have side-effects, they store generated code or code indices for future usage by the next build step, or by the next analysis step. + * ((util::LanguageServer::build))s are triggered on _save-file_ events; they _push_ information to an internal cache. + * Warning: ((util::LanguageServer::build))s are _not_ triggered when a file changes on disk outside of VS Code; instead, this results in a change event (not a save event), which triggers the ((analyzer)). + * If `providesDocumentation` is false, then the ((hover)) service may be activated. Same for `providesDefinitions` and `providesDocumentation` +)) +* the following contributions are _on-demand_ (pull) versions of information also provided by the ((analysis)) and ((util::LanguageServer::build)) summaries. + * you can provide these more lightweight on-demand services _instead of_ the ((Summary)) versions. + * these functions are run synchronously after a user interaction. The run-time of each service corresponds directly to the UX response time. + * a ((hover)) service is a fast and location specific version of the `documentation` relation in a ((Summary)). + * a ((definition)) service is a fast and location specific version of the `definitions` relation in a ((Summary)). + * a ((references)) service is a fast and location specific version of the `references` relation in a ((Summary)). + * an ((implementation)) service is a fast and location specific version of the `implementations` relation in a ((Summary)). +* The ((documentSymbol)) service maps a source file to a pretty hierarchy for visualization in the "outline" view and "symbol search" features. +* The ((codeLens)) service discovers places to add "lenses" (little views embedded in the editor on a separate line) and connects commands to execute to each lense +* The ((inlayHint)) service discovers plances to add "inlays" (little views embedded in the editor on the same line). Unlike ((lenses)) inlays do not offer command execution. +* The ((execution)) service executes the commands registered by ((lenses)) and ((inlayHinter))s. +* The ((actions)) service discovers places in the editor to add "code actions" (little hints in the margin next to where the action is relevant) and connects ((CodeAction))s to execute when the users selects the action from a menu. + +Many services receive a ((Focus)) parameter. The focus lists the syntactical constructs under the current cursor, from the current +leaf all the way up to the root of the tree. This list helps to create functionality that is syntax-directed, and always relevant to the +programmer. + +To start developing an LSP extension step-by-step: +1. first write a ((SyntaxDefinition)) in Rascal and register it via the ((parsing)) service. Use ((registerLanguage)) from the terminal ((REPL)) to +test it immediately. Create some example files for your language to play around with. +2. either make an ((analysis)) service that produces a ((Summary)) _or_ start ((hover)), ((definition)), ((references)) and ((implementation)) +lookup services. Each of those four services require the same information that is useful for filling a ((Summary)) with an ((analysis)) or a ((builder)). +3. the ((documentSymbol)) service is next, good for the outline view and also quick search features. +4. the to add interactive features, optionally ((inlayHint)), ((codeLens)) and ((codeAction)) can be created to add visible hooks in the UI to trigger +your own ((CodeAction))s and ((Commands)) + * create an ((execution)) service to give semantics to each command. This includes creating ((DocumentEdit))s but also ((IDEServices)) + can be used to have interesting effects in the IDE. + * ((CodeAction))s can also be attached to error, warning and into ((Message))s as a result of ((parsing)), ((analysis)) or ((util::LanguageServer::build)). + Such actions will lead to "quick-fix" UX options in the editor. } @benefits{ -* is focused on a single implementation request, so does not need full program analysis. +* You can create editor services thinking only of your programming language or domain-specific language constructs. All of the communication +and (de)serialization and scheduling is taken care of. +* It is always possible and useful to test your services manually in the ((REPL)). This is the preferred way of testing and debugging language services. +* Except for the ((parsing)) service, all services are independent of each other. If one fails, or is removed, the others still work. +* Language services in general can be unit-tested easily by providing example parse trees and testing properties of their output. Write lots of test functions! +* LanguageServices are editor-independent/IDE-independent via the LSP protocol. In principle they can work with any editor that implements LSP 3.17 or higher. +* Older Eclipse DSL plugins via the rascal-eclipse plugin are easily ported to ((util::LanguageServer)). } @pitfalls{ -* should be extremely fast in order to provide interactive access. -* careful use of `@memo` may help to cache dependencies, but this is tricky! -} -alias FocusImplementer = set[loc] (Focus _focus); - -@synopsis{Each kind of service contibutes the implementation of one (or several) IDE features.} -@description{ -Each LanguageService provides one aspect of definining the language server protocol. -* ((parser)) maps source code to a parse tree and indexes each part based on offset and length -* ((analyzer)) indexes a file as a ((Summary)), offering precomputed relations for looking up -documentation, definitions, references, implementations and compiler errors and warnings. - * ((analyzer))s focus on their own file, but may reuse cached or stored indices from other files. - * ((analyzer))s have to be quick since they run in an interactive editor setting. - * ((analyzer))s may store previous results (in memory) for incremental updates. - * ((analyzer))s are triggered during typing, in a short typing pause. -* ((builder)) is similar to an `analyzer`, but it may perform computation-heavier additional checks. - * ((builder))s typically run whole-program analyses and compilation steps. - * ((builder))s have side-effects, they store generated code or code indices for future usage by the next build step, or by the next analysis step. - * ((builder))s are triggered on _save-file_ events; they _push_ information to an internal cache. - * Warning: ((builder))s are _not_ triggered when a file changes on disk outside of VS Code; instead, this results in a change event (not a save event), which triggers the ((analyzer)). -* the following contributions are _on-demand_ (pull) versions of information also provided by the analyzer and builder summaries. - * a ((documenter)) is a fast and location specific version of the `documentation` relation in a ((Summary)). - * a ((definer)) is a fast and location specific version of the `definitions` relation in a ((Summary)). - * a ((referrer)) is a fast and location specific version of the `references` relation in a ((Summary)). - * an ((implementer)) is a fast and location specific version of the `implementations` relation in a ((Summary)). -* ((outliner)) maps a source file to a pretty hierarchy for visualization in the "outline" view -* ((lenses)) discovers places to add "lenses" (little views embedded in the editor on a separate line) and connects commands to execute to each lense -* ((inlayHinter)) discovers plances to add "inlays" (little views embedded in the editor on the same line). Unlike ((lenses)) inlays do not offer command execution. -* ((executor)) executes the commands registered by ((lenses)) and ((inlayHinter))s. - -Many language contributions received a ((Focus)) parameter. This helps to create functionality that -is syntax-directed: relevant to the current syntactical constructs under the cursor. +* If one of the services does not type-check in Rascal, or throws an exception at ((registerLanguage)) time, the extension fails completely. Typically the editor produces a parse error on the first line of the code. The +failure is printed in the log window of the IDE. +* Users have expectations with the concepts of ((references)), ((definition)), ((implementation)) which are based on +typical programming language concepts. Since these are all just `rel[loc, loc]` it can be easy to confound them. + * ((references)) point from declarations sites to use sites + * ((definition)) points the other way around, from a use to the declaration, but only if a value is associated there explicitly or implicitly. + * ((implementation)) points from abstract declarations (interfaces, classes, function signatures) to more concrete realizations of those declarations. +* `providesDocumentation` is deprecated. Use `providesHovers` instead. } data LanguageService - = parser(Parser parser) - | analyzer(Summarizer summarizer + = parsing(Tree (str _input, loc _origin) parsingService) + | analysis(Summary (loc _origin, Tree _input) analysisService , bool providesDocumentation = true + , bool providesHovers = providesDocumentation , bool providesDefinitions = true , bool providesReferences = true , bool providesImplementations = true) - | builder(Summarizer summarizer + | build(Summary (loc _origin, Tree _input) buildService , bool providesDocumentation = true + , bool providesHovers = providesDocumentation , bool providesDefinitions = true , bool providesReferences = true , bool providesImplementations = true) - | outliner(Outliner outliner) - | lenses(OrderedLensDetector detector) - | inlayHinter(InlayHinter hinter) - | executor(CommandExecutor executor) - | documenter(FocusDocumenter documenter) - | definer(FocusDefiner definer) - | referrer(FocusReferrer reference) - | implementer(FocusImplementer implementer) - | actions(CodeActionContributor actions) + | documentSymbol(list[DocumentSymbol] (Tree _input) documentSymbolService) + | codeLens (lrel[loc src, Command lens] (Tree _input) codeLensService) + | inlayHint (list[InlayHint] (Tree _input) inlayHintService) + | execution (value (Command _command) executionService) + | hover (set[str] (Focus _focus) hoverService) + | definition (set[loc] (Focus _focus) definitionService) + | references (set[loc] (Focus _focus) referencesService) + | implementation(set[loc] (Focus _focus) implementationService) + | codeAction (list[CodeAction] (Focus _focus) codeActionService) ; +@deprecated{Backward compatible with ((parsing)).} +@synopsis{Construct a `parsing` ((LanguageService))} +LanguageService parser(Parser parser) = parsing(parser); -@deprecated{ -This is a backward compatibility layer for the pre-existing ((LensDetector)) alias. +@deprecated{Backward compatible with ((codeLens))} +@synopsis{Construct a ((codeLens)) ((LanguageService))} +@description{ +Not only translates to the old name of the LanguageService, +it also maps the list to an arbitrarily ordered set as it was before. +} +@benefits{ +* If you need your lenses in a stable order in the editor, +use the ((codeLens)) constructor instead to provide a function that +uses an ordered list. +} +LanguageService lenses(LensDetector detector) = codeLens(lrel[loc src, Command lens] (Tree input) { + return [*detector(input)]; +}); -The primary change is that lenses are now ordered, such that you have control over in which order they appear in the client. +@deprecated{Backward compatible with ((codeAction))} +@synopsis{Construct a ((codeAction)) ((LanguageService))} +LanguageService actions(CodeActionContributor contributor) = codeAction(contributor); -To adapt to the new one, use a function that generates a `lrel` instead of a `rel`. -} -LanguageService lenses(LensDetector oldDetector) - = lenses(lrel[loc src, Command lens] (Tree input) { return [*oldDetector(input)]; }); +@deprecated{Backward compatible with ((util::LanguageServer::build))} +@synopsis{Construct a ((util::LanguageServer::build)) ((LanguageService))} +LanguageService builder(Summarizer summarizer) = build(summarizer); + +@deprecated{Backward compatible with ((documentSymbol))} +@synopsis{Construct a ((documentSymbol)) ((LanguageService))} +LanguageService outliner(Outliner outliner) = documentSymbol(outliner); + +@deprecated{Backward compatible with ((inlayHint))} +@synopsis{Construct a ((inlayHint)) ((LanguageService))} +LanguageService inlayHinter(InlayHinter hinter) = inlayHint(hinter); + +@deprecated{Backward compatible with ((execution))} +@synopsis{Construct a ((execution)) ((LanguageService))} +LanguageService executor(CommandExecutor executor) = execution(executor); @deprecated{ This is a backward compatibility layer for the pre-existing ((Documenter)) alias. -To replace an old-style ((Documenter)) with a new style ((FocusDocumenter)) follow +To replace an old-style ((Documenter)) with a new style ((hover)) service follow this scheme: ```rascal -set[loc] oldImplementer(loc document, Tree selection, Tree fullTree) { +set[loc] oldDocumenter(loc document, Tree selection, Tree fullTree) { ... } // by this scheme: -set[loc] newImplementer([Tree selection, *Tree _spine, Tree fullTree]) { +set[loc] newHoverService([Tree selection, *Tree _spine, Tree fullTree]) { loc document = selection@\loc.top; ... } -default set[loc] newImplementer(list[Tree] _focus) = {}; +default set[loc] newHoverService(list[Tree] _focus) = {}; ``` } LanguageService documenter(Documenter d) { @@ -351,14 +324,13 @@ LanguageService documenter(Documenter d) { return {}; } - return documenter(focusAcceptor); + return hover(focusAcceptor); } - @deprecated{ This is a backward compatibility layer for the pre-existing ((Definer)) alias. -To replace an old-style ((Definer)) with a new style ((FocusDefiner)) follow +To replace an old-style ((Definer)) with a new style ((definition)) service follow this scheme: ```rascal @@ -366,11 +338,11 @@ set[loc] oldDefiner(loc document, Tree selection, Tree fullTree) { ... } // by this scheme: -set[loc] newDefiner([Tree selection, *Tree _spine, Tree fullTree]) { +set[loc] newDefinitionService([Tree selection, *Tree _spine, Tree fullTree]) { loc document = selection@\loc.top; ... } -default set[loc] newDefiner(list[Tree] _focus) = {}; +default set[loc] newDefinitionService(list[Tree] _focus) = {}; ``` } LanguageService definer(Definer d) { @@ -382,7 +354,7 @@ LanguageService definer(Definer d) { return {}; } - return definer(focusAcceptor); + return definition(focusAcceptor); } @@ -390,7 +362,7 @@ LanguageService definer(Definer d) { @deprecated{ This is a backward compatibility layer for the pre-existing ((Referrer)) alias. -To replace an old-style ((Referrer)) with a new style ((FocusReferrer)) follow +To replace an old-style ((Referrer)) with a new style ((references)) service follow this scheme. ```rascal @@ -398,11 +370,11 @@ set[loc] oldReferrer(loc document, Tree selection, Tree fullTree) { ... } // by this scheme: -set[loc] newReferrer([Tree selection, *Tree _spine, Tree fullTree]) { +set[loc] newReferencesService([Tree selection, *Tree _spine, Tree fullTree]) { loc document = selection@\loc.top; ... } -default set[loc] newReferrer(list[Tree] _focus) = {}; +default set[loc] newReferencesService(list[Tree] _focus) = {}; ``` } LanguageService referrer(Referrer d) { @@ -414,14 +386,14 @@ LanguageService referrer(Referrer d) { return {}; } - return referrer(focusAcceptor); + return references(focusAcceptor); } @synopsis{Registers an old-style ((Implementer))} @deprecated{ This is a backward compatibility layer for the pre-existing ((Implementer)) alias. -To replace an old-style ((Implementer)) with a new style ((FocusImplementer)) follow +To replace an old-style ((Implementer)) with a new style ((implementation)) service follow this scheme: ```rascal @@ -429,10 +401,12 @@ set[loc] oldImplementer(loc document, Tree selection, Tree fullTree) { ... } // by this scheme: -set[loc] newImplementer([Tree selection, *Tree _spine, Tree fullTree]) { +set[loc] newImplementationService([Tree selection, *Tree _spine, Tree fullTree]) { loc document = selection@\loc.top; ... } +default set[loc] newImplementationService(list[Tree] _focus) = {}; + ``` } LanguageService implementer(Implementer d) { @@ -444,18 +418,34 @@ LanguageService implementer(Implementer d) { return {}; } - return implementer(focusAcceptor); + return implementation(focusAcceptor); } -@deprecated{Please use ((builder)) or ((analyzer))} +@deprecated{Please use ((build)) or ((analysis))} @synopsis{A summarizer collects information for later use in interactive IDE features.} LanguageService summarizer(Summarizer summarizer , bool providesDocumentation = true + , bool providesHovers = providesDocumentation , bool providesDefinitions = true , bool providesReferences = true , bool providesImplementations = true) { println("Summarizers are deprecated. Please use builders (triggered on save) and analyzers (triggered on change) instead."); - return builder(summarizer + return build(summarizer + , providesDocumentation = providesDocumentation + , providesHovers = providesHovers + , providesDefinitions = providesDefinitions + , providesReferences = providesReferences + , providesImplementations = providesImplementations); +} + +@deprecated{Please use ((build)) or ((analysis))} +@synopsis{An analyzer collects information for later use in interactive IDE features.} +LanguageService analyzer(Summarizer summarizer + , bool providesDocumentation = true + , bool providesDefinitions = true + , bool providesReferences = true + , bool providesImplementations = true) { + return analysis(summarizer , providesDocumentation = providesDocumentation , providesDefinitions = providesDefinitions , providesReferences = providesReferences @@ -466,7 +456,8 @@ LanguageService summarizer(Summarizer summarizer @description{ * `src` refers to the "compilation unit" or "file" that this model is for. * `messages` collects all the errors, warnings and error messages. -* `documentation` maps uses of concepts to a documentation message that can be shown as a hover. +* `documentation` is the deprecated name for `hovers` +* `hovers` maps uses of concepts to a documentation message that can be shown as a hover. * `definitions` maps use locations to declaration locations to implement "jump-to-definition". * `references` maps declaration locations to use locations to implement "jump-to-references". * `implementations` maps the declaration of a type/class to its implementations "jump-to-implementations". @@ -474,6 +465,7 @@ LanguageService summarizer(Summarizer summarizer data Summary = summary(loc src, rel[loc, Message] messages = {}, rel[loc, str] documentation = {}, + rel[loc, str] hovers = documentation, rel[loc, loc] definitions = {}, rel[loc, loc] references = {}, rel[loc, loc] implementations = {} @@ -732,3 +724,22 @@ void unregisterLanguage(str name, set[str] extensions, str mainModule = "", str void unregisterLanguage(str name, str extension, str mainModule = "", str mainFunction = "") { unregisterLanguage(name, {extension}, mainModule = mainModule, mainFunction = mainFunction); } + +@javaClass{org.rascalmpl.vscode.lsp.parametric.RascalInterface} +@synopsis{Produce a ((Focus)) for a given tree and cursor position} +@description{ +This function exists to be able to unit test ((LanguageService))s that +accept a ((Focus)) parameter, indepently of using ((registerLanguage)). + +* `line` is a 1-based indication of what the current line is +* `column` is a 0-based indication of what the current column is. +} +@benefits{ +* test services without spinning up an LSP server or having to run UI tests. +Each UI interaction is tested generically for you already. +} +@pitfalls{ +* LSP indexing is different, but those differences are resolved in the implementation of the protocol. On the Rascal side, we see the above. +Differences are width of the character encoding for non-ASCII characters, and lines are 0-based, etc. +} +java Focus computeFocusList(Tree input, int line, int column); diff --git a/rascal-vscode-extension/src/test/vscode-suite/dsl.test.ts b/rascal-vscode-extension/src/test/vscode-suite/dsl.test.ts index 287331323..32abc9c21 100644 --- a/rascal-vscode-extension/src/test/vscode-suite/dsl.test.ts +++ b/rascal-vscode-extension/src/test/vscode-suite/dsl.test.ts @@ -46,14 +46,15 @@ describe('DSL', function () { async function loadPico() { const repl = new RascalREPL(bench, driver); await repl.start(); - await repl.execute("import demo::lang::pico::LanguageServer;"); - repl.execute("main();"); // we don't wait, be cause we might miss pico loading window + await repl.execute("import demo::lang::pico::OldStyleLanguageServer;"); + const replExecuteMain = repl.execute("main();"); // we don't wait yet, because we might miss pico loading window const ide = new IDEOperations(browser); const isPicoLoading = ide.statusContains("Pico"); await driver.wait(isPicoLoading, Delays.slow, "Pico DSL should start loading"); - await repl.terminate(); - // now wait for the Pico loader to dissapear + // now wait for the Pico loader to disappear await driver.wait(async () => !(await isPicoLoading()), Delays.extremelySlow, "Pico DSL should be finished starting", 100); + await replExecuteMain; + await repl.terminate(); } @@ -70,6 +71,12 @@ describe('DSL', function () { await ide.load(); }); + beforeEach(async function () { + if (this.test?.title) { + await ide.screenshot("DSL-" + this.test?.title); + } + }); + afterEach(async function () { if (this.test?.title) { await ide.screenshot("DSL-" + this.test?.title); @@ -87,6 +94,11 @@ describe('DSL', function () { try { await editor.setTextAtLine(10, "b := ;"); await ide.hasErrorSquiggly(editor, Delays.slow); + } catch (e) { + console.log(`Failed to trigger parse error: ${e}`); + if (e instanceof Error) { + console.log(e.stack); + } } finally { await ide.revertOpenChanges(); } diff --git a/rascal-vscode-extension/src/test/vscode-suite/utils.ts b/rascal-vscode-extension/src/test/vscode-suite/utils.ts index 680991d24..9ec8a51bd 100644 --- a/rascal-vscode-extension/src/test/vscode-suite/utils.ts +++ b/rascal-vscode-extension/src/test/vscode-suite/utils.ts @@ -315,8 +315,12 @@ export class IDEOperations { }; } + private screenshotSeqNumber = 0; + screenshot(name: string): Promise { - return this.browser.takeScreenshot(name.replace(/[/\\?%*:|"<>]/g, '-')); + return this.browser.takeScreenshot( + `${String(this.screenshotSeqNumber++).padStart(4, '0')}-` + // Make sorting screenshots chronologically in VS Code easier + name.replace(/[/\\?%*:|"<>]/g, '-')); } } diff --git a/runUItests.sh b/runUItests.sh index 641c9ef20..7e05214f0 100755 --- a/runUItests.sh +++ b/runUItests.sh @@ -21,4 +21,7 @@ npm run compile-tests # test what was compiled -exec npx extest setup-and-run out/test/vscode-suite/*.test.js --storage $UITESTS +exec npx extest setup-and-run out/test/vscode-suite/*.test.js \ + --code_version 1.82.3 \ + --storage $UITESTS \ + --extensions_dir $UITESTS/extensions_dir \ No newline at end of file