mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-26 19:44:25 +01:00
Merge remote-tracking branch 'origin/master' into alex/tokenization
This commit is contained in:
@@ -141,6 +141,16 @@
|
||||
"default": true,
|
||||
"description": "%html.suggest.html5.desc%"
|
||||
},
|
||||
"html.validate.scripts": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "%html.validate.scripts%"
|
||||
},
|
||||
"html.validate.styles": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "%html.validate.styles%"
|
||||
},
|
||||
"html.trace.server": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
||||
@@ -10,5 +10,7 @@
|
||||
"html.format.extraLiners.desc": "List of tags, comma separated, that should have an extra newline before them. 'null' defaults to \"head, body, /html\".",
|
||||
"html.suggest.angular1.desc": "Configures if the built-in HTML language support suggests Angular V1 tags and properties.",
|
||||
"html.suggest.ionic.desc": "Configures if the built-in HTML language support suggests Ionic tags, properties and values.",
|
||||
"html.suggest.html5.desc":"Configures if the built-in HTML language support suggests HTML5 tags, properties and values."
|
||||
"html.suggest.html5.desc":"Configures if the built-in HTML language support suggests HTML5 tags, properties and values.",
|
||||
"html.validate.scripts": "Configures if the built-in HTML language support validates embedded scripts.",
|
||||
"html.validate.styles": "Configures if the built-in HTML language support validates embedded styles."
|
||||
}
|
||||
@@ -6,9 +6,11 @@
|
||||
|
||||
import { createConnection, IConnection, TextDocuments, InitializeParams, InitializeResult, RequestType } from 'vscode-languageserver';
|
||||
import { DocumentContext } from 'vscode-html-languageservice';
|
||||
import { TextDocument, Diagnostic, DocumentLink, Range, TextEdit, SymbolInformation } from 'vscode-languageserver-types';
|
||||
import { TextDocument, Diagnostic, DocumentLink, Range, SymbolInformation } from 'vscode-languageserver-types';
|
||||
import { getLanguageModes, LanguageModes } from './modes/languageModes';
|
||||
|
||||
import { format } from './modes/formatting';
|
||||
|
||||
import * as url from 'url';
|
||||
import * as path from 'path';
|
||||
import uri from 'vscode-uri';
|
||||
@@ -69,9 +71,18 @@ connection.onInitialize((params: InitializeParams): InitializeResult => {
|
||||
};
|
||||
});
|
||||
|
||||
let validation = {
|
||||
html: true,
|
||||
css: true,
|
||||
javascript: true
|
||||
};
|
||||
|
||||
// The settings have changed. Is send on server activation as well.
|
||||
connection.onDidChangeConfiguration((change) => {
|
||||
settings = change.settings;
|
||||
let validationSettings = settings && settings.html && settings.html.validate || {};
|
||||
validation.css = validationSettings.styles !== false;
|
||||
validation.javascript = validationSettings.scripts !== false;
|
||||
|
||||
languageModes.getAllModes().forEach(m => {
|
||||
if (m.configure) {
|
||||
@@ -115,7 +126,7 @@ function triggerValidation(textDocument: TextDocument): void {
|
||||
function validateTextDocument(textDocument: TextDocument): void {
|
||||
let diagnostics: Diagnostic[] = [];
|
||||
languageModes.getAllModesInDocument(textDocument).forEach(mode => {
|
||||
if (mode.doValidation) {
|
||||
if (mode.doValidation && validation[mode.getId()]) {
|
||||
pushAll(diagnostics, mode.doValidation(textDocument));
|
||||
}
|
||||
});
|
||||
@@ -201,18 +212,11 @@ connection.onSignatureHelp(signatureHelpParms => {
|
||||
|
||||
connection.onDocumentRangeFormatting(formatParams => {
|
||||
let document = documents.get(formatParams.textDocument.uri);
|
||||
let ranges = languageModes.getModesInRange(document, formatParams.range);
|
||||
let result: TextEdit[] = [];
|
||||
|
||||
let unformattedTags: string = settings && settings.html && settings.html.format && settings.html.format.unformatted || '';
|
||||
let enabledModes = { css: !unformattedTags.match(/\bstyle\b/), javascript: !unformattedTags.match(/\bscript\b/), html: true };
|
||||
ranges.forEach(r => {
|
||||
let mode = r.mode;
|
||||
if (mode && mode.format && enabledModes[mode.getId()] && !r.attributeValue) {
|
||||
let edits = mode.format(document, r, formatParams.options);
|
||||
pushAll(result, edits);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
let enabledModes = { css: !unformattedTags.match(/\bstyle\b/), javascript: !unformattedTags.match(/\bscript\b/) };
|
||||
|
||||
return format(languageModes, document, formatParams.range, formatParams.options, enabledModes);
|
||||
});
|
||||
|
||||
connection.onDocumentLinks(documentLinkParam => {
|
||||
|
||||
55
extensions/html/server/src/modes/formatting.ts
Normal file
55
extensions/html/server/src/modes/formatting.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { applyEdits } from '../utils/edits';
|
||||
import { TextDocument, Range, TextEdit, FormattingOptions } from 'vscode-languageserver-types';
|
||||
import { LanguageModes } from './languageModes';
|
||||
|
||||
export function format(languageModes: LanguageModes, document: TextDocument, formatRange: Range, formattingOptions: FormattingOptions, enabledModes: { [mode: string]: boolean }) {
|
||||
// run the html formatter on the full range and pass the result content to the embedded formatters.
|
||||
// from the final content create a single edit
|
||||
// advantages of this approach are
|
||||
// - correct indents in the html document
|
||||
// - correct initial indent for embedded formatters
|
||||
// - no worrying of overlapping edits
|
||||
|
||||
// perform a html format and apply changes to a new document
|
||||
let htmlMode = languageModes.getMode('html');
|
||||
let htmlEdits = htmlMode.format(document, formatRange, formattingOptions);
|
||||
let htmlFormattedContent = applyEdits(document, htmlEdits);
|
||||
let newDocument = TextDocument.create(document.uri + '.tmp', document.languageId, document.version, htmlFormattedContent);
|
||||
try {
|
||||
// run embedded formatters on html formatted content: - formatters see correct initial indent
|
||||
let afterFormatRangeLength = document.getText().length - document.offsetAt(formatRange.end); // length of unchanged content after replace range
|
||||
let newFormatRange = Range.create(formatRange.start, newDocument.positionAt(htmlFormattedContent.length - afterFormatRangeLength));
|
||||
let embeddedRanges = languageModes.getModesInRange(newDocument, newFormatRange);
|
||||
|
||||
let embeddedEdits: TextEdit[] = [];
|
||||
|
||||
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);
|
||||
for (let edit of edits) {
|
||||
embeddedEdits.push(edit);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (embeddedEdits.length === 0) {
|
||||
return htmlEdits;
|
||||
}
|
||||
|
||||
// apply all embedded format edits and create a single edit for all changes
|
||||
let resultContent = applyEdits(newDocument, embeddedEdits);
|
||||
let resultReplaceText = resultContent.substring(document.offsetAt(formatRange.start), resultContent.length - afterFormatRangeLength);
|
||||
|
||||
return [TextEdit.replace(formatRange, resultReplaceText)];
|
||||
} finally {
|
||||
languageModes.onDocumentRemoved(newDocument);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,7 +20,7 @@ export function getHTMLMode(htmlLanguageService: HTMLLanguageService): LanguageM
|
||||
settings = options && options.html;
|
||||
},
|
||||
doComplete(document: TextDocument, position: Position) {
|
||||
let options = settings && settings.html && settings.html.suggest;
|
||||
let options = settings && settings.suggest;
|
||||
return htmlLanguageService.doComplete(document, position, htmlDocuments.get(document), options);
|
||||
},
|
||||
doHover(document: TextDocument, position: Position) {
|
||||
|
||||
@@ -8,6 +8,8 @@ import * as assert from 'assert';
|
||||
import { getLanguageModes } from '../modes/languageModes';
|
||||
import { TextDocument, Range, TextEdit, FormattingOptions } from 'vscode-languageserver-types';
|
||||
|
||||
import { format } from '../modes/formatting';
|
||||
|
||||
suite('HTML Embedded Formatting', () => {
|
||||
|
||||
function assertFormat(value: string, expected: string, options?: any): void {
|
||||
@@ -31,15 +33,8 @@ suite('HTML Embedded Formatting', () => {
|
||||
let range = Range.create(document.positionAt(rangeStartOffset), document.positionAt(rangeEndOffset));
|
||||
let formatOptions = FormattingOptions.create(2, true);
|
||||
|
||||
let ranges = languageModes.getModesInRange(document, range);
|
||||
let result: TextEdit[] = [];
|
||||
ranges.forEach(r => {
|
||||
let mode = r.mode;
|
||||
if (mode && mode.format) {
|
||||
let edits = mode.format(document, r, formatOptions);
|
||||
pushAll(result, edits);
|
||||
}
|
||||
});
|
||||
let result = format(languageModes, document, range, formatOptions, { css: true, javascript: true });
|
||||
|
||||
let actual = applyEdits(document, result);
|
||||
assert.equal(actual, expected);
|
||||
}
|
||||
@@ -52,38 +47,38 @@ suite('HTML Embedded Formatting', () => {
|
||||
|
||||
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><script>var x=1;</script></head></html>', '<html>\n\n<head>\n <script>\n var x = 1;\n </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\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\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\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>');
|
||||
assertFormat('<html><head>\n |<script>\nvar x=5;\n</script>|</head></html>', '<html><head>\n <script>\n var x = 5;\n\n </script></head></html>');
|
||||
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>');
|
||||
});
|
||||
|
||||
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>');
|
||||
assertFormat('<html>\n<head>\n <script>\nvar x = 0;\n</script></head></html>', '<html>\n\n<head>\n <script>\n var x = 0;\n\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>');
|
||||
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\n </script>\n <script>\n function(x) { }\n\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{display:none;}\n</style>\n</head>\n\n</html>');
|
||||
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 {
|
||||
let options = {
|
||||
html: {
|
||||
format: {
|
||||
endWithNewline : true
|
||||
endWithNewline: true
|
||||
}
|
||||
}
|
||||
};
|
||||
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);
|
||||
assertFormat('<html><head><script>\nvar x=1;\n</script></head></html>', '<html>\n\n<head>\n <script>\n var x = 1;\n\n </script>\n</head>\n\n</html>\n', options);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
34
extensions/html/server/src/utils/edits.ts
Normal file
34
extensions/html/server/src/utils/edits.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TextDocument, TextEdit, Position } from 'vscode-languageserver-types';
|
||||
|
||||
export function applyEdits(document: TextDocument, edits: TextEdit[]): string {
|
||||
let text = document.getText();
|
||||
let sortedEdits = edits.sort((a, b) => {
|
||||
let startDiff = comparePositions(a.range.start, b.range.start);
|
||||
if (startDiff === 0) {
|
||||
return comparePositions(a.range.end, b.range.end);
|
||||
}
|
||||
return startDiff;
|
||||
});
|
||||
let lastOffset = text.length;
|
||||
sortedEdits.forEach(e => {
|
||||
let startOffset = document.offsetAt(e.range.start);
|
||||
let endOffset = document.offsetAt(e.range.end);
|
||||
text = text.substring(0, startOffset) + e.newText + text.substring(endOffset, text.length);
|
||||
lastOffset = startOffset;
|
||||
});
|
||||
return text;
|
||||
}
|
||||
|
||||
function comparePositions(p1: Position, p2: Position) {
|
||||
let diff = p2.line - p1.line;
|
||||
if (diff === 0) {
|
||||
return p2.character - p1.character;
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
Reference in New Issue
Block a user