From 61a5df7af1af688f90142de52cf7661e7f11db48 Mon Sep 17 00:00:00 2001 From: David Barbet Date: Tue, 29 Mar 2022 16:30:56 -0700 Subject: [PATCH 1/2] Bump LSP version for item defaults --- eng/Versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/Versions.props b/eng/Versions.props index f6ee0f21915..4da287bbd61 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -77,7 +77,7 @@ 17.0.31723.112 17.0.487 4.2.0-3.22164.11 - 17.1.11 + 17.2.8 4.2.0-1.final From 55c55fdf323cb07cfe7d4dde511836fd1f4a634d Mon Sep 17 00:00:00 2001 From: David Barbet Date: Tue, 29 Mar 2022 18:05:21 -0700 Subject: [PATCH 2/2] Support item defaults from C# completion --- .../HtmlCSharp/CompletionHandler.cs | 62 ++++++++++------- .../HtmlCSharp/CompletionHandlerTest.cs | 68 ++++++++++++++++++- 2 files changed, 103 insertions(+), 27 deletions(-) diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/HtmlCSharp/CompletionHandler.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/HtmlCSharp/CompletionHandler.cs index 513fc140e28..f198b2d6fb1 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/HtmlCSharp/CompletionHandler.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServerClient.Razor/HtmlCSharp/CompletionHandler.cs @@ -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); @@ -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; @@ -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) @@ -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; - // 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; } } diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServerClient.Razor.Test/HtmlCSharp/CompletionHandlerTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServerClient.Razor.Test/HtmlCSharp/CompletionHandlerTest.cs index ae2e48ed6fb..1c1790b3945 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServerClient.Razor.Test/HtmlCSharp/CompletionHandlerTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServerClient.Razor.Test/HtmlCSharp/CompletionHandlerTest.cs @@ -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; @@ -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); } @@ -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(MockBehavior.Strict); + requestInvoker + .Setup(r => r.ReinvokeRequestOnServerAsync?>(TextBuffer, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((textBuffer, method, clientName, completionParams, ct) => + { + Assert.Equal(Methods.TextDocumentCompletionName, method); + Assert.Equal(RazorLSPConstants.RazorCSharpLanguageServerName, clientName); + var vsCompletionContext = Assert.IsType(completionParams.Context); + Assert.Equal(VSInternalCompletionInvokeKind.Explicit, vsCompletionContext.InvokeKind); + called = true; + }) + .Returns(Task.FromResult(new ReinvocationResponse?>(_languageClient, returnedCompletionList))); + + var projectionResult = new ProjectionResult() + { + LanguageKind = RazorLanguageKind.CSharp, + Position = new Position(10, 30), + }; + var projectionProvider = new Mock(MockBehavior.Strict); + projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny(), It.IsAny(), It.IsAny())).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(MockBehavior.Strict);