예제 #1
0
        /// <summary>
        ///     Get item element completions.
        /// </summary>
        /// <param name="projectDocument">
        ///     The <see cref="ProjectDocument"/> for which completions will be offered.
        /// </param>
        /// <param name="replaceRange">
        ///     The range of text to be replaced by the completions.
        /// </param>
        /// <returns>
        ///     A sequence of <see cref="CompletionItem"/>s.
        /// </returns>
        public IEnumerable <CompletionItem> GetCompletionItems(ProjectDocument projectDocument, Range replaceRange)
        {
            if (replaceRange == null)
            {
                throw new ArgumentNullException(nameof(replaceRange));
            }

            LspModels.Range replaceRangeLsp = replaceRange.ToLsp();

            HashSet <string> offeredItemNames = new HashSet <string>
            {
                "PackageReference",
                "DotNetCliToolReference"
            };

            // Well-known (but standard-format) properties.

            foreach (string wellKnownItemName in MSBuildSchemaHelp.WellKnownItemTypes)
            {
                if (!offeredItemNames.Add(wellKnownItemName))
                {
                    continue;
                }

                yield return(ItemCompletion(wellKnownItemName, replaceRangeLsp,
                                            description: MSBuildSchemaHelp.ForItemType(wellKnownItemName)
                                            ));
            }

            if (!projectDocument.HasMSBuildProject)
            {
                yield break; // Without a valid MSBuild project (even a cached one will do), we can't inspect existing MSBuild properties.
            }
            if (!projectDocument.Workspace.Configuration.Language.CompletionsFromProject.Contains(CompletionSource.ItemType))
            {
                yield break;
            }

            int otherItemPriority = Priority + 10;

            string[] otherItemNames =
                projectDocument.MSBuildProject.Properties
                .Select(item => item.Name)
                .Where(itemName => !itemName.StartsWith("_"))     // Ignore private item types.
                .ToArray();
            foreach (string itemName in otherItemNames)
            {
                if (!offeredItemNames.Add(itemName))
                {
                    continue;
                }

                yield return(ItemCompletion(itemName, replaceRangeLsp, otherItemPriority,
                                            description: $"I don't know anything about the '{itemName}' item type, but it's defined in this project (or a project that it imports); you can override its value by specifying it here."
                                            ));
            }
        }
예제 #2
0
        /// <summary>
        ///     Get hover content for an <see cref="MSBuildUnusedItemGroup"/>.
        /// </summary>
        /// <param name="unusedItemGroup">
        ///     The <see cref="MSBuildUnusedItemGroup"/>.
        /// </param>
        /// <returns>
        ///     The content, or <c>null</c> if no content is provided.
        /// </returns>
        public MarkedStringContainer UnusedItemGroup(MSBuildUnusedItemGroup unusedItemGroup)
        {
            if (unusedItemGroup == null)
            {
                throw new ArgumentNullException(nameof(unusedItemGroup));
            }

            string condition          = unusedItemGroup.Condition;
            string evaluatedCondition = _projectDocument.MSBuildProject.ExpandString(condition);

            List <MarkedString> content = new List <MarkedString>
            {
                $"Unused Item Group: `{unusedItemGroup.OriginatingElement.ItemType}` (condition is false)"
            };

            string itemTypeHelp = MSBuildSchemaHelp.ForItemType(unusedItemGroup.Name);

            if (itemTypeHelp != null)
            {
                content.Add(itemTypeHelp);
            }

            StringBuilder descriptionContent = new StringBuilder();

            string[] includes = unusedItemGroup.Includes.ToArray();
            descriptionContent.AppendLine(
                $"Include: `{unusedItemGroup.OriginatingElement.Include}`  "
                );
            descriptionContent.AppendLine();
            descriptionContent.Append(
                $"Would have evaluated to {unusedItemGroup.Items.Count} item"
                );
            if (!unusedItemGroup.HasSingleItem)
            {
                descriptionContent.Append("s");
            }
            descriptionContent.AppendLine(":");

            foreach (string include in includes.Take(5))
            {
                // TODO: Consider making hyperlinks for includes that map to files which exist.
                descriptionContent.AppendLine(
                    $"* `{include}`"
                    );
            }
            if (includes.Length > 5)
            {
                descriptionContent.AppendLine("* ...");
            }

            content.Add(
                descriptionContent.ToString()
                );

            return(new MarkedStringContainer(content));
        }
        /// <summary>
        ///     Get item group element completions.
        /// </summary>
        /// <param name="projectDocument">
        ///     The <see cref="ProjectDocument"/> for which completions will be offered.
        /// </param>
        /// <param name="replaceRange">
        ///     The range of text to be replaced by the completions.
        /// </param>
        /// <returns>
        ///     A sequence of <see cref="CompletionItem"/>s.
        /// </returns>
        public IEnumerable <CompletionItem> GetCompletionItems(ProjectDocument projectDocument, Range replaceRange)
        {
            if (replaceRange == null)
            {
                throw new ArgumentNullException(nameof(replaceRange));
            }

            LspModels.Range replaceRangeLsp = replaceRange.ToLsp();

            HashSet <string> offeredItemGroupNames = new HashSet <string>
            {
                "*" // Skip virtual item type representing well-known metadata.
            };

            // Well-known item types.
            foreach (string itemType in MSBuildSchemaHelp.WellKnownItemTypes)
            {
                if (!offeredItemGroupNames.Add(itemType))
                {
                    continue;
                }

                yield return(ItemGroupCompletionItem(itemType, replaceRangeLsp,
                                                     description: MSBuildSchemaHelp.ForItemType(itemType)
                                                     ));
            }

            if (!projectDocument.HasMSBuildProject)
            {
                yield break; // Without a valid MSBuild project (even a cached one will do), we can't inspect existing MSBuild properties.
            }
            if (!projectDocument.Workspace.Configuration.Language.CompletionsFromProject.Contains(CompletionSource.ItemType))
            {
                yield break;
            }

            int otherItemGroupPriority = Priority + 10;

            string[] otherItemTypes =
                projectDocument.MSBuildProject.ItemTypes
                .Where(itemType => !itemType.StartsWith("_"))     // Ignore private item groups.
                .ToArray();
            foreach (string otherItemType in otherItemTypes)
            {
                if (!offeredItemGroupNames.Add(otherItemType))
                {
                    continue;
                }

                yield return(ItemGroupCompletionItem(otherItemType, replaceRangeLsp, otherItemGroupPriority,
                                                     description: "Item group defined in this project (or a project it imports)."
                                                     ));
            }
        }
        /// <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 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>
        ///     Get hover content for an <see cref="MSBuildItemGroup"/>.
        /// </summary>
        /// <param name="itemGroup">
        ///     The <see cref="MSBuildItemGroup"/>.
        /// </param>
        /// <returns>
        ///     The content, or <c>null</c> if no content is provided.
        /// </returns>
        public MarkedStringContainer ItemGroup(MSBuildItemGroup itemGroup)
        {
            if (itemGroup == null)
            {
                throw new ArgumentNullException(nameof(itemGroup));
            }

            if (itemGroup.Name == "PackageReference")
            {
                string packageVersion = itemGroup.GetFirstMetadataValue("Version");

                // TODO: Verify package is from NuGet (later, we can also recognise MyGet)

                return(new MarkedStringContainer(
                           $"NuGet Package: [{itemGroup.FirstInclude}](https://nuget.org/packages/{itemGroup.FirstInclude}/{packageVersion})",
                           $"Version: {packageVersion}"
                           ));
            }

            List <MarkedString> content = new List <MarkedString>
            {
                $"Item Group: `{itemGroup.OriginatingElement.ItemType}`"
            };

            string itemTypeHelp = MSBuildSchemaHelp.ForItemType(itemGroup.Name);

            if (itemTypeHelp != null)
            {
                content.Add(itemTypeHelp);
            }

            string[]      includes           = itemGroup.Includes.ToArray();
            StringBuilder itemIncludeContent = new StringBuilder();

            itemIncludeContent.AppendLine(
                $"Include: `{itemGroup.OriginatingElement.Include}`  "
                );
            itemIncludeContent.AppendLine();
            itemIncludeContent.Append(
                $"Evaluates to {itemGroup.Items.Count} item"
                );
            if (!itemGroup.HasSingleItem)
            {
                itemIncludeContent.Append("s");
            }
            itemIncludeContent.AppendLine(".");

            foreach (string include in includes.Take(5))
            {
                // TODO: Consider making hyperlinks for includes that map to files which exist.
                itemIncludeContent.AppendLine(
                    $"* `{include}`"
                    );
            }
            if (includes.Length > 5)
            {
                itemIncludeContent.AppendLine("* ...");
            }

            content.Add(
                itemIncludeContent.ToString()
                );

            string helpLink = MSBuildSchemaHelp.HelpLinkForItem(itemGroup.Name);

            if (!String.IsNullOrWhiteSpace(helpLink))
            {
                content.Add(
                    $"[Help]({helpLink})"
                    );
            }

            return(new MarkedStringContainer(content));
        }