Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,13 @@ public CompletionHandler(
completionList = PostProcessCSharpCompletionList(request, documentSnapshot, wordExtent.Value, completionList);
}

completionList = TranslateTextEdits(request.Position, projectedPosition, wordExtent, completionList);
var wordRange = wordExtent.HasValue && wordExtent.Value.IsSignificant ? wordExtent?.Span.AsRange() : null;
completionList = TranslateTextEdits(request.Position, projectedPosition, wordRange, completionList);

if (completionList.ItemDefaults?.EditRange != null)
{
completionList.ItemDefaults.EditRange = TranslateRange(request.Position, projectedPosition, wordRange, completionList.ItemDefaults.EditRange);
}

var requestContext = new CompletionRequestContext(documentSnapshot.Uri, projectedDocumentUri, serverKind);
var resultId = _completionRequestContextCache.Set(requestContext);
Expand Down Expand Up @@ -604,10 +610,9 @@ private static CompletionList RemovePreselection(CompletionList completionList)
internal static CompletionList TranslateTextEdits(
Position hostDocumentPosition,
Position projectedPosition,
TextExtent? wordExtent,
Range? wordRange,
CompletionList completionList)
{
var wordRange = wordExtent.HasValue && wordExtent.Value.IsSignificant ? wordExtent?.Span.AsRange() : null;
var newItems = completionList.Items.Select(item => TranslateTextEdits(hostDocumentPosition, projectedPosition, wordRange, item)).ToArray();
completionList.Items = newItems;

Expand All @@ -617,23 +622,11 @@ static CompletionItem TranslateTextEdits(Position hostDocumentPosition, Position
{
if (item.TextEdit != null)
{
var offset = projectedPosition.Character - hostDocumentPosition.Character;

var editStartPosition = item.TextEdit.Range.Start;
var translatedStartPosition = TranslatePosition(offset, hostDocumentPosition, editStartPosition);
var editEndPosition = item.TextEdit.Range.End;
var translatedEndPosition = TranslatePosition(offset, hostDocumentPosition, editEndPosition);
var translatedRange = new Range()
{
Start = translatedStartPosition,
End = translatedEndPosition,
};

var translatedText = item.TextEdit.NewText;
item.TextEdit = new TextEdit()
var translatedRange = TranslateRange(hostDocumentPosition, projectedPosition, wordRange, item.TextEdit.Range);
item.TextEdit = new TextEdit
{
NewText = item.TextEdit.NewText,
Range = translatedRange,
NewText = translatedText,
};
}
else if (item.AdditionalTextEdits != null)
Expand All @@ -643,16 +636,33 @@ static CompletionItem TranslateTextEdits(Position hostDocumentPosition, Position
}

return item;
}
}

static Position TranslatePosition(int offset, Position hostDocumentPosition, Position editPosition)
{
var translatedCharacter = editPosition.Character - offset;
internal static Range TranslateRange(Position hostDocumentPosition, Position projectedPosition, Range? wordRange, Range textEditRange)
{
var offset = projectedPosition.Character - hostDocumentPosition.Character;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just moved into its own method


// Note: If this completion handler ever expands to deal with multi-line TextEdits, this logic will likely need to change since
// it assumes we're only dealing with single-line TextEdits.
var translatedPosition = new Position(hostDocumentPosition.Line, translatedCharacter);
return translatedPosition;
}
var editStartPosition = textEditRange.Start;
var translatedStartPosition = TranslatePosition(offset, hostDocumentPosition, editStartPosition);
var editEndPosition = textEditRange.End;
var translatedEndPosition = TranslatePosition(offset, hostDocumentPosition, editEndPosition);
var translatedRange = new Range()
{
Start = translatedStartPosition,
End = translatedEndPosition,
};

return translatedRange;

static Position TranslatePosition(int offset, Position hostDocumentPosition, Position editPosition)
{
var translatedCharacter = editPosition.Character - offset;

// Note: If this completion handler ever expands to deal with multi-line TextEdits, this logic will likely need to change since
// it assumes we're only dealing with single-line TextEdits.
var translatedPosition = new Position(hostDocumentPosition.Line, translatedCharacter);
return translatedPosition;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Threading.Tasks;
using Microsoft.VisualStudio.LanguageServer.ContainedLanguage;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Microsoft.VisualStudio.LanguageServerClient.Razor.Extensions;
using Microsoft.VisualStudio.Test;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Operations;
Expand Down Expand Up @@ -1427,7 +1428,7 @@ void M()
}
};

var result = CompletionHandler.TranslateTextEdits(razorDocPosition, cSharpDocPosition, wordRange, completionList);
var result = CompletionHandler.TranslateTextEdits(razorDocPosition, cSharpDocPosition, wordRange.Span.AsRange(), completionList);
var actualRange = result.Items.First().TextEdit.Range;
Assert.Equal(expectedRange, actualRange);
}
Expand Down Expand Up @@ -1512,6 +1513,71 @@ public void GetBaseIndentation_Tabs_Mixed()
Assert.Equal(8, indentation);
}

[Fact]
public async Task HandleRequestAsync_CSharpProjection_ItemDefault()
{
// Arrange
var called = false;
var expectedItem = new CompletionItem() { InsertText = "DateTime", Label = "DateTime" };
var completionRequest = new CompletionParams()
{
TextDocument = new TextDocumentIdentifier() { Uri = Uri },
Context = new VSInternalCompletionContext() { TriggerKind = CompletionTriggerKind.Invoked, InvokeKind = VSInternalCompletionInvokeKind.Explicit },
Position = new Position(0, 1)
};

var documentManager = new TestDocumentManager();
documentManager.AddDocument(Uri, new TestLSPDocumentSnapshot(new Uri("C:/path/file.razor"), 0, CSharpVirtualDocumentSnapshot));

var returnedCompletionList = new VSInternalCompletionList
{
Items = new CompletionItem[] { expectedItem },
ItemDefaults = new CompletionListItemDefaults
{
EditRange = new LanguageServer.Protocol.Range
{
Start = new Position(20, 30),
End = new Position(20, 30),
}
}
};

var requestInvoker = new Mock<LSPRequestInvoker>(MockBehavior.Strict);
requestInvoker
.Setup(r => r.ReinvokeRequestOnServerAsync<CompletionParams, SumType<CompletionItem[], CompletionList>?>(TextBuffer, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CompletionParams>(), It.IsAny<CancellationToken>()))
.Callback<ITextBuffer, string, string, CompletionParams, CancellationToken>((textBuffer, method, clientName, completionParams, ct) =>
{
Assert.Equal(Methods.TextDocumentCompletionName, method);
Assert.Equal(RazorLSPConstants.RazorCSharpLanguageServerName, clientName);
var vsCompletionContext = Assert.IsType<VSInternalCompletionContext>(completionParams.Context);
Assert.Equal(VSInternalCompletionInvokeKind.Explicit, vsCompletionContext.InvokeKind);
called = true;
})
.Returns(Task.FromResult(new ReinvocationResponse<SumType<CompletionItem[], CompletionList>?>(_languageClient, returnedCompletionList)));

var projectionResult = new ProjectionResult()
{
LanguageKind = RazorLanguageKind.CSharp,
Position = new Position(10, 30),
};
var projectionProvider = new Mock<LSPProjectionProvider>(MockBehavior.Strict);
projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));

var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object, TextStructureNavigatorSelectorService, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);

// Act
var result = await completionHandler.HandleRequestAsync(completionRequest, new ClientCapabilities(), CancellationToken.None).ConfigureAwait(false);

// Assert
Assert.True(called);
var item = ((CompletionList)result.Value).Items.First();
Assert.Equal(expectedItem.InsertText, item.InsertText);
Assert.Equal(new LanguageServer.Protocol.Range
{
Start = new Position(0, 1), End = new Position(0, 1)
}, ((CompletionList)result.Value).ItemDefaults.EditRange);
}

private static ITextStructureNavigatorSelectorService BuildNavigatorSelector(TextExtent wordRange)
{
var navigator = new Mock<ITextStructureNavigator>(MockBehavior.Strict);
Expand Down