diff --git a/extensions/html-language-features/client/src/htmlMain.ts b/extensions/html-language-features/client/src/htmlMain.ts
index c54a97f9b07..567e073f838 100644
--- a/extensions/html-language-features/client/src/htmlMain.ts
+++ b/extensions/html-language-features/client/src/htmlMain.ts
@@ -21,13 +21,12 @@ import { EMPTY_ELEMENTS } from './htmlEmptyTagsShared';
import { activateTagClosing } from './tagClosing';
import TelemetryReporter from 'vscode-extension-telemetry';
import { getCustomDataPathsInAllWorkspaces, getCustomDataPathsFromAllExtensions } from './customData';
-import { activateMirrorCursor } from './mirrorCursor';
namespace TagCloseRequest {
export const type: RequestType = new RequestType('html/tag');
}
-namespace MatchingTagPositionRequest {
- export const type: RequestType = new RequestType('html/matchingTagPosition');
+namespace OnTypeRenameRequest {
+ export const type: RequestType = new RequestType('html/onTypeRename');
}
// experimental: semantic tokens
@@ -131,14 +130,6 @@ 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 = activateMirrorCursor(matchingTagPositionRequestor, { html: true, handlebars: true }, 'html.mirrorCursorOnMatchingTag');
- toDispose.push(disposable);
-
disposable = client.onTelemetry(e => {
if (telemetryReporter) {
telemetryReporter.sendTelemetryEvent(e.key, e.data);
@@ -289,6 +280,15 @@ export function activate(context: ExtensionContext) {
return results;
}
});
+
+ languages.registerOnTypeRenameProvider(documentSelector, {
+ async provideOnTypeRenameRanges(document, position) {
+ const param = client.code2ProtocolConverter.asTextDocumentPositionParams(document, position);
+ const response = await client.sendRequest(OnTypeRenameRequest.type, param);
+
+ return response || [];
+ }
+ });
}
function getPackageInfo(context: ExtensionContext): IPackageInfo | null {
diff --git a/extensions/html-language-features/client/src/mirrorCursor.ts b/extensions/html-language-features/client/src/mirrorCursor.ts
deleted file mode 100644
index 5c9ae446087..00000000000
--- a/extensions/html-language-features/client/src/mirrorCursor.ts
+++ /dev/null
@@ -1,259 +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 {
- window,
- workspace,
- Disposable,
- TextDocument,
- Position,
- TextEditorSelectionChangeEvent,
- Selection,
- Range,
- WorkspaceEdit
-} from 'vscode';
-
-export function activateMirrorCursor(
- matchingTagPositionProvider: (document: TextDocument, position: Position) => Thenable,
- 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(configName)) {
- return;
- }
- isEnabled = true;
- }
-
- let prevCursors: readonly Selection[] = [];
- let cursors: readonly Selection[] = [];
- let inMirrorMode = false;
-
- function onDidChangeTextEditorSelection(event: TextEditorSelectionChangeEvent) {
- if (!isEnabled) {
- return;
- }
-
- if (event.textEditor.document?.languageId !== 'html' && event.textEditor.document?.languageId !== 'handlebars') {
- return;
- }
-
- prevCursors = cursors;
- cursors = event.selections;
-
- if (cursors.length === 1) {
- if (inMirrorMode && prevCursors.length === 2) {
- if (cursors[0].isEqual(prevCursors[0]) || cursors[0].isEqual(prevCursors[1])) {
- return;
- }
- }
- if (event.selections[0].isEmpty) {
- matchingTagPositionProvider(event.textEditor.document, event.selections[0].active).then(matchingTagPosition => {
- if (matchingTagPosition && window.activeTextEditor) {
- const charBeforeAndAfterPositionsRoughlyEqual = isCharBeforeAndAfterPositionsRoughlyEqual(
- event.textEditor.document,
- event.selections[0].anchor,
- new Position(matchingTagPosition.line, matchingTagPosition.character)
- );
-
- if (charBeforeAndAfterPositionsRoughlyEqual) {
- inMirrorMode = true;
- const newCursor = new Selection(
- matchingTagPosition.line,
- matchingTagPosition.character,
- matchingTagPosition.line,
- matchingTagPosition.character
- );
- window.activeTextEditor.selections = [...window.activeTextEditor.selections, newCursor];
- }
- }
- });
- }
- }
-
- const exitMirrorMode = () => {
- inMirrorMode = false;
- window.activeTextEditor!.selections = [window.activeTextEditor!.selections[0]];
- };
-
- if (cursors.length === 2 && inMirrorMode) {
- /**
- * Both cursors are positions
- */
- if (event.selections[0].isEmpty && event.selections[1].isEmpty) {
- if (
- prevCursors.length === 2 &&
- event.selections[0].anchor.line !== prevCursors[0].anchor.line &&
- event.selections[1].anchor.line !== prevCursors[0].anchor.line
- ) {
- exitMirrorMode();
- return;
- }
-
- const charBeforeAndAfterPositionsRoughlyEqual = isCharBeforeAndAfterPositionsRoughlyEqual(
- event.textEditor.document,
- event.selections[0].anchor,
- event.selections[1].anchor
- );
-
- if (!charBeforeAndAfterPositionsRoughlyEqual) {
- exitMirrorMode();
- return;
- } else {
- // Need to cleanup in the case of
- if (
- shouldDoCleanupForHtmlAttributeInput(
- event.textEditor.document,
- event.selections[0].anchor,
- event.selections[1].anchor
- )
- ) {
- const cleanupEdit = new WorkspaceEdit();
- const cleanupRange = new Range(event.selections[1].anchor.translate(0, -1), event.selections[1].anchor);
- cleanupEdit.replace(event.textEditor.document.uri, cleanupRange, '');
- exitMirrorMode();
- workspace.applyEdit(cleanupEdit);
- }
- }
- } else {
- /**
- * Both cursors are selections
- */
- const charBeforeAndAfterAnchorPositionsRoughlyEqual = isCharBeforeAndAfterPositionsRoughlyEqual(
- event.textEditor.document,
- event.selections[0].anchor,
- event.selections[1].anchor
- );
-
- const charBeforeAndAfterActivePositionsRoughlyEqual = isCharBeforeAndAfterPositionsRoughlyEqual(
- event.textEditor.document,
- event.selections[0].active,
- event.selections[1].active
- );
-
- if (!charBeforeAndAfterAnchorPositionsRoughlyEqual || !charBeforeAndAfterActivePositionsRoughlyEqual) {
- exitMirrorMode();
- }
- }
- }
- }
-
- 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)));
-}
-
-// Check if chars before and after the two positions are equal
-// For the chars before, `<` and `/` are considered equal to handle the case of `<|>|>`
-function isCharBeforeAndAfterPositionsRoughlyEqual(document: TextDocument, firstPos: Position, secondPos: Position) {
- const charBeforePrimarySelection = getCharBefore(document, firstPos);
- const charAfterPrimarySelection = getCharAfter(document, firstPos);
- const charBeforeSecondarySelection = getCharBefore(document, secondPos);
- const charAfterSecondarySelection = getCharAfter(document, secondPos);
-
- /**
- * Special case for exiting
- * |
- * |
- */
- if (
- charBeforePrimarySelection === ' ' &&
- charBeforeSecondarySelection === ' ' &&
- charAfterPrimarySelection === '<' &&
- charAfterSecondarySelection === '<'
- ) {
- return false;
- }
- /**
- * Special case for exiting
- * |
- * |
- */
- if (charBeforePrimarySelection === '\n' && charBeforeSecondarySelection === '\n') {
- return false;
- }
- /**
- * Special case for exiting
- * |
- *
|
- */
- if (charAfterPrimarySelection === '\n' && charAfterSecondarySelection === '\n') {
- return false;
- }
-
- // 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 === '>');
-
- return charBeforeBothPositionRoughlyEqual && charAfterBothPositionRoughlyEqual;
-}
-
-function shouldDoCleanupForHtmlAttributeInput(document: TextDocument, firstPos: Position, secondPos: Position) {
- // Need to cleanup in the case of
- const charBeforePrimarySelection = getCharBefore(document, firstPos);
- const charAfterPrimarySelection = getCharAfter(document, firstPos);
- const charBeforeSecondarySelection = getCharBefore(document, secondPos);
- const charAfterSecondarySelection = getCharAfter(document, secondPos);
-
- const primaryBeforeSecondary = document.offsetAt(firstPos) < document.offsetAt(secondPos);
-
- /**
- * Check two cases
- *
- *
- * Before 1st cursor: ` `
- * After 1st cursor: `>` or ` `
- * Before 2nd cursor: ` `
- * After 2nd cursor: `>`
- */
- return (
- primaryBeforeSecondary &&
- charBeforePrimarySelection === ' ' &&
- (charAfterPrimarySelection === '>' || charAfterPrimarySelection === ' ') &&
- charBeforeSecondarySelection === ' ' &&
- charAfterSecondarySelection === '>'
- );
-}
diff --git a/extensions/html-language-features/package.json b/extensions/html-language-features/package.json
index 00289a314a1..b92820fade8 100644
--- a/extensions/html-language-features/package.json
+++ b/extensions/html-language-features/package.json
@@ -165,7 +165,8 @@
"type": "boolean",
"scope": "resource",
"default": false,
- "description": "%html.mirrorCursorOnMatchingTag%"
+ "description": "%html.mirrorCursorOnMatchingTag%",
+ "deprecationMessage": "%html.mirrorCursorOnMatchingTagDeprecationMessage%"
},
"html.trace.server": {
"type": "string",
diff --git a/extensions/html-language-features/package.nls.json b/extensions/html-language-features/package.nls.json
index 8fa34d25270..90e4e73f568 100644
--- a/extensions/html-language-features/package.nls.json
+++ b/extensions/html-language-features/package.nls.json
@@ -25,5 +25,6 @@
"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.mirrorCursorOnMatchingTag": "Enable/disable mirroring cursor on matching HTML tag."
+ "html.mirrorCursorOnMatchingTag": "Enable/disable mirroring cursor on matching HTML tag.",
+ "html.mirrorCursorOnMatchingTagDeprecationMessage": "Deprecated in favor of `editor.renameOnType`"
}
diff --git a/extensions/html-language-features/server/src/htmlServerMain.ts b/extensions/html-language-features/server/src/htmlServerMain.ts
index 9ed3ce0f0a0..78385d54719 100644
--- a/extensions/html-language-features/server/src/htmlServerMain.ts
+++ b/extensions/html-language-features/server/src/htmlServerMain.ts
@@ -28,8 +28,8 @@ import { SemanticTokenProvider, newSemanticTokenProvider } from './modes/semanti
namespace TagCloseRequest {
export const type: RequestType = new RequestType('html/tag');
}
-namespace MatchingTagPositionRequest {
- export const type: RequestType = new RequestType('html/matchingTagPosition');
+namespace OnTypeRenameRequest {
+ export const type: RequestType = new RequestType('html/onTypeRename');
}
// experimental: semantic tokens
@@ -499,20 +499,20 @@ connection.onRenameRequest((params, token) => {
}, null, `Error while computing rename for ${params.textDocument.uri}`, token);
});
-connection.onRequest(MatchingTagPositionRequest.type, (params, 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.findMatchingTagPosition) {
- return mode.findMatchingTagPosition(document, pos);
+ if (mode && mode.doOnTypeRename) {
+ return mode.doOnTypeRename(document, pos);
}
}
}
return null;
- }, null, `Error while computing matching tag position for ${params.textDocument.uri}`, token);
+ }, null, `Error while computing synced regions for ${params.textDocument.uri}`, token);
});
let semanticTokensProvider: SemanticTokenProvider | undefined;
diff --git a/extensions/html-language-features/server/src/modes/htmlMode.ts b/extensions/html-language-features/server/src/modes/htmlMode.ts
index 251821d1272..b4b72fe7c96 100644
--- a/extensions/html-language-features/server/src/modes/htmlMode.ts
+++ b/extensions/html-language-features/server/src/modes/htmlMode.ts
@@ -85,6 +85,10 @@ export function getHTMLMode(htmlLanguageService: HTMLLanguageService, workspace:
const htmlDocument = htmlDocuments.get(document);
return htmlLanguageService.findMatchingTagPosition(document, position, htmlDocument);
},
+ doOnTypeRename(document: TextDocument, position: Position) {
+ const htmlDocument = htmlDocuments.get(document);
+ return htmlLanguageService.findSyncedRegions(document, position, htmlDocument);
+ },
dispose() {
htmlDocuments.dispose();
}
diff --git a/extensions/html-language-features/server/src/modes/languageModes.ts b/extensions/html-language-features/server/src/modes/languageModes.ts
index 43bbc1f3d6c..624e8de448f 100644
--- a/extensions/html-language-features/server/src/modes/languageModes.ts
+++ b/extensions/html-language-features/server/src/modes/languageModes.ts
@@ -47,6 +47,7 @@ export interface LanguageMode {
doHover?: (document: TextDocument, position: Position) => Hover | null;
doSignatureHelp?: (document: TextDocument, position: Position) => SignatureHelp | null;
doRename?: (document: TextDocument, position: Position, newName: string) => WorkspaceEdit | null;
+ doOnTypeRename?: (document: TextDocument, position: Position) => Range[] | null;
findDocumentHighlight?: (document: TextDocument, position: Position) => DocumentHighlight[];
findDocumentSymbols?: (document: TextDocument) => SymbolInformation[];
findDocumentLinks?: (document: TextDocument, documentContext: DocumentContext) => DocumentLink[];