public void CanCompleteElement(string testFileName, int line, int column) { Position testPosition = new Position(line, column); string testXml = LoadTestFile("TestFiles", testFileName + ".xml"); TextPositions positions = new TextPositions(testXml); XmlDocumentSyntax document = Parser.ParseText(testXml); XmlLocator locator = new XmlLocator(document, positions); XmlLocation location = locator.Inspect(testPosition); Assert.NotNull(location); XSElement replacingElement; Assert.True(location.CanCompleteElement(out replacingElement), "CanCompleteElement"); Assert.NotNull(replacingElement); }
public void CanCompleteElementInParentWithRelativePath(string testFileName, int line, int column, string expectedParent) { Position testPosition = new Position(line, column); string testXml = LoadTestFile("TestFiles", testFileName + ".xml"); TextPositions positions = new TextPositions(testXml); XmlDocumentSyntax document = Parser.ParseText(testXml); XmlLocator locator = new XmlLocator(document, positions); XmlLocation location = locator.Inspect(testPosition); Assert.NotNull(location); XSPath expectedParentPath = XSPath.Parse(expectedParent); XSElement replaceElement; Assert.True( location.CanCompleteElement(out replaceElement, parentPath: expectedParentPath), "CanCompleteElement" ); Assert.NotNull(replaceElement); Assert.Equal(expectedParent, replaceElement.ParentElement?.Name); }
/// <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> /// 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>(); Log.Verbose("Evaluate completions for {XmlLocation:l}", location); using (await projectDocument.Lock.ReaderLockAsync()) { XSElement replaceElement; if (!location.CanCompleteElement(out replaceElement, parentPath: WellKnownElementPaths.PropertyGroup)) { Log.Verbose("Not offering any completions for {XmlLocation:l} (not a direct child of a 'PropertyGroup' element).", location); return(null); } Range replaceRange; if (replaceElement != null) { replaceRange = replaceElement.Range; Log.Verbose("Offering completions to replace element {ElementName} @ {ReplaceRange:l}", replaceElement.Name, replaceRange ); } else { replaceRange = location.Position.ToEmptyRange(); Log.Verbose("Offering completions to create element @ {ReplaceRange:l}", replaceRange ); } completions.AddRange( GetCompletionItems(projectDocument, replaceRange) ); } Log.Verbose("Offering {CompletionCount} completion(s) for {XmlLocation:l}", completions.Count, location); if (completions.Count == 0) { return(null); } return(new CompletionList(completions, isIncomplete: false // Consider this list to be exhaustive )); }
/// <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="triggerCharacters"> /// The character(s), if any, that triggered completion. /// </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, string triggerCharacters, 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>(); Log.Verbose("Evaluate completions for {XmlLocation:l} (trigger characters = {TriggerCharacters})", location, triggerCharacters); using (await projectDocument.Lock.ReaderLockAsync()) { XSElement replaceElement; if (!location.CanCompleteElement(out replaceElement, parentPath: WellKnownElementPaths.Project)) { Log.Verbose("Not offering any completions for {XmlLocation:l} (not a direct child of the 'Project' element).", location); return(null); } Range targetRange; if (replaceElement != null) { targetRange = replaceElement.Range; Log.Verbose("Offering completions to replace element {ElementName} @ {ReplaceRange:l}", replaceElement.Name, targetRange ); } else { targetRange = location.Position.ToEmptyRange(); Log.Verbose("Offering completions to insert element @ {InsertPosition:l}", location.Position ); } // Replace any characters that were typed to trigger the completion. HandleTriggerCharacters(triggerCharacters, projectDocument, ref targetRange); completions.AddRange( GetCompletionItems(targetRange) ); } Log.Verbose("Offering {CompletionCount} completion(s) for {XmlLocation:l}", completions.Count, location); if (completions.Count == 0) { return(null); } return(new CompletionList(completions, isIncomplete: false // Consider this list to be exhaustive )); }
/// <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="triggerCharacters"> /// The character(s), if any, that triggered completion. /// </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, string triggerCharacters, CancellationToken cancellationToken = default(CancellationToken)) { if (location == null) { throw new ArgumentNullException(nameof(location)); } if (projectDocument == null) { throw new ArgumentNullException(nameof(projectDocument)); } if (!projectDocument.Workspace.Configuration.Language.CompletionsFromProject.Contains(CompletionSource.Task)) { Log.Verbose("Not offering task element completions for {XmlLocation:l} (task completions not enabled in extension settings).", location); return(null); } if (!projectDocument.HasMSBuildProject) { Log.Verbose("Not offering task element completions for {XmlLocation:l} (underlying MSBuild project is not loaded).", location); return(null); } List <CompletionItem> completions = new List <CompletionItem>(); Log.Verbose("Evaluate completions for {XmlLocation:l}", location); using (await projectDocument.Lock.ReaderLockAsync()) { XSElement replaceElement; if (!location.CanCompleteElement(out replaceElement, parentPath: WellKnownElementPaths.Target)) { Log.Verbose("Not offering any completions for {XmlLocation:l} (does not represent the direct child of a 'Target' element).", location); return(null); } Range targetRange = replaceElement?.Range ?? location.Position.ToEmptyRange(); // Replace any characters that were typed to trigger the completion. HandleTriggerCharacters(triggerCharacters, projectDocument, ref targetRange); if (replaceElement != null) { Log.Verbose("Offering completions to replace element {ElementName} @ {ReplaceRange:l}", replaceElement.Name, targetRange ); } else { Log.Verbose("Offering completions to create task element @ {ReplaceRange:l}", targetRange ); } Dictionary <string, MSBuildTaskMetadata> projectTasks = await GetProjectTasks(projectDocument); completions.AddRange( GetCompletionItems(projectDocument, projectTasks, targetRange) ); } Log.Verbose("Offering {CompletionCount} completion(s) for {XmlLocation:l}", completions.Count, location); if (completions.Count == 0) { return(null); } return(new CompletionList(completions, isIncomplete: false // Consider this list to be exhaustive )); }