[html] Format embedded JavaScript

This commit is contained in:
Martin Aeschlimann
2016-11-18 16:56:06 +01:00
parent e51299d9cb
commit cbdddca5ed
8 changed files with 251 additions and 33 deletions

View File

@@ -5,7 +5,11 @@
'use strict';
import { TextDocument, Position, HTMLDocument, Node, LanguageService, TokenType } from 'vscode-html-languageservice';
import { TextDocument, Position, HTMLDocument, Node, LanguageService, TokenType, Range } from 'vscode-html-languageservice';
export interface LanguageRange extends Range {
languageId: string;
}
export function getLanguageAtPosition(languageService: LanguageService, document: TextDocument, htmlDocument: HTMLDocument, position: Position): string {
let offset = document.offsetAt(position);
@@ -33,6 +37,50 @@ export function getLanguagesInContent(languageService: LanguageService, document
return Object.keys(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 c = getEmbeddedContentForNode(languageService, document, node);
if (c && 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 = [];
function collectEmbeddedNodes(node: Node): void {

View File

@@ -54,7 +54,7 @@ export function getJavascriptMode(htmlLanguageService: HTMLLanguageService, html
return {
configure(options: any) {
settings = options && options.html;
settings = options && options.javascript;
},
doValidation(document: TextDocument): Diagnostic[] {
currentTextDocument = jsDocuments.get(document);
@@ -194,15 +194,22 @@ export function getJavascriptMode(htmlLanguageService: HTMLLanguageService, html
},
format(document: TextDocument, range: Range, formatParams: FormattingOptions): TextEdit[] {
currentTextDocument = jsDocuments.get(document);
let formatSettings = convertOptions(formatParams, settings && settings.format);
let initialIndentLevel = computeInitialIndent(document, range, formatParams) + 1;
let formatSettings = convertOptions(formatParams, settings && settings.format, initialIndentLevel);
let start = currentTextDocument.offsetAt(range.start);
let end = currentTextDocument.offsetAt(range.end);
let edits = jsLanguageService.getFormattingEditsForRange(FILE_NAME, start, end, formatSettings);
if (edits) {
return edits.map(e => ({
range: convertRange(currentTextDocument, e.span),
newText: e.newText
}));
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),
newText: edit.newText
});
}
}
return result;
}
return null;
},
@@ -255,23 +262,44 @@ function convertKind(kind: string): CompletionItemKind {
return CompletionItemKind.Property;
}
function convertOptions(options: FormattingOptions, formatSettings?: any): ts.FormatCodeOptions {
function convertOptions(options: FormattingOptions, formatSettings: any, initialIndentLevel: number): ts.FormatCodeOptions {
return {
ConvertTabsToSpaces: options.insertSpaces,
TabSize: options.tabSize,
IndentSize: options.tabSize,
IndentStyle: ts.IndentStyle.Smart,
NewLineCharacter: '\n',
BaseIndentSize: 1, //
InsertSpaceAfterCommaDelimiter: !formatSettings || formatSettings.insertSpaceAfterCommaDelimiter,
InsertSpaceAfterSemicolonInForStatements: !formatSettings || formatSettings.insertSpaceAfterSemicolonInForStatements,
InsertSpaceBeforeAndAfterBinaryOperators: !formatSettings || formatSettings.insertSpaceBeforeAndAfterBinaryOperators,
InsertSpaceAfterKeywordsInControlFlowStatements: !formatSettings || formatSettings.insertSpaceAfterKeywordsInControlFlowStatements,
InsertSpaceAfterFunctionKeywordForAnonymousFunctions: !formatSettings || formatSettings.insertSpaceAfterFunctionKeywordForAnonymousFunctions,
InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: formatSettings && formatSettings.insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis,
InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: formatSettings && formatSettings.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets,
InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: formatSettings && formatSettings.insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces,
PlaceOpenBraceOnNewLineForControlBlocks: formatSettings && formatSettings.placeOpenBraceOnNewLineForFunctions,
PlaceOpenBraceOnNewLineForFunctions: formatSettings && formatSettings.placeOpenBraceOnNewLineForControlBlocks
BaseIndentSize: options.tabSize * initialIndentLevel,
InsertSpaceAfterCommaDelimiter: Boolean(!formatSettings || formatSettings.insertSpaceAfterCommaDelimiter),
InsertSpaceAfterSemicolonInForStatements: Boolean(!formatSettings || formatSettings.insertSpaceAfterSemicolonInForStatements),
InsertSpaceBeforeAndAfterBinaryOperators: Boolean(!formatSettings || formatSettings.insertSpaceBeforeAndAfterBinaryOperators),
InsertSpaceAfterKeywordsInControlFlowStatements: Boolean(!formatSettings || formatSettings.insertSpaceAfterKeywordsInControlFlowStatements),
InsertSpaceAfterFunctionKeywordForAnonymousFunctions: Boolean(!formatSettings || formatSettings.insertSpaceAfterFunctionKeywordForAnonymousFunctions),
InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: Boolean(formatSettings && formatSettings.insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis),
InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: Boolean(formatSettings && formatSettings.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets),
InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: Boolean(formatSettings && formatSettings.insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces),
PlaceOpenBraceOnNewLineForControlBlocks: Boolean(formatSettings && formatSettings.placeOpenBraceOnNewLineForFunctions),
PlaceOpenBraceOnNewLineForFunctions: Boolean(formatSettings && formatSettings.placeOpenBraceOnNewLineForControlBlocks)
};
}
function computeInitialIndent(document: TextDocument, range: Range, options: FormattingOptions) {
let lineStart = document.offsetAt(Position.create(range.start.line, 0));
let content = document.getText();
let i = lineStart;
let nChars = 0;
let tabSize = options.tabSize || 4;
while (i < content.length) {
let ch = content.charAt(i);
if (ch === ' ') {
nChars++;
} else if (ch === '\t') {
nChars += tabSize;
} else {
break;
}
i++;
}
return Math.floor(nChars / tabSize);
}

View File

@@ -11,7 +11,7 @@ import {
} from 'vscode-languageserver-types';
import { getLanguageModelCache } from '../languageModelCache';
import { getLanguageAtPosition, getLanguagesInContent } from './embeddedSupport';
import { getLanguageAtPosition, getLanguagesInContent, getLanguagesInRange } from './embeddedSupport';
import { getCSSMode } from './cssMode';
import { getJavascriptMode } from './javascriptMode';
import { getHTMLMode } from './htmlMode';
@@ -35,11 +35,16 @@ export interface LanguageMode {
export interface LanguageModes {
getModeAtPosition(document: TextDocument, position: Position): LanguageMode;
getModesInRange(document: TextDocument, range: Range): LanguageModeRange[];
getAllModesInDocument(document: TextDocument): LanguageMode[];
getAllModes(): LanguageMode[];
getMode(languageId: string): LanguageMode;
}
export interface LanguageModeRange extends Range {
mode: LanguageMode;
}
export function getLanguageModes(supportedLanguages: { [languageId: string]: boolean; }): LanguageModes {
var htmlLanguageService = getHTMLLanguageService();
@@ -69,6 +74,15 @@ export function getLanguageModes(supportedLanguages: { [languageId: string]: boo
}
return result;
},
getModesInRange(document: TextDocument, range: Range): LanguageModeRange[] {
return getLanguagesInRange(htmlLanguageService, document, htmlDocuments.get(document), range).map(r => {
return {
start: r.start,
end: r.end,
mode: modes[r.languageId]
};
});
},
getAllModes(): LanguageMode[] {
let result = [];
for (let languageId in modes) {