public void TryCreateTooltip_Markup_Attribute_MultipleAssociatedAttributes_ReturnsTrue() { // Arrange var descriptionFactory = new DefaultLSPTagHelperTooltipFactory(LanguageServer); var associatedAttributeDescriptions = new[] { new BoundAttributeDescriptionInfo( returnTypeName: "System.String", typeName: "Microsoft.AspNetCore.SomeTagHelpers.SomeTypeName", propertyName: "SomeProperty", documentation: "<summary>Uses <see cref=\"T:System.Collections.List{System.String}\" />s</summary>"), new BoundAttributeDescriptionInfo( propertyName: "AnotherProperty", typeName: "Microsoft.AspNetCore.SomeTagHelpers.AnotherTypeName", returnTypeName: "System.Boolean?", documentation: "<summary>\nUses <see cref=\"T:System.Collections.List{System.String}\" />s\n</summary>"), }; var attributeDescription = new AggregateBoundAttributeDescription(associatedAttributeDescriptions); // Act var result = descriptionFactory.TryCreateTooltip(attributeDescription, out var markdown); // Assert Assert.True(result); Assert.Equal(@"**string** SomeTypeName.**SomeProperty** Uses `List<System.String>`s --- **Boolean?** AnotherTypeName.**AnotherProperty** Uses `List<System.String>`s", markdown.Value); Assert.Equal(MarkupKind.Markdown, markdown.Kind); }
public void TryCreateTooltip_Markup_Attribute_PlainText_NoBold() { // Arrange var languageServer = LanguageServer; languageServer.ClientSettings.Capabilities.TextDocument.Completion.Value.CompletionItem.DocumentationFormat = new Container <MarkupKind>(MarkupKind.PlainText); var descriptionFactory = new DefaultLSPTagHelperTooltipFactory(languageServer); var associatedAttributeDescriptions = new[] { new BoundAttributeDescriptionInfo( returnTypeName: "System.String", typeName: "Microsoft.AspNetCore.SomeTagHelpers.SomeTypeName", propertyName: "SomeProperty", documentation: "<summary>Uses <see cref=\"T:System.Collections.List{System.String}\" />s</summary>") }; var attributeDescription = new AggregateBoundAttributeDescription(associatedAttributeDescriptions); // Act var result = descriptionFactory.TryCreateTooltip(attributeDescription, out var markdown); // Assert Assert.True(result); Assert.Equal(@"string SomeTypeName.SomeProperty Uses `List<System.String>`s", markdown.Value); Assert.Equal(MarkupKind.PlainText, markdown.Kind); }
private HoverModel AttributeInfoToHover(IEnumerable <BoundAttributeDescriptor> descriptors, RangeModel range, string attributeName, ClientCapabilities clientCapabilities) { var descriptionInfos = descriptors.Select(boundAttribute => { var indexer = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(attributeName, boundAttribute); var descriptionInfo = BoundAttributeDescriptionInfo.From(boundAttribute, indexer); return(descriptionInfo); }).ToList().AsReadOnly(); var attrDescriptionInfo = new AggregateBoundAttributeDescription(descriptionInfos); var isVSClient = clientCapabilities is PlatformAgnosticClientCapabilities platformAgnosticClientCapabilities && platformAgnosticClientCapabilities.SupportsVisualStudioExtensions; if (isVSClient && _vsLspTagHelperTooltipFactory.TryCreateTooltip(attrDescriptionInfo, out ContainerElement classifiedTextElement)) { var vsHover = new OmniSharpVSHover { Contents = new MarkedStringsOrMarkupContent(), Range = range, RawContent = classifiedTextElement, }; return(vsHover); } else { var hoverContentFormat = GetHoverContentFormat(clientCapabilities); if (!_lspTagHelperTooltipFactory.TryCreateTooltip(attrDescriptionInfo, hoverContentFormat, out var vsMarkupContent)) { return(null); } Enum.TryParse(vsMarkupContent.Kind.Value, out MarkupKind markupKind); var markupContent = new MarkupContent() { Value = vsMarkupContent.Value, Kind = markupKind, }; var hover = new HoverModel { Contents = new MarkedStringsOrMarkupContent(markupContent), Range = range }; return(hover); } }
public void CreateClassifiedDescription_SingleDescription_NoSeparator() { // Arrange var factory = new DefaultVisualStudioDescriptionFactory(); var description = new AggregateBoundAttributeDescription(new[] { new BoundAttributeDescriptionInfo("TheReturnType", "TheTypeName", "ThePropertyName", "The documentation"), }); // Act var result = factory.CreateClassifiedDescription(description); // Assert Assert.DoesNotContain(DefaultVisualStudioDescriptionFactory.SeparatorElement, result.Elements); }
public void CreateClassifiedDescription_RepresentsPropertyName() { // Arrange var factory = new DefaultVisualStudioDescriptionFactory(); var description = new AggregateBoundAttributeDescription(new[] { new BoundAttributeDescriptionInfo("TheReturnType", "TheTypeName", "ThePropertyName", "The documentation"), }); // Act var result = factory.CreateClassifiedDescription(description); // Assert var flattened = FlattenToStrings(result); Assert.Contains(description.DescriptionInfos[0].PropertyName, flattened); }
public void TryCreateTooltip_ClassifiedTextElement_Attribute_SingleAssociatedAttribute_ReturnsTrue_NestedTypes() { // Arrange var descriptionFactory = new DefaultVSLSPTagHelperTooltipFactory(); var associatedAttributeDescriptions = new[] { new BoundAttributeDescriptionInfo( returnTypeName: "System.String", typeName: "Microsoft.AspNetCore.SomeTagHelpers.SomeTypeName", propertyName: "SomeProperty", documentation: "<summary>Uses <see cref=\"T:System.Collections.List{System.Collections.List{System.String}}\" />s</summary>") }; var attributeDescription = new AggregateBoundAttributeDescription(associatedAttributeDescriptions); // Act var result = descriptionFactory.TryCreateTooltip(attributeDescription, out VSClassifiedTextElement classifiedTextElement); // Assert Assert.True(result); // Expected output: // string Microsoft.AspNetCore.SomeTagHelpers.SomeTypeName.SomeProperty // Uses List<List<string>>s Assert.Collection(classifiedTextElement.Runs, run => AssertExpectedClassification(run, "string", VSPredefinedClassificationTypeNames.Keyword), run => AssertExpectedClassification(run, " ", VSPredefinedClassificationTypeNames.WhiteSpace), run => AssertExpectedClassification(run, "Microsoft", VSPredefinedClassificationTypeNames.Text), run => AssertExpectedClassification(run, ".", VSPredefinedClassificationTypeNames.Punctuation), run => AssertExpectedClassification(run, "AspNetCore", VSPredefinedClassificationTypeNames.Text), run => AssertExpectedClassification(run, ".", VSPredefinedClassificationTypeNames.Punctuation), run => AssertExpectedClassification(run, "SomeTagHelpers", VSPredefinedClassificationTypeNames.Text), run => AssertExpectedClassification(run, ".", VSPredefinedClassificationTypeNames.Punctuation), run => AssertExpectedClassification(run, "SomeTypeName", VSPredefinedClassificationTypeNames.Type), run => AssertExpectedClassification(run, ".", VSPredefinedClassificationTypeNames.Punctuation), run => AssertExpectedClassification(run, "SomeProperty", VSPredefinedClassificationTypeNames.Identifier), run => AssertExpectedClassification(run, Environment.NewLine, VSPredefinedClassificationTypeNames.WhiteSpace), run => AssertExpectedClassification(run, "Uses ", VSPredefinedClassificationTypeNames.Text), run => AssertExpectedClassification(run, "List", VSPredefinedClassificationTypeNames.Type), run => AssertExpectedClassification(run, "<", VSPredefinedClassificationTypeNames.Punctuation), run => AssertExpectedClassification(run, "List", VSPredefinedClassificationTypeNames.Type), run => AssertExpectedClassification(run, "<", VSPredefinedClassificationTypeNames.Punctuation), run => AssertExpectedClassification(run, "string", VSPredefinedClassificationTypeNames.Keyword), run => AssertExpectedClassification(run, ">", VSPredefinedClassificationTypeNames.Punctuation), run => AssertExpectedClassification(run, ">", VSPredefinedClassificationTypeNames.Punctuation), run => AssertExpectedClassification(run, "s", VSPredefinedClassificationTypeNames.Text)); }
public void CreateClassifiedDescription_CanSimplifyKeywordReturnTypes() { // Arrange var factory = new DefaultVisualStudioDescriptionFactory(); var description = new AggregateBoundAttributeDescription(new[] { new BoundAttributeDescriptionInfo("System.String", "TheTypeName", "ThePropertyName", "The documentation"), }); // Act var result = factory.CreateClassifiedDescription(description); // Assert var flattened = FlattenToStrings(result); Assert.DoesNotContain(description.DescriptionInfos[0].ReturnTypeName, flattened); Assert.Contains("string", flattened); }
public void CreateClassifiedDescription_CanRepresentMultipleDescriptions() { // Arrange var factory = new DefaultVisualStudioDescriptionFactory(); var description = new AggregateBoundAttributeDescription(new[] { new BoundAttributeDescriptionInfo("System.String", "TheTypeName", "ThePropertyName", "The documentation"), new BoundAttributeDescriptionInfo("System.Int32", "TheSecondTypeName", "TheSecondPropertyName", "The second documentation"), }); // Act var result = factory.CreateClassifiedDescription(description); // Assert var flattened = FlattenToStrings(result); Assert.Contains(description.DescriptionInfos[0].TypeName, flattened); Assert.Contains(description.DescriptionInfos[1].TypeName, flattened); Assert.Contains(description.DescriptionInfos[0].Documentation, flattened); Assert.Contains(description.DescriptionInfos[1].Documentation, flattened); }
public void TryConvert_TagHelperAttribute_ForHtml_ReturnsTrue() { // Arrange var completionItem = new RazorCompletionItem("format", "format=\"$0\"", RazorCompletionItemKind.TagHelperAttribute, isSnippet: true); var attributeCompletionDescription = new AggregateBoundAttributeDescription(new BoundAttributeDescriptionInfo[] { }); completionItem.SetAttributeCompletionDescription(attributeCompletionDescription); // Act var result = RazorCompletionEndpoint.TryConvert(completionItem, ClientCapabilities, out var converted); // Assert Assert.True(result); Assert.Equal(completionItem.DisplayText, converted.Label); Assert.Equal("format=\"$0\"", converted.InsertText); Assert.Equal(InsertTextFormat.Snippet, converted.InsertTextFormat); Assert.Equal(completionItem.DisplayText, converted.FilterText); Assert.Equal(completionItem.DisplayText, converted.SortText); Assert.Null(converted.Detail); Assert.Null(converted.Documentation); Assert.Null(converted.Command); }
private HoverModel AttributeInfoToHover(IEnumerable <BoundAttributeDescriptor> descriptors, RangeModel range, string attributeName) { var descriptionInfos = descriptors.Select(boundAttribute => { var indexer = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(attributeName, boundAttribute); var descriptionInfo = BoundAttributeDescriptionInfo.From(boundAttribute, indexer); return(descriptionInfo); }).ToList().AsReadOnly(); var attrDescriptionInfo = new AggregateBoundAttributeDescription(descriptionInfos); if (!_tagHelperTooltipFactory.TryCreateTooltip(attrDescriptionInfo, out var markupContent)) { return(null); } var hover = new HoverModel { Contents = new MarkedStringsOrMarkupContent(markupContent), Range = range }; return(hover); }
public async Task GetDescriptionAsync_DescriptionData_AsksFactoryForDescription() { // Arrange var expectedResult = new ContainerElement(ContainerElementStyle.Wrapped); var description = new AggregateBoundAttributeDescription(Array.Empty <BoundAttributeDescriptionInfo>()); var descriptionFactory = Mock.Of <VisualStudioDescriptionFactory>(factory => factory.CreateClassifiedDescription(description) == expectedResult, MockBehavior.Strict); var source = new RazorDirectiveAttributeCompletionSource( Dispatcher, Mock.Of <VisualStudioRazorParser>(MockBehavior.Strict), Mock.Of <RazorCompletionFactsService>(MockBehavior.Strict), Mock.Of <ICompletionBroker>(MockBehavior.Strict), descriptionFactory); var completionSessionSource = Mock.Of <IAsyncCompletionSource>(MockBehavior.Strict); var completionItem = new CompletionItem("@random", completionSessionSource); completionItem.Properties.AddProperty(RazorDirectiveAttributeCompletionSource.DescriptionKey, description); // Act var result = await source.GetDescriptionAsync(session : null, completionItem, CancellationToken.None); // Assert Assert.Equal(expectedResult, result); }
public void TryConvert_TagHelperAttribute_ForBool_ReturnsTrue() { // Arrange var completionItem = new RazorCompletionItem("format", "format", RazorCompletionItemKind.TagHelperAttribute); var attributeCompletionDescription = new AggregateBoundAttributeDescription(new[] { new BoundAttributeDescriptionInfo("System.Boolean", "Stuff", "format", "SomeDocs") }); completionItem.SetAttributeCompletionDescription(attributeCompletionDescription); // Act var result = RazorCompletionEndpoint.TryConvert(completionItem, ClientCapabilities, out var converted); // Assert Assert.True(result); Assert.Equal(completionItem.DisplayText, converted.Label); Assert.Equal("format", converted.InsertText); Assert.Equal(InsertTextFormat.Plaintext, converted.InsertTextFormat); Assert.Equal(completionItem.InsertText, converted.FilterText); Assert.Equal(completionItem.InsertText, converted.SortText); Assert.Null(converted.Detail); Assert.Null(converted.Documentation); Assert.Null(converted.Command); }
public abstract bool TryCreateTooltip(AggregateBoundAttributeDescription attributeDescriptionInfo, MarkupKind markupKind, [NotNullWhen(true)] out MarkupContent?tooltipContent);
// Internal for testing internal IReadOnlyList <RazorCompletionItem> GetAttributeParameterCompletions( string attributeName, string parameterName, string containingTagName, IEnumerable <string> attributes, TagHelperDocumentContext tagHelperDocumentContext) { var descriptorsForTag = _tagHelperFactsService.GetTagHelpersGivenTag(tagHelperDocumentContext, containingTagName, parentTag: null); if (descriptorsForTag.Count == 0) { // If the current tag has no possible descriptors then we can't have any additional attributes. return(Array.Empty <RazorCompletionItem>()); } // Attribute parameters are case sensitive when matching var attributeCompletions = new Dictionary <string, HashSet <BoundAttributeDescriptionInfo> >(StringComparer.Ordinal); foreach (var descriptor in descriptorsForTag) { for (var i = 0; i < descriptor.BoundAttributes.Count; i++) { var attributeDescriptor = descriptor.BoundAttributes[i]; var boundAttributeParameters = attributeDescriptor.BoundAttributeParameters; if (boundAttributeParameters.Count == 0) { continue; } if (TagHelperMatchingConventions.CanSatisfyBoundAttribute(attributeName, attributeDescriptor)) { for (var j = 0; j < boundAttributeParameters.Count; j++) { var parameterDescriptor = boundAttributeParameters[j]; if (attributes.Any(name => TagHelperMatchingConventions.SatisfiesBoundAttributeWithParameter(name, attributeDescriptor, parameterDescriptor))) { // There's already an existing attribute that satisfies this parameter, don't show it in the completion list. continue; } if (!attributeCompletions.TryGetValue(parameterDescriptor.Name, out var attributeDescriptionInfos)) { attributeDescriptionInfos = new HashSet <BoundAttributeDescriptionInfo>(); attributeCompletions[parameterDescriptor.Name] = attributeDescriptionInfos; } var tagHelperTypeName = descriptor.GetTypeName(); var descriptionInfo = BoundAttributeDescriptionInfo.From(parameterDescriptor, tagHelperTypeName); attributeDescriptionInfos.Add(descriptionInfo); } } } } var completionItems = new List <RazorCompletionItem>(); foreach (var completion in attributeCompletions) { if (string.Equals(completion.Key, parameterName, StringComparison.Ordinal)) { // This completion is identical to the selected parameter, don't provide for completions for what's already // present in the document. continue; } var razorCompletionItem = new RazorCompletionItem( completion.Key, completion.Key, RazorCompletionItemKind.DirectiveAttributeParameter); var completionDescription = new AggregateBoundAttributeDescription(completion.Value.ToArray()); razorCompletionItem.SetAttributeCompletionDescription(completionDescription); completionItems.Add(razorCompletionItem); } return(completionItems); }
// Internal for testing internal IReadOnlyList <RazorCompletionItem> GetAttributeCompletions( string selectedAttributeName, string containingTagName, IEnumerable <string> attributes, TagHelperDocumentContext tagHelperDocumentContext) { var descriptorsForTag = _tagHelperFactsService.GetTagHelpersGivenTag(tagHelperDocumentContext, containingTagName, parentTag: null); if (descriptorsForTag.Count == 0) { // If the current tag has no possible descriptors then we can't have any directive attributes. return(Array.Empty <RazorCompletionItem>()); } // Attributes are case sensitive when matching var attributeCompletions = new Dictionary <string, (HashSet <BoundAttributeDescriptionInfo>, HashSet <string>)>(StringComparer.Ordinal); for (var i = 0; i < descriptorsForTag.Count; i++) { var descriptor = descriptorsForTag[i]; foreach (var attributeDescriptor in descriptor.BoundAttributes) { if (!attributeDescriptor.IsDirectiveAttribute()) { // We don't care about non-directive attributes continue; } if (!TryAddCompletion(attributeDescriptor.Name, attributeDescriptor, descriptor) && attributeDescriptor.BoundAttributeParameters.Count > 0) { // This attribute has parameters and the base attribute name (@bind) is already satisfied. We need to check if there are any valid // parameters left to be provided, if so, we need to still represent the base attribute name in the completion list. for (var j = 0; j < attributeDescriptor.BoundAttributeParameters.Count; j++) { var parameterDescriptor = attributeDescriptor.BoundAttributeParameters[j]; if (!attributes.Any(name => TagHelperMatchingConventions.SatisfiesBoundAttributeWithParameter(name, attributeDescriptor, parameterDescriptor))) { // This bound attribute parameter has not had a completion entry added for it, re-represent the base attribute name in the completion list AddCompletion(attributeDescriptor.Name, attributeDescriptor, descriptor); break; } } } if (!string.IsNullOrEmpty(attributeDescriptor.IndexerNamePrefix)) { TryAddCompletion(attributeDescriptor.IndexerNamePrefix + "...", attributeDescriptor, descriptor); } } } var completionItems = new List <RazorCompletionItem>(); foreach (var completion in attributeCompletions) { var insertText = completion.Key; if (insertText.EndsWith("...", StringComparison.Ordinal)) { // Indexer attribute, we don't want to insert with the triple dot. insertText = insertText.Substring(0, insertText.Length - 3); } if (insertText.StartsWith("@", StringComparison.Ordinal)) { // Strip off the @ from the insertion text. This change is here to align the insertion text with the // completion hooks into VS and VSCode. Basically, completion triggers when `@` is typed so we don't // want to insert `@bind` because `@` already exists. insertText = insertText.Substring(1); } var(attributeDescriptionInfos, commitCharacters) = completion.Value; var razorCompletionItem = new RazorCompletionItem( completion.Key, insertText, RazorCompletionItemKind.DirectiveAttribute, commitCharacters); var completionDescription = new AggregateBoundAttributeDescription(attributeDescriptionInfos.ToArray()); razorCompletionItem.SetAttributeCompletionDescription(completionDescription); completionItems.Add(razorCompletionItem); } return(completionItems); bool TryAddCompletion(string attributeName, BoundAttributeDescriptor boundAttributeDescriptor, TagHelperDescriptor tagHelperDescriptor) { if (attributes.Any(name => string.Equals(name, attributeName, StringComparison.Ordinal)) && !string.Equals(selectedAttributeName, attributeName, StringComparison.Ordinal)) { // Attribute is already present on this element and it is not the selected attribute. // It shouldn't exist in the completion list. return(false); } AddCompletion(attributeName, boundAttributeDescriptor, tagHelperDescriptor); return(true); } void AddCompletion(string attributeName, BoundAttributeDescriptor boundAttributeDescriptor, TagHelperDescriptor tagHelperDescriptor) { if (!attributeCompletions.TryGetValue(attributeName, out var attributeDetails)) { attributeDetails = (new HashSet <BoundAttributeDescriptionInfo>(), new HashSet <string>()); attributeCompletions[attributeName] = attributeDetails; } (var attributeDescriptionInfos, var commitCharacters) = attributeDetails; var indexerCompletion = attributeName.EndsWith("...", StringComparison.Ordinal); var tagHelperTypeName = tagHelperDescriptor.GetTypeName(); var descriptionInfo = BoundAttributeDescriptionInfo.From(boundAttributeDescriptor, indexer: indexerCompletion, tagHelperTypeName); attributeDescriptionInfos.Add(descriptionInfo); if (indexerCompletion) { // Indexer attribute, we don't want to commit with standard chars return; } commitCharacters.Add("="); if (tagHelperDescriptor.BoundAttributes.Any(b => b.IsBooleanProperty)) { commitCharacters.Add(" "); } if (tagHelperDescriptor.BoundAttributes.Any(b => b.BoundAttributeParameters.Count > 0)) { commitCharacters.Add(":"); } } }
public abstract bool TryCreateTooltip(AggregateBoundAttributeDescription attributeDescriptionInfo, [NotNullWhen(true)] out ClassifiedTextElement?tooltipContent);
public static void SetAttributeCompletionDescription(this RazorCompletionItem completionItem, AggregateBoundAttributeDescription attributeCompletionDescription) { if (completionItem is null) { throw new ArgumentNullException(nameof(completionItem)); } completionItem.Items[s_attributeCompletionDescriptionKey] = attributeCompletionDescription; }
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 ContainerElement CreateClassifiedDescription(AggregateBoundAttributeDescription completionDescription);
public override ContainerElement CreateClassifiedDescription(AggregateBoundAttributeDescription completionDescription) { if (completionDescription is null) { throw new ArgumentNullException(nameof(completionDescription)); } var descriptionElements = new List <object>(); foreach (var descriptionInfo in completionDescription.DescriptionInfos) { if (descriptionElements.Count > 0) { descriptionElements.Add(SeparatorElement); } var returnTypeClassification = PredefinedClassificationNames.Type; if (TypeNameStringResolver.TryGetSimpleName(descriptionInfo.ReturnTypeName, out var returnTypeName)) { returnTypeClassification = PredefinedClassificationNames.Keyword; } else { returnTypeName = descriptionInfo.ReturnTypeName; } var tagHelperTypeName = descriptionInfo.TypeName; var tagHelperTypeNamePrefix = string.Empty; var tagHelperTypeNameProper = tagHelperTypeName; var lastDot = tagHelperTypeName.LastIndexOf('.'); if (lastDot > 0) { var afterLastDot = lastDot + 1; // We're pulling apart the type name so the prefix looks like: // // Microsoft.AspnetCore.Components. tagHelperTypeNamePrefix = tagHelperTypeName.Substring(0, afterLastDot); // And the type name looks like BindBinds tagHelperTypeNameProper = tagHelperTypeName.Substring(afterLastDot); } descriptionElements.Add( new ContainerElement( ContainerElementStyle.Wrapped, PropertyGlyph, new ClassifiedTextElement( new ClassifiedTextRun(returnTypeClassification, returnTypeName), SpaceLiteral, new ClassifiedTextRun(PredefinedClassificationNames.Literal, tagHelperTypeNamePrefix), new ClassifiedTextRun(PredefinedClassificationNames.Type, tagHelperTypeNameProper), DotLiteral, new ClassifiedTextRun(PredefinedClassificationNames.Identifier, descriptionInfo.PropertyName)))); if (descriptionInfo.Documentation != null) { descriptionElements.Add( new ContainerElement( ContainerElementStyle.Wrapped, new ClassifiedTextElement( new ClassifiedTextRun(PredefinedClassificationNames.NaturalLanguage, descriptionInfo.Documentation)))); } } var descriptionContainer = new ContainerElement(ContainerElementStyle.Stacked, descriptionElements); return(descriptionContainer); }
public abstract bool TryCreateTooltip(AggregateBoundAttributeDescription attributeDescriptionInfo, out MarkupContent tooltipContent);
public abstract bool TryCreateTooltip(AggregateBoundAttributeDescription attributeDescriptionInfo, out VSClassifiedTextElement tooltipContent);
public void TryCreateTooltip_ContainerElement_Attribute_MultipleAssociatedAttributes_ReturnsTrue() { // Arrange var descriptionFactory = new DefaultVSLSPTagHelperTooltipFactory(); var associatedAttributeDescriptions = new[] { new BoundAttributeDescriptionInfo( returnTypeName: "System.String", typeName: "Microsoft.AspNetCore.SomeTagHelpers.SomeTypeName", propertyName: "SomeProperty", documentation: "<summary>Uses <see cref=\"T:System.Collections.List{System.String}\" />s</summary>"), new BoundAttributeDescriptionInfo( propertyName: "AnotherProperty", typeName: "Microsoft.AspNetCore.SomeTagHelpers.AnotherTypeName", returnTypeName: "System.Boolean?", documentation: "<summary>\nUses <see cref=\"T:System.Collections.List{System.String}\" />s\n</summary>"), }; var attributeDescription = new AggregateBoundAttributeDescription(associatedAttributeDescriptions); // Act var result = descriptionFactory.TryCreateTooltip(attributeDescription, out VSContainerElement container); // Assert Assert.True(result); var containerElements = container.Elements.ToList(); // Expected output: // [Property Glyph] string Microsoft.AspNetCore.SomeTagHelpers.SomeTypeName.SomeProperty // Uses List<string>s // // [Property Glyph] bool? Microsoft.AspNetCore.SomeTagHelpers.AnotherTypeName.AnotherProperty // Uses List<string>s Assert.Equal(VSContainerElementStyle.Stacked, container.Style); Assert.Equal(4, containerElements.Count); // [TagHelper Glyph] string Microsoft.AspNetCore.SomeTagHelpers.SomeTypeName.SomeProperty var innerContainer = ((VSContainerElement)containerElements[0]).Elements.ToList(); var classifiedTextElement = (VSClassifiedTextElement)innerContainer[1]; Assert.Equal(2, innerContainer.Count); Assert.Equal(PropertyGlyph, innerContainer[0]); Assert.Collection(classifiedTextElement.Runs, run => AssertExpectedClassification(run, "string", VSPredefinedClassificationTypeNames.Keyword), run => AssertExpectedClassification(run, " ", VSPredefinedClassificationTypeNames.WhiteSpace), run => AssertExpectedClassification(run, "Microsoft", VSPredefinedClassificationTypeNames.Text), run => AssertExpectedClassification(run, ".", VSPredefinedClassificationTypeNames.Punctuation), run => AssertExpectedClassification(run, "AspNetCore", VSPredefinedClassificationTypeNames.Text), run => AssertExpectedClassification(run, ".", VSPredefinedClassificationTypeNames.Punctuation), run => AssertExpectedClassification(run, "SomeTagHelpers", VSPredefinedClassificationTypeNames.Text), run => AssertExpectedClassification(run, ".", VSPredefinedClassificationTypeNames.Punctuation), run => AssertExpectedClassification(run, "SomeTypeName", VSPredefinedClassificationTypeNames.Type), run => AssertExpectedClassification(run, ".", VSPredefinedClassificationTypeNames.Punctuation), run => AssertExpectedClassification(run, "SomeProperty", VSPredefinedClassificationTypeNames.Identifier)); // Uses List<string>s innerContainer = ((VSContainerElement)containerElements[1]).Elements.ToList(); classifiedTextElement = (VSClassifiedTextElement)innerContainer[0]; Assert.Single(innerContainer); Assert.Collection(classifiedTextElement.Runs, run => AssertExpectedClassification(run, "Uses ", VSPredefinedClassificationTypeNames.Text), run => AssertExpectedClassification(run, "List", VSPredefinedClassificationTypeNames.Type), run => AssertExpectedClassification(run, "<", VSPredefinedClassificationTypeNames.Punctuation), run => AssertExpectedClassification(run, "string", VSPredefinedClassificationTypeNames.Keyword), run => AssertExpectedClassification(run, ">", VSPredefinedClassificationTypeNames.Punctuation), run => AssertExpectedClassification(run, "s", VSPredefinedClassificationTypeNames.Text)); // [TagHelper Glyph] bool? Microsoft.AspNetCore.SomeTagHelpers.AnotherTypeName.AnotherProperty innerContainer = ((VSContainerElement)containerElements[2]).Elements.ToList(); classifiedTextElement = (VSClassifiedTextElement)innerContainer[1]; Assert.Equal(2, innerContainer.Count); Assert.Equal(PropertyGlyph, innerContainer[0]); Assert.Collection(classifiedTextElement.Runs, run => AssertExpectedClassification(run, "bool", VSPredefinedClassificationTypeNames.Keyword), run => AssertExpectedClassification(run, "?", VSPredefinedClassificationTypeNames.Punctuation), run => AssertExpectedClassification(run, " ", VSPredefinedClassificationTypeNames.WhiteSpace), run => AssertExpectedClassification(run, "Microsoft", VSPredefinedClassificationTypeNames.Text), run => AssertExpectedClassification(run, ".", VSPredefinedClassificationTypeNames.Punctuation), run => AssertExpectedClassification(run, "AspNetCore", VSPredefinedClassificationTypeNames.Text), run => AssertExpectedClassification(run, ".", VSPredefinedClassificationTypeNames.Punctuation), run => AssertExpectedClassification(run, "SomeTagHelpers", VSPredefinedClassificationTypeNames.Text), run => AssertExpectedClassification(run, ".", VSPredefinedClassificationTypeNames.Punctuation), run => AssertExpectedClassification(run, "AnotherTypeName", VSPredefinedClassificationTypeNames.Type), run => AssertExpectedClassification(run, ".", VSPredefinedClassificationTypeNames.Punctuation), run => AssertExpectedClassification(run, "AnotherProperty", VSPredefinedClassificationTypeNames.Identifier)); // Uses List<string>s innerContainer = ((VSContainerElement)containerElements[3]).Elements.ToList(); classifiedTextElement = (VSClassifiedTextElement)innerContainer[0]; Assert.Single(innerContainer); Assert.Collection(classifiedTextElement.Runs, run => AssertExpectedClassification(run, "Uses ", VSPredefinedClassificationTypeNames.Text), run => AssertExpectedClassification(run, "List", VSPredefinedClassificationTypeNames.Type), run => AssertExpectedClassification(run, "<", VSPredefinedClassificationTypeNames.Punctuation), run => AssertExpectedClassification(run, "string", VSPredefinedClassificationTypeNames.Keyword), run => AssertExpectedClassification(run, ">", VSPredefinedClassificationTypeNames.Punctuation), run => AssertExpectedClassification(run, "s", VSPredefinedClassificationTypeNames.Text)); }
public override bool TryCreateTooltip(AggregateBoundAttributeDescription attributeDescriptionInfo, out MarkupContent tooltipContent) { if (attributeDescriptionInfo is null) { throw new ArgumentNullException(nameof(attributeDescriptionInfo)); } var associatedAttributeInfos = attributeDescriptionInfo.DescriptionInfos; if (associatedAttributeInfos.Count == 0) { tooltipContent = null; return(false); } // This generates a markdown description that looks like the following: // **ReturnTypeName** SomeTypeName.**SomeProperty** // // The Summary documentation text with `CrefTypeValues` in code. // // Additional description infos result in a triple `---` to separate the markdown entries. var descriptionBuilder = new StringBuilder(); for (var i = 0; i < associatedAttributeInfos.Count; i++) { var descriptionInfo = associatedAttributeInfos[i]; if (descriptionBuilder.Length > 0) { descriptionBuilder.AppendLine(); descriptionBuilder.AppendLine("---"); } StartOrEndBold(descriptionBuilder); if (!TypeNameStringResolver.TryGetSimpleName(descriptionInfo.ReturnTypeName, out var returnTypeName)) { returnTypeName = descriptionInfo.ReturnTypeName; } var reducedReturnTypeName = ReduceTypeName(returnTypeName); descriptionBuilder.Append(reducedReturnTypeName); StartOrEndBold(descriptionBuilder); descriptionBuilder.Append(" "); var tagHelperTypeName = descriptionInfo.TypeName; var reducedTagHelperTypeName = ReduceTypeName(tagHelperTypeName); descriptionBuilder.Append(reducedTagHelperTypeName); descriptionBuilder.Append("."); StartOrEndBold(descriptionBuilder); descriptionBuilder.Append(descriptionInfo.PropertyName); StartOrEndBold(descriptionBuilder); var documentation = descriptionInfo.Documentation; if (!TryExtractSummary(documentation, out var summaryContent)) { continue; } descriptionBuilder.AppendLine(); descriptionBuilder.AppendLine(); var finalSummaryContent = CleanSummaryContent(summaryContent); descriptionBuilder.Append(finalSummaryContent); } tooltipContent = new MarkupContent { Kind = GetMarkupKind() }; tooltipContent.Value = descriptionBuilder.ToString(); return(true); }
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); }