// We don't want to classify TagNames of well-known HTML // elements as TagHelpers (even if they are). So the 'input' in`<input @onclick='...' />` // needs to not be marked as a TagHelper, but `<Input @onclick='...' />` should be. private bool ClassifyTagName(MarkupTagHelperElementSyntax node) { if (node is null) { throw new ArgumentNullException(nameof(node)); } if (node.StartTag != null && node.StartTag.Name != null) { var name = node.StartTag.Name.Content; if (!HtmlFactsService.IsHtmlTagName(name)) { // We always classify non-HTML tag names as TagHelpers if they're within a MarkupTagHelperElementSyntax return(true); } // This must be a well-known HTML tag name like 'input', 'br'. var binding = node.TagHelperInfo.BindingResult; foreach (var descriptor in binding.Descriptors) { if (!descriptor.IsComponentTagHelper()) { return(false); } } if (name.Length > 0 && char.IsUpper(name[0])) { // pascal cased Component TagHelper tag name such as <Input> return(true); } } return(false); }
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); }