@@ -174,6 +174,8 @@ namespace ts.Completions {
174174 return jsdocCompletionInfo ( JsDoc . getJSDocTagCompletions ( ) ) ;
175175 case CompletionDataKind . JsDocParameterName :
176176 return jsdocCompletionInfo ( JsDoc . getJSDocParameterNameCompletions ( completionData . tag ) ) ;
177+ case CompletionDataKind . Keywords :
178+ return specificKeywordCompletionInfo ( completionData . keywords ) ;
177179 default :
178180 return Debug . assertNever ( completionData ) ;
179181 }
@@ -183,6 +185,20 @@ namespace ts.Completions {
183185 return { isGlobalCompletion : false , isMemberCompletion : false , isNewIdentifierLocation : false , entries } ;
184186 }
185187
188+ function specificKeywordCompletionInfo ( keywords : readonly SyntaxKind [ ] ) : CompletionInfo {
189+ return {
190+ isGlobalCompletion : false ,
191+ isMemberCompletion : false ,
192+ isNewIdentifierLocation : false ,
193+ entries : keywords . map ( k => ( {
194+ name : tokenToString ( k ) ! ,
195+ kind : ScriptElementKind . keyword ,
196+ kindModifiers : ScriptElementKindModifier . none ,
197+ sortText : SortText . GlobalsOrKeywords ,
198+ } ) ) ,
199+ } ;
200+ }
201+
186202 function getOptionalReplacementSpan ( location : Node | undefined ) {
187203 // StringLiteralLike locations are handled separately in stringCompletions.ts
188204 return location ?. kind === SyntaxKind . Identifier ? createTextSpanFromNode ( location ) : undefined ;
@@ -802,6 +818,8 @@ namespace ts.Completions {
802818 return JsDoc . getJSDocTagCompletionDetails ( name ) ;
803819 case CompletionDataKind . JsDocParameterName :
804820 return JsDoc . getJSDocParameterNameCompletionDetails ( name ) ;
821+ case CompletionDataKind . Keywords :
822+ return request . keywords . indexOf ( stringToToken ( name ) ! ) > - 1 ? createSimpleDetails ( name , ScriptElementKind . keyword , SymbolDisplayPartKind . keyword ) : undefined ;
805823 default :
806824 return Debug . assertNever ( request ) ;
807825 }
@@ -893,7 +911,7 @@ namespace ts.Completions {
893911 return completion . type === "symbol" ? completion . symbol : undefined ;
894912 }
895913
896- const enum CompletionDataKind { Data , JsDocTagName , JsDocTag , JsDocParameterName }
914+ const enum CompletionDataKind { Data , JsDocTagName , JsDocTag , JsDocParameterName , Keywords }
897915 /** true: after the `=` sign but no identifier has been typed yet. Else is the Identifier after the initializer. */
898916 type IsJsxInitializer = boolean | Identifier ;
899917 interface CompletionData {
@@ -918,7 +936,10 @@ namespace ts.Completions {
918936 readonly isJsxIdentifierExpected : boolean ;
919937 readonly importCompletionNode ?: Node ;
920938 }
921- type Request = { readonly kind : CompletionDataKind . JsDocTagName | CompletionDataKind . JsDocTag } | { readonly kind : CompletionDataKind . JsDocParameterName , tag : JSDocParameterTag } ;
939+ type Request =
940+ | { readonly kind : CompletionDataKind . JsDocTagName | CompletionDataKind . JsDocTag }
941+ | { readonly kind : CompletionDataKind . JsDocParameterName , tag : JSDocParameterTag }
942+ | { readonly kind : CompletionDataKind . Keywords , keywords : readonly SyntaxKind [ ] } ;
922943
923944 export const enum CompletionKind {
924945 ObjectPropertyDeclaration ,
@@ -1101,13 +1122,17 @@ namespace ts.Completions {
11011122 let location = getTouchingPropertyName ( sourceFile , position ) ;
11021123
11031124 if ( contextToken ) {
1125+ const importCompletionCandidate = getImportCompletionNode ( contextToken ) ;
1126+ if ( importCompletionCandidate === SyntaxKind . FromKeyword ) {
1127+ return { kind : CompletionDataKind . Keywords , keywords : [ SyntaxKind . FromKeyword ] } ;
1128+ }
11041129 // Import statement completions use `insertText`, and also require the `data` property of `CompletionEntryIdentifier`
11051130 // added in TypeScript 4.3 to be sent back from the client during `getCompletionEntryDetails`. Since this feature
11061131 // is not backward compatible with older clients, the language service defaults to disabling it, allowing newer clients
11071132 // to opt in with the `includeCompletionsForImportStatements` user preference.
1108- importCompletionNode = preferences . includeCompletionsForImportStatements && preferences . includeCompletionsWithInsertText
1109- ? getImportCompletionNode ( contextToken )
1110- : undefined ;
1133+ if ( importCompletionCandidate && preferences . includeCompletionsForImportStatements && preferences . includeCompletionsWithInsertText ) {
1134+ importCompletionNode = importCompletionCandidate ;
1135+ }
11111136 // Bail out if this is a known invalid completion location
11121137 if ( ! importCompletionNode && isCompletionListBlocker ( contextToken ) ) {
11131138 log ( "Returning an empty list because completion was requested in an invalid position." ) ;
@@ -3041,17 +3066,21 @@ namespace ts.Completions {
30413066
30423067 function getImportCompletionNode ( contextToken : Node ) {
30433068 const candidate = getCandidate ( ) ;
3044- return candidate && rangeIsOnSingleLine ( candidate , candidate . getSourceFile ( ) ) ? candidate : undefined ;
3069+ return candidate === SyntaxKind . FromKeyword || candidate && rangeIsOnSingleLine ( candidate , candidate . getSourceFile ( ) ) ? candidate : undefined ;
30453070
30463071 function getCandidate ( ) {
30473072 const parent = contextToken . parent ;
30483073 if ( isImportEqualsDeclaration ( parent ) ) {
30493074 return isModuleSpecifierMissingOrEmpty ( parent . moduleReference ) ? parent : undefined ;
30503075 }
30513076 if ( isNamedImports ( parent ) || isNamespaceImport ( parent ) ) {
3052- return isModuleSpecifierMissingOrEmpty ( parent . parent . parent . moduleSpecifier ) && ( isNamespaceImport ( parent ) || parent . elements . length < 2 ) && ! parent . parent . name
3053- ? parent . parent . parent
3054- : undefined ;
3077+ if ( isModuleSpecifierMissingOrEmpty ( parent . parent . parent . moduleSpecifier ) && ( isNamespaceImport ( parent ) || parent . elements . length < 2 ) && ! parent . parent . name ) {
3078+ // At `import { ... } |` or `import * as Foo |`, the only possible completion is `from`
3079+ return contextToken . kind === SyntaxKind . CloseBraceToken || contextToken . kind === SyntaxKind . Identifier
3080+ ? SyntaxKind . FromKeyword
3081+ : parent . parent . parent ;
3082+ }
3083+ return undefined ;
30553084 }
30563085 if ( isImportKeyword ( contextToken ) && isSourceFile ( parent ) ) {
30573086 // A lone import keyword with nothing following it does not parse as a statement at all
0 commit comments