[html] make mode services async

This commit is contained in:
Martin Aeschlimann 2020-06-25 11:48:16 +02:00
parent f591492df8
commit a180933669
16 changed files with 322 additions and 307 deletions

View file

@ -25,6 +25,11 @@ const serverConfig = withDefaults({
performance: {
hints: false
},
optimization: {
splitChunks: {
chunks: "async"
}
},
resolve: {
alias: {
'vscode-nls': path.resolve(__dirname, '../../../build/polyfills/vscode-nls.js')

View file

@ -18,7 +18,7 @@ import { format } from './modes/formatting';
import { pushAll } from './utils/arrays';
import { getDocumentContext } from './utils/documentContext';
import { URI } from 'vscode-uri';
import { formatError, runSafe, runSafeAsync } from './utils/runner';
import { formatError, runSafe } from './utils/runner';
import { getFoldingRanges } from './modes/htmlFolding';
import { fetchHTMLDataProviders } from './customData';
@ -261,11 +261,11 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
const settings = await getDocumentSettings(textDocument, () => modes.some(m => !!m.doValidation));
const latestTextDocument = documents.get(textDocument.uri);
if (latestTextDocument && latestTextDocument.version === version) { // check no new version has come in after in after the async op
modes.forEach(mode => {
for (const mode of modes) {
if (mode.doValidation && isValidationEnabled(mode.getId(), settings)) {
pushAll(diagnostics, mode.doValidation(latestTextDocument, settings));
pushAll(diagnostics, await mode.doValidation(latestTextDocument, settings));
}
});
}
connection.sendDiagnostics({ uri: latestTextDocument.uri, diagnostics });
}
}
@ -275,7 +275,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
}
connection.onCompletion(async (textDocumentPosition, token) => {
return runSafeAsync(async () => {
return runSafe(async () => {
const document = documents.get(textDocumentPosition.textDocument.uri);
if (!document) {
return null;
@ -303,7 +303,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
});
connection.onCompletionResolve((item, token) => {
return runSafe(() => {
return runSafe(async () => {
const data = item.data;
if (data && data.languageId && data.uri) {
const mode = languageModes.getMode(data.languageId);
@ -317,7 +317,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
});
connection.onHover((textDocumentPosition, token) => {
return runSafe(() => {
return runSafe(async () => {
const document = documents.get(textDocumentPosition.textDocument.uri);
if (document) {
const mode = languageModes.getModeAtPosition(document, textDocumentPosition.position);
@ -330,7 +330,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
});
connection.onDocumentHighlight((documentHighlightParams, token) => {
return runSafe(() => {
return runSafe(async () => {
const document = documents.get(documentHighlightParams.textDocument.uri);
if (document) {
const mode = languageModes.getModeAtPosition(document, documentHighlightParams.position);
@ -343,7 +343,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
});
connection.onDefinition((definitionParams, token) => {
return runSafe(() => {
return runSafe(async () => {
const document = documents.get(definitionParams.textDocument.uri);
if (document) {
const mode = languageModes.getModeAtPosition(document, definitionParams.position);
@ -356,7 +356,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
});
connection.onReferences((referenceParams, token) => {
return runSafe(() => {
return runSafe(async () => {
const document = documents.get(referenceParams.textDocument.uri);
if (document) {
const mode = languageModes.getModeAtPosition(document, referenceParams.position);
@ -369,7 +369,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
});
connection.onSignatureHelp((signatureHelpParms, token) => {
return runSafe(() => {
return runSafe(async () => {
const document = documents.get(signatureHelpParms.textDocument.uri);
if (document) {
const mode = languageModes.getModeAtPosition(document, signatureHelpParms.position);
@ -382,7 +382,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
});
connection.onDocumentRangeFormatting(async (formatParams, token) => {
return runSafeAsync(async () => {
return runSafe(async () => {
const document = documents.get(formatParams.textDocument.uri);
if (document) {
let settings = await getDocumentSettings(document, () => true);
@ -399,53 +399,53 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
});
connection.onDocumentLinks((documentLinkParam, token) => {
return runSafe(() => {
return runSafe(async () => {
const document = documents.get(documentLinkParam.textDocument.uri);
const links: DocumentLink[] = [];
if (document) {
const documentContext = getDocumentContext(document.uri, workspaceFolders);
languageModes.getAllModesInDocument(document).forEach(m => {
for (const m of languageModes.getAllModesInDocument(document)) {
if (m.findDocumentLinks) {
pushAll(links, m.findDocumentLinks(document, documentContext));
pushAll(links, await m.findDocumentLinks(document, documentContext));
}
});
}
}
return links;
}, [], `Error while document links for ${documentLinkParam.textDocument.uri}`, token);
});
connection.onDocumentSymbol((documentSymbolParms, token) => {
return runSafe(() => {
return runSafe(async () => {
const document = documents.get(documentSymbolParms.textDocument.uri);
const symbols: SymbolInformation[] = [];
if (document) {
languageModes.getAllModesInDocument(document).forEach(m => {
for (const m of languageModes.getAllModesInDocument(document)) {
if (m.findDocumentSymbols) {
pushAll(symbols, m.findDocumentSymbols(document));
pushAll(symbols, await m.findDocumentSymbols(document));
}
});
}
}
return symbols;
}, [], `Error while computing document symbols for ${documentSymbolParms.textDocument.uri}`, token);
});
connection.onRequest(DocumentColorRequest.type, (params, token) => {
return runSafe(() => {
return runSafe(async () => {
const infos: ColorInformation[] = [];
const document = documents.get(params.textDocument.uri);
if (document) {
languageModes.getAllModesInDocument(document).forEach(m => {
for (const m of languageModes.getAllModesInDocument(document)) {
if (m.findDocumentColors) {
pushAll(infos, m.findDocumentColors(document));
pushAll(infos, await m.findDocumentColors(document));
}
});
}
}
return infos;
}, [], `Error while computing document colors for ${params.textDocument.uri}`, token);
});
connection.onRequest(ColorPresentationRequest.type, (params, token) => {
return runSafe(() => {
return runSafe(async () => {
const document = documents.get(params.textDocument.uri);
if (document) {
const mode = languageModes.getModeAtPosition(document, params.range.start);
@ -458,7 +458,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
});
connection.onRequest(TagCloseRequest.type, (params, token) => {
return runSafe(() => {
return runSafe(async () => {
const document = documents.get(params.textDocument.uri);
if (document) {
const pos = params.position;
@ -474,7 +474,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
});
connection.onFoldingRanges((params, token) => {
return runSafe(() => {
return runSafe(async () => {
const document = documents.get(params.textDocument.uri);
if (document) {
return getFoldingRanges(languageModes, document, foldingRangeLimit, token);
@ -484,7 +484,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
});
connection.onSelectionRanges((params, token) => {
return runSafe(() => {
return runSafe(async () => {
const document = documents.get(params.textDocument.uri);
if (document) {
return getSelectionRanges(languageModes, document, params.positions);
@ -494,7 +494,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
});
connection.onRenameRequest((params, token) => {
return runSafe(() => {
return runSafe(async () => {
const document = documents.get(params.textDocument.uri);
const position: Position = params.position;
@ -509,7 +509,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
});
connection.onRequest(OnTypeRenameRequest.type, (params, token) => {
return runSafe(() => {
return runSafe(async () => {
const document = documents.get(params.textDocument.uri);
if (document) {
const pos = params.position;
@ -533,7 +533,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
}
connection.onRequest(SemanticTokenRequest.type, (params, token) => {
return runSafe(() => {
return runSafe(async () => {
const document = documents.get(params.textDocument.uri);
if (document) {
return getSemanticTokenProvider().getSemanticTokens(document, params.ranges);
@ -543,7 +543,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
});
connection.onRequest(SemanticTokenLegendRequest.type, (_params, token) => {
return runSafe(() => {
return runSafe(async () => {
return getSemanticTokenProvider().legend;
}, null, `Error while computing semantic tokens legend`, token);
});

View file

@ -5,7 +5,7 @@
import { LanguageModelCache, getLanguageModelCache } from '../languageModelCache';
import { Stylesheet, LanguageService as CSSLanguageService } from 'vscode-css-languageservice';
import { FoldingRange, LanguageMode, Workspace, Color, TextDocument, Position, Range, CompletionList, DocumentContext } from './languageModes';
import { LanguageMode, Workspace, Color, TextDocument, Position, Range, CompletionList, DocumentContext } from './languageModes';
import { HTMLDocumentRegions, CSS_STYLE_RULE } from './embeddedSupport';
export function getCSSMode(cssLanguageService: CSSLanguageService, documentRegions: LanguageModelCache<HTMLDocumentRegions>, workspace: Workspace): LanguageMode {
@ -16,48 +16,48 @@ export function getCSSMode(cssLanguageService: CSSLanguageService, documentRegio
getId() {
return 'css';
},
doValidation(document: TextDocument, settings = workspace.settings) {
async doValidation(document: TextDocument, settings = workspace.settings) {
let embedded = embeddedCSSDocuments.get(document);
return cssLanguageService.doValidation(embedded, cssStylesheets.get(embedded), settings && settings.css);
},
doComplete(document: TextDocument, position: Position, documentContext: DocumentContext, _settings = workspace.settings) {
async doComplete(document: TextDocument, position: Position, documentContext: DocumentContext, _settings = workspace.settings) {
let embedded = embeddedCSSDocuments.get(document);
const stylesheet = cssStylesheets.get(embedded);
return cssLanguageService.doComplete2(embedded, position, stylesheet, documentContext) || CompletionList.create();
},
doHover(document: TextDocument, position: Position) {
async doHover(document: TextDocument, position: Position) {
let embedded = embeddedCSSDocuments.get(document);
return cssLanguageService.doHover(embedded, position, cssStylesheets.get(embedded));
},
findDocumentHighlight(document: TextDocument, position: Position) {
async findDocumentHighlight(document: TextDocument, position: Position) {
let embedded = embeddedCSSDocuments.get(document);
return cssLanguageService.findDocumentHighlights(embedded, position, cssStylesheets.get(embedded));
},
findDocumentSymbols(document: TextDocument) {
async findDocumentSymbols(document: TextDocument) {
let embedded = embeddedCSSDocuments.get(document);
return cssLanguageService.findDocumentSymbols(embedded, cssStylesheets.get(embedded)).filter(s => s.name !== CSS_STYLE_RULE);
},
findDefinition(document: TextDocument, position: Position) {
async findDefinition(document: TextDocument, position: Position) {
let embedded = embeddedCSSDocuments.get(document);
return cssLanguageService.findDefinition(embedded, position, cssStylesheets.get(embedded));
},
findReferences(document: TextDocument, position: Position) {
async findReferences(document: TextDocument, position: Position) {
let embedded = embeddedCSSDocuments.get(document);
return cssLanguageService.findReferences(embedded, position, cssStylesheets.get(embedded));
},
findDocumentColors(document: TextDocument) {
async findDocumentColors(document: TextDocument) {
let embedded = embeddedCSSDocuments.get(document);
return cssLanguageService.findDocumentColors(embedded, cssStylesheets.get(embedded));
},
getColorPresentations(document: TextDocument, color: Color, range: Range) {
async getColorPresentations(document: TextDocument, color: Color, range: Range) {
let embedded = embeddedCSSDocuments.get(document);
return cssLanguageService.getColorPresentations(embedded, cssStylesheets.get(embedded), color, range);
},
getFoldingRanges(document: TextDocument): FoldingRange[] {
async getFoldingRanges(document: TextDocument) {
let embedded = embeddedCSSDocuments.get(document);
return cssLanguageService.getFoldingRanges(embedded, {});
},
getSelectionRange(document: TextDocument, position: Position) {
async getSelectionRange(document: TextDocument, position: Position) {
let embedded = embeddedCSSDocuments.get(document);
return cssLanguageService.getSelectionRanges(embedded, [position], cssStylesheets.get(embedded))[0];
},

View file

@ -7,7 +7,7 @@ import { LanguageModes, Settings, LanguageModeRange, TextDocument, Range, TextEd
import { pushAll } from '../utils/arrays';
import { isEOL } from '../utils/strings';
export function format(languageModes: LanguageModes, document: TextDocument, formatRange: Range, formattingOptions: FormattingOptions, settings: Settings | undefined, enabledModes: { [mode: string]: boolean }) {
export async function format(languageModes: LanguageModes, document: TextDocument, formatRange: Range, formattingOptions: FormattingOptions, settings: Settings | undefined, enabledModes: { [mode: string]: boolean }) {
let result: TextEdit[] = [];
let endPos = formatRange.end;
@ -39,7 +39,7 @@ export function format(languageModes: LanguageModes, document: TextDocument, for
while (i < allRanges.length && !isHTML(allRanges[i])) {
let range = allRanges[i];
if (!range.attributeValue && range.mode && range.mode.format) {
let edits = range.mode.format(document, Range.create(startPos, range.end), formattingOptions, settings);
let edits = await range.mode.format(document, Range.create(startPos, range.end), formattingOptions, settings);
pushAll(result, edits);
}
startPos = range.end;
@ -53,7 +53,7 @@ export function format(languageModes: LanguageModes, document: TextDocument, for
// perform a html format and apply changes to a new document
let htmlMode = languageModes.getMode('html')!;
let htmlEdits = htmlMode.format!(document, formatRange, formattingOptions, settings);
let htmlEdits = await htmlMode.format!(document, formatRange, formattingOptions, settings);
let htmlFormattedContent = TextDocument.applyEdits(document, htmlEdits);
let newDocument = TextDocument.create(document.uri + '.tmp', document.languageId, document.version, htmlFormattedContent);
try {
@ -67,7 +67,7 @@ export function format(languageModes: LanguageModes, document: TextDocument, for
for (let r of embeddedRanges) {
let mode = r.mode;
if (mode && mode.format && enabledModes[mode.getId()] && !r.attributeValue) {
let edits = mode.format(newDocument, r, formattingOptions, settings);
let edits = await mode.format(newDocument, r, formattingOptions, settings);
for (let edit of edits) {
embeddedEdits.push(edit);
}

View file

@ -6,21 +6,21 @@
import { TextDocument, FoldingRange, Position, Range, LanguageModes, LanguageMode } from './languageModes';
import { CancellationToken } from 'vscode-languageserver';
export function getFoldingRanges(languageModes: LanguageModes, document: TextDocument, maxRanges: number | undefined, _cancellationToken: CancellationToken | null): FoldingRange[] {
export async function getFoldingRanges(languageModes: LanguageModes, document: TextDocument, maxRanges: number | undefined, _cancellationToken: CancellationToken | null): Promise<FoldingRange[]> {
let htmlMode = languageModes.getMode('html');
let range = Range.create(Position.create(0, 0), Position.create(document.lineCount, 0));
let result: FoldingRange[] = [];
if (htmlMode && htmlMode.getFoldingRanges) {
result.push(...htmlMode.getFoldingRanges(document));
result.push(... await htmlMode.getFoldingRanges(document));
}
// cache folding ranges per mode
let rangesPerMode: { [mode: string]: FoldingRange[] } = Object.create(null);
let getRangesForMode = (mode: LanguageMode) => {
let getRangesForMode = async (mode: LanguageMode) => {
if (mode.getFoldingRanges) {
let ranges = rangesPerMode[mode.getId()];
if (!Array.isArray(ranges)) {
ranges = mode.getFoldingRanges(document) || [];
ranges = await mode.getFoldingRanges(document) || [];
rangesPerMode[mode.getId()] = ranges;
}
return ranges;
@ -32,7 +32,7 @@ export function getFoldingRanges(languageModes: LanguageModes, document: TextDoc
for (let modeRange of modeRanges) {
let mode = modeRange.mode;
if (mode && mode !== htmlMode && !modeRange.attributeValue) {
const ranges = getRangesForMode(mode);
const ranges = await getRangesForMode(mode);
result.push(...ranges.filter(r => r.startLine >= modeRange.start.line && r.endLine < modeRange.end.line));
}
}

View file

@ -17,7 +17,7 @@ export function getHTMLMode(htmlLanguageService: HTMLLanguageService, workspace:
getId() {
return 'html';
},
getSelectionRange(document: TextDocument, position: Position): SelectionRange {
async getSelectionRange(document: TextDocument, position: Position): Promise<SelectionRange> {
return htmlLanguageService.getSelectionRanges(document, [position])[0];
},
doComplete(document: TextDocument, position: Position, documentContext: DocumentContext, settings = workspace.settings) {
@ -31,19 +31,19 @@ export function getHTMLMode(htmlLanguageService: HTMLLanguageService, workspace:
let completionList = htmlLanguageService.doComplete2(document, position, htmlDocument, documentContext, options);
return completionList;
},
doHover(document: TextDocument, position: Position) {
async doHover(document: TextDocument, position: Position) {
return htmlLanguageService.doHover(document, position, htmlDocuments.get(document));
},
findDocumentHighlight(document: TextDocument, position: Position) {
async findDocumentHighlight(document: TextDocument, position: Position) {
return htmlLanguageService.findDocumentHighlights(document, position, htmlDocuments.get(document));
},
findDocumentLinks(document: TextDocument, documentContext: DocumentContext) {
async findDocumentLinks(document: TextDocument, documentContext: DocumentContext) {
return htmlLanguageService.findDocumentLinks(document, documentContext);
},
findDocumentSymbols(document: TextDocument) {
async findDocumentSymbols(document: TextDocument) {
return htmlLanguageService.findDocumentSymbols(document, htmlDocuments.get(document));
},
format(document: TextDocument, range: Range, formatParams: FormattingOptions, settings = workspace.settings) {
async format(document: TextDocument, range: Range, formatParams: FormattingOptions, settings = workspace.settings) {
let formatSettings: HTMLFormatConfiguration = settings && settings.html && settings.html.format;
if (formatSettings) {
formatSettings = merge(formatSettings, {});
@ -58,10 +58,10 @@ export function getHTMLMode(htmlLanguageService: HTMLLanguageService, workspace:
formatSettings = merge(formatParams, formatSettings);
return htmlLanguageService.format(document, range, formatSettings);
},
getFoldingRanges(document: TextDocument): FoldingRange[] {
async getFoldingRanges(document: TextDocument): Promise<FoldingRange[]> {
return htmlLanguageService.getFoldingRanges(document);
},
doAutoClose(document: TextDocument, position: Position) {
async doAutoClose(document: TextDocument, position: Position) {
let offset = document.offsetAt(position);
let text = document.getText();
if (offset > 0 && text.charAt(offset - 1).match(/[>\/]/g)) {
@ -69,18 +69,18 @@ export function getHTMLMode(htmlLanguageService: HTMLLanguageService, workspace:
}
return null;
},
doRename(document: TextDocument, position: Position, newName: string) {
async doRename(document: TextDocument, position: Position, newName: string) {
const htmlDocument = htmlDocuments.get(document);
return htmlLanguageService.doRename(document, position, newName, htmlDocument);
},
onDocumentRemoved(document: TextDocument) {
async onDocumentRemoved(document: TextDocument) {
htmlDocuments.onDocumentRemoved(document);
},
findMatchingTagPosition(document: TextDocument, position: Position) {
async findMatchingTagPosition(document: TextDocument, position: Position) {
const htmlDocument = htmlDocuments.get(document);
return htmlLanguageService.findMatchingTagPosition(document, position, htmlDocument);
},
doOnTypeRename(document: TextDocument, position: Position) {
async doOnTypeRename(document: TextDocument, position: Position) {
const htmlDocument = htmlDocuments.get(document);
return htmlLanguageService.findOnTypeRenameRanges(document, position, htmlDocument);
},

View file

@ -15,67 +15,83 @@ import { HTMLDocumentRegions } from './embeddedSupport';
import * as ts from 'typescript';
import { getSemanticTokens, getSemanticTokenLegend } from './javascriptSemanticTokens';
import { loadLibrary } from './javascriptLibs';
const JS_WORD_REGEX = /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g;
function getLanguageServiceHost(scriptKind: ts.ScriptKind) {
const compilerOptions: ts.CompilerOptions = { allowNonTsExtensions: true, allowJs: true, lib: ['lib.es6.d.ts'], target: ts.ScriptTarget.Latest, moduleResolution: ts.ModuleResolutionKind.Classic, experimentalDecorators: false };
let currentTextDocument = TextDocument.create('init', 'javascript', 1, '');
const jsLanguageService = import(/* webpackChunkName: "javascriptLibs" */ './javascriptLibs').then(libs => {
const host: ts.LanguageServiceHost = {
getCompilationSettings: () => compilerOptions,
getScriptFileNames: () => [currentTextDocument.uri, 'jquery'],
getScriptKind: (fileName) => {
if (fileName === currentTextDocument.uri) {
return scriptKind;
}
return fileName.substr(fileName.length - 2) === 'ts' ? ts.ScriptKind.TS : ts.ScriptKind.JS;
},
getScriptVersion: (fileName: string) => {
if (fileName === currentTextDocument.uri) {
return String(currentTextDocument.version);
}
return '1'; // default lib an jquery.d.ts are static
},
getScriptSnapshot: (fileName: string) => {
let text = '';
if (fileName === currentTextDocument.uri) {
text = currentTextDocument.getText();
} else {
text = libs.loadLibrary(fileName);
}
return {
getText: (start, end) => text.substring(start, end),
getLength: () => text.length,
getChangeRange: () => undefined
};
},
getCurrentDirectory: () => '',
getDefaultLibFileName: (_options: ts.CompilerOptions) => 'es6'
};
return ts.createLanguageService(host);
});
return {
async getLanguageService(jsDocument: TextDocument): Promise<ts.LanguageService> {
currentTextDocument = jsDocument;
return jsLanguageService;
},
getCompilationSettings() {
return compilerOptions;
},
dispose() {
if (jsLanguageService) {
jsLanguageService.then(s => s.dispose());
}
}
};
}
export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocumentRegions>, languageId: 'javascript' | 'typescript', workspace: Workspace): LanguageMode {
let jsDocuments = getLanguageModelCache<TextDocument>(10, 60, document => documentRegions.get(document).getEmbeddedDocument(languageId));
const workingFile = languageId === 'javascript' ? 'vscode://javascript/1.js' : 'vscode://javascript/2.ts'; // the same 'file' is used for all contents
const jQueryFile = 'jquery';
let compilerOptions: ts.CompilerOptions = { allowNonTsExtensions: true, allowJs: true, lib: ['lib.es6.d.ts'], target: ts.ScriptTarget.Latest, moduleResolution: ts.ModuleResolutionKind.Classic, experimentalDecorators: false };
let currentTextDocument: TextDocument;
let scriptFileVersion: number = 0;
function updateCurrentTextDocument(doc: TextDocument) {
if (!currentTextDocument || doc.uri !== currentTextDocument.uri || doc.version !== currentTextDocument.version) {
currentTextDocument = jsDocuments.get(doc);
scriptFileVersion++;
}
}
const host: ts.LanguageServiceHost = {
getCompilationSettings: () => compilerOptions,
getScriptFileNames: () => [workingFile, jQueryFile],
getScriptKind: (fileName) => fileName.substr(fileName.length - 2) === 'ts' ? ts.ScriptKind.TS : ts.ScriptKind.JS,
getScriptVersion: (fileName: string) => {
if (fileName === workingFile) {
return String(scriptFileVersion);
}
return '1'; // default lib an jquery.d.ts are static
},
getScriptSnapshot: (fileName: string) => {
let text = '';
if (fileName === workingFile) {
text = currentTextDocument.getText();
} else {
text = loadLibrary(fileName);
}
return {
getText: (start, end) => text.substring(start, end),
getLength: () => text.length,
getChangeRange: () => undefined
};
},
getCurrentDirectory: () => '',
getDefaultLibFileName: (_options: ts.CompilerOptions) => 'es6'
};
let jsLanguageService = ts.createLanguageService(host);
const host = getLanguageServiceHost(languageId === 'javascript' ? ts.ScriptKind.JS : ts.ScriptKind.TS);
let globalSettings: Settings = {};
return {
getId() {
return languageId;
},
doValidation(document: TextDocument, settings = workspace.settings): Diagnostic[] {
updateCurrentTextDocument(document);
async doValidation(document: TextDocument, settings = workspace.settings): Promise<Diagnostic[]> {
host.getCompilationSettings()['experimentalDecorators'] = settings && settings.javascript && settings.javascript.implicitProjectConfig.experimentalDecorators;
const syntaxDiagnostics: ts.Diagnostic[] = jsLanguageService.getSyntacticDiagnostics(workingFile);
const semanticDiagnostics = jsLanguageService.getSemanticDiagnostics(workingFile);
const jsDocument = jsDocuments.get(document);
const languageService = await host.getLanguageService(jsDocument);
const syntaxDiagnostics: ts.Diagnostic[] = languageService.getSyntacticDiagnostics(jsDocument.uri);
const semanticDiagnostics = languageService.getSemanticDiagnostics(jsDocument.uri);
return syntaxDiagnostics.concat(semanticDiagnostics).map((diag: ts.Diagnostic): Diagnostic => {
return {
range: convertRange(currentTextDocument, diag),
range: convertRange(jsDocument, diag),
severity: DiagnosticSeverity.Error,
source: languageId,
message: ts.flattenDiagnosticMessageText(diag.messageText, '\n')
@ -83,13 +99,14 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
});
},
async doComplete(document: TextDocument, position: Position, _documentContext: DocumentContext): Promise<CompletionList> {
updateCurrentTextDocument(document);
let offset = currentTextDocument.offsetAt(position);
let completions = jsLanguageService.getCompletionsAtPosition(workingFile, offset, { includeExternalModuleExports: false, includeInsertTextCompletions: false });
const jsDocument = jsDocuments.get(document);
const jsLanguageService = await host.getLanguageService(jsDocument);
let offset = jsDocument.offsetAt(position);
let completions = jsLanguageService.getCompletionsAtPosition(jsDocument.uri, offset, { includeExternalModuleExports: false, includeInsertTextCompletions: false });
if (!completions) {
return { isIncomplete: false, items: [] };
}
let replaceRange = convertRange(currentTextDocument, getWordAtText(currentTextDocument.getText(), offset, JS_WORD_REGEX));
let replaceRange = convertRange(jsDocument, getWordAtText(jsDocument.getText(), offset, JS_WORD_REGEX));
return {
isIncomplete: false,
items: completions.entries.map(entry => {
@ -109,9 +126,10 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
})
};
},
doResolve(document: TextDocument, item: CompletionItem): CompletionItem {
updateCurrentTextDocument(document);
let details = jsLanguageService.getCompletionEntryDetails(workingFile, item.data.offset, item.label, undefined, undefined, undefined);
async doResolve(document: TextDocument, item: CompletionItem): Promise<CompletionItem> {
const jsDocument = jsDocuments.get(document);
const jsLanguageService = await host.getLanguageService(jsDocument);
let details = jsLanguageService.getCompletionEntryDetails(jsDocument.uri, item.data.offset, item.label, undefined, undefined, undefined);
if (details) {
item.detail = ts.displayPartsToString(details.displayParts);
item.documentation = ts.displayPartsToString(details.documentation);
@ -119,21 +137,23 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
}
return item;
},
doHover(document: TextDocument, position: Position): Hover | null {
updateCurrentTextDocument(document);
let info = jsLanguageService.getQuickInfoAtPosition(workingFile, currentTextDocument.offsetAt(position));
async doHover(document: TextDocument, position: Position): Promise<Hover | null> {
const jsDocument = jsDocuments.get(document);
const jsLanguageService = await host.getLanguageService(jsDocument);
let info = jsLanguageService.getQuickInfoAtPosition(jsDocument.uri, jsDocument.offsetAt(position));
if (info) {
let contents = ts.displayPartsToString(info.displayParts);
return {
range: convertRange(currentTextDocument, info.textSpan),
range: convertRange(jsDocument, info.textSpan),
contents: MarkedString.fromPlainText(contents)
};
}
return null;
},
doSignatureHelp(document: TextDocument, position: Position): SignatureHelp | null {
updateCurrentTextDocument(document);
let signHelp = jsLanguageService.getSignatureHelpItems(workingFile, currentTextDocument.offsetAt(position), undefined);
async doSignatureHelp(document: TextDocument, position: Position): Promise<SignatureHelp | null> {
const jsDocument = jsDocuments.get(document);
const jsLanguageService = await host.getLanguageService(jsDocument);
let signHelp = jsLanguageService.getSignatureHelpItems(jsDocument.uri, jsDocument.offsetAt(position), undefined);
if (signHelp) {
let ret: SignatureHelp = {
activeSignature: signHelp.selectedItemIndex,
@ -168,23 +188,25 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
}
return null;
},
findDocumentHighlight(document: TextDocument, position: Position): DocumentHighlight[] {
updateCurrentTextDocument(document);
const highlights = jsLanguageService.getDocumentHighlights(workingFile, currentTextDocument.offsetAt(position), [workingFile]);
async findDocumentHighlight(document: TextDocument, position: Position): Promise<DocumentHighlight[]> {
const jsDocument = jsDocuments.get(document);
const jsLanguageService = await host.getLanguageService(jsDocument);
const highlights = jsLanguageService.getDocumentHighlights(jsDocument.uri, jsDocument.offsetAt(position), [jsDocument.uri]);
const out: DocumentHighlight[] = [];
for (const entry of highlights || []) {
for (const highlight of entry.highlightSpans) {
out.push({
range: convertRange(currentTextDocument, highlight.textSpan),
range: convertRange(jsDocument, highlight.textSpan),
kind: highlight.kind === 'writtenReference' ? DocumentHighlightKind.Write : DocumentHighlightKind.Text
});
}
}
return out;
},
findDocumentSymbols(document: TextDocument): SymbolInformation[] {
updateCurrentTextDocument(document);
let items = jsLanguageService.getNavigationBarItems(workingFile);
async findDocumentSymbols(document: TextDocument): Promise<SymbolInformation[]> {
const jsDocument = jsDocuments.get(document);
const jsLanguageService = await host.getLanguageService(jsDocument);
let items = jsLanguageService.getNavigationBarItems(jsDocument.uri);
if (items) {
let result: SymbolInformation[] = [];
let existing = Object.create(null);
@ -196,7 +218,7 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
kind: convertSymbolKind(item.kind),
location: {
uri: document.uri,
range: convertRange(currentTextDocument, item.spans[0])
range: convertRange(jsDocument, item.spans[0])
},
containerName: containerLabel
};
@ -218,63 +240,66 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
}
return [];
},
findDefinition(document: TextDocument, position: Position): Definition | null {
updateCurrentTextDocument(document);
let definition = jsLanguageService.getDefinitionAtPosition(workingFile, currentTextDocument.offsetAt(position));
async findDefinition(document: TextDocument, position: Position): Promise<Definition | null> {
const jsDocument = jsDocuments.get(document);
const jsLanguageService = await host.getLanguageService(jsDocument);
let definition = jsLanguageService.getDefinitionAtPosition(jsDocument.uri, jsDocument.offsetAt(position));
if (definition) {
return definition.filter(d => d.fileName === workingFile).map(d => {
return definition.filter(d => d.fileName === jsDocument.uri).map(d => {
return {
uri: document.uri,
range: convertRange(currentTextDocument, d.textSpan)
range: convertRange(jsDocument, d.textSpan)
};
});
}
return null;
},
findReferences(document: TextDocument, position: Position): Location[] {
updateCurrentTextDocument(document);
let references = jsLanguageService.getReferencesAtPosition(workingFile, currentTextDocument.offsetAt(position));
async findReferences(document: TextDocument, position: Position): Promise<Location[]> {
const jsDocument = jsDocuments.get(document);
const jsLanguageService = await host.getLanguageService(jsDocument);
let references = jsLanguageService.getReferencesAtPosition(jsDocument.uri, jsDocument.offsetAt(position));
if (references) {
return references.filter(d => d.fileName === workingFile).map(d => {
return references.filter(d => d.fileName === jsDocument.uri).map(d => {
return {
uri: document.uri,
range: convertRange(currentTextDocument, d.textSpan)
range: convertRange(jsDocument, d.textSpan)
};
});
}
return [];
},
getSelectionRange(document: TextDocument, position: Position): SelectionRange {
updateCurrentTextDocument(document);
async getSelectionRange(document: TextDocument, position: Position): Promise<SelectionRange> {
const jsDocument = jsDocuments.get(document);
const jsLanguageService = await host.getLanguageService(jsDocument);
function convertSelectionRange(selectionRange: ts.SelectionRange): SelectionRange {
const parent = selectionRange.parent ? convertSelectionRange(selectionRange.parent) : undefined;
return SelectionRange.create(convertRange(currentTextDocument, selectionRange.textSpan), parent);
return SelectionRange.create(convertRange(jsDocument, selectionRange.textSpan), parent);
}
const range = jsLanguageService.getSmartSelectionRange(workingFile, currentTextDocument.offsetAt(position));
const range = jsLanguageService.getSmartSelectionRange(jsDocument.uri, jsDocument.offsetAt(position));
return convertSelectionRange(range);
},
format(document: TextDocument, range: Range, formatParams: FormattingOptions, settings: Settings = globalSettings): TextEdit[] {
currentTextDocument = documentRegions.get(document).getEmbeddedDocument('javascript', true);
scriptFileVersion++;
async format(document: TextDocument, range: Range, formatParams: FormattingOptions, settings: Settings = globalSettings): Promise<TextEdit[]> {
const jsDocument = documentRegions.get(document).getEmbeddedDocument('javascript', true);
const jsLanguageService = await host.getLanguageService(jsDocument);
let formatterSettings = settings && settings.javascript && settings.javascript.format;
let initialIndentLevel = computeInitialIndent(document, range, formatParams);
let formatSettings = convertOptions(formatParams, formatterSettings, initialIndentLevel + 1);
let start = currentTextDocument.offsetAt(range.start);
let end = currentTextDocument.offsetAt(range.end);
let start = jsDocument.offsetAt(range.start);
let end = jsDocument.offsetAt(range.end);
let lastLineRange = null;
if (range.end.line > range.start.line && (range.end.character === 0 || isWhitespaceOnly(currentTextDocument.getText().substr(end - range.end.character, range.end.character)))) {
if (range.end.line > range.start.line && (range.end.character === 0 || isWhitespaceOnly(jsDocument.getText().substr(end - range.end.character, range.end.character)))) {
end -= range.end.character;
lastLineRange = Range.create(Position.create(range.end.line, 0), range.end);
}
let edits = jsLanguageService.getFormattingEditsForRange(workingFile, start, end, formatSettings);
let edits = jsLanguageService.getFormattingEditsForRange(jsDocument.uri, start, end, formatSettings);
if (edits) {
let result = [];
for (let edit of edits) {
if (edit.span.start >= start && edit.span.start + edit.span.length <= end) {
result.push({
range: convertRange(currentTextDocument, edit.span),
range: convertRange(jsDocument, edit.span),
newText: edit.newText
});
}
@ -289,12 +314,13 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
}
return [];
},
getFoldingRanges(document: TextDocument): FoldingRange[] {
updateCurrentTextDocument(document);
let spans = jsLanguageService.getOutliningSpans(workingFile);
async getFoldingRanges(document: TextDocument): Promise<FoldingRange[]> {
const jsDocument = jsDocuments.get(document);
const jsLanguageService = await host.getLanguageService(jsDocument);
let spans = jsLanguageService.getOutliningSpans(jsDocument.uri);
let ranges: FoldingRange[] = [];
for (let span of spans) {
let curr = convertRange(currentTextDocument, span.textSpan);
let curr = convertRange(jsDocument, span.textSpan);
let startLine = curr.start.line;
let endLine = curr.end.line;
if (startLine < endLine) {
@ -311,15 +337,16 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
onDocumentRemoved(document: TextDocument) {
jsDocuments.onDocumentRemoved(document);
},
getSemanticTokens(document: TextDocument): SemanticTokenData[] {
updateCurrentTextDocument(document);
return getSemanticTokens(jsLanguageService, currentTextDocument, workingFile);
async getSemanticTokens(document: TextDocument): Promise<SemanticTokenData[]> {
const jsDocument = jsDocuments.get(document);
const jsLanguageService = await host.getLanguageService(jsDocument);
return getSemanticTokens(jsLanguageService, jsDocument, jsDocument.uri);
},
getSemanticTokenLegend(): { types: string[], modifiers: string[] } {
return getSemanticTokenLegend();
},
dispose() {
jsLanguageService.dispose();
host.dispose();
jsDocuments.dispose();
}
};

View file

@ -41,27 +41,27 @@ export interface SemanticTokenData {
export interface LanguageMode {
getId(): string;
getSelectionRange?: (document: TextDocument, position: Position) => SelectionRange;
doValidation?: (document: TextDocument, settings?: Settings) => Diagnostic[];
getSelectionRange?: (document: TextDocument, position: Position) => Promise<SelectionRange>;
doValidation?: (document: TextDocument, settings?: Settings) => Promise<Diagnostic[]>;
doComplete?: (document: TextDocument, position: Position, documentContext: DocumentContext, settings?: Settings) => Promise<CompletionList>;
doResolve?: (document: TextDocument, item: CompletionItem) => CompletionItem;
doHover?: (document: TextDocument, position: Position) => Hover | null;
doSignatureHelp?: (document: TextDocument, position: Position) => SignatureHelp | null;
doRename?: (document: TextDocument, position: Position, newName: string) => WorkspaceEdit | null;
doOnTypeRename?: (document: TextDocument, position: Position) => Range[] | null;
findDocumentHighlight?: (document: TextDocument, position: Position) => DocumentHighlight[];
findDocumentSymbols?: (document: TextDocument) => SymbolInformation[];
findDocumentLinks?: (document: TextDocument, documentContext: DocumentContext) => DocumentLink[];
findDefinition?: (document: TextDocument, position: Position) => Definition | null;
findReferences?: (document: TextDocument, position: Position) => Location[];
format?: (document: TextDocument, range: Range, options: FormattingOptions, settings?: Settings) => TextEdit[];
findDocumentColors?: (document: TextDocument) => ColorInformation[];
getColorPresentations?: (document: TextDocument, color: Color, range: Range) => ColorPresentation[];
doAutoClose?: (document: TextDocument, position: Position) => string | null;
findMatchingTagPosition?: (document: TextDocument, position: Position) => Position | null;
getFoldingRanges?: (document: TextDocument) => FoldingRange[];
doResolve?: (document: TextDocument, item: CompletionItem) => Promise<CompletionItem>;
doHover?: (document: TextDocument, position: Position) => Promise<Hover | null>;
doSignatureHelp?: (document: TextDocument, position: Position) => Promise<SignatureHelp | null>;
doRename?: (document: TextDocument, position: Position, newName: string) => Promise<WorkspaceEdit | null>;
doOnTypeRename?: (document: TextDocument, position: Position) => Promise<Range[] | null>;
findDocumentHighlight?: (document: TextDocument, position: Position) => Promise<DocumentHighlight[]>;
findDocumentSymbols?: (document: TextDocument) => Promise<SymbolInformation[]>;
findDocumentLinks?: (document: TextDocument, documentContext: DocumentContext) => Promise<DocumentLink[]>;
findDefinition?: (document: TextDocument, position: Position) => Promise<Definition | null>;
findReferences?: (document: TextDocument, position: Position) => Promise<Location[]>;
format?: (document: TextDocument, range: Range, options: FormattingOptions, settings?: Settings) => Promise<TextEdit[]>;
findDocumentColors?: (document: TextDocument) => Promise<ColorInformation[]>;
getColorPresentations?: (document: TextDocument, color: Color, range: Range) => Promise<ColorPresentation[]>;
doAutoClose?: (document: TextDocument, position: Position) => Promise<string | null>;
findMatchingTagPosition?: (document: TextDocument, position: Position) => Promise<Position | null>;
getFoldingRanges?: (document: TextDocument) => Promise<FoldingRange[]>;
onDocumentRemoved(document: TextDocument): void;
getSemanticTokens?(document: TextDocument): SemanticTokenData[];
getSemanticTokens?(document: TextDocument): Promise<SemanticTokenData[]>;
getSemanticTokenLegend?(): { types: string[], modifiers: string[] };
dispose(): void;
}

View file

@ -6,13 +6,13 @@
import { LanguageModes, TextDocument, Position, Range, SelectionRange } from './languageModes';
import { insideRangeButNotSame } from '../utils/positions';
export function getSelectionRanges(languageModes: LanguageModes, document: TextDocument, positions: Position[]) {
export async function getSelectionRanges(languageModes: LanguageModes, document: TextDocument, positions: Position[]) {
const htmlMode = languageModes.getMode('html');
return positions.map(position => {
const htmlRange = htmlMode!.getSelectionRange!(document, position);
return Promise.all(positions.map(async position => {
const htmlRange = await htmlMode!.getSelectionRange!(document, position);
const mode = languageModes.getModeAtPosition(document, position);
if (mode && mode.getSelectionRange) {
let range = mode.getSelectionRange(document, position);
let range = await mode.getSelectionRange(document, position);
let top = range;
while (top.parent && insideRangeButNotSame(htmlRange.range, top.parent.range)) {
top = top.parent;
@ -21,6 +21,6 @@ export function getSelectionRanges(languageModes: LanguageModes, document: TextD
return range;
}
return htmlRange || SelectionRange.create(Range.create(position, position));
});
}));
}

View file

@ -13,14 +13,14 @@ interface LegendMapping {
export interface SemanticTokenProvider {
readonly legend: { types: string[]; modifiers: string[] };
getSemanticTokens(document: TextDocument, ranges?: Range[]): number[];
getSemanticTokens(document: TextDocument, ranges?: Range[]): Promise<number[]>;
}
export function newSemanticTokenProvider(languageModes: LanguageModes): SemanticTokenProvider {
// combined legend across modes
const legend = { types: [], modifiers: [] };
const legend: { types: string[], modifiers: string[] } = { types: [], modifiers: [] };
const legendMappings: { [modeId: string]: LegendMapping } = {};
for (let mode of languageModes.getAllModes()) {
@ -32,12 +32,12 @@ export function newSemanticTokenProvider(languageModes: LanguageModes): Semantic
return {
legend,
getSemanticTokens(document: TextDocument, ranges?: Range[]): number[] {
async getSemanticTokens(document: TextDocument, ranges?: Range[]): Promise<number[]> {
const allTokens: SemanticTokenData[] = [];
for (let mode of languageModes.getAllModesInDocument(document)) {
if (mode.getSemanticTokens) {
const mapping = legendMappings[mode.getId()];
const tokens = mode.getSemanticTokens(document);
const tokens = await mode.getSemanticTokens(document);
applyTypesMapping(tokens, mapping.types);
applyModifiersMapping(tokens, mapping.modifiers);
for (let token of tokens) {

View file

@ -86,6 +86,11 @@ suite('HTML Completion', () => {
{ label: 'getJSON', resultText: '<html><script>$.getJSON</script></html>' },
]
});
await testCompletionFor('<html><script>const x = { a: 1 };</script><script>x.|</script></html>', {
items: [
{ label: 'a', resultText: '<html><script>const x = { a: 1 };</script><script>x.a</script></html>' },
]
}, 'test://test/test2.html');
});
});

View file

@ -16,14 +16,14 @@ interface ExpectedIndentRange {
kind?: string;
}
function assertRanges(lines: string[], expected: ExpectedIndentRange[], message?: string, nRanges?: number): void {
async function assertRanges(lines: string[], expected: ExpectedIndentRange[], message?: string, nRanges?: number): Promise<void> {
const document = TextDocument.create('test://foo/bar.html', 'html', 1, lines.join('\n'));
const workspace = {
settings: {},
folders: [{ name: 'foo', uri: 'test://foo' }]
};
const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST, getNodeFSRequestService());
const actual = getFoldingRanges(languageModes, document, nRanges, null);
const actual = await getFoldingRanges(languageModes, document, nRanges, null);
let actualRanges = [];
for (let i = 0; i < actual.length; i++) {
@ -37,9 +37,9 @@ function r(startLine: number, endLine: number, kind?: string): ExpectedIndentRan
return { startLine, endLine, kind };
}
suite('HTML Folding', () => {
suite('HTML Folding', async () => {
test('Embedded JavaScript', () => {
test('Embedded JavaScript', async () => {
const input = [
/*0*/'<html>',
/*1*/'<head>',
@ -50,10 +50,10 @@ suite('HTML Folding', () => {
/*6*/'</head>',
/*7*/'</html>',
];
assertRanges(input, [r(0, 6), r(1, 5), r(2, 4), r(3, 4)]);
await await assertRanges(input, [r(0, 6), r(1, 5), r(2, 4), r(3, 4)]);
});
test('Embedded JavaScript - multiple areas', () => {
test('Embedded JavaScript - multiple areas', async () => {
const input = [
/* 0*/'<html>',
/* 1*/'<head>',
@ -71,10 +71,10 @@ suite('HTML Folding', () => {
/*13*/'</head>',
/*14*/'</html>',
];
assertRanges(input, [r(0, 13), r(1, 12), r(2, 6), r(3, 6), r(8, 11), r(9, 11)]);
await assertRanges(input, [r(0, 13), r(1, 12), r(2, 6), r(3, 6), r(8, 11), r(9, 11)]);
});
test('Embedded JavaScript - incomplete', () => {
test('Embedded JavaScript - incomplete', async () => {
const input = [
/* 0*/'<html>',
/* 1*/'<head>',
@ -87,10 +87,10 @@ suite('HTML Folding', () => {
/* 8*/'</head>',
/* 9*/'</html>',
];
assertRanges(input, [r(0, 8), r(1, 7), r(2, 3), r(5, 6)]);
await assertRanges(input, [r(0, 8), r(1, 7), r(2, 3), r(5, 6)]);
});
test('Embedded JavaScript - regions', () => {
test('Embedded JavaScript - regions', async () => {
const input = [
/* 0*/'<html>',
/* 1*/'<head>',
@ -104,10 +104,10 @@ suite('HTML Folding', () => {
/* 9*/'</head>',
/*10*/'</html>',
];
assertRanges(input, [r(0, 9), r(1, 8), r(2, 7), r(3, 7, 'region'), r(4, 6, 'region')]);
await assertRanges(input, [r(0, 9), r(1, 8), r(2, 7), r(3, 7, 'region'), r(4, 6, 'region')]);
});
test('Embedded CSS', () => {
test('Embedded CSS', async () => {
const input = [
/* 0*/'<html>',
/* 1*/'<head>',
@ -120,10 +120,10 @@ suite('HTML Folding', () => {
/* 8*/'</head>',
/* 9*/'</html>',
];
assertRanges(input, [r(0, 8), r(1, 7), r(2, 6), r(3, 5)]);
await assertRanges(input, [r(0, 8), r(1, 7), r(2, 6), r(3, 5)]);
});
test('Embedded CSS - multiple areas', () => {
test('Embedded CSS - multiple areas', async () => {
const input = [
/* 0*/'<html>',
/* 1*/'<head style="color:red">',
@ -141,10 +141,10 @@ suite('HTML Folding', () => {
/*13*/'</head>',
/*14*/'</html>',
];
assertRanges(input, [r(0, 13), r(1, 12), r(2, 6), r(3, 6, 'comment'), r(8, 11), r(9, 10)]);
await assertRanges(input, [r(0, 13), r(1, 12), r(2, 6), r(3, 6, 'comment'), r(8, 11), r(9, 10)]);
});
test('Embedded CSS - regions', () => {
test('Embedded CSS - regions', async () => {
const input = [
/* 0*/'<html>',
/* 1*/'<head>',
@ -158,11 +158,11 @@ suite('HTML Folding', () => {
/* 9*/'</head>',
/*10*/'</html>',
];
assertRanges(input, [r(0, 9), r(1, 8), r(2, 7), r(3, 7, 'region'), r(4, 6, 'region')]);
await assertRanges(input, [r(0, 9), r(1, 8), r(2, 7), r(3, 7, 'region'), r(4, 6, 'region')]);
});
// test('Embedded JavaScript - multi line comment', () => {
// test('Embedded JavaScript - multi line comment', async () => {
// const input = [
// /* 0*/'<html>',
// /* 1*/'<head>',
@ -174,10 +174,10 @@ suite('HTML Folding', () => {
// /* 7*/'</head>',
// /* 8*/'</html>',
// ];
// assertRanges(input, [r(0, 7), r(1, 6), r(2, 5), r(3, 5, 'comment')]);
// await assertRanges(input, [r(0, 7), r(1, 6), r(2, 5), r(3, 5, 'comment')]);
// });
test('Test limit', () => {
test('Test limit', async () => {
const input = [
/* 0*/'<div>',
/* 1*/' <span>',
@ -201,15 +201,15 @@ suite('HTML Folding', () => {
/*19*/' </span>',
/*20*/'</div>',
];
assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11), r(6, 7), r(9, 10), r(13, 14), r(16, 17)], 'no limit', undefined);
assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11), r(6, 7), r(9, 10), r(13, 14), r(16, 17)], 'limit 8', 8);
assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11), r(6, 7), r(13, 14), r(16, 17)], 'limit 7', 7);
assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11), r(13, 14), r(16, 17)], 'limit 6', 6);
assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11), r(13, 14)], 'limit 5', 5);
assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11)], 'limit 4', 4);
assertRanges(input, [r(0, 19), r(1, 18), r(2, 3)], 'limit 3', 3);
assertRanges(input, [r(0, 19), r(1, 18)], 'limit 2', 2);
assertRanges(input, [r(0, 19)], 'limit 1', 1);
await assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11), r(6, 7), r(9, 10), r(13, 14), r(16, 17)], 'no limit', undefined);
await assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11), r(6, 7), r(9, 10), r(13, 14), r(16, 17)], 'limit 8', 8);
await assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11), r(6, 7), r(13, 14), r(16, 17)], 'limit 7', 7);
await assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11), r(13, 14), r(16, 17)], 'limit 6', 6);
await assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11), r(13, 14)], 'limit 5', 5);
await assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11)], 'limit 4', 4);
await assertRanges(input, [r(0, 19), r(1, 18), r(2, 3)], 'limit 3', 3);
await assertRanges(input, [r(0, 19), r(1, 18)], 'limit 2', 2);
await assertRanges(input, [r(0, 19)], 'limit 1', 1);
});
});

View file

@ -14,7 +14,7 @@ import { getNodeFSRequestService } from '../node/nodeFs';
suite('HTML Embedded Formatting', () => {
function assertFormat(value: string, expected: string, options?: any, formatOptions?: FormattingOptions, message?: string): void {
async function assertFormat(value: string, expected: string, options?: any, formatOptions?: FormattingOptions, message?: string): Promise<void> {
let workspace = {
settings: options,
folders: [{ name: 'foo', uri: 'test://foo' }]
@ -38,53 +38,53 @@ suite('HTML Embedded Formatting', () => {
formatOptions = FormattingOptions.create(2, true);
}
let result = format(languageModes, document, range, formatOptions, undefined, { css: true, javascript: true });
let result = await format(languageModes, document, range, formatOptions, undefined, { css: true, javascript: true });
let actual = TextDocument.applyEdits(document, result);
assert.equal(actual, expected, message);
}
function assertFormatWithFixture(fixtureName: string, expectedPath: string, options?: any, formatOptions?: FormattingOptions): void {
async function assertFormatWithFixture(fixtureName: string, expectedPath: string, options?: any, formatOptions?: FormattingOptions): Promise<void> {
let input = fs.readFileSync(path.join(__dirname, '..', '..', 'src', 'test', 'fixtures', 'inputs', fixtureName)).toString().replace(/\r\n/mg, '\n');
let expected = fs.readFileSync(path.join(__dirname, '..', '..', 'src', 'test', 'fixtures', 'expected', expectedPath)).toString().replace(/\r\n/mg, '\n');
assertFormat(input, expected, options, formatOptions, expectedPath);
await assertFormat(input, expected, options, formatOptions, expectedPath);
}
test('HTML only', function (): any {
assertFormat('<html><body><p>Hello</p></body></html>', '<html>\n\n<body>\n <p>Hello</p>\n</body>\n\n</html>');
assertFormat('|<html><body><p>Hello</p></body></html>|', '<html>\n\n<body>\n <p>Hello</p>\n</body>\n\n</html>');
assertFormat('<html>|<body><p>Hello</p></body>|</html>', '<html><body>\n <p>Hello</p>\n</body></html>');
test('HTML only', async () => {
await assertFormat('<html><body><p>Hello</p></body></html>', '<html>\n\n<body>\n <p>Hello</p>\n</body>\n\n</html>');
await assertFormat('|<html><body><p>Hello</p></body></html>|', '<html>\n\n<body>\n <p>Hello</p>\n</body>\n\n</html>');
await assertFormat('<html>|<body><p>Hello</p></body>|</html>', '<html><body>\n <p>Hello</p>\n</body></html>');
});
test('HTML & Scripts', function (): any {
assertFormat('<html><head><script></script></head></html>', '<html>\n\n<head>\n <script></script>\n</head>\n\n</html>');
assertFormat('<html><head><script>var x=1;</script></head></html>', '<html>\n\n<head>\n <script>var x = 1;</script>\n</head>\n\n</html>');
assertFormat('<html><head><script>\nvar x=2;\n</script></head></html>', '<html>\n\n<head>\n <script>\n var x = 2;\n </script>\n</head>\n\n</html>');
assertFormat('<html><head>\n <script>\nvar x=3;\n</script></head></html>', '<html>\n\n<head>\n <script>\n var x = 3;\n </script>\n</head>\n\n</html>');
assertFormat('<html><head>\n <script>\nvar x=4;\nconsole.log("Hi");\n</script></head></html>', '<html>\n\n<head>\n <script>\n var x = 4;\n console.log("Hi");\n </script>\n</head>\n\n</html>');
assertFormat('<html><head>\n |<script>\nvar x=5;\n</script>|</head></html>', '<html><head>\n <script>\n var x = 5;\n </script></head></html>');
test('HTML & Scripts', async () => {
await assertFormat('<html><head><script></script></head></html>', '<html>\n\n<head>\n <script></script>\n</head>\n\n</html>');
await assertFormat('<html><head><script>var x=1;</script></head></html>', '<html>\n\n<head>\n <script>var x = 1;</script>\n</head>\n\n</html>');
await assertFormat('<html><head><script>\nvar x=2;\n</script></head></html>', '<html>\n\n<head>\n <script>\n var x = 2;\n </script>\n</head>\n\n</html>');
await assertFormat('<html><head>\n <script>\nvar x=3;\n</script></head></html>', '<html>\n\n<head>\n <script>\n var x = 3;\n </script>\n</head>\n\n</html>');
await assertFormat('<html><head>\n <script>\nvar x=4;\nconsole.log("Hi");\n</script></head></html>', '<html>\n\n<head>\n <script>\n var x = 4;\n console.log("Hi");\n </script>\n</head>\n\n</html>');
await assertFormat('<html><head>\n |<script>\nvar x=5;\n</script>|</head></html>', '<html><head>\n <script>\n var x = 5;\n </script></head></html>');
});
test('HTLM & Scripts - Fixtures', function () {
test('HTLM & Scripts - Fixtures', async () => {
assertFormatWithFixture('19813.html', '19813.html');
assertFormatWithFixture('19813.html', '19813-4spaces.html', undefined, FormattingOptions.create(4, true));
assertFormatWithFixture('19813.html', '19813-tab.html', undefined, FormattingOptions.create(1, false));
assertFormatWithFixture('21634.html', '21634.html');
});
test('Script end tag', function (): any {
assertFormat('<html>\n<head>\n <script>\nvar x = 0;\n</script></head></html>', '<html>\n\n<head>\n <script>\n var x = 0;\n </script>\n</head>\n\n</html>');
test('Script end tag', async () => {
await assertFormat('<html>\n<head>\n <script>\nvar x = 0;\n</script></head></html>', '<html>\n\n<head>\n <script>\n var x = 0;\n </script>\n</head>\n\n</html>');
});
test('HTML & Multiple Scripts', function (): any {
assertFormat('<html><head>\n<script>\nif(x){\nbar(); }\n</script><script>\nfunction(x){ }\n</script></head></html>', '<html>\n\n<head>\n <script>\n if (x) {\n bar();\n }\n </script>\n <script>\n function(x) {}\n </script>\n</head>\n\n</html>');
test('HTML & Multiple Scripts', async () => {
await assertFormat('<html><head>\n<script>\nif(x){\nbar(); }\n</script><script>\nfunction(x){ }\n</script></head></html>', '<html>\n\n<head>\n <script>\n if (x) {\n bar();\n }\n </script>\n <script>\n function(x) {}\n </script>\n</head>\n\n</html>');
});
test('HTML & Styles', function (): any {
assertFormat('<html><head>\n<style>\n.foo{display:none;}\n</style></head></html>', '<html>\n\n<head>\n <style>\n .foo {\n display: none;\n }\n </style>\n</head>\n\n</html>');
test('HTML & Styles', async () => {
await assertFormat('<html><head>\n<style>\n.foo{display:none;}\n</style></head></html>', '<html>\n\n<head>\n <style>\n .foo {\n display: none;\n }\n </style>\n</head>\n\n</html>');
});
test('EndWithNewline', function (): any {
test('EndWithNewline', async () => {
let options = {
html: {
format: {
@ -92,26 +92,26 @@ suite('HTML Embedded Formatting', () => {
}
}
};
assertFormat('<html><body><p>Hello</p></body></html>', '<html>\n\n<body>\n <p>Hello</p>\n</body>\n\n</html>\n', options);
assertFormat('<html>|<body><p>Hello</p></body>|</html>', '<html><body>\n <p>Hello</p>\n</body></html>', options);
assertFormat('<html><head><script>\nvar x=1;\n</script></head></html>', '<html>\n\n<head>\n <script>\n var x = 1;\n </script>\n</head>\n\n</html>\n', options);
await assertFormat('<html><body><p>Hello</p></body></html>', '<html>\n\n<body>\n <p>Hello</p>\n</body>\n\n</html>\n', options);
await assertFormat('<html>|<body><p>Hello</p></body>|</html>', '<html><body>\n <p>Hello</p>\n</body></html>', options);
await assertFormat('<html><head><script>\nvar x=1;\n</script></head></html>', '<html>\n\n<head>\n <script>\n var x = 1;\n </script>\n</head>\n\n</html>\n', options);
});
test('Inside script', function (): any {
assertFormat('<html><head>\n <script>\n|var x=6;|\n</script></head></html>', '<html><head>\n <script>\n var x = 6;\n</script></head></html>');
assertFormat('<html><head>\n <script>\n|var x=6;\nvar y= 9;|\n</script></head></html>', '<html><head>\n <script>\n var x = 6;\n var y = 9;\n</script></head></html>');
test('Inside script', async () => {
await assertFormat('<html><head>\n <script>\n|var x=6;|\n</script></head></html>', '<html><head>\n <script>\n var x = 6;\n</script></head></html>');
await assertFormat('<html><head>\n <script>\n|var x=6;\nvar y= 9;|\n</script></head></html>', '<html><head>\n <script>\n var x = 6;\n var y = 9;\n</script></head></html>');
});
test('Range after new line', function (): any {
assertFormat('<html><head>\n |<script>\nvar x=6;\n</script>\n|</head></html>', '<html><head>\n <script>\n var x = 6;\n </script>\n</head></html>');
test('Range after new line', async () => {
await assertFormat('<html><head>\n |<script>\nvar x=6;\n</script>\n|</head></html>', '<html><head>\n <script>\n var x = 6;\n </script>\n</head></html>');
});
test('bug 36574', function (): any {
assertFormat('<script src="/js/main.js"> </script>', '<script src="/js/main.js"> </script>');
test('bug 36574', async () => {
await assertFormat('<script src="/js/main.js"> </script>', '<script src="/js/main.js"> </script>');
});
test('bug 48049', function (): any {
assertFormat(
test('bug 48049', async () => {
await assertFormat(
[
'<html>',
'<head>',
@ -159,7 +159,7 @@ suite('HTML Embedded Formatting', () => {
].join('\n')
);
});
test('#58435', () => {
test('#58435', async () => {
let options = {
html: {
format: {
@ -190,7 +190,7 @@ suite('HTML Embedded Formatting', () => {
'</html>',
].join('\n');
assertFormat(content, expected, options);
await assertFormat(content, expected, options);
});
}); /*

View file

@ -9,7 +9,7 @@ import { getLanguageModes, ClientCapabilities, TextDocument, SelectionRange} fro
import { getSelectionRanges } from '../modes/selectionRanges';
import { getNodeFSRequestService } from '../node/nodeFs';
function assertRanges(content: string, expected: (number | string)[][]): void {
async function assertRanges(content: string, expected: (number | string)[][]): Promise<void> {
let message = `${content} gives selection range:\n`;
const offset = content.indexOf('|');
@ -22,7 +22,7 @@ function assertRanges(content: string, expected: (number | string)[][]): void {
const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST, getNodeFSRequestService());
const document = TextDocument.create('test://foo.html', 'html', 1, content);
const actualRanges = getSelectionRanges(languageModes, document, [document.positionAt(offset)]);
const actualRanges = await getSelectionRanges(languageModes, document, [document.positionAt(offset)]);
assert.equal(actualRanges.length, 1);
const offsetPairs: [number, string][] = [];
let curr: SelectionRange | undefined = actualRanges[0];
@ -36,8 +36,8 @@ function assertRanges(content: string, expected: (number | string)[][]): void {
}
suite('HTML SelectionRange', () => {
test('Embedded JavaScript', () => {
assertRanges('<html><head><script> function foo() { return ((1|+2)*6) }</script></head></html>', [
test('Embedded JavaScript', async () => {
await assertRanges('<html><head><script> function foo() { return ((1|+2)*6) }</script></head></html>', [
[48, '1'],
[48, '1+2'],
[47, '(1+2)'],
@ -52,8 +52,8 @@ suite('HTML SelectionRange', () => {
]);
});
test('Embedded CSS', () => {
assertRanges('<html><head><style>foo { display: |none; } </style></head></html>', [
test('Embedded CSS', async () => {
await assertRanges('<html><head><style>foo { display: |none; } </style></head></html>', [
[34, 'none'],
[25, 'display: none'],
[24, ' display: none; '],
@ -66,8 +66,8 @@ suite('HTML SelectionRange', () => {
]);
});
test('Embedded style', () => {
assertRanges('<div style="color: |red"></div>', [
test('Embedded style', async () => {
await assertRanges('<div style="color: |red"></div>', [
[19, 'red'],
[12, 'color: red'],
[11, '"color: red"'],

View file

@ -16,7 +16,7 @@ interface ExpectedToken {
tokenClassifiction: string;
}
function assertTokens(lines: string[], expected: ExpectedToken[], ranges?: Range[], message?: string): void {
async function assertTokens(lines: string[], expected: ExpectedToken[], ranges?: Range[], message?: string): Promise<void> {
const document = TextDocument.create('test://foo/bar.html', 'html', 1, lines.join('\n'));
const workspace = {
settings: {},
@ -26,7 +26,7 @@ function assertTokens(lines: string[], expected: ExpectedToken[], ranges?: Range
const semanticTokensProvider = newSemanticTokenProvider(languageModes);
const legend = semanticTokensProvider.legend;
const actual = semanticTokensProvider.getSemanticTokens(document, ranges);
const actual = await semanticTokensProvider.getSemanticTokens(document, ranges);
let actualRanges = [];
let lastLine = 0;
@ -49,7 +49,7 @@ function t(startLine: number, character: number, length: number, tokenClassifict
suite('HTML Semantic Tokens', () => {
test('Variables', () => {
test('Variables', async () => {
const input = [
/*0*/'<html>',
/*1*/'<head>',
@ -64,7 +64,7 @@ suite('HTML Semantic Tokens', () => {
/*10*/'</head>',
/*11*/'</html>',
];
assertTokens(input, [
await assertTokens(input, [
t(3, 6, 1, 'variable.declaration'), t(3, 13, 2, 'variable.declaration'), t(3, 19, 1, 'variable'),
t(5, 15, 1, 'variable.declaration.readonly'), t(5, 20, 2, 'variable'), t(5, 26, 1, 'variable'), t(5, 30, 1, 'variable.readonly'),
t(6, 11, 1, 'variable.declaration'),
@ -72,7 +72,7 @@ suite('HTML Semantic Tokens', () => {
]);
});
test('Functions', () => {
test('Functions', async () => {
const input = [
/*0*/'<html>',
/*1*/'<head>',
@ -85,14 +85,14 @@ suite('HTML Semantic Tokens', () => {
/*8*/'</head>',
/*9*/'</html>',
];
assertTokens(input, [
await assertTokens(input, [
t(3, 11, 3, 'function.declaration'), t(3, 15, 2, 'parameter.declaration'),
t(4, 11, 3, 'function'), t(4, 15, 4, 'interface'), t(4, 20, 3, 'member'), t(4, 24, 2, 'parameter'),
t(6, 6, 6, 'variable'), t(6, 13, 8, 'property'), t(6, 24, 5, 'member'), t(6, 35, 7, 'member'), t(6, 43, 1, 'parameter.declaration'), t(6, 48, 3, 'function'), t(6, 52, 1, 'parameter')
]);
});
test('Members', () => {
test('Members', async () => {
const input = [
/*0*/'<html>',
/*1*/'<head>',
@ -111,7 +111,7 @@ suite('HTML Semantic Tokens', () => {
];
assertTokens(input, [
await assertTokens(input, [
t(3, 8, 1, 'class.declaration'),
t(4, 11, 1, 'property.declaration.static'),
t(5, 4, 1, 'property.declaration'),
@ -121,7 +121,7 @@ suite('HTML Semantic Tokens', () => {
]);
});
test('Interfaces', () => {
test('Interfaces', async () => {
const input = [
/*0*/'<html>',
/*1*/'<head>',
@ -133,14 +133,14 @@ suite('HTML Semantic Tokens', () => {
/*7*/'</head>',
/*8*/'</html>',
];
assertTokens(input, [
await assertTokens(input, [
t(3, 12, 8, 'interface.declaration'), t(3, 23, 1, 'property.declaration'), t(3, 34, 1, 'property.declaration'),
t(4, 8, 1, 'variable.declaration.readonly'), t(4, 30, 8, 'interface'),
t(5, 8, 3, 'variable.declaration.readonly'), t(5, 15, 1, 'parameter.declaration'), t(5, 18, 8, 'interface'), t(5, 31, 1, 'parameter'), t(5, 33, 1, 'property'), t(5, 37, 1, 'parameter'), t(5, 39, 1, 'property')
]);
});
test('Readonly', () => {
test('Readonly', async () => {
const input = [
/*0*/'<html>',
/*1*/'<head>',
@ -153,7 +153,7 @@ suite('HTML Semantic Tokens', () => {
/*8*/'</head>',
/*9*/'</html>',
];
assertTokens(input, [
await assertTokens(input, [
t(3, 8, 1, 'variable.declaration.readonly'),
t(4, 8, 1, 'class.declaration'), t(4, 28, 1, 'property.declaration.static.readonly'), t(4, 42, 3, 'property.declaration.static'), t(4, 47, 3, 'interface'),
t(5, 13, 1, 'enum.declaration'), t(5, 17, 1, 'property.declaration.readonly'), t(5, 24, 1, 'property.declaration.readonly'), t(5, 28, 1, 'property.readonly'),
@ -162,7 +162,7 @@ suite('HTML Semantic Tokens', () => {
});
test('Type aliases and type parameters', () => {
test('Type aliases and type parameters', async () => {
const input = [
/*0*/'<html>',
/*1*/'<head>',
@ -175,14 +175,14 @@ suite('HTML Semantic Tokens', () => {
/*8*/'</head>',
/*9*/'</html>',
];
assertTokens(input, [
await assertTokens(input, [
t(3, 7, 5, 'type.declaration'), t(3, 15, 3, 'interface') /* to investiagte */,
t(4, 11, 1, 'function.declaration'), t(4, 13, 1, 'typeParameter.declaration'), t(4, 23, 5, 'type'), t(4, 30, 1, 'parameter.declaration'), t(4, 33, 1, 'typeParameter'), t(4, 47, 1, 'typeParameter'),
t(5, 12, 1, 'typeParameter'), t(5, 29, 3, 'interface'), t(5, 41, 5, 'type'),
]);
});
test('TS and JS', () => {
test('TS and JS', async () => {
const input = [
/*0*/'<html>',
/*1*/'<head>',
@ -195,13 +195,13 @@ suite('HTML Semantic Tokens', () => {
/*8*/'</head>',
/*9*/'</html>',
];
assertTokens(input, [
await assertTokens(input, [
t(3, 11, 1, 'function.declaration'), t(3, 13, 1, 'typeParameter.declaration'), t(3, 16, 2, 'parameter.declaration'), t(3, 20, 1, 'typeParameter'), t(3, 24, 1, 'typeParameter'), t(3, 39, 2, 'parameter'),
t(6, 2, 6, 'variable'), t(6, 9, 5, 'member')
]);
});
test('Ranges', () => {
test('Ranges', async () => {
const input = [
/*0*/'<html>',
/*1*/'<head>',
@ -214,11 +214,11 @@ suite('HTML Semantic Tokens', () => {
/*8*/'</head>',
/*9*/'</html>',
];
assertTokens(input, [
await assertTokens(input, [
t(3, 2, 6, 'variable'), t(3, 9, 5, 'member')
], [Range.create(Position.create(2, 0), Position.create(4, 0))]);
assertTokens(input, [
await assertTokens(input, [
t(6, 2, 6, 'variable'),
], [Range.create(Position.create(6, 2), Position.create(6, 8))]);
});

View file

@ -17,7 +17,7 @@ export function formatError(message: string, err: any): string {
return message;
}
export function runSafeAsync<T>(func: () => Thenable<T>, errorVal: T, errorMessage: string, token: CancellationToken): Thenable<T | ResponseError<any>> {
export function runSafe<T>(func: () => Thenable<T>, errorVal: T, errorMessage: string, token: CancellationToken): Thenable<T | ResponseError<any>> {
return new Promise<T | ResponseError<any>>((resolve) => {
setImmediate(() => {
if (token.isCancellationRequested) {
@ -38,29 +38,7 @@ export function runSafeAsync<T>(func: () => Thenable<T>, errorVal: T, errorMessa
});
}
export function runSafe<T, E>(func: () => T, errorVal: T, errorMessage: string, token: CancellationToken): Thenable<T | ResponseError<E>> {
return new Promise<T | ResponseError<E>>((resolve) => {
setImmediate(() => {
if (token.isCancellationRequested) {
resolve(cancelValue());
} else {
try {
let result = func();
if (token.isCancellationRequested) {
resolve(cancelValue());
return;
} else {
resolve(result);
}
} catch (e) {
console.error(formatError(errorMessage, e));
resolve(errorVal);
}
}
});
});
}
function cancelValue<E>() {
return new ResponseError<E>(ErrorCodes.RequestCancelled, 'Request cancelled');