static void AddItems(ImmutableArray <CompletionItem> items, CompletionContext completionContext, HashSet <string> namespacesInScope, TelemetryCounter counter) { foreach (var item in items) { var containingNamespace = ImportCompletionItem.GetContainingNamespace(item); if (!namespacesInScope.Contains(containingNamespace)) { // We can return cached item directly, item's span will be fixed by completion service. // On the other hand, because of this (i.e. mutating the span of cached item for each run), // the provider can not be used as a service by components that might be run in parallel // with completion, which would be a race. completionContext.AddItem(item); counter.ItemsCount++;; } } }
public override async Task <CompletionChange> GetChangeAsync( Document document, CompletionItem completionItem, char?commitKey, CancellationToken cancellationToken) { var containingNamespace = ImportCompletionItem.GetContainingNamespace(completionItem); var provideParenthesisCompletion = await ShouldProvideParenthesisCompletionAsync( document, completionItem, commitKey, cancellationToken).ConfigureAwait(false); var insertText = completionItem.DisplayText; if (provideParenthesisCompletion) { insertText += "()"; CompletionProvidersLogger.LogCustomizedCommitToAddParenthesis(commitKey); } if (await ShouldCompleteWithFullyQualifyTypeName().ConfigureAwait(false)) { var completionText = $"{containingNamespace}.{insertText}"; return(CompletionChange.Create(new TextChange(completionItem.Span, completionText))); } // Find context node so we can use it to decide where to insert using/imports. var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var root = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false); var addImportContextNode = root.FindToken(completionItem.Span.Start, findInsideTrivia: true).Parent; // Add required using/imports directive. var addImportService = document.GetRequiredLanguageService <IAddImportsService>(); var generator = document.GetRequiredLanguageService <SyntaxGenerator>(); var addImportsOptions = await AddImportPlacementOptions.FromDocumentAsync(document, cancellationToken).ConfigureAwait(false); var formattingOptions = await SyntaxFormattingOptions.FromDocumentAsync(document, cancellationToken).ConfigureAwait(false); var importNode = CreateImport(document, containingNamespace); var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); var rootWithImport = addImportService.AddImport(compilation, root, addImportContextNode !, importNode, generator, addImportsOptions, cancellationToken); var documentWithImport = document.WithSyntaxRoot(rootWithImport); // This only formats the annotated import we just added, not the entire document. var formattedDocumentWithImport = await Formatter.FormatAsync(documentWithImport, Formatter.Annotation, formattingOptions, cancellationToken).ConfigureAwait(false); using var _ = ArrayBuilder <TextChange> .GetInstance(out var builder); // Get text change for add import var importChanges = await formattedDocumentWithImport.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false); builder.AddRange(importChanges); // Create text change for complete type name. // // Note: Don't try to obtain TextChange for completed type name by replacing the text directly, // then use Document.GetTextChangesAsync on document created from the changed text. This is // because it will do a diff and return TextChanges with minimum span instead of actual // replacement span. // // For example: If I'm typing "asd", the completion provider could be triggered after "a" // is typed. Then if I selected type "AsnEncodedData" to commit, by using the approach described // above, we will get a TextChange of "AsnEncodedDat" with 0 length span, instead of a change of // the full display text with a span of length 1. This will later mess up span-tracking and end up // with "AsnEncodedDatasd" in the code. builder.Add(new TextChange(completionItem.Span, insertText)); // Then get the combined change var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var newText = text.WithChanges(builder); var changes = builder.ToImmutable(); var change = Utilities.Collapse(newText, changes); return(CompletionChange.Create(change, changes)); async Task <bool> ShouldCompleteWithFullyQualifyTypeName() { if (!IsAddingImportsSupported(document)) { return(true); } // We might need to qualify unimported types to use them in an import directive, because they only affect members of the containing // import container (e.g. namespace/class/etc. declarations). // // For example, `List` and `StringBuilder` both need to be fully qualified below: // // using CollectionOfStringBuilders = System.Collections.Generic.List<System.Text.StringBuilder>; // // However, if we are typing in an C# using directive that is inside a nested import container (i.e. inside a namespace declaration block), // then we can add an using in the outer import container instead (this is not allowed in VB). // // For example: // // using System.Collections.Generic; // using System.Text; // // namespace Foo // { // using CollectionOfStringBuilders = List<StringBuilder>; // } // // Here we will always choose to qualify the unimported type, just to be consistent and keeps things simple. return(await IsInImportsDirectiveAsync(document, completionItem.Span.Start, cancellationToken).ConfigureAwait(false)); } }