@@ -10,7 +10,7 @@ import type { LangiumCompletionParser } from '../../parser/langium-parser.js';
1010import type { NameProvider } from '../../references/name-provider.js' ;
1111import type { ScopeProvider } from '../../references/scope-provider.js' ;
1212import type { LangiumServices } from '../../services.js' ;
13- import type { AstNode , AstNodeDescription , CstNode , Reference , ReferenceInfo } from '../../syntax-tree.js' ;
13+ import type { AstNode , AstNodeDescription , AstReflection , CstNode , ReferenceInfo } from '../../syntax-tree.js' ;
1414import type { MaybePromise } from '../../utils/promise-util.js' ;
1515import type { LangiumDocument } from '../../workspace/documents.js' ;
1616import type { NextFeature } from './follow-element-computation.js' ;
@@ -22,7 +22,7 @@ import type { IToken } from 'chevrotain';
2222import { CompletionItemKind , CompletionList , Position } from 'vscode-languageserver' ;
2323import * as ast from '../../grammar/generated/ast.js' ;
2424import { getExplicitRuleType } from '../../grammar/internal-grammar-util.js' ;
25- import { getContainerOfType } from '../../utils/ast-util.js' ;
25+ import { assignMandatoryAstProperties , getContainerOfType } from '../../utils/ast-util.js' ;
2626import { findDeclarationNodeAtOffset , findLeafNodeAtOffset } from '../../utils/cst-util.js' ;
2727import { getEntryRule } from '../../utils/grammar-util.js' ;
2828import { stream } from '../../utils/stream.js' ;
@@ -128,6 +128,7 @@ export class DefaultCompletionProvider implements CompletionProvider {
128128 protected readonly nodeKindProvider : NodeKindProvider ;
129129 protected readonly fuzzyMatcher : FuzzyMatcher ;
130130 protected readonly grammarConfig : GrammarConfig ;
131+ protected readonly astReflection : AstReflection ;
131132
132133 constructor ( services : LangiumServices ) {
133134 this . scopeProvider = services . references . ScopeProvider ;
@@ -138,6 +139,7 @@ export class DefaultCompletionProvider implements CompletionProvider {
138139 this . nodeKindProvider = services . shared . lsp . NodeKindProvider ;
139140 this . fuzzyMatcher = services . shared . lsp . FuzzyMatcher ;
140141 this . grammarConfig = services . parser . GrammarConfig ;
142+ this . astReflection = services . shared . AstReflection ;
141143 }
142144
143145 async getCompletion ( document : LangiumDocument , params : CompletionParams ) : Promise < CompletionList | undefined > {
@@ -200,7 +202,6 @@ export class DefaultCompletionProvider implements CompletionProvider {
200202 const parserRule = getEntryRule ( this . grammar ) ! ;
201203 const firstFeatures = findFirstFeatures ( {
202204 feature : parserRule . definition ,
203- new : true ,
204205 type : getExplicitRuleType ( parserRule )
205206 } ) ;
206207 if ( tokens . length > 0 ) {
@@ -373,37 +374,36 @@ export class DefaultCompletionProvider implements CompletionProvider {
373374 } ;
374375 }
375376
376- protected async completionForRule ( context : CompletionContext , rule : ast . AbstractRule , acceptor : CompletionAcceptor ) : Promise < void > {
377- if ( ast . isParserRule ( rule ) ) {
378- const firstFeatures = findFirstFeatures ( rule . definition ) ;
379- await Promise . all ( firstFeatures . map ( next => this . completionFor ( context , next , acceptor ) ) ) ;
380- }
381- }
382-
383377 protected completionFor ( context : CompletionContext , next : NextFeature , acceptor : CompletionAcceptor ) : MaybePromise < void > {
384378 if ( ast . isKeyword ( next . feature ) ) {
385379 return this . completionForKeyword ( context , next . feature , acceptor ) ;
386380 } else if ( ast . isCrossReference ( next . feature ) && context . node ) {
387381 return this . completionForCrossReference ( context , next as NextFeature < ast . CrossReference > , acceptor ) ;
388382 }
383+ // Don't offer any completion for other elements (i.e. terminals, datatype rules)
384+ // We - from a framework level - cannot reasonably assume their contents.
385+ // Adopters can just override `completionFor` if they want to do that anyway.
389386 }
390387
391- protected completionForCrossReference ( context : CompletionContext , crossRef : NextFeature < ast . CrossReference > , acceptor : CompletionAcceptor ) : MaybePromise < void > {
392- const assignment = getContainerOfType ( crossRef . feature , ast . isAssignment ) ;
388+ protected completionForCrossReference ( context : CompletionContext , next : NextFeature < ast . CrossReference > , acceptor : CompletionAcceptor ) : MaybePromise < void > {
389+ const assignment = getContainerOfType ( next . feature , ast . isAssignment ) ;
393390 let node = context . node ;
394391 if ( assignment && node ) {
395- if ( crossRef . type && ( crossRef . new || node . $type !== crossRef . type ) ) {
392+ if ( next . type ) {
393+ // When `type` is set, it indicates that we have just entered a new parser rule.
394+ // The cross reference that we're trying to complete is on a new element that doesn't exist yet.
395+ // So we create a new synthetic element with the correct type information.
396396 node = {
397- $type : crossRef . type ,
397+ $type : next . type ,
398398 $container : node ,
399- $containerProperty : crossRef . property
399+ $containerProperty : next . property
400400 } ;
401- }
402- if ( ! context ) {
403- return ;
401+ assignMandatoryAstProperties ( this . astReflection , node ) ;
404402 }
405403 const refInfo : ReferenceInfo = {
406- reference : { } as Reference ,
404+ reference : {
405+ $refText : ''
406+ } ,
407407 container : node ,
408408 property : assignment . feature
409409 } ;
@@ -452,7 +452,7 @@ export class DefaultCompletionProvider implements CompletionProvider {
452452 } ) ;
453453 }
454454
455- protected filterKeyword ( _context : CompletionContext , keyword : ast . Keyword ) : boolean {
455+ protected filterKeyword ( context : CompletionContext , keyword : ast . Keyword ) : boolean {
456456 // Filter out keywords that do not contain any word character
457457 return keyword . value . match ( / [ \w ] / ) !== null ;
458458 }
0 commit comments