未验证 提交 e1a46ca6 编写于 作者: B Benjamin Pasero 提交者: GitHub

fix #122699 (#122712)

* fix #122699

* add tests
上级 6449b6b7
......@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { EditorInput, EditorResourceAccessor, IEditorInput, IEditorInputFactoryRegistry, SideBySideEditorInput, EditorExtensions } from 'vs/workbench/common/editor';
import { EditorInput, EditorResourceAccessor, IEditorInput, EditorExtensions, SideBySideEditor } from 'vs/workbench/common/editor';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { Registry } from 'vs/platform/registry/common/platform';
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
......@@ -15,7 +15,6 @@ import { Promises } from 'vs/base/common/async';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { NO_TYPE_ID } from 'vs/workbench/services/workingCopy/common/workingCopy';
import { URI } from 'vs/workbench/workbench.web.api';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
......@@ -196,39 +195,20 @@ Registry.add(EditorExtensions.Editors, new EditorRegistry());
//#endregion
//#region Text Editor Close Tracker
//#region Editor Close Tracker
export function whenTextEditorClosed(accessor: ServicesAccessor, resources: URI[]): Promise<void> {
export function whenEditorClosed(accessor: ServicesAccessor, resources: URI[]): Promise<void> {
const editorService = accessor.get(IEditorService);
const uriIdentityService = accessor.get(IUriIdentityService);
const workingCopyService = accessor.get(IWorkingCopyService);
const fileEditorInputFactory = Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).getFileEditorInputFactory();
return new Promise(resolve => {
let remainingResources = [...resources];
// Observe any editor closing from this moment on
const listener = editorService.onDidCloseEditor(async event => {
let primaryResource: URI | undefined = undefined;
let secondaryResource: URI | undefined = undefined;
// Resolve the resources from the editor that closed
// but only consider file editor inputs, given we
// are only tracking text editors.
if (event.editor instanceof SideBySideEditorInput) {
if (fileEditorInputFactory.isFileEditorInput(event.editor.primary)) {
primaryResource = EditorResourceAccessor.getOriginalUri(event.editor.primary);
}
if (fileEditorInputFactory.isFileEditorInput(event.editor.secondary)) {
secondaryResource = EditorResourceAccessor.getOriginalUri(event.editor.secondary);
}
} else {
if (fileEditorInputFactory.isFileEditorInput(event.editor)) {
primaryResource = EditorResourceAccessor.getOriginalUri(event.editor);
}
}
const primaryResource = EditorResourceAccessor.getOriginalUri(event.editor, { supportSideBySide: SideBySideEditor.PRIMARY });
const secondaryResource = EditorResourceAccessor.getOriginalUri(event.editor, { supportSideBySide: SideBySideEditor.SECONDARY });
// Remove from resources to wait for being closed based on the
// resources from editors that got closed
......@@ -247,10 +227,10 @@ export function whenTextEditorClosed(accessor: ServicesAccessor, resources: URI[
// to close the editor while the save still continues in the background. As such
// we have to also check if the editors to track for are dirty and if so wait
// for them to get saved.
const dirtyResources = resources.filter(resource => workingCopyService.isDirty(resource, NO_TYPE_ID /* only check on text file working copies */));
const dirtyResources = resources.filter(resource => workingCopyService.isDirty(resource));
if (dirtyResources.length > 0) {
await Promises.settled(dirtyResources.map(async resource => await new Promise<void>(resolve => {
if (!workingCopyService.isDirty(resource, NO_TYPE_ID /* only check on text file working copies */)) {
if (!workingCopyService.isDirty(resource)) {
return resolve(); // return early if resource is not dirty
}
......
......@@ -24,7 +24,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { whenTextEditorClosed } from 'vs/workbench/browser/editor';
import { FILE_EDITOR_INPUT_ID } from 'vs/workbench/contrib/files/common/files';
suite('Files - TextFileEditorTracker', () => {
......@@ -182,26 +181,4 @@ suite('Files - TextFileEditorTracker', () => {
});
});
}
test('whenTextEditorClosed (single editor)', async function () {
return testWhenTextEditorClosed(toResource.call(this, '/path/index.txt'));
});
test('whenTextEditorClosed (multiple editor)', async function () {
return testWhenTextEditorClosed(toResource.call(this, '/path/index.txt'), toResource.call(this, '/test.html'));
});
async function testWhenTextEditorClosed(...resources: URI[]): Promise<void> {
const accessor = await createTracker(false);
for (const resource of resources) {
await accessor.editorService.openEditor({ resource, options: { pinned: true } });
}
const closedPromise = accessor.instantitionService.invokeFunction(accessor => whenTextEditorClosed(accessor, resources));
accessor.editorGroupService.activeGroup.closeAllEditors();
await closedPromise;
}
});
......@@ -59,7 +59,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { AuthInfo } from 'vs/base/parts/sandbox/electron-sandbox/electronTypes';
import { ILogService } from 'vs/platform/log/common/log';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { whenTextEditorClosed } from 'vs/workbench/browser/editor';
import { whenEditorClosed } from 'vs/workbench/browser/editor';
export class NativeWindow extends Disposable {
......@@ -669,7 +669,7 @@ export class NativeWindow extends Disposable {
private async trackClosedWaitFiles(waitMarkerFile: URI, resourcesToWaitFor: URI[]): Promise<void> {
// Wait for the resources to be closed in the text editor...
await this.instantiationService.invokeFunction(accessor => whenTextEditorClosed(accessor, resourcesToWaitFor));
await this.instantiationService.invokeFunction(accessor => whenEditorClosed(accessor, resourcesToWaitFor));
// ...before deleting the wait marker file
await this.fileService.del(waitMarkerFile);
......
......@@ -11,7 +11,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWindowSettings, IWindowOpenable, IOpenWindowOptions, isFolderToOpen, isWorkspaceToOpen, isFileToOpen, IOpenEmptyWindowOptions, IPathData, IFileToOpen } from 'vs/platform/windows/common/windows';
import { pathsToEditors } from 'vs/workbench/common/editor';
import { whenTextEditorClosed } from 'vs/workbench/browser/editor';
import { whenEditorClosed } from 'vs/workbench/browser/editor';
import { IFileService } from 'vs/platform/files/common/files';
import { ILabelService } from 'vs/platform/label/common/label';
import { IModifierKeyStatus, ModifierKeyEmitter, trackFocus } from 'vs/base/browser/dom';
......@@ -315,7 +315,7 @@ export class BrowserHostService extends Disposable implements IHostService {
(async () => {
// Wait for the resources to be closed in the text editor...
await this.instantiationService.invokeFunction(accessor => whenTextEditorClosed(accessor, fileOpenables.map(fileOpenable => fileOpenable.fileUri)));
await this.instantiationService.invokeFunction(accessor => whenEditorClosed(accessor, fileOpenables.map(fileOpenable => fileOpenable.fileUri)));
// ...before deleting the wait marker file
await this.fileService.del(waitMarkerFileURI);
......
......@@ -8,9 +8,16 @@ import { EditorResourceAccessor, SideBySideEditor, IEditorInputWithPreferredReso
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { URI } from 'vs/base/common/uri';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { workbenchInstantiationService, TestServiceAccessor, TestEditorInput } from 'vs/workbench/test/browser/workbenchTestServices';
import { workbenchInstantiationService, TestServiceAccessor, TestEditorInput, registerTestDiffEditor, registerTestEditor, registerTestFileEditor, registerTestResourceEditor, TestFileEditorInput, createEditorPart } from 'vs/workbench/test/browser/workbenchTestServices';
import { Schemas } from 'vs/base/common/network';
import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { toResource } from 'vs/base/test/common/utils';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { whenEditorClosed } from 'vs/workbench/browser/editor';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { EditorService } from 'vs/workbench/services/editor/browser/editorService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
suite('Workbench editor', () => {
......@@ -21,16 +28,40 @@ suite('Workbench editor', () => {
}
}
const disposables = new DisposableStore();
const TEST_EDITOR_ID = 'MyTestEditorForEditors';
let instantiationService: IInstantiationService;
let accessor: TestServiceAccessor;
async function createServices(): Promise<TestServiceAccessor> {
const instantiationService = workbenchInstantiationService();
const part = await createEditorPart(instantiationService, disposables);
instantiationService.stub(IEditorGroupsService, part);
const editorService = instantiationService.createInstance(EditorService);
instantiationService.stub(IEditorService, editorService);
return instantiationService.createInstance(TestServiceAccessor);
}
setup(() => {
instantiationService = workbenchInstantiationService();
accessor = instantiationService.createInstance(TestServiceAccessor);
disposables.add(registerTestFileEditor());
disposables.add(registerTestDiffEditor());
disposables.add(registerTestResourceEditor());
disposables.add(registerTestEditor(TEST_EDITOR_ID, [new SyncDescriptor(TestFileEditorInput)]));
});
teardown(() => {
accessor.untitledTextEditorService.dispose();
disposables.clear();
});
test('EditorResourceAccessor', () => {
......@@ -123,4 +154,48 @@ suite('Workbench editor', () => {
assert.strictEqual(EditorResourceAccessor.getCanonicalUri(fileWithPreferredResource)?.toString(), resource.toString());
assert.strictEqual(EditorResourceAccessor.getOriginalUri(fileWithPreferredResource)?.toString(), preferredResource.toString());
});
test('whenEditorClosed (single editor)', async function () {
return testWhenEditorClosed(false, false, toResource.call(this, '/path/index.txt'));
});
test('whenEditorClosed (multiple editor)', async function () {
return testWhenEditorClosed(false, false, toResource.call(this, '/path/index.txt'), toResource.call(this, '/test.html'));
});
test('whenEditorClosed (single editor, diff editor)', async function () {
return testWhenEditorClosed(true, false, toResource.call(this, '/path/index.txt'));
});
test('whenEditorClosed (multiple editor, diff editor)', async function () {
return testWhenEditorClosed(true, false, toResource.call(this, '/path/index.txt'), toResource.call(this, '/test.html'));
});
test('whenEditorClosed (single custom editor)', async function () {
return testWhenEditorClosed(false, true, toResource.call(this, '/path/index.txt'));
});
test('whenEditorClosed (multiple custom editor)', async function () {
return testWhenEditorClosed(false, true, toResource.call(this, '/path/index.txt'), toResource.call(this, '/test.html'));
});
async function testWhenEditorClosed(sideBySide: boolean, custom: boolean, ...resources: URI[]): Promise<void> {
const accessor = await createServices();
for (const resource of resources) {
if (custom) {
await accessor.editorService.openEditor(new TestFileEditorInput(resource, 'testTypeId'), { pinned: true });
} else if (sideBySide) {
await accessor.editorService.openEditor({ leftResource: resource, rightResource: resource, options: { pinned: true } });
} else {
await accessor.editorService.openEditor({ resource, options: { pinned: true } });
}
}
const closedPromise = accessor.instantitionService.invokeFunction(accessor => whenEditorClosed(accessor, resources));
accessor.editorGroupService.activeGroup.closeAllEditors();
await closedPromise;
}
});
......@@ -42,7 +42,7 @@ import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { MockContextKeyService, MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
import { ITextBufferFactory, DefaultEndOfLine, EndOfLinePreference, ITextSnapshot } from 'vs/editor/common/model';
import { Range } from 'vs/editor/common/core/range';
import { IRange, Range } from 'vs/editor/common/core/range';
import { IDialogService, IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
......@@ -96,7 +96,7 @@ import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogSer
import { CodeEditorService } from 'vs/workbench/services/editor/browser/codeEditorService';
import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IDiffEditor, IEditor } from 'vs/editor/common/editorCommon';
import { IChange, IDiffEditor, IEditor } from 'vs/editor/common/editorCommon';
import { IInputBox, IInputOptions, IPickOptions, IQuickInputButton, IQuickInputService, IQuickNavigateConfiguration, IQuickPick, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
import { QuickInputService } from 'vs/workbench/services/quickinput/browser/quickInputService';
import { IListService } from 'vs/platform/list/browser/listService';
......@@ -136,6 +136,10 @@ import { IEditorOverrideService } from 'vs/workbench/services/editor/common/edit
import { IWorkingCopyEditorService, WorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService';
import { IElevatedFileService } from 'vs/workbench/services/files/common/elevatedFileService';
import { BrowserElevatedFileService } from 'vs/workbench/services/files/browser/elevatedFileService';
import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { IDiffComputationResult, IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
import { TextEdit, IInplaceReplaceSupportResult } from 'vs/editor/common/modes';
export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput {
return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined, undefined, undefined, undefined);
......@@ -183,6 +187,7 @@ export function workbenchInstantiationService(
): ITestInstantiationService {
const instantiationService = new TestInstantiationService(new ServiceCollection([ILifecycleService, new TestLifecycleService()]));
instantiationService.stub(IEditorWorkerService, new TestEditorWorkerService());
instantiationService.stub(IWorkingCopyService, disposables.add(new WorkingCopyService()));
instantiationService.stub(IEnvironmentService, TestEnvironmentService);
instantiationService.stub(IWorkbenchEnvironmentService, TestEnvironmentService);
......@@ -1384,6 +1389,23 @@ export function registerTestSideBySideEditor(): IDisposable {
return disposables;
}
export function registerTestDiffEditor(): IDisposable {
const disposables = new DisposableStore();
disposables.add(Registry.as<IEditorRegistry>(Extensions.Editors).registerEditor(
EditorDescriptor.create(
TextDiffEditor,
TextDiffEditor.ID,
'Text Diff Editor'
),
[
new SyncDescriptor(DiffEditorInput)
]
));
return disposables;
}
export class TestFileEditorInput extends EditorInput implements IFileEditorInput {
readonly preferredResource = this.resource;
......@@ -1650,3 +1672,18 @@ export class TestQuickInputService implements IQuickInputService {
back(): Promise<void> { throw new Error('not implemented.'); }
cancel(): Promise<void> { throw new Error('not implemented.'); }
}
export class TestEditorWorkerService implements IEditorWorkerService {
declare readonly _serviceBrand: undefined;
canComputeDiff(original: URI, modified: URI): boolean { return false; }
async computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean, maxComputationTime: number): Promise<IDiffComputationResult | null> { return null; }
canComputeDirtyDiff(original: URI, modified: URI): boolean { return false; }
async computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise<IChange[] | null> { return null; }
async computeMoreMinimalEdits(resource: URI, edits: TextEdit[] | null | undefined): Promise<TextEdit[] | undefined> { return undefined; }
canComputeWordRanges(resource: URI): boolean { return false; }
async computeWordRanges(resource: URI, range: IRange): Promise<{ [word: string]: IRange[]; } | null> { return null; }
canNavigateValueSet(resource: URI): boolean { return false; }
async navigateValueSet(resource: URI, range: IRange, up: boolean): Promise<IInplaceReplaceSupportResult | null> { return null; }
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册