diff --git a/extensions/html/client/src/htmlMain.ts b/extensions/html/client/src/htmlMain.ts index 7f4c9fe3440..82a1d0ffc59 100644 --- a/extensions/html/client/src/htmlMain.ts +++ b/extensions/html/client/src/htmlMain.ts @@ -12,6 +12,8 @@ import { EMPTY_ELEMENTS } from './htmlEmptyTagsShared'; import { activateColorDecorations } from './colorDecorators'; import TelemetryReporter from 'vscode-extension-telemetry'; +import { ConfigurationFeature } from 'vscode-languageclient/lib/proposed'; + import * as nls from 'vscode-nls'; let localize = nls.loadMessageBundle(); @@ -34,7 +36,7 @@ export function activate(context: ExtensionContext) { // The server is implemented in node let serverModule = context.asAbsolutePath(path.join('server', 'out', 'htmlServerMain.js')); // The debug options for the server - let debugOptions = { execArgv: ['--nolazy', '--debug=6004'] }; + let debugOptions = { execArgv: ['--nolazy', '--inspect=6004'] }; // If the extension is launch in debug mode the debug server options are use // Otherwise the run options are used @@ -59,6 +61,8 @@ export function activate(context: ExtensionContext) { // Create the language client and start the client. let client = new LanguageClient('html', localize('htmlserver.name', 'HTML Language Server'), serverOptions, clientOptions); + client.registerFeature(new ConfigurationFeature(client)); + let disposable = client.start(); context.subscriptions.push(disposable); client.onReady().then(() => { diff --git a/extensions/html/npm-shrinkwrap.json b/extensions/html/npm-shrinkwrap.json index 7fee667332b..9dc0cf34176 100644 --- a/extensions/html/npm-shrinkwrap.json +++ b/extensions/html/npm-shrinkwrap.json @@ -8,24 +8,24 @@ "resolved": "https://registry.npmjs.org/applicationinsights/-/applicationinsights-0.18.0.tgz" }, "vscode-extension-telemetry": { - "version": "0.0.7", + "version": "0.0.8", "from": "vscode-extension-telemetry@>=0.0.8 <0.0.9", "resolved": "https://registry.npmjs.org/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.8.tgz" }, "vscode-jsonrpc": { - "version": "3.1.0-alpha.1", - "from": "vscode-jsonrpc@>=3.1.0-alpha.1 <4.0.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.1.0-alpha.1.tgz" + "version": "3.3.1", + "from": "vscode-jsonrpc@>=3.3.0 <4.0.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.3.1.tgz" }, "vscode-languageclient": { - "version": "3.1.0-alpha.1", + "version": "3.4.0-next.10", "from": "vscode-languageclient@next", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-3.1.0-alpha.1.tgz" + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-3.4.0-next.10.tgz" }, "vscode-languageserver-types": { - "version": "3.0.3", - "from": "vscode-languageserver-types@>=3.0.2-beta.5 <4.0.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.0.3.tgz" + "version": "3.3.0", + "from": "vscode-languageserver-types@>=3.3.0 <4.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.3.0.tgz" }, "vscode-nls": { "version": "2.0.2", @@ -38,4 +38,4 @@ "resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.3.tgz" } } -} \ No newline at end of file +} diff --git a/extensions/html/package.json b/extensions/html/package.json index f79ac361538..a06789f08a2 100644 --- a/extensions/html/package.json +++ b/extensions/html/package.json @@ -75,11 +75,13 @@ "properties": { "html.format.enable": { "type": "boolean", + "scope": "window", "default": true, "description": "%html.format.enable.desc%" }, "html.format.wrapLineLength": { "type": "integer", + "scope": "resource", "default": 120, "description": "%html.format.wrapLineLength.desc%" }, @@ -88,6 +90,7 @@ "string", "null" ], + "scope": "resource", "default": "a, abbr, acronym, b, bdo, big, br, button, cite, code, dfn, em, i, img, input, kbd, label, map, object, q, samp, select, small, span, strong, sub, sup, textarea, tt, var", "description": "%html.format.unformatted.desc%" }, @@ -96,16 +99,19 @@ "string", "null" ], + "scope": "resource", "default": "pre", "description": "%html.format.contentUnformatted.desc%" }, "html.format.indentInnerHtml": { "type": "boolean", + "scope": "resource", "default": false, "description": "%html.format.indentInnerHtml.desc%" }, "html.format.preserveNewLines": { "type": "boolean", + "scope": "resource", "default": true, "description": "%html.format.preserveNewLines.desc%" }, @@ -114,16 +120,19 @@ "number", "null" ], + "scope": "resource", "default": null, "description": "%html.format.maxPreserveNewLines.desc%" }, "html.format.indentHandlebars": { "type": "boolean", + "scope": "resource", "default": false, "description": "%html.format.indentHandlebars.desc%" }, "html.format.endWithNewline": { "type": "boolean", + "scope": "resource", "default": false, "description": "%html.format.endWithNewline.desc%" }, @@ -132,11 +141,13 @@ "string", "null" ], + "scope": "resource", "default": "head, body, /html", "description": "%html.format.extraLiners.desc%" }, "html.format.wrapAttributes": { "type": "string", + "scope": "resource", "default": "auto", "enum": [ "auto", @@ -154,31 +165,37 @@ }, "html.suggest.angular1": { "type": "boolean", + "scope": "resource", "default": true, "description": "%html.suggest.angular1.desc%" }, "html.suggest.ionic": { "type": "boolean", + "scope": "resource", "default": true, "description": "%html.suggest.ionic.desc%" }, "html.suggest.html5": { "type": "boolean", + "scope": "resource", "default": true, "description": "%html.suggest.html5.desc%" }, "html.validate.scripts": { "type": "boolean", + "scope": "resource", "default": true, "description": "%html.validate.scripts%" }, "html.validate.styles": { "type": "boolean", + "scope": "resource", "default": true, "description": "%html.validate.styles%" }, "html.trace.server": { "type": "string", + "scope": "window", "enum": [ "off", "messages", @@ -192,12 +209,12 @@ }, "dependencies": { "vscode-extension-telemetry": "0.0.8", - "vscode-languageclient": "3.1.0-alpha.1", - "vscode-languageserver-types": "3.0.3", + "vscode-languageclient": "3.4.0-next.10", + "vscode-languageserver-types": "^3.3.0", "vscode-nls": "2.0.2" }, "devDependencies": { "@types/node": "^6.0.51", "@types/mocha": "^2.2.33" } -} \ No newline at end of file +} diff --git a/extensions/html/server/npm-shrinkwrap.json b/extensions/html/server/npm-shrinkwrap.json index ee83f51e71e..8853dfc48b4 100644 --- a/extensions/html/server/npm-shrinkwrap.json +++ b/extensions/html/server/npm-shrinkwrap.json @@ -3,16 +3,9 @@ "version": "1.0.0", "dependencies": { "vscode-css-languageservice": { - "version": "2.1.0", + "version": "2.1.3", "from": "vscode-css-languageservice@next", - "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-2.1.0.tgz", - "dependencies": { - "vscode-languageserver-types": { - "version": "3.2.0", - "from": "vscode-languageserver-types@>=3.2.0 <4.0.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.2.0.tgz" - } - } + "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-2.1.3.tgz" }, "vscode-html-languageservice": { "version": "2.0.5", @@ -20,19 +13,19 @@ "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-2.0.5.tgz" }, "vscode-jsonrpc": { - "version": "3.1.0-alpha.1", - "from": "vscode-jsonrpc@>=3.1.0-alpha.1 <4.0.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.1.0-alpha.1.tgz" + "version": "3.3.1", + "from": "vscode-jsonrpc@>=3.3.0 <4.0.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.3.1.tgz" }, "vscode-languageserver": { - "version": "3.1.0-alpha.1", + "version": "3.4.0-next.4", "from": "vscode-languageserver@next", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-3.1.0-alpha.1.tgz" + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-3.4.0-next.4.tgz" }, "vscode-languageserver-types": { - "version": "3.0.3", - "from": "vscode-languageserver-types@>=3.0.3 <4.0.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.0.3.tgz" + "version": "3.3.0", + "from": "vscode-languageserver-types@>=3.3.0 <4.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.3.0.tgz" }, "vscode-nls": { "version": "2.0.2", @@ -40,9 +33,9 @@ "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-2.0.2.tgz" }, "vscode-uri": { - "version": "1.0.0", - "from": "vscode-uri@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.0.tgz" + "version": "1.0.1", + "from": "vscode-uri@latest", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.1.tgz" } } } diff --git a/extensions/html/server/package.json b/extensions/html/server/package.json index 5284d547143..3fe6263139d 100644 --- a/extensions/html/server/package.json +++ b/extensions/html/server/package.json @@ -8,11 +8,12 @@ "node": "*" }, "dependencies": { - "vscode-css-languageservice": "^2.1.0", + "vscode-css-languageservice": "^2.1.3", "vscode-html-languageservice": "^2.0.5", - "vscode-languageserver": "^3.1.0-alpha.1", + "vscode-languageserver": "3.4.0-next.4", + "vscode-languageserver-types": "^3.3.0", "vscode-nls": "^2.0.2", - "vscode-uri": "^1.0.0" + "vscode-uri": "^1.0.1" }, "devDependencies": { "@types/node": "^6.0.51", @@ -25,6 +26,6 @@ "install-service-local": "npm install ../../../../vscode-css-languageservice -f -S && npm install ../../../../vscode-html-languageservice -f -S", "install-server-next": "npm install vscode-languageserver@next -f -S", "install-server-local": "npm install ../../../../vscode-languageserver-node/server -f -S", - "test": "mocha" + "test": "../../../node_modules/.bin/mocha" } } diff --git a/extensions/html/server/src/htmlServerMain.ts b/extensions/html/server/src/htmlServerMain.ts index 0ea5b627424..c29012bd8b1 100644 --- a/extensions/html/server/src/htmlServerMain.ts +++ b/extensions/html/server/src/htmlServerMain.ts @@ -4,10 +4,12 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { createConnection, IConnection, TextDocuments, InitializeParams, InitializeResult, RequestType, DocumentRangeFormattingRequest, Disposable, DocumentSelector } from 'vscode-languageserver'; +import { createConnection, IConnection, TextDocuments, InitializeParams, InitializeResult, RequestType, DocumentRangeFormattingRequest, Disposable, DocumentSelector, GetConfigurationParams } from 'vscode-languageserver'; import { DocumentContext } from 'vscode-html-languageservice'; import { TextDocument, Diagnostic, DocumentLink, Range, SymbolInformation } from 'vscode-languageserver-types'; -import { getLanguageModes, LanguageModes } from './modes/languageModes'; +import { getLanguageModes, LanguageModes, Settings } from './modes/languageModes'; + +import { GetConfigurationRequest } from 'vscode-languageserver/lib/protocol.proposed'; import { format } from './modes/formatting'; import { pushAll } from './utils/arrays'; @@ -38,10 +40,27 @@ documents.listen(connection); let workspacePath: string; var languageModes: LanguageModes; -var settings: any = {}; let clientSnippetSupport = false; let clientDynamicRegisterSupport = false; +let scopedSettingsSupport = false; + +var globalSettings: Settings = {}; +let documentSettings: { [key: string]: Thenable } = {}; + +function getDocumentSettings(textDocument: TextDocument, needsDocumentSettings: () => boolean): Thenable { + if (scopedSettingsSupport && needsDocumentSettings()) { + let promise = documentSettings[textDocument.uri]; + if (!promise) { + let scopeUri = textDocument.uri; + let configRequestParam: GetConfigurationParams = { items: [{ scopeUri, section: 'css' }, { scopeUri, section: 'html' }, { scopeUri, section: 'javascript' }] }; + promise = connection.sendRequest(GetConfigurationRequest.type, configRequestParam).then(s => ({ css: s[0], html: s[1], javascript: s[2] })); + documentSettings[textDocument.uri] = promise; + } + return promise; + } + return Promise.resolve(void 0); +} // After the server has started the client sends an initilize request. The server receives // in the passed params the rootPath of the workspace plus the client capabilites @@ -68,6 +87,7 @@ connection.onInitialize((params: InitializeParams): InitializeResult => { clientSnippetSupport = hasClientCapability('textDocument', 'completion', 'completionItem', 'snippetSupport'); clientDynamicRegisterSupport = hasClientCapability('workspace', 'symbol', 'dynamicRegistration'); + scopedSettingsSupport = hasClientCapability('workspace', 'configuration'); return { capabilities: { // Tell the client that the server works in FULL text document sync mode @@ -85,21 +105,13 @@ connection.onInitialize((params: InitializeParams): InitializeResult => { }; }); -let validation = { - html: true, - css: true, - javascript: true -}; - let formatterRegistration: Thenable = null; // 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; + globalSettings = change.settings; + documentSettings = {}; // reset all document settings languageModes.getAllModes().forEach(m => { if (m.configure) { m.configure(change.settings); @@ -109,7 +121,7 @@ connection.onDidChangeConfiguration((change) => { // dynamically enable & disable the formatter if (clientDynamicRegisterSupport) { - let enableFormatter = settings && settings.html && settings.html.format && settings.html.format.enable; + let enableFormatter = globalSettings && globalSettings.html && globalSettings.html.format && globalSettings.html.format.enable; if (enableFormatter) { if (!formatterRegistration) { let documentSelector: DocumentSelector = [{ language: 'html' }, { language: 'handlebars' }]; // don't register razor, the formatter does more harm than good @@ -154,26 +166,37 @@ function triggerValidation(textDocument: TextDocument): void { }, validationDelayMs); } -function validateTextDocument(textDocument: TextDocument): void { +function isValidationEnabled(languageId: string, settings: Settings = globalSettings) { + let validationSettings = settings && settings.html && settings.html.validate; + if (validationSettings) { + return languageId === 'css' && validationSettings.styles !== false || languageId === 'javascript' && validationSettings.scripts !== false; + } + return true; +} + +async function validateTextDocument(textDocument: TextDocument) { let diagnostics: Diagnostic[] = []; if (textDocument.languageId === 'html') { - languageModes.getAllModesInDocument(textDocument).forEach(mode => { - if (mode.doValidation && validation[mode.getId()]) { - pushAll(diagnostics, mode.doValidation(textDocument)); + let modes = languageModes.getAllModesInDocument(textDocument); + let settings = await getDocumentSettings(textDocument, () => modes.some(m => m.doValidation && m.doValidation.length > 1)); + modes.forEach(mode => { + if (mode.doValidation && isValidationEnabled(mode.getId(), settings)) { + pushAll(diagnostics, mode.doValidation(textDocument, settings)); } }); } connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); } -connection.onCompletion(textDocumentPosition => { +connection.onCompletion(async textDocumentPosition => { let document = documents.get(textDocumentPosition.textDocument.uri); let mode = languageModes.getModeAtPosition(document, textDocumentPosition.position); if (mode && mode.doComplete) { if (mode.getId() !== 'html') { connection.telemetry.logEvent({ key: 'html.embbedded.complete', value: { languageId: mode.getId() } }); } - return mode.doComplete(document, textDocumentPosition.position); + let settings = await getDocumentSettings(document, () => mode.doComplete.length > 2); + return mode.doComplete(document, textDocumentPosition.position, settings); } return { isIncomplete: true, items: [] }; }); @@ -235,13 +258,16 @@ connection.onSignatureHelp(signatureHelpParms => { return null; }); -connection.onDocumentRangeFormatting(formatParams => { +connection.onDocumentRangeFormatting(async formatParams => { let document = documents.get(formatParams.textDocument.uri); - + let settings = await getDocumentSettings(document, () => true); + if (!settings) { + settings = globalSettings; + } 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/) }; - return format(languageModes, document, formatParams.range, formatParams.options, enabledModes); + return format(languageModes, document, formatParams.range, formatParams.options, settings, enabledModes); }); connection.onDocumentLinks(documentLinkParam => { diff --git a/extensions/html/server/src/modes/cssMode.ts b/extensions/html/server/src/modes/cssMode.ts index b813c59f572..bcaa48391d9 100644 --- a/extensions/html/server/src/modes/cssMode.ts +++ b/extensions/html/server/src/modes/cssMode.ts @@ -7,7 +7,7 @@ import { LanguageModelCache, getLanguageModelCache } from '../languageModelCache'; import { TextDocument, Position } from 'vscode-languageserver-types'; import { getCSSLanguageService, Stylesheet } from 'vscode-css-languageservice'; -import { LanguageMode } from './languageModes'; +import { LanguageMode, Settings } from './languageModes'; import { HTMLDocumentRegions, CSS_STYLE_RULE } from './embeddedSupport'; export function getCSSMode(documentRegions: LanguageModelCache): LanguageMode { @@ -22,9 +22,9 @@ export function getCSSMode(documentRegions: LanguageModelCache(10, 60, document => htmlLanguageService.parseHTMLDocument(document)); return { getId() { return 'html'; }, configure(options: any) { - settings = options && options.html; + globalSettings = options; }, - doComplete(document: TextDocument, position: Position) { - let options = settings && settings.suggest; + doComplete(document: TextDocument, position: Position, settings: Settings = globalSettings) { + let options = settings && settings.html && settings.html.suggest; return htmlLanguageService.doComplete(document, position, htmlDocuments.get(document), options); }, doHover(document: TextDocument, position: Position) { @@ -35,8 +35,8 @@ export function getHTMLMode(htmlLanguageService: HTMLLanguageService): LanguageM findDocumentSymbols(document: TextDocument) { return htmlLanguageService.findDocumentSymbols(document, htmlDocuments.get(document)); }, - format(document: TextDocument, range: Range, formatParams: FormattingOptions) { - let formatSettings = settings && settings.format; + format(document: TextDocument, range: Range, formatParams: FormattingOptions, settings: Settings = globalSettings) { + let formatSettings = settings && settings.html && settings.html.format; if (!formatSettings) { formatSettings = formatParams; } else { diff --git a/extensions/html/server/src/modes/javascriptMode.ts b/extensions/html/server/src/modes/javascriptMode.ts index b9cd9075b20..128969d0140 100644 --- a/extensions/html/server/src/modes/javascriptMode.ts +++ b/extensions/html/server/src/modes/javascriptMode.ts @@ -6,7 +6,7 @@ import { LanguageModelCache, getLanguageModelCache } from '../languageModelCache'; import { SymbolInformation, SymbolKind, CompletionItem, Location, SignatureHelp, SignatureInformation, ParameterInformation, Definition, TextEdit, TextDocument, Diagnostic, DiagnosticSeverity, Range, CompletionItemKind, Hover, MarkedString, DocumentHighlight, DocumentHighlightKind, CompletionList, Position, FormattingOptions } from 'vscode-languageserver-types'; -import { LanguageMode } from './languageModes'; +import { LanguageMode, Settings } from './languageModes'; import { getWordAtText, startsWith, isWhitespaceOnly, repeat } from '../utils/strings'; import { HTMLDocumentRegions } from './embeddedSupport'; @@ -60,14 +60,14 @@ export function getJavascriptMode(documentRegions: LanguageModelCache; +} + export interface LanguageMode { getId(); - configure?: (options: any) => void; - doValidation?: (document: TextDocument) => Diagnostic[]; - doComplete?: (document: TextDocument, position: Position) => CompletionList; + configure?: (options: Settings) => void; + doValidation?: (document: TextDocument, settings?: Settings) => Diagnostic[]; + doComplete?: (document: TextDocument, position: Position, settings?: Settings) => CompletionList; doResolve?: (document: TextDocument, item: CompletionItem) => CompletionItem; doHover?: (document: TextDocument, position: Position) => Hover; doSignatureHelp?: (document: TextDocument, position: Position) => SignatureHelp; @@ -29,7 +39,7 @@ export interface LanguageMode { findDocumentLinks?: (document: TextDocument, documentContext: DocumentContext) => DocumentLink[]; findDefinition?: (document: TextDocument, position: Position) => Definition; findReferences?: (document: TextDocument, position: Position) => Location[]; - format?: (document: TextDocument, range: Range, options: FormattingOptions) => TextEdit[]; + format?: (document: TextDocument, range: Range, options: FormattingOptions, settings: Settings) => TextEdit[]; findColorSymbols?: (document: TextDocument) => Range[]; onDocumentRemoved(document: TextDocument): void; dispose(): void; diff --git a/extensions/html/server/src/test/formatting.test.ts b/extensions/html/server/src/test/formatting.test.ts index ea4ecf33153..713d2943ed4 100644 --- a/extensions/html/server/src/test/formatting.test.ts +++ b/extensions/html/server/src/test/formatting.test.ts @@ -38,7 +38,7 @@ suite('HTML Embedded Formatting', () => { formatOptions = FormattingOptions.create(2, true); } - let result = format(languageModes, document, range, formatOptions, { css: true, javascript: true }); + let result = format(languageModes, document, range, formatOptions, void 0, { css: true, javascript: true }); let actual = applyEdits(document, result); assert.equal(actual, expected, message); diff --git a/extensions/html/server/src/test/javascriptMode.test.ts b/extensions/html/server/src/test/javascriptMode.test.ts index 8b231644ee5..c5d44e88cae 100644 --- a/extensions/html/server/src/test/javascriptMode.test.ts +++ b/extensions/html/server/src/test/javascriptMode.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { getJavascriptMode } from '../modes/javascriptMode'; -import { TextDocument, Range, TextEdit, FormattingOptions } from 'vscode-languageserver-types'; +import { TextDocument } from 'vscode-languageserver-types'; import { getLanguageModelCache } from '../languageModelCache'; import { getLanguageService } from 'vscode-html-languageservice';