Skip to content

Commit

Permalink
Merge pull request #2456 from bjorkstromm/feature/cake-v2-highlight
Browse files Browse the repository at this point in the history
Adds V2 Highlight support to Cake
  • Loading branch information
bjorkstromm authored Oct 5, 2022
2 parents dd24a60 + d26fb63 commit ee8e47d
Show file tree
Hide file tree
Showing 4 changed files with 261 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ 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))
* Include OmniSharp.Cake in .NET 6 builds (PR: [#2455](https://github.com/OmniSharp/omnisharp-roslyn/pull/2455))
* 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))
Expand Down
35 changes: 35 additions & 0 deletions src/OmniSharp.Cake/Extensions/ResponseExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -232,6 +233,40 @@ public static async Task<CompletionResponse> TranslateAsync(this CompletionRespo
return response;
}

public static async Task<SemanticHighlightResponse> TranslateAsync(this SemanticHighlightResponse response,
OmniSharpWorkspace workspace, SemanticHighlightRequest request)
{
var zeroIndex = await LineIndexHelper.TranslateToGenerated(request.FileName, 0, workspace);
var spans = new List<SemanticHighlightSpan>();

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<CodeElement> TranslateAsync(this CodeElement element, OmniSharpWorkspace workspace, SimpleFileRequest request)
{
var builder = new CodeElement.Builder
Expand Down
Original file line number Diff line number Diff line change
@@ -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<SemanticHighlightRequest, SemanticHighlightResponse>
{
[ImportingConstructor]
public SemanticHighlightHandler(OmniSharpWorkspace workspace) : base(workspace)
{
}

protected override async Task<SemanticHighlightRequest> 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<SemanticHighlightResponse> TranslateResponse(SemanticHighlightResponse response, SemanticHighlightRequest request)
{
return response.TranslateAsync(Workspace, request);
}
}
183 changes: 183 additions & 0 deletions tests/OmniSharp.Cake.Tests/SemanticHighlightFacts.cs
Original file line number Diff line number Diff line change
@@ -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<SemanticHighlightHandler>
{
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<SemanticHighlightSpan[]> GetSemanticHighlightsForFileAsync(string source)
{
return GetSemanticHighlightsAsync(source, range: null, versionedText: null);
}

private Task<SemanticHighlightSpan[]> 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<SemanticHighlightSpan[]> 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);
}
}

0 comments on commit ee8e47d

Please sign in to comment.