2
2
// Licensed under the MIT license. See License.txt in the project root for license information.
3
3
4
4
using System ;
5
- using System . Collections . Generic ;
6
5
using System . Threading ;
7
6
using System . Threading . Tasks ;
8
- using Microsoft . AspNetCore . Razor . Language ;
9
- using Microsoft . AspNetCore . Razor . Language . Syntax ;
10
7
using Microsoft . AspNetCore . Razor . LanguageServer . EndpointContracts ;
11
- using Microsoft . AspNetCore . Razor . LanguageServer . Hosting ;
12
- using Microsoft . AspNetCore . Razor . PooledObjects ;
13
- using Microsoft . CodeAnalysis . Razor . DocumentMapping ;
14
- using Microsoft . CodeAnalysis . Razor . ProjectSystem ;
15
- using Microsoft . CodeAnalysis . Razor . Protocol ;
16
- using Microsoft . CodeAnalysis . Razor . Workspaces ;
8
+ using Microsoft . CodeAnalysis . Razor . SpellCheck ;
17
9
using Microsoft . VisualStudio . LanguageServer . Protocol ;
18
10
19
11
namespace Microsoft . AspNetCore . Razor . LanguageServer . SpellCheck ;
20
12
21
13
[ RazorLanguageServerEndpoint ( VSInternalMethods . TextDocumentSpellCheckableRangesName ) ]
22
- internal sealed class DocumentSpellCheckEndpoint : IRazorRequestHandler < VSInternalDocumentSpellCheckableParams , VSInternalSpellCheckableRangeReport [ ] ? > , ICapabilitiesProvider
14
+ internal sealed class DocumentSpellCheckEndpoint (
15
+ ISpellCheckService spellCheckService ) : IRazorRequestHandler < VSInternalDocumentSpellCheckableParams , VSInternalSpellCheckableRangeReport [ ] ? > , ICapabilitiesProvider
23
16
{
24
- private readonly IDocumentMappingService _documentMappingService ;
25
- private readonly LanguageServerFeatureOptions _languageServerFeatureOptions ;
26
- private readonly IClientConnection _clientConnection ;
27
-
28
- public DocumentSpellCheckEndpoint (
29
- IDocumentMappingService documentMappingService ,
30
- LanguageServerFeatureOptions languageServerFeatureOptions ,
31
- IClientConnection clientConnection )
32
- {
33
- _documentMappingService = documentMappingService ?? throw new ArgumentNullException ( nameof ( documentMappingService ) ) ;
34
- _languageServerFeatureOptions = languageServerFeatureOptions ?? throw new ArgumentNullException ( nameof ( languageServerFeatureOptions ) ) ;
35
- _clientConnection = clientConnection ?? throw new ArgumentNullException ( nameof ( clientConnection ) ) ;
36
- }
17
+ private readonly ISpellCheckService _spellCheckService = spellCheckService ;
37
18
38
19
public bool MutatesSolutionState => false ;
39
20
@@ -43,14 +24,7 @@ public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, V
43
24
}
44
25
45
26
public TextDocumentIdentifier GetTextDocumentIdentifier ( VSInternalDocumentSpellCheckableParams request )
46
- {
47
- if ( request . TextDocument is null )
48
- {
49
- throw new ArgumentNullException ( nameof ( request . TextDocument ) ) ;
50
- }
51
-
52
- return request . TextDocument ;
53
- }
27
+ => request . TextDocument ;
54
28
55
29
public async Task < VSInternalSpellCheckableRangeReport [ ] ? > HandleRequestAsync ( VSInternalDocumentSpellCheckableParams request , RazorRequestContext requestContext , CancellationToken cancellationToken )
56
30
{
@@ -60,150 +34,15 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(VSInternalDocumentSpellC
60
34
return null ;
61
35
}
62
36
63
- using var _ = ListPool < SpellCheckRange > . GetPooledObject ( out var ranges ) ;
37
+ var data = await _spellCheckService . GetSpellCheckRangeTriplesAsync ( documentContext , cancellationToken ) . ConfigureAwait ( false ) ;
64
38
65
- await AddRazorSpellCheckRangesAsync ( ranges , documentContext , cancellationToken ) . ConfigureAwait ( false ) ;
66
-
67
- if ( _languageServerFeatureOptions . SingleServerSupport )
68
- {
69
- await AddCSharpSpellCheckRangesAsync ( ranges , documentContext , cancellationToken ) . ConfigureAwait ( false ) ;
70
- }
71
-
72
- return new [ ]
73
- {
39
+ return
40
+ [
74
41
new VSInternalSpellCheckableRangeReport
75
42
{
76
- Ranges = ConvertSpellCheckRangesToIntTriples ( ranges ) ,
43
+ Ranges = data ,
77
44
ResultId = Guid . NewGuid ( ) . ToString ( )
78
45
}
79
- } ;
80
- }
81
-
82
- private static async Task AddRazorSpellCheckRangesAsync ( List < SpellCheckRange > ranges , DocumentContext documentContext , CancellationToken cancellationToken )
83
- {
84
- var tree = await documentContext . GetSyntaxTreeAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
85
-
86
- // We don't want to report spelling errors in script or style tags, so we avoid descending into them at all, which
87
- // means we don't need complicated logic, and it performs a bit better. We assume any C# in them will still be reported
88
- // by Roslyn.
89
- // In an ideal world we wouldn't need this logic at all, as we would defer to the Html LSP server to provide spell checking
90
- // but it doesn't currently support it. When that support is added, we can remove all of this but the RazorCommentBlockSyntax
91
- // handling.
92
- foreach ( var node in tree . Root . DescendantNodes ( n => n is not MarkupElementSyntax { StartTag . Name . Content : "script" or "style" } ) )
93
- {
94
- if ( node is RazorCommentBlockSyntax commentBlockSyntax )
95
- {
96
- ranges . Add ( new ( ( int ) VSInternalSpellCheckableRangeKind . Comment , commentBlockSyntax . Comment . SpanStart , commentBlockSyntax . Comment . Span . Length ) ) ;
97
- }
98
- else if ( node is MarkupTextLiteralSyntax textLiteralSyntax )
99
- {
100
- // Attribute names are text literals, but we don't want to spell check them because either C# will,
101
- // whether they're component attributes based on property names, or they come from tag helper attribute
102
- // parameters as strings, or they're Html attributes which are not necessarily expected to be real words.
103
- if ( node . Parent is MarkupTagHelperAttributeSyntax or
104
- MarkupAttributeBlockSyntax or
105
- MarkupMinimizedAttributeBlockSyntax or
106
- MarkupTagHelperDirectiveAttributeSyntax or
107
- MarkupMinimizedTagHelperAttributeSyntax or
108
- MarkupMinimizedTagHelperDirectiveAttributeSyntax or
109
- MarkupMiscAttributeContentSyntax )
110
- {
111
- continue ;
112
- }
113
-
114
- // Text literals appear everywhere in Razor to hold newlines and indentation, so its worth saving the tokens
115
- if ( textLiteralSyntax . ContainsOnlyWhitespace ( ) )
116
- {
117
- continue ;
118
- }
119
-
120
- if ( textLiteralSyntax . Span . Length == 0 )
121
- {
122
- continue ;
123
- }
124
-
125
- ranges . Add ( new ( ( int ) VSInternalSpellCheckableRangeKind . String , textLiteralSyntax . SpanStart , textLiteralSyntax . Span . Length ) ) ;
126
- }
127
- }
128
- }
129
-
130
- private async Task AddCSharpSpellCheckRangesAsync ( List < SpellCheckRange > ranges , DocumentContext documentContext , CancellationToken cancellationToken )
131
- {
132
- var delegatedParams = new DelegatedSpellCheckParams ( documentContext . GetTextDocumentIdentifierAndVersion ( ) ) ;
133
- var delegatedResponse = await _clientConnection . SendRequestAsync < DelegatedSpellCheckParams , VSInternalSpellCheckableRangeReport [ ] ? > (
134
- CustomMessageNames . RazorSpellCheckEndpoint ,
135
- delegatedParams ,
136
- cancellationToken ) . ConfigureAwait ( false ) ;
137
-
138
- if ( delegatedResponse is null )
139
- {
140
- return ;
141
- }
142
-
143
- var codeDocument = await documentContext . GetCodeDocumentAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
144
- var csharpDocument = codeDocument . GetCSharpDocument ( ) ;
145
-
146
- foreach ( var report in delegatedResponse )
147
- {
148
- if ( report . Ranges is not { } csharpRanges )
149
- {
150
- continue ;
151
- }
152
-
153
- // Since we get C# tokens that have relative starts, we need to convert them back to absolute indexes
154
- // so we can sort them with the Razor tokens later
155
- var absoluteCSharpStartIndex = 0 ;
156
- for ( var i = 0 ; i < csharpRanges . Length ; i += 3 )
157
- {
158
- var kind = csharpRanges [ i ] ;
159
- var start = csharpRanges [ i + 1 ] ;
160
- var length = csharpRanges [ i + 2 ] ;
161
-
162
- absoluteCSharpStartIndex += start ;
163
-
164
- // We need to map the start index to produce results, and we validate that we can map the end index so we don't have
165
- // squiggles that go from C# into Razor/Html.
166
- if ( _documentMappingService . TryMapToHostDocumentPosition ( csharpDocument , absoluteCSharpStartIndex , out var _1 , out var hostDocumentIndex ) &&
167
- _documentMappingService . TryMapToHostDocumentPosition ( csharpDocument , absoluteCSharpStartIndex + length , out var _2 , out var _3 ) )
168
- {
169
- ranges . Add ( new ( kind , hostDocumentIndex , length ) ) ;
170
- }
171
-
172
- absoluteCSharpStartIndex += length ;
173
- }
174
- }
175
- }
176
-
177
- private static int [ ] ConvertSpellCheckRangesToIntTriples ( List < SpellCheckRange > ranges )
178
- {
179
- // Important to sort first, or the client will just ignore anything we say
180
- ranges . Sort ( CompareSpellCheckRanges ) ;
181
-
182
- using var _ = ListPool < int > . GetPooledObject ( out var data ) ;
183
- data . SetCapacityIfLarger ( ranges . Count * 3 ) ;
184
-
185
- var lastAbsoluteEndIndex = 0 ;
186
- foreach ( var range in ranges )
187
- {
188
- if ( range . Length == 0 )
189
- {
190
- continue ;
191
- }
192
-
193
- data . Add ( range . Kind ) ;
194
- data . Add ( range . AbsoluteStartIndex - lastAbsoluteEndIndex ) ;
195
- data . Add ( range . Length ) ;
196
-
197
- lastAbsoluteEndIndex = range . AbsoluteStartIndex + range . Length ;
198
- }
199
-
200
- return data . ToArray ( ) ;
201
- }
202
-
203
- private record struct SpellCheckRange ( int Kind , int AbsoluteStartIndex , int Length ) ;
204
-
205
- private static int CompareSpellCheckRanges ( SpellCheckRange x , SpellCheckRange y )
206
- {
207
- return x . AbsoluteStartIndex . CompareTo ( y . AbsoluteStartIndex ) ;
46
+ ] ;
208
47
}
209
48
}
0 commit comments