Skip to main content

module util::LanguageServer

rascal-0.34.0
rascal-lsp-2.20.0

Bridges {DSL,PL,Modeling} language features to the language server protocol.

Usage

import util::LanguageServer;

Source code

http://github.com/usethesource/rascal-language-servers/blob/main/rascal-lsp/src/main/rascal/util/LanguageServer.rsc

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.
  • 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, the atEnd can be set to true to change this
  • label 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 either type() or parameter() 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.

  1. The meta-data is used to instantiate a fresh run-time to execute the main-module.
  2. The extension is registered with the IDE to link to this new runtime.
  3. 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 = "")