private IReadOnlyList <CompletionItem> GetAttributeCompletions( SyntaxNode containingAttribute, string containingTagName, string selectedAttributeName, IEnumerable <KeyValuePair <string, string> > attributes, RazorCodeDocument codeDocument) { var ancestors = containingAttribute.Parent.Ancestors(); var tagHelperDocumentContext = codeDocument.GetTagHelperContext(); var nonDirectiveAttributeTagHelpers = tagHelperDocumentContext.TagHelpers.Where(tagHelper => !tagHelper.BoundAttributes.Any(attribute => attribute.IsDirectiveAttribute())); var filteredContext = TagHelperDocumentContext.Create(tagHelperDocumentContext.Prefix, nonDirectiveAttributeTagHelpers); var(ancestorTagName, ancestorIsTagHelper) = _tagHelperFactsService.GetNearestAncestorTagInfo(ancestors); var attributeCompletionContext = new AttributeCompletionContext( filteredContext, existingCompletions: Enumerable.Empty <string>(), containingTagName, selectedAttributeName, attributes, ancestorTagName, ancestorIsTagHelper, HtmlFactsService.IsHtmlTagName); var completionItems = new List <CompletionItem>(); var completionResult = _razorTagHelperCompletionService.GetAttributeCompletions(attributeCompletionContext); foreach (var completion in completionResult.Completions) { var filterText = completion.Key; // This is a little bit of a hack because the information returned by _razorTagHelperCompletionService.GetAttributeCompletions // does not have enough information for us to determine if a completion is an indexer completion or not. Therefore we have to // jump through a few hoops below to: // 1. Determine if this specific completion is an indexer based completion // 2. Resolve an appropriate snippet if it is. This is more troublesome because we need to remove the ... suffix to accurately // build a snippet that makes sense for the user to type. var indexerCompletion = filterText.EndsWith("..."); if (indexerCompletion) { filterText = filterText.Substring(0, filterText.Length - 3); } var insertTextFormat = InsertTextFormat.Snippet; if (!TryResolveAttributeInsertionSnippet(filterText, completion.Value, indexerCompletion, out var insertText)) { insertTextFormat = InsertTextFormat.PlainText; insertText = filterText; } var razorCompletionItem = new CompletionItem() { Label = completion.Key, InsertText = insertText, InsertTextFormat = insertTextFormat, FilterText = filterText, SortText = filterText, Kind = CompletionItemKind.TypeParameter, CommitCharacters = AttributeCommitCharacters, }; var attributeDescriptions = completion.Value.Select(boundAttribute => new TagHelperAttributeDescriptionInfo( boundAttribute.DisplayName, boundAttribute.GetPropertyName(), indexerCompletion ? boundAttribute.IndexerTypeName : boundAttribute.TypeName, boundAttribute.Documentation)); var attributeDescriptionInfo = new AttributeDescriptionInfo(attributeDescriptions.ToList()); razorCompletionItem.SetDescriptionInfo(attributeDescriptionInfo); completionItems.Add(razorCompletionItem); } return(completionItems); }
private IReadOnlyList <RazorCompletionItem> GetAttributeCompletions( SyntaxNode containingAttribute, string containingTagName, string?selectedAttributeName, IEnumerable <KeyValuePair <string, string> > attributes, TagHelperDocumentContext tagHelperDocumentContext) { var ancestors = containingAttribute.Parent.Ancestors(); var nonDirectiveAttributeTagHelpers = tagHelperDocumentContext.TagHelpers.Where(tagHelper => !tagHelper.BoundAttributes.Any(attribute => attribute.IsDirectiveAttribute())); var filteredContext = TagHelperDocumentContext.Create(tagHelperDocumentContext.Prefix, nonDirectiveAttributeTagHelpers); var(ancestorTagName, ancestorIsTagHelper) = _tagHelperFactsService.GetNearestAncestorTagInfo(ancestors); var attributeCompletionContext = new AttributeCompletionContext( filteredContext, existingCompletions: Enumerable.Empty <string>(), containingTagName, selectedAttributeName, attributes, ancestorTagName, ancestorIsTagHelper, HtmlFactsService.IsHtmlTagName); var completionItems = new List <RazorCompletionItem>(); var completionResult = _tagHelperCompletionService.GetAttributeCompletions(attributeCompletionContext); foreach (var completion in completionResult.Completions) { var filterText = completion.Key; // This is a little bit of a hack because the information returned by _razorTagHelperCompletionService.GetAttributeCompletions // does not have enough information for us to determine if a completion is an indexer completion or not. Therefore we have to // jump through a few hoops below to: // 1. Determine if this specific completion is an indexer based completion // 2. Resolve an appropriate snippet if it is. This is more troublesome because we need to remove the ... suffix to accurately // build a snippet that makes sense for the user to type. var indexerCompletion = filterText.EndsWith("...", StringComparison.Ordinal); if (indexerCompletion) { filterText = filterText.Substring(0, filterText.Length - 3); } var attributeCommitCharacters = ResolveAttributeCommitCharacters(completion.Value, indexerCompletion); var razorCompletionItem = new RazorCompletionItem( displayText: completion.Key, insertText: filterText, RazorCompletionItemKind.TagHelperAttribute, attributeCommitCharacters); var attributeDescriptions = completion.Value.Select(boundAttribute => { var descriptionInfo = BoundAttributeDescriptionInfo.From(boundAttribute, indexerCompletion); return(descriptionInfo); }); var attributeDescriptionInfo = new AggregateBoundAttributeDescription(attributeDescriptions.ToList()); razorCompletionItem.SetAttributeCompletionDescription(attributeDescriptionInfo); completionItems.Add(razorCompletionItem); } return(completionItems); }
public abstract AttributeCompletionResult GetAttributeCompletions(AttributeCompletionContext completionContext);
private IReadOnlyList <RazorCompletionItem> GetAttributeCompletions( SyntaxNode containingAttribute, string containingTagName, string?selectedAttributeName, IEnumerable <KeyValuePair <string, string> > attributes, TagHelperDocumentContext tagHelperDocumentContext, RazorCompletionOptions options) { var ancestors = containingAttribute.Parent.Ancestors(); var nonDirectiveAttributeTagHelpers = tagHelperDocumentContext.TagHelpers.Where(tagHelper => !tagHelper.BoundAttributes.Any(attribute => attribute.IsDirectiveAttribute())); var filteredContext = TagHelperDocumentContext.Create(tagHelperDocumentContext.Prefix, nonDirectiveAttributeTagHelpers); var(ancestorTagName, ancestorIsTagHelper) = _tagHelperFactsService.GetNearestAncestorTagInfo(ancestors); var attributeCompletionContext = new AttributeCompletionContext( filteredContext, existingCompletions: Enumerable.Empty <string>(), containingTagName, selectedAttributeName, attributes, ancestorTagName, ancestorIsTagHelper, HtmlFactsService.IsHtmlTagName); var completionItems = new List <RazorCompletionItem>(); var completionResult = _tagHelperCompletionService.GetAttributeCompletions(attributeCompletionContext); foreach (var completion in completionResult.Completions) { var filterText = completion.Key; // This is a little bit of a hack because the information returned by _razorTagHelperCompletionService.GetAttributeCompletions // does not have enough information for us to determine if a completion is an indexer completion or not. Therefore we have to // jump through a few hoops below to: // 1. Determine if this specific completion is an indexer based completion // 2. Resolve an appropriate snippet if it is. This is more troublesome because we need to remove the ... suffix to accurately // build a snippet that makes sense for the user to type. var indexerCompletion = filterText.EndsWith("...", StringComparison.Ordinal); if (indexerCompletion) { filterText = filterText.Substring(0, filterText.Length - 3); } var attributeContext = ResolveAttributeContext(completion.Value, indexerCompletion, options.SnippetsSupported); var attributeCommitCharacters = ResolveAttributeCommitCharacters(attributeContext); var isSnippet = false; var insertText = filterText; if (TryResolveInsertText(insertText, attributeContext, out var snippetText)) { isSnippet = true; insertText = snippetText; } // We change the sort text depending on the tag name due to TagHelper/non-TagHelper concerns. For instance lets say you have a TagHelper that binds to `input`. // Chances are you're expecting to get every other `input` completion item in addition to the TagHelper completion items and the sort order should be the default // because HTML completion items are 100% as applicable as other items. // // Next assume that we have a TagHelper that binds `custom` (or even `Custom`); this is a special scenario where the user has effectively created a new HTML tag // meaning they're probably expecting to provide all of the attributes necessary for that tag to operate. Meaning, HTML attribute completions are less important. // To make sure we prioritize our attribute completions above all other types of completions we set the priority to high so they're showed in the completion list // above all other completion items. var sortText = HtmlFactsService.IsHtmlTagName(containingTagName) ? CompletionSortTextHelper.DefaultSortPriority : CompletionSortTextHelper.HighSortPriority; var razorCompletionItem = new RazorCompletionItem( displayText: completion.Key, insertText: insertText, sortText: sortText, kind: RazorCompletionItemKind.TagHelperAttribute, commitCharacters: attributeCommitCharacters, isSnippet: isSnippet); var attributeDescriptions = completion.Value.Select(boundAttribute => { var descriptionInfo = BoundAttributeDescriptionInfo.From(boundAttribute, indexerCompletion); return(descriptionInfo); }); var attributeDescriptionInfo = new AggregateBoundAttributeDescription(attributeDescriptions.ToList()); razorCompletionItem.SetAttributeCompletionDescription(attributeDescriptionInfo); completionItems.Add(razorCompletionItem); } return(completionItems); }