/// <summary> /// Get completions for item elements. /// </summary> /// <param name="location"> /// The <see cref="XmlLocation"/> where completions are requested. /// </param> /// <param name="projectDocument"> /// The <see cref="ProjectDocument"/> that contains the <paramref name="location"/>. /// </param> /// <param name="existingMetadata"> /// Metadata already declared on the item. /// </param> /// <returns> /// A sequence of <see cref="CompletionItem"/>s. /// </returns> IEnumerable <CompletionItem> GetElementCompletions(XmlLocation location, ProjectDocument projectDocument, HashSet <string> existingMetadata) { Log.Verbose("Evaluate element completions for {XmlLocation:l}", location); XSElement replaceElement; if (!location.CanCompleteElement(out replaceElement, parentPath: WellKnownElementPaths.Item)) { Log.Verbose("Not offering any element completions for {XmlLocation:l} (not a location where an item metadata element can be created or replaced by completion).", location); yield break; } Range replaceRange; string itemType; if (replaceElement != null) { replaceRange = replaceElement.Range; itemType = replaceElement.ParentElement?.Name; } else { replaceRange = location.Position.ToEmptyRange(); itemType = location.Node.Path.Parent.Name; } // These items are handled by PackageReferenceCompletion. if (itemType == "PackageReference" || itemType == "DotNetCliToolReference") { Log.Verbose("Not offering any element completions for {XmlLocation:l} ({ItemType} items are handled by another provider).", location, itemType ); yield break; } if (MSBuildSchemaHelp.ForItemType(itemType) == null) { Log.Verbose("Not offering any element completions for {XmlLocation:l} ({ItemType} is not a well-known item type).", location, itemType ); yield break; } if (replaceElement != null) { // Don't offer completions for existing metadata. existingMetadata.UnionWith( GetExistingMetadataNames(replaceElement) ); Log.Verbose("Will offer completions to replace item metadata element spanning {Range:l}", replaceRange); } else { Log.Verbose("Will offer completions to create item metadata element at {Position:l}", location.Position); } foreach (string metadataName in MSBuildSchemaHelp.WellKnownItemMetadataNames(itemType)) { if (existingMetadata.Contains(metadataName)) { continue; } if (MSBuildHelper.IsWellKnownItemMetadata(metadataName)) { continue; } string completionLabel = $"<{metadataName}>"; yield return(new CompletionItem { Label = completionLabel, Kind = CompletionItemKind.Field, Detail = $"Item Metadata ({itemType})", Documentation = MSBuildSchemaHelp.ForItemMetadata(itemType, metadataName), SortText = GetItemSortText(completionLabel), TextEdit = new TextEdit { NewText = $"<{metadataName}>$0</{metadataName}>", Range = replaceRange.ToLsp() }, InsertTextFormat = InsertTextFormat.Snippet }); } }
/// <summary> /// Get hover content for a metadata attribute of an <see cref="MSBuildUnusedItemGroup"/>. /// </summary> /// <param name="itemGroup"> /// The <see cref="MSBuildUnusedItemGroup"/>. /// </param> /// <param name="metadataName"> /// The name of the metadata attribute. /// </param> /// <returns> /// The content, or <c>null</c> if no content is provided. /// </returns> public MarkedStringContainer UnusedItemGroupMetadata(MSBuildUnusedItemGroup itemGroup, string metadataName) { if (itemGroup == null) { throw new ArgumentNullException(nameof(itemGroup)); } if (String.IsNullOrWhiteSpace(metadataName)) { throw new ArgumentException("Argument cannot be null, empty, or entirely composed of whitespace: 'metadataName'.", nameof(metadataName)); } if (itemGroup.Name == "PackageReference") { return(UnusedItemGroup(itemGroup)); } if (metadataName == "Condition") { return(Condition(itemGroup.Name, itemGroup.FirstItem.Xml.Condition)); } if (metadataName == "Include") { metadataName = "Identity"; } List <MarkedString> content = new List <MarkedString> { $"Unused Item Metadata: `{itemGroup.Name}.{metadataName}` (item condition is false)" }; string metadataHelp = MSBuildSchemaHelp.ForItemMetadata(itemGroup.Name, metadataName); if (metadataHelp != null) { content.Add(metadataHelp); } string[] metadataValues = itemGroup.GetMetadataValues(metadataName).Where( value => !String.IsNullOrWhiteSpace(value) ) .Distinct() .ToArray(); StringBuilder metadataContent = new StringBuilder(); if (metadataValues.Length > 0) { metadataContent.AppendLine("Values:"); foreach (string metadataValue in metadataValues) { metadataContent.AppendLine( $"* `{metadataValue}`" ); } } else { metadataContent.AppendLine("No values are present for this metadata."); } content.Add( metadataContent.ToString() ); return(new MarkedStringContainer(content)); }
/// <summary> /// Get completions for item attributes. /// </summary> /// <param name="location"> /// The <see cref="XmlLocation"/> where completions are requested. /// </param> /// <param name="projectDocument"> /// The <see cref="ProjectDocument"/> that contains the <paramref name="location"/>. /// </param> /// <param name="existingMetadata"> /// Metadata already declared on the item. /// </param> /// <returns> /// A sequence of <see cref="CompletionItem"/>s. /// </returns> IEnumerable <CompletionItem> GetAttributeCompletions(XmlLocation location, ProjectDocument projectDocument, HashSet <string> existingMetadata) { Log.Verbose("Evaluate attribute completions for {XmlLocation:l}", location); XSElement itemElement; XSAttribute replaceAttribute; PaddingType needsPadding; if (!location.CanCompleteAttribute(out itemElement, out replaceAttribute, out needsPadding)) { Log.Verbose("Not offering any attribute completions for {XmlLocation:l} (not a location where we can offer attribute completion.", location); yield break; } // Must be an item element. if (!itemElement.HasParentPath(WellKnownElementPaths.ItemGroup)) { Log.Verbose("Not offering any attribute completions for {XmlLocation:l} (element is not a direct child of a 'PropertyGroup' element).", location); yield break; } string itemType = itemElement.Name; if (String.IsNullOrWhiteSpace(itemType)) { Log.Verbose("Not offering any attribute completions for {XmlLocation:l} (element represents a new, unnamed, item group).", location, itemType ); yield break; } if (MSBuildSchemaHelp.ForItemType(itemType) == null) { Log.Verbose("Not offering any attribute completions for {XmlLocation:l} ({ItemType} is not a well-known item type).", location, itemType ); yield break; } Log.Verbose("Will offer attribute completions for {XmlLocation:l} (padding: {NeedsPadding})", location, needsPadding); // Don't offer completions for existing metadata. existingMetadata.UnionWith( GetExistingMetadataNames(itemElement) ); Range replaceRange = replaceAttribute?.Range ?? location.Position.ToEmptyRange(); foreach (string metadataName in MSBuildSchemaHelp.WellKnownItemMetadataNames(itemType)) { if (existingMetadata.Contains(metadataName)) { continue; } if (MSBuildHelper.IsWellKnownItemMetadata(metadataName)) { continue; } yield return(new CompletionItem { Label = metadataName, Kind = CompletionItemKind.Field, Detail = "Item Metadata", Documentation = MSBuildSchemaHelp.ForItemMetadata(itemType, metadataName), SortText = GetItemSortText(metadataName), TextEdit = new TextEdit { NewText = $"{metadataName}=\"$0\"".WithPadding(needsPadding), Range = replaceRange.ToLsp() }, InsertTextFormat = InsertTextFormat.Snippet }); } }
/// <summary> /// Provide completions for the specified location. /// </summary> /// <param name="location"> /// The <see cref="XmlLocation"/> where completions are requested. /// </param> /// <param name="projectDocument"> /// The <see cref="ProjectDocument"/> that contains the <paramref name="location"/>. /// </param> /// <param name="cancellationToken"> /// A <see cref="CancellationToken"/> that can be used to cancel the operation. /// </param> /// <returns> /// A <see cref="Task{TResult}"/> that resolves either a <see cref="CompletionList"/>s, or <c>null</c> if no completions are provided. /// </returns> public override async Task <CompletionList> ProvideCompletions(XmlLocation location, ProjectDocument projectDocument, CancellationToken cancellationToken = default(CancellationToken)) { if (location == null) { throw new ArgumentNullException(nameof(location)); } if (projectDocument == null) { throw new ArgumentNullException(nameof(projectDocument)); } List <CompletionItem> completions = new List <CompletionItem>(); using (await projectDocument.Lock.ReaderLockAsync()) { XSElement element; XSAttribute replaceAttribute; PaddingType needsPadding; if (!location.CanCompleteAttribute(out element, out replaceAttribute, out needsPadding)) { return(null); } // Must be a valid item element. if (!element.IsValid || !element.HasParentPath(WellKnownElementPaths.ItemGroup)) { return(null); } Range replaceRange = replaceAttribute?.Range ?? location.Position.ToEmptyRange(); completions.AddRange( WellKnownItemAttributes.Except( element.AttributeNames ) .Select(attributeName => new CompletionItem { Label = attributeName, Detail = "Attribute", Documentation = MSBuildSchemaHelp.ForItemMetadata(itemType: element.Name, metadataName: attributeName) ?? MSBuildSchemaHelp.ForAttribute(element.Name, attributeName), Kind = CompletionItemKind.Field, SortText = GetItemSortText(attributeName), TextEdit = new TextEdit { NewText = $"{attributeName}=\"$1\"$0".WithPadding(needsPadding), Range = replaceRange.ToLsp() }, InsertTextFormat = InsertTextFormat.Snippet }) ); } if (completions.Count == 0) { return(null); } return(new CompletionList(completions, isIncomplete: false)); }