mirror of https://github.com/microsoft/vscode.git
Fix #47069
This commit is contained in:
parent
6b78be0286
commit
0d25d0a798
|
@ -14,10 +14,14 @@ import { EMPTY_ELEMENTS } from './htmlEmptyTagsShared';
|
|||
import { activateTagClosing } from './tagClosing';
|
||||
import TelemetryReporter from 'vscode-extension-telemetry';
|
||||
import { getCustomDataPathsInAllWorkspaces, getCustomDataPathsFromAllExtensions } from './customData';
|
||||
import { activateMatchingTagPosition as activateMatchingTagSelection } from './matchingTag';
|
||||
|
||||
namespace TagCloseRequest {
|
||||
export const type: RequestType<TextDocumentPositionParams, string, any, any> = new RequestType('html/tag');
|
||||
}
|
||||
namespace MatchingTagPositionRequest {
|
||||
export const type: RequestType<TextDocumentPositionParams, Position | null, any, any> = new RequestType('html/matchingTagPosition');
|
||||
}
|
||||
|
||||
interface IPackageInfo {
|
||||
name: string;
|
||||
|
@ -84,6 +88,14 @@ export function activate(context: ExtensionContext) {
|
|||
disposable = activateTagClosing(tagRequestor, { html: true, handlebars: true }, 'html.autoClosingTags');
|
||||
toDispose.push(disposable);
|
||||
|
||||
const matchingTagPositionRequestor = (document: TextDocument, position: Position) => {
|
||||
let param = client.code2ProtocolConverter.asTextDocumentPositionParams(document, position);
|
||||
return client.sendRequest(MatchingTagPositionRequest.type, param);
|
||||
};
|
||||
|
||||
disposable = activateMatchingTagSelection(matchingTagPositionRequestor, { html: true, handlebars: true }, 'html.autoSelectingMatchingTags');
|
||||
toDispose.push(disposable);
|
||||
|
||||
disposable = client.onTelemetry(e => {
|
||||
if (telemetryReporter) {
|
||||
telemetryReporter.sendTelemetryEvent(e.key, e.data);
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import {
|
||||
window,
|
||||
workspace,
|
||||
Disposable,
|
||||
TextDocument,
|
||||
Position,
|
||||
TextEditorSelectionChangeEvent,
|
||||
Selection,
|
||||
Range,
|
||||
WorkspaceEdit
|
||||
} from 'vscode';
|
||||
|
||||
export function activateMatchingTagPosition(
|
||||
matchingTagPositionProvider: (document: TextDocument, position: Position) => Thenable<Position | null>,
|
||||
supportedLanguages: { [id: string]: boolean },
|
||||
configName: string
|
||||
): Disposable {
|
||||
let disposables: Disposable[] = [];
|
||||
|
||||
window.onDidChangeTextEditorSelection(event => onDidChangeTextEditorSelection(event), null, disposables);
|
||||
|
||||
let isEnabled = false;
|
||||
updateEnabledState();
|
||||
|
||||
window.onDidChangeActiveTextEditor(updateEnabledState, null, disposables);
|
||||
|
||||
function updateEnabledState() {
|
||||
isEnabled = false;
|
||||
let editor = window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
let document = editor.document;
|
||||
if (!supportedLanguages[document.languageId]) {
|
||||
return;
|
||||
}
|
||||
if (!workspace.getConfiguration(undefined, document.uri).get<boolean>(configName)) {
|
||||
return;
|
||||
}
|
||||
isEnabled = true;
|
||||
}
|
||||
|
||||
// let prevCursorCount = 0;
|
||||
let cursorCount = 0;
|
||||
let inMirrorMode = false;
|
||||
|
||||
function onDidChangeTextEditorSelection(event: TextEditorSelectionChangeEvent) {
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// prevCursorCount = cursorCount;
|
||||
cursorCount = event.selections.length;
|
||||
|
||||
if (cursorCount === 1) {
|
||||
if (event.selections[0].isEmpty) {
|
||||
matchingTagPositionProvider(event.textEditor.document, event.selections[0].active).then(position => {
|
||||
if (position && window.activeTextEditor) {
|
||||
inMirrorMode = true;
|
||||
const newCursor = new Selection(position.line, position.character, position.line, position.character);
|
||||
window.activeTextEditor.selections = [...window.activeTextEditor.selections, newCursor];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (cursorCount === 2 && inMirrorMode) {
|
||||
// Check two cases
|
||||
if (event.selections[0].isEmpty && event.selections[1].isEmpty) {
|
||||
const charBeforePrimarySelection = getCharBefore(event.textEditor.document, event.selections[0].anchor);
|
||||
const charAfterPrimarySelection = getCharAfter(event.textEditor.document, event.selections[0].anchor);
|
||||
const charBeforeSecondarySelection = getCharBefore(event.textEditor.document, event.selections[1].anchor);
|
||||
const charAfterSecondarySelection = getCharAfter(event.textEditor.document, event.selections[1].anchor);
|
||||
|
||||
// Exit mirror mode when cursor position no longer mirror
|
||||
// Unless it's in the case of `<|></|>`
|
||||
const charBeforeBothPositionRoughlyEqual =
|
||||
charBeforePrimarySelection === charBeforeSecondarySelection ||
|
||||
(charBeforePrimarySelection === '/' && charBeforeSecondarySelection === '<') ||
|
||||
(charBeforeSecondarySelection === '/' && charBeforePrimarySelection === '<');
|
||||
const charAfterBothPositionRoughlyEqual =
|
||||
charAfterPrimarySelection === charAfterSecondarySelection ||
|
||||
(charAfterPrimarySelection === ' ' && charAfterSecondarySelection === '>') ||
|
||||
(charAfterSecondarySelection === ' ' && charAfterPrimarySelection === '>');
|
||||
|
||||
if (!charBeforeBothPositionRoughlyEqual || !charAfterBothPositionRoughlyEqual) {
|
||||
inMirrorMode = false;
|
||||
window.activeTextEditor!.selections = [window.activeTextEditor!.selections[0]];
|
||||
return;
|
||||
} else {
|
||||
// Need to cleanup in the case of <div |></div |>
|
||||
if (
|
||||
charBeforePrimarySelection === ' ' &&
|
||||
charAfterPrimarySelection === '>' &&
|
||||
charBeforeSecondarySelection === ' ' &&
|
||||
charAfterSecondarySelection === '>'
|
||||
) {
|
||||
inMirrorMode = false;
|
||||
const cleanupEdit = new WorkspaceEdit();
|
||||
|
||||
const primaryBeforeSecondary =
|
||||
event.textEditor.document.offsetAt(event.selections[0].anchor) <
|
||||
event.textEditor.document.offsetAt(event.selections[1].anchor);
|
||||
const cleanupRange = primaryBeforeSecondary
|
||||
? new Range(event.selections[1].anchor.translate(0, -1), event.selections[1].anchor)
|
||||
: new Range(event.selections[0].anchor.translate(0, -1), event.selections[0].anchor);
|
||||
|
||||
cleanupEdit.replace(event.textEditor.document.uri, cleanupRange, '');
|
||||
window.activeTextEditor!.selections = primaryBeforeSecondary
|
||||
? [window.activeTextEditor!.selections[0]]
|
||||
: [window.activeTextEditor!.selections[1]];
|
||||
workspace.applyEdit(cleanupEdit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Disposable.from(...disposables);
|
||||
}
|
||||
|
||||
function getCharBefore(document: TextDocument, position: Position) {
|
||||
const offset = document.offsetAt(position);
|
||||
if (offset === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return document.getText(
|
||||
new Range(document.positionAt(offset - 1), position)
|
||||
);
|
||||
}
|
||||
|
||||
function getCharAfter(document: TextDocument, position: Position) {
|
||||
const offset = document.offsetAt(position);
|
||||
if (offset === document.getText().length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return document.getText(
|
||||
new Range(position, document.positionAt(offset + 1))
|
||||
);
|
||||
}
|
|
@ -161,6 +161,12 @@
|
|||
"default": true,
|
||||
"description": "%html.autoClosingTags%"
|
||||
},
|
||||
"html.autoSelectingMatchingTags": {
|
||||
"type": "boolean",
|
||||
"scope": "resource",
|
||||
"default": true,
|
||||
"description": "%html.autoSelectingMatchingTags%"
|
||||
},
|
||||
"html.trace.server": {
|
||||
"type": "string",
|
||||
"scope": "window",
|
||||
|
|
|
@ -24,5 +24,6 @@
|
|||
"html.trace.server.desc": "Traces the communication between VS Code and the HTML language server.",
|
||||
"html.validate.scripts": "Controls whether the built-in HTML language support validates embedded scripts.",
|
||||
"html.validate.styles": "Controls whether the built-in HTML language support validates embedded styles.",
|
||||
"html.autoClosingTags": "Enable/disable autoclosing of HTML tags."
|
||||
"html.autoClosingTags": "Enable/disable autoclosing of HTML tags.",
|
||||
"html.autoSelectingMatchingTags": "Enable/disable auto selecting matching HTML tags."
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"main": "./out/htmlServerMain",
|
||||
"dependencies": {
|
||||
"vscode-css-languageservice": "^4.0.3-next.20",
|
||||
"vscode-html-languageservice": "^3.0.4-next.9",
|
||||
"vscode-html-languageservice": "^3.0.4-next.10",
|
||||
"vscode-languageserver": "^6.0.0-next.3",
|
||||
"vscode-nls": "^4.1.1",
|
||||
"vscode-uri": "^2.0.3"
|
||||
|
|
|
@ -24,6 +24,9 @@ import { getDataProviders } from './customData';
|
|||
namespace TagCloseRequest {
|
||||
export const type: RequestType<TextDocumentPositionParams, string | null, any, any> = new RequestType('html/tag');
|
||||
}
|
||||
namespace MatchingTagPositionRequest {
|
||||
export const type: RequestType<TextDocumentPositionParams, Position | null, any, any> = new RequestType('html/matchingTagPosition');
|
||||
}
|
||||
|
||||
// Create a connection for the server
|
||||
const connection: IConnection = createConnection();
|
||||
|
@ -485,5 +488,21 @@ connection.onRenameRequest((params, token) => {
|
|||
}, null, `Error while computing rename for ${params.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
connection.onRequest(MatchingTagPositionRequest.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.findMatchingTagPosition) {
|
||||
return mode.findMatchingTagPosition(document, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}, null, `Error while computing matching tag position for ${params.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
// Listen on the connection
|
||||
connection.listen();
|
||||
|
|
|
@ -81,6 +81,10 @@ export function getHTMLMode(htmlLanguageService: HTMLLanguageService, workspace:
|
|||
onDocumentRemoved(document: TextDocument) {
|
||||
htmlDocuments.onDocumentRemoved(document);
|
||||
},
|
||||
findMatchingTagPosition(document: TextDocument, position: Position) {
|
||||
const htmlDocument = htmlDocuments.get(document);
|
||||
return htmlLanguageService.findMatchingTagPosition(document, position, htmlDocument);
|
||||
},
|
||||
dispose() {
|
||||
htmlDocuments.dispose();
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ export interface LanguageMode {
|
|||
findDocumentColors?: (document: TextDocument) => ColorInformation[];
|
||||
getColorPresentations?: (document: TextDocument, color: Color, range: Range) => ColorPresentation[];
|
||||
doAutoClose?: (document: TextDocument, position: Position) => string | null;
|
||||
findMatchingTagPosition?: (document: TextDocument, position: Position) => Position | null;
|
||||
getFoldingRanges?: (document: TextDocument) => FoldingRange[];
|
||||
onDocumentRemoved(document: TextDocument): void;
|
||||
dispose(): void;
|
||||
|
|
|
@ -621,10 +621,10 @@ vscode-css-languageservice@^4.0.3-next.20:
|
|||
vscode-nls "^4.1.1"
|
||||
vscode-uri "^2.1.1"
|
||||
|
||||
vscode-html-languageservice@^3.0.4-next.9:
|
||||
version "3.0.4-next.9"
|
||||
resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-3.0.4-next.9.tgz#b27f26c29f3af64fa32eabb7425749f95f64036a"
|
||||
integrity sha512-9V9G7508ybFcn9gQpuucEZIGv8kKlBMEVD8lFFWwWS1yEonKchsxIGJZFbmSGr/n//2anfya8F8yL5ybKuWIRA==
|
||||
vscode-html-languageservice@^3.0.4-next.10:
|
||||
version "3.0.4-next.10"
|
||||
resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-3.0.4-next.10.tgz#da426326833770c51712abb2c7473b9b30bf1cbc"
|
||||
integrity sha512-8P0QBtMPJ9nDMhW8MF/z+5JGg6rK6UOa9po18KIleNuV0rDHU9CAqDyUjxW0CEfLrHYz6dQdkW12ZTClvQnNHw==
|
||||
dependencies:
|
||||
vscode-languageserver-textdocument "^1.0.0-next.4"
|
||||
vscode-languageserver-types "^3.15.0-next.6"
|
||||
|
|
Loading…
Reference in New Issue