From 8f029731712d3329c7eba8d5a5b8cead75b36787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Bj=C3=B6rkstr=C3=B6m?= Date: Wed, 5 Oct 2022 22:49:08 +0300 Subject: [PATCH 1/2] Adds V2 Highlight support to Cake --- .../Extensions/ResponseExtensions.cs | 35 ++++ .../SemanticHighlightHandler.cs | 41 ++++ .../SemanticHighlightFacts.cs | 183 ++++++++++++++++++ 3 files changed, 259 insertions(+) create mode 100644 src/OmniSharp.Cake/Services/RequestHandlers/SemanticHighlight/SemanticHighlightHandler.cs create mode 100644 tests/OmniSharp.Cake.Tests/SemanticHighlightFacts.cs diff --git a/src/OmniSharp.Cake/Extensions/ResponseExtensions.cs b/src/OmniSharp.Cake/Extensions/ResponseExtensions.cs index f9dba706a1..40ba66c03a 100644 --- a/src/OmniSharp.Cake/Extensions/ResponseExtensions.cs +++ b/src/OmniSharp.Cake/Extensions/ResponseExtensions.cs @@ -8,6 +8,7 @@ using OmniSharp.Models.Navigate; using OmniSharp.Models.MembersTree; using OmniSharp.Models.Rename; +using OmniSharp.Models.SemanticHighlight; using OmniSharp.Models.v1.Completion; using OmniSharp.Models.V2; using OmniSharp.Models.V2.CodeActions; @@ -232,6 +233,40 @@ public static async Task TranslateAsync(this CompletionRespo return response; } + public static async Task TranslateAsync(this SemanticHighlightResponse response, + OmniSharpWorkspace workspace, SemanticHighlightRequest request) + { + var zeroIndex = await LineIndexHelper.TranslateToGenerated(request.FileName, 0, workspace); + var spans = new List(); + + foreach (var span in response.Spans) + { + if (span.StartLine < zeroIndex) + { + continue; + } + + var (startLine, _) = await LineIndexHelper.TranslateFromGenerated(request.FileName, span.StartLine, workspace, true); + + if (startLine < 0) + { + continue; + } + + var (endLine, _) = span.StartLine != span.EndLine + ? await LineIndexHelper.TranslateFromGenerated(request.FileName, span.EndLine, workspace, true) + : (startLine, null); + + span.StartLine = startLine; + span.EndLine = endLine; + + spans.Add(span); + } + + response.Spans = spans.ToArray(); + return response; + } + private static async Task TranslateAsync(this CodeElement element, OmniSharpWorkspace workspace, SimpleFileRequest request) { var builder = new CodeElement.Builder diff --git a/src/OmniSharp.Cake/Services/RequestHandlers/SemanticHighlight/SemanticHighlightHandler.cs b/src/OmniSharp.Cake/Services/RequestHandlers/SemanticHighlight/SemanticHighlightHandler.cs new file mode 100644 index 0000000000..06b7139d8e --- /dev/null +++ b/src/OmniSharp.Cake/Services/RequestHandlers/SemanticHighlight/SemanticHighlightHandler.cs @@ -0,0 +1,41 @@ +using System.Composition; +using System.Threading.Tasks; +using OmniSharp.Cake.Extensions; +using OmniSharp.Cake.Utilities; +using OmniSharp.Mef; +using OmniSharp.Models.SemanticHighlight; + +namespace OmniSharp.Cake.Services.RequestHandlers.SemanticHighlight; + +[OmniSharpHandler(OmniSharpEndpoints.V2.Highlight, Constants.LanguageNames.Cake), Shared] +public class SemanticHighlightHandler : CakeRequestHandler +{ + [ImportingConstructor] + public SemanticHighlightHandler(OmniSharpWorkspace workspace) : base(workspace) + { + } + + protected override async Task TranslateRequestAsync(SemanticHighlightRequest request) + { + if (request.Range is not null) + { + var startLine = await LineIndexHelper.TranslateToGenerated(request.FileName, request.Range.Start.Line, Workspace); + var endLine = request.Range.Start.Line != request.Range.End.Line + ? await LineIndexHelper.TranslateToGenerated(request.FileName, request.Range.End.Line, Workspace) + : startLine; + + request.Range = request.Range with + { + Start = request.Range.Start with { Line = startLine }, + End = request.Range.End with { Line = endLine } + }; + } + + return await base.TranslateRequestAsync(request); + } + + protected override Task TranslateResponse(SemanticHighlightResponse response, SemanticHighlightRequest request) + { + return response.TranslateAsync(Workspace, request); + } +} diff --git a/tests/OmniSharp.Cake.Tests/SemanticHighlightFacts.cs b/tests/OmniSharp.Cake.Tests/SemanticHighlightFacts.cs new file mode 100644 index 0000000000..1da2b61e7e --- /dev/null +++ b/tests/OmniSharp.Cake.Tests/SemanticHighlightFacts.cs @@ -0,0 +1,183 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Text; +using OmniSharp.Cake.Services.RequestHandlers.SemanticHighlight; +using OmniSharp.Models.SemanticHighlight; +using OmniSharp.Models.UpdateBuffer; +using OmniSharp.Models.V2; +using TestUtility; +using Xunit; +using Xunit.Abstractions; +using Range = OmniSharp.Models.V2.Range; + +namespace OmniSharp.Cake.Tests +{ + public class SemanticHighlightFacts : CakeSingleRequestHandlerTestFixture + { + public SemanticHighlightFacts(ITestOutputHelper testOutput) : base(testOutput) + { + } + + protected override string EndpointName => OmniSharpEndpoints.V2.Highlight; + + [Fact] + public async Task SemanticHighlightSingleLine() + { + const string source = @" +class C1 +{ + class C2 { int n = true; } +} +"; + + var line = 3; + var highlights = await GetSemanticHighlightsForLineAsync(source, line, versionedText: null); + + AssertSyntax(highlights, source, line, + Keyword("class"), + ClassName("C2"), + Punctuation("{"), + Keyword("int"), + Field("n"), + Operator("="), + Keyword("true"), + Punctuation(";"), + Punctuation("}")); + } + + [Fact] + public async Task SemanticHighlightEntireFile() + { + const string source = @" +class C1 +{ + class C2 { int n = true; } +} +"; + + var highlights = await GetSemanticHighlightsForFileAsync(source); + + AssertSyntax(highlights, source, 0, + Keyword("class"), + ClassName("C1"), + Punctuation("{"), + Keyword("class"), + ClassName("C2"), + Punctuation("{"), + Keyword("int"), + Field("n"), + Operator("="), + Keyword("true"), + Punctuation(";"), + Punctuation("}"), + Punctuation("}") + ); + } + + private Task GetSemanticHighlightsForFileAsync(string source) + { + return GetSemanticHighlightsAsync(source, range: null, versionedText: null); + } + + private Task GetSemanticHighlightsForLineAsync(string source, int line, string versionedText) + { + var range = new Range() + { + Start = new Point() { Column = 0, Line = line }, + End = new Point() { Column = 0, Line = line + 1 } + }; + + return GetSemanticHighlightsAsync(source, range, versionedText); + } + + private async Task GetSemanticHighlightsAsync(string source, Range range, string versionedText) + { + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("CakeProject", shadowCopy : false)) + using (var host = CreateOmniSharpHost(testProject.Directory)) + { + var testFile = new TestFile(Path.Combine(testProject.Directory, "build.cake"), source); + + var request = new SemanticHighlightRequest + { + FileName = testFile.FileName, + Range = range, + VersionedText = versionedText, + }; + + var updateBufferRequest = new UpdateBufferRequest + { + Buffer = source, + FileName = request.FileName, + FromDisk = false, + }; + + await GetUpdateBufferHandler(host).Handle(updateBufferRequest); + + var requestHandler = GetRequestHandler(host); + + var response = await requestHandler.Handle(request); + + return response.Spans; + } + } + + private static void AssertSyntax(SemanticHighlightSpan[] highlights, string code, int startLine, params (SemanticHighlightClassification kind, string text, SemanticHighlightModifier[] modifiers)[] expectedTokens) + { + var lineNo = startLine; + var lastIndex = 0; + var lines = SourceText.From(code).Lines; + + for (var i = 0; i < highlights.Length; i++) + { + var (type, text, modifiers) = expectedTokens[i]; + var highlight = highlights[i]; + + string line; + int start, end; + do + { + line = lines[lineNo].ToString(); + start = line.IndexOf(text, lastIndex); + if (start == -1) + { + if (++lineNo >= lines.Count) + { + throw new Exception($"Could not find token {text} in the code"); + } + + lastIndex = 0; + } + } + while (start == -1); + + end = start + text.Length; + lastIndex = end; + + Assert.Equal(type, highlight.Type); + Assert.Equal(modifiers, highlight.Modifiers); + Assert.Equal(lineNo, highlight.StartLine); + Assert.Equal(lineNo, highlight.EndLine); + Assert.Equal(start, highlight.StartColumn); + Assert.Equal(end, highlight.EndColumn); + } + + Assert.Equal(expectedTokens.Length, highlights.Length); + } + + private static (SemanticHighlightClassification type, string text, SemanticHighlightModifier[] modifiers) Method(string text, params SemanticHighlightModifier[] modifiers) => (SemanticHighlightClassification.MethodName, text, modifiers); + private static (SemanticHighlightClassification type, string text, SemanticHighlightModifier[] modifiers) Local(string text, params SemanticHighlightModifier[] modifiers) => (SemanticHighlightClassification.LocalName, text, modifiers); + private static (SemanticHighlightClassification type, string text, SemanticHighlightModifier[] modifiers) ClassName(string text, params SemanticHighlightModifier[] modifiers) => (SemanticHighlightClassification.ClassName, text, modifiers); + private static (SemanticHighlightClassification type, string text, SemanticHighlightModifier[] modifiers) StructName(string text, params SemanticHighlightModifier[] modifiers) => (SemanticHighlightClassification.StructName, text, modifiers); + private static (SemanticHighlightClassification type, string text, SemanticHighlightModifier[] modifiers) Field(string text, params SemanticHighlightModifier[] modifiers) => (SemanticHighlightClassification.FieldName, text, modifiers); + private static (SemanticHighlightClassification type, string text, SemanticHighlightModifier[] modifiers) Identifier(string text, params SemanticHighlightModifier[] modifiers) => (SemanticHighlightClassification.Identifier, text, modifiers); + private static (SemanticHighlightClassification type, string text, SemanticHighlightModifier[] modifiers) Parameter(string text, params SemanticHighlightModifier[] modifiers) => (SemanticHighlightClassification.ParameterName, text, modifiers); + private static (SemanticHighlightClassification type, string text, SemanticHighlightModifier[] modifiers) NamespaceName(string text, params SemanticHighlightModifier[] modifiers) => (SemanticHighlightClassification.NamespaceName, text, modifiers); + private static (SemanticHighlightClassification type, string text, SemanticHighlightModifier[] modifiers) Keyword(string text, params SemanticHighlightModifier[] modifiers) => (SemanticHighlightClassification.Keyword, text, modifiers); + private static (SemanticHighlightClassification type, string text, SemanticHighlightModifier[] modifiers) ControlKeyword(string text, params SemanticHighlightModifier[] modifiers) => (SemanticHighlightClassification.ControlKeyword, text, modifiers); + private static (SemanticHighlightClassification type, string text, SemanticHighlightModifier[] modifiers) Number(string text, params SemanticHighlightModifier[] modifiers) => (SemanticHighlightClassification.NumericLiteral, text, modifiers); + private static (SemanticHighlightClassification type, string text, SemanticHighlightModifier[] modifiers) Operator(string text, params SemanticHighlightModifier[] modifiers) => (SemanticHighlightClassification.Operator, text, modifiers); + private static (SemanticHighlightClassification type, string text, SemanticHighlightModifier[] modifiers) Punctuation(string text, params SemanticHighlightModifier[] modifiers) => (SemanticHighlightClassification.Punctuation, text, modifiers); + private static (SemanticHighlightClassification type, string text, SemanticHighlightModifier[] modifiers) String(string text, params SemanticHighlightModifier[] modifiers) => (SemanticHighlightClassification.StringLiteral, text, modifiers); + } +} From a08d0426e7297677f15e939e5071527e5ef72637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Bj=C3=B6rkstr=C3=B6m?= Date: Wed, 5 Oct 2022 22:52:31 +0300 Subject: [PATCH 2/2] Update Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25fbb47d96..63709b9929 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All changes to the project will be documented in this file. ## [1.39.2] - not yet released * Updated ILSpy to 7.2.1.6856 (PR: [#2447](https://github.com/OmniSharp/omnisharp-roslyn/pull/2447)) +* Implement /v2/highlight for Cake (PR: [#2456](https://github.com/OmniSharp/omnisharp-roslyn/pull/2456)) ## [1.39.1] - 2022-07-25 * Update Roslyn to 4.4.0 1.22369.1 (PR: [#2420](https://github.com/OmniSharp/omnisharp-roslyn/pull/2420))