module util::LanguageServer
Bridges {DSL,PL,Modeling} language features to the language server protocol.
Usage
import util::LanguageServer;
Source code
Dependencies
import util::Reflective;
import IO;
import ParseTree;
Description
Using the Register Language function you can connect any parsers, checkers, source-to-source transformers, visualizers, etc. that are made with Rascal, to the Language Server Protocol.
Benefits
- Turn your language implementation into an interactive IDE at almost zero cost.
data Language
Definition of a language server by its meta-data
data Language
= language(PathConfig pcfg, str name, set[str] extensions, str mainModule, str mainFunction)
;
The Register Language function takes this as its parameter to generate and run a fresh language protocol server.
Benefits
- each registered language is run in its own Rascal run-time environment.
- reloading a language is always done in a fresh environment.
Pitfalls
- even though Register Language is called in a given run-time environment, the registered language runs in another instance of the JVM and of Rascal.
function language
Language language(PathConfig pcfg, str name, str extension, str mainModule, str mainFunction)
alias Parser
Function profile for parser contributions to a language server
Tree (str /*input*/, loc /*origin*/)
alias Summarizer
Function profile for summarizer contributions to a language server
Summary (loc /*origin*/, Tree /*input*/)
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 Builders. 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 Analyzers. 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:
The difference is that these contributions are executed on-demand (pulled), while Summarizers are executed after build or after typing (push).
alias Outliner
Function profile for outliner contributions to a language server
list[DocumentSymbol] (Tree /*input*/)
alias LensDetector
Function profile for lenses contributions to a language server
rel[loc src, Command lens] (Tree /*input*/)
alias CommandExecutor
Function profile for executor contributions to a language server
value (Command /*command*/)
alias InlayHinter
Function profile for inlay contributions to a language server
list[InlayHint] (Tree /*input*/)
alias Documenter
Function profile for documentation contributions to a language server
set[str] (loc /*origin*/, Tree /*fullTree*/, Tree /*lexicalAtCursor*/)
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!
alias Definer
Function profile for definer contributions to a language server
set[loc] (loc /*origin*/, Tree /*fullTree*/, Tree /*lexicalAtCursor*/)
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 Referrer
Function profile for referrer contributions to a language server
set[loc] (loc /*origin*/, Tree /*fullTree*/, Tree /*lexicalAtCursor*/)
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 Implementer
Function profile for implementer contributions to a language server
set[loc] (loc /*origin*/, Tree /*fullTree*/, Tree /*lexicalAtCursor*/)
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!
data LanguageService
Each kind of service contibutes the implementation of one (or several) IDE features.
data LanguageService
= parser(Parser parser)
| analyzer(Summarizer summarizer
, bool providesDocumentation = true
, bool providesDefinitions = true
, bool providesReferences = true
, bool providesImplementations = true)
| builder(Summarizer summarizer
, bool providesDocumentation = true
, bool providesDefinitions = true
, bool providesReferences = true
, bool providesImplementations = true)
| outliner(Outliner outliner)
| lenses(LensDetector detector)
| inlayHinter(InlayHinter hinter)
| executor(CommandExecutor executor)
| documenter(Documenter documenter)
| definer(Definer definer)
| referrer(Referrer reference)
| implementer(Implementer implementer)
;
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.
- Analyzers focus on their own file, but may reuse cached or stored indices from other files.
- Analyzers have to be quick since they run in an interactive editor setting.
- Analyzers may store previous results (in memory) for incremental updates.
- Analyzers are triggered during typing, in a short typing pause.
- Builder is similar to an
analyzer
, but it may perform computation-heavier additional checks.- Builders typically run whole-program analyses and compilation steps.
- Builders have side-effects, they store generated code or code indices for future usage by the next build step, or by the next analysis step.
- Builders are triggered on save-file events; they push information to an internal cache.
- Warning: Builders 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.
- a Documenter is a fast and location specific version of the
- 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
- Inlay Hinter 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 Inlay Hinters
function summarizer
A summarizer collects information for later use in interactive IDE features.
LanguageService summarizer(Summarizer summarizer
, bool providesDocumentation = true
, bool providesDefinitions = true
, bool providesReferences = true
, bool providesImplementations = true)
data Summary
A model encodes all IDE-relevant information about a single source file.
data Summary
= summary(loc src,
rel[loc, Message] messages = {},
rel[loc, str] documentation = {},
rel[loc, loc] definitions = {},
rel[loc, loc] references = {},
rel[loc, loc] implementations = {}
)
;
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.definition
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".
data Completion
data Completion
= completion(str newText, str proposal=newText)
;
data DocumentSymbol
DocumentSymbol encodes a sorted and hierarchical outline of a source file
data DocumentSymbol
= symbol(
str name,
DocumentSymbolKind kind,
loc range,
loc selection=range,
str detail="",
list[DocumentSymbol] children=[]
)
;
data DocumentSymbolKind
data DocumentSymbolKind
= \file()
| \module()
| \namespace()
| \package()
| \class()
| \method()
| \property()
| \field()
| \constructor()
| \enum()
| \interface()
| \function()
| \variable()
| \constant()
| \string()
| \number()
| \boolean()
| \array()
| \object()
| \key()
| \null()
| \enumMember()
| \struct()
| \event()
| \operator()
| \typeParameter()
;
data DocumentSymbolTag
data DocumentSymbolTag
= \deprecated()
;
data CompletionProposal
data CompletionProposal
= sourceProposal(str newText, str proposal=newText)
;
data Command
data Command (str title="")
= noop()
;
data InlayHint
Represents one inlayHint for display in an editor
data InlayHint
= hint(
loc position,
str label,
InlayKind kind,
str toolTip = "",
bool atEnd = false
)
;
position
where the hint should be placed, by default at the beginning of this location, theatEnd
can be set to true to change thislabel
text that should be printed in the ide, spaces in front and back of the text are trimmed and turned into subtle spacing to the content around it.kind
his eithertype()
orparameter()
which influences styling in the editor.toolTip
optionally show extra information when hovering over the inlayhint.atEnd
instead of appearing at the beginning of the position, appear at the end.
data InlayKind
Style of an inlay
data InlayKind
= \type()
| parameter()
;
function registerLanguage
Generates and instantiates a new language server for the given language
void registerLanguage(Language lang)
We register languages by uploading the meta-data of the implementation to a "lanuage-parametric" language server.
- The meta-data is used to instantiate a fresh run-time to execute the main-module.
- The extension is registered with the IDE to link to this new runtime.
- Each specific extension is mapped to a specific part of the language server protocol.
By registering a language twice, more things can happen:
- existing contributions are re-loaded and overwritten with the newest version.
- new contributions to an existing language (
Language
constructor instance), will be added to the existing LSP server instance. You can use this to load expensive features later or more lazily. - errors appear during loading or first execution of the contribution. The specific contribution is then usually aborted and unregistered.
Because registerLanguage has effect in a different OS process, errors and warnings are not printed in the calling execution context. In general look at the "Parametric Rascal Language Server" log tab in the IDE to see what is going on.
However since language contributions are just Rascal functions, it is advised to simply test them first right there in the terminal.
Use util::Reflective::getProjectPathConfig
for a representative configuration.
function unregisterLanguage
Spins down and removes a previously registered language server
void unregisterLanguage(Language lang)
function unregisterLanguage
Spins down and removes a previously registered language server
void unregisterLanguage(str name, set[str] extensions, str mainModule = "", str mainFunction = "")
function unregisterLanguage
Spins down and removes a previously registered language server
void unregisterLanguage(str name, str extension, str mainModule = "", str mainFunction = "")