Skip to content

Commit 6379c6a

Browse files
committed
Move code down to Workspaces to share
1 parent 496cfcd commit 6379c6a

File tree

6 files changed

+186
-176
lines changed

6 files changed

+186
-176
lines changed
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Immutable;
6+
using System.Diagnostics;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Microsoft.AspNetCore.Razor.Language;
10+
using Microsoft.CodeAnalysis.Classification;
11+
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
12+
using Microsoft.CodeAnalysis.Text;
13+
14+
namespace Microsoft.CodeAnalysis.Razor.DocumentExcerpt;
15+
16+
internal static class DocumentExcerptHelper
17+
{
18+
public static async Task<ImmutableArray<ClassifiedSpan>.Builder> ClassifyPreviewAsync(
19+
TextSpan excerptSpan,
20+
Document generatedDocument,
21+
ImmutableArray<SourceMapping> mappings,
22+
RazorClassificationOptionsWrapper options,
23+
CancellationToken cancellationToken)
24+
{
25+
var builder = ImmutableArray.CreateBuilder<ClassifiedSpan>();
26+
27+
var sorted = mappings.Sort((x, y) => x.OriginalSpan.AbsoluteIndex.CompareTo(y.OriginalSpan.AbsoluteIndex));
28+
29+
// The algorithm here is to iterate through the source mappings (sorted) and use the C# classifier
30+
// on the spans that are known to be C#. For the spans that are not known to be C# then
31+
// we just treat them as text since we'd don't currently have our own classifications.
32+
33+
var remainingSpan = excerptSpan;
34+
foreach (var span in sorted)
35+
{
36+
if (excerptSpan.Length == 0)
37+
{
38+
break;
39+
}
40+
41+
var primarySpan = span.OriginalSpan.AsTextSpan();
42+
if (primarySpan.Intersection(remainingSpan) is not TextSpan intersection)
43+
{
44+
// This span is outside the area we're interested in.
45+
continue;
46+
}
47+
48+
// OK this span intersects with the excerpt span, so we will process it. Let's compute
49+
// the secondary span that matches the intersection.
50+
var secondarySpan = span.GeneratedSpan.AsTextSpan();
51+
secondarySpan = new TextSpan(secondarySpan.Start + intersection.Start - primarySpan.Start, intersection.Length);
52+
primarySpan = intersection;
53+
54+
if (remainingSpan.Start < primarySpan.Start)
55+
{
56+
// The position is before the next C# span. Classify everything up to the C# start
57+
// as text.
58+
builder.Add(new ClassifiedSpan(ClassificationTypeNames.Text, new TextSpan(remainingSpan.Start, primarySpan.Start - remainingSpan.Start)));
59+
60+
// Advance to the start of the C# span.
61+
remainingSpan = new TextSpan(primarySpan.Start, remainingSpan.Length - (primarySpan.Start - remainingSpan.Start));
62+
}
63+
64+
// We should be able to process this whole span as C#, so classify it.
65+
//
66+
// However, we'll have to translate it to the the generated document's coordinates to do that.
67+
Debug.Assert(remainingSpan.Contains(primarySpan) && remainingSpan.Start == primarySpan.Start);
68+
var classifiedSecondarySpans = await RazorClassifierAccessor.GetClassifiedSpansAsync(
69+
generatedDocument,
70+
secondarySpan,
71+
options,
72+
cancellationToken).ConfigureAwait(false);
73+
74+
// NOTE: The Classifier will only returns spans for things that it understands. That means
75+
// that whitespace is not classified. The preview expects us to provide contiguous spans,
76+
// so we are going to have to fill in the gaps.
77+
78+
// Now we have to translate back to the primary document's coordinates.
79+
var offset = primarySpan.Start - secondarySpan.Start;
80+
foreach (var classifiedSecondarySpan in classifiedSecondarySpans)
81+
{
82+
// It's possible for the classified span to extend past our secondary span, so we cap it
83+
var classifiedSpan = classifiedSecondarySpan.TextSpan.End > secondarySpan.End
84+
? TextSpan.FromBounds(classifiedSecondarySpan.TextSpan.Start, secondarySpan.End)
85+
: classifiedSecondarySpan.TextSpan;
86+
Debug.Assert(secondarySpan.Contains(classifiedSpan));
87+
88+
var updated = new TextSpan(classifiedSpan.Start + offset, classifiedSpan.Length);
89+
Debug.Assert(primarySpan.Contains(updated));
90+
91+
// Make sure that we're not introducing a gap. Remember, we need to fill in the whitespace.
92+
if (remainingSpan.Start < updated.Start)
93+
{
94+
builder.Add(new ClassifiedSpan(
95+
ClassificationTypeNames.Text,
96+
new TextSpan(remainingSpan.Start, updated.Start - remainingSpan.Start)));
97+
remainingSpan = new TextSpan(updated.Start, remainingSpan.Length - (updated.Start - remainingSpan.Start));
98+
}
99+
100+
builder.Add(new ClassifiedSpan(classifiedSecondarySpan.ClassificationType, updated));
101+
remainingSpan = new TextSpan(updated.End, remainingSpan.Length - (updated.End - remainingSpan.Start));
102+
}
103+
104+
// Make sure that we're not introducing a gap. Remember, we need to fill in the whitespace.
105+
if (remainingSpan.Start < primarySpan.End)
106+
{
107+
builder.Add(new ClassifiedSpan(
108+
ClassificationTypeNames.Text,
109+
new TextSpan(remainingSpan.Start, primarySpan.End - remainingSpan.Start)));
110+
remainingSpan = new TextSpan(primarySpan.End, remainingSpan.Length - (primarySpan.End - remainingSpan.Start));
111+
}
112+
}
113+
114+
// Deal with residue
115+
if (remainingSpan.Length > 0)
116+
{
117+
// Trailing Razor/markup text.
118+
builder.Add(new ClassifiedSpan(ClassificationTypeNames.Text, remainingSpan));
119+
}
120+
121+
return builder;
122+
}
123+
124+
public static TextSpan ChooseExcerptSpan(SourceText text, TextSpan span, ExcerptModeInternal mode)
125+
{
126+
var startLine = text.Lines.GetLineFromPosition(span.Start);
127+
var endLine = text.Lines.GetLineFromPosition(span.End);
128+
129+
if (mode == ExcerptModeInternal.Tooltip)
130+
{
131+
// Expand the range by 3 in each direction (if possible).
132+
var startIndex = Math.Max(startLine.LineNumber - 3, 0);
133+
startLine = text.Lines[startIndex];
134+
135+
var endIndex = Math.Min(endLine.LineNumber + 3, text.Lines.Count - 1);
136+
endLine = text.Lines[endIndex];
137+
return CreateTextSpan(startLine, endLine);
138+
}
139+
else
140+
{
141+
// Trim leading whitespace in a single line excerpt
142+
var excerptSpan = CreateTextSpan(startLine, endLine);
143+
var trimmedExcerptSpan = excerptSpan.TrimLeadingWhitespace(text);
144+
return trimmedExcerptSpan;
145+
}
146+
147+
static TextSpan CreateTextSpan(TextLine startLine, TextLine endLine)
148+
{
149+
return new TextSpan(startLine.Start, endLine.End - startLine.Start);
150+
}
151+
}
152+
153+
public static SourceText GetTranslatedExcerptText(
154+
SourceText razorDocumentText,
155+
ref TextSpan razorDocumentSpan,
156+
ref TextSpan excerptSpan,
157+
ImmutableArray<ClassifiedSpan>.Builder classifiedSpans)
158+
{
159+
// Now translate everything to be relative to the excerpt
160+
var offset = 0 - excerptSpan.Start;
161+
var excerptText = razorDocumentText.GetSubText(excerptSpan);
162+
excerptSpan = new TextSpan(0, excerptSpan.Length);
163+
razorDocumentSpan = new TextSpan(razorDocumentSpan.Start + offset, razorDocumentSpan.Length);
164+
165+
for (var i = 0; i < classifiedSpans.Count; i++)
166+
{
167+
var classifiedSpan = classifiedSpans[i];
168+
var updated = new TextSpan(classifiedSpan.TextSpan.Start + offset, classifiedSpan.TextSpan.Length);
169+
Debug.Assert(excerptSpan.Contains(updated));
170+
171+
classifiedSpans[i] = new ClassifiedSpan(classifiedSpan.ClassificationType, updated);
172+
}
173+
174+
return excerptText;
175+
}
176+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
55

6-
namespace Microsoft.VisualStudio.Razor.DynamicFiles;
6+
namespace Microsoft.CodeAnalysis.Razor.DocumentExcerpt;
77

88
// We have IVT access to the Roslyn APIs for product code, but not for testing.
99
internal enum ExcerptModeInternal
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Collections.Immutable;
5-
using Microsoft.CodeAnalysis;
65
using Microsoft.CodeAnalysis.Classification;
76
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
87
using Microsoft.CodeAnalysis.Text;
98

10-
namespace Microsoft.VisualStudio.Razor.DynamicFiles;
9+
namespace Microsoft.CodeAnalysis.Razor.DocumentExcerpt;
1110

1211
// We have IVT access to the Roslyn APIs for product code, but not for testing.
1312
internal readonly struct ExcerptResultInternal
Lines changed: 1 addition & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System;
5-
using System.Collections.Immutable;
6-
using System.Diagnostics;
74
using System.Threading;
85
using System.Threading.Tasks;
96
using Microsoft.CodeAnalysis;
10-
using Microsoft.CodeAnalysis.Classification;
117
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
8+
using Microsoft.CodeAnalysis.Razor.DocumentExcerpt;
129
using Microsoft.CodeAnalysis.Text;
1310

1411
namespace Microsoft.VisualStudio.Razor.DynamicFiles;
@@ -32,57 +29,4 @@ internal abstract class DocumentExcerptService : IRazorDocumentExcerptServiceImp
3229
ExcerptModeInternal mode,
3330
RazorClassificationOptionsWrapper options,
3431
CancellationToken cancellationToken);
35-
36-
protected static TextSpan ChooseExcerptSpan(SourceText text, TextSpan span, ExcerptModeInternal mode)
37-
{
38-
var startLine = text.Lines.GetLineFromPosition(span.Start);
39-
var endLine = text.Lines.GetLineFromPosition(span.End);
40-
41-
if (mode == ExcerptModeInternal.Tooltip)
42-
{
43-
// Expand the range by 3 in each direction (if possible).
44-
var startIndex = Math.Max(startLine.LineNumber - 3, 0);
45-
startLine = text.Lines[startIndex];
46-
47-
var endIndex = Math.Min(endLine.LineNumber + 3, text.Lines.Count - 1);
48-
endLine = text.Lines[endIndex];
49-
return CreateTextSpan(startLine, endLine);
50-
}
51-
else
52-
{
53-
// Trim leading whitespace in a single line excerpt
54-
var excerptSpan = CreateTextSpan(startLine, endLine);
55-
var trimmedExcerptSpan = excerptSpan.TrimLeadingWhitespace(text);
56-
return trimmedExcerptSpan;
57-
}
58-
59-
static TextSpan CreateTextSpan(TextLine startLine, TextLine endLine)
60-
{
61-
return new TextSpan(startLine.Start, endLine.End - startLine.Start);
62-
}
63-
}
64-
65-
protected static SourceText GetTranslatedExcerptText(
66-
SourceText razorDocumentText,
67-
ref TextSpan razorDocumentSpan,
68-
ref TextSpan excerptSpan,
69-
ImmutableArray<ClassifiedSpan>.Builder classifiedSpans)
70-
{
71-
// Now translate everything to be relative to the excerpt
72-
var offset = 0 - excerptSpan.Start;
73-
var excerptText = razorDocumentText.GetSubText(excerptSpan);
74-
excerptSpan = new TextSpan(0, excerptSpan.Length);
75-
razorDocumentSpan = new TextSpan(razorDocumentSpan.Start + offset, razorDocumentSpan.Length);
76-
77-
for (var i = 0; i < classifiedSpans.Count; i++)
78-
{
79-
var classifiedSpan = classifiedSpans[i];
80-
var updated = new TextSpan(classifiedSpan.TextSpan.Start + offset, classifiedSpan.TextSpan.Length);
81-
Debug.Assert(excerptSpan.Contains(updated));
82-
83-
classifiedSpans[i] = new ClassifiedSpan(classifiedSpan.ClassificationType, updated);
84-
}
85-
86-
return excerptText;
87-
}
8832
}

0 commit comments

Comments
 (0)