7
7
using System . Linq ;
8
8
using System . Text ;
9
9
using System . Threading . Tasks ;
10
- using System . Xml . Resolvers ;
11
10
using Microsoft . CodeAnalysis ;
12
11
using Microsoft . CodeAnalysis . Completion ;
12
+ using Microsoft . CodeAnalysis . CSharp ;
13
+ using Microsoft . CodeAnalysis . CSharp . Syntax ;
13
14
using Microsoft . CodeAnalysis . Tags ;
14
15
using Microsoft . CodeAnalysis . Text ;
15
16
using Microsoft . Extensions . Logging ;
@@ -159,17 +160,19 @@ public async Task<CompletionResponse> Handle(CompletionRequest request)
159
160
// the completion as done.
160
161
bool seenUnimportedCompletions = false ;
161
162
bool expectingImportedItems = expandedItemsAvailable && _workspace . Options . GetOption ( CompletionItemExtensions . ShowItemsFromUnimportedNamespaces , LanguageNames . CSharp ) == true ;
163
+ var syntax = await document . GetSyntaxTreeAsync ( ) ;
162
164
163
165
for ( int i = 0 ; i < completions . Items . Length ; i ++ )
164
166
{
165
167
var completion = completions . Items [ i ] ;
166
168
var insertTextFormat = InsertTextFormat . PlainText ;
167
- ImmutableArray < LinePositionSpanTextChange > ? additionalTextEdits = null ;
169
+ IReadOnlyList < LinePositionSpanTextChange > ? additionalTextEdits = null ;
168
170
char sortTextPrepend = '0' ;
169
171
170
172
if ( ! completion . TryGetInsertionText ( out string insertText ) )
171
173
{
172
- switch ( completion . GetProviderName ( ) )
174
+ string providerName = completion . GetProviderName ( ) ;
175
+ switch ( providerName )
173
176
{
174
177
case CompletionItemExtensions . InternalsVisibleToCompletionProvider :
175
178
// The IVT completer doesn't add extra things before the completion
@@ -240,7 +243,7 @@ public async Task<CompletionResponse> Handle(CompletionRequest request)
240
243
}
241
244
242
245
int additionalEditEndOffset ;
243
- ( additionalTextEdits , additionalEditEndOffset ) = GetAdditionalTextEdits ( change , sourceText , typedSpan , completion . DisplayText , isImportCompletion : false ) ;
246
+ ( additionalTextEdits , additionalEditEndOffset ) = await GetAdditionalTextEdits ( change , sourceText , ( CSharpParseOptions ) syntax ! . Options , typedSpan , completion . DisplayText , providerName ) ;
244
247
245
248
// Now that we have the additional edit, adjust the rest of the new text
246
249
( insertText , insertTextFormat ) = getAdjustedInsertTextWithPosition ( change , position , additionalEditEndOffset ) ;
@@ -435,14 +438,16 @@ public async Task<CompletionResolveResponse> Handle(CompletionResolveRequest req
435
438
436
439
request . Item . Documentation = textBuilder . ToString ( ) ;
437
440
438
- switch ( lastCompletionItem . GetProviderName ( ) )
441
+ string providerName = lastCompletionItem . GetProviderName ( ) ;
442
+ switch ( providerName )
439
443
{
440
444
case CompletionItemExtensions . ExtensionMethodImportCompletionProvider :
441
445
case CompletionItemExtensions . TypeImportCompletionProvider :
446
+ var syntax = await document . GetSyntaxTreeAsync ( ) ;
442
447
var sourceText = await document . GetTextAsync ( ) ;
443
448
var typedSpan = completionService . GetDefaultCompletionListSpan ( sourceText , position ) ;
444
449
var change = await completionService . GetChangeAsync ( document , lastCompletionItem , typedSpan ) ;
445
- ( request . Item . AdditionalTextEdits , _ ) = GetAdditionalTextEdits ( change , sourceText , typedSpan , lastCompletionItem . DisplayText , isImportCompletion : true ) ;
450
+ ( request . Item . AdditionalTextEdits , _ ) = await GetAdditionalTextEdits ( change , sourceText , ( CSharpParseOptions ) syntax ! . Options , typedSpan , lastCompletionItem . DisplayText , providerName ) ;
446
451
break ;
447
452
}
448
453
@@ -452,19 +457,20 @@ public async Task<CompletionResolveResponse> Handle(CompletionResolveRequest req
452
457
} ;
453
458
}
454
459
455
- private ( ImmutableArray < LinePositionSpanTextChange > edits , int endOffset ) GetAdditionalTextEdits ( CompletionChange change , SourceText sourceText , TextSpan typedSpan , string completionDisplayText , bool isImportCompletion )
460
+ private async ValueTask < ( IReadOnlyList < LinePositionSpanTextChange > edits , int endOffset ) > GetAdditionalTextEdits (
461
+ CompletionChange change ,
462
+ SourceText sourceText ,
463
+ CSharpParseOptions parseOptions ,
464
+ TextSpan typedSpan ,
465
+ string completionDisplayText ,
466
+ string providerName )
456
467
{
457
468
// We know the span starts before the text we're keying off of. So, break that
458
469
// out into a separate edit. We need to cut out the space before the current word,
459
470
// as the additional edit is not allowed to overlap with the insertion point.
460
471
var additionalEditStartPosition = sourceText . Lines . GetLinePosition ( change . TextChange . Span . Start ) ;
461
472
var additionalEditEndPosition = sourceText . Lines . GetLinePosition ( typedSpan . Start - 1 ) ;
462
- int additionalEditEndOffset = isImportCompletion
463
- // Import completion will put the displaytext at the end of the line, override completion will
464
- // put it at the front.
465
- ? change . TextChange . NewText ! . LastIndexOf ( completionDisplayText )
466
- : change . TextChange . NewText ! . IndexOf ( completionDisplayText ) ;
467
-
473
+ int additionalEditEndOffset = await getAdditionalTextEditEndOffset ( change , sourceText , parseOptions , typedSpan , completionDisplayText , providerName ) ;
468
474
if ( additionalEditEndOffset < 1 )
469
475
{
470
476
// The first index of this was either 0 and the edit span was wrong,
@@ -484,6 +490,44 @@ public async Task<CompletionResolveResponse> Handle(CompletionResolveRequest req
484
490
EndLine = additionalEditEndPosition . Line ,
485
491
EndColumn = additionalEditEndPosition . Character ,
486
492
} ) , additionalEditEndOffset ) ;
493
+
494
+ static async ValueTask < int > getAdditionalTextEditEndOffset ( CompletionChange change , SourceText sourceText , CSharpParseOptions parseOptions , TextSpan typedSpan , string completionDisplayText , string providerName )
495
+ {
496
+ // For many simple cases, we can just find the first or last index of the completionDisplayText and that's good
497
+ // enough
498
+ int endOffset = ( providerName == CompletionItemExtensions . ExtensionMethodImportCompletionProvider ||
499
+ providerName == CompletionItemExtensions . TypeImportCompletionProvider )
500
+ // Import completion will put the displaytext at the end of the line, override completion will
501
+ // put it at the front.
502
+ ? change . TextChange . NewText ! . LastIndexOf ( completionDisplayText )
503
+ : change . TextChange . NewText ! . IndexOf ( completionDisplayText ) ;
504
+
505
+ if ( endOffset > - 1 )
506
+ {
507
+ return endOffset ;
508
+ }
509
+
510
+ // The DisplayText wasn't in the final string. This can happen in a few cases:
511
+ // * The override or partial method completion is involving types that need
512
+ // to have a using added in the final version, and won't be fully qualified
513
+ // as they were in the DisplayText
514
+ // * Nullable context differences, such as if the thing you're overriding is
515
+ // annotated but the final context being generated into does not have
516
+ // annotations enabled.
517
+ // For these cases, we currently should only be seeing override or partial
518
+ // completions, as import completions don't have nullable annotations or
519
+ // fully-qualified types in their DisplayTexts. If that ever changes, we'll have
520
+ // to adjust the API here.
521
+ //
522
+ // In order to find the correct location here, we parse the change. The location
523
+ // of the name of the last method is the correct location in the string.
524
+ Debug . Assert ( providerName == CompletionItemExtensions . OverrideCompletionProvider ||
525
+ providerName == CompletionItemExtensions . PartialMethodCompletionProvider ) ;
526
+
527
+ var parsedTree = CSharpSyntaxTree . ParseText ( change . TextChange . NewText , parseOptions ) ;
528
+ var lastMethodDecl = ( await parsedTree . GetRootAsync ( ) ) . DescendantNodes ( ) . OfType < MethodDeclarationSyntax > ( ) . Last ( ) ;
529
+ return lastMethodDecl . Identifier . SpanStart ;
530
+ }
487
531
}
488
532
}
489
533
}
0 commit comments