From 6c1bee1940e94387d96b03ed03837ad232a82099 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Sat, 27 Oct 2018 20:53:25 -0700 Subject: [PATCH] Classify non-C# inside C# Fixes a bug with preview formatting for FAR. So when we ask the Roslyn API to classify C# for us, it will only classify the actual C# tokens. We are responsible for filling in the gaps and whitespace. The bug is that the following text would have all of its whitespace removed in the VS FAR preview window. ``` @{ var foo = "Hello, world!"; } ``` Would look like: ``` @{varfoo="Hello, world!";} ``` This fixes the issue and makes it look like what one would expect. --- .../RazorDocumentExcerptService.cs | 24 ++++- .../RazorExcerptServiceTest.cs | 90 +++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorDocumentExcerptService.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorDocumentExcerptService.cs index 93f836221f..bb3ba757cc 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorDocumentExcerptService.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorDocumentExcerptService.cs @@ -176,6 +176,10 @@ namespace Microsoft.CodeAnalysis.Razor secondaryDocument, secondarySpan, cancellationToken); + + // NOTE: The Classifier will only returns spans for things that it understands. That means + // that whitespace is not classified. The preview expects us to provide contiguous spans, + // so we are going to have to fill in the gaps. // Now we have to translate back to the primary document's coordinates. var offset = primarySpan.Start - secondarySpan.Start; @@ -185,11 +189,29 @@ namespace Microsoft.CodeAnalysis.Razor var updated = new TextSpan(classifiedSecondarySpan.TextSpan.Start + offset, classifiedSecondarySpan.TextSpan.Length); Debug.Assert(primarySpan.Contains(updated)); + + // Make sure that we're not introducing a gap. Remember, we need to fill in the whitespace. + if (remainingSpan.Start < updated.Start) + { + builder.Add(new ClassifiedSpan( + ClassificationTypeNames.Text, + new TextSpan(remainingSpan.Start, updated.Start - remainingSpan.Start))); + remainingSpan = new TextSpan(updated.Start, remainingSpan.Length - (updated.Start - remainingSpan.Start)); + } builder.Add(new ClassifiedSpan(classifiedSecondarySpan.ClassificationType, updated)); + remainingSpan = new TextSpan(updated.End, remainingSpan.Length - (updated.End - remainingSpan.Start)); + } + + // Make sure that we're not introducing a gap. Remember, we need to fill in the whitespace. + if (remainingSpan.Start < primarySpan.End) + { + builder.Add(new ClassifiedSpan( + ClassificationTypeNames.Text, + new TextSpan(remainingSpan.Start, primarySpan.End - remainingSpan.Start))); + remainingSpan = new TextSpan(primarySpan.End, remainingSpan.Length - (primarySpan.End - remainingSpan.Start)); } - remainingSpan = new TextSpan(primarySpan.End, remainingSpan.Length - primarySpan.Length); } // Deal with residue diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/RazorExcerptServiceTest.cs b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/RazorExcerptServiceTest.cs index 92cced1968..7466c4f09f 100644 --- a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/RazorExcerptServiceTest.cs +++ b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/RazorExcerptServiceTest.cs @@ -70,22 +70,42 @@ namespace Microsoft.CodeAnalysis.Razor Assert.Equal(@" var foo = ""Hello, World!"";", result.Value.Content.ToString(), ignoreLineEndingDifferences: true); Assert.Collection( result.Value.ClassifiedSpans, + c => + { + Assert.Equal(ClassificationTypeNames.Text, c.ClassificationType); + Assert.Equal(" ", result.Value.Content.GetSubText(c.TextSpan).ToString()); + }, c => { Assert.Equal(ClassificationTypeNames.Keyword, c.ClassificationType); Assert.Equal("var", result.Value.Content.GetSubText(c.TextSpan).ToString()); }, c => + { + Assert.Equal(ClassificationTypeNames.Text, c.ClassificationType); + Assert.Equal(" ", result.Value.Content.GetSubText(c.TextSpan).ToString()); + }, + c => { Assert.Equal(ClassificationTypeNames.LocalName, c.ClassificationType); Assert.Equal("foo", result.Value.Content.GetSubText(c.TextSpan).ToString()); }, c => + { + Assert.Equal(ClassificationTypeNames.Text, c.ClassificationType); + Assert.Equal(" ", result.Value.Content.GetSubText(c.TextSpan).ToString()); + }, + c => { Assert.Equal(ClassificationTypeNames.Operator, c.ClassificationType); Assert.Equal("=", result.Value.Content.GetSubText(c.TextSpan).ToString()); }, c => + { + Assert.Equal(ClassificationTypeNames.Text, c.ClassificationType); + Assert.Equal(" ", result.Value.Content.GetSubText(c.TextSpan).ToString()); + }, + c => { Assert.Equal(ClassificationTypeNames.StringLiteral, c.ClassificationType); Assert.Equal("\"Hello, World!\"", result.Value.Content.GetSubText(c.TextSpan).ToString()); @@ -199,11 +219,21 @@ namespace Microsoft.CodeAnalysis.Razor Assert.Equal("3", result.Value.Content.GetSubText(c.TextSpan).ToString()); }, c => + { + Assert.Equal(ClassificationTypeNames.Text, c.ClassificationType); + Assert.Equal(" ", result.Value.Content.GetSubText(c.TextSpan).ToString()); + }, + c => { Assert.Equal(ClassificationTypeNames.Operator, c.ClassificationType); Assert.Equal("+", result.Value.Content.GetSubText(c.TextSpan).ToString()); }, c => + { + Assert.Equal(ClassificationTypeNames.Text, c.ClassificationType); + Assert.Equal(" ", result.Value.Content.GetSubText(c.TextSpan).ToString()); + }, + c => { Assert.Equal(ClassificationTypeNames.NumericLiteral, c.ClassificationType); Assert.Equal("4", result.Value.Content.GetSubText(c.TextSpan).ToString()); @@ -219,11 +249,21 @@ namespace Microsoft.CodeAnalysis.Razor Assert.Equal("foo", result.Value.Content.GetSubText(c.TextSpan).ToString()); }, c => + { + Assert.Equal(ClassificationTypeNames.Text, c.ClassificationType); + Assert.Equal(" ", result.Value.Content.GetSubText(c.TextSpan).ToString()); + }, + c => { Assert.Equal(ClassificationTypeNames.Operator, c.ClassificationType); Assert.Equal("+", result.Value.Content.GetSubText(c.TextSpan).ToString()); }, c => + { + Assert.Equal(ClassificationTypeNames.Text, c.ClassificationType); + Assert.Equal(" ", result.Value.Content.GetSubText(c.TextSpan).ToString()); + }, + c => { Assert.Equal(ClassificationTypeNames.LocalName, c.ClassificationType); Assert.Equal("foo", result.Value.Content.GetSubText(c.TextSpan).ToString()); @@ -292,21 +332,41 @@ namespace Microsoft.CodeAnalysis.Razor ignoreLineEndingDifferences: true); }, c => + { + Assert.Equal(ClassificationTypeNames.Text, c.ClassificationType); + Assert.Equal("\r\n ", result.Value.Content.GetSubText(c.TextSpan).ToString(), ignoreLineEndingDifferences: true); + }, + c => { Assert.Equal(ClassificationTypeNames.Keyword, c.ClassificationType); Assert.Equal("var", result.Value.Content.GetSubText(c.TextSpan).ToString()); }, c => + { + Assert.Equal(ClassificationTypeNames.Text, c.ClassificationType); + Assert.Equal(" ", result.Value.Content.GetSubText(c.TextSpan).ToString()); + }, + c => { Assert.Equal(ClassificationTypeNames.LocalName, c.ClassificationType); Assert.Equal("foo", result.Value.Content.GetSubText(c.TextSpan).ToString()); }, c => + { + Assert.Equal(ClassificationTypeNames.Text, c.ClassificationType); + Assert.Equal(" ", result.Value.Content.GetSubText(c.TextSpan).ToString()); + }, + c => { Assert.Equal(ClassificationTypeNames.Operator, c.ClassificationType); Assert.Equal("=", result.Value.Content.GetSubText(c.TextSpan).ToString()); }, c => + { + Assert.Equal(ClassificationTypeNames.Text, c.ClassificationType); + Assert.Equal(" ", result.Value.Content.GetSubText(c.TextSpan).ToString()); + }, + c => { Assert.Equal(ClassificationTypeNames.StringLiteral, c.ClassificationType); Assert.Equal("\"Hello, World!\"", result.Value.Content.GetSubText(c.TextSpan).ToString()); @@ -317,6 +377,11 @@ namespace Microsoft.CodeAnalysis.Razor Assert.Equal(";", result.Value.Content.GetSubText(c.TextSpan).ToString()); }, c => + { + Assert.Equal(ClassificationTypeNames.Text, c.ClassificationType); + Assert.Equal("\r\n", result.Value.Content.GetSubText(c.TextSpan).ToString(), ignoreLineEndingDifferences: true); + }, + c => { Assert.Equal(ClassificationTypeNames.Text, c.ClassificationType); Assert.Equal( @@ -365,21 +430,41 @@ namespace Microsoft.CodeAnalysis.Razor Assert.Equal("@{", result.Value.Content.GetSubText(c.TextSpan).ToString()); }, c => + { + Assert.Equal(ClassificationTypeNames.Text, c.ClassificationType); + Assert.Equal(" ", result.Value.Content.GetSubText(c.TextSpan).ToString()); + }, + c => { Assert.Equal(ClassificationTypeNames.Keyword, c.ClassificationType); Assert.Equal("var", result.Value.Content.GetSubText(c.TextSpan).ToString()); }, c => + { + Assert.Equal(ClassificationTypeNames.Text, c.ClassificationType); + Assert.Equal(" ", result.Value.Content.GetSubText(c.TextSpan).ToString()); + }, + c => { Assert.Equal(ClassificationTypeNames.LocalName, c.ClassificationType); Assert.Equal("foo", result.Value.Content.GetSubText(c.TextSpan).ToString()); }, c => + { + Assert.Equal(ClassificationTypeNames.Text, c.ClassificationType); + Assert.Equal(" ", result.Value.Content.GetSubText(c.TextSpan).ToString()); + }, + c => { Assert.Equal(ClassificationTypeNames.Operator, c.ClassificationType); Assert.Equal("=", result.Value.Content.GetSubText(c.TextSpan).ToString()); }, c => + { + Assert.Equal(ClassificationTypeNames.Text, c.ClassificationType); + Assert.Equal(" ", result.Value.Content.GetSubText(c.TextSpan).ToString()); + }, + c => { Assert.Equal(ClassificationTypeNames.StringLiteral, c.ClassificationType); Assert.Equal("\"Hello, World!\"", result.Value.Content.GetSubText(c.TextSpan).ToString()); @@ -390,6 +475,11 @@ namespace Microsoft.CodeAnalysis.Razor Assert.Equal(";", result.Value.Content.GetSubText(c.TextSpan).ToString()); }, c => + { + Assert.Equal(ClassificationTypeNames.Text, c.ClassificationType); + Assert.Equal(" ", result.Value.Content.GetSubText(c.TextSpan).ToString()); + }, + c => { Assert.Equal(ClassificationTypeNames.Text, c.ClassificationType); Assert.Equal("}", result.Value.Content.GetSubText(c.TextSpan).ToString());