Skip to content

Commit 3dd1a9d

Browse files
author
Benjamin Pasero
committed
web - support --wait
1 parent ff5915e commit 3dd1a9d

6 files changed

Lines changed: 124 additions & 67 deletions

File tree

src/vs/workbench/electron-browser/window.ts

Lines changed: 9 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction, SubmenuIte
2929
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
3030
import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
3131
import { RunOnceScheduler } from 'vs/base/common/async';
32-
import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
32+
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
3333
import { LifecyclePhase, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
3434
import { IWorkspaceFolderCreationData, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
3535
import { IIntegrityService } from 'vs/workbench/services/integrity/common/integrity';
@@ -43,7 +43,6 @@ import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessi
4343
import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
4444
import { coalesce } from 'vs/base/common/arrays';
4545
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
46-
import { isEqual } from 'vs/base/common/resources';
4746
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
4847
import { MenubarControl } from '../browser/parts/titlebar/menubarControl';
4948
import { ILabelService } from 'vs/platform/label/common/label';
@@ -242,10 +241,7 @@ export class NativeWindow extends Disposable {
242241
// Listen to editor closing (if we run with --wait)
243242
const filesToWait = this.environmentService.configuration.filesToWait;
244243
if (filesToWait) {
245-
const waitMarkerFile = filesToWait.waitMarkerFileUri;
246-
const resourcesToWaitFor = coalesce(filesToWait.paths.map(p => p.fileUri));
247-
248-
this._register(this.trackClosedWaitFiles(waitMarkerFile, resourcesToWaitFor));
244+
this.trackClosedWaitFiles(filesToWait.waitMarkerFileUri, coalesce(filesToWait.paths.map(path => path.fileUri)));
249245
}
250246

251247
// macOS OS integration
@@ -593,7 +589,7 @@ export class NativeWindow extends Disposable {
593589
private onAddFoldersRequest(request: IAddFoldersRequest): void {
594590

595591
// Buffer all pending requests
596-
this.pendingFoldersToAdd.push(...request.foldersToAdd.map(f => URI.revive(f)));
592+
this.pendingFoldersToAdd.push(...request.foldersToAdd.map(folder => URI.revive(folder)));
597593

598594
// Delay the adding of folders a bit to buffer in case more requests are coming
599595
if (!this.addFoldersScheduler.isScheduled()) {
@@ -633,65 +629,17 @@ export class NativeWindow extends Disposable {
633629
// In wait mode, listen to changes to the editors and wait until the files
634630
// are closed that the user wants to wait for. When this happens we delete
635631
// the wait marker file to signal to the outside that editing is done.
636-
const waitMarkerFile = URI.revive(request.filesToWait.waitMarkerFileUri);
637-
const resourcesToWaitFor = coalesce(request.filesToWait.paths.map(p => URI.revive(p.fileUri)));
638-
this.trackClosedWaitFiles(waitMarkerFile, resourcesToWaitFor);
632+
this.trackClosedWaitFiles(URI.revive(request.filesToWait.waitMarkerFileUri), coalesce(request.filesToWait.paths.map(p => URI.revive(p.fileUri))));
639633
}
640634
}
641635

642-
private trackClosedWaitFiles(waitMarkerFile: URI, resourcesToWaitFor: URI[]): IDisposable {
643-
let remainingResourcesToWaitFor = resourcesToWaitFor.slice(0);
644-
645-
// In wait mode, listen to changes to the editors and wait until the files
646-
// are closed that the user wants to wait for. When this happens we delete
647-
// the wait marker file to signal to the outside that editing is done.
648-
const listener = this.editorService.onDidCloseEditor(async event => {
649-
const detailsResource = toResource(event.editor, { supportSideBySide: SideBySideEditor.DETAILS });
650-
const masterResource = toResource(event.editor, { supportSideBySide: SideBySideEditor.MASTER });
651-
652-
// Remove from resources to wait for based on the
653-
// resources from editors that got closed
654-
remainingResourcesToWaitFor = remainingResourcesToWaitFor.filter(resourceToWaitFor => {
655-
if (isEqual(resourceToWaitFor, masterResource) || isEqual(resourceToWaitFor, detailsResource)) {
656-
return false; // remove - the closing editor matches this resource
657-
}
658-
659-
return true; // keep - not yet closed
660-
});
661-
662-
if (remainingResourcesToWaitFor.length === 0) {
663-
// If auto save is configured with the default delay (1s) it is possible
664-
// to close the editor while the save still continues in the background. As such
665-
// we have to also check if the files to wait for are dirty and if so wait
666-
// for them to get saved before deleting the wait marker file.
667-
const dirtyFilesToWait = resourcesToWaitFor.filter(resourceToWaitFor => this.workingCopyService.isDirty(resourceToWaitFor));
668-
if (dirtyFilesToWait.length > 0) {
669-
await Promise.all(dirtyFilesToWait.map(async dirtyFileToWait => await this.joinResourceSaved(dirtyFileToWait)));
670-
}
671-
672-
listener.dispose();
673-
await this.fileService.del(waitMarkerFile);
674-
}
675-
});
676-
677-
return listener;
678-
}
679-
680-
private joinResourceSaved(resource: URI): Promise<void> {
681-
return new Promise(resolve => {
682-
if (!this.workingCopyService.isDirty(resource)) {
683-
return resolve(); // return early if resource is not dirty
684-
}
636+
private async trackClosedWaitFiles(waitMarkerFile: URI, resourcesToWaitFor: URI[]): Promise<void> {
685637

686-
// Otherwise resolve promise when resource is saved
687-
const listener = this.workingCopyService.onDidChangeDirty(workingCopy => {
688-
if (!workingCopy.isDirty() && isEqual(resource, workingCopy.resource)) {
689-
listener.dispose();
638+
// Wait for the resources to be closed in the editor...
639+
await this.editorService.whenClosed(resourcesToWaitFor);
690640

691-
resolve();
692-
}
693-
});
694-
});
641+
// ...before deleting the wait marker file
642+
await this.fileService.del(waitMarkerFile);
695643
}
696644

697645
private async openResources(resources: Array<IResourceEditorInput | IUntitledTextResourceEditorInput>, diffMode: boolean): Promise<unknown> {

src/vs/workbench/services/editor/browser/editorService.ts

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { IFileService, FileOperationEvent, FileOperation, FileChangesEvent, File
1515
import { Schemas } from 'vs/base/common/network';
1616
import { Event, Emitter } from 'vs/base/common/event';
1717
import { URI } from 'vs/base/common/uri';
18-
import { basename, isEqualOrParent, joinPath } from 'vs/base/common/resources';
18+
import { basename, isEqualOrParent, joinPath, isEqual } from 'vs/base/common/resources';
1919
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
2020
import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, GroupChangeKind, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService';
2121
import { IResourceEditorInputType, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler, IEditorService, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, ISaveEditorsOptions, ISaveAllEditorsOptions, IRevertAllEditorsOptions, IBaseSaveRevertAllEditorOptions, IOpenEditorOverrideEntry, ICustomEditorViewTypesHandler, ICustomEditorInfo } from 'vs/workbench/services/editor/common/editorService';
@@ -36,6 +36,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
3636
import { indexOfPath } from 'vs/base/common/extpath';
3737
import { DEFAULT_CUSTOM_EDITOR, updateViewTypeSchema, editorAssociationsConfigurationNode } from 'vs/workbench/services/editor/common/editorAssociationsSetting';
3838
import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
39+
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
3940

4041
type CachedEditorInput = ResourceEditorInput | IFileEditorInput | UntitledTextEditorInput;
4142
type OpenInEditorGroup = IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE;
@@ -72,7 +73,8 @@ export class EditorService extends Disposable implements EditorServiceImpl {
7273
@ILabelService private readonly labelService: ILabelService,
7374
@IFileService private readonly fileService: IFileService,
7475
@IConfigurationService private readonly configurationService: IConfigurationService,
75-
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService
76+
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
77+
@IWorkingCopyService private readonly workingCopyService: IWorkingCopyService
7678
) {
7779
super();
7880

@@ -1109,7 +1111,9 @@ export class EditorService extends Disposable implements EditorServiceImpl {
11091111
//#endregion
11101112

11111113
//#region Custom View Type
1112-
private customEditorViewTypesHandlers = new Map<string, ICustomEditorViewTypesHandler>();
1114+
1115+
private readonly customEditorViewTypesHandlers = new Map<string, ICustomEditorViewTypesHandler>();
1116+
11131117
registerCustomEditorViewTypesHandler(source: string, handler: ICustomEditorViewTypesHandler): IDisposable {
11141118
if (this.customEditorViewTypesHandlers.has(source)) {
11151119
throw new Error(`Use a different name for the custom editor component, ${source} is already occupied.`);
@@ -1150,6 +1154,64 @@ export class EditorService extends Disposable implements EditorServiceImpl {
11501154
}
11511155

11521156
//#endregion
1157+
1158+
//#region Editor Tracking
1159+
1160+
whenClosed(resources: URI[]): Promise<void> {
1161+
let remainingResources = [...resources];
1162+
1163+
return new Promise(resolve => {
1164+
const listener = this.onDidCloseEditor(async event => {
1165+
const detailsResource = toResource(event.editor, { supportSideBySide: SideBySideEditor.DETAILS });
1166+
const masterResource = toResource(event.editor, { supportSideBySide: SideBySideEditor.MASTER });
1167+
1168+
// Remove from resources to wait for being closed based on the
1169+
// resources from editors that got closed
1170+
remainingResources = remainingResources.filter(resource => {
1171+
if (isEqual(resource, masterResource) || isEqual(resource, detailsResource)) {
1172+
return false; // remove - the closing editor matches this resource
1173+
}
1174+
1175+
return true; // keep - not yet closed
1176+
});
1177+
1178+
if (remainingResources.length === 0) {
1179+
// If auto save is configured with the default delay (1s) it is possible
1180+
// to close the editor while the save still continues in the background. As such
1181+
// we have to also check if the files to track for are dirty and if so wait
1182+
// for them to get saved.
1183+
const dirtyFiles = resources.filter(resource => this.workingCopyService.isDirty(resource));
1184+
if (dirtyFiles.length > 0) {
1185+
await Promise.all(dirtyFiles.map(async dirtyFile => await this.joinResourceSaved(dirtyFile)));
1186+
}
1187+
1188+
listener.dispose();
1189+
1190+
resolve();
1191+
}
1192+
});
1193+
});
1194+
}
1195+
1196+
private joinResourceSaved(resource: URI): Promise<void> {
1197+
return new Promise(resolve => {
1198+
if (!this.workingCopyService.isDirty(resource)) {
1199+
return resolve(); // return early if resource is not dirty
1200+
}
1201+
1202+
// Otherwise resolve promise when resource is saved
1203+
const listener = this.workingCopyService.onDidChangeDirty(workingCopy => {
1204+
if (!workingCopy.isDirty() && isEqual(resource, workingCopy.resource)) {
1205+
listener.dispose();
1206+
1207+
resolve();
1208+
}
1209+
});
1210+
});
1211+
}
1212+
1213+
//#endregion
1214+
11531215
dispose(): void {
11541216
super.dispose();
11551217

@@ -1265,9 +1327,9 @@ export class DelegatingEditorService implements IEditorService {
12651327
revert(editors: IEditorIdentifier | IEditorIdentifier[], options?: IRevertOptions): Promise<boolean> { return this.editorService.revert(editors, options); }
12661328
revertAll(options?: IRevertAllEditorsOptions): Promise<boolean> { return this.editorService.revertAll(options); }
12671329

1268-
registerCustomEditorViewTypesHandler(source: string, handler: ICustomEditorViewTypesHandler): IDisposable {
1269-
throw new Error('Method not implemented.');
1270-
}
1330+
registerCustomEditorViewTypesHandler(source: string, handler: ICustomEditorViewTypesHandler): IDisposable { return this.editorService.registerCustomEditorViewTypesHandler(source, handler); }
1331+
1332+
whenClosed(resources: URI[]): Promise<void> { return this.editorService.whenClosed(resources); }
12711333

12721334
//#endregion
12731335
}

src/vs/workbench/services/editor/common/editorService.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,9 @@ export interface IEditorService {
240240
*/
241241
overrideOpenEditor(handler: IOpenEditorOverrideHandler): IDisposable;
242242

243+
/**
244+
* Register handlers for custom editor view types.
245+
*/
243246
registerCustomEditorViewTypesHandler(source: string, handler: ICustomEditorViewTypesHandler): IDisposable;
244247

245248
/**
@@ -279,4 +282,10 @@ export interface IEditorService {
279282
* @returns `true` if all editors reverted and `false` otherwise.
280283
*/
281284
revertAll(options?: IRevertAllEditorsOptions): Promise<boolean>;
285+
286+
/**
287+
* Track the provided list of resources for being opened as editors
288+
* and resolve once all have been closed.
289+
*/
290+
whenClosed(resources: URI[]): Promise<void>;
282291
}

src/vs/workbench/services/editor/test/browser/editorService.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,4 +1059,24 @@ suite('EditorService', () => {
10591059
handler.dispose();
10601060
part.dispose();
10611061
});
1062+
1063+
test('whenClosed', async function () {
1064+
const [part, service] = createEditorService();
1065+
1066+
const input1 = new TestFileEditorInput(URI.parse('file://resource1'), TEST_EDITOR_INPUT_ID);
1067+
const input2 = new TestFileEditorInput(URI.parse('file://resource2'), TEST_EDITOR_INPUT_ID);
1068+
1069+
await part.whenRestored;
1070+
1071+
const editor = await service.openEditor(input1, { pinned: true });
1072+
await service.openEditor(input2, { pinned: true });
1073+
1074+
const whenClosed = service.whenClosed([input1.resource, input2.resource]);
1075+
1076+
editor?.group?.closeAllEditors();
1077+
1078+
await whenClosed;
1079+
1080+
part.dispose();
1081+
});
10621082
});

src/vs/workbench/services/host/browser/browserHostService.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,19 @@ export class BrowserHostService extends Disposable implements IHostService {
217217
}
218218
}
219219
}
220+
221+
// Support wait mode
222+
const waitMarkerFileURI = options?.waitMarkerFileURI;
223+
if (waitMarkerFileURI) {
224+
(async () => {
225+
226+
// Wait for the resources to be closed in the editor...
227+
await this.editorService.whenClosed(fileOpenables.map(openable => openable.fileUri));
228+
229+
// ...before deleting the wait marker file
230+
await this.fileService.del(waitMarkerFileURI);
231+
})();
232+
}
220233
}
221234
}
222235

@@ -254,6 +267,10 @@ export class BrowserHostService extends Disposable implements IHostService {
254267
}
255268

256269
private shouldReuse(options: IOpenWindowOptions = Object.create(null), isFile: boolean): boolean {
270+
if (options.waitMarkerFileURI) {
271+
return true; // always handle --wait in same window
272+
}
273+
257274
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
258275
const openInNewWindowConfig = isFile ? (windowConfig?.openFilesInNewWindow || 'off' /* default */) : (windowConfig?.openFoldersInNewWindow || 'default' /* default */);
259276

src/vs/workbench/test/browser/workbenchTestServices.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,7 @@ export class TestEditorService implements EditorServiceImpl {
670670
saveAll(options?: ISaveEditorsOptions): Promise<boolean> { throw new Error('Method not implemented.'); }
671671
revert(editors: IEditorIdentifier[], options?: IRevertOptions): Promise<boolean> { throw new Error('Method not implemented.'); }
672672
revertAll(options?: IRevertAllEditorsOptions): Promise<boolean> { throw new Error('Method not implemented.'); }
673+
whenClosed(resources: URI[]): Promise<void> { throw new Error('Method not implemented.'); }
673674
}
674675

675676
export class TestFileService implements IFileService {

0 commit comments

Comments
 (0)