mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-20 00:28:52 +01:00
[html] split extension (for #45900)
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export const EMPTY_ELEMENTS: string[] = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr'];
|
||||
215
extensions/html-language-features/client/src/htmlMain.ts
Normal file
215
extensions/html-language-features/client/src/htmlMain.ts
Normal file
@@ -0,0 +1,215 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as path from 'path';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { languages, ExtensionContext, IndentAction, Position, TextDocument, Range, CompletionItem, CompletionItemKind, SnippetString, FoldingRangeList, FoldingRange, workspace, FoldingContext } from 'vscode';
|
||||
import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, RequestType, TextDocumentPositionParams, Disposable, CancellationToken } from 'vscode-languageclient';
|
||||
import { EMPTY_ELEMENTS } from './htmlEmptyTagsShared';
|
||||
import { activateTagClosing } from './tagClosing';
|
||||
import TelemetryReporter from 'vscode-extension-telemetry';
|
||||
|
||||
import { FoldingRangesRequest, FoldingRangeRequestParam } from './protocol/foldingProvider.proposed';
|
||||
|
||||
namespace TagCloseRequest {
|
||||
export const type: RequestType<TextDocumentPositionParams, string, any, any> = new RequestType('html/tag');
|
||||
}
|
||||
|
||||
interface IPackageInfo {
|
||||
name: string;
|
||||
version: string;
|
||||
aiKey: string;
|
||||
}
|
||||
|
||||
let telemetryReporter: TelemetryReporter | null;
|
||||
|
||||
let foldingProviderRegistration: Disposable | undefined = void 0;
|
||||
const foldingSetting = 'html.experimental.syntaxFolding';
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
let toDispose = context.subscriptions;
|
||||
|
||||
let packageInfo = getPackageInfo(context);
|
||||
telemetryReporter = packageInfo && new TelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey);
|
||||
|
||||
// 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', '--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', 'razor'];
|
||||
let embeddedLanguages = { css: true, javascript: true };
|
||||
|
||||
// Options to control the language client
|
||||
let clientOptions: LanguageClientOptions = {
|
||||
documentSelector,
|
||||
synchronize: {
|
||||
configurationSection: ['html', 'css', 'javascript', 'emmet'], // the settings to synchronize
|
||||
},
|
||||
initializationOptions: {
|
||||
embeddedLanguages
|
||||
}
|
||||
};
|
||||
|
||||
// Create the language client and start the client.
|
||||
let client = new LanguageClient('html', localize('htmlserver.name', 'HTML Language Server'), serverOptions, clientOptions);
|
||||
client.registerProposedFeatures();
|
||||
|
||||
let disposable = client.start();
|
||||
toDispose.push(disposable);
|
||||
client.onReady().then(() => {
|
||||
let tagRequestor = (document: TextDocument, position: Position) => {
|
||||
let param = client.code2ProtocolConverter.asTextDocumentPositionParams(document, position);
|
||||
return client.sendRequest(TagCloseRequest.type, param);
|
||||
};
|
||||
disposable = activateTagClosing(tagRequestor, { html: true, handlebars: true, razor: true }, 'html.autoClosingTags');
|
||||
toDispose.push(disposable);
|
||||
|
||||
disposable = client.onTelemetry(e => {
|
||||
if (telemetryReporter) {
|
||||
telemetryReporter.sendTelemetryEvent(e.key, e.data);
|
||||
}
|
||||
});
|
||||
toDispose.push(disposable);
|
||||
|
||||
initFoldingProvider();
|
||||
toDispose.push(workspace.onDidChangeConfiguration(c => {
|
||||
if (c.affectsConfiguration(foldingSetting)) {
|
||||
initFoldingProvider();
|
||||
}
|
||||
}));
|
||||
toDispose.push({ dispose: () => foldingProviderRegistration && foldingProviderRegistration.dispose() });
|
||||
});
|
||||
|
||||
languages.setLanguageConfiguration('html', {
|
||||
indentationRules: {
|
||||
increaseIndentPattern: /<(?!\?|(?:area|base|br|col|frame|hr|html|img|input|link|meta|param)\b|[^>]*\/>)([-_\.A-Za-z0-9]+)(?=\s|>)\b[^>]*>(?!.*<\/\1>)|<!--(?!.*-->)|\{[^}"']*$/,
|
||||
decreaseIndentPattern: /^\s*(<\/(?!html)[-_\.A-Za-z0-9]+\b[^>]*>|-->|\})/
|
||||
},
|
||||
wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\$\^\&\*\(\)\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\s]+)/g,
|
||||
onEnterRules: [
|
||||
{
|
||||
beforeText: new RegExp(`<(?!(?:${EMPTY_ELEMENTS.join('|')}))([_:\\w][_:\\w-.\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'),
|
||||
afterText: /^<\/([_:\w][_:\w-.\d]*)\s*>/i,
|
||||
action: { indentAction: IndentAction.IndentOutdent }
|
||||
},
|
||||
{
|
||||
beforeText: new RegExp(`<(?!(?:${EMPTY_ELEMENTS.join('|')}))(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'),
|
||||
action: { indentAction: IndentAction.Indent }
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
languages.setLanguageConfiguration('handlebars', {
|
||||
wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\$\^\&\*\(\)\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\s]+)/g,
|
||||
onEnterRules: [
|
||||
{
|
||||
beforeText: new RegExp(`<(?!(?:${EMPTY_ELEMENTS.join('|')}))([_:\\w][_:\\w-.\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'),
|
||||
afterText: /^<\/([_:\w][_:\w-.\d]*)\s*>/i,
|
||||
action: { indentAction: IndentAction.IndentOutdent }
|
||||
},
|
||||
{
|
||||
beforeText: new RegExp(`<(?!(?:${EMPTY_ELEMENTS.join('|')}))(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'),
|
||||
action: { indentAction: IndentAction.Indent }
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
languages.setLanguageConfiguration('razor', {
|
||||
wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\$\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\s]+)/g,
|
||||
onEnterRules: [
|
||||
{
|
||||
beforeText: new RegExp(`<(?!(?:${EMPTY_ELEMENTS.join('|')}))([_:\\w][_:\\w-.\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'),
|
||||
afterText: /^<\/([_:\w][_:\w-.\d]*)\s*>/i,
|
||||
action: { indentAction: IndentAction.IndentOutdent }
|
||||
},
|
||||
{
|
||||
beforeText: new RegExp(`<(?!(?:${EMPTY_ELEMENTS.join('|')}))(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'),
|
||||
action: { indentAction: IndentAction.Indent }
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
const regionCompletionRegExpr = /^(\s*)(<(!(-(-\s*(#\w*)?)?)?)?)?$/;
|
||||
languages.registerCompletionItemProvider(documentSelector, {
|
||||
provideCompletionItems(doc, pos) {
|
||||
let lineUntilPos = doc.getText(new Range(new Position(pos.line, 0), pos));
|
||||
let match = lineUntilPos.match(regionCompletionRegExpr);
|
||||
if (match) {
|
||||
let range = new Range(new Position(pos.line, match[1].length), pos);
|
||||
let beginProposal = new CompletionItem('#region', CompletionItemKind.Snippet);
|
||||
beginProposal.range = range;
|
||||
beginProposal.insertText = new SnippetString('<!-- #region $1-->');
|
||||
beginProposal.documentation = localize('folding.start', 'Folding Region Start');
|
||||
beginProposal.filterText = match[2];
|
||||
beginProposal.sortText = 'za';
|
||||
let endProposal = new CompletionItem('#endregion', CompletionItemKind.Snippet);
|
||||
endProposal.range = range;
|
||||
endProposal.insertText = new SnippetString('<!-- #endregion -->');
|
||||
endProposal.documentation = localize('folding.end', 'Folding Region End');
|
||||
endProposal.filterText = match[2];
|
||||
endProposal.sortText = 'zb';
|
||||
return [beginProposal, endProposal];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
function initFoldingProvider() {
|
||||
let enable = workspace.getConfiguration().get(foldingSetting);
|
||||
if (enable) {
|
||||
if (!foldingProviderRegistration) {
|
||||
foldingProviderRegistration = languages.registerFoldingProvider(documentSelector, {
|
||||
provideFoldingRanges(document: TextDocument, context: FoldingContext, token: CancellationToken) {
|
||||
const param: FoldingRangeRequestParam = {
|
||||
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),
|
||||
maxRanges: context.maxRanges
|
||||
};
|
||||
return client.sendRequest(FoldingRangesRequest.type, param, token).then(res => {
|
||||
if (res && Array.isArray(res.ranges)) {
|
||||
return new FoldingRangeList(res.ranges.map(r => new FoldingRange(r.startLine, r.endLine, r.type)));
|
||||
}
|
||||
return null;
|
||||
}, error => {
|
||||
client.logFailedRequest(FoldingRangesRequest.type, error);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (foldingProviderRegistration) {
|
||||
foldingProviderRegistration.dispose();
|
||||
foldingProviderRegistration = void 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getPackageInfo(context: ExtensionContext): IPackageInfo | null {
|
||||
let extensionPackage = require(context.asAbsolutePath('./package.json'));
|
||||
if (extensionPackage) {
|
||||
return {
|
||||
name: extensionPackage.name,
|
||||
version: extensionPackage.version,
|
||||
aiKey: extensionPackage.aiKey
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function deactivate(): Promise<any> {
|
||||
return telemetryReporter ? telemetryReporter.dispose() : Promise.resolve(null);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TextDocumentIdentifier } from 'vscode-languageserver-types';
|
||||
import { RequestType, TextDocumentRegistrationOptions, StaticRegistrationOptions } from 'vscode-languageserver-protocol';
|
||||
|
||||
// ---- capabilities
|
||||
|
||||
export interface FoldingProviderClientCapabilities {
|
||||
/**
|
||||
* The text document client capabilities
|
||||
*/
|
||||
textDocument?: {
|
||||
/**
|
||||
* Capabilities specific to the foldingProvider
|
||||
*/
|
||||
foldingProvider?: {
|
||||
/**
|
||||
* Whether implementation supports dynamic registration. If this is set to `true`
|
||||
* the client supports the new `(FoldingProviderOptions & TextDocumentRegistrationOptions & StaticRegistrationOptions)`
|
||||
* return value for the corresponding server capability as well.
|
||||
*/
|
||||
dynamicRegistration?: boolean;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface FoldingProviderOptions {
|
||||
}
|
||||
|
||||
export interface FoldingProviderServerCapabilities {
|
||||
/**
|
||||
* The server provides folding provider support.
|
||||
*/
|
||||
foldingProvider?: FoldingProviderOptions | (FoldingProviderOptions & TextDocumentRegistrationOptions & StaticRegistrationOptions);
|
||||
}
|
||||
|
||||
export interface FoldingRangeList {
|
||||
/**
|
||||
* The folding ranges.
|
||||
*/
|
||||
ranges: FoldingRange[];
|
||||
}
|
||||
|
||||
export enum FoldingRangeType {
|
||||
/**
|
||||
* Folding range for a comment
|
||||
*/
|
||||
Comment = 'comment',
|
||||
/**
|
||||
* Folding range for a imports or includes
|
||||
*/
|
||||
Imports = 'imports',
|
||||
/**
|
||||
* Folding range for a region (e.g. `#region`)
|
||||
*/
|
||||
Region = 'region'
|
||||
}
|
||||
|
||||
export interface FoldingRange {
|
||||
|
||||
/**
|
||||
* The start line number
|
||||
*/
|
||||
startLine: number;
|
||||
|
||||
/**
|
||||
* The end line number
|
||||
*/
|
||||
endLine: number;
|
||||
|
||||
/**
|
||||
* The actual color value for this folding range.
|
||||
*/
|
||||
type?: FoldingRangeType | string;
|
||||
}
|
||||
|
||||
export interface FoldingRangeRequestParam {
|
||||
/**
|
||||
* The text document.
|
||||
*/
|
||||
textDocument: TextDocumentIdentifier;
|
||||
|
||||
/**
|
||||
* The maximum number of ranges to provide
|
||||
*/
|
||||
maxRanges?: number;
|
||||
}
|
||||
|
||||
export namespace FoldingRangesRequest {
|
||||
export const type: RequestType<FoldingRangeRequestParam, FoldingRangeList | null, any, any> = new RequestType('textDocument/foldingRanges');
|
||||
}
|
||||
76
extensions/html-language-features/client/src/tagClosing.ts
Normal file
76
extensions/html-language-features/client/src/tagClosing.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { window, workspace, Disposable, TextDocumentContentChangeEvent, TextDocument, Position, SnippetString } from 'vscode';
|
||||
|
||||
export function activateTagClosing(tagProvider: (document: TextDocument, position: Position) => Thenable<string>, supportedLanguages: { [id: string]: boolean }, configName: string): Disposable {
|
||||
|
||||
let disposables: Disposable[] = [];
|
||||
workspace.onDidChangeTextDocument(event => onDidChangeTextDocument(event.document, event.contentChanges), null, disposables);
|
||||
|
||||
let isEnabled = false;
|
||||
updateEnabledState();
|
||||
window.onDidChangeActiveTextEditor(updateEnabledState, null, disposables);
|
||||
|
||||
let timeout: NodeJS.Timer | undefined = void 0;
|
||||
|
||||
function updateEnabledState() {
|
||||
isEnabled = false;
|
||||
let editor = window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
let document = editor.document;
|
||||
if (!supportedLanguages[document.languageId]) {
|
||||
return;
|
||||
}
|
||||
if (!workspace.getConfiguration(void 0, document.uri).get<boolean>(configName)) {
|
||||
return;
|
||||
}
|
||||
isEnabled = true;
|
||||
}
|
||||
|
||||
function onDidChangeTextDocument(document: TextDocument, changes: TextDocumentContentChangeEvent[]) {
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
let activeDocument = window.activeTextEditor && window.activeTextEditor.document;
|
||||
if (document !== activeDocument || changes.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (typeof timeout !== 'undefined') {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
let lastChange = changes[changes.length - 1];
|
||||
let lastCharacter = lastChange.text[lastChange.text.length - 1];
|
||||
if (lastChange.rangeLength > 0 || lastCharacter !== '>' && lastCharacter !== '/') {
|
||||
return;
|
||||
}
|
||||
let rangeStart = lastChange.range.start;
|
||||
let version = document.version;
|
||||
timeout = setTimeout(() => {
|
||||
let position = new Position(rangeStart.line, rangeStart.character + lastChange.text.length);
|
||||
tagProvider(document, position).then(text => {
|
||||
if (text && isEnabled) {
|
||||
let activeEditor = window.activeTextEditor;
|
||||
if (activeEditor) {
|
||||
let activeDocument = activeEditor.document;
|
||||
if (document === activeDocument && activeDocument.version === version) {
|
||||
let selections = activeEditor.selections;
|
||||
if (selections.length && selections.some(s => s.active.isEqual(position))) {
|
||||
activeEditor.insertSnippet(new SnippetString(text), selections.map(s => s.active));
|
||||
} else {
|
||||
activeEditor.insertSnippet(new SnippetString(text), position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
timeout = void 0;
|
||||
}, 100);
|
||||
}
|
||||
return Disposable.from(...disposables);
|
||||
}
|
||||
7
extensions/html-language-features/client/src/typings/ref.d.ts
vendored
Normal file
7
extensions/html-language-features/client/src/typings/ref.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/// <reference path='../../../../../src/vs/vscode.d.ts'/>
|
||||
/// <reference path='../../../../../src/vs/vscode.proposed.d.ts'/>
|
||||
13
extensions/html-language-features/client/tsconfig.json
Normal file
13
extensions/html-language-features/client/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"module": "commonjs",
|
||||
"outDir": "./out",
|
||||
"noUnusedLocals": true,
|
||||
"sourceMap": true,
|
||||
"lib": [
|
||||
"es2016"
|
||||
],
|
||||
"strict": true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user