mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 13:00:58 +00:00
[html] VSCode doesn't automatically close HTML tags Fixes #2246.
This commit is contained in:
parent
4010c1b092
commit
b08cde32fe
|
@ -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', {
|
||||
|
|
67
extensions/html/client/src/tagClosing.ts
Normal file
67
extensions/html/client/src/tagClosing.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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);
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"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."
|
||||
}
|
4
extensions/html/server/npm-shrinkwrap.json
generated
4
extensions/html/server/npm-shrinkwrap.json
generated
|
@ -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();
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue