From 43ecd728986fca5f1ebcc97377659cfb285b9d3d Mon Sep 17 00:00:00 2001 From: Joey Robichaud Date: Thu, 3 Oct 2024 00:51:30 -0700 Subject: [PATCH] LSP hover responses escape backticks within inline code --- .../Extensions/ProtocolConversions.cs | 9 ++--- .../ProtocolUnitTests/Hover/HoverTests.cs | 33 +++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/LanguageServer/Protocol/Extensions/ProtocolConversions.cs b/src/LanguageServer/Protocol/Extensions/ProtocolConversions.cs index eac849642c1fe..71e2553dcbb44 100644 --- a/src/LanguageServer/Protocol/Extensions/ProtocolConversions.cs +++ b/src/LanguageServer/Protocol/Extensions/ProtocolConversions.cs @@ -216,9 +216,9 @@ public static Uri CreateAbsoluteUri(string absolutePath) internal static Uri CreateRelativePatternBaseUri(string path) { - // According to VSCode LSP RelativePattern spec, + // According to VSCode LSP RelativePattern spec, // found at https://github.com/microsoft/vscode/blob/9e1974682eb84eebb073d4ae775bad1738c281f6/src/vscode-dts/vscode.d.ts#L2226 - // the baseUri should not end in a trailing separator, nor should it + // the baseUri should not end in a trailing separator, nor should it // have any relative segmeents (., ..) if (path[^1] == System.IO.Path.DirectorySeparatorChar) { @@ -995,7 +995,7 @@ static string GetStyledText(TaggedText taggedText, bool isInCodeBlock) if (!string.IsNullOrEmpty(taggedText.NavigationHint) && taggedText.NavigationHint == taggedText.NavigationTarget) return $"[{text}]({taggedText.NavigationHint})"; - // Markdown ignores spaces at the start of lines outside of code blocks, + // Markdown ignores spaces at the start of lines outside of code blocks, // so we replace regular spaces with non-breaking spaces to ensure structural space is retained. // We want to use regular spaces everywhere else to allow the client to wrap long text. if (!isCode && taggedText.Tag is TextTags.Space or TextTags.ContainerStart) @@ -1007,7 +1007,8 @@ static string GetStyledText(TaggedText taggedText, bool isInCodeBlock) TaggedTextStyle.Strong => $"**{text}**", TaggedTextStyle.Emphasis => $"_{text}_", TaggedTextStyle.Underline => $"{text}", - TaggedTextStyle.Code => $"`{text}`", + // Use double backticks to escape code which contains a backtick. + TaggedTextStyle.Code => text.Contains('`') ? $"``{text}``" : $"`{text}`", _ => text, }; } diff --git a/src/LanguageServer/ProtocolUnitTests/Hover/HoverTests.cs b/src/LanguageServer/ProtocolUnitTests/Hover/HoverTests.cs index bea12674040c8..f614909890de6 100644 --- a/src/LanguageServer/ProtocolUnitTests/Hover/HoverTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Hover/HoverTests.cs @@ -466,6 +466,39 @@ void A.AMethod(int i) Assert.Equal(expectedMarkdown, results.Contents.Fourth.Value); } + [Theory, CombinatorialData, WorkItem("https://github.com/microsoft/vscode-dotnettools/issues/1499")] + public async Task TestGetHoverAsync_UsingMarkupContentEscapesBacktickInCode(bool mutatingLspWorkspace) + { + var markup = +@"class A +{ + /// + /// Hello A`1[B,C] + /// + void {|caret:AMethod|}(int i) + { + } +}"; + var clientCapabilities = new LSP.ClientCapabilities + { + TextDocument = new LSP.TextDocumentClientCapabilities { Hover = new LSP.HoverSetting { ContentFormat = [LSP.MarkupKind.Markdown] } } + }; + await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, clientCapabilities); + var expectedLocation = testLspServer.GetLocations("caret").Single(); + + var expectedMarkdown = @"```csharp +void A.AMethod(int i) +``` + +Hello ``A`1[B,C]`` +"; + + var results = await RunGetHoverAsync( + testLspServer, + expectedLocation).ConfigureAwait(false); + Assert.Equal(expectedMarkdown, results.Contents.Fourth.Value); + } + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/vscode-csharp/issues/6577")] public async Task TestGetHoverAsync_UsesInlineCodeFencesInAwaitReturn(bool mutatingLspWorkspace) {