static CompletionItem GetAppropriateAttributeItem(CompletionItem attributeItem, bool isCaseSensitive) { if (attributeItem.DisplayText.TryGetWithoutAttributeSuffix(isCaseSensitive: isCaseSensitive, out var attributeNameWithoutSuffix)) { // We don't want to cache this item. return(ImportCompletionItem.CreateAttributeItemWithoutSuffix(attributeItem, attributeNameWithoutSuffix, CompletionItemFlags.Expanded)); } return(attributeItem); }
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 void AddItem(INamedTypeSymbol symbol, string containingNamespace, bool isPublic) { // We want to cache items with EditoBrowsableState == Advanced regardless of current "hide adv members" option value var(isBrowsable, isEditorBrowsableStateAdvanced) = symbol.IsEditorBrowsableWithState( hideAdvancedMembers: false, _editorBrowsableInfo.Compilation, _editorBrowsableInfo); if (!isBrowsable) { // Hide this item from completion return; } var isGeneric = symbol.Arity > 0; // Need to determine if a type is an attribute up front since we want to filter out // non-attribute types when in attribute context. We can't do this lazily since we don't hold // on to symbols. However, the cost of calling `IsAttribute` on every top-level type symbols // is prohibitively high, so we opt for the heuristic that would do the simple textual "Attribute" // suffix check first, then the more expensive symbolic check. As a result, all unimported // attribute types that don't have "Attribute" suffix would be filtered out when in attribute context. var isAttribute = symbol.Name.HasAttributeSuffix(isCaseSensitive: false) && symbol.IsAttribute(); var item = ImportCompletionItem.Create( symbol.Name, symbol.Arity, containingNamespace, symbol.GetGlyph(), _genericTypeSuffix, CompletionItemFlags.CachedAndExpanded, extensionMethodData: null); if (isPublic) { _publicItemCount++; } _itemsBuilder.Add(new TypeImportCompletionItemInfo(item, isPublic, isGeneric, isAttribute, isEditorBrowsableStateAdvanced)); }
internal override Task <CompletionDescription> GetDescriptionWorkerAsync(Document document, CompletionItem item, CompletionOptions options, SymbolDescriptionOptions displayOptions, CancellationToken cancellationToken) => ImportCompletionItem.GetCompletionDescriptionAsync(document, item, displayOptions, cancellationToken);
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)); } }
protected override Task <CompletionDescription> GetDescriptionWorkerAsync(Document document, CompletionItem item, CancellationToken cancellationToken) => ImportCompletionItem.GetCompletionDescriptionAsync(document, item, cancellationToken);
public ImmutableArray <CompletionItem> GetItemsForContext( Compilation originCompilation, string language, string genericTypeSuffix, bool isAttributeContext, bool isCaseSensitive, bool hideAdvancedMembers) { if (AssemblySymbolKey.Resolve(originCompilation).Symbol is not IAssemblySymbol assemblySymbol) { return(ImmutableArray <CompletionItem> .Empty); } var isSameLanguage = Language == language; var isInternalsVisible = originCompilation.Assembly.IsSameAssemblyOrHasFriendAccessTo(assemblySymbol); using var _ = ArrayBuilder <CompletionItem> .GetInstance(out var builder); // PERF: try set the capacity upfront to avoid allocation from Resize if (!isAttributeContext) { if (isInternalsVisible) { builder.EnsureCapacity(ItemInfos.Length); } else { builder.EnsureCapacity(PublicItemCount); } } foreach (var info in ItemInfos) { if (!info.IsPublic && !isInternalsVisible) { continue; } // Option to show advanced members is false so we need to exclude them. if (hideAdvancedMembers && info.IsEditorBrowsableStateAdvanced) { continue; } var item = info.Item; if (isAttributeContext) { // Don't show non attribute item in attribute context if (!info.IsAttribute) { continue; } // We are in attribute context, will not show or complete with "Attribute" suffix. item = GetAppropriateAttributeItem(info.Item, isCaseSensitive); } // C# and VB the display text is different for generics, i.e. <T> and (Of T). For simpllicity, we only cache for one language. // But when we trigger in a project with different language than when the cache entry was created for, we will need to // change the generic suffix accordingly. if (!isSameLanguage && info.IsGeneric) { // We don't want to cache this item. item = ImportCompletionItem.CreateItemWithGenericDisplaySuffix(item, genericTypeSuffix); } builder.Add(item); } return(builder.ToImmutable());