66 * found in the LICENSE file at https://angular.io/license
77 */
88
9- import { AST , BindingPipe , EmptyExpr , ImplicitReceiver , LiteralPrimitive , MethodCall , ParseSourceSpan , PropertyRead , PropertyWrite , SafeMethodCall , SafePropertyRead , TmplAstBoundAttribute , TmplAstBoundEvent , TmplAstElement , TmplAstNode , TmplAstReference , TmplAstTemplate , TmplAstTextAttribute , TmplAstVariable } from '@angular/compiler' ;
9+ import { AST , BindingPipe , EmptyExpr , ImplicitReceiver , LiteralPrimitive , MethodCall , ParseSourceSpan , PropertyRead , PropertyWrite , SafeMethodCall , SafePropertyRead , TmplAstBoundAttribute , TmplAstBoundEvent , TmplAstElement , TmplAstNode , TmplAstReference , TmplAstTemplate , TmplAstText , TmplAstTextAttribute , TmplAstVariable } from '@angular/compiler' ;
1010import { NgCompiler } from '@angular/compiler-cli/src/ngtsc/core' ;
1111import { CompletionKind , DirectiveInScope , TemplateDeclarationSymbol } from '@angular/compiler-cli/src/ngtsc/typecheck/api' ;
1212import { BoundEvent } from '@angular/compiler/src/render3/r3_ast' ;
1313import * as ts from 'typescript' ;
1414
1515import { addAttributeCompletionEntries , AttributeCompletionKind , buildAttributeCompletionTable , getAttributeCompletionSymbol } from './attribute_completions' ;
1616import { DisplayInfo , DisplayInfoKind , getDirectiveDisplayInfo , getSymbolDisplayInfo , getTsSymbolDisplayInfo , unsafeCastDisplayInfoKindToScriptElementKind } from './display_parts' ;
17+ import { TargetContext , TargetNodeKind , TemplateTarget } from './template_target' ;
1718import { filterAliasImports } from './utils' ;
1819
1920type PropertyExpressionCompletionBuilder =
@@ -48,13 +49,15 @@ export enum CompletionNodeContext {
4849export class CompletionBuilder < N extends TmplAstNode | AST > {
4950 private readonly typeChecker = this . compiler . getNextProgram ( ) . getTypeChecker ( ) ;
5051 private readonly templateTypeChecker = this . compiler . getTemplateTypeChecker ( ) ;
52+ private readonly nodeParent = this . targetDetails . parent ;
53+ private readonly nodeContext = nodeContextFromTarget ( this . targetDetails . context ) ;
54+ private readonly template = this . targetDetails . template ;
55+ private readonly position = this . targetDetails . position ;
5156
5257 constructor (
5358 private readonly tsLS : ts . LanguageService , private readonly compiler : NgCompiler ,
5459 private readonly component : ts . ClassDeclaration , private readonly node : N ,
55- private readonly nodeContext : CompletionNodeContext ,
56- private readonly nodeParent : TmplAstNode | AST | null ,
57- private readonly template : TmplAstTemplate | null ) { }
60+ private readonly targetDetails : TemplateTarget ) { }
5861
5962 /**
6063 * Analogue for `ts.LanguageService.getCompletionsAtPosition`.
@@ -335,20 +338,37 @@ export class CompletionBuilder<N extends TmplAstNode|AST> {
335338 }
336339 }
337340
338- private isElementTagCompletion ( ) : this is CompletionBuilder < TmplAstElement > {
339- return this . node instanceof TmplAstElement &&
340- this . nodeContext === CompletionNodeContext . ElementTag ;
341+ private isElementTagCompletion ( ) : this is CompletionBuilder < TmplAstElement | TmplAstText > {
342+ if ( this . node instanceof TmplAstText ) {
343+ const positionInTextNode = this . position - this . node . sourceSpan . start . offset ;
344+ // We only provide element completions in a text node when there is an open tag immediately to
345+ // the left of the position.
346+ return this . node . value . substring ( 0 , positionInTextNode ) . endsWith ( '<' ) ;
347+ } else if ( this . node instanceof TmplAstElement ) {
348+ return this . nodeContext === CompletionNodeContext . ElementTag ;
349+ }
350+ return false ;
341351 }
342352
343- private getElementTagCompletion ( this : CompletionBuilder < TmplAstElement > ) :
353+ private getElementTagCompletion ( this : CompletionBuilder < TmplAstElement | TmplAstText > ) :
344354 ts . WithMetadata < ts . CompletionInfo > | undefined {
345355 const templateTypeChecker = this . compiler . getTemplateTypeChecker ( ) ;
346356
347- // The replacementSpan is the tag name.
348- const replacementSpan : ts . TextSpan = {
349- start : this . node . sourceSpan . start . offset + 1 , // account for leading '<'
350- length : this . node . name . length ,
351- } ;
357+ let start : number ;
358+ let length : number ;
359+ if ( this . node instanceof TmplAstElement ) {
360+ // The replacementSpan is the tag name.
361+ start = this . node . sourceSpan . start . offset + 1 ; // account for leading '<'
362+ length = this . node . name . length ;
363+ } else {
364+ const positionInTextNode = this . position - this . node . sourceSpan . start . offset ;
365+ const textToLeftOfPosition = this . node . value . substring ( 0 , positionInTextNode ) ;
366+ start = this . node . sourceSpan . start . offset + textToLeftOfPosition . lastIndexOf ( '<' ) + 1 ;
367+ // We only autocomplete immediately after the < so we don't replace any existing text
368+ length = 0 ;
369+ }
370+
371+ const replacementSpan : ts . TextSpan = { start, length} ;
352372
353373 const entries : ts . CompletionEntry [ ] =
354374 Array . from ( templateTypeChecker . getPotentialElementTags ( this . component ) )
@@ -368,8 +388,8 @@ export class CompletionBuilder<N extends TmplAstNode|AST> {
368388 }
369389
370390 private getElementTagCompletionDetails (
371- this : CompletionBuilder < TmplAstElement > , entryName : string ) : ts . CompletionEntryDetails
372- | undefined {
391+ this : CompletionBuilder < TmplAstElement | TmplAstText > ,
392+ entryName : string ) : ts . CompletionEntryDetails | undefined {
373393 const templateTypeChecker = this . compiler . getTemplateTypeChecker ( ) ;
374394
375395 const tagMap = templateTypeChecker . getPotentialElementTags ( this . component ) ;
@@ -397,8 +417,8 @@ export class CompletionBuilder<N extends TmplAstNode|AST> {
397417 } ;
398418 }
399419
400- private getElementTagCompletionSymbol ( this : CompletionBuilder < TmplAstElement > , entryName : string ) :
401- ts . Symbol | undefined {
420+ private getElementTagCompletionSymbol (
421+ this : CompletionBuilder < TmplAstElement | TmplAstText > , entryName : string ) : ts . Symbol | undefined {
402422 const templateTypeChecker = this . compiler . getTemplateTypeChecker ( ) ;
403423
404424 const tagMap = templateTypeChecker . getPotentialElementTags ( this . component ) ;
@@ -664,3 +684,26 @@ function stripBindingSugar(binding: string): {name: string, kind: DisplayInfoKin
664684 return { name, kind : DisplayInfoKind . ATTRIBUTE } ;
665685 }
666686}
687+
688+ function nodeContextFromTarget ( target : TargetContext ) : CompletionNodeContext {
689+ switch ( target . kind ) {
690+ case TargetNodeKind . ElementInTagContext :
691+ return CompletionNodeContext . ElementTag ;
692+ case TargetNodeKind . ElementInBodyContext :
693+ // Completions in element bodies are for new attributes.
694+ return CompletionNodeContext . ElementAttributeKey ;
695+ case TargetNodeKind . TwoWayBindingContext :
696+ return CompletionNodeContext . TwoWayBinding ;
697+ case TargetNodeKind . AttributeInKeyContext :
698+ return CompletionNodeContext . ElementAttributeKey ;
699+ case TargetNodeKind . AttributeInValueContext :
700+ if ( target . node instanceof TmplAstBoundEvent ) {
701+ return CompletionNodeContext . EventValue ;
702+ } else {
703+ return CompletionNodeContext . None ;
704+ }
705+ default :
706+ // No special context is available.
707+ return CompletionNodeContext . None ;
708+ }
709+ }
0 commit comments