|
3 | 3 | * Licensed under the MIT License. See License.txt in the project root for license information. |
4 | 4 | *--------------------------------------------------------------------------------------------*/ |
5 | 5 |
|
6 | | -import { TextDocument, Position, Range } from 'vscode-languageclient'; |
7 | 6 | import { LanguageService, TokenType } from 'vscode-html-languageservice'; |
8 | 7 |
|
9 | | -export interface LanguageRange extends Range { |
10 | | - languageId: string | undefined; |
11 | | - attributeValue?: boolean; |
12 | | -} |
13 | | - |
14 | | -export interface HTMLDocumentRegions { |
15 | | - getEmbeddedDocument(languageId: string, ignoreAttributeValues?: boolean): TextDocument; |
16 | | - getLanguageRanges(range: Range): LanguageRange[]; |
17 | | - getLanguageAtPosition(position: Position): string | undefined; |
18 | | - getLanguagesInDocument(): string[]; |
19 | | - getImportedScripts(): string[]; |
20 | | -} |
21 | | - |
22 | | -export const CSS_STYLE_RULE = '__'; |
23 | | - |
24 | 8 | interface EmbeddedRegion { |
25 | 9 | languageId: string | undefined; |
26 | 10 | start: number; |
@@ -143,269 +127,6 @@ export function getCSSVirtualContent( |
143 | 127 | return content; |
144 | 128 | } |
145 | 129 |
|
146 | | - |
147 | | -export function getDocumentRegions( |
148 | | - languageService: LanguageService, |
149 | | - document: TextDocument |
150 | | -): HTMLDocumentRegions { |
151 | | - const regions: EmbeddedRegion[] = []; |
152 | | - const scanner = languageService.createScanner(document.getText()); |
153 | | - let lastTagName = ''; |
154 | | - let lastAttributeName: string | null = null; |
155 | | - let languageIdFromType: string | undefined = undefined; |
156 | | - const importedScripts: string[] = []; |
157 | | - |
158 | | - let token = scanner.scan(); |
159 | | - while (token !== TokenType.EOS) { |
160 | | - switch (token) { |
161 | | - case TokenType.StartTag: |
162 | | - lastTagName = scanner.getTokenText(); |
163 | | - lastAttributeName = null; |
164 | | - languageIdFromType = 'javascript'; |
165 | | - break; |
166 | | - case TokenType.Styles: |
167 | | - regions.push({ |
168 | | - languageId: 'css', |
169 | | - start: scanner.getTokenOffset(), |
170 | | - end: scanner.getTokenEnd() |
171 | | - }); |
172 | | - break; |
173 | | - case TokenType.Script: |
174 | | - regions.push({ |
175 | | - languageId: languageIdFromType, |
176 | | - start: scanner.getTokenOffset(), |
177 | | - end: scanner.getTokenEnd() |
178 | | - }); |
179 | | - break; |
180 | | - case TokenType.AttributeName: |
181 | | - lastAttributeName = scanner.getTokenText(); |
182 | | - break; |
183 | | - case TokenType.AttributeValue: |
184 | | - if (lastAttributeName === 'src' && lastTagName.toLowerCase() === 'script') { |
185 | | - let value = scanner.getTokenText(); |
186 | | - if (value[0] === "'" || value[0] === '"') { |
187 | | - value = value.substr(1, value.length - 1); |
188 | | - } |
189 | | - importedScripts.push(value); |
190 | | - } else if (lastAttributeName === 'type' && lastTagName.toLowerCase() === 'script') { |
191 | | - if ( |
192 | | - /["'](module|(text|application)\/(java|ecma)script|text\/babel)["']/.test( |
193 | | - scanner.getTokenText() |
194 | | - ) |
195 | | - ) { |
196 | | - languageIdFromType = 'javascript'; |
197 | | - } else if (/["']text\/typescript["']/.test(scanner.getTokenText())) { |
198 | | - languageIdFromType = 'typescript'; |
199 | | - } else { |
200 | | - languageIdFromType = undefined; |
201 | | - } |
202 | | - } else { |
203 | | - const attributeLanguageId = getAttributeLanguage(lastAttributeName!); |
204 | | - if (attributeLanguageId) { |
205 | | - let start = scanner.getTokenOffset(); |
206 | | - let end = scanner.getTokenEnd(); |
207 | | - const firstChar = document.getText()[start]; |
208 | | - if (firstChar === "'" || firstChar === '"') { |
209 | | - start++; |
210 | | - end--; |
211 | | - } |
212 | | - regions.push({ |
213 | | - languageId: attributeLanguageId, |
214 | | - start, |
215 | | - end, |
216 | | - attributeValue: true |
217 | | - }); |
218 | | - } |
219 | | - } |
220 | | - lastAttributeName = null; |
221 | | - break; |
222 | | - } |
223 | | - token = scanner.scan(); |
224 | | - } |
225 | | - return { |
226 | | - getLanguageRanges: (range: Range) => getLanguageRanges(document, regions, range), |
227 | | - getEmbeddedDocument: (languageId: string, ignoreAttributeValues: boolean) => |
228 | | - getEmbeddedDocument(document, regions, languageId, ignoreAttributeValues), |
229 | | - getLanguageAtPosition: (position: Position) => |
230 | | - getLanguageAtPosition(document, regions, position), |
231 | | - getLanguagesInDocument: () => getLanguagesInDocument(document, regions), |
232 | | - getImportedScripts: () => importedScripts |
233 | | - }; |
234 | | -} |
235 | | - |
236 | | -function getLanguageRanges( |
237 | | - document: TextDocument, |
238 | | - regions: EmbeddedRegion[], |
239 | | - range: Range |
240 | | -): LanguageRange[] { |
241 | | - const result: LanguageRange[] = []; |
242 | | - let currentPos = range ? range.start : Position.create(0, 0); |
243 | | - let currentOffset = range ? document.offsetAt(range.start) : 0; |
244 | | - const endOffset = range ? document.offsetAt(range.end) : document.getText().length; |
245 | | - for (const region of regions) { |
246 | | - if (region.end > currentOffset && region.start < endOffset) { |
247 | | - const start = Math.max(region.start, currentOffset); |
248 | | - const startPos = document.positionAt(start); |
249 | | - if (currentOffset < region.start) { |
250 | | - result.push({ |
251 | | - start: currentPos, |
252 | | - end: startPos, |
253 | | - languageId: 'html' |
254 | | - }); |
255 | | - } |
256 | | - const end = Math.min(region.end, endOffset); |
257 | | - const endPos = document.positionAt(end); |
258 | | - if (end > region.start) { |
259 | | - result.push({ |
260 | | - start: startPos, |
261 | | - end: endPos, |
262 | | - languageId: region.languageId, |
263 | | - attributeValue: region.attributeValue |
264 | | - }); |
265 | | - } |
266 | | - currentOffset = end; |
267 | | - currentPos = endPos; |
268 | | - } |
269 | | - } |
270 | | - if (currentOffset < endOffset) { |
271 | | - const endPos = range ? range.end : document.positionAt(endOffset); |
272 | | - result.push({ |
273 | | - start: currentPos, |
274 | | - end: endPos, |
275 | | - languageId: 'html' |
276 | | - }); |
277 | | - } |
278 | | - return result; |
279 | | -} |
280 | | - |
281 | | -function getLanguagesInDocument( |
282 | | - _document: TextDocument, |
283 | | - regions: EmbeddedRegion[] |
284 | | -): string[] { |
285 | | - const result = []; |
286 | | - for (const region of regions) { |
287 | | - if (region.languageId && result.indexOf(region.languageId) === -1) { |
288 | | - result.push(region.languageId); |
289 | | - if (result.length === 3) { |
290 | | - return result; |
291 | | - } |
292 | | - } |
293 | | - } |
294 | | - result.push('html'); |
295 | | - return result; |
296 | | -} |
297 | | - |
298 | | -function getLanguageAtPosition( |
299 | | - document: TextDocument, |
300 | | - regions: EmbeddedRegion[], |
301 | | - position: Position |
302 | | -): string | undefined { |
303 | | - const offset = document.offsetAt(position); |
304 | | - for (const region of regions) { |
305 | | - if (region.start <= offset) { |
306 | | - if (offset <= region.end) { |
307 | | - return region.languageId; |
308 | | - } |
309 | | - } else { |
310 | | - break; |
311 | | - } |
312 | | - } |
313 | | - return 'html'; |
314 | | -} |
315 | | - |
316 | | -function getEmbeddedDocument( |
317 | | - document: TextDocument, |
318 | | - contents: EmbeddedRegion[], |
319 | | - languageId: string, |
320 | | - ignoreAttributeValues: boolean |
321 | | -): TextDocument { |
322 | | - let currentPos = 0; |
323 | | - const oldContent = document.getText(); |
324 | | - let result = ''; |
325 | | - let lastSuffix = ''; |
326 | | - for (const c of contents) { |
327 | | - if (c.languageId === languageId && (!ignoreAttributeValues || !c.attributeValue)) { |
328 | | - result = substituteWithWhitespace( |
329 | | - result, |
330 | | - currentPos, |
331 | | - c.start, |
332 | | - oldContent, |
333 | | - lastSuffix, |
334 | | - getPrefix(c) |
335 | | - ); |
336 | | - result += oldContent.substring(c.start, c.end); |
337 | | - currentPos = c.end; |
338 | | - lastSuffix = getSuffix(c); |
339 | | - } |
340 | | - } |
341 | | - result = substituteWithWhitespace( |
342 | | - result, |
343 | | - currentPos, |
344 | | - oldContent.length, |
345 | | - oldContent, |
346 | | - lastSuffix, |
347 | | - '' |
348 | | - ); |
349 | | - return TextDocument.create(document.uri, languageId, document.version, result); |
350 | | -} |
351 | | - |
352 | | -function getPrefix(c: EmbeddedRegion) { |
353 | | - if (c.attributeValue) { |
354 | | - switch (c.languageId) { |
355 | | - case 'css': |
356 | | - return CSS_STYLE_RULE + '{'; |
357 | | - } |
358 | | - } |
359 | | - return ''; |
360 | | -} |
361 | | -function getSuffix(c: EmbeddedRegion) { |
362 | | - if (c.attributeValue) { |
363 | | - switch (c.languageId) { |
364 | | - case 'css': |
365 | | - return '}'; |
366 | | - case 'javascript': |
367 | | - return ';'; |
368 | | - } |
369 | | - } |
370 | | - return ''; |
371 | | -} |
372 | | - |
373 | | -function substituteWithWhitespace( |
374 | | - result: string, |
375 | | - start: number, |
376 | | - end: number, |
377 | | - oldContent: string, |
378 | | - before: string, |
379 | | - after: string |
380 | | -) { |
381 | | - let accumulatedWS = 0; |
382 | | - result += before; |
383 | | - for (let i = start + before.length; i < end; i++) { |
384 | | - const ch = oldContent[i]; |
385 | | - if (ch === '\n' || ch === '\r') { |
386 | | - // only write new lines, skip the whitespace |
387 | | - accumulatedWS = 0; |
388 | | - result += ch; |
389 | | - } else { |
390 | | - accumulatedWS++; |
391 | | - } |
392 | | - } |
393 | | - result = append(result, ' ', accumulatedWS - after.length); |
394 | | - result += after; |
395 | | - return result; |
396 | | -} |
397 | | - |
398 | | -function append(result: string, str: string, n: number): string { |
399 | | - while (n > 0) { |
400 | | - if (n & 1) { |
401 | | - result += str; |
402 | | - } |
403 | | - n >>= 1; |
404 | | - str += str; |
405 | | - } |
406 | | - return result; |
407 | | -} |
408 | | - |
409 | 130 | function getAttributeLanguage(attributeName: string): string | null { |
410 | 131 | const match = attributeName.match(/^(style)$|^(on\w+)$/i); |
411 | 132 | if (!match) { |
|
0 commit comments