@@ -174,6 +174,8 @@ namespace ts.Completions {
174
174
return jsdocCompletionInfo ( JsDoc . getJSDocTagCompletions ( ) ) ;
175
175
case CompletionDataKind . JsDocParameterName :
176
176
return jsdocCompletionInfo ( JsDoc . getJSDocParameterNameCompletions ( completionData . tag ) ) ;
177
+ case CompletionDataKind . Keywords :
178
+ return specificKeywordCompletionInfo ( completionData . keywords ) ;
177
179
default :
178
180
return Debug . assertNever ( completionData ) ;
179
181
}
@@ -183,6 +185,20 @@ namespace ts.Completions {
183
185
return { isGlobalCompletion : false , isMemberCompletion : false , isNewIdentifierLocation : false , entries } ;
184
186
}
185
187
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
+
186
202
function getOptionalReplacementSpan ( location : Node | undefined ) {
187
203
// StringLiteralLike locations are handled separately in stringCompletions.ts
188
204
return location ?. kind === SyntaxKind . Identifier ? createTextSpanFromNode ( location ) : undefined ;
@@ -802,6 +818,8 @@ namespace ts.Completions {
802
818
return JsDoc . getJSDocTagCompletionDetails ( name ) ;
803
819
case CompletionDataKind . JsDocParameterName :
804
820
return JsDoc . getJSDocParameterNameCompletionDetails ( name ) ;
821
+ case CompletionDataKind . Keywords :
822
+ return request . keywords . indexOf ( stringToToken ( name ) ! ) > - 1 ? createSimpleDetails ( name , ScriptElementKind . keyword , SymbolDisplayPartKind . keyword ) : undefined ;
805
823
default :
806
824
return Debug . assertNever ( request ) ;
807
825
}
@@ -893,7 +911,7 @@ namespace ts.Completions {
893
911
return completion . type === "symbol" ? completion . symbol : undefined ;
894
912
}
895
913
896
- const enum CompletionDataKind { Data , JsDocTagName , JsDocTag , JsDocParameterName }
914
+ const enum CompletionDataKind { Data , JsDocTagName , JsDocTag , JsDocParameterName , Keywords }
897
915
/** true: after the `=` sign but no identifier has been typed yet. Else is the Identifier after the initializer. */
898
916
type IsJsxInitializer = boolean | Identifier ;
899
917
interface CompletionData {
@@ -918,7 +936,10 @@ namespace ts.Completions {
918
936
readonly isJsxIdentifierExpected : boolean ;
919
937
readonly importCompletionNode ?: Node ;
920
938
}
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 [ ] } ;
922
943
923
944
export const enum CompletionKind {
924
945
ObjectPropertyDeclaration ,
@@ -1101,13 +1122,17 @@ namespace ts.Completions {
1101
1122
let location = getTouchingPropertyName ( sourceFile , position ) ;
1102
1123
1103
1124
if ( contextToken ) {
1125
+ const importCompletionCandidate = getImportCompletionNode ( contextToken ) ;
1126
+ if ( importCompletionCandidate === SyntaxKind . FromKeyword ) {
1127
+ return { kind : CompletionDataKind . Keywords , keywords : [ SyntaxKind . FromKeyword ] } ;
1128
+ }
1104
1129
// Import statement completions use `insertText`, and also require the `data` property of `CompletionEntryIdentifier`
1105
1130
// added in TypeScript 4.3 to be sent back from the client during `getCompletionEntryDetails`. Since this feature
1106
1131
// is not backward compatible with older clients, the language service defaults to disabling it, allowing newer clients
1107
1132
// 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
+ }
1111
1136
// Bail out if this is a known invalid completion location
1112
1137
if ( ! importCompletionNode && isCompletionListBlocker ( contextToken ) ) {
1113
1138
log ( "Returning an empty list because completion was requested in an invalid position." ) ;
@@ -3041,17 +3066,21 @@ namespace ts.Completions {
3041
3066
3042
3067
function getImportCompletionNode ( contextToken : Node ) {
3043
3068
const candidate = getCandidate ( ) ;
3044
- return candidate && rangeIsOnSingleLine ( candidate , candidate . getSourceFile ( ) ) ? candidate : undefined ;
3069
+ return candidate === SyntaxKind . FromKeyword || candidate && rangeIsOnSingleLine ( candidate , candidate . getSourceFile ( ) ) ? candidate : undefined ;
3045
3070
3046
3071
function getCandidate ( ) {
3047
3072
const parent = contextToken . parent ;
3048
3073
if ( isImportEqualsDeclaration ( parent ) ) {
3049
3074
return isModuleSpecifierMissingOrEmpty ( parent . moduleReference ) ? parent : undefined ;
3050
3075
}
3051
3076
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 ;
3055
3084
}
3056
3085
if ( isImportKeyword ( contextToken ) && isSourceFile ( parent ) ) {
3057
3086
// A lone import keyword with nothing following it does not parse as a statement at all
0 commit comments