Skip to content
Draft
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
c8899ba
First version of "completion" design
PieterOlivier Aug 22, 2025
392711b
Completed first version of "completion for dsls" design.
PieterOlivier Aug 25, 2025
c1ab656
Updated completion service design based on better understanding of th…
PieterOlivier Aug 25, 2025
87000af
Fixed documentation
PieterOlivier Aug 26, 2025
92eef7b
Update rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc
PieterOlivier Aug 26, 2025
11517b8
Update rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc
PieterOlivier Aug 26, 2025
8dd1872
Update rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc
PieterOlivier Aug 26, 2025
7ac0970
Update rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc
PieterOlivier Aug 26, 2025
370169c
Update rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc
PieterOlivier Aug 26, 2025
fa12957
Update rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc
PieterOlivier Aug 26, 2025
9fd52b3
Update rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc
PieterOlivier Aug 26, 2025
90d3d71
Processed review comments on "completion for DSL design" PR.
PieterOlivier Aug 26, 2025
07916c0
Merge branch 'completions/parametric' of github.com:usethesource/rasc…
PieterOlivier Aug 26, 2025
c329475
Documented differences between CompletionItemKind and DocumentSymbolK…
PieterOlivier Aug 26, 2025
5bd8b5d
Update rascal-lsp/src/main/rascal/library/demo/lang/pico/LanguageServ…
PieterOlivier Aug 27, 2025
75de3de
Replaced string command with Command.
PieterOlivier Aug 27, 2025
bfac3d9
Merge branch 'completions/parametric' of github.com:usethesource/rasc…
PieterOlivier Aug 27, 2025
77b9990
Merge branch 'main' into completions/parametric
PieterOlivier Aug 27, 2025
250b04e
Brought completion design more in line with lsp spec
PieterOlivier Sep 1, 2025
9f16fa5
Unified DocumentSymbolKind and CompletionItemKind again
PieterOlivier Sep 6, 2025
f0f1468
Updated Rascal completion example
PieterOlivier Sep 6, 2025
e50b42d
Update rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc
PieterOlivier Sep 22, 2025
d37a1df
Added synopsis to new ADTs
PieterOlivier Sep 22, 2025
92758e2
Merge branch 'completions/parametric' of github.com:usethesource/rasc…
PieterOlivier Sep 22, 2025
fa18f89
Replaced cursor loc with an offset inside the inner focus tree
PieterOlivier Sep 24, 2025
9a5ae8d
Replaced CompletionDocumentation adt with just a simple str
PieterOlivier Sep 24, 2025
a224319
Merge remote-tracking branch 'origin/main' into completions/parametric
toinehartman Oct 20, 2025
79d07e7
Implement parametric completion.
toinehartman Oct 20, 2025
22d6e43
Multiplex completion requests by trigger character.
toinehartman Oct 21, 2025
c42e37e
Set missing positional fields.
toinehartman Oct 21, 2025
ff45aa5
Set markdown content.
toinehartman Oct 21, 2025
ed6a0b3
Dynamically register completion capability.
toinehartman Oct 21, 2025
1909749
Map completion edit and kind.
toinehartman Oct 21, 2025
e7cee55
Simplify conversion of edits.
toinehartman Oct 21, 2025
7d5ffd4
Safe keyword access.
toinehartman Oct 22, 2025
7de757c
Improved routing of completion requests.
toinehartman Oct 22, 2025
8f1440a
Set completion insert mode.
toinehartman Oct 22, 2025
80c9d36
Fix positions and offsets.
toinehartman Oct 22, 2025
3140133
Improved dynamic registration bookkeeping.
toinehartman Oct 22, 2025
16255ff
Synchronizing multiple contribution registrations.
toinehartman Oct 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ import util::ParseErrorRecovery;
import util::Reflective;
import lang::pico::\syntax::Main;
import DateTime;
import IO;
import String;

private Tree (str _input, loc _origin) picoParser(bool allowRecovery) {
return ParseTree::parser(#start[Program], allowRecovery=allowRecovery, filters=allowRecovery ? {createParseErrorFilter(false)} : {});
Expand All @@ -61,7 +63,8 @@ set[LanguageService] picoLanguageServer(bool allowRecovery) = {
codeAction(picoCodeActionService),
rename(picoRenamingService, prepareRenameService = picoRenamePreparingService),
didRenameFiles(picoFileRenameService),
selectionRange(picoSelectionRangeService)
selectionRange(picoSelectionRangeService),
completion(picoCompletionService, triggerCharacters = ["="])
};

set[LanguageService] picoLanguageServer() = picoLanguageServer(false);
Expand Down Expand Up @@ -227,6 +230,30 @@ tuple[list[DocumentEdit],set[Message]] picoFileRenameService(list[DocumentEdit]
list[loc] picoSelectionRangeService(Focus focus)
= dup([t@\loc | t <- focus]);

CompletionSuggestion createVarCompletion(int cursorColumn, loc selectedIdent, IdType decl) {
str name = "<decl.id>";
CompletionEdit edit = completionEdit(
selectedIdent.begin.column,
cursorColumn,
selectedIdent.end.column,
name
);

return completion(variable(), edit, name, labelDetail="<decl.t>");
}

list[CompletionSuggestion] picoCompletionService(loc cursor, Focus focus, CompletionTrigger trigger) {
str prefix = "<focus[0]>";
return [createVarCompletion(cursor.begin.column, focus[0].src, var) | /IdType var := focus[-1], startsWith("<var.id>", "<prefix>")];
}

void testCompletion() {
start[Program] prg = parse(#start[Program], "begin declare var1 : natural, var2 : natural; va := 1 end");
Focus focus = computeFocusList(prg, 1, 47); // after v
completions = picoCompletionService(focus[0].src(47, 0, <1,47>,<1,47>), focus, invoked());
println("completions: <completions>");
}

@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.
Expand Down
112 changes: 108 additions & 4 deletions rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,114 @@ data LanguageService
| selectionRange(list[loc](Focus _focus) selectionRangeService)
;

@description{
Definition of completion service. Kept separate from the LanguageService for now to allow for easy discussion.
The completion service is called with the current cursor location, the focus, and the how the user triggered completion (explicit invocation or by typing a trigger character).
It should return a list of completion suggestions.
The optional list of trigger characters can contain a list of extra characters that trigger completion.
These characters are an addition to the defaults provided by the client (typically [a-zA-Z]). A typical example would be to include "." for languages like Java to start field/method completion.

We have choosen to support all features of the LSP CompletionItem except:
* We use "DocumentSymbolKind" instead of introducing a new enum to represent CompletionKind. This is only used to show a tiny icon next to each completion alternative, and the few concept that differ can usually be mapped to some related or more general concept.
* To keep this API simple, we have left out support for incomplete (partial) completions, so "CompletionList.isIncomplete" will always be set to false.
* Again to keep the API simple we have not implemented support for defaults, so CompletionItem, edit range (and commitCharacters if you want them) must be set explicitly on each CompletionItem.

Note: Depending on the capabilities of the client, we will generate "InsertReplaceEdit" items or "TextEdit" items.
}
data LanguageService
= completion (list[CompletionSuggestion] (loc cursor, Focus _focus, CompletionTrigger trigger) completionService, list[str] additionalTriggerCharacters = []);

@description{
Represents a concrete completion proposal that the user can select. The following fields can be used:
* *kind* (required): Used to indicate what kind of completion this is. This is typically used to show an appropriate icon next to the completion item.
* *edit* (required): Specification of the edit that will occur if this completion proposal is accepted by the user.
* *label* (required): The label shown to identify the completion item. The label should make it clear for the user what the result of the completion will be.
* *labelDetail*: Shown directly after the label and can for instance be used to show the function parameters.
* *labelDescription*: Shown after the label details. This is typically used to show information about the (return) type when a completion is a function or variable.
* *details*: Text that is shown when the user asks for more information about a particular completion.
* *documentation*: Text or markup that documents the construct that a completion will generate.
* *sortText*: Used to sort the list of completions. If not set, completion suggestions will be sorted based on *label*.
* *filterText*: Used to filter the list of completions based on user input while selecting a completion item. A known VSCode quirk is that
if *completionEdit* starts before the cursor, the text currently before the cursor is included as "user input" for filtering.
For example if the user starts completion like this: `pr|i` (where `|` represents the cursor) and you generate the completion edit to
replace the whole string `pri` with `print`, *filterText* should be set to `pr` or the completion will not show up in the list.
If you leave this blank, the correct filter text will be generated for you. If not, we assume you know what you are doing and use the
*filterText* you provide as-is.
* *deprecated*: Set this to `true` to mark this completion suggestion as deprecated.
* *commitCharacters*: When one of these characters is types, the completion suggestion is accepted and the the commit character is inserted after the inserted text.
* *additionalChanges*: Any additional changes anywhere else in the document. This can for instance be used to add imports.
* *command*: Command executed after the completion edits are done. For instance, in some cases it might be practical to move the cursor after the edit.
}
data CompletionSuggestion = completion(
CompletionKind kind,
CompletionEdit edit,

str label,
str labelDetail = "",
str labelDescription = "",

str details = "",
CompletionDocumentation documentation = none(),

str sortText = "",
str filterText = "",

bool deprecated = false,
bool preselect = false,

list[str] commitCharacters = [],

list[TextEdit] additionalChanges = [],

str command = ""
);

@description{
Definition of a completion edit:
* *startColumn (required): The column where the completion edit operation will take place. Must be at or before the cursor position.
* *insertEndColumn* (required): End column when the user chooses completion by insertion (for instance by pressing "Enter" in VSCode).
* *replaceEndColumn* (required): End column when the user chooses completion by replacement (for instance by pressing "Shift-Enter" in VSCode).
Note: *insertEndColumn* must not be larger than *replaceEndColumn* and both must be at or to the right of the cursor position.
* *newText* (required): The text that will be used to perform the completion. Depending on what kind of completion (insertion
or replacement) selected by the user, the original text from *startColumn* to either *insertEndColumn* or *replaceEndColumn* will
be replaced by *newText*.
* *snippet*: Can be set to true to indicate that the replacement text should be interpreted as a snippet.
Snippets can contain tabstops, placeholders, choices, and variables. For more information about snippets see the
[LSP specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#snippet_syntax).
Note that it is the responsibility of the language developer that snippets as presented to the user are syntactically correct to
provide a decent user experience.
}
data CompletionEdit = completionEdit(
int startColumn,
int insertEndColumn,
int replaceEndColumn,
str newText,
bool snippet = false
);

@description{
The lsp *CompletionItemKind* and our ((DocumentSymbolKind)) describe largely the same concepts. As these are only used (in VSCode)
to display a tiny icon next to each completion item, the small differences remaining can be overcome by selecting a related
or more general concept.

*CompletionItemKind* but not a ((DocumentSymbolKind)): `Text`, `Unit`, `Value`, `Keyword`, `Snippet`, `Color`, `Reference`, `Folder`, `EnumMember`.

((DocumentSymbolKind)) but not a *CompletionItemKind*: `\namespace()`, `\package()`, `\string()`, `\number()`, `\boolean()`, `\array()`, `\object()`, `\key()`, `\null()`, `\enumMember()`.
Note that most of these can be mapped on the *CompletionItemKind* `Constant`.
}
alias CompletionKind = DocumentSymbolKind;

@synopsis{
Manual invocation or invocation by trigger characters
}
data CompletionTrigger = invoked() | character();

@synopsis{
Used to provide either plain text or markup as documentaton for a completion suggestion, or indicate no documentation is provided.
}
data CompletionDocumentation = none() | text(str text) | markup(str markup);


loc defaultPrepareRenameService(Focus _:[Tree tr, *_]) = tr.src when tr.src?;
default loc defaultPrepareRenameService(Focus focus) { throw IllegalArgument(focus, "Element under cursor does not have source location"); }

Expand Down Expand Up @@ -495,8 +603,6 @@ data Summary = summary(loc src,
rel[loc, loc] implementations = {}
);

data Completion = completion(str newText, str proposal=newText);

@synopsis{DocumentSymbol encodes a sorted and hierarchical outline of a source file}
data DocumentSymbol
= symbol(
Expand Down Expand Up @@ -542,8 +648,6 @@ data DocumentSymbolTag
= \deprecated()
;

data CompletionProposal = sourceProposal(str newText, str proposal=newText);

@synopsis{Attach any command to a message for it to be exposed as a quick-fix code action automatically.}
@description{
The fixes you provide with a message will be hinted at by a light-bulb in the editor's margin.
Expand Down
Loading