mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 21:09:43 +00:00
Merge pull request #92597 from microsoft/octref/live-rename
On Type Rename for #88424
This commit is contained in:
commit
f76ca9f6cc
|
@ -135,6 +135,11 @@ export interface IEditorOptions {
|
|||
* Defaults to false.
|
||||
*/
|
||||
readOnly?: boolean;
|
||||
/**
|
||||
* Rename matching regions on type.
|
||||
* Defaults to false.
|
||||
*/
|
||||
renameOnType?: boolean;
|
||||
/**
|
||||
* Should the editor render validation decorations.
|
||||
* Defaults to editable.
|
||||
|
@ -3374,6 +3379,7 @@ export const enum EditorOption {
|
|||
quickSuggestions,
|
||||
quickSuggestionsDelay,
|
||||
readOnly,
|
||||
renameOnType,
|
||||
renderControlCharacters,
|
||||
renderIndentGuides,
|
||||
renderFinalNewline,
|
||||
|
@ -3790,6 +3796,10 @@ export const EditorOptions = {
|
|||
readOnly: register(new EditorBooleanOption(
|
||||
EditorOption.readOnly, 'readOnly', false,
|
||||
)),
|
||||
renameOnType: register(new EditorBooleanOption(
|
||||
EditorOption.renameOnType, 'renameOnType', false,
|
||||
{ description: nls.localize('renameOnType', "Controls whether the editor auto renames on type.") }
|
||||
)),
|
||||
renderControlCharacters: register(new EditorBooleanOption(
|
||||
EditorOption.renderControlCharacters, 'renderControlCharacters', false,
|
||||
{ description: nls.localize('renderControlCharacters', "Controls whether the editor should render control characters.") }
|
||||
|
|
|
@ -789,6 +789,20 @@ export interface DocumentHighlightProvider {
|
|||
provideDocumentHighlights(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult<DocumentHighlight[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rename provider interface defines the contract between extensions and
|
||||
* the live-rename feature.
|
||||
*/
|
||||
export interface OnTypeRenameProvider {
|
||||
|
||||
stopPattern?: RegExp;
|
||||
|
||||
/**
|
||||
* Provide a list of ranges that can be live-renamed together.
|
||||
*/
|
||||
provideOnTypeRenameRanges(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult<IRange[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Value-object that contains additional information when
|
||||
* requesting references.
|
||||
|
@ -1642,6 +1656,11 @@ export const DocumentSymbolProviderRegistry = new LanguageFeatureRegistry<Docume
|
|||
*/
|
||||
export const DocumentHighlightProviderRegistry = new LanguageFeatureRegistry<DocumentHighlightProvider>();
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const OnTypeRenameProviderRegistry = new LanguageFeatureRegistry<OnTypeRenameProvider>();
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
|
|
@ -238,47 +238,48 @@ export enum EditorOption {
|
|||
quickSuggestions = 70,
|
||||
quickSuggestionsDelay = 71,
|
||||
readOnly = 72,
|
||||
renderControlCharacters = 73,
|
||||
renderIndentGuides = 74,
|
||||
renderFinalNewline = 75,
|
||||
renderLineHighlight = 76,
|
||||
renderValidationDecorations = 77,
|
||||
renderWhitespace = 78,
|
||||
revealHorizontalRightPadding = 79,
|
||||
roundedSelection = 80,
|
||||
rulers = 81,
|
||||
scrollbar = 82,
|
||||
scrollBeyondLastColumn = 83,
|
||||
scrollBeyondLastLine = 84,
|
||||
scrollPredominantAxis = 85,
|
||||
selectionClipboard = 86,
|
||||
selectionHighlight = 87,
|
||||
selectOnLineNumbers = 88,
|
||||
showFoldingControls = 89,
|
||||
showUnused = 90,
|
||||
snippetSuggestions = 91,
|
||||
smoothScrolling = 92,
|
||||
stopRenderingLineAfter = 93,
|
||||
suggest = 94,
|
||||
suggestFontSize = 95,
|
||||
suggestLineHeight = 96,
|
||||
suggestOnTriggerCharacters = 97,
|
||||
suggestSelection = 98,
|
||||
tabCompletion = 99,
|
||||
useTabStops = 100,
|
||||
wordSeparators = 101,
|
||||
wordWrap = 102,
|
||||
wordWrapBreakAfterCharacters = 103,
|
||||
wordWrapBreakBeforeCharacters = 104,
|
||||
wordWrapColumn = 105,
|
||||
wordWrapMinified = 106,
|
||||
wrappingIndent = 107,
|
||||
wrappingStrategy = 108,
|
||||
editorClassName = 109,
|
||||
pixelRatio = 110,
|
||||
tabFocusMode = 111,
|
||||
layoutInfo = 112,
|
||||
wrappingInfo = 113
|
||||
renameOnType = 73,
|
||||
renderControlCharacters = 74,
|
||||
renderIndentGuides = 75,
|
||||
renderFinalNewline = 76,
|
||||
renderLineHighlight = 77,
|
||||
renderValidationDecorations = 78,
|
||||
renderWhitespace = 79,
|
||||
revealHorizontalRightPadding = 80,
|
||||
roundedSelection = 81,
|
||||
rulers = 82,
|
||||
scrollbar = 83,
|
||||
scrollBeyondLastColumn = 84,
|
||||
scrollBeyondLastLine = 85,
|
||||
scrollPredominantAxis = 86,
|
||||
selectionClipboard = 87,
|
||||
selectionHighlight = 88,
|
||||
selectOnLineNumbers = 89,
|
||||
showFoldingControls = 90,
|
||||
showUnused = 91,
|
||||
snippetSuggestions = 92,
|
||||
smoothScrolling = 93,
|
||||
stopRenderingLineAfter = 94,
|
||||
suggest = 95,
|
||||
suggestFontSize = 96,
|
||||
suggestLineHeight = 97,
|
||||
suggestOnTriggerCharacters = 98,
|
||||
suggestSelection = 99,
|
||||
tabCompletion = 100,
|
||||
useTabStops = 101,
|
||||
wordSeparators = 102,
|
||||
wordWrap = 103,
|
||||
wordWrapBreakAfterCharacters = 104,
|
||||
wordWrapBreakBeforeCharacters = 105,
|
||||
wordWrapColumn = 106,
|
||||
wordWrapMinified = 107,
|
||||
wrappingIndent = 108,
|
||||
wrappingStrategy = 109,
|
||||
editorClassName = 110,
|
||||
pixelRatio = 111,
|
||||
tabFocusMode = 112,
|
||||
layoutInfo = 113,
|
||||
wrappingInfo = 114
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
11
src/vs/editor/contrib/rename/media/onTypeRename.css
Normal file
11
src/vs/editor/contrib/rename/media/onTypeRename.css
Normal file
|
@ -0,0 +1,11 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-editor .on-type-rename-decoration {
|
||||
background: rgba(255, 0, 0, 0.3);
|
||||
border-left: 1px solid rgba(255, 0, 0, 0.3);
|
||||
/* So border can be transparent */
|
||||
background-clip: padding-box;
|
||||
}
|
367
src/vs/editor/contrib/rename/onTypeRename.ts
Normal file
367
src/vs/editor/contrib/rename/onTypeRename.ts
Normal file
|
@ -0,0 +1,367 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/onTypeRename';
|
||||
import * as nls from 'vs/nls';
|
||||
import { registerEditorContribution, registerModelAndPositionCommand, EditorAction, EditorCommand, ServicesAccessor, registerEditorAction, registerEditorCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { Position, IPosition } from 'vs/editor/common/core/position';
|
||||
import { ITextModel, IModelDeltaDecoration, TrackedRangeStickiness, IIdentifiedSingleEditOperation } from 'vs/editor/common/model';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { OnTypeRenameProviderRegistry } from 'vs/editor/common/modes';
|
||||
import { first, createCancelablePromise, CancelablePromise, RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
import { ContextKeyExpr, RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/errors';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
|
||||
export const CONTEXT_ONTYPE_RENAME_INPUT_VISIBLE = new RawContextKey<boolean>('onTypeRenameInputVisible', false);
|
||||
|
||||
export class OnTypeRenameContribution extends Disposable implements IEditorContribution {
|
||||
|
||||
public static readonly ID = 'editor.contrib.onTypeRename';
|
||||
|
||||
private static readonly DECORATION = ModelDecorationOptions.register({
|
||||
stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges,
|
||||
className: 'on-type-rename-decoration'
|
||||
});
|
||||
|
||||
static get(editor: ICodeEditor): OnTypeRenameContribution {
|
||||
return editor.getContribution<OnTypeRenameContribution>(OnTypeRenameContribution.ID);
|
||||
}
|
||||
|
||||
private readonly _editor: ICodeEditor;
|
||||
private _enabled: boolean;
|
||||
|
||||
private readonly _visibleContextKey: IContextKey<boolean>;
|
||||
|
||||
private _currentRequest: CancelablePromise<{
|
||||
ranges: IRange[],
|
||||
stopPattern?: RegExp
|
||||
} | null | undefined> | null;
|
||||
private _currentDecorations: string[]; // The one at index 0 is the reference one
|
||||
private _stopPattern: RegExp;
|
||||
private _ignoreChangeEvent: boolean;
|
||||
private _updateMirrors: RunOnceScheduler;
|
||||
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
@IContextKeyService contextKeyService: IContextKeyService
|
||||
) {
|
||||
super();
|
||||
this._editor = editor;
|
||||
this._enabled = this._editor.getOption(EditorOption.renameOnType);
|
||||
this._visibleContextKey = CONTEXT_ONTYPE_RENAME_INPUT_VISIBLE.bindTo(contextKeyService);
|
||||
this._currentRequest = null;
|
||||
this._currentDecorations = [];
|
||||
this._stopPattern = /^\s/;
|
||||
this._ignoreChangeEvent = false;
|
||||
this._updateMirrors = this._register(new RunOnceScheduler(() => this._doUpdateMirrors(), 0));
|
||||
|
||||
this._register(this._editor.onDidChangeModel((e) => {
|
||||
this.stopAll();
|
||||
this.run();
|
||||
}));
|
||||
|
||||
this._register(this._editor.onDidChangeConfiguration((e) => {
|
||||
if (e.hasChanged(EditorOption.renameOnType)) {
|
||||
this._enabled = this._editor.getOption(EditorOption.renameOnType);
|
||||
this.stopAll();
|
||||
this.run();
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._editor.onDidChangeCursorPosition((e) => {
|
||||
// no regions, run
|
||||
if (this._currentDecorations.length === 0) {
|
||||
this.run(e.position);
|
||||
}
|
||||
|
||||
// has cached regions, don't run
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
if (this._currentDecorations.length === 0) {
|
||||
return;
|
||||
}
|
||||
const model = this._editor.getModel();
|
||||
const currentRanges = this._currentDecorations.map(decId => model.getDecorationRange(decId)!);
|
||||
|
||||
// just moving cursor around, don't run again
|
||||
if (Range.containsPosition(currentRanges[0], e.position)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// moving cursor out of primary region, run
|
||||
this.run(e.position);
|
||||
}));
|
||||
|
||||
this._register(OnTypeRenameProviderRegistry.onDidChange(() => {
|
||||
this.run();
|
||||
}));
|
||||
|
||||
this._register(this._editor.onDidChangeModelContent((e) => {
|
||||
if (this._ignoreChangeEvent) {
|
||||
return;
|
||||
}
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
if (this._currentDecorations.length === 0) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
if (e.isUndoing || e.isRedoing) {
|
||||
return;
|
||||
}
|
||||
if (e.changes[0] && this._stopPattern.test(e.changes[0].text)) {
|
||||
this.stopAll();
|
||||
return;
|
||||
}
|
||||
this._updateMirrors.schedule();
|
||||
}));
|
||||
}
|
||||
|
||||
private _doUpdateMirrors(): void {
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
if (this._currentDecorations.length === 0) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
const model = this._editor.getModel();
|
||||
const currentRanges = this._currentDecorations.map(decId => model.getDecorationRange(decId)!);
|
||||
|
||||
const referenceRange = currentRanges[0];
|
||||
if (referenceRange.startLineNumber !== referenceRange.endLineNumber) {
|
||||
return this.stopAll();
|
||||
}
|
||||
|
||||
const referenceValue = model.getValueInRange(referenceRange);
|
||||
if (this._stopPattern.test(referenceValue)) {
|
||||
return this.stopAll();
|
||||
}
|
||||
|
||||
let edits: IIdentifiedSingleEditOperation[] = [];
|
||||
for (let i = 1, len = currentRanges.length; i < len; i++) {
|
||||
const mirrorRange = currentRanges[i];
|
||||
if (mirrorRange.startLineNumber !== mirrorRange.endLineNumber) {
|
||||
edits.push({
|
||||
range: mirrorRange,
|
||||
text: referenceValue
|
||||
});
|
||||
} else {
|
||||
let oldValue = model.getValueInRange(mirrorRange);
|
||||
let newValue = referenceValue;
|
||||
let rangeStartColumn = mirrorRange.startColumn;
|
||||
let rangeEndColumn = mirrorRange.endColumn;
|
||||
|
||||
const commonPrefixLength = strings.commonPrefixLength(oldValue, newValue);
|
||||
rangeStartColumn += commonPrefixLength;
|
||||
oldValue = oldValue.substr(commonPrefixLength);
|
||||
newValue = newValue.substr(commonPrefixLength);
|
||||
|
||||
const commonSuffixLength = strings.commonSuffixLength(oldValue, newValue);
|
||||
rangeEndColumn -= commonSuffixLength;
|
||||
oldValue = oldValue.substr(0, oldValue.length - commonSuffixLength);
|
||||
newValue = newValue.substr(0, newValue.length - commonSuffixLength);
|
||||
|
||||
if (rangeStartColumn !== rangeEndColumn || newValue.length !== 0) {
|
||||
edits.push({
|
||||
range: new Range(mirrorRange.startLineNumber, rangeStartColumn, mirrorRange.endLineNumber, rangeEndColumn),
|
||||
text: newValue
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (edits.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this._ignoreChangeEvent = true;
|
||||
const prevEditOperationType = this._editor._getCursors().getPrevEditOperationType();
|
||||
this._editor.executeEdits('onTypeRename', edits);
|
||||
this._editor._getCursors().setPrevEditOperationType(prevEditOperationType);
|
||||
} finally {
|
||||
this._ignoreChangeEvent = false;
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
this.stopAll();
|
||||
}
|
||||
|
||||
stopAll(): void {
|
||||
this._visibleContextKey.set(false);
|
||||
this._currentDecorations = this._editor.deltaDecorations(this._currentDecorations, []);
|
||||
}
|
||||
|
||||
async run(position: Position | null = this._editor.getPosition(), force = false): Promise<void> {
|
||||
if (!position) {
|
||||
return;
|
||||
}
|
||||
if (!this._enabled && !force) {
|
||||
return;
|
||||
}
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._currentRequest) {
|
||||
this._currentRequest.cancel();
|
||||
this._currentRequest = null;
|
||||
}
|
||||
|
||||
const model = this._editor.getModel();
|
||||
|
||||
this._currentRequest = createCancelablePromise(token => getOnTypeRenameRanges(model, position, token));
|
||||
try {
|
||||
const response = await this._currentRequest;
|
||||
|
||||
let ranges: IRange[] = [];
|
||||
if (response?.ranges) {
|
||||
ranges = response.ranges;
|
||||
}
|
||||
if (response?.stopPattern) {
|
||||
this._stopPattern = response.stopPattern;
|
||||
}
|
||||
|
||||
let foundReferenceRange = false;
|
||||
for (let i = 0, len = ranges.length; i < len; i++) {
|
||||
if (Range.containsPosition(ranges[i], position)) {
|
||||
foundReferenceRange = true;
|
||||
if (i !== 0) {
|
||||
const referenceRange = ranges[i];
|
||||
ranges.splice(i, 1);
|
||||
ranges.unshift(referenceRange);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundReferenceRange) {
|
||||
// Cannot do on type rename if the ranges are not where the cursor is...
|
||||
this.stopAll();
|
||||
return;
|
||||
}
|
||||
|
||||
const decorations: IModelDeltaDecoration[] = ranges.map(range => ({ range: range, options: OnTypeRenameContribution.DECORATION }));
|
||||
this._visibleContextKey.set(true);
|
||||
this._currentDecorations = this._editor.deltaDecorations(this._currentDecorations, decorations);
|
||||
} catch (err) {
|
||||
onUnexpectedError(err);
|
||||
this.stopAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class OnTypeRenameAction extends EditorAction {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.action.onTypeRename',
|
||||
label: nls.localize('onTypeRename.label', "On Type Rename Symbol"),
|
||||
alias: 'On Type Rename Symbol',
|
||||
precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasRenameProvider),
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.F2,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
runCommand(accessor: ServicesAccessor, args: [URI, IPosition]): void | Promise<void> {
|
||||
const editorService = accessor.get(ICodeEditorService);
|
||||
const [uri, pos] = Array.isArray(args) && args || [undefined, undefined];
|
||||
|
||||
if (URI.isUri(uri) && Position.isIPosition(pos)) {
|
||||
return editorService.openCodeEditor({ resource: uri }, editorService.getActiveCodeEditor()).then(editor => {
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
editor.setPosition(pos);
|
||||
editor.invokeWithinContext(accessor => {
|
||||
this.reportTelemetry(accessor, editor);
|
||||
return this.run(accessor, editor);
|
||||
});
|
||||
}, onUnexpectedError);
|
||||
}
|
||||
|
||||
return super.runCommand(accessor, args);
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
|
||||
const controller = OnTypeRenameContribution.get(editor);
|
||||
if (controller) {
|
||||
return Promise.resolve(controller.run(editor.getPosition(), true));
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
const OnTypeRenameCommand = EditorCommand.bindToContribution<OnTypeRenameContribution>(OnTypeRenameContribution.get);
|
||||
registerEditorCommand(new OnTypeRenameCommand({
|
||||
id: 'cancelOnTypeRenameInput',
|
||||
precondition: CONTEXT_ONTYPE_RENAME_INPUT_VISIBLE,
|
||||
handler: x => x.stopAll(),
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
weight: KeybindingWeight.EditorContrib + 99,
|
||||
primary: KeyCode.Escape,
|
||||
secondary: [KeyMod.Shift | KeyCode.Escape]
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
export function getOnTypeRenameRanges(model: ITextModel, position: Position, token: CancellationToken): Promise<{
|
||||
ranges: IRange[],
|
||||
stopPattern?: RegExp
|
||||
} | undefined | null> {
|
||||
const orderedByScore = OnTypeRenameProviderRegistry.ordered(model);
|
||||
|
||||
// in order of score ask the occurrences provider
|
||||
// until someone response with a good result
|
||||
// (good = none empty array)
|
||||
return first<{
|
||||
ranges: IRange[],
|
||||
stopPattern?: RegExp
|
||||
} | undefined>(orderedByScore.map(provider => () => {
|
||||
return Promise.resolve(provider.provideOnTypeRenameRanges(model, position, token)).then((ranges) => {
|
||||
if (!ranges) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
ranges,
|
||||
stopPattern: provider.stopPattern
|
||||
};
|
||||
}, (err) => {
|
||||
onUnexpectedExternalError(err);
|
||||
return undefined;
|
||||
});
|
||||
|
||||
}), result => !!result && arrays.isNonEmptyArray(result?.ranges));
|
||||
}
|
||||
|
||||
|
||||
registerModelAndPositionCommand('_executeRenameOnTypeProvider', (model, position) => getOnTypeRenameRanges(model, position, CancellationToken.None));
|
||||
|
||||
registerEditorContribution(OnTypeRenameContribution.ID, OnTypeRenameContribution);
|
||||
registerEditorAction(OnTypeRenameAction);
|
451
src/vs/editor/contrib/rename/test/onTypeRename.test.ts
Normal file
451
src/vs/editor/contrib/rename/test/onTypeRename.test.ts
Normal file
|
@ -0,0 +1,451 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Handler } from 'vs/editor/common/editorCommon';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { OnTypeRenameContribution } from 'vs/editor/contrib/rename/onTypeRename';
|
||||
import { createTestCodeEditor, TestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
|
||||
import { createTextModel } from 'vs/editor/test/common/editorTestUtils';
|
||||
import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands';
|
||||
|
||||
const mockFile = URI.parse('test:somefile.ttt');
|
||||
const mockFileSelector = { scheme: 'test' };
|
||||
const timeout = 30;
|
||||
|
||||
suite('On type rename', () => {
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
setup(() => {
|
||||
disposables.clear();
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
disposables.clear();
|
||||
});
|
||||
|
||||
function createMockEditor(text: string | string[]) {
|
||||
const model = typeof text === 'string'
|
||||
? createTextModel(text, undefined, undefined, mockFile)
|
||||
: createTextModel(text.join('\n'), undefined, undefined, mockFile);
|
||||
|
||||
const editor = createTestCodeEditor({ model });
|
||||
disposables.add(model);
|
||||
disposables.add(editor);
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
|
||||
function testCase(
|
||||
name: string,
|
||||
initialState: { text: string | string[], ranges: Range[], stopPattern?: RegExp },
|
||||
operations: (editor: TestCodeEditor, contrib: OnTypeRenameContribution) => Promise<void>,
|
||||
expectedEndText: string | string[]
|
||||
) {
|
||||
test(name, async () => {
|
||||
disposables.add(modes.OnTypeRenameProviderRegistry.register(mockFileSelector, {
|
||||
stopPattern: initialState.stopPattern || /^\s/,
|
||||
|
||||
provideOnTypeRenameRanges() {
|
||||
return initialState.ranges;
|
||||
}
|
||||
}));
|
||||
|
||||
const editor = createMockEditor(initialState.text);
|
||||
const ontypeRenameContribution = editor.registerAndInstantiateContribution(
|
||||
OnTypeRenameContribution.ID,
|
||||
OnTypeRenameContribution
|
||||
);
|
||||
|
||||
await operations(editor, ontypeRenameContribution);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
if (typeof expectedEndText === 'string') {
|
||||
assert.equal(editor.getModel()!.getValue(), expectedEndText);
|
||||
} else {
|
||||
assert.equal(editor.getModel()!.getValue(), expectedEndText.join('\n'));
|
||||
}
|
||||
resolve();
|
||||
}, timeout);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const state = {
|
||||
text: '<ooo></ooo>',
|
||||
ranges: [
|
||||
new Range(1, 2, 1, 5),
|
||||
new Range(1, 8, 1, 11),
|
||||
]
|
||||
};
|
||||
|
||||
/**
|
||||
* Simple insertion
|
||||
*/
|
||||
testCase('Simple insert - initial', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<iooo></iooo>');
|
||||
|
||||
testCase('Simple insert - middle', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 3);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<oioo></oioo>');
|
||||
|
||||
testCase('Simple insert - end', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 5);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<oooi></oooi>');
|
||||
|
||||
/**
|
||||
* Simple insertion - end
|
||||
*/
|
||||
testCase('Simple insert end - initial', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 8);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<iooo></iooo>');
|
||||
|
||||
testCase('Simple insert end - middle', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 9);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<oioo></oioo>');
|
||||
|
||||
testCase('Simple insert end - end', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 11);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<oooi></oooi>');
|
||||
|
||||
/**
|
||||
* Boundary insertion
|
||||
*/
|
||||
testCase('Simple insert - out of boundary', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 1);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, 'i<ooo></ooo>');
|
||||
|
||||
testCase('Simple insert - out of boundary 2', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 6);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<ooo>i</ooo>');
|
||||
|
||||
testCase('Simple insert - out of boundary 3', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 7);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<ooo><i/ooo>');
|
||||
|
||||
testCase('Simple insert - out of boundary 4', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 12);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<ooo></ooo>i');
|
||||
|
||||
/**
|
||||
* Insert + Move
|
||||
*/
|
||||
testCase('Continuous insert', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<iiooo></iiooo>');
|
||||
|
||||
testCase('Insert - move - insert', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
editor.setPosition(new Position(1, 4));
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<ioioo></ioioo>');
|
||||
|
||||
testCase('Insert - move - insert outside region', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
editor.setPosition(new Position(1, 7));
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<iooo>i</iooo>');
|
||||
|
||||
/**
|
||||
* Selection insert
|
||||
*/
|
||||
testCase('Selection insert - simple', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.setSelection(new Range(1, 2, 1, 3));
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<ioo></ioo>');
|
||||
|
||||
testCase('Selection insert - whole', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.setSelection(new Range(1, 2, 1, 5));
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<i></i>');
|
||||
|
||||
testCase('Selection insert - across boundary', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.setSelection(new Range(1, 1, 1, 3));
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, 'ioo></oo>');
|
||||
|
||||
/**
|
||||
* @todo
|
||||
* Undefined behavior
|
||||
*/
|
||||
// testCase('Selection insert - across two boundary', state, async (editor, ontypeRenameContribution) => {
|
||||
// const pos = new Position(1, 2);
|
||||
// editor.setPosition(pos);
|
||||
// await ontypeRenameContribution.run(pos, true);
|
||||
// editor.setSelection(new Range(1, 4, 1, 9));
|
||||
// editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
// }, '<ooioo>');
|
||||
|
||||
/**
|
||||
* Break out behavior
|
||||
*/
|
||||
testCase('Breakout - type space', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 5);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: ' ' });
|
||||
}, '<ooo ></ooo>');
|
||||
|
||||
testCase('Breakout - type space then undo', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 5);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: ' ' });
|
||||
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||
}, '<ooo></ooo>');
|
||||
|
||||
testCase('Breakout - type space in middle', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 4);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: ' ' });
|
||||
}, '<oo o></ooo>');
|
||||
|
||||
testCase('Breakout - paste content starting with space', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 5);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Paste, { text: ' i="i"' });
|
||||
}, '<ooo i="i"></ooo>');
|
||||
|
||||
testCase('Breakout - paste content starting with space then undo', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 5);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Paste, { text: ' i="i"' });
|
||||
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||
}, '<ooo></ooo>');
|
||||
|
||||
testCase('Breakout - paste content starting with space in middle', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 4);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Paste, { text: ' i' });
|
||||
}, '<oo io></ooo>');
|
||||
|
||||
/**
|
||||
* Break out with custom stopPattern
|
||||
*/
|
||||
|
||||
const state3 = {
|
||||
...state,
|
||||
stopPattern: /^s/
|
||||
};
|
||||
|
||||
testCase('Breakout with stop pattern - insert', state3, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, '<iooo></iooo>');
|
||||
|
||||
testCase('Breakout with stop pattern - insert stop char', state3, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 's' });
|
||||
}, '<sooo></ooo>');
|
||||
|
||||
testCase('Breakout with stop pattern - paste char', state3, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Paste, { text: 's' });
|
||||
}, '<sooo></ooo>');
|
||||
|
||||
testCase('Breakout with stop pattern - paste string', state3, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Paste, { text: 'so' });
|
||||
}, '<soooo></ooo>');
|
||||
|
||||
testCase('Breakout with stop pattern - insert at end', state3, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 5);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 's' });
|
||||
}, '<ooos></ooo>');
|
||||
|
||||
/**
|
||||
* Delete
|
||||
*/
|
||||
testCase('Delete - left char', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 5);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', 'deleteLeft', {});
|
||||
}, '<oo></oo>');
|
||||
|
||||
testCase('Delete - left char then undo', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 5);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', 'deleteLeft', {});
|
||||
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||
}, '<ooo></ooo>');
|
||||
|
||||
testCase('Delete - left word', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 5);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', 'deleteWordLeft', {});
|
||||
}, '<></>');
|
||||
|
||||
testCase('Delete - left word then undo', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 5);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', 'deleteWordLeft', {});
|
||||
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||
}, '<ooo></ooo>');
|
||||
|
||||
/**
|
||||
* Todo: Fix test
|
||||
*/
|
||||
// testCase('Delete - left all', state, async (editor, ontypeRenameContribution) => {
|
||||
// const pos = new Position(1, 3);
|
||||
// editor.setPosition(pos);
|
||||
// await ontypeRenameContribution.run(pos, true);
|
||||
// editor.trigger('keyboard', 'deleteAllLeft', {});
|
||||
// }, '></>');
|
||||
|
||||
/**
|
||||
* Todo: Fix test
|
||||
*/
|
||||
// testCase('Delete - left all then undo', state, async (editor, ontypeRenameContribution) => {
|
||||
// const pos = new Position(1, 5);
|
||||
// editor.setPosition(pos);
|
||||
// await ontypeRenameContribution.run(pos, true);
|
||||
// editor.trigger('keyboard', 'deleteAllLeft', {});
|
||||
// CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||
// }, '></ooo>');
|
||||
|
||||
testCase('Delete - left all then undo twice', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 5);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', 'deleteAllLeft', {});
|
||||
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||
}, '<ooo></ooo>');
|
||||
|
||||
testCase('Delete - selection', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 5);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.setSelection(new Range(1, 2, 1, 3));
|
||||
editor.trigger('keyboard', 'deleteLeft', {});
|
||||
}, '<oo></oo>');
|
||||
|
||||
testCase('Delete - selection across boundary', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 3);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.setSelection(new Range(1, 1, 1, 3));
|
||||
editor.trigger('keyboard', 'deleteLeft', {});
|
||||
}, 'oo></oo>');
|
||||
|
||||
/**
|
||||
* Undo / redo
|
||||
*/
|
||||
testCase('Undo/redo - simple undo', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||
}, '<ooo></ooo>');
|
||||
|
||||
testCase('Undo/redo - simple undo/redo', state, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
|
||||
}, '<iooo></iooo>');
|
||||
|
||||
/**
|
||||
* Multi line
|
||||
*/
|
||||
const state2 = {
|
||||
text: [
|
||||
'<ooo>',
|
||||
'</ooo>'
|
||||
],
|
||||
ranges: [
|
||||
new Range(1, 2, 1, 5),
|
||||
new Range(2, 3, 2, 6),
|
||||
]
|
||||
};
|
||||
|
||||
testCase('Multiline insert', state2, async (editor, ontypeRenameContribution) => {
|
||||
const pos = new Position(1, 2);
|
||||
editor.setPosition(pos);
|
||||
await ontypeRenameContribution.run(pos, true);
|
||||
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
}, [
|
||||
'<iooo>',
|
||||
'</iooo>'
|
||||
]);
|
||||
});
|
|
@ -32,6 +32,7 @@ import 'vs/editor/contrib/linesOperations/linesOperations';
|
|||
import 'vs/editor/contrib/links/links';
|
||||
import 'vs/editor/contrib/multicursor/multicursor';
|
||||
import 'vs/editor/contrib/parameterHints/parameterHints';
|
||||
import 'vs/editor/contrib/rename/onTypeRename';
|
||||
import 'vs/editor/contrib/rename/rename';
|
||||
import 'vs/editor/contrib/smartSelect/smartSelect';
|
||||
import 'vs/editor/contrib/snippet/snippetController2';
|
||||
|
|
|
@ -391,6 +391,13 @@ export function registerDocumentHighlightProvider(languageId: string, provider:
|
|||
return modes.DocumentHighlightProviderRegistry.register(languageId, provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an on type rename provider.
|
||||
*/
|
||||
export function registerOnTypeRenameProvider(languageId: string, provider: modes.OnTypeRenameProvider): IDisposable {
|
||||
return modes.OnTypeRenameProviderRegistry.register(languageId, provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a definition provider (used by e.g. go to definition).
|
||||
*/
|
||||
|
@ -559,6 +566,7 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages {
|
|||
registerHoverProvider: <any>registerHoverProvider,
|
||||
registerDocumentSymbolProvider: <any>registerDocumentSymbolProvider,
|
||||
registerDocumentHighlightProvider: <any>registerDocumentHighlightProvider,
|
||||
registerOnTypeRenameProvider: <any>registerOnTypeRenameProvider,
|
||||
registerDefinitionProvider: <any>registerDefinitionProvider,
|
||||
registerImplementationProvider: <any>registerImplementationProvider,
|
||||
registerTypeDefinitionProvider: <any>registerTypeDefinitionProvider,
|
||||
|
|
106
src/vs/monaco.d.ts
vendored
106
src/vs/monaco.d.ts
vendored
|
@ -2673,6 +2673,11 @@ declare namespace monaco.editor {
|
|||
* Defaults to false.
|
||||
*/
|
||||
readOnly?: boolean;
|
||||
/**
|
||||
* Rename matching regions on type.
|
||||
* Defaults to false.
|
||||
*/
|
||||
renameOnType?: boolean;
|
||||
/**
|
||||
* Should the editor render validation decorations.
|
||||
* Defaults to editable.
|
||||
|
@ -3882,47 +3887,48 @@ declare namespace monaco.editor {
|
|||
quickSuggestions = 70,
|
||||
quickSuggestionsDelay = 71,
|
||||
readOnly = 72,
|
||||
renderControlCharacters = 73,
|
||||
renderIndentGuides = 74,
|
||||
renderFinalNewline = 75,
|
||||
renderLineHighlight = 76,
|
||||
renderValidationDecorations = 77,
|
||||
renderWhitespace = 78,
|
||||
revealHorizontalRightPadding = 79,
|
||||
roundedSelection = 80,
|
||||
rulers = 81,
|
||||
scrollbar = 82,
|
||||
scrollBeyondLastColumn = 83,
|
||||
scrollBeyondLastLine = 84,
|
||||
scrollPredominantAxis = 85,
|
||||
selectionClipboard = 86,
|
||||
selectionHighlight = 87,
|
||||
selectOnLineNumbers = 88,
|
||||
showFoldingControls = 89,
|
||||
showUnused = 90,
|
||||
snippetSuggestions = 91,
|
||||
smoothScrolling = 92,
|
||||
stopRenderingLineAfter = 93,
|
||||
suggest = 94,
|
||||
suggestFontSize = 95,
|
||||
suggestLineHeight = 96,
|
||||
suggestOnTriggerCharacters = 97,
|
||||
suggestSelection = 98,
|
||||
tabCompletion = 99,
|
||||
useTabStops = 100,
|
||||
wordSeparators = 101,
|
||||
wordWrap = 102,
|
||||
wordWrapBreakAfterCharacters = 103,
|
||||
wordWrapBreakBeforeCharacters = 104,
|
||||
wordWrapColumn = 105,
|
||||
wordWrapMinified = 106,
|
||||
wrappingIndent = 107,
|
||||
wrappingStrategy = 108,
|
||||
editorClassName = 109,
|
||||
pixelRatio = 110,
|
||||
tabFocusMode = 111,
|
||||
layoutInfo = 112,
|
||||
wrappingInfo = 113
|
||||
renameOnType = 73,
|
||||
renderControlCharacters = 74,
|
||||
renderIndentGuides = 75,
|
||||
renderFinalNewline = 76,
|
||||
renderLineHighlight = 77,
|
||||
renderValidationDecorations = 78,
|
||||
renderWhitespace = 79,
|
||||
revealHorizontalRightPadding = 80,
|
||||
roundedSelection = 81,
|
||||
rulers = 82,
|
||||
scrollbar = 83,
|
||||
scrollBeyondLastColumn = 84,
|
||||
scrollBeyondLastLine = 85,
|
||||
scrollPredominantAxis = 86,
|
||||
selectionClipboard = 87,
|
||||
selectionHighlight = 88,
|
||||
selectOnLineNumbers = 89,
|
||||
showFoldingControls = 90,
|
||||
showUnused = 91,
|
||||
snippetSuggestions = 92,
|
||||
smoothScrolling = 93,
|
||||
stopRenderingLineAfter = 94,
|
||||
suggest = 95,
|
||||
suggestFontSize = 96,
|
||||
suggestLineHeight = 97,
|
||||
suggestOnTriggerCharacters = 98,
|
||||
suggestSelection = 99,
|
||||
tabCompletion = 100,
|
||||
useTabStops = 101,
|
||||
wordSeparators = 102,
|
||||
wordWrap = 103,
|
||||
wordWrapBreakAfterCharacters = 104,
|
||||
wordWrapBreakBeforeCharacters = 105,
|
||||
wordWrapColumn = 106,
|
||||
wordWrapMinified = 107,
|
||||
wrappingIndent = 108,
|
||||
wrappingStrategy = 109,
|
||||
editorClassName = 110,
|
||||
pixelRatio = 111,
|
||||
tabFocusMode = 112,
|
||||
layoutInfo = 113,
|
||||
wrappingInfo = 114
|
||||
}
|
||||
export const EditorOptions: {
|
||||
acceptSuggestionOnCommitCharacter: IEditorOption<EditorOption.acceptSuggestionOnCommitCharacter, boolean>;
|
||||
|
@ -3998,6 +4004,7 @@ declare namespace monaco.editor {
|
|||
quickSuggestions: IEditorOption<EditorOption.quickSuggestions, ValidQuickSuggestionsOptions>;
|
||||
quickSuggestionsDelay: IEditorOption<EditorOption.quickSuggestionsDelay, number>;
|
||||
readOnly: IEditorOption<EditorOption.readOnly, boolean>;
|
||||
renameOnType: IEditorOption<EditorOption.renameOnType, boolean>;
|
||||
renderControlCharacters: IEditorOption<EditorOption.renderControlCharacters, boolean>;
|
||||
renderIndentGuides: IEditorOption<EditorOption.renderIndentGuides, boolean>;
|
||||
renderFinalNewline: IEditorOption<EditorOption.renderFinalNewline, boolean>;
|
||||
|
@ -4954,6 +4961,11 @@ declare namespace monaco.languages {
|
|||
*/
|
||||
export function registerDocumentHighlightProvider(languageId: string, provider: DocumentHighlightProvider): IDisposable;
|
||||
|
||||
/**
|
||||
* Register an on type rename provider.
|
||||
*/
|
||||
export function registerOnTypeRenameProvider(languageId: string, provider: OnTypeRenameProvider): IDisposable;
|
||||
|
||||
/**
|
||||
* Register a definition provider (used by e.g. go to definition).
|
||||
*/
|
||||
|
@ -5712,6 +5724,18 @@ declare namespace monaco.languages {
|
|||
provideDocumentHighlights(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult<DocumentHighlight[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rename provider interface defines the contract between extensions and
|
||||
* the live-rename feature.
|
||||
*/
|
||||
export interface OnTypeRenameProvider {
|
||||
stopPattern?: RegExp;
|
||||
/**
|
||||
* Provide a list of ranges that can be live-renamed together.
|
||||
*/
|
||||
provideOnTypeRenameRanges(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult<IRange[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Value-object that contains additional information when
|
||||
* requesting references.
|
||||
|
|
37
src/vs/vscode.proposed.d.ts
vendored
37
src/vs/vscode.proposed.d.ts
vendored
|
@ -1230,6 +1230,43 @@ declare module 'vscode' {
|
|||
|
||||
//#endregion
|
||||
|
||||
//#region OnTypeRename: https://github.com/microsoft/vscode/issues/88424
|
||||
|
||||
/**
|
||||
* The rename provider interface defines the contract between extensions and
|
||||
* the live-rename feature.
|
||||
*/
|
||||
export interface OnTypeRenameProvider {
|
||||
/**
|
||||
* Provide a list of ranges that can be live renamed together.
|
||||
*
|
||||
* @param document The document in which the command was invoked.
|
||||
* @param position The position at which the command was invoked.
|
||||
* @param token A cancellation token.
|
||||
* @return A list of ranges that can be live-renamed togehter. The ranges must have
|
||||
* identical length and contain identical text content. The ranges cannot overlap.
|
||||
*/
|
||||
provideOnTypeRenameRanges(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<Range[]>;
|
||||
}
|
||||
|
||||
namespace languages {
|
||||
/**
|
||||
* Register a rename provider that works on type.
|
||||
*
|
||||
* Multiple providers can be registered for a language. In that case providers are sorted
|
||||
* by their [score](#languages.match) and the best-matching provider is used. Failure
|
||||
* of the selected provider will cause a failure of the whole operation.
|
||||
*
|
||||
* @param selector A selector that defines the documents this provider is applicable to.
|
||||
* @param provider An on type rename provider.
|
||||
* @param stopPattern Stop on type renaming when input text matches the regular expression. Defaults to `^\s`.
|
||||
* @return A [disposable](#Disposable) that unregisters this provider when being disposed.
|
||||
*/
|
||||
export function registerOnTypeRenameProvider(selector: DocumentSelector, provider: OnTypeRenameProvider, stopPattern?: RegExp): Disposable;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Custom editors: https://github.com/microsoft/vscode/issues/77131
|
||||
|
||||
// TODO:
|
||||
|
|
|
@ -261,6 +261,18 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
|||
}));
|
||||
}
|
||||
|
||||
// --- on type rename
|
||||
|
||||
$registerOnTypeRenameProvider(handle: number, selector: IDocumentFilterDto[], stopPattern?: IRegExpDto): void {
|
||||
const revivedStopPattern = stopPattern ? MainThreadLanguageFeatures._reviveRegExp(stopPattern) : undefined;
|
||||
this._registrations.set(handle, modes.OnTypeRenameProviderRegistry.register(selector, <modes.OnTypeRenameProvider>{
|
||||
stopPattern: revivedStopPattern,
|
||||
provideOnTypeRenameRanges: (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise<IRange[] | undefined> => {
|
||||
return this._proxy.$provideOnTypeRenameRanges(handle, model.uri, position, token);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// --- references
|
||||
|
||||
$registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void {
|
||||
|
|
|
@ -362,6 +362,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|||
registerDocumentHighlightProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable {
|
||||
return extHostLanguageFeatures.registerDocumentHighlightProvider(extension, checkSelector(selector), provider);
|
||||
},
|
||||
registerOnTypeRenameProvider(selector: vscode.DocumentSelector, provider: vscode.OnTypeRenameProvider, stopPattern?: RegExp): vscode.Disposable {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostLanguageFeatures.registerOnTypeRenameProvider(extension, checkSelector(selector), provider, stopPattern);
|
||||
},
|
||||
registerReferenceProvider(selector: vscode.DocumentSelector, provider: vscode.ReferenceProvider): vscode.Disposable {
|
||||
return extHostLanguageFeatures.registerReferenceProvider(extension, checkSelector(selector), provider);
|
||||
},
|
||||
|
|
|
@ -362,6 +362,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable {
|
|||
$registerHoverProvider(handle: number, selector: IDocumentFilterDto[]): void;
|
||||
$registerEvaluatableExpressionProvider(handle: number, selector: IDocumentFilterDto[]): void;
|
||||
$registerDocumentHighlightProvider(handle: number, selector: IDocumentFilterDto[]): void;
|
||||
$registerOnTypeRenameProvider(handle: number, selector: IDocumentFilterDto[], stopPattern: IRegExpDto | undefined): void;
|
||||
$registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void;
|
||||
$registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], metadata: ICodeActionProviderMetadataDto, displayName: string): void;
|
||||
$registerDocumentFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void;
|
||||
|
@ -1286,6 +1287,7 @@ export interface ExtHostLanguageFeaturesShape {
|
|||
$provideHover(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.Hover | undefined>;
|
||||
$provideEvaluatableExpression(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.EvaluatableExpression | undefined>;
|
||||
$provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.DocumentHighlight[] | undefined>;
|
||||
$provideOnTypeRenameRanges(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<IRange[] | undefined>;
|
||||
$provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext, token: CancellationToken): Promise<ILocationDto[] | undefined>;
|
||||
$provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise<ICodeActionListDto | undefined>;
|
||||
$releaseCodeActions(handle: number, cacheId: number): void;
|
||||
|
|
|
@ -318,6 +318,26 @@ class DocumentHighlightAdapter {
|
|||
}
|
||||
}
|
||||
|
||||
class OnTypeRenameAdapter {
|
||||
constructor(
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
private readonly _provider: vscode.OnTypeRenameProvider
|
||||
) { }
|
||||
|
||||
provideOnTypeRenameRanges(resource: URI, position: IPosition, token: CancellationToken): Promise<IRange[] | undefined> {
|
||||
|
||||
const doc = this._documents.getDocument(resource);
|
||||
const pos = typeConvert.Position.to(position);
|
||||
|
||||
return asPromise(() => this._provider.provideOnTypeRenameRanges(doc, pos, token)).then(value => {
|
||||
if (Array.isArray(value)) {
|
||||
return coalesce(value.map(typeConvert.Range.from));
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class ReferenceAdapter {
|
||||
|
||||
constructor(
|
||||
|
@ -1350,7 +1370,8 @@ type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | Hov
|
|||
| RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter
|
||||
| SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter
|
||||
| TypeDefinitionAdapter | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter
|
||||
| SelectionRangeAdapter | CallHierarchyAdapter | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter | EvaluatableExpressionAdapter;
|
||||
| SelectionRangeAdapter | CallHierarchyAdapter | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter | EvaluatableExpressionAdapter
|
||||
| OnTypeRenameAdapter;
|
||||
|
||||
class AdapterData {
|
||||
constructor(
|
||||
|
@ -1594,6 +1615,19 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
|
|||
return this._withAdapter(handle, DocumentHighlightAdapter, adapter => adapter.provideDocumentHighlights(URI.revive(resource), position, token), undefined);
|
||||
}
|
||||
|
||||
// --- on type rename
|
||||
|
||||
registerOnTypeRenameProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.OnTypeRenameProvider, stopPattern?: RegExp): vscode.Disposable {
|
||||
const handle = this._addNewAdapter(new OnTypeRenameAdapter(this._documents, provider), extension);
|
||||
const serializedStopPattern = stopPattern ? ExtHostLanguageFeatures._serializeRegExp(stopPattern) : undefined;
|
||||
this._proxy.$registerOnTypeRenameProvider(handle, this._transformDocumentSelector(selector), serializedStopPattern);
|
||||
return this._createDisposable(handle);
|
||||
}
|
||||
|
||||
$provideOnTypeRenameRanges(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<IRange[] | undefined> {
|
||||
return this._withAdapter(handle, OnTypeRenameAdapter, adapter => adapter.provideOnTypeRenameRanges(URI.revive(resource), position, token), undefined);
|
||||
}
|
||||
|
||||
// --- references
|
||||
|
||||
registerReferenceProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.ReferenceProvider): vscode.Disposable {
|
||||
|
|
Loading…
Reference in a new issue