mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-20 12:00:24 -04:00
Merge from vscode 61d5f2b82f17bf9f99f56405204caab88a7e8747
This commit is contained in:
@@ -261,7 +261,7 @@ export class FindDecorations implements IDisposable {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static readonly _CURRENT_FIND_MATCH_DECORATION = ModelDecorationOptions.register({
|
||||
public static readonly _CURRENT_FIND_MATCH_DECORATION = ModelDecorationOptions.register({
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
zIndex: 13,
|
||||
className: 'currentFindMatch',
|
||||
@@ -276,7 +276,7 @@ export class FindDecorations implements IDisposable {
|
||||
}
|
||||
});
|
||||
|
||||
private static readonly _FIND_MATCH_DECORATION = ModelDecorationOptions.register({
|
||||
public static readonly _FIND_MATCH_DECORATION = ModelDecorationOptions.register({
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
className: 'findMatch',
|
||||
showIfCollapsed: true,
|
||||
@@ -290,7 +290,7 @@ export class FindDecorations implements IDisposable {
|
||||
}
|
||||
});
|
||||
|
||||
private static readonly _FIND_MATCH_NO_OVERVIEW_DECORATION = ModelDecorationOptions.register({
|
||||
public static readonly _FIND_MATCH_NO_OVERVIEW_DECORATION = ModelDecorationOptions.register({
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
className: 'findMatch',
|
||||
showIfCollapsed: true
|
||||
|
||||
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 Source EULA. 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 Source EULA. 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 Source EULA. 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>'
|
||||
]);
|
||||
});
|
||||
Reference in New Issue
Block a user