module demo::lang::pico::OldStyleLanguageServer
Demonstrating all of the LSP services for the demo language Pico.
deprecated: marked for future deletion This demo has been superseded by LanguageServer which avoids the use of deprecated API.
Usage
import demo::lang::pico::OldStyleLanguageServer;
Source code
Dependencies
import util::LanguageServer;
import util::IDEServices;
import ParseTree;
import util::Reflective;
import lang::pico::\syntax::Main;
Description
This module is here to test the backward compatibility layer over the new LanguageServer API. For learning how to build a new set of lanuage services, please go here.
function picoLanguageContributor
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)
};
function picoLanguageContributorSlowSummary
This set of contributions runs slower but provides more detail.
set[LanguageService] picoLanguageContributorSlowSummary() = {
parser(parser(#start[Program])),
analyzer(picoAnalyzer, providesImplementations = false),
builder(picoBuilder)
};
function picoOutliner
The outliner maps pico syntax trees to lists of DocumentSymbols.
list[DocumentSymbol] picoOutliner(start[Program] input)
= [symbol("<input.src>", DocumentSymbolKind::\file(), input.src, children=[
*[symbol("<var.id>", \variable(), var.src) | /IdType var := input]
])];
function picoAnalyzer
The analyzer maps pico syntax trees to error messages and references.
Summary picoAnalyzer(loc l, start[Program] input) = picoSummarizer(l, input, analyze());
function picoBuilder
The builder does a more thorough analysis then the analyzer, providing more detail.
Summary picoBuilder(loc l, start[Program] input) = picoSummarizer(l, input, build());
data PicoSummarizerMode
A simple "enum" data type for switching between analysis modes.
data PicoSummarizerMode
= analyze()
| build()
;
function picoSummarizer
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.id>", var.src> | /IdType var := input};
// uses of identifiers
rel[loc, str] uses = {<id.src, "<id>"> | /Id id := input};
// documentation strings for identifier uses
rel[loc, str] docs = {<var.src, "*variable* <var>"> | /IdType var := input};
// Provide errors (cheap to compute) both in analyze mode and in build mode.
s.messages += {<src, error("<id> is not defined", src, fixes=prepareNotDefinedFixes(src, defs))>
| <src, id> <- 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 = {<id.src, "<id>"> | /Statement stmt := input, (Statement) `<Id id> := <Expression _>` := stmt};
s.messages += {<src, warning("<id> is not assigned", src)> | <id, src> <- defs, id notin asgn<1>};
}
return s;
}
function lookupDef
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};
function prepareNotDefinedFixes
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 <existing<0>>", edits=[changed(src.top, [replace(src, existing<0>)])]) | existing <- defs];
function picoActions
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 <x>"))];
default list[CodeAction] picoActions(Focus _focus) = [];
data Command
data Command
= renameAtoB(start[Program] program)
| removeDecl(start[Program] program, IdType toBeRemoved)
;
function picoLenses
Adds an example lense to the entire program.
rel[loc,Command] picoLenses(start[Program] input)
= {<input@\loc, renameAtoB(input, title="Rename variables a to b.")>};
function picoHinter
Generates inlay hints that explain the type of each variable usage.
list[InlayHint] picoHinter(start[Program] input) {
typeLookup = ( "<name>" : "<tp>" | /(IdType)`<Id name> : <Type tp>` := input);
return [
hint(name.src, " : <typeLookup["<name>"]>", \type(), atEnd = true)
| /(Expression)`<Id name>` := input
, "<name>" in typeLookup
];
}
function getAtoBEdits
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])];
function picoCommands
Command handler for the renameAtoB command.
value picoCommands(renameAtoB(start[Program] input)) {
applyDocumentsEdits(getAtoBEdits(input));
return ("result": true);
}
function picoCommands
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);
}
function main
The main function registers the Pico language with the IDE.
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"
)
);
}
Register the Pico language and the contributions that supply the IDE with features.
Register Language is called twice here:
- first for fast and cheap contributions
- 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.