@@ -268,48 +268,63 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
268
268
const fileUrl = pathToUrl ( tsDoc . filePath ) ;
269
269
const isCompletionInTag = svelteIsInTag ( svelteNode , originalOffset ) ;
270
270
271
+ const completionItems : CompletionItem [ ] = eventAndSlotLetCompletions ;
272
+ const isValidCompletion = createIsValidCompletion ( document , position , ! ! tsDoc . parserError ) ;
273
+ const addCompletion = ( entry : ts . CompletionEntry , asStore : boolean ) => {
274
+ if ( isValidCompletion ( entry ) ) {
275
+ let completion = this . toCompletionItem (
276
+ tsDoc ,
277
+ entry ,
278
+ fileUrl ,
279
+ position ,
280
+ isCompletionInTag ,
281
+ addCommitCharacters ,
282
+ asStore ,
283
+ existingImports
284
+ ) ;
285
+ if ( completion ) {
286
+ completionItems . push (
287
+ this . fixTextEditRange (
288
+ wordRangeStartPosition ,
289
+ mapCompletionItemToOriginal ( tsDoc , completion )
290
+ )
291
+ ) ;
292
+ }
293
+ }
294
+ } ;
295
+
271
296
// If completion is about a store which is not imported yet, do another
272
297
// completion request at the beginning of the file to get all global
273
298
// import completions and then filter them down to likely matches.
274
299
if ( word . charAt ( 0 ) === '$' ) {
275
300
const storeName = word . substring ( 1 ) ;
276
301
const text = '__sveltets_2_store_get(' + storeName ;
277
302
if ( ! tsDoc . getFullText ( ) . includes ( text ) ) {
278
- const storeImportCompletions =
279
- lang
280
- . getCompletionsAtPosition (
281
- filePath ,
282
- 0 ,
283
- {
284
- ...userPreferences ,
285
- triggerCharacter : validTriggerCharacter
286
- } ,
287
- formatSettings
288
- )
289
- ?. entries . filter (
290
- ( entry ) => entry . source && entry . name . startsWith ( storeName )
291
- ) || [ ] ;
292
- completions . push ( ...storeImportCompletions ) ;
303
+ const pos = ( tsDoc . scriptInfo || tsDoc . moduleScriptInfo ) ?. endPos ?? {
304
+ line : 0 ,
305
+ character : 0
306
+ } ;
307
+ const virtualOffset = tsDoc . offsetAt ( tsDoc . getGeneratedPosition ( pos ) ) ;
308
+ const storeCompletions = lang . getCompletionsAtPosition (
309
+ filePath ,
310
+ virtualOffset ,
311
+ {
312
+ ...userPreferences ,
313
+ triggerCharacter : validTriggerCharacter
314
+ } ,
315
+ formatSettings
316
+ ) ;
317
+ for ( const entry of storeCompletions ?. entries || [ ] ) {
318
+ if ( entry . name . startsWith ( storeName ) ) {
319
+ addCompletion ( entry , true ) ;
320
+ }
321
+ }
293
322
}
294
323
}
295
324
296
- const completionItems = completions
297
- . filter ( isValidCompletion ( document , position , ! ! tsDoc . parserError ) )
298
- . map ( ( comp ) =>
299
- this . toCompletionItem (
300
- tsDoc ,
301
- comp ,
302
- fileUrl ,
303
- position ,
304
- isCompletionInTag ,
305
- addCommitCharacters ,
306
- existingImports
307
- )
308
- )
309
- . filter ( isNotNullOrUndefined )
310
- . map ( ( comp ) => mapCompletionItemToOriginal ( tsDoc , comp ) )
311
- . map ( ( comp ) => this . fixTextEditRange ( wordRangeStartPosition , comp ) )
312
- . concat ( eventAndSlotLetCompletions ) ;
325
+ for ( const entry of completions ) {
326
+ addCompletion ( entry , false ) ;
327
+ }
313
328
314
329
// Add ./$types imports for SvelteKit since TypeScript is bad at it
315
330
if ( basename ( filePath ) . startsWith ( '+' ) ) {
@@ -455,14 +470,16 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
455
470
position : Position ,
456
471
isCompletionInTag : boolean ,
457
472
addCommitCharacters : boolean ,
473
+ asStore : boolean ,
458
474
existingImports : Set < string >
459
475
) : AppCompletionItem < CompletionEntryWithIdentifier > | null {
460
476
const completionLabelAndInsert = this . getCompletionLabelAndInsert ( snapshot , comp ) ;
461
477
if ( ! completionLabelAndInsert ) {
462
478
return null ;
463
479
}
464
480
465
- let { label, insertText, isSvelteComp, replacementSpan } = completionLabelAndInsert ;
481
+ let { label, insertText, isSvelteComp, isRunesCompletion, replacementSpan } =
482
+ completionLabelAndInsert ;
466
483
// TS may suggest another Svelte component even if there already exists an import
467
484
// with the same name, because under the hood every Svelte component is postfixed
468
485
// with `__SvelteComponent`. In this case, filter out this completion by returning null.
@@ -477,6 +494,9 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
477
494
label [ label . length - 1 ] === '"'
478
495
) {
479
496
label = label . slice ( 1 , - 1 ) ;
497
+ } else if ( asStore ) {
498
+ // only modify label, so that the data property is untouched, which is important so the resolving still works
499
+ label = `$${ label } ` ;
480
500
}
481
501
482
502
const textEdit = replacementSpan
@@ -496,9 +516,9 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
496
516
insertText,
497
517
kind : scriptElementKindToCompletionItemKind ( comp . kind ) ,
498
518
commitCharacters : addCommitCharacters ? this . commitCharacters : undefined ,
499
- // Make sure svelte component takes precedence
500
- sortText : isSvelteComp ? '-1' : comp . sortText ,
501
- preselect : isSvelteComp ? true : comp . isRecommended ,
519
+ // Make sure svelte component and runes take precedence
520
+ sortText : isRunesCompletion || isSvelteComp ? '-1' : comp . sortText ,
521
+ preselect : isRunesCompletion || isSvelteComp ? true : comp . isRecommended ,
502
522
insertTextFormat : comp . isSnippet ? InsertTextFormat . Snippet : undefined ,
503
523
labelDetails,
504
524
textEdit,
@@ -518,7 +538,9 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
518
538
let { name, insertText, kindModifiers } = comp ;
519
539
const isScriptElement = comp . kind === ts . ScriptElementKind . scriptElement ;
520
540
const hasModifier = Boolean ( comp . kindModifiers ) ;
521
- const isSvelteComp = isGeneratedSvelteComponentName ( name ) ;
541
+ const isRunesCompletion =
542
+ name === '$props' || name === '$state' || name === '$derived' || name === '$effect' ;
543
+ const isSvelteComp = ! isRunesCompletion && isGeneratedSvelteComponentName ( name ) ;
522
544
if ( isSvelteComp ) {
523
545
name = changeSvelteComponentName ( name ) ;
524
546
@@ -533,14 +555,16 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
533
555
return {
534
556
insertText : name ,
535
557
label,
536
- isSvelteComp
558
+ isSvelteComp,
559
+ isRunesCompletion
537
560
} ;
538
561
}
539
562
540
563
if ( comp . replacementSpan ) {
541
564
return {
542
565
label : name ,
543
566
isSvelteComp,
567
+ isRunesCompletion,
544
568
insertText : insertText ? changeSvelteComponentName ( insertText ) : undefined ,
545
569
replacementSpan : comp . replacementSpan
546
570
} ;
@@ -549,7 +573,8 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
549
573
return {
550
574
label : name ,
551
575
insertText,
552
- isSvelteComp
576
+ isSvelteComp,
577
+ isRunesCompletion
553
578
} ;
554
579
}
555
580
@@ -653,12 +678,12 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
653
678
654
679
const detail = lang . getCompletionEntryDetails (
655
680
filePath ,
656
- tsDoc . offsetAt ( tsDoc . getGeneratedPosition ( comp ! . position ) ) ,
657
- comp ! . name ,
681
+ tsDoc . offsetAt ( tsDoc . getGeneratedPosition ( comp . position ) ) ,
682
+ comp . name ,
658
683
formatCodeOptions ,
659
- comp ! . source ,
684
+ comp . source ,
660
685
errorPreventingUserPreferences ,
661
- comp ! . data
686
+ comp . data
662
687
) ;
663
688
664
689
if ( detail ) {
@@ -913,7 +938,7 @@ const svelte2tsxTypes = new Set([
913
938
914
939
const startsWithUppercase = / ^ [ A - Z ] / ;
915
940
916
- function isValidCompletion (
941
+ function createIsValidCompletion (
917
942
document : Document ,
918
943
position : Position ,
919
944
hasParserError : boolean
0 commit comments