diff --git a/src/LanguageServer.Common/Utilities/TextPositions.cs b/src/LanguageServer.Common/Utilities/TextPositions.cs index 08278c9..2027253 100644 --- a/src/LanguageServer.Common/Utilities/TextPositions.cs +++ b/src/LanguageServer.Common/Utilities/TextPositions.cs @@ -167,6 +167,47 @@ public int GetDistance(Position position1, Position position2) return GetAbsolutePosition(position2) - GetAbsolutePosition(position1); } + /// + /// Move the specified position closer to the start of the document by the specified number of characters. + /// + /// + /// The position. + /// + /// + /// The number of characters. + /// + /// + /// The updated position. If would take the position past the start of the document, this becomes (1,1). + /// + public Position MoveLeft(Position position, int byCharCount) + { + int absolutePosition = GetAbsolutePosition(position); + absolutePosition -= byCharCount; + if (absolutePosition < 0) + absolutePosition = 0; + + return GetPosition(absolutePosition); + } + + /// + /// Extend the specified range closer to the start of the document by the specified number of characters. + /// + /// + /// The range. + /// + /// + /// The number of characters. + /// + /// + /// The updated range. If would take the range start past the start of the document, this becomes (1,1). + /// + public Range ExtendLeft(Range range, int byCharCount) + { + return range.WithStart( + MoveLeft(range.Start, byCharCount) + ); + } + /// /// Calculate the start position for each line in the text. /// diff --git a/src/LanguageServer.Engine/CompletionProviders/ItemElementCompletion.cs b/src/LanguageServer.Engine/CompletionProviders/ItemElementCompletion.cs index fba888c..538c284 100644 --- a/src/LanguageServer.Engine/CompletionProviders/ItemElementCompletion.cs +++ b/src/LanguageServer.Engine/CompletionProviders/ItemElementCompletion.cs @@ -92,6 +92,13 @@ public ItemElementCompletion(ILogger logger) { replaceRange = location.Position.ToEmptyRange(); + // Handle replacement of existing "<" if one was typed. + if (projectDocument.IsMSBuildProjectCached) + { + // Project XML is currently invalid; assume it's because they've typed a "<" character and attempt to compensate. + replaceRange = projectDocument.XmlPositions.ExtendLeft(replaceRange, byCharCount: 1); + } + Log.Verbose("Offering completions to create element @ {ReplaceRange:l}", replaceRange ); diff --git a/src/LanguageServer.Engine/CompletionProviders/PackageReferenceCompletion.cs b/src/LanguageServer.Engine/CompletionProviders/PackageReferenceCompletion.cs index ea3a3fb..7eb71ca 100644 --- a/src/LanguageServer.Engine/CompletionProviders/PackageReferenceCompletion.cs +++ b/src/LanguageServer.Engine/CompletionProviders/PackageReferenceCompletion.cs @@ -98,8 +98,19 @@ public PackageReferenceCompletion(ILogger logger) } else if (location.CanCompleteElement(out XSElement replaceElement, parentPath: WellKnownElementPaths.ItemGroup)) { + Range replaceRange; + if (replaceElement != null) { + replaceRange = replaceElement.Range; + + // Handle replacement of existing "<" if one was typed. + if (projectDocument.IsMSBuildProjectCached) + { + // Project XML is currently invalid; assume it's because they've typed a "<" character and attempt to compensate. + replaceRange = projectDocument.XmlPositions.ExtendLeft(replaceRange, byCharCount: 1); + } + Log.Verbose("Offering completions to replace child element @ {ReplaceRange} of {ElementName} @ {Position:l}", replaceElement.Range, "ItemGroup", @@ -108,13 +119,15 @@ public PackageReferenceCompletion(ILogger logger) } else { + replaceRange = location.Position.ToEmptyRange(); + Log.Verbose("Offering completions for new child element of {ElementName} @ {Position:l}", "ItemGroup", - location.Position + replaceRange ); } - List elementCompletions = HandlePackageReferenceElementCompletion(location, projectDocument, replaceElement); + List elementCompletions = HandlePackageReferenceElementCompletion(location, projectDocument, replaceRange); if (elementCompletions != null) completions.AddRange(elementCompletions); } @@ -216,13 +229,13 @@ async Task> HandlePackageReferenceAttributeCompletion(Proje /// /// The current project document. /// - /// - /// The element (if any) that will be replaced by the completion. + /// + /// The range of text that will be replaced by the completion. /// /// /// The completion list or null if no completions are provided. /// - List HandlePackageReferenceElementCompletion(XmlLocation location, ProjectDocument projectDocument, XSElement replaceElement) + List HandlePackageReferenceElementCompletion(XmlLocation location, ProjectDocument projectDocument, Range replaceRange) { if (projectDocument == null) throw new ArgumentNullException(nameof(projectDocument)); @@ -230,8 +243,6 @@ List HandlePackageReferenceElementCompletion(XmlLocation locatio if (location == null) throw new ArgumentNullException(nameof(location)); - Range replaceRange = replaceElement?.Range ?? location.Position.ToEmptyRange(); - return new List { new CompletionItem diff --git a/src/LanguageServer.Engine/CompletionProviders/PropertyElementCompletion.cs b/src/LanguageServer.Engine/CompletionProviders/PropertyElementCompletion.cs index 8f27cac..7508304 100644 --- a/src/LanguageServer.Engine/CompletionProviders/PropertyElementCompletion.cs +++ b/src/LanguageServer.Engine/CompletionProviders/PropertyElementCompletion.cs @@ -88,6 +88,13 @@ public PropertyElementCompletion(ILogger logger) { replaceRange = location.Position.ToEmptyRange(); + // Handle replacement of existing "<" if one was typed. + if (projectDocument.IsMSBuildProjectCached) + { + // Project XML is currently invalid; assume it's because they've typed a "<" character and attempt to compensate. + replaceRange = projectDocument.XmlPositions.ExtendLeft(replaceRange, byCharCount: 1); + } + Log.Verbose("Offering completions to create element @ {ReplaceRange:l}", replaceRange );