diff --git a/extensions/html-language-features/client/src/browser/htmlClientMain.ts b/extensions/html-language-features/client/src/browser/htmlClientMain.ts
new file mode 100644
index 00000000000..1623bea9e20
--- /dev/null
+++ b/extensions/html-language-features/client/src/browser/htmlClientMain.ts
@@ -0,0 +1,32 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { ExtensionContext } from 'vscode';
+import { LanguageClientOptions } from 'vscode-languageclient';
+import { startClient, LanguageClientConstructor } from '../htmlClient';
+import { LanguageClient } from 'vscode-languageclient/browser';
+
+declare const Worker: {
+ new(stringUrl: string): any;
+};
+declare const TextDecoder: {
+ new(encoding?: string): { decode(buffer: ArrayBuffer): string; };
+};
+
+// this method is called when vs code is activated
+export function activate(context: ExtensionContext) {
+ const serverMain = context.asAbsolutePath('server/dist/browser/htmlServerMain.js');
+ try {
+ const worker = new Worker(serverMain);
+ const newLanguageClient: LanguageClientConstructor = (id: string, name: string, clientOptions: LanguageClientOptions) => {
+ return new LanguageClient(id, name, clientOptions, worker);
+ };
+
+ startClient(context, newLanguageClient, { TextDecoder });
+
+ } catch (e) {
+ console.log(e);
+ }
+}
diff --git a/extensions/html-language-features/client/src/customData.ts b/extensions/html-language-features/client/src/customData.ts
index 38b51d37596..ecf964056e5 100644
--- a/extensions/html-language-features/client/src/customData.ts
+++ b/extensions/html-language-features/client/src/customData.ts
@@ -3,55 +3,86 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import * as path from 'path';
-import { workspace, WorkspaceFolder, extensions } from 'vscode';
+import { workspace, extensions, Uri, EventEmitter, Disposable } from 'vscode';
+import { resolvePath, joinPath } from './requests';
-interface ExperimentalConfig {
- customData?: string[];
- experimental?: {
- customData?: string[];
+export function getCustomDataSource(toDispose: Disposable[]) {
+ let pathsInWorkspace = getCustomDataPathsInAllWorkspaces();
+ let pathsInExtensions = getCustomDataPathsFromAllExtensions();
+
+ const onChange = new EventEmitter();
+
+ toDispose.push(extensions.onDidChange(_ => {
+ const newPathsInExtensions = getCustomDataPathsFromAllExtensions();
+ if (newPathsInExtensions.length !== pathsInExtensions.length || !newPathsInExtensions.every((val, idx) => val === pathsInExtensions[idx])) {
+ pathsInExtensions = newPathsInExtensions;
+ onChange.fire();
+ }
+ }));
+ toDispose.push(workspace.onDidChangeConfiguration(e => {
+ if (e.affectsConfiguration('html.customData')) {
+ pathsInWorkspace = getCustomDataPathsInAllWorkspaces();
+ onChange.fire();
+ }
+ }));
+
+ return {
+ get uris() {
+ return pathsInWorkspace.concat(pathsInExtensions);
+ },
+ get onDidChange() {
+ return onChange.event;
+ }
};
}
-export function getCustomDataPathsInAllWorkspaces(workspaceFolders: readonly WorkspaceFolder[] | undefined): string[] {
+
+function getCustomDataPathsInAllWorkspaces(): string[] {
+ const workspaceFolders = workspace.workspaceFolders;
+
const dataPaths: string[] = [];
if (!workspaceFolders) {
return dataPaths;
}
- workspaceFolders.forEach(wf => {
- const allHtmlConfig = workspace.getConfiguration(undefined, wf.uri);
- const wfHtmlConfig = allHtmlConfig.inspect('html');
-
- if (wfHtmlConfig && wfHtmlConfig.workspaceFolderValue && wfHtmlConfig.workspaceFolderValue.customData) {
- const customData = wfHtmlConfig.workspaceFolderValue.customData;
- if (Array.isArray(customData)) {
- customData.forEach(t => {
- if (typeof t === 'string') {
- dataPaths.push(path.resolve(wf.uri.fsPath, t));
- }
- });
+ const collect = (paths: string[] | undefined, rootFolder: Uri) => {
+ if (Array.isArray(paths)) {
+ for (const path of paths) {
+ if (typeof path === 'string') {
+ dataPaths.push(resolvePath(rootFolder, path).toString());
+ }
}
}
- });
+ };
+ for (let i = 0; i < workspaceFolders.length; i++) {
+ const folderUri = workspaceFolders[i].uri;
+ const allHtmlConfig = workspace.getConfiguration('html', folderUri);
+ const customDataInspect = allHtmlConfig.inspect('customData');
+ if (customDataInspect) {
+ collect(customDataInspect.workspaceFolderValue, folderUri);
+ if (i === 0) {
+ if (workspace.workspaceFile) {
+ collect(customDataInspect.workspaceValue, workspace.workspaceFile);
+ }
+ collect(customDataInspect.globalValue, folderUri);
+ }
+ }
+
+ }
return dataPaths;
}
-export function getCustomDataPathsFromAllExtensions(): string[] {
+function getCustomDataPathsFromAllExtensions(): string[] {
const dataPaths: string[] = [];
-
for (const extension of extensions.all) {
- const contributes = extension.packageJSON && extension.packageJSON.contributes;
-
- if (contributes && contributes.html && contributes.html.customData && Array.isArray(contributes.html.customData)) {
- const relativePaths: string[] = contributes.html.customData;
- relativePaths.forEach(rp => {
- dataPaths.push(path.resolve(extension.extensionPath, rp));
- });
+ const customData = extension.packageJSON?.contributes?.html?.customData;
+ if (Array.isArray(customData)) {
+ for (const rp of customData) {
+ dataPaths.push(joinPath(extension.extensionUri, rp).toString());
+ }
}
}
-
return dataPaths;
}
diff --git a/extensions/html-language-features/client/src/htmlMain.ts b/extensions/html-language-features/client/src/htmlClient.ts
similarity index 84%
rename from extensions/html-language-features/client/src/htmlMain.ts
rename to extensions/html-language-features/client/src/htmlClient.ts
index 913b43378b0..e818d1a2d02 100644
--- a/extensions/html-language-features/client/src/htmlMain.ts
+++ b/extensions/html-language-features/client/src/htmlClient.ts
@@ -3,7 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import * as fs from 'fs';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
@@ -13,13 +12,17 @@ import {
DocumentSemanticTokensProvider, DocumentRangeSemanticTokensProvider, SemanticTokens, window, commands
} from 'vscode';
import {
- LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, RequestType, TextDocumentPositionParams, DocumentRangeFormattingParams,
- DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, TextDocumentIdentifier, RequestType0, Range as LspRange
+ LanguageClientOptions, RequestType, TextDocumentPositionParams, DocumentRangeFormattingParams,
+ DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, TextDocumentIdentifier, RequestType0, Range as LspRange, NotificationType, CommonLanguageClient
} from 'vscode-languageclient';
import { EMPTY_ELEMENTS } from './htmlEmptyTagsShared';
import { activateTagClosing } from './tagClosing';
-import TelemetryReporter from 'vscode-extension-telemetry';
-import { getCustomDataPathsInAllWorkspaces, getCustomDataPathsFromAllExtensions } from './customData';
+import { RequestService } from './requests';
+import { getCustomDataSource } from './customData';
+
+namespace CustomDataChangedNotification {
+ export const type: NotificationType = new NotificationType('html/customDataChanged');
+}
namespace TagCloseRequest {
export const type: RequestType = new RequestType('html/tag');
@@ -46,44 +49,33 @@ namespace SettingIds {
}
-interface IPackageInfo {
- name: string;
- version: string;
- aiKey: string;
- main: string;
+export interface TelemetryReporter {
+ sendTelemetryEvent(eventName: string, properties?: {
+ [key: string]: string;
+ }, measurements?: {
+ [key: string]: number;
+ }): void;
}
-let telemetryReporter: TelemetryReporter | null;
+export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => CommonLanguageClient;
+export interface Runtime {
+ TextDecoder: { new(encoding?: string): { decode(buffer: ArrayBuffer): string; } };
+ fs?: RequestService;
+ telemetry?: TelemetryReporter;
+}
+
+export function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime) {
-export function activate(context: ExtensionContext) {
let toDispose = context.subscriptions;
- let clientPackageJSON = getPackageInfo(context);
- telemetryReporter = new TelemetryReporter(clientPackageJSON.name, clientPackageJSON.version, clientPackageJSON.aiKey);
-
- const serverMain = `./server/${clientPackageJSON.main.indexOf('/dist/') !== -1 ? 'dist' : 'out'}/htmlServerMain`;
- const serverModule = context.asAbsolutePath(serverMain);
-
- // The debug options for the server
- let debugOptions = { execArgv: ['--nolazy', '--inspect=6045'] };
-
- // If the extension is launch in debug mode the debug server options are use
- // Otherwise the run options are used
- let serverOptions: ServerOptions = {
- run: { module: serverModule, transport: TransportKind.ipc },
- debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions }
- };
let documentSelector = ['html', 'handlebars'];
let embeddedLanguages = { css: true, javascript: true };
let rangeFormatting: Disposable | undefined = undefined;
- let dataPaths = [
- ...getCustomDataPathsInAllWorkspaces(workspace.workspaceFolders),
- ...getCustomDataPathsFromAllExtensions()
- ];
+ const customDataSource = getCustomDataSource(context.subscriptions);
// Options to control the language client
let clientOptions: LanguageClientOptions = {
@@ -93,7 +85,7 @@ export function activate(context: ExtensionContext) {
},
initializationOptions: {
embeddedLanguages,
- dataPaths,
+ handledSchemas: ['file'],
provideFormatter: false, // tell the server to not provide formatting capability and ignore the `html.format.enable` setting.
},
middleware: {
@@ -123,12 +115,18 @@ 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);
+ let client = newLanguageClient('html', localize('htmlserver.name', 'HTML Language Server'), clientOptions);
client.registerProposedFeatures();
let disposable = client.start();
toDispose.push(disposable);
client.onReady().then(() => {
+
+ client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris);
+ customDataSource.onDidChange(() => {
+ client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris);
+ });
+
let tagRequestor = (document: TextDocument, position: Position) => {
let param = client.code2ProtocolConverter.asTextDocumentPositionParams(document, position);
return client.sendRequest(TagCloseRequest.type, param);
@@ -137,9 +135,7 @@ export function activate(context: ExtensionContext) {
toDispose.push(disposable);
disposable = client.onTelemetry(e => {
- if (telemetryReporter) {
- telemetryReporter.sendTelemetryEvent(e.key, e.data);
- }
+ runtime.telemetry?.sendTelemetryEvent(e.key, e.data);
});
toDispose.push(disposable);
@@ -201,7 +197,7 @@ export function activate(context: ExtensionContext) {
return client.sendRequest(DocumentRangeFormattingRequest.type, params, token).then(
client.protocol2CodeConverter.asTextEdits,
(error) => {
- client.logFailedRequest(DocumentRangeFormattingRequest.type, error);
+ client.handleFailedRequest(DocumentRangeFormattingRequest.type, error, []);
return Promise.resolve([]);
}
);
@@ -319,17 +315,3 @@ export function activate(context: ExtensionContext) {
toDispose.push();
}
-
-function getPackageInfo(context: ExtensionContext): IPackageInfo {
- const location = context.asAbsolutePath('./package.json');
- try {
- return JSON.parse(fs.readFileSync(location).toString());
- } catch (e) {
- console.log(`Problems reading ${location}: ${e}`);
- return { name: '', version: '', aiKey: '', main: '' };
- }
-}
-
-export function deactivate(): Promise {
- return telemetryReporter ? telemetryReporter.dispose() : Promise.resolve(null);
-}
diff --git a/extensions/html-language-features/client/src/node/htmlClientMain.ts b/extensions/html-language-features/client/src/node/htmlClientMain.ts
new file mode 100644
index 00000000000..c58515b3712
--- /dev/null
+++ b/extensions/html-language-features/client/src/node/htmlClientMain.ts
@@ -0,0 +1,58 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { getNodeFSRequestService } from './nodeFs';
+import { ExtensionContext } from 'vscode';
+import { startClient, LanguageClientConstructor } from '../htmlClient';
+import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient } from 'vscode-languageclient/node';
+import { TextDecoder } from 'util';
+import * as fs from 'fs';
+import TelemetryReporter from 'vscode-extension-telemetry';
+
+
+let telemetry: TelemetryReporter | undefined;
+
+// this method is called when vs code is activated
+export function activate(context: ExtensionContext) {
+
+ let clientPackageJSON = getPackageInfo(context);
+ telemetry = new TelemetryReporter(clientPackageJSON.name, clientPackageJSON.version, clientPackageJSON.aiKey);
+
+ const serverMain = `./server/${clientPackageJSON.main.indexOf('/dist/') !== -1 ? 'dist' : 'out'}/node/htmlServerMain`;
+ const serverModule = context.asAbsolutePath(serverMain);
+
+ // The debug options for the server
+ const debugOptions = { execArgv: ['--nolazy', '--inspect=6044'] };
+
+ // If the extension is launch in debug mode the debug server options are use
+ // Otherwise the run options are used
+ const serverOptions: ServerOptions = {
+ run: { module: serverModule, transport: TransportKind.ipc },
+ debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions }
+ };
+
+ const newLanguageClient: LanguageClientConstructor = (id: string, name: string, clientOptions: LanguageClientOptions) => {
+ return new LanguageClient(id, name, serverOptions, clientOptions);
+ };
+
+ startClient(context, newLanguageClient, { fs: getNodeFSRequestService(), TextDecoder, telemetry });
+}
+
+interface IPackageInfo {
+ name: string;
+ version: string;
+ aiKey: string;
+ main: string;
+}
+
+function getPackageInfo(context: ExtensionContext): IPackageInfo {
+ const location = context.asAbsolutePath('./package.json');
+ try {
+ return JSON.parse(fs.readFileSync(location).toString());
+ } catch (e) {
+ console.log(`Problems reading ${location}: ${e}`);
+ return { name: '', version: '', aiKey: '', main: '' };
+ }
+}
diff --git a/extensions/html-language-features/client/src/node/nodeFs.ts b/extensions/html-language-features/client/src/node/nodeFs.ts
new file mode 100644
index 00000000000..c13ef2e1c08
--- /dev/null
+++ b/extensions/html-language-features/client/src/node/nodeFs.ts
@@ -0,0 +1,85 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as fs from 'fs';
+import { Uri } from 'vscode';
+import { getScheme, RequestService, FileType } from '../requests';
+
+export function getNodeFSRequestService(): RequestService {
+ function ensureFileUri(location: string) {
+ if (getScheme(location) !== 'file') {
+ throw new Error('fileRequestService can only handle file URLs');
+ }
+ }
+ return {
+ getContent(location: string, encoding?: string) {
+ ensureFileUri(location);
+ return new Promise((c, e) => {
+ const uri = Uri.parse(location);
+ fs.readFile(uri.fsPath, encoding, (err, buf) => {
+ if (err) {
+ return e(err);
+ }
+ c(buf.toString());
+
+ });
+ });
+ },
+ stat(location: string) {
+ ensureFileUri(location);
+ return new Promise((c, e) => {
+ const uri = Uri.parse(location);
+ fs.stat(uri.fsPath, (err, stats) => {
+ if (err) {
+ if (err.code === 'ENOENT') {
+ return c({ type: FileType.Unknown, ctime: -1, mtime: -1, size: -1 });
+ } else {
+ return e(err);
+ }
+ }
+
+ let type = FileType.Unknown;
+ if (stats.isFile()) {
+ type = FileType.File;
+ } else if (stats.isDirectory()) {
+ type = FileType.Directory;
+ } else if (stats.isSymbolicLink()) {
+ type = FileType.SymbolicLink;
+ }
+
+ c({
+ type,
+ ctime: stats.ctime.getTime(),
+ mtime: stats.mtime.getTime(),
+ size: stats.size
+ });
+ });
+ });
+ },
+ readDirectory(location: string) {
+ ensureFileUri(location);
+ return new Promise((c, e) => {
+ const path = Uri.parse(location).fsPath;
+
+ fs.readdir(path, { withFileTypes: true }, (err, children) => {
+ if (err) {
+ return e(err);
+ }
+ c(children.map(stat => {
+ if (stat.isSymbolicLink()) {
+ return [stat.name, FileType.SymbolicLink];
+ } else if (stat.isDirectory()) {
+ return [stat.name, FileType.Directory];
+ } else if (stat.isFile()) {
+ return [stat.name, FileType.File];
+ } else {
+ return [stat.name, FileType.Unknown];
+ }
+ }));
+ });
+ });
+ }
+ };
+}
diff --git a/extensions/html-language-features/client/src/requests.ts b/extensions/html-language-features/client/src/requests.ts
new file mode 100644
index 00000000000..ac966636314
--- /dev/null
+++ b/extensions/html-language-features/client/src/requests.ts
@@ -0,0 +1,148 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { Uri, workspace } from 'vscode';
+import { RequestType, CommonLanguageClient } from 'vscode-languageclient';
+import { Runtime } from './htmlClient';
+
+export namespace FsContentRequest {
+ export const type: RequestType<{ uri: string; encoding?: string; }, string, any, any> = new RequestType('fs/content');
+}
+export namespace FsStatRequest {
+ export const type: RequestType = new RequestType('fs/stat');
+}
+
+export namespace FsReadDirRequest {
+ export const type: RequestType = new RequestType('fs/readDir');
+}
+
+export function serveFileSystemRequests(client: CommonLanguageClient, runtime: Runtime) {
+ client.onRequest(FsContentRequest.type, (param: { uri: string; encoding?: string; }) => {
+ const uri = Uri.parse(param.uri);
+ if (uri.scheme === 'file' && runtime.fs) {
+ return runtime.fs.getContent(param.uri);
+ }
+ return workspace.fs.readFile(uri).then(buffer => {
+ return new runtime.TextDecoder(param.encoding).decode(buffer);
+ });
+ });
+ client.onRequest(FsReadDirRequest.type, (uriString: string) => {
+ const uri = Uri.parse(uriString);
+ if (uri.scheme === 'file' && runtime.fs) {
+ return runtime.fs.readDirectory(uriString);
+ }
+ return workspace.fs.readDirectory(uri);
+ });
+ client.onRequest(FsStatRequest.type, (uriString: string) => {
+ const uri = Uri.parse(uriString);
+ if (uri.scheme === 'file' && runtime.fs) {
+ return runtime.fs.stat(uriString);
+ }
+ return workspace.fs.stat(uri);
+ });
+}
+
+export enum FileType {
+ /**
+ * The file type is unknown.
+ */
+ Unknown = 0,
+ /**
+ * A regular file.
+ */
+ File = 1,
+ /**
+ * A directory.
+ */
+ Directory = 2,
+ /**
+ * A symbolic link to a file.
+ */
+ SymbolicLink = 64
+}
+export interface FileStat {
+ /**
+ * The type of the file, e.g. is a regular file, a directory, or symbolic link
+ * to a file.
+ */
+ type: FileType;
+ /**
+ * The creation timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
+ */
+ ctime: number;
+ /**
+ * The modification timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
+ */
+ mtime: number;
+ /**
+ * The size in bytes.
+ */
+ size: number;
+}
+
+export interface RequestService {
+ getContent(uri: string, encoding?: string): Promise;
+
+ stat(uri: string): Promise;
+ readDirectory(uri: string): Promise<[string, FileType][]>;
+}
+
+export function getScheme(uri: string) {
+ return uri.substr(0, uri.indexOf(':'));
+}
+
+export function dirname(uri: string) {
+ const lastIndexOfSlash = uri.lastIndexOf('/');
+ return lastIndexOfSlash !== -1 ? uri.substr(0, lastIndexOfSlash) : '';
+}
+
+export function basename(uri: string) {
+ const lastIndexOfSlash = uri.lastIndexOf('/');
+ return uri.substr(lastIndexOfSlash + 1);
+}
+
+const Slash = '/'.charCodeAt(0);
+const Dot = '.'.charCodeAt(0);
+
+export function isAbsolutePath(path: string) {
+ return path.charCodeAt(0) === Slash;
+}
+
+export function resolvePath(uri: Uri, path: string): Uri {
+ if (isAbsolutePath(path)) {
+ return uri.with({ path: normalizePath(path.split('/')) });
+ }
+ return joinPath(uri, path);
+}
+
+export function normalizePath(parts: string[]): string {
+ const newParts: string[] = [];
+ for (const part of parts) {
+ if (part.length === 0 || part.length === 1 && part.charCodeAt(0) === Dot) {
+ // ignore
+ } else if (part.length === 2 && part.charCodeAt(0) === Dot && part.charCodeAt(1) === Dot) {
+ newParts.pop();
+ } else {
+ newParts.push(part);
+ }
+ }
+ if (parts.length > 1 && parts[parts.length - 1].length === 0) {
+ newParts.push('');
+ }
+ let res = newParts.join('/');
+ if (parts[0].length === 0) {
+ res = '/' + res;
+ }
+ return res;
+}
+
+
+export function joinPath(uri: Uri, ...paths: string[]): Uri {
+ const parts = uri.path.split('/');
+ for (let path of paths) {
+ parts.push(...path.split('/'));
+ }
+ return uri.with({ path: normalizePath(parts) });
+}
diff --git a/extensions/html-language-features/extension-browser.webpack.config.js b/extensions/html-language-features/extension-browser.webpack.config.js
new file mode 100644
index 00000000000..ec6c3e60eb3
--- /dev/null
+++ b/extensions/html-language-features/extension-browser.webpack.config.js
@@ -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.
+ *--------------------------------------------------------------------------------------------*/
+
+//@ts-check
+
+'use strict';
+
+const withDefaults = require('../shared.webpack.config');
+const path = require('path');
+
+const clientConfig = withDefaults({
+ target: 'webworker',
+ context: path.join(__dirname, 'client'),
+ entry: {
+ extension: './src/browser/htmlClientMain.ts'
+ },
+ output: {
+ filename: 'htmlClientMain.js',
+ path: path.join(__dirname, 'client', 'dist', 'browser')
+ },
+ performance: {
+ hints: false
+ },
+ resolve: {
+ alias: {
+ 'vscode-nls': path.resolve(__dirname, '../../build/polyfills/vscode-nls.js')
+ }
+ }
+});
+clientConfig.module.rules[0].use.shift(); // remove nls loader
+
+module.exports = clientConfig;
diff --git a/extensions/html-language-features/extension.webpack.config.js b/extensions/html-language-features/extension.webpack.config.js
index 9624295ff5b..6af444db930 100644
--- a/extensions/html-language-features/extension.webpack.config.js
+++ b/extensions/html-language-features/extension.webpack.config.js
@@ -13,10 +13,10 @@ const path = require('path');
module.exports = withDefaults({
context: path.join(__dirname, 'client'),
entry: {
- extension: './src/htmlMain.ts',
+ extension: './src/node/htmlClientMain.ts',
},
output: {
- filename: 'htmlMain.js',
- path: path.join(__dirname, 'client', 'dist')
+ filename: 'htmlClientMain.js',
+ path: path.join(__dirname, 'client', 'dist', 'node')
}
});
diff --git a/extensions/html-language-features/package.json b/extensions/html-language-features/package.json
index c86a74e2cc0..1e223641b13 100644
--- a/extensions/html-language-features/package.json
+++ b/extensions/html-language-features/package.json
@@ -15,7 +15,8 @@
"onLanguage:html",
"onLanguage:handlebars"
],
- "main": "./client/out/htmlMain",
+ "main": "./client/out/node/htmlClientMain",
+ "browser": "./client/dist/browser/htmlClientMain",
"scripts": {
"compile": "npx gulp compile-extension:html-language-features-client compile-extension:html-language-features-server",
"watch": "npx gulp watch-extension:html-language-features-client watch-extension:html-language-features-server",
@@ -202,7 +203,7 @@
},
"dependencies": {
"vscode-extension-telemetry": "0.1.1",
- "vscode-languageclient": "^6.1.3",
+ "vscode-languageclient": "7.0.0-next.5",
"vscode-nls": "^4.1.2"
},
"devDependencies": {
diff --git a/extensions/html-language-features/server/extension-browser.webpack.config.js b/extensions/html-language-features/server/extension-browser.webpack.config.js
new file mode 100644
index 00000000000..2220a6c3ace
--- /dev/null
+++ b/extensions/html-language-features/server/extension-browser.webpack.config.js
@@ -0,0 +1,36 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+//@ts-check
+
+'use strict';
+
+const withDefaults = require('../../shared.webpack.config');
+const path = require('path');
+
+const serverConfig = withDefaults({
+ target: 'webworker',
+ context: __dirname,
+ entry: {
+ extension: './src/browser/htmlServerMain.ts',
+ },
+ output: {
+ filename: 'htmlServerMain.js',
+ path: path.join(__dirname, 'dist', 'browser'),
+ libraryTarget: 'var'
+ },
+ performance: {
+ hints: false
+ },
+ resolve: {
+ alias: {
+ 'vscode-nls': path.resolve(__dirname, '../../../build/polyfills/vscode-nls.js')
+ }
+ }
+});
+serverConfig.module.rules[0].use.shift(); // remove nls loader
+serverConfig.module.noParse = /typescript\/lib\/typescript\.js/;
+
+module.exports = serverConfig;
diff --git a/extensions/html-language-features/server/extension.webpack.config.js b/extensions/html-language-features/server/extension.webpack.config.js
index 77b86e718b1..33cb0b4f0a1 100644
--- a/extensions/html-language-features/server/extension.webpack.config.js
+++ b/extensions/html-language-features/server/extension.webpack.config.js
@@ -13,11 +13,11 @@ const path = require('path');
module.exports = withDefaults({
context: path.join(__dirname),
entry: {
- extension: './src/htmlServerMain.ts',
+ extension: './src/node/htmlServerMain.ts',
},
output: {
filename: 'htmlServerMain.js',
- path: path.join(__dirname, 'dist'),
+ path: path.join(__dirname, 'dist', 'node'),
},
externals: {
'typescript': 'commonjs typescript'
diff --git a/extensions/html-language-features/server/package.json b/extensions/html-language-features/server/package.json
index 70665bf8497..4d3b5ba9a65 100644
--- a/extensions/html-language-features/server/package.json
+++ b/extensions/html-language-features/server/package.json
@@ -7,11 +7,11 @@
"engines": {
"node": "*"
},
- "main": "./out/htmlServerMain",
+ "main": "./out/node/htmlServerMain",
"dependencies": {
- "vscode-css-languageservice": "^4.1.2",
- "vscode-html-languageservice": "^3.1.0-next.2",
- "vscode-languageserver": "^6.1.1",
+ "vscode-css-languageservice": "4.3.0-next.2",
+ "vscode-html-languageservice": "3.1.0-next.2",
+ "vscode-languageserver": "7.0.0-next.3",
"vscode-nls": "^4.1.2",
"vscode-uri": "^2.1.2"
},
diff --git a/extensions/html-language-features/server/src/browser/htmlServerMain.ts b/extensions/html-language-features/server/src/browser/htmlServerMain.ts
new file mode 100644
index 00000000000..1d38d33db37
--- /dev/null
+++ b/extensions/html-language-features/server/src/browser/htmlServerMain.ts
@@ -0,0 +1,16 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { createConnection, BrowserMessageReader, BrowserMessageWriter } from 'vscode-languageserver/browser';
+import { startServer } from '../htmlServer';
+
+declare let self: any;
+
+const messageReader = new BrowserMessageReader(self);
+const messageWriter = new BrowserMessageWriter(self);
+
+const connection = createConnection(messageReader, messageWriter);
+
+startServer(connection, {});
diff --git a/extensions/html-language-features/server/src/customData.ts b/extensions/html-language-features/server/src/customData.ts
index 1d550eddf9f..3ef347a23d2 100644
--- a/extensions/html-language-features/server/src/customData.ts
+++ b/extensions/html-language-features/server/src/customData.ts
@@ -4,26 +4,35 @@
*--------------------------------------------------------------------------------------------*/
import { IHTMLDataProvider, newHTMLDataProvider } from 'vscode-html-languageservice';
-import * as fs from 'fs';
+import { RequestService } from './requests';
-export function getDataProviders(dataPaths?: string[]): IHTMLDataProvider[] {
- if (!dataPaths) {
- return [];
- }
-
- const providers: IHTMLDataProvider[] = [];
-
- dataPaths.forEach((path, i) => {
+export function fetchHTMLDataProviders(dataPaths: string[], requestService: RequestService): Promise {
+ const providers = dataPaths.map(async p => {
try {
- if (fs.existsSync(path)) {
- const htmlData = JSON.parse(fs.readFileSync(path, 'utf-8'));
-
- providers.push(newHTMLDataProvider(`customProvider${i}`, htmlData));
- }
- } catch (err) {
- console.log(`Failed to load tag from ${path}`);
+ const content = await requestService.getContent(p);
+ return parseHTMLData(p, content);
+ } catch (e) {
+ return newHTMLDataProvider(p, { version: 1 });
}
});
- return providers;
-}
\ No newline at end of file
+ return Promise.all(providers);
+}
+
+function parseHTMLData(id: string, source: string): IHTMLDataProvider {
+ let rawData: any;
+
+ try {
+ rawData = JSON.parse(source);
+ } catch (err) {
+ return newHTMLDataProvider(id, { version: 1 });
+ }
+
+ return newHTMLDataProvider(id, {
+ version: rawData.version || 1,
+ tags: rawData.tags || [],
+ globalAttributes: rawData.globalAttributes || [],
+ valueSets: rawData.valueSets || []
+ });
+}
+
diff --git a/extensions/html-language-features/server/src/htmlServer.ts b/extensions/html-language-features/server/src/htmlServer.ts
new file mode 100644
index 00000000000..680fc4f5352
--- /dev/null
+++ b/extensions/html-language-features/server/src/htmlServer.ts
@@ -0,0 +1,559 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import {
+ Connection, TextDocuments, InitializeParams, InitializeResult, RequestType,
+ DocumentRangeFormattingRequest, Disposable, DocumentSelector, TextDocumentPositionParams, ServerCapabilities,
+ ConfigurationRequest, ConfigurationParams, DidChangeWorkspaceFoldersNotification,
+ DocumentColorRequest, ColorPresentationRequest, TextDocumentSyncKind, NotificationType
+} from 'vscode-languageserver';
+import {
+ getLanguageModes, LanguageModes, Settings, TextDocument, Position, Diagnostic, WorkspaceFolder, ColorInformation,
+ Range, DocumentLink, SymbolInformation, TextDocumentIdentifier
+} from './modes/languageModes';
+
+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 { getFoldingRanges } from './modes/htmlFolding';
+import { fetchHTMLDataProviders } from './customData';
+import { getSelectionRanges } from './modes/selectionRanges';
+import { SemanticTokenProvider, newSemanticTokenProvider } from './modes/semanticTokens';
+import { RequestService, getRequestService } from './requests';
+
+namespace CustomDataChangedNotification {
+ export const type: NotificationType = new NotificationType('html/customDataChanged');
+}
+
+namespace TagCloseRequest {
+ export const type: RequestType = new RequestType('html/tag');
+}
+namespace OnTypeRenameRequest {
+ export const type: RequestType = new RequestType('html/onTypeRename');
+}
+
+// experimental: semantic tokens
+interface SemanticTokenParams {
+ textDocument: TextDocumentIdentifier;
+ ranges?: Range[];
+}
+namespace SemanticTokenRequest {
+ export const type: RequestType = new RequestType('html/semanticTokens');
+}
+namespace SemanticTokenLegendRequest {
+ export const type: RequestType = new RequestType('html/semanticTokenLegend');
+}
+
+export interface RuntimeEnvironment {
+ file?: RequestService;
+ http?: RequestService
+ configureHttpRequests?(proxy: string, strictSSL: boolean): void;
+}
+
+export function startServer(connection: Connection, runtime: RuntimeEnvironment) {
+
+ // Create a text document manager.
+ const documents = new TextDocuments(TextDocument);
+ // Make the text document manager listen on the connection
+ // for open, change and close text document events
+ documents.listen(connection);
+
+ let workspaceFolders: WorkspaceFolder[] = [];
+
+ let languageModes: LanguageModes;
+
+ let clientSnippetSupport = false;
+ let dynamicFormatterRegistration = false;
+ let scopedSettingsSupport = false;
+ let workspaceFoldersSupport = false;
+ let foldingRangeLimit = Number.MAX_VALUE;
+
+ const notReady = () => Promise.reject('Not Ready');
+ let requestService: RequestService = { getContent: notReady, stat: notReady, readDirectory: notReady };
+
+
+
+ let globalSettings: Settings = {};
+ let documentSettings: { [key: string]: Thenable } = {};
+ // remove document settings on close
+ documents.onDidClose(e => {
+ delete documentSettings[e.document.uri];
+ });
+
+ function getDocumentSettings(textDocument: TextDocument, needsDocumentSettings: () => boolean): Thenable {
+ if (scopedSettingsSupport && needsDocumentSettings()) {
+ let promise = documentSettings[textDocument.uri];
+ if (!promise) {
+ const scopeUri = textDocument.uri;
+ const configRequestParam: ConfigurationParams = { items: [{ scopeUri, section: 'css' }, { scopeUri, section: 'html' }, { scopeUri, section: 'javascript' }] };
+ promise = connection.sendRequest(ConfigurationRequest.type, configRequestParam).then(s => ({ css: s[0], html: s[1], javascript: s[2] }));
+ documentSettings[textDocument.uri] = promise;
+ }
+ return promise;
+ }
+ return Promise.resolve(undefined);
+ }
+
+ // After the server has started the client sends an initialize request. The server receives
+ // in the passed params the rootPath of the workspace plus the client capabilities
+ connection.onInitialize((params: InitializeParams): InitializeResult => {
+ const initializationOptions = params.initializationOptions;
+
+ workspaceFolders = (params).workspaceFolders;
+ if (!Array.isArray(workspaceFolders)) {
+ workspaceFolders = [];
+ if (params.rootPath) {
+ workspaceFolders.push({ name: '', uri: URI.file(params.rootPath).toString() });
+ }
+ }
+
+ requestService = getRequestService(params.initializationOptions.handledSchemas || ['file'], connection, runtime);
+
+ const workspace = {
+ get settings() { return globalSettings; },
+ get folders() { return workspaceFolders; }
+ };
+
+ languageModes = getLanguageModes(initializationOptions ? initializationOptions.embeddedLanguages : { css: true, javascript: true }, workspace, params.capabilities, requestService);
+
+ const dataPaths: string[] = params.initializationOptions.dataPaths || [];
+ fetchHTMLDataProviders(dataPaths, requestService).then(dataProviders => {
+ languageModes.updateDataProviders(dataProviders);
+ });
+
+ documents.onDidClose(e => {
+ languageModes.onDocumentRemoved(e.document);
+ });
+ connection.onShutdown(() => {
+ languageModes.dispose();
+ });
+
+ function getClientCapability(name: string, def: T) {
+ const keys = name.split('.');
+ let c: any = params.capabilities;
+ for (let i = 0; c && i < keys.length; i++) {
+ if (!c.hasOwnProperty(keys[i])) {
+ return def;
+ }
+ c = c[keys[i]];
+ }
+ return c;
+ }
+
+ clientSnippetSupport = getClientCapability('textDocument.completion.completionItem.snippetSupport', false);
+ dynamicFormatterRegistration = getClientCapability('textDocument.rangeFormatting.dynamicRegistration', false) && (typeof params.initializationOptions.provideFormatter !== 'boolean');
+ scopedSettingsSupport = getClientCapability('workspace.configuration', false);
+ workspaceFoldersSupport = getClientCapability('workspace.workspaceFolders', false);
+ foldingRangeLimit = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE);
+ const capabilities: ServerCapabilities = {
+ textDocumentSync: TextDocumentSyncKind.Incremental,
+ completionProvider: clientSnippetSupport ? { resolveProvider: true, triggerCharacters: ['.', ':', '<', '"', '=', '/'] } : undefined,
+ hoverProvider: true,
+ documentHighlightProvider: true,
+ documentRangeFormattingProvider: params.initializationOptions.provideFormatter === true,
+ documentLinkProvider: { resolveProvider: false },
+ documentSymbolProvider: true,
+ definitionProvider: true,
+ signatureHelpProvider: { triggerCharacters: ['('] },
+ referencesProvider: true,
+ colorProvider: {},
+ foldingRangeProvider: true,
+ selectionRangeProvider: true,
+ renameProvider: true
+ };
+ return { capabilities };
+ });
+
+ connection.onInitialized(() => {
+ if (workspaceFoldersSupport) {
+ connection.client.register(DidChangeWorkspaceFoldersNotification.type);
+
+ connection.onNotification(DidChangeWorkspaceFoldersNotification.type, e => {
+ const toAdd = e.event.added;
+ const toRemove = e.event.removed;
+ const updatedFolders = [];
+ if (workspaceFolders) {
+ for (const folder of workspaceFolders) {
+ if (!toRemove.some(r => r.uri === folder.uri) && !toAdd.some(r => r.uri === folder.uri)) {
+ updatedFolders.push(folder);
+ }
+ }
+ }
+ workspaceFolders = updatedFolders.concat(toAdd);
+ documents.all().forEach(triggerValidation);
+ });
+ }
+ });
+
+ let formatterRegistration: Thenable | null = null;
+
+ // The settings have changed. Is send on server activation as well.
+ connection.onDidChangeConfiguration((change) => {
+ globalSettings = change.settings;
+ documentSettings = {}; // reset all document settings
+ documents.all().forEach(triggerValidation);
+
+ // dynamically enable & disable the formatter
+ if (dynamicFormatterRegistration) {
+ const enableFormatter = globalSettings && globalSettings.html && globalSettings.html.format && globalSettings.html.format.enable;
+ if (enableFormatter) {
+ if (!formatterRegistration) {
+ const documentSelector: DocumentSelector = [{ language: 'html' }, { language: 'handlebars' }];
+ formatterRegistration = connection.client.register(DocumentRangeFormattingRequest.type, { documentSelector });
+ }
+ } else if (formatterRegistration) {
+ formatterRegistration.then(r => r.dispose());
+ formatterRegistration = null;
+ }
+ }
+ });
+
+ const pendingValidationRequests: { [uri: string]: NodeJS.Timer } = {};
+ const validationDelayMs = 500;
+
+ // The content of a text document has changed. This event is emitted
+ // when the text document first opened or when its content has changed.
+ documents.onDidChangeContent(change => {
+ triggerValidation(change.document);
+ });
+
+ // a document has closed: clear all diagnostics
+ documents.onDidClose(event => {
+ cleanPendingValidation(event.document);
+ connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] });
+ });
+
+ function cleanPendingValidation(textDocument: TextDocument): void {
+ const request = pendingValidationRequests[textDocument.uri];
+ if (request) {
+ clearTimeout(request);
+ delete pendingValidationRequests[textDocument.uri];
+ }
+ }
+
+ function triggerValidation(textDocument: TextDocument): void {
+ cleanPendingValidation(textDocument);
+ pendingValidationRequests[textDocument.uri] = setTimeout(() => {
+ delete pendingValidationRequests[textDocument.uri];
+ validateTextDocument(textDocument);
+ }, validationDelayMs);
+ }
+
+ function isValidationEnabled(languageId: string, settings: Settings = globalSettings) {
+ const 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) {
+ try {
+ const version = textDocument.version;
+ const diagnostics: Diagnostic[] = [];
+ if (textDocument.languageId === 'html') {
+ const modes = languageModes.getAllModesInDocument(textDocument);
+ 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 => {
+ if (mode.doValidation && isValidationEnabled(mode.getId(), settings)) {
+ pushAll(diagnostics, mode.doValidation(latestTextDocument, settings));
+ }
+ });
+ connection.sendDiagnostics({ uri: latestTextDocument.uri, diagnostics });
+ }
+ }
+ } catch (e) {
+ connection.console.error(formatError(`Error while validating ${textDocument.uri}`, e));
+ }
+ }
+
+ connection.onCompletion(async (textDocumentPosition, token) => {
+ return runSafeAsync(async () => {
+ const document = documents.get(textDocumentPosition.textDocument.uri);
+ if (!document) {
+ return null;
+ }
+ const mode = languageModes.getModeAtPosition(document, textDocumentPosition.position);
+ if (!mode || !mode.doComplete) {
+ return { isIncomplete: true, items: [] };
+ }
+ const doComplete = mode.doComplete!;
+
+ if (mode.getId() !== 'html') {
+ /* __GDPR__
+ "html.embbedded.complete" : {
+ "languageId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
+ }
+ */
+ connection.telemetry.logEvent({ key: 'html.embbedded.complete', value: { languageId: mode.getId() } });
+ }
+
+ const settings = await getDocumentSettings(document, () => doComplete.length > 2);
+ const documentContext = getDocumentContext(document.uri, workspaceFolders);
+ return doComplete(document, textDocumentPosition.position, documentContext, settings);
+
+ }, null, `Error while computing completions for ${textDocumentPosition.textDocument.uri}`, token);
+ });
+
+ connection.onCompletionResolve((item, token) => {
+ return runSafe(() => {
+ const data = item.data;
+ if (data && data.languageId && data.uri) {
+ const mode = languageModes.getMode(data.languageId);
+ const document = documents.get(data.uri);
+ if (mode && mode.doResolve && document) {
+ return mode.doResolve(document, item);
+ }
+ }
+ return item;
+ }, item, `Error while resolving completion proposal`, token);
+ });
+
+ connection.onHover((textDocumentPosition, token) => {
+ return runSafe(() => {
+ const document = documents.get(textDocumentPosition.textDocument.uri);
+ if (document) {
+ const mode = languageModes.getModeAtPosition(document, textDocumentPosition.position);
+ if (mode && mode.doHover) {
+ return mode.doHover(document, textDocumentPosition.position);
+ }
+ }
+ return null;
+ }, null, `Error while computing hover for ${textDocumentPosition.textDocument.uri}`, token);
+ });
+
+ connection.onDocumentHighlight((documentHighlightParams, token) => {
+ return runSafe(() => {
+ const document = documents.get(documentHighlightParams.textDocument.uri);
+ if (document) {
+ const mode = languageModes.getModeAtPosition(document, documentHighlightParams.position);
+ if (mode && mode.findDocumentHighlight) {
+ return mode.findDocumentHighlight(document, documentHighlightParams.position);
+ }
+ }
+ return [];
+ }, [], `Error while computing document highlights for ${documentHighlightParams.textDocument.uri}`, token);
+ });
+
+ connection.onDefinition((definitionParams, token) => {
+ return runSafe(() => {
+ const document = documents.get(definitionParams.textDocument.uri);
+ if (document) {
+ const mode = languageModes.getModeAtPosition(document, definitionParams.position);
+ if (mode && mode.findDefinition) {
+ return mode.findDefinition(document, definitionParams.position);
+ }
+ }
+ return [];
+ }, null, `Error while computing definitions for ${definitionParams.textDocument.uri}`, token);
+ });
+
+ connection.onReferences((referenceParams, token) => {
+ return runSafe(() => {
+ const document = documents.get(referenceParams.textDocument.uri);
+ if (document) {
+ const mode = languageModes.getModeAtPosition(document, referenceParams.position);
+ if (mode && mode.findReferences) {
+ return mode.findReferences(document, referenceParams.position);
+ }
+ }
+ return [];
+ }, [], `Error while computing references for ${referenceParams.textDocument.uri}`, token);
+ });
+
+ connection.onSignatureHelp((signatureHelpParms, token) => {
+ return runSafe(() => {
+ const document = documents.get(signatureHelpParms.textDocument.uri);
+ if (document) {
+ const mode = languageModes.getModeAtPosition(document, signatureHelpParms.position);
+ if (mode && mode.doSignatureHelp) {
+ return mode.doSignatureHelp(document, signatureHelpParms.position);
+ }
+ }
+ return null;
+ }, null, `Error while computing signature help for ${signatureHelpParms.textDocument.uri}`, token);
+ });
+
+ connection.onDocumentRangeFormatting(async (formatParams, token) => {
+ return runSafeAsync(async () => {
+ const document = documents.get(formatParams.textDocument.uri);
+ if (document) {
+ let settings = await getDocumentSettings(document, () => true);
+ if (!settings) {
+ settings = globalSettings;
+ }
+ const unformattedTags: string = settings && settings.html && settings.html.format && settings.html.format.unformatted || '';
+ const enabledModes = { css: !unformattedTags.match(/\bstyle\b/), javascript: !unformattedTags.match(/\bscript\b/) };
+
+ return format(languageModes, document, formatParams.range, formatParams.options, settings, enabledModes);
+ }
+ return [];
+ }, [], `Error while formatting range for ${formatParams.textDocument.uri}`, token);
+ });
+
+ connection.onDocumentLinks((documentLinkParam, token) => {
+ return runSafe(() => {
+ const document = documents.get(documentLinkParam.textDocument.uri);
+ const links: DocumentLink[] = [];
+ if (document) {
+ const documentContext = getDocumentContext(document.uri, workspaceFolders);
+ languageModes.getAllModesInDocument(document).forEach(m => {
+ if (m.findDocumentLinks) {
+ pushAll(links, m.findDocumentLinks(document, documentContext));
+ }
+ });
+ }
+ return links;
+ }, [], `Error while document links for ${documentLinkParam.textDocument.uri}`, token);
+ });
+
+ connection.onDocumentSymbol((documentSymbolParms, token) => {
+ return runSafe(() => {
+ const document = documents.get(documentSymbolParms.textDocument.uri);
+ const symbols: SymbolInformation[] = [];
+ if (document) {
+ languageModes.getAllModesInDocument(document).forEach(m => {
+ if (m.findDocumentSymbols) {
+ pushAll(symbols, m.findDocumentSymbols(document));
+ }
+ });
+ }
+ return symbols;
+ }, [], `Error while computing document symbols for ${documentSymbolParms.textDocument.uri}`, token);
+ });
+
+ connection.onRequest(DocumentColorRequest.type, (params, token) => {
+ return runSafe(() => {
+ const infos: ColorInformation[] = [];
+ const document = documents.get(params.textDocument.uri);
+ if (document) {
+ languageModes.getAllModesInDocument(document).forEach(m => {
+ if (m.findDocumentColors) {
+ pushAll(infos, m.findDocumentColors(document));
+ }
+ });
+ }
+ return infos;
+ }, [], `Error while computing document colors for ${params.textDocument.uri}`, token);
+ });
+
+ connection.onRequest(ColorPresentationRequest.type, (params, token) => {
+ return runSafe(() => {
+ const document = documents.get(params.textDocument.uri);
+ if (document) {
+ const mode = languageModes.getModeAtPosition(document, params.range.start);
+ if (mode && mode.getColorPresentations) {
+ return mode.getColorPresentations(document, params.color, params.range);
+ }
+ }
+ return [];
+ }, [], `Error while computing color presentations for ${params.textDocument.uri}`, token);
+ });
+
+ connection.onRequest(TagCloseRequest.type, (params, token) => {
+ return runSafe(() => {
+ const document = documents.get(params.textDocument.uri);
+ if (document) {
+ const pos = params.position;
+ if (pos.character > 0) {
+ const mode = languageModes.getModeAtPosition(document, Position.create(pos.line, pos.character - 1));
+ if (mode && mode.doAutoClose) {
+ return mode.doAutoClose(document, pos);
+ }
+ }
+ }
+ return null;
+ }, null, `Error while computing tag close actions for ${params.textDocument.uri}`, token);
+ });
+
+ connection.onFoldingRanges((params, token) => {
+ return runSafe(() => {
+ const document = documents.get(params.textDocument.uri);
+ if (document) {
+ return getFoldingRanges(languageModes, document, foldingRangeLimit, token);
+ }
+ return null;
+ }, null, `Error while computing folding regions for ${params.textDocument.uri}`, token);
+ });
+
+ connection.onSelectionRanges((params, token) => {
+ return runSafe(() => {
+ const document = documents.get(params.textDocument.uri);
+ if (document) {
+ return getSelectionRanges(languageModes, document, params.positions);
+ }
+ return [];
+ }, [], `Error while computing selection ranges for ${params.textDocument.uri}`, token);
+ });
+
+ connection.onRenameRequest((params, token) => {
+ return runSafe(() => {
+ const document = documents.get(params.textDocument.uri);
+ const position: Position = params.position;
+
+ if (document) {
+ const htmlMode = languageModes.getMode('html');
+ if (htmlMode && htmlMode.doRename) {
+ return htmlMode.doRename(document, position, params.newName);
+ }
+ }
+ return null;
+ }, null, `Error while computing rename for ${params.textDocument.uri}`, token);
+ });
+
+ connection.onRequest(OnTypeRenameRequest.type, (params, token) => {
+ return runSafe(() => {
+ const document = documents.get(params.textDocument.uri);
+ if (document) {
+ const pos = params.position;
+ if (pos.character > 0) {
+ const mode = languageModes.getModeAtPosition(document, Position.create(pos.line, pos.character - 1));
+ if (mode && mode.doOnTypeRename) {
+ return mode.doOnTypeRename(document, pos);
+ }
+ }
+ }
+ return null;
+ }, null, `Error while computing synced regions for ${params.textDocument.uri}`, token);
+ });
+
+ let semanticTokensProvider: SemanticTokenProvider | undefined;
+ function getSemanticTokenProvider() {
+ if (!semanticTokensProvider) {
+ semanticTokensProvider = newSemanticTokenProvider(languageModes);
+ }
+ return semanticTokensProvider;
+ }
+
+ connection.onRequest(SemanticTokenRequest.type, (params, token) => {
+ return runSafe(() => {
+ const document = documents.get(params.textDocument.uri);
+ if (document) {
+ return getSemanticTokenProvider().getSemanticTokens(document, params.ranges);
+ }
+ return null;
+ }, null, `Error while computing semantic tokens for ${params.textDocument.uri}`, token);
+ });
+
+ connection.onRequest(SemanticTokenLegendRequest.type, (_params, token) => {
+ return runSafe(() => {
+ return getSemanticTokenProvider().legend;
+ }, null, `Error while computing semantic tokens legend`, token);
+ });
+
+ connection.onNotification(CustomDataChangedNotification.type, dataPaths => {
+ fetchHTMLDataProviders(dataPaths, requestService).then(dataProviders => {
+ languageModes.updateDataProviders(dataProviders);
+ });
+ });
+
+ // Listen on the connection
+ connection.listen();
+}
diff --git a/extensions/html-language-features/server/src/htmlServerMain.ts b/extensions/html-language-features/server/src/htmlServerMain.ts
deleted file mode 100644
index 78385d54719..00000000000
--- a/extensions/html-language-features/server/src/htmlServerMain.ts
+++ /dev/null
@@ -1,544 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-import {
- createConnection, IConnection, TextDocuments, InitializeParams, InitializeResult, RequestType,
- DocumentRangeFormattingRequest, Disposable, DocumentSelector, TextDocumentPositionParams, ServerCapabilities,
- ConfigurationRequest, ConfigurationParams, DidChangeWorkspaceFoldersNotification,
- DocumentColorRequest, ColorPresentationRequest, TextDocumentSyncKind
-} from 'vscode-languageserver';
-import {
- getLanguageModes, LanguageModes, Settings, TextDocument, Position, Diagnostic, WorkspaceFolder, ColorInformation,
- Range, DocumentLink, SymbolInformation, TextDocumentIdentifier
-} from './modes/languageModes';
-
-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 { getFoldingRanges } from './modes/htmlFolding';
-import { getDataProviders } from './customData';
-import { getSelectionRanges } from './modes/selectionRanges';
-import { SemanticTokenProvider, newSemanticTokenProvider } from './modes/semanticTokens';
-
-namespace TagCloseRequest {
- export const type: RequestType = new RequestType('html/tag');
-}
-namespace OnTypeRenameRequest {
- export const type: RequestType = new RequestType('html/onTypeRename');
-}
-
-// experimental: semantic tokens
-interface SemanticTokenParams {
- textDocument: TextDocumentIdentifier;
- ranges?: Range[];
-}
-namespace SemanticTokenRequest {
- export const type: RequestType = new RequestType('html/semanticTokens');
-}
-namespace SemanticTokenLegendRequest {
- export const type: RequestType = new RequestType('html/semanticTokenLegend');
-}
-
-// Create a connection for the server
-const connection: IConnection = createConnection();
-
-console.log = connection.console.log.bind(connection.console);
-console.error = connection.console.error.bind(connection.console);
-
-process.on('unhandledRejection', (e: any) => {
- console.error(formatError(`Unhandled exception`, e));
-});
-process.on('uncaughtException', (e: any) => {
- console.error(formatError(`Unhandled exception`, e));
-});
-
-// Create a text document manager.
-const documents = new TextDocuments(TextDocument);
-// Make the text document manager listen on the connection
-// for open, change and close text document events
-documents.listen(connection);
-
-let workspaceFolders: WorkspaceFolder[] = [];
-
-let languageModes: LanguageModes;
-
-let clientSnippetSupport = false;
-let dynamicFormatterRegistration = false;
-let scopedSettingsSupport = false;
-let workspaceFoldersSupport = false;
-let foldingRangeLimit = Number.MAX_VALUE;
-
-let globalSettings: Settings = {};
-let documentSettings: { [key: string]: Thenable } = {};
-// remove document settings on close
-documents.onDidClose(e => {
- delete documentSettings[e.document.uri];
-});
-
-function getDocumentSettings(textDocument: TextDocument, needsDocumentSettings: () => boolean): Thenable {
- if (scopedSettingsSupport && needsDocumentSettings()) {
- let promise = documentSettings[textDocument.uri];
- if (!promise) {
- const scopeUri = textDocument.uri;
- const configRequestParam: ConfigurationParams = { items: [{ scopeUri, section: 'css' }, { scopeUri, section: 'html' }, { scopeUri, section: 'javascript' }] };
- promise = connection.sendRequest(ConfigurationRequest.type, configRequestParam).then(s => ({ css: s[0], html: s[1], javascript: s[2] }));
- documentSettings[textDocument.uri] = promise;
- }
- return promise;
- }
- return Promise.resolve(undefined);
-}
-
-// After the server has started the client sends an initialize request. The server receives
-// in the passed params the rootPath of the workspace plus the client capabilities
-connection.onInitialize((params: InitializeParams): InitializeResult => {
- const initializationOptions = params.initializationOptions;
-
- workspaceFolders = (params).workspaceFolders;
- if (!Array.isArray(workspaceFolders)) {
- workspaceFolders = [];
- if (params.rootPath) {
- workspaceFolders.push({ name: '', uri: URI.file(params.rootPath).toString() });
- }
- }
-
- const dataPaths: string[] = params.initializationOptions.dataPaths;
- const providers = getDataProviders(dataPaths);
-
- const workspace = {
- get settings() { return globalSettings; },
- get folders() { return workspaceFolders; }
- };
-
- languageModes = getLanguageModes(initializationOptions ? initializationOptions.embeddedLanguages : { css: true, javascript: true }, workspace, params.capabilities, providers);
-
- documents.onDidClose(e => {
- languageModes.onDocumentRemoved(e.document);
- });
- connection.onShutdown(() => {
- languageModes.dispose();
- });
-
- function getClientCapability(name: string, def: T) {
- const keys = name.split('.');
- let c: any = params.capabilities;
- for (let i = 0; c && i < keys.length; i++) {
- if (!c.hasOwnProperty(keys[i])) {
- return def;
- }
- c = c[keys[i]];
- }
- return c;
- }
-
- clientSnippetSupport = getClientCapability('textDocument.completion.completionItem.snippetSupport', false);
- dynamicFormatterRegistration = getClientCapability('textDocument.rangeFormatting.dynamicRegistration', false) && (typeof params.initializationOptions.provideFormatter !== 'boolean');
- scopedSettingsSupport = getClientCapability('workspace.configuration', false);
- workspaceFoldersSupport = getClientCapability('workspace.workspaceFolders', false);
- foldingRangeLimit = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE);
- const capabilities: ServerCapabilities = {
- textDocumentSync: TextDocumentSyncKind.Incremental,
- completionProvider: clientSnippetSupport ? { resolveProvider: true, triggerCharacters: ['.', ':', '<', '"', '=', '/'] } : undefined,
- hoverProvider: true,
- documentHighlightProvider: true,
- documentRangeFormattingProvider: params.initializationOptions.provideFormatter === true,
- documentLinkProvider: { resolveProvider: false },
- documentSymbolProvider: true,
- definitionProvider: true,
- signatureHelpProvider: { triggerCharacters: ['('] },
- referencesProvider: true,
- colorProvider: {},
- foldingRangeProvider: true,
- selectionRangeProvider: true,
- renameProvider: true
- };
- return { capabilities };
-});
-
-connection.onInitialized(() => {
- if (workspaceFoldersSupport) {
- connection.client.register(DidChangeWorkspaceFoldersNotification.type);
-
- connection.onNotification(DidChangeWorkspaceFoldersNotification.type, e => {
- const toAdd = e.event.added;
- const toRemove = e.event.removed;
- const updatedFolders = [];
- if (workspaceFolders) {
- for (const folder of workspaceFolders) {
- if (!toRemove.some(r => r.uri === folder.uri) && !toAdd.some(r => r.uri === folder.uri)) {
- updatedFolders.push(folder);
- }
- }
- }
- workspaceFolders = updatedFolders.concat(toAdd);
- documents.all().forEach(triggerValidation);
- });
- }
-});
-
-let formatterRegistration: Thenable | null = null;
-
-// The settings have changed. Is send on server activation as well.
-connection.onDidChangeConfiguration((change) => {
- globalSettings = change.settings;
- documentSettings = {}; // reset all document settings
- documents.all().forEach(triggerValidation);
-
- // dynamically enable & disable the formatter
- if (dynamicFormatterRegistration) {
- const enableFormatter = globalSettings && globalSettings.html && globalSettings.html.format && globalSettings.html.format.enable;
- if (enableFormatter) {
- if (!formatterRegistration) {
- const documentSelector: DocumentSelector = [{ language: 'html' }, { language: 'handlebars' }];
- formatterRegistration = connection.client.register(DocumentRangeFormattingRequest.type, { documentSelector });
- }
- } else if (formatterRegistration) {
- formatterRegistration.then(r => r.dispose());
- formatterRegistration = null;
- }
- }
-});
-
-const pendingValidationRequests: { [uri: string]: NodeJS.Timer } = {};
-const validationDelayMs = 500;
-
-// The content of a text document has changed. This event is emitted
-// when the text document first opened or when its content has changed.
-documents.onDidChangeContent(change => {
- triggerValidation(change.document);
-});
-
-// a document has closed: clear all diagnostics
-documents.onDidClose(event => {
- cleanPendingValidation(event.document);
- connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] });
-});
-
-function cleanPendingValidation(textDocument: TextDocument): void {
- const request = pendingValidationRequests[textDocument.uri];
- if (request) {
- clearTimeout(request);
- delete pendingValidationRequests[textDocument.uri];
- }
-}
-
-function triggerValidation(textDocument: TextDocument): void {
- cleanPendingValidation(textDocument);
- pendingValidationRequests[textDocument.uri] = setTimeout(() => {
- delete pendingValidationRequests[textDocument.uri];
- validateTextDocument(textDocument);
- }, validationDelayMs);
-}
-
-function isValidationEnabled(languageId: string, settings: Settings = globalSettings) {
- const 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) {
- try {
- const version = textDocument.version;
- const diagnostics: Diagnostic[] = [];
- if (textDocument.languageId === 'html') {
- const modes = languageModes.getAllModesInDocument(textDocument);
- 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 => {
- if (mode.doValidation && isValidationEnabled(mode.getId(), settings)) {
- pushAll(diagnostics, mode.doValidation(latestTextDocument, settings));
- }
- });
- connection.sendDiagnostics({ uri: latestTextDocument.uri, diagnostics });
- }
- }
- } catch (e) {
- connection.console.error(formatError(`Error while validating ${textDocument.uri}`, e));
- }
-}
-
-connection.onCompletion(async (textDocumentPosition, token) => {
- return runSafeAsync(async () => {
- const document = documents.get(textDocumentPosition.textDocument.uri);
- if (!document) {
- return null;
- }
- const mode = languageModes.getModeAtPosition(document, textDocumentPosition.position);
- if (!mode || !mode.doComplete) {
- return { isIncomplete: true, items: [] };
- }
- const doComplete = mode.doComplete!;
-
- if (mode.getId() !== 'html') {
- /* __GDPR__
- "html.embbedded.complete" : {
- "languageId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
- }
- */
- connection.telemetry.logEvent({ key: 'html.embbedded.complete', value: { languageId: mode.getId() } });
- }
-
- const settings = await getDocumentSettings(document, () => doComplete.length > 2);
- const result = doComplete(document, textDocumentPosition.position, settings);
- return result;
-
- }, null, `Error while computing completions for ${textDocumentPosition.textDocument.uri}`, token);
-});
-
-connection.onCompletionResolve((item, token) => {
- return runSafe(() => {
- const data = item.data;
- if (data && data.languageId && data.uri) {
- const mode = languageModes.getMode(data.languageId);
- const document = documents.get(data.uri);
- if (mode && mode.doResolve && document) {
- return mode.doResolve(document, item);
- }
- }
- return item;
- }, item, `Error while resolving completion proposal`, token);
-});
-
-connection.onHover((textDocumentPosition, token) => {
- return runSafe(() => {
- const document = documents.get(textDocumentPosition.textDocument.uri);
- if (document) {
- const mode = languageModes.getModeAtPosition(document, textDocumentPosition.position);
- if (mode && mode.doHover) {
- return mode.doHover(document, textDocumentPosition.position);
- }
- }
- return null;
- }, null, `Error while computing hover for ${textDocumentPosition.textDocument.uri}`, token);
-});
-
-connection.onDocumentHighlight((documentHighlightParams, token) => {
- return runSafe(() => {
- const document = documents.get(documentHighlightParams.textDocument.uri);
- if (document) {
- const mode = languageModes.getModeAtPosition(document, documentHighlightParams.position);
- if (mode && mode.findDocumentHighlight) {
- return mode.findDocumentHighlight(document, documentHighlightParams.position);
- }
- }
- return [];
- }, [], `Error while computing document highlights for ${documentHighlightParams.textDocument.uri}`, token);
-});
-
-connection.onDefinition((definitionParams, token) => {
- return runSafe(() => {
- const document = documents.get(definitionParams.textDocument.uri);
- if (document) {
- const mode = languageModes.getModeAtPosition(document, definitionParams.position);
- if (mode && mode.findDefinition) {
- return mode.findDefinition(document, definitionParams.position);
- }
- }
- return [];
- }, null, `Error while computing definitions for ${definitionParams.textDocument.uri}`, token);
-});
-
-connection.onReferences((referenceParams, token) => {
- return runSafe(() => {
- const document = documents.get(referenceParams.textDocument.uri);
- if (document) {
- const mode = languageModes.getModeAtPosition(document, referenceParams.position);
- if (mode && mode.findReferences) {
- return mode.findReferences(document, referenceParams.position);
- }
- }
- return [];
- }, [], `Error while computing references for ${referenceParams.textDocument.uri}`, token);
-});
-
-connection.onSignatureHelp((signatureHelpParms, token) => {
- return runSafe(() => {
- const document = documents.get(signatureHelpParms.textDocument.uri);
- if (document) {
- const mode = languageModes.getModeAtPosition(document, signatureHelpParms.position);
- if (mode && mode.doSignatureHelp) {
- return mode.doSignatureHelp(document, signatureHelpParms.position);
- }
- }
- return null;
- }, null, `Error while computing signature help for ${signatureHelpParms.textDocument.uri}`, token);
-});
-
-connection.onDocumentRangeFormatting(async (formatParams, token) => {
- return runSafeAsync(async () => {
- const document = documents.get(formatParams.textDocument.uri);
- if (document) {
- let settings = await getDocumentSettings(document, () => true);
- if (!settings) {
- settings = globalSettings;
- }
- const unformattedTags: string = settings && settings.html && settings.html.format && settings.html.format.unformatted || '';
- const enabledModes = { css: !unformattedTags.match(/\bstyle\b/), javascript: !unformattedTags.match(/\bscript\b/) };
-
- return format(languageModes, document, formatParams.range, formatParams.options, settings, enabledModes);
- }
- return [];
- }, [], `Error while formatting range for ${formatParams.textDocument.uri}`, token);
-});
-
-connection.onDocumentLinks((documentLinkParam, token) => {
- return runSafe(() => {
- const document = documents.get(documentLinkParam.textDocument.uri);
- const links: DocumentLink[] = [];
- if (document) {
- const documentContext = getDocumentContext(document.uri, workspaceFolders);
- languageModes.getAllModesInDocument(document).forEach(m => {
- if (m.findDocumentLinks) {
- pushAll(links, m.findDocumentLinks(document, documentContext));
- }
- });
- }
- return links;
- }, [], `Error while document links for ${documentLinkParam.textDocument.uri}`, token);
-});
-
-connection.onDocumentSymbol((documentSymbolParms, token) => {
- return runSafe(() => {
- const document = documents.get(documentSymbolParms.textDocument.uri);
- const symbols: SymbolInformation[] = [];
- if (document) {
- languageModes.getAllModesInDocument(document).forEach(m => {
- if (m.findDocumentSymbols) {
- pushAll(symbols, m.findDocumentSymbols(document));
- }
- });
- }
- return symbols;
- }, [], `Error while computing document symbols for ${documentSymbolParms.textDocument.uri}`, token);
-});
-
-connection.onRequest(DocumentColorRequest.type, (params, token) => {
- return runSafe(() => {
- const infos: ColorInformation[] = [];
- const document = documents.get(params.textDocument.uri);
- if (document) {
- languageModes.getAllModesInDocument(document).forEach(m => {
- if (m.findDocumentColors) {
- pushAll(infos, m.findDocumentColors(document));
- }
- });
- }
- return infos;
- }, [], `Error while computing document colors for ${params.textDocument.uri}`, token);
-});
-
-connection.onRequest(ColorPresentationRequest.type, (params, token) => {
- return runSafe(() => {
- const document = documents.get(params.textDocument.uri);
- if (document) {
- const mode = languageModes.getModeAtPosition(document, params.range.start);
- if (mode && mode.getColorPresentations) {
- return mode.getColorPresentations(document, params.color, params.range);
- }
- }
- return [];
- }, [], `Error while computing color presentations for ${params.textDocument.uri}`, token);
-});
-
-connection.onRequest(TagCloseRequest.type, (params, token) => {
- return runSafe(() => {
- const document = documents.get(params.textDocument.uri);
- if (document) {
- const pos = params.position;
- if (pos.character > 0) {
- const mode = languageModes.getModeAtPosition(document, Position.create(pos.line, pos.character - 1));
- if (mode && mode.doAutoClose) {
- return mode.doAutoClose(document, pos);
- }
- }
- }
- return null;
- }, null, `Error while computing tag close actions for ${params.textDocument.uri}`, token);
-});
-
-connection.onFoldingRanges((params, token) => {
- return runSafe(() => {
- const document = documents.get(params.textDocument.uri);
- if (document) {
- return getFoldingRanges(languageModes, document, foldingRangeLimit, token);
- }
- return null;
- }, null, `Error while computing folding regions for ${params.textDocument.uri}`, token);
-});
-
-connection.onSelectionRanges((params, token) => {
- return runSafe(() => {
- const document = documents.get(params.textDocument.uri);
- if (document) {
- return getSelectionRanges(languageModes, document, params.positions);
- }
- return [];
- }, [], `Error while computing selection ranges for ${params.textDocument.uri}`, token);
-});
-
-connection.onRenameRequest((params, token) => {
- return runSafe(() => {
- const document = documents.get(params.textDocument.uri);
- const position: Position = params.position;
-
- if (document) {
- const htmlMode = languageModes.getMode('html');
- if (htmlMode && htmlMode.doRename) {
- return htmlMode.doRename(document, position, params.newName);
- }
- }
- return null;
- }, null, `Error while computing rename for ${params.textDocument.uri}`, token);
-});
-
-connection.onRequest(OnTypeRenameRequest.type, (params, token) => {
- return runSafe(() => {
- const document = documents.get(params.textDocument.uri);
- if (document) {
- const pos = params.position;
- if (pos.character > 0) {
- const mode = languageModes.getModeAtPosition(document, Position.create(pos.line, pos.character - 1));
- if (mode && mode.doOnTypeRename) {
- return mode.doOnTypeRename(document, pos);
- }
- }
- }
- return null;
- }, null, `Error while computing synced regions for ${params.textDocument.uri}`, token);
-});
-
-let semanticTokensProvider: SemanticTokenProvider | undefined;
-function getSemanticTokenProvider() {
- if (!semanticTokensProvider) {
- semanticTokensProvider = newSemanticTokenProvider(languageModes);
- }
- return semanticTokensProvider;
-}
-
-connection.onRequest(SemanticTokenRequest.type, (params, token) => {
- return runSafe(() => {
- const document = documents.get(params.textDocument.uri);
- if (document) {
- return getSemanticTokenProvider().getSemanticTokens(document, params.ranges);
- }
- return null;
- }, null, `Error while computing semantic tokens for ${params.textDocument.uri}`, token);
-});
-
-connection.onRequest(SemanticTokenLegendRequest.type, (_params, token) => {
- return runSafe(() => {
- return getSemanticTokenProvider().legend;
- }, null, `Error while computing semantic tokens legend`, token);
-});
-
-
-// Listen on the connection
-connection.listen();
diff --git a/extensions/html-language-features/server/src/modes/cssMode.ts b/extensions/html-language-features/server/src/modes/cssMode.ts
index e705f44bbab..dafc79f0feb 100644
--- a/extensions/html-language-features/server/src/modes/cssMode.ts
+++ b/extensions/html-language-features/server/src/modes/cssMode.ts
@@ -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 } from './languageModes';
+import { FoldingRange, LanguageMode, Workspace, Color, TextDocument, Position, Range, CompletionList, DocumentContext } from './languageModes';
import { HTMLDocumentRegions, CSS_STYLE_RULE } from './embeddedSupport';
export function getCSSMode(cssLanguageService: CSSLanguageService, documentRegions: LanguageModelCache, workspace: Workspace): LanguageMode {
@@ -20,10 +20,10 @@ export function getCSSMode(cssLanguageService: CSSLanguageService, documentRegio
let embedded = embeddedCSSDocuments.get(document);
return cssLanguageService.doValidation(embedded, cssStylesheets.get(embedded), settings && settings.css);
},
- doComplete(document: TextDocument, position: Position, _settings = workspace.settings) {
+ doComplete(document: TextDocument, position: Position, documentContext: DocumentContext, _settings = workspace.settings) {
let embedded = embeddedCSSDocuments.get(document);
const stylesheet = cssStylesheets.get(embedded);
- return cssLanguageService.doComplete(embedded, position, stylesheet) || CompletionList.create();
+ return cssLanguageService.doComplete2(embedded, position, stylesheet, documentContext) || CompletionList.create();
},
doHover(document: TextDocument, position: Position) {
let embedded = embeddedCSSDocuments.get(document);
diff --git a/extensions/html-language-features/server/src/modes/htmlMode.ts b/extensions/html-language-features/server/src/modes/htmlMode.ts
index 4a3be8244a4..8faf51e15fb 100644
--- a/extensions/html-language-features/server/src/modes/htmlMode.ts
+++ b/extensions/html-language-features/server/src/modes/htmlMode.ts
@@ -7,10 +7,9 @@ import { getLanguageModelCache } from '../languageModelCache';
import {
LanguageService as HTMLLanguageService, HTMLDocument, DocumentContext, FormattingOptions,
HTMLFormatConfiguration, SelectionRange,
- TextDocument, Position, Range, CompletionItem, FoldingRange,
+ TextDocument, Position, Range, FoldingRange,
LanguageMode, Workspace
} from './languageModes';
-import { getPathCompletionParticipant } from './pathCompletion';
export function getHTMLMode(htmlLanguageService: HTMLLanguageService, workspace: Workspace): LanguageMode {
let htmlDocuments = getLanguageModelCache(10, 60, document => htmlLanguageService.parseHTMLDocument(document));
@@ -21,19 +20,15 @@ export function getHTMLMode(htmlLanguageService: HTMLLanguageService, workspace:
getSelectionRange(document: TextDocument, position: Position): SelectionRange {
return htmlLanguageService.getSelectionRanges(document, [position])[0];
},
- doComplete(document: TextDocument, position: Position, settings = workspace.settings) {
+ doComplete(document: TextDocument, position: Position, documentContext: DocumentContext, settings = workspace.settings) {
let options = settings && settings.html && settings.html.suggest;
let doAutoComplete = settings && settings.html && settings.html.autoClosingTags;
if (doAutoComplete) {
options.hideAutoCompleteProposals = true;
}
- let pathCompletionProposals: CompletionItem[] = [];
- let participants = [getPathCompletionParticipant(document, workspace.folders, pathCompletionProposals)];
- htmlLanguageService.setCompletionParticipants(participants);
const htmlDocument = htmlDocuments.get(document);
- let completionList = htmlLanguageService.doComplete(document, position, htmlDocument, options);
- completionList.items.push(...pathCompletionProposals);
+ let completionList = htmlLanguageService.doComplete2(document, position, htmlDocument, documentContext, options);
return completionList;
},
doHover(document: TextDocument, position: Position) {
diff --git a/extensions/html-language-features/server/src/modes/javascriptMode.ts b/extensions/html-language-features/server/src/modes/javascriptMode.ts
index 9841c7167ec..789fb4b6332 100644
--- a/extensions/html-language-features/server/src/modes/javascriptMode.ts
+++ b/extensions/html-language-features/server/src/modes/javascriptMode.ts
@@ -8,20 +8,21 @@ import {
SymbolInformation, SymbolKind, CompletionItem, Location, SignatureHelp, SignatureInformation, ParameterInformation,
Definition, TextEdit, TextDocument, Diagnostic, DiagnosticSeverity, Range, CompletionItemKind, Hover, MarkedString,
DocumentHighlight, DocumentHighlightKind, CompletionList, Position, FormattingOptions, FoldingRange, FoldingRangeKind, SelectionRange,
- LanguageMode, Settings, SemanticTokenData, Workspace
+ LanguageMode, Settings, SemanticTokenData, Workspace, DocumentContext
} from './languageModes';
import { getWordAtText, startsWith, isWhitespaceOnly, repeat } from '../utils/strings';
import { HTMLDocumentRegions } from './embeddedSupport';
import * as ts from 'typescript';
-import { join } from 'path';
import { getSemanticTokens, getSemanticTokenLegend } from './javascriptSemanticTokens';
+import { joinPath } from '../requests';
+
const JS_WORD_REGEX = /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g;
-let jquery_d_ts = join(__dirname, '../lib/jquery.d.ts'); // when packaged
+let jquery_d_ts = joinPath(__dirname, '../lib/jquery.d.ts'); // when packaged
if (!ts.sys.fileExists(jquery_d_ts)) {
- jquery_d_ts = join(__dirname, '../../lib/jquery.d.ts'); // from source
+ jquery_d_ts = joinPath(__dirname, '../../lib/jquery.d.ts'); // from source
}
export function getJavaScriptMode(documentRegions: LanguageModelCache, languageId: 'javascript' | 'typescript', workspace: Workspace): LanguageMode {
@@ -64,7 +65,8 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache '',
- getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options)
+ getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options),
+
};
let jsLanguageService = ts.createLanguageService(host);
@@ -88,7 +90,7 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache {
updateCurrentTextDocument(document);
let offset = currentTextDocument.offsetAt(position);
let completions = jsLanguageService.getCompletionsAtPosition(workingFile, offset, { includeExternalModuleExports: false, includeInsertTextCompletions: false });
diff --git a/extensions/html-language-features/server/src/modes/languageModes.ts b/extensions/html-language-features/server/src/modes/languageModes.ts
index a69a2516dcf..69ec4ce7a2a 100644
--- a/extensions/html-language-features/server/src/modes/languageModes.ts
+++ b/extensions/html-language-features/server/src/modes/languageModes.ts
@@ -16,6 +16,7 @@ import { getCSSMode } from './cssMode';
import { getDocumentRegions, HTMLDocumentRegions } from './embeddedSupport';
import { getHTMLMode } from './htmlMode';
import { getJavaScriptMode } from './javascriptMode';
+import { RequestService } from '../requests';
export * from 'vscode-html-languageservice';
export { WorkspaceFolder } from 'vscode-languageserver';
@@ -42,7 +43,7 @@ export interface LanguageMode {
getId(): string;
getSelectionRange?: (document: TextDocument, position: Position) => SelectionRange;
doValidation?: (document: TextDocument, settings?: Settings) => Diagnostic[];
- doComplete?: (document: TextDocument, position: Position, settings?: Settings) => CompletionList;
+ doComplete?: (document: TextDocument, position: Position, documentContext: DocumentContext, settings?: Settings) => Promise;
doResolve?: (document: TextDocument, item: CompletionItem) => CompletionItem;
doHover?: (document: TextDocument, position: Position) => Hover | null;
doSignatureHelp?: (document: TextDocument, position: Position) => SignatureHelp | null;
@@ -66,6 +67,7 @@ export interface LanguageMode {
}
export interface LanguageModes {
+ updateDataProviders(dataProviders: IHTMLDataProvider[]): void;
getModeAtPosition(document: TextDocument, position: Position): LanguageMode | undefined;
getModesInRange(document: TextDocument, range: Range): LanguageModeRange[];
getAllModes(): LanguageMode[];
@@ -80,9 +82,9 @@ export interface LanguageModeRange extends Range {
attributeValue?: boolean;
}
-export function getLanguageModes(supportedLanguages: { [languageId: string]: boolean; }, workspace: Workspace, clientCapabilities: ClientCapabilities, customDataProviders?: IHTMLDataProvider[]): LanguageModes {
- const htmlLanguageService = getHTMLLanguageService({ customDataProviders, clientCapabilities });
- const cssLanguageService = getCSSLanguageService({ clientCapabilities });
+export function getLanguageModes(supportedLanguages: { [languageId: string]: boolean; }, workspace: Workspace, clientCapabilities: ClientCapabilities, requestService: RequestService): LanguageModes {
+ const htmlLanguageService = getHTMLLanguageService({ clientCapabilities, fileSystemProvider: requestService });
+ const cssLanguageService = getCSSLanguageService({ clientCapabilities, fileSystemProvider: requestService });
let documentRegions = getLanguageModelCache(10, 60, document => getDocumentRegions(htmlLanguageService, document));
@@ -99,6 +101,9 @@ export function getLanguageModes(supportedLanguages: { [languageId: string]: boo
modes['typescript'] = getJavaScriptMode(documentRegions, 'typescript', workspace);
}
return {
+ async updateDataProviders(dataProviders: IHTMLDataProvider[]): Promise {
+ htmlLanguageService.setDataProviders(true, dataProviders);
+ },
getModeAtPosition(document: TextDocument, position: Position): LanguageMode | undefined {
let languageId = documentRegions.get(document).getLanguageAtPosition(position);
if (languageId) {
diff --git a/extensions/html-language-features/server/src/modes/pathCompletion.ts b/extensions/html-language-features/server/src/modes/pathCompletion.ts
deleted file mode 100644
index d522efdc0df..00000000000
--- a/extensions/html-language-features/server/src/modes/pathCompletion.ts
+++ /dev/null
@@ -1,183 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-import * as path from 'path';
-import * as fs from 'fs';
-import { URI } from 'vscode-uri';
-import { ICompletionParticipant, TextDocument, CompletionItemKind, CompletionItem, TextEdit, Range, Position, WorkspaceFolder } from './languageModes';
-import { startsWith } from '../utils/strings';
-import { contains } from '../utils/arrays';
-
-export function getPathCompletionParticipant(
- document: TextDocument,
- workspaceFolders: WorkspaceFolder[],
- result: CompletionItem[]
-): ICompletionParticipant {
- return {
- onHtmlAttributeValue: ({ tag, attribute, value: valueBeforeCursor, range }) => {
- const fullValue = stripQuotes(document.getText(range));
-
- if (shouldDoPathCompletion(tag, attribute, fullValue)) {
- if (workspaceFolders.length === 0) {
- return;
- }
- const workspaceRoot = resolveWorkspaceRoot(document, workspaceFolders);
-
- const paths = providePaths(valueBeforeCursor, URI.parse(document.uri).fsPath, workspaceRoot);
- result.push(...paths.map(p => pathToSuggestion(p, valueBeforeCursor, fullValue, range)));
- }
- }
- };
-}
-
-function stripQuotes(fullValue: string) {
- if (startsWith(fullValue, `'`) || startsWith(fullValue, `"`)) {
- return fullValue.slice(1, -1);
- } else {
- return fullValue;
- }
-}
-
-function shouldDoPathCompletion(tag: string, attr: string, value: string) {
- if (startsWith(value, 'http') || startsWith(value, 'https') || startsWith(value, '//')) {
- return false;
- }
-
- if (PATH_TAG_AND_ATTR[tag]) {
- if (typeof PATH_TAG_AND_ATTR[tag] === 'string') {
- return PATH_TAG_AND_ATTR[tag] === attr;
- } else {
- return contains(PATH_TAG_AND_ATTR[tag], attr);
- }
- }
-
- return false;
-}
-
-/**
- * Get a list of path suggestions. Folder suggestions are suffixed with a slash.
- */
-function providePaths(valueBeforeCursor: string, activeDocFsPath: string, root?: string): string[] {
- const lastIndexOfSlash = valueBeforeCursor.lastIndexOf('/');
- const valueBeforeLastSlash = valueBeforeCursor.slice(0, lastIndexOfSlash + 1);
-
- const startsWithSlash = startsWith(valueBeforeCursor, '/');
- let parentDir: string;
- if (startsWithSlash) {
- if (!root) {
- return [];
- }
- parentDir = path.resolve(root, '.' + valueBeforeLastSlash);
- } else {
- parentDir = path.resolve(activeDocFsPath, '..', valueBeforeLastSlash);
- }
-
- try {
- const paths = fs.readdirSync(parentDir).map(f => {
- return isDir(path.resolve(parentDir, f))
- ? f + '/'
- : f;
- });
- return paths.filter(p => p[0] !== '.');
- } catch (e) {
- return [];
- }
-}
-
-function isDir(p: string) {
- try {
- return fs.statSync(p).isDirectory();
- } catch (e) {
- return false;
- }
-}
-
-function pathToSuggestion(p: string, valueBeforeCursor: string, fullValue: string, range: Range): CompletionItem {
- const isDir = p[p.length - 1] === '/';
-
- let replaceRange: Range;
- const lastIndexOfSlash = valueBeforeCursor.lastIndexOf('/');
- if (lastIndexOfSlash === -1) {
- replaceRange = shiftRange(range, 1, -1);
- } else {
- // For cases where cursor is in the middle of attribute value, like