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
);