提交 b08cde32 编写于 作者: M Martin Aeschlimann

[html] VSCode doesn't automatically close HTML tags Fixes #2246.

上级 4010c1b0
......@@ -6,10 +6,11 @@
import * as path from 'path';
import { languages, workspace, ExtensionContext, IndentAction } from 'vscode';
import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, Range, RequestType } from 'vscode-languageclient';
import { languages, workspace, ExtensionContext, IndentAction, Position, TextDocument } from 'vscode';
import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, Range as LSRange, RequestType, TextDocumentPositionParams } from 'vscode-languageclient';
import { EMPTY_ELEMENTS } from './htmlEmptyTagsShared';
import { activateColorDecorations } from './colorDecorators';
import { activateTagClosing } from './tagClosing';
import TelemetryReporter from 'vscode-extension-telemetry';
import { ConfigurationFeature } from 'vscode-languageclient/lib/proposed';
......@@ -18,7 +19,11 @@ import * as nls from 'vscode-nls';
let localize = nls.loadMessageBundle();
namespace ColorSymbolRequest {
export const type: RequestType<string, Range[], any, any> = new RequestType('css/colorSymbols');
export const type: RequestType<string, LSRange[], any, any> = new RequestType('html/colorSymbols');
}
namespace TagCloseRequest {
export const type: RequestType<TextDocumentPositionParams, string, any, any> = new RequestType('html/tag');
}
interface IPackageInfo {
......@@ -28,10 +33,13 @@ interface IPackageInfo {
}
export function activate(context: ExtensionContext) {
let toDispose = context.subscriptions;
let packageInfo = getPackageInfo(context);
let telemetryReporter: TelemetryReporter = packageInfo && new TelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey);
context.subscriptions.push(telemetryReporter);
if (telemetryReporter) {
toDispose.push(telemetryReporter);
}
// The server is implemented in node
let serverModule = context.asAbsolutePath(path.join('server', 'out', 'htmlServerMain.js'));
......@@ -64,7 +72,7 @@ export function activate(context: ExtensionContext) {
client.registerFeature(new ConfigurationFeature(client));
let disposable = client.start();
context.subscriptions.push(disposable);
toDispose.push(disposable);
client.onReady().then(() => {
let colorRequestor = (uri: string) => {
return client.sendRequest(ColorSymbolRequest.type, uri).then(ranges => ranges.map(client.protocol2CodeConverter.asRange));
......@@ -73,12 +81,21 @@ export function activate(context: ExtensionContext) {
return workspace.getConfiguration().get<boolean>('css.colorDecorators.enable');
};
let disposable = activateColorDecorations(colorRequestor, { html: true, handlebars: true, razor: true }, isDecoratorEnabled);
context.subscriptions.push(disposable);
client.onTelemetry(e => {
toDispose.push(disposable);
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.enable');
toDispose.push(disposable);
disposable = client.onTelemetry(e => {
if (telemetryReporter) {
telemetryReporter.sendTelemetryEvent(e.key, e.data);
}
});
toDispose.push(disposable);
});
languages.setLanguageConfiguration('html', {
......
/*---------------------------------------------------------------------------------------------
* 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 = 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 activeDocument = window.activeTextEditor && window.activeTextEditor.document;
if (document === activeDocument && activeDocument.version === version) {
window.activeTextEditor.insertSnippet(new SnippetString(text), position);
}
}
});
}, 100);
}
return Disposable.from(...disposables);
}
\ No newline at end of file
......@@ -193,6 +193,12 @@
"default": true,
"description": "%html.validate.styles%"
},
"html.autoClosingTags.enable": {
"type": "boolean",
"scope": "resource",
"default": true,
"description": "%html.autoClosingTags.enable%"
},
"html.trace.server": {
"type": "string",
"scope": "window",
......
{
"html.format.enable.desc": "Enable/disable default HTML formatter (requires restart)",
"html.format.enable.desc": "Enable/disable default HTML formatter",
"html.format.wrapLineLength.desc": "Maximum amount of characters per line (0 = disable).",
"html.format.unformatted.desc": "List of tags, comma separated, that shouldn't be reformatted. 'null' defaults to all tags listed at https://www.w3.org/TR/html5/dom.html#phrasing-content.",
"html.format.contentUnformatted.desc": "List of tags, comma separated, where the content shouldn't be reformatted. 'null' defaults to the 'pre' tag.",
......@@ -18,5 +18,6 @@
"html.suggest.ionic.desc": "Configures if the built-in HTML language support suggests Ionic tags, properties and values.",
"html.suggest.html5.desc":"Configures if the built-in HTML language support suggests HTML5 tags, properties and values.",
"html.validate.scripts": "Configures if the built-in HTML language support validates embedded scripts.",
"html.validate.styles": "Configures if the built-in HTML language support validates embedded styles."
"html.validate.styles": "Configures if the built-in HTML language support validates embedded styles.",
"html.autoClosingTags.enable": "Enable/disable autoclosing of HTML tags."
}
\ No newline at end of file
......@@ -8,9 +8,9 @@
"resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-2.1.3.tgz"
},
"vscode-html-languageservice": {
"version": "2.0.5",
"version": "2.0.7",
"from": "vscode-html-languageservice@next",
"resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-2.0.5.tgz"
"resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-2.0.7.tgz"
},
"vscode-jsonrpc": {
"version": "3.3.1",
......
......@@ -9,7 +9,7 @@
},
"dependencies": {
"vscode-css-languageservice": "^2.1.3",
"vscode-html-languageservice": "^2.0.5",
"vscode-html-languageservice": "^2.0.7",
"vscode-languageserver": "3.4.0-next.4",
"vscode-languageserver-types": "^3.3.0",
"vscode-nls": "^2.0.2",
......
......@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { createConnection, IConnection, TextDocuments, InitializeParams, InitializeResult, RequestType, DocumentRangeFormattingRequest, Disposable, DocumentSelector, GetConfigurationParams } from 'vscode-languageserver';
import { createConnection, IConnection, TextDocuments, InitializeParams, InitializeResult, RequestType, DocumentRangeFormattingRequest, Disposable, DocumentSelector, GetConfigurationParams, TextDocumentPositionParams } from 'vscode-languageserver';
import { DocumentContext } from 'vscode-html-languageservice';
import { TextDocument, Diagnostic, DocumentLink, Range, SymbolInformation } from 'vscode-languageserver-types';
import { getLanguageModes, LanguageModes, Settings } from './modes/languageModes';
......@@ -22,9 +22,14 @@ import * as nls from 'vscode-nls';
nls.config(process.env['VSCODE_NLS_CONFIG']);
namespace ColorSymbolRequest {
export const type: RequestType<string, Range[], any, any> = new RequestType('css/colorSymbols');
export const type: RequestType<string, Range[], any, any> = new RequestType('html/colorSymbols');
}
namespace TagCloseRequest {
export const type: RequestType<TextDocumentPositionParams, string, any, any> = new RequestType('html/tag');
}
// Create a connection for the server
let connection: IConnection = createConnection();
......@@ -96,7 +101,7 @@ connection.onInitialize((params: InitializeParams): InitializeResult => {
capabilities: {
// Tell the client that the server works in FULL text document sync mode
textDocumentSync: documents.syncKind,
completionProvider: clientSnippetSupport ? { resolveProvider: true, triggerCharacters: ['.', ':', '<', '"', '=', '/'] } : null,
completionProvider: clientSnippetSupport ? { resolveProvider: true, triggerCharacters: ['.', ':', '<', '"', '=', '/', '>'] } : null,
hoverProvider: true,
documentHighlightProvider: true,
documentRangeFormattingProvider: false,
......@@ -321,5 +326,17 @@ connection.onRequest(ColorSymbolRequest.type, uri => {
return ranges;
});
connection.onRequest(TagCloseRequest.type, params => {
let document = documents.get(params.textDocument.uri);
if (document) {
let mode = languageModes.getModeAtPosition(document, params.position);
if (mode && mode.doAutoClose) {
return mode.doAutoClose(document, params.position);
}
}
return null;
});
// Listen on the connection
connection.listen();
\ No newline at end of file
......@@ -21,6 +21,10 @@ export function getHTMLMode(htmlLanguageService: HTMLLanguageService): LanguageM
},
doComplete(document: TextDocument, position: Position, settings: Settings = globalSettings) {
let options = settings && settings.html && settings.html.suggest;
let doAutoComplete = settings && settings.html && settings.html.autoClosingTags.enable;
if (doAutoComplete) {
options.hideAutoCompleteProposals = true;
}
return htmlLanguageService.doComplete(document, position, htmlDocuments.get(document), options);
},
doHover(document: TextDocument, position: Position) {
......@@ -44,6 +48,14 @@ export function getHTMLMode(htmlLanguageService: HTMLLanguageService): LanguageM
}
return htmlLanguageService.format(document, range, formatSettings);
},
doAutoClose(document: TextDocument, position: Position) {
let offset = document.offsetAt(position);
let text = document.getText();
if (offset > 0 && text.charAt(offset - 1).match(/[>\/]/g)) {
return htmlLanguageService.doTagComplete(document, position, htmlDocuments.get(document));
}
return null;
},
onDocumentRemoved(document: TextDocument) {
htmlDocuments.onDocumentRemoved(document);
},
......
......@@ -41,6 +41,7 @@ export interface LanguageMode {
findReferences?: (document: TextDocument, position: Position) => Location[];
format?: (document: TextDocument, range: Range, options: FormattingOptions, settings: Settings) => TextEdit[];
findColorSymbols?: (document: TextDocument) => Range[];
doAutoClose?: (document: TextDocument, position: Position) => string;
onDocumentRemoved(document: TextDocument): void;
dispose(): void;
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册