history - implement more tests around in-editor selection changes

This commit is contained in:
Benjamin Pasero 2022-02-07 14:32:51 +01:00
parent f83c30b73a
commit cfaa9caf89
No known key found for this signature in database
GPG key ID: E6380CC4C8219E65
6 changed files with 147 additions and 42 deletions

View file

@ -375,6 +375,7 @@ export class SideBySideEditor extends AbstractEditorWithViewState<ISideBySideEdi
}
override setOptions(options: ISideBySideEditorOptions | undefined): void {
super.setOptions(options);
// Update focus if target is provided
if (typeof options?.target === 'number') {

View file

@ -194,6 +194,8 @@ export abstract class BaseTextEditor<T extends IEditorViewState> extends Abstrac
}
override setOptions(options: ITextEditorOptions | undefined): void {
super.setOptions(options);
if (options) {
applyTextEditorOptions(options, assertIsDefined(this.getControl()), ScrollType.Smooth);
}

View file

@ -8,7 +8,7 @@ import { EditorActivation, EditorResolution, IResourceEditorInput } from 'vs/pla
import { URI } from 'vs/base/common/uri';
import { Event } from 'vs/base/common/event';
import { DEFAULT_EDITOR_ASSOCIATION, EditorCloseContext, EditorsOrder, IEditorCloseEvent, EditorInputWithOptions, IEditorPane, IResourceDiffEditorInput, isEditorInputWithOptions, IUntitledTextResourceEditorInput, IUntypedEditorInput, SideBySideEditor } from 'vs/workbench/common/editor';
import { workbenchInstantiationService, TestServiceAccessor, registerTestEditor, TestFileEditorInput, ITestInstantiationService, registerTestResourceEditor, registerTestSideBySideEditor, createEditorPart, registerTestFileEditor, TestEditorWithOptions, TestTextFileEditor, TestSingletonFileEditorInput } from 'vs/workbench/test/browser/workbenchTestServices';
import { workbenchInstantiationService, TestServiceAccessor, registerTestEditor, TestFileEditorInput, ITestInstantiationService, registerTestResourceEditor, registerTestSideBySideEditor, createEditorPart, registerTestFileEditor, TestTextFileEditor, TestSingletonFileEditorInput } from 'vs/workbench/test/browser/workbenchTestServices';
import { EditorService } from 'vs/workbench/services/editor/browser/editorService';
import { IEditorGroup, IEditorGroupsService, GroupDirection, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService';
import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart';
@ -1408,9 +1408,8 @@ suite('EditorService', () => {
let pane = await service.openEditor(new TestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID));
pane = await service.openEditor(new TestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID), { sticky: true, preserveFocus: true });
assert.ok(pane instanceof TestEditorWithOptions);
assert.strictEqual(pane.lastSetOptions?.sticky, true);
assert.strictEqual(pane.lastSetOptions?.preserveFocus, true);
assert.strictEqual(pane?.options?.sticky, true);
assert.strictEqual(pane?.options?.preserveFocus, true);
await pane.group?.closeAllEditors();
@ -1419,16 +1418,15 @@ suite('EditorService', () => {
pane = await service.openEditor({ resource: URI.file('resource-openEditors'), options: { sticky: true, preserveFocus: true } });
assert.ok(pane instanceof TestTextFileEditor);
assert.strictEqual(pane.lastSetOptions?.sticky, true);
assert.strictEqual(pane.lastSetOptions?.preserveFocus, true);
assert.strictEqual(pane?.options?.sticky, true);
assert.strictEqual(pane?.options?.preserveFocus, true);
// Untyped editor (with registered editor)
pane = await service.openEditor({ resource: URI.file('file.editor-service-override-tests') });
pane = await service.openEditor({ resource: URI.file('file.editor-service-override-tests'), options: { sticky: true, preserveFocus: true } });
assert.ok(pane instanceof TestEditorWithOptions);
assert.strictEqual(pane.lastSetOptions?.sticky, true);
assert.strictEqual(pane.lastSetOptions?.preserveFocus, true);
assert.strictEqual(pane?.options?.sticky, true);
assert.strictEqual(pane?.options?.preserveFocus, true);
});
test('isOpen() with side by side editor', async () => {

View file

@ -17,7 +17,7 @@ import { dispose, Disposable, DisposableStore, IDisposable } from 'vs/base/commo
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { Emitter, Event } from 'vs/base/common/event';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { getExcludes, ISearchConfiguration, SEARCH_EXCLUDE_CONFIG } from 'vs/workbench/services/search/common/search';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor';
@ -278,6 +278,12 @@ export class HistoryService extends Disposable implements IHistoryService {
this.globalNavigationsEditorNavigationStack
];
readonly onDidChangeEditorNavigationStack = Event.any(
this.globalDefaultEditorNavigationStack.onDidChange,
this.globalModificationsEditorNavigationStack.onDidChange,
this.globalNavigationsEditorNavigationStack.onDidChange
);
private createEditorNavigationStack(): EditorNavigationStack {
const editorNavigationStack = this._register(this.instantiationService.createInstance(EditorNavigationStack));
@ -1054,9 +1060,12 @@ export class EditorNavigationStack extends Disposable {
this._register(this.editorGroupService.onDidRemoveGroup(e => this.onDidRemoveGroup(e.id)));
}
private registerGroupListeners(group: IEditorGroup): void {
if (!this.mapGroupToDisposable.has(group.id)) {
this.mapGroupToDisposable.set(group.id, group.onWillMoveEditor(e => this.onWillMoveEditor(e)));
private registerGroupListeners(groupId: GroupIdentifier): void {
if (!this.mapGroupToDisposable.has(groupId)) {
const group = this.editorGroupService.getGroup(groupId);
if (group) {
this.mapGroupToDisposable.set(groupId, group.onWillMoveEditor(e => this.onWillMoveEditor(e)));
}
}
}
@ -1091,11 +1100,6 @@ export class EditorNavigationStack extends Disposable {
const isSelectionAwareEditorPane = isEditorPaneWithSelection(editorPane);
const hasValidEditor = editorPane?.group && editorPane.input && !editorPane.input.isDisposed();
// Ensure we listen to changes in group
if (hasValidEditor) {
this.registerGroupListeners(editorPane.group);
}
// Treat editor changes that happen as part of stack navigation specially
// we do not want to add a new stack entry as a matter of navigating the
// stack but we need to keep our currentEditorSelectionState up to date
@ -1170,6 +1174,9 @@ export class EditorNavigationStack extends Disposable {
addOrReplace(groupId: GroupIdentifier, editorCandidate: EditorInput | IResourceEditorInput, selection?: IEditorPaneSelection, forceReplace?: boolean): void {
// Ensure we listen to changes in group
this.registerGroupListeners(groupId);
// Check whether to replace an existing entry or not
let replace = false;
if (this.current) {

View file

@ -6,7 +6,7 @@
import * as assert from 'assert';
import { toResource } from 'vs/base/test/common/utils';
import { URI } from 'vs/base/common/uri';
import { workbenchInstantiationService, TestFileEditorInput, registerTestEditor, createEditorPart, registerTestFileEditor, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices';
import { workbenchInstantiationService, TestFileEditorInput, registerTestEditor, createEditorPart, registerTestFileEditor, TestServiceAccessor, TestTextFileEditor } from 'vs/workbench/test/browser/workbenchTestServices';
import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IEditorGroupsService, GroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService';
@ -18,12 +18,15 @@ import { GoFilter, IHistoryService } from 'vs/workbench/services/history/common/
import { DeferredPromise, timeout } from 'vs/base/common/async';
import { Event } from 'vs/base/common/event';
import { EditorPaneSelectionChangeReason, isResourceEditorInput, IUntypedEditorInput } from 'vs/workbench/common/editor';
import { IResourceEditorInput } from 'vs/platform/editor/common/editor';
import { IResourceEditorInput, ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { IResolvedTextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { FileChangesEvent, FileChangeType, FileOperation, FileOperationEvent } from 'vs/platform/files/common/files';
import { isLinux } from 'vs/base/common/platform';
import { TextEditorPaneSelection } from 'vs/workbench/browser/parts/editor/textEditor';
import { Selection } from 'vs/editor/common/core/selection';
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
suite('HistoryService', function () {
@ -122,6 +125,103 @@ suite('HistoryService', function () {
assert.strictEqual(part.activeGroup.activeEditor?.resource?.toString(), otherResource.toString());
});
test('back / forward: in-editor text selection changes (user)', async function () {
const [, historyService, editorService] = await createServices();
const resource = toResource.call(this, '/path/index.txt');
const pane = await editorService.openEditor({ resource, options: { pinned: true } }) as TestTextFileEditor;
await setTextSelection(historyService, pane, new Selection(1, 2, 1, 2));
await setTextSelection(historyService, pane, new Selection(15, 1, 15, 1)); // will be merged and dropped
await setTextSelection(historyService, pane, new Selection(16, 1, 16, 1)); // will be merged and dropped
await setTextSelection(historyService, pane, new Selection(17, 1, 17, 1));
await setTextSelection(historyService, pane, new Selection(30, 5, 30, 8));
await setTextSelection(historyService, pane, new Selection(40, 1, 40, 1));
await historyService.goBack(GoFilter.NONE);
assertTextSelection(new Selection(30, 5, 30, 8), pane);
await historyService.goBack(GoFilter.NONE);
assertTextSelection(new Selection(17, 1, 17, 1), pane);
await historyService.goBack(GoFilter.NONE);
assertTextSelection(new Selection(1, 2, 1, 2), pane);
await historyService.goForward(GoFilter.NONE);
assertTextSelection(new Selection(17, 1, 17, 1), pane);
});
test('back / forward: in-editor text selection changes (navigation)', async function () {
const [, historyService, editorService] = await createServices();
const resource = toResource.call(this, '/path/index.txt');
const pane = await editorService.openEditor({ resource, options: { pinned: true } }) as TestTextFileEditor;
await setTextSelection(historyService, pane, new Selection(2, 2, 2, 10)); // this is our starting point
await setTextSelection(historyService, pane, new Selection(5, 3, 5, 20), EditorPaneSelectionChangeReason.NAVIGATION); // this is our first target definition
await setTextSelection(historyService, pane, new Selection(120, 8, 120, 18), EditorPaneSelectionChangeReason.NAVIGATION); // this is our second target definition
await setTextSelection(historyService, pane, new Selection(300, 3, 300, 20)); // unrelated user navigation
await setTextSelection(historyService, pane, new Selection(500, 3, 500, 20)); // unrelated user navigation
await setTextSelection(historyService, pane, new Selection(200, 3, 200, 20)); // unrelated user navigation
await historyService.goLast(GoFilter.NAVIGATION);
assertTextSelection(new Selection(120, 8, 120, 18), pane);
await historyService.goBack(GoFilter.NAVIGATION);
assertTextSelection(new Selection(5, 3, 5, 20), pane);
await historyService.goBack(GoFilter.NAVIGATION);
assertTextSelection(new Selection(2, 2, 2, 10), pane);
await historyService.goForward(GoFilter.NAVIGATION);
assertTextSelection(new Selection(5, 3, 5, 20), pane);
});
test('back / forward: edit selection changes', async function () {
const [, historyService, editorService] = await createServices();
const resource = toResource.call(this, '/path/index.txt');
const pane = await editorService.openEditor({ resource, options: { pinned: true } }) as TestTextFileEditor;
await setTextSelection(historyService, pane, new Selection(2, 2, 2, 10));
await setTextSelection(historyService, pane, new Selection(50, 3, 50, 20), EditorPaneSelectionChangeReason.EDIT);
await setTextSelection(historyService, pane, new Selection(300, 3, 300, 20)); // unrelated user navigation
await setTextSelection(historyService, pane, new Selection(500, 3, 500, 20)); // unrelated user navigation
await setTextSelection(historyService, pane, new Selection(200, 3, 200, 20)); // unrelated user navigation
await setTextSelection(historyService, pane, new Selection(5, 3, 5, 20), EditorPaneSelectionChangeReason.EDIT);
await setTextSelection(historyService, pane, new Selection(200, 3, 200, 20)); // unrelated user navigation
await historyService.goLast(GoFilter.EDITS);
assertTextSelection(new Selection(5, 3, 5, 20), pane);
await historyService.goBack(GoFilter.EDITS);
assertTextSelection(new Selection(50, 3, 50, 20), pane);
await historyService.goForward(GoFilter.EDITS);
assertTextSelection(new Selection(5, 3, 5, 20), pane);
});
async function setTextSelection(historyService: IHistoryService, pane: TestTextFileEditor, selection: Selection, reason = EditorPaneSelectionChangeReason.USER): Promise<void> {
const promise = Event.toPromise((historyService as HistoryService).onDidChangeEditorNavigationStack);
pane.setSelection(new TextEditorPaneSelection(selection), reason);
await promise;
}
function assertTextSelection(expected: Selection, pane: EditorPane): void {
const options: ITextEditorOptions | undefined = pane.options;
if (!options) {
assert.fail('EditorPane has no selection');
}
assert.strictEqual(expected.startLineNumber, options.selection?.startLineNumber);
assert.strictEqual(expected.startColumn, options.selection?.startColumn);
assert.strictEqual(expected.endLineNumber, options.selection?.endLineNumber);
assert.strictEqual(expected.endColumn, options.selection?.endColumn);
}
test('back / forward: tracks editor moves across groups', async function () {
const [part, historyService, editorService] = await createServices();

View file

@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { EditorInputWithOptions, IEditorIdentifier, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, IEditorPane, IEditorCloseEvent, IEditorPartOptions, IRevertOptions, GroupIdentifier, EditorsOrder, IFileEditorInput, IEditorFactoryRegistry, IEditorSerializer, EditorExtensions, ISaveOptions, IMoveResult, ITextDiffEditorPane, IVisibleEditorPane, IEditorOpenContext, EditorExtensions as Extensions, EditorInputCapabilities, IUntypedEditorInput, IEditorWillMoveEvent, IEditorWillOpenEvent, IActiveEditorChangeEvent } from 'vs/workbench/common/editor';
import { EditorInputWithOptions, IEditorIdentifier, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, IEditorPane, IEditorCloseEvent, IEditorPartOptions, IRevertOptions, GroupIdentifier, EditorsOrder, IFileEditorInput, IEditorFactoryRegistry, IEditorSerializer, EditorExtensions, ISaveOptions, IMoveResult, ITextDiffEditorPane, IVisibleEditorPane, IEditorOpenContext, EditorExtensions as Extensions, EditorInputCapabilities, IUntypedEditorInput, IEditorWillMoveEvent, IEditorWillOpenEvent, IActiveEditorChangeEvent, EditorPaneSelectionChangeReason, IEditorPaneSelection } from 'vs/workbench/common/editor';
import { EditorServiceImpl, IEditorGroupView, IEditorGroupsAccessor, IEditorGroupTitleHeight } from 'vs/workbench/browser/parts/editor/editor';
import { Event, Emitter } from 'vs/base/common/event';
import { IResolvedWorkingCopyBackup, IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup';
@ -18,7 +18,7 @@ import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configur
import { IWorkbenchLayoutService, PanelAlignment, Parts, Position as PartPosition } from 'vs/workbench/services/layout/browser/layoutService';
import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { IEditorOptions, IResourceEditorInput, IEditorModel, IResourceEditorInputIdentifier, ITextResourceEditorInput, ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { IEditorOptions, IResourceEditorInput, IEditorModel, IResourceEditorInputIdentifier, ITextResourceEditorInput } from 'vs/platform/editor/common/editor';
import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { ILifecycleService, ShutdownReason, StartupKind, LifecyclePhase, WillShutdownEvent, BeforeShutdownErrorEvent, InternalBeforeShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle';
@ -180,17 +180,25 @@ export class TestTextResourceEditor extends TextResourceEditor {
export class TestTextFileEditor extends TextFileEditor {
lastSetOptions: ITextEditorOptions | undefined = undefined;
override setOptions(options: ITextEditorOptions | undefined): void {
this.lastSetOptions = options;
super.setOptions(options);
}
protected override createEditorControl(parent: HTMLElement, configuration: any): IEditor {
return this.instantiationService.createInstance(TestCodeEditor, parent, configuration, {});
}
fireSelectionChangeEvent(reason: EditorPaneSelectionChangeReason) {
this._onDidChangeSelection.fire({ reason });
}
private _testSelection: IEditorPaneSelection | undefined = undefined;
setSelection(selection: IEditorPaneSelection | undefined, reason: EditorPaneSelectionChangeReason): void {
this._testSelection = selection;
this._onDidChangeSelection.fire({ reason });
}
override getSelection(): IEditorPaneSelection | undefined {
return this._testSelection ?? super.getSelection();
}
}
export interface ITestInstantiationService extends IInstantiationService {
@ -1422,19 +1430,8 @@ export class TestEditorInput extends EditorInput {
}
}
export abstract class TestEditorWithOptions extends EditorPane {
lastSetOptions: ITextEditorOptions | undefined = undefined;
override setOptions(options: ITextEditorOptions | undefined): void {
this.lastSetOptions = options;
super.setOptions(options);
}
}
export function registerTestEditor(id: string, inputs: SyncDescriptor<EditorInput>[], serializerInputId?: string): IDisposable {
class TestEditor extends TestEditorWithOptions {
class TestEditor extends EditorPane {
private _scopedContextKeyService: IContextKeyService;