Skip to content

Commit 5da26fc

Browse files
authored
Refactor follow element computation (#2087)
1 parent a7f32d7 commit 5da26fc

3 files changed

Lines changed: 472 additions & 173 deletions

File tree

packages/langium/src/lsp/completion/completion-provider.ts

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type { LangiumCompletionParser } from '../../parser/langium-parser.js';
99
import type { NameProvider } from '../../references/name-provider.js';
1010
import type { ScopeProvider } from '../../references/scope-provider.js';
1111
import type { LangiumServices } from '../lsp-services.js';
12-
import type { AstNode, AstNodeDescription, AstReflection, CstNode, MultiReference, Reference, ReferenceInfo } from '../../syntax-tree.js';
12+
import type { AstNode, AstNodeDescription, AstReflection, CstNode, MultiReference, Mutable, Reference, ReferenceInfo } from '../../syntax-tree.js';
1313
import type { CancellationToken } from '../../utils/cancellation.js';
1414
import type { MaybePromise } from '../../utils/promise-utils.js';
1515
import type { LangiumDocument, TextDocument } from '../../workspace/documents.js';
@@ -25,7 +25,7 @@ import { CompletionItemKind, CompletionList, Position } from 'vscode-languageser
2525
import * as ast from '../../languages/generated/ast.js';
2626
import { assignMandatoryProperties, getContainerOfType } from '../../utils/ast-utils.js';
2727
import { findDeclarationNodeAtOffset, findLeafNodeBeforeOffset, getDatatypeNode } from '../../utils/cst-utils.js';
28-
import { getEntryRule, getExplicitRuleType } from '../../utils/grammar-utils.js';
28+
import { getEntryRule } from '../../utils/grammar-utils.js';
2929
import { stream, type Stream } from '../../utils/stream.js';
3030
import { findFirstFeatures, findNextFeatures } from './follow-element-computation.js';
3131

@@ -208,24 +208,48 @@ export class DefaultCompletionProvider implements CompletionProvider {
208208
// If the parser didn't parse any tokens, return the next features of the entry rule
209209
if (parserResult.tokenIndex === 0) {
210210
const parserRule = getEntryRule(this.grammar)!;
211-
const firstFeatures = findFirstFeatures({
212-
feature: parserRule.definition,
213-
type: getExplicitRuleType(parserRule)
214-
});
215-
if (tokens.length > 0) {
216-
// We have to skip the first token
217-
// The interpreter will only look at the next features, which requires every token after the first
218-
tokens.shift();
219-
return findNextFeatures(firstFeatures.map(e => [e]), tokens);
220-
} else {
221-
return firstFeatures;
222-
}
211+
// Generate a synthetic RuleCall to the entry rule
212+
const syntheticEntryRuleCall = this.buildSyntheticEntryRuleCall(parserRule);
213+
return findNextFeatures([[syntheticEntryRuleCall]], tokens);
223214
}
224215
const leftoverTokens = [...tokens].splice(parserResult.tokenIndex);
225216
const features = findNextFeatures([parserResult.elementStack.map(feature => ({ feature }))], leftoverTokens);
226217
return features;
227218
}
228219

220+
protected buildSyntheticEntryRuleCall(rule: ast.ParserRule): NextFeature {
221+
// The "start" node is simply an empty group that is followed by the rule call
222+
const start: ast.Group = {
223+
$type: 'Group',
224+
$container: undefined!,
225+
elements: []
226+
};
227+
const startNext: NextFeature<ast.Group> = {
228+
feature: start
229+
};
230+
// This is the element that we want to complete
231+
const ruleCall: ast.RuleCall = {
232+
$type: 'RuleCall',
233+
$container: undefined!,
234+
rule: {
235+
ref: rule,
236+
$refText: rule.name
237+
},
238+
arguments: []
239+
};
240+
const group: ast.Group = {
241+
$type: 'Group',
242+
$container: undefined!,
243+
elements: [
244+
start,
245+
ruleCall
246+
]
247+
};
248+
(start as Mutable<AstNode>).$container = group;
249+
(ruleCall as Mutable<AstNode>).$container = group;
250+
return startNext;
251+
}
252+
229253
protected *buildContexts(document: LangiumDocument, position: Position): IterableIterator<CompletionContext> {
230254
const cst = document.parseResult.value.$cstNode;
231255
if (!cst) {
@@ -303,7 +327,7 @@ export class DefaultCompletionProvider implements CompletionProvider {
303327
...partialContext,
304328
tokenOffset: nextTokenStart,
305329
tokenEndOffset: nextTokenEnd,
306-
features: findFirstFeatures(parserRule.definition)
330+
features: findFirstFeatures(parserRule.definition).map(f => f[f.length - 1]),
307331
};
308332
} else if (performNextCompletion) {
309333
// This context aims to complete the next feature, using the next cst start/end

0 commit comments

Comments
 (0)