From 8efb9e68d036d9fb9fd5fd5d48171590bb0bb39c Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 22 Nov 2016 16:57:20 +0100 Subject: [PATCH] [html] refactor and simplify embeddedSupport --- extensions/html/server/src/htmlServerMain.ts | 6 +- extensions/html/server/src/modes/cssMode.ts | 21 +- .../html/server/src/modes/embeddedSupport.ts | 302 ++++++++---------- extensions/html/server/src/modes/htmlMode.ts | 6 +- .../html/server/src/modes/javascriptMode.ts | 11 +- .../html/server/src/modes/languageModes.ts | 68 ++-- .../html/server/src/test/embedded.test.ts | 13 +- .../html/server/src/test/formatting.test.ts | 2 +- 8 files changed, 197 insertions(+), 232 deletions(-) diff --git a/extensions/html/server/src/htmlServerMain.ts b/extensions/html/server/src/htmlServerMain.ts index 796b7b8ae78..5136b44b8e5 100644 --- a/extensions/html/server/src/htmlServerMain.ts +++ b/extensions/html/server/src/htmlServerMain.ts @@ -44,10 +44,10 @@ connection.onInitialize((params: InitializeParams): InitializeResult => { languageModes = getLanguageModes(initializationOptions ? initializationOptions.embeddedLanguages : { css: true, javascript: true }); documents.onDidClose(e => { - languageModes.getAllModes().forEach(m => m.onDocumentRemoved(e.document)); + languageModes.onDocumentRemoved(e.document); }); connection.onShutdown(() => { - languageModes.getAllModes().forEach(m => m.dispose()); + languageModes.dispose(); }); return { @@ -198,7 +198,7 @@ connection.onDocumentRangeFormatting(formatParams => { let result: TextEdit[] = []; ranges.forEach(r => { let mode = r.mode; - if (mode && mode.format) { + if (mode && mode.format && !r.attributeValue) { let edits = mode.format(document, r, formatParams.options); pushAll(result, edits); } diff --git a/extensions/html/server/src/modes/cssMode.ts b/extensions/html/server/src/modes/cssMode.ts index e23f63ab5bc..6e8d80a3351 100644 --- a/extensions/html/server/src/modes/cssMode.ts +++ b/extensions/html/server/src/modes/cssMode.ts @@ -5,16 +5,13 @@ 'use strict'; import { LanguageModelCache, getLanguageModelCache } from '../languageModelCache'; -import { LanguageService as HTMLLanguageService, HTMLDocument } from 'vscode-html-languageservice'; import { TextDocument, Position } from 'vscode-languageserver-types'; import { getCSSLanguageService, Stylesheet } from 'vscode-css-languageservice'; -import { getEmbeddedDocument } from './embeddedSupport'; import { LanguageMode } from './languageModes'; -export function getCSSMode(htmlLanguageService: HTMLLanguageService, htmlDocuments: LanguageModelCache): LanguageMode { +export function getCSSMode(embeddedCSSDocuments: LanguageModelCache): LanguageMode { let cssLanguageService = getCSSLanguageService(); - let cssStylesheets = getLanguageModelCache(10, 60, document => cssLanguageService.parseStylesheet(document)); - let getEmbeddedCSSDocument = (document: TextDocument) => getEmbeddedDocument(htmlLanguageService, document, htmlDocuments.get(document), 'css'); + let cssStylesheets = getLanguageModelCache(10, 60, document => cssStylesheets.get(document)); return { getId() { @@ -24,31 +21,31 @@ export function getCSSMode(htmlLanguageService: HTMLLanguageService, htmlDocumen cssLanguageService.configure(options && options.css); }, doValidation(document: TextDocument) { - let embedded = getEmbeddedCSSDocument(document); + let embedded = embeddedCSSDocuments.get(document); return cssLanguageService.doValidation(embedded, cssStylesheets.get(embedded)); }, doComplete(document: TextDocument, position: Position) { - let embedded = getEmbeddedCSSDocument(document); + let embedded = embeddedCSSDocuments.get(document); return cssLanguageService.doComplete(embedded, position, cssStylesheets.get(embedded)); }, doHover(document: TextDocument, position: Position) { - let embedded = getEmbeddedCSSDocument(document); + let embedded = embeddedCSSDocuments.get(document); return cssLanguageService.doHover(embedded, position, cssStylesheets.get(embedded)); }, findDocumentHighlight(document: TextDocument, position: Position) { - let embedded = getEmbeddedCSSDocument(document); + let embedded = embeddedCSSDocuments.get(document); return cssLanguageService.findDocumentHighlights(embedded, position, cssStylesheets.get(embedded)); }, findDefinition(document: TextDocument, position: Position) { - let embedded = getEmbeddedCSSDocument(document); + let embedded = embeddedCSSDocuments.get(document); return cssLanguageService.findDefinition(embedded, position, cssStylesheets.get(embedded)); }, findReferences(document: TextDocument, position: Position) { - let embedded = getEmbeddedCSSDocument(document); + let embedded = embeddedCSSDocuments.get(document); return cssLanguageService.findReferences(embedded, position, cssStylesheets.get(embedded)); }, findColorSymbols(document: TextDocument) { - let embedded = getEmbeddedCSSDocument(document); + let embedded = embeddedCSSDocuments.get(document); return cssLanguageService.findColorSymbols(embedded, cssStylesheets.get(embedded)); }, onDocumentRemoved(document: TextDocument) { diff --git a/extensions/html/server/src/modes/embeddedSupport.ts b/extensions/html/server/src/modes/embeddedSupport.ts index 94f2f52993d..102c1babac3 100644 --- a/extensions/html/server/src/modes/embeddedSupport.ts +++ b/extensions/html/server/src/modes/embeddedSupport.ts @@ -5,135 +5,119 @@ 'use strict'; -import { TextDocument, Position, HTMLDocument, Node, LanguageService, TokenType, Range, Scanner } from 'vscode-html-languageservice'; +import { TextDocument, Position, LanguageService, TokenType, Range } from 'vscode-html-languageservice'; export interface LanguageRange extends Range { languageId: string; + attributeValue?: boolean; } -interface EmbeddedContent { languageId: string; start: number; end: number; attributeValue?: boolean; }; +export interface HTMLDocumentRegions { + getEmbeddedDocument(languageId: string): TextDocument; + getLanguageRanges(range: Range): LanguageRange[]; + getLanguageAtPosition(position: Position): string; + getLanguagesInDocument(): string[]; +} -export function getLanguageAtPosition(languageService: LanguageService, document: TextDocument, htmlDocument: HTMLDocument, position: Position): string { - let offset = document.offsetAt(position); - let node = htmlDocument.findNodeAt(offset); - if (node) { - let embeddedContent = getEmbeddedContentForNode(languageService, document, node); - if (embeddedContent) { - for (let c of embeddedContent) { - if (c.start <= offset && offset <= c.end) { - return c.languageId; - } +interface EmbeddedRegion { languageId: string; start: number; end: number; attributeValue?: boolean; }; + +export function getDocumentRegions(languageService: LanguageService, document: TextDocument): HTMLDocumentRegions { + let regions = getEmbeddedRegions(languageService, document); + return { + getLanguageRanges: (range: Range) => getLanguageRanges(document, regions, range), + getEmbeddedDocument: (languageId: string) => getEmbeddedDocument(document, regions, languageId), + getLanguageAtPosition: (position: Position) => getLanguageAtPosition(document, regions, position), + getLanguagesInDocument: () => getLanguagesInDocument(document, regions) + }; +} + +function getLanguageRanges(document: TextDocument, regions: EmbeddedRegion[], range: Range): LanguageRange[] { + let result: LanguageRange[] = []; + let currentPos = range ? range.start : Position.create(0, 0); + let currentOffset = range ? document.offsetAt(range.start) : 0; + let endOffset = range ? document.offsetAt(range.end) : document.getText().length; + for (let region of regions) { + if (region.end > currentOffset && region.start < endOffset) { + let start = Math.max(region.start, currentOffset); + let startPos = document.positionAt(start); + if (currentOffset < region.start) { + result.push({ + start: currentPos, + end: startPos, + languageId: 'html' + }); } + let end = Math.min(region.end, endOffset); + let endPos = document.positionAt(end); + if (end > region.start) { + result.push({ + start: startPos, + end: endPos, + languageId: region.languageId, + attributeValue: region.attributeValue + }); + } + currentOffset = end; + currentPos = endPos; + } + } + if (currentOffset < endOffset) { + let endPos = range ? range.end : document.positionAt(endOffset); + result.push({ + start: currentPos, + end: endPos, + languageId: 'html' + }); + } + return result; +} + +function getLanguagesInDocument(document: TextDocument, regions: EmbeddedRegion[]): string[] { + let result = []; + for (let region of regions) { + if (result.indexOf(region.languageId) === -1) { + result.push(region.languageId); + if (result.length === 3) { + return result; + } + } + } + result.push('html'); + return result; +} + +function getLanguageAtPosition(document: TextDocument, regions: EmbeddedRegion[], position: Position): string { + let offset = document.offsetAt(position); + for (let region of regions) { + if (region.start <= offset) { + if (offset <= region.end) { + return region.languageId; + } + } else { + break; } } return 'html'; } -export function getLanguagesInContent(languageService: LanguageService, document: TextDocument, htmlDocument: HTMLDocument): string[] { - let embeddedLanguageIds = ['html']; - const maxEmbbeddedLanguages = 3; - function collectEmbeddedLanguages(node: Node): void { - if (embeddedLanguageIds.length < maxEmbbeddedLanguages) { - let embeddedContent = getEmbeddedContentForNode(languageService, document, node); - if (embeddedContent) { - for (let c of embeddedContent) { - if (!isWhitespace(document.getText(), c.start, c.end)) { - if (embeddedLanguageIds.lastIndexOf(c.languageId) === -1) { - embeddedLanguageIds.push(c.languageId); - if (embeddedLanguageIds.length === maxEmbbeddedLanguages) { - return; - } - } - } - } - } - node.children.forEach(collectEmbeddedLanguages); - } - } - - htmlDocument.roots.forEach(collectEmbeddedLanguages); - return embeddedLanguageIds; -} - -export function getLanguagesInRange(languageService: LanguageService, document: TextDocument, htmlDocument: HTMLDocument, range: Range): LanguageRange[] { - let ranges: LanguageRange[] = []; - let currentPos = range.start; - let currentOffset = document.offsetAt(currentPos); - let rangeEndOffset = document.offsetAt(range.end); - function collectEmbeddedNodes(node: Node): void { - if (node.start < rangeEndOffset && node.end > currentOffset) { - let embeddedContent = getEmbeddedContentForNode(languageService, document, node); - if (embeddedContent) { - for (let c of embeddedContent) { - if (c.start < rangeEndOffset) { - let startPos = document.positionAt(c.start); - if (currentOffset < c.start) { - ranges.push({ - start: currentPos, - end: startPos, - languageId: 'html' - }); - } - let end = Math.min(c.end, rangeEndOffset); - let endPos = document.positionAt(end); - if (end > c.start) { - ranges.push({ - start: startPos, - end: endPos, - languageId: c.languageId - }); - } - currentOffset = end; - currentPos = endPos; - } - } - } - } - node.children.forEach(collectEmbeddedNodes); - } - - htmlDocument.roots.forEach(collectEmbeddedNodes); - if (currentOffset < rangeEndOffset) { - ranges.push({ - start: currentPos, - end: range.end, - languageId: 'html' - }); - } - return ranges; -} - -export function getEmbeddedDocument(languageService: LanguageService, document: TextDocument, htmlDocument: HTMLDocument, languageId: string): TextDocument { - let contents: EmbeddedContent[] = []; - function collectEmbeddedNodes(node: Node): void { - let embeddedContent = getEmbeddedContentForNode(languageService, document, node); - if (embeddedContent) { - for (let c of embeddedContent) { - if (c.languageId === languageId) { - contents.push(c); - } - } - } - node.children.forEach(collectEmbeddedNodes); - } - - htmlDocument.roots.forEach(collectEmbeddedNodes); - +function getEmbeddedDocument(document: TextDocument, contents: EmbeddedRegion[], languageId: string): TextDocument { let currentPos = 0; let oldContent = document.getText(); let result = ''; let lastSuffix = ''; for (let c of contents) { - result = substituteWithWhitespace(result, currentPos, c.start, oldContent, lastSuffix, getPrefix(c)); - result += oldContent.substring(c.start, c.end); - currentPos = c.end; - lastSuffix = getSuffix(c); + if (c.languageId === languageId) { + result = substituteWithWhitespace(result, currentPos, c.start, oldContent, lastSuffix, getPrefix(c)); + result += oldContent.substring(c.start, c.end); + currentPos = c.end; + lastSuffix = getSuffix(c); + } } result = substituteWithWhitespace(result, currentPos, oldContent.length, oldContent, lastSuffix, ''); return TextDocument.create(document.uri, languageId, document.version, result); } -function getPrefix(c: EmbeddedContent) { +function getPrefix(c: EmbeddedRegion) { if (c.attributeValue) { switch (c.languageId) { case 'css': return 'x{'; @@ -141,7 +125,7 @@ function getPrefix(c: EmbeddedContent) { } return ''; } -function getSuffix(c: EmbeddedContent) { +function getSuffix(c: EmbeddedRegion) { if (c.attributeValue) { switch (c.languageId) { case 'css': return '}'; @@ -151,7 +135,6 @@ function getSuffix(c: EmbeddedContent) { return ''; } - function substituteWithWhitespace(result: string, start: number, end: number, oldContent: string, before: string, after: string) { let accumulatedWS = 0; result += before; @@ -181,74 +164,56 @@ function append(result: string, str: string, n: number): string { return result; } -function getEmbeddedContentForNode(languageService: LanguageService, document: TextDocument, node: Node): EmbeddedContent[] { - if (node.tag === 'style') { - let scanner = languageService.createScanner(document.getText().substring(node.start, node.end)); - let token = scanner.scan(); - while (token !== TokenType.EOS) { - if (token === TokenType.Styles) { - return [{ languageId: 'css', start: node.start + scanner.getTokenOffset(), end: node.start + scanner.getTokenEnd() }]; - } - token = scanner.scan(); - } - } else if (node.tag === 'script') { - let scanner = languageService.createScanner(document.getText().substring(node.start, node.end)); - let token = scanner.scan(); - let isTypeAttribute = false; - let languageId = 'javascript'; - while (token !== TokenType.EOS) { - if (token === TokenType.AttributeName) { - isTypeAttribute = scanner.getTokenText() === 'type'; - } else if (token === TokenType.AttributeValue) { - if (isTypeAttribute) { +function getEmbeddedRegions(languageService: LanguageService, document: TextDocument): EmbeddedRegion[] { + let regions: EmbeddedRegion[] = []; + let scanner = languageService.createScanner(document.getText()); + let lastTagName: string; + let lastAttributeName: string; + let languageIdFromType: string; + + let token = scanner.scan(); + while (token !== TokenType.EOS) { + switch (token) { + case TokenType.StartTag: + lastTagName = scanner.getTokenText(); + lastAttributeName = null; + languageIdFromType = 'javascript'; + break; + case TokenType.Styles: + regions.push({ languageId: 'css', start: scanner.getTokenOffset(), end: scanner.getTokenEnd() }); + break; + case TokenType.Script: + regions.push({ languageId: languageIdFromType, start: scanner.getTokenOffset(), end: scanner.getTokenEnd() }); + break; + case TokenType.AttributeName: + lastAttributeName = scanner.getTokenText(); + break; + case TokenType.AttributeValue: + if (lastAttributeName === 'type' && lastTagName.toLowerCase() === 'script') { if (/["'](text|application)\/(java|ecma)script["']/.test(scanner.getTokenText())) { - languageId = 'javascript'; + languageIdFromType = 'javascript'; } else { - languageId = void 0; + languageIdFromType = void 0; } - } - isTypeAttribute = false; - } else if (token === TokenType.Script) { - return [{ languageId, start: node.start + scanner.getTokenOffset(), end: node.start + scanner.getTokenEnd() }]; - } - token = scanner.scan(); - } - } else if (node.attributeNames) { - let scanner: Scanner; - let result; - for (let name of node.attributeNames) { - let languageId = getAttributeLanguage(name); - if (languageId) { - if (!scanner) { - scanner = languageService.createScanner(document.getText().substring(node.start, node.end)); - } - let token = scanner.scan(); - let lastAttribute; - while (token !== TokenType.EOS) { - if (token === TokenType.AttributeName) { - lastAttribute = scanner.getTokenText(); - } else if (token === TokenType.AttributeValue && lastAttribute === name) { - let start = scanner.getTokenOffset() + node.start; - let end = scanner.getTokenEnd() + node.start; + } else { + let attributelLanguageId = getAttributeLanguage(lastAttributeName); + if (attributelLanguageId) { + let start = scanner.getTokenOffset(); + let end = scanner.getTokenEnd(); let firstChar = document.getText()[start]; if (firstChar === '\'' || firstChar === '"') { start++; end--; } - if (!result) { - result = []; - } - result.push({ languageId, start, end, attributeValue: true }); - lastAttribute = null; - break; + regions.push({ languageId: attributelLanguageId, start, end, attributeValue: true }); } - token = scanner.scan(); } - } + lastAttributeName = null; + break; } - return result; + token = scanner.scan(); } - return void 0; + return regions; } function getAttributeLanguage(attributeName: string): string { @@ -257,11 +222,4 @@ function getAttributeLanguage(attributeName: string): string { return null; } return match[1] ? 'css' : 'javascript'; -} - -function isWhitespace(str: string, start: number, end: number): boolean { - if (start === end) { - return true; - } - return !!str.substring(start, end).match(/^\s*$/); } \ No newline at end of file diff --git a/extensions/html/server/src/modes/htmlMode.ts b/extensions/html/server/src/modes/htmlMode.ts index 6a32a8c3510..ecee841da41 100644 --- a/extensions/html/server/src/modes/htmlMode.ts +++ b/extensions/html/server/src/modes/htmlMode.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { LanguageModelCache } from '../languageModelCache'; +import { getLanguageModelCache } from '../languageModelCache'; import { LanguageService as HTMLLanguageService, HTMLDocument, DocumentContext, FormattingOptions } from 'vscode-html-languageservice'; import { TextDocument, Position, Range } from 'vscode-languageserver-types'; import { LanguageMode } from './languageModes'; -export function getHTMLMode(htmlLanguageService: HTMLLanguageService, htmlDocuments: LanguageModelCache): LanguageMode { +export function getHTMLMode(htmlLanguageService: HTMLLanguageService): LanguageMode { let settings: any = {}; - + let htmlDocuments = getLanguageModelCache(10, 60, document => htmlLanguageService.parseHTMLDocument(document)); return { getId() { return 'html'; diff --git a/extensions/html/server/src/modes/javascriptMode.ts b/extensions/html/server/src/modes/javascriptMode.ts index 79ecda2e8c7..e117bd1bb63 100644 --- a/extensions/html/server/src/modes/javascriptMode.ts +++ b/extensions/html/server/src/modes/javascriptMode.ts @@ -4,9 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { LanguageModelCache, getLanguageModelCache } from '../languageModelCache'; -import { LanguageService as HTMLLanguageService, HTMLDocument } from 'vscode-html-languageservice'; -import { getEmbeddedDocument } from './embeddedSupport'; +import { LanguageModelCache } from '../languageModelCache'; import { CompletionItem, Location, SignatureHelp, SignatureInformation, ParameterInformation, Definition, TextEdit, TextDocument, Diagnostic, DiagnosticSeverity, Range, CompletionItemKind, Hover, MarkedString, DocumentHighlight, DocumentHighlightKind, CompletionList, Position, FormattingOptions } from 'vscode-languageserver-types'; import { LanguageMode } from './languageModes'; import { getWordAtText } from '../utils/words'; @@ -22,7 +20,7 @@ const FILE_NAME = 'typescript://singlefile/1'; // the same 'file' is used for a const JS_WORD_REGEX = /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g; -export function getJavascriptMode(htmlLanguageService: HTMLLanguageService, htmlDocuments: LanguageModelCache): LanguageMode { +export function getJavascriptMode(jsDocuments: LanguageModelCache): LanguageMode { let compilerOptions = { allowNonTsExtensions: true, allowJs: true, target: ts.ScriptTarget.Latest }; let currentTextDocument: TextDocument; let host = { @@ -47,9 +45,6 @@ export function getJavascriptMode(htmlLanguageService: HTMLLanguageService, html }; let jsLanguageService = ts.createLanguageService(host); - let jsDocuments = getLanguageModelCache(10, 60, document => { - return getEmbeddedDocument(htmlLanguageService, document, htmlDocuments.get(document), 'javascript'); - }); let settings: any = {}; return { @@ -217,10 +212,8 @@ export function getJavascriptMode(htmlLanguageService: HTMLLanguageService, html return null; }, onDocumentRemoved(document: TextDocument) { - jsDocuments.onDocumentRemoved(document); }, dispose() { - jsDocuments.dispose(); jsLanguageService.dispose(); } }; diff --git a/extensions/html/server/src/modes/languageModes.ts b/extensions/html/server/src/modes/languageModes.ts index c048937034b..47c817dc299 100644 --- a/extensions/html/server/src/modes/languageModes.ts +++ b/extensions/html/server/src/modes/languageModes.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { HTMLDocument, getLanguageService as getHTMLLanguageService, DocumentContext } from 'vscode-html-languageservice'; +import { getLanguageService as getHTMLLanguageService, DocumentContext } from 'vscode-html-languageservice'; import { CompletionItem, Location, SignatureHelp, Definition, TextEdit, TextDocument, Diagnostic, DocumentLink, Range, Hover, DocumentHighlight, CompletionList, Position, FormattingOptions } from 'vscode-languageserver-types'; -import { getLanguageModelCache } from '../languageModelCache'; -import { getLanguageAtPosition, getLanguagesInContent, getLanguagesInRange } from './embeddedSupport'; +import { getLanguageModelCache, LanguageModelCache } from '../languageModelCache'; +import { getDocumentRegions, HTMLDocumentRegions } from './embeddedSupport'; import { getCSSMode } from './cssMode'; import { getJavascriptMode } from './javascriptMode'; import { getHTMLMode } from './htmlMode'; @@ -37,53 +37,59 @@ export interface LanguageMode { export interface LanguageModes { getModeAtPosition(document: TextDocument, position: Position): LanguageMode; getModesInRange(document: TextDocument, range: Range): LanguageModeRange[]; - getAllModesInDocument(document: TextDocument): LanguageMode[]; getAllModes(): LanguageMode[]; + getAllModesInDocument(document: TextDocument): LanguageMode[]; getMode(languageId: string): LanguageMode; + onDocumentRemoved(document: TextDocument): void; + dispose(): void; } export interface LanguageModeRange extends Range { mode: LanguageMode; + attributeValue?: boolean; } export function getLanguageModes(supportedLanguages: { [languageId: string]: boolean; }): LanguageModes { var htmlLanguageService = getHTMLLanguageService(); - let htmlDocuments = getLanguageModelCache(10, 60, document => htmlLanguageService.parseHTMLDocument(document)); + let documentRegions = getLanguageModelCache(10, 60, document => getDocumentRegions(htmlLanguageService, document)); - let modes = { - 'html': getHTMLMode(htmlLanguageService, htmlDocuments), - 'css': supportedLanguages['css'] && getCSSMode(htmlLanguageService, htmlDocuments), - 'javascript': supportedLanguages['javascript'] && getJavascriptMode(htmlLanguageService, htmlDocuments) - }; + let modelCaches: LanguageModelCache[] = []; + modelCaches.push(documentRegions); + + let modes = {}; + modes['html'] = getHTMLMode(htmlLanguageService); + if (supportedLanguages['css']) { + let embeddedCSSDocuments = getLanguageModelCache(10, 60, document => documentRegions.get(document).getEmbeddedDocument('css')); + modelCaches.push(embeddedCSSDocuments); + modes['css'] = getCSSMode(embeddedCSSDocuments); + } + if (supportedLanguages['javascript']) { + let embeddedJSDocuments = getLanguageModelCache(10, 60, document => documentRegions.get(document).getEmbeddedDocument('javascript')); + modelCaches.push(embeddedJSDocuments); + modes['javascript'] = getJavascriptMode(embeddedJSDocuments); + } return { getModeAtPosition(document: TextDocument, position: Position): LanguageMode { - let languageId = getLanguageAtPosition(htmlLanguageService, document, htmlDocuments.get(document), position); + let languageId = documentRegions.get(document).getLanguageAtPosition(position);; if (languageId) { return modes[languageId]; } return null; }, - getAllModesInDocument(document: TextDocument): LanguageMode[] { - let result = []; - let languageIds = getLanguagesInContent(htmlLanguageService, document, htmlDocuments.get(document)); - for (let languageId of languageIds) { - let mode = modes[languageId]; - if (mode) { - result.push(mode); - } - } - return result; - }, getModesInRange(document: TextDocument, range: Range): LanguageModeRange[] { - return getLanguagesInRange(htmlLanguageService, document, htmlDocuments.get(document), range).map(r => { + return documentRegions.get(document).getLanguageRanges(range).map(r => { return { start: r.start, end: r.end, - mode: modes[r.languageId] + mode: modes[r.languageId], + attributeValue: r.attributeValue }; }); }, + getAllModesInDocument(document: TextDocument): LanguageMode[] { + return documentRegions.get(document).getLanguagesInDocument().map(languageId => modes[languageId]); + }, getAllModes(): LanguageMode[] { let result = []; for (let languageId in modes) { @@ -96,6 +102,20 @@ export function getLanguageModes(supportedLanguages: { [languageId: string]: boo }, getMode(languageId: string): LanguageMode { return modes[languageId]; + }, + onDocumentRemoved(document: TextDocument) { + modelCaches.forEach(mc => mc.onDocumentRemoved(document)); + for (let mode in modes) { + modes[mode].onDocumentRemoved(document); + } + }, + dispose(): void { + modelCaches.forEach(mc => mc.dispose()); + modelCaches = []; + for (let mode in modes) { + modes[mode].dispose(); + } + modes = {}; } }; } \ No newline at end of file diff --git a/extensions/html/server/src/test/embedded.test.ts b/extensions/html/server/src/test/embedded.test.ts index 74faa7f53f0..31885ec733c 100644 --- a/extensions/html/server/src/test/embedded.test.ts +++ b/extensions/html/server/src/test/embedded.test.ts @@ -20,21 +20,18 @@ suite('HTML Embedded Support', () => { let document = TextDocument.create('test://test/test.html', 'html', 0, value); let position = document.positionAt(offset); - let ls = getLanguageService(); - let htmlDoc = ls.parseHTMLDocument(document); - let languageId = embeddedSupport.getLanguageAtPosition(htmlLanguageService, document, htmlDoc, position); + let docRegions = embeddedSupport.getDocumentRegions(htmlLanguageService, document); + let languageId = docRegions.getLanguageAtPosition(position); + assert.equal(languageId, expectedLanguageId); } function assertEmbeddedLanguageContent(value: string, languageId: string, expectedContent: string): void { - let document = TextDocument.create('test://test/test.html', 'html', 0, value); - let ls = getLanguageService(); - let htmlDoc = ls.parseHTMLDocument(document); - - let content = embeddedSupport.getEmbeddedDocument(ls, document, htmlDoc, languageId); + let docRegions = embeddedSupport.getDocumentRegions(htmlLanguageService, document); + let content = docRegions.getEmbeddedDocument(languageId); assert.equal(content.getText(), expectedContent); } diff --git a/extensions/html/server/src/test/formatting.test.ts b/extensions/html/server/src/test/formatting.test.ts index 1aaf6075848..08951426e0a 100644 --- a/extensions/html/server/src/test/formatting.test.ts +++ b/extensions/html/server/src/test/formatting.test.ts @@ -58,7 +58,7 @@ suite('HTML Embedded Formatting', () => { assertFormat('\n ', '\n\n\n \n\n\n'); assertFormat('\n ||', '\n '); - assertFormat('\n ', '\n '); + assertFormat('\n ', '\n '); }); test('HTML & Multiple Scripts', function (): any {