editorService.ts 28.6 KB
Newer Older
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
7
import { IResourceInput, ITextEditorOptions, IEditorOptions, EditorActivation } from 'vs/platform/editor/common/editor';
8
import { IEditorInput, IEditor, GroupIdentifier, IFileEditorInput, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, IFileInputFactory, EditorInput, SideBySideEditorInput, IEditorInputWithOptions, isEditorInputWithOptions, EditorOptions, TextEditorOptions, IEditorIdentifier, IEditorCloseEvent, ITextEditor, ITextDiffEditor, ITextSideBySideEditor, toResource, SideBySideEditor } from 'vs/workbench/common/editor';
9 10 11 12
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
import { DataUriEditorInput } from 'vs/workbench/common/editor/dataUriEditorInput';
import { Registry } from 'vs/platform/registry/common/platform';
import { ResourceMap } from 'vs/base/common/map';
13
import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
14 15
import { IFileService } from 'vs/platform/files/common/files';
import { Schemas } from 'vs/base/common/network';
J
Joao Moreno 已提交
16
import { Event, Emitter } from 'vs/base/common/event';
17
import { URI } from 'vs/base/common/uri';
18
import { basename, isEqual } from 'vs/base/common/resources';
19 20
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { localize } from 'vs/nls';
21 22
import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, GroupChangeKind, preferredSideBySideGroupDirection, EditorsOrder } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IResourceEditor, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler, IVisibleEditor, IEditorService, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, ISaveEditorsOptions, ISaveAllEditorsOptions } from 'vs/workbench/services/editor/common/editorService';
23
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
24
import { Disposable, IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
25
import { coalesce } from 'vs/base/common/arrays';
26
import { isCodeEditor, isDiffEditor, ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser';
27
import { IEditorGroupView, IEditorOpeningEvent, EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor';
I
isidor 已提交
28
import { ILabelService } from 'vs/platform/label/common/label';
29
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
30
import { withNullAsUndefined } from 'vs/base/common/types';
31
import { IRevertOptions } from 'vs/workbench/services/workingCopy/common/workingCopyService';
32

B
Benjamin Pasero 已提交
33 34
type CachedEditorInput = ResourceEditorInput | IFileEditorInput | DataUriEditorInput;
type OpenInEditorGroup = IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE;
35

B
Benjamin Pasero 已提交
36
export class EditorService extends Disposable implements EditorServiceImpl {
37

38
	_serviceBrand: undefined;
39

B
Benjamin Pasero 已提交
40
	private static CACHE: ResourceMap<CachedEditorInput> = new ResourceMap<CachedEditorInput>();
41

42 43
	//#region events

M
Matt Bierner 已提交
44
	private readonly _onDidActiveEditorChange: Emitter<void> = this._register(new Emitter<void>());
45
	readonly onDidActiveEditorChange: Event<void> = this._onDidActiveEditorChange.event;
46

M
Matt Bierner 已提交
47
	private readonly _onDidVisibleEditorsChange: Emitter<void> = this._register(new Emitter<void>());
48
	readonly onDidVisibleEditorsChange: Event<void> = this._onDidVisibleEditorsChange.event;
49

M
Matt Bierner 已提交
50
	private readonly _onDidCloseEditor: Emitter<IEditorCloseEvent> = this._register(new Emitter<IEditorCloseEvent>());
51
	readonly onDidCloseEditor: Event<IEditorCloseEvent> = this._onDidCloseEditor.event;
52

M
Matt Bierner 已提交
53
	private readonly _onDidOpenEditorFail: Emitter<IEditorIdentifier> = this._register(new Emitter<IEditorIdentifier>());
54
	readonly onDidOpenEditorFail: Event<IEditorIdentifier> = this._onDidOpenEditorFail.event;
55

56 57
	//#endregion

58
	private fileInputFactory: IFileInputFactory;
59
	private openEditorHandlers: IOpenEditorOverrideHandler[] = [];
B
Benjamin Pasero 已提交
60

61 62
	private lastActiveEditor: IEditorInput | null = null;
	private lastActiveGroupId: GroupIdentifier | null = null;
63

64
	constructor(
65
		@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,
66
		@IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService,
67 68 69 70
		@IInstantiationService private readonly instantiationService: IInstantiationService,
		@ILabelService private readonly labelService: ILabelService,
		@IFileService private readonly fileService: IFileService,
		@IConfigurationService private readonly configurationService: IConfigurationService
71
	) {
72 73
		super();

74
		this.fileInputFactory = Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).getFileInputFactory();
75 76 77 78 79

		this.registerListeners();
	}

	private registerListeners(): void {
80
		this.editorGroupService.whenRestored.then(() => this.onEditorsRestored());
81
		this.editorGroupService.onDidActiveGroupChange(group => this.handleActiveEditorChange(group));
82
		this.editorGroupService.onDidAddGroup(group => this.registerGroupListeners(group as IEditorGroupView));
83 84
	}

85 86 87 88 89 90 91 92 93 94 95 96
	private onEditorsRestored(): void {

		// Register listeners to each opened group
		this.editorGroupService.groups.forEach(group => this.registerGroupListeners(group as IEditorGroupView));

		// Fire initial set of editor events if there is an active editor
		if (this.activeEditor) {
			this.doEmitActiveEditorChangeEvent();
			this._onDidVisibleEditorsChange.fire();
		}
	}

97
	private handleActiveEditorChange(group: IEditorGroup): void {
98
		if (group !== this.editorGroupService.activeGroup) {
99
			return; // ignore if not the active group
100 101 102 103 104 105
		}

		if (!this.lastActiveEditor && !group.activeEditor) {
			return; // ignore if we still have no active editor
		}

B
Benjamin Pasero 已提交
106 107 108 109
		if (this.lastActiveGroupId === group.id && this.lastActiveEditor === group.activeEditor) {
			return; // ignore if the editor actually did not change
		}

110 111 112 113 114 115 116 117
		this.doEmitActiveEditorChangeEvent();
	}

	private doEmitActiveEditorChangeEvent(): void {
		const activeGroup = this.editorGroupService.activeGroup;

		this.lastActiveGroupId = activeGroup.id;
		this.lastActiveEditor = activeGroup.activeEditor;
118

119 120 121
		this._onDidActiveEditorChange.fire();
	}

122
	private registerGroupListeners(group: IEditorGroupView): void {
B
Benjamin Pasero 已提交
123
		const groupDisposables = new DisposableStore();
124

B
Benjamin Pasero 已提交
125
		groupDisposables.add(group.onDidGroupChange(e => {
126 127 128 129
			if (e.kind === GroupChangeKind.EDITOR_ACTIVE) {
				this.handleActiveEditorChange(group);
				this._onDidVisibleEditorsChange.fire();
			}
130 131
		}));

B
Benjamin Pasero 已提交
132
		groupDisposables.add(group.onDidCloseEditor(event => {
B
Benjamin Pasero 已提交
133
			this._onDidCloseEditor.fire(event);
134 135
		}));

B
Benjamin Pasero 已提交
136
		groupDisposables.add(group.onWillOpenEditor(event => {
137
			this.onGroupWillOpenEditor(group, event);
138 139
		}));

B
Benjamin Pasero 已提交
140
		groupDisposables.add(group.onDidOpenEditorFail(editor => {
I
isidor 已提交
141
			this._onDidOpenEditorFail.fire({ editor, groupId: group.id });
142 143
		}));

J
Joao Moreno 已提交
144
		Event.once(group.onWillDispose)(() => {
B
Benjamin Pasero 已提交
145
			dispose(groupDisposables);
146 147 148
		});
	}

149
	private onGroupWillOpenEditor(group: IEditorGroup, event: IEditorOpeningEvent): void {
150 151 152 153
		if (event.options && event.options.ignoreOverrides) {
			return;
		}

154
		for (const handler of this.openEditorHandlers) {
155
			const result = handler(event.editor, event.options, group);
M
Matt Bierner 已提交
156
			const override = result?.override;
157 158
			if (override) {
				event.prevent((() => override.then(editor => withNullAsUndefined(editor))));
159 160 161 162 163
				break;
			}
		}
	}

164
	get activeControl(): IVisibleEditor | undefined {
M
Matt Bierner 已提交
165
		return this.editorGroupService.activeGroup?.activeControl;
166 167
	}

168
	get activeTextEditorWidget(): ICodeEditor | IDiffEditor | undefined {
169 170 171
		const activeControl = this.activeControl;
		if (activeControl) {
			const activeControlWidget = activeControl.getControl();
172
			if (isCodeEditor(activeControlWidget) || isDiffEditor(activeControlWidget)) {
173 174 175 176
				return activeControlWidget;
			}
		}

R
Rob Lourens 已提交
177
		return undefined;
178 179
	}

180 181
	get editors(): IEditorInput[] {
		const editors: IEditorInput[] = [];
182
		this.editorGroupService.groups.forEach(group => {
183 184 185 186 187 188
			editors.push(...group.editors);
		});

		return editors;
	}

189
	get activeEditor(): IEditorInput | undefined {
190
		const activeGroup = this.editorGroupService.activeGroup;
191

M
Matt Bierner 已提交
192
		return activeGroup ? withNullAsUndefined(activeGroup.activeEditor) : undefined;
193 194
	}

M
Matt Bierner 已提交
195
	get visibleControls(): IVisibleEditor[] {
196
		return coalesce(this.editorGroupService.groups.map(group => group.activeControl));
197 198
	}

199
	get visibleTextEditorWidgets(): Array<ICodeEditor | IDiffEditor> {
200
		return this.visibleControls.map(control => control.getControl() as ICodeEditor | IDiffEditor).filter(widget => isCodeEditor(widget) || isDiffEditor(widget));
201 202
	}

203
	get visibleEditors(): IEditorInput[] {
204
		return coalesce(this.editorGroupService.groups.map(group => group.activeEditor));
205 206
	}

207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
	//#region preventOpenEditor()

	overrideOpenEditor(handler: IOpenEditorOverrideHandler): IDisposable {
		this.openEditorHandlers.push(handler);

		return toDisposable(() => {
			const index = this.openEditorHandlers.indexOf(handler);
			if (index >= 0) {
				this.openEditorHandlers.splice(index, 1);
			}
		});
	}

	//#endregion

222 223
	//#region openEditor()

B
Benjamin Pasero 已提交
224
	openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: OpenInEditorGroup): Promise<IEditor | undefined>;
225
	openEditor(editor: IResourceInput | IUntitledTextResourceInput, group?: OpenInEditorGroup): Promise<ITextEditor | undefined>;
B
Benjamin Pasero 已提交
226 227 228
	openEditor(editor: IResourceDiffInput, group?: OpenInEditorGroup): Promise<ITextDiffEditor | undefined>;
	openEditor(editor: IResourceSideBySideInput, group?: OpenInEditorGroup): Promise<ITextSideBySideEditor | undefined>;
	async openEditor(editor: IEditorInput | IResourceEditor, optionsOrGroup?: IEditorOptions | ITextEditorOptions | OpenInEditorGroup, group?: OpenInEditorGroup): Promise<IEditor | undefined> {
B
Benjamin Pasero 已提交
229 230 231
		let resolvedGroup: IEditorGroup | undefined;
		let candidateGroup: OpenInEditorGroup | undefined;

B
Benjamin Pasero 已提交
232 233
		let typedEditor: EditorInput | undefined;
		let typedOptions: EditorOptions | undefined;
234 235 236

		// Typed Editor Support
		if (editor instanceof EditorInput) {
B
Benjamin Pasero 已提交
237 238
			typedEditor = editor;
			typedOptions = this.toOptions(optionsOrGroup as IEditorOptions);
239

B
Benjamin Pasero 已提交
240 241
			candidateGroup = group;
			resolvedGroup = this.findTargetGroup(typedEditor, typedOptions, candidateGroup);
242 243 244
		}

		// Untyped Text Editor Support
B
Benjamin Pasero 已提交
245 246 247 248 249
		else {
			const textInput = <IResourceEditor>editor;
			typedEditor = this.createInput(textInput);
			if (typedEditor) {
				typedOptions = TextEditorOptions.from(textInput);
250

B
Benjamin Pasero 已提交
251 252 253 254 255 256
				candidateGroup = optionsOrGroup as OpenInEditorGroup;
				resolvedGroup = this.findTargetGroup(typedEditor, typedOptions, candidateGroup);
			}
		}

		if (typedEditor && resolvedGroup) {
B
Benjamin Pasero 已提交
257
			if (
258 259 260
				this.editorGroupService.activeGroup !== resolvedGroup && 	// only if target group is not already active
				typedOptions && !typedOptions.inactive &&					// never for inactive editors
				typedOptions.preserveFocus &&								// only if preserveFocus
261
				typeof typedOptions.activation !== 'number' &&				// only if activation is not already defined (either true or false)
262
				candidateGroup !== SIDE_GROUP								// never for the SIDE_GROUP
B
Benjamin Pasero 已提交
263
			) {
264 265 266
				// If the resolved group is not the active one, we typically
				// want the group to become active. There are a few cases
				// where we stay away from encorcing this, e.g. if the caller
267
				// is already providing `activation`.
268 269 270 271 272
				//
				// Specifically for historic reasons we do not activate a
				// group is it is opened as `SIDE_GROUP` with `preserveFocus:true`.
				// repeated Alt-clicking of files in the explorer always open
				// into the same side group and not cause a group to be created each time.
273
				typedOptions.overwrite({ activation: EditorActivation.ACTIVATE });
B
Benjamin Pasero 已提交
274
			}
B
Benjamin Pasero 已提交
275

B
Benjamin Pasero 已提交
276
			return this.doOpenEditor(resolvedGroup, typedEditor, typedOptions);
277 278
		}

B
Benjamin Pasero 已提交
279
		return undefined;
280
	}
281

B
Benjamin Pasero 已提交
282 283
	protected async doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise<IEditor | undefined> {
		return withNullAsUndefined(await group.openEditor(editor, options));
284 285
	}

B
Benjamin Pasero 已提交
286
	private findTargetGroup(input: IEditorInput, options?: IEditorOptions, group?: OpenInEditorGroup): IEditorGroup {
287
		let targetGroup: IEditorGroup | undefined;
288

B
Benjamin Pasero 已提交
289 290
		// Group: Instance of Group
		if (group && typeof group !== 'number') {
B
Benjamin Pasero 已提交
291
			targetGroup = group;
B
Benjamin Pasero 已提交
292 293
		}

294
		// Group: Side by Side
B
Benjamin Pasero 已提交
295
		else if (group === SIDE_GROUP) {
296
			targetGroup = this.findSideBySideGroup();
297 298
		}

299
		// Group: Specific Group
B
Benjamin Pasero 已提交
300
		else if (typeof group === 'number' && group >= 0) {
301
			targetGroup = this.editorGroupService.getGroup(group);
302 303
		}

304 305
		// Group: Unspecified without a specific index to open
		else if (!options || typeof options.index !== 'number') {
306
			const groupsByLastActive = this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE);
307 308

			// Respect option to reveal an editor if it is already visible in any group
B
Benjamin Pasero 已提交
309
			if (options?.revealIfVisible) {
310
				for (const group of groupsByLastActive) {
B
Benjamin Pasero 已提交
311
					if (group.isActive(input)) {
312 313 314 315 316 317 318
						targetGroup = group;
						break;
					}
				}
			}

			// Respect option to reveal an editor if it is open (not necessarily visible)
B
Benjamin Pasero 已提交
319
			// Still prefer to reveal an editor in a group where the editor is active though.
B
Benjamin Pasero 已提交
320
			if (!targetGroup) {
B
Benjamin Pasero 已提交
321
				if (options?.revealIfOpened || this.configurationService.getValue<boolean>('workbench.editor.revealIfOpen')) {
B
Benjamin Pasero 已提交
322 323 324
					let groupWithInputActive: IEditorGroup | undefined = undefined;
					let groupWithInputOpened: IEditorGroup | undefined = undefined;

B
Benjamin Pasero 已提交
325
					for (const group of groupsByLastActive) {
B
Benjamin Pasero 已提交
326 327 328 329 330 331 332 333
						if (group.isOpened(input)) {
							if (!groupWithInputOpened) {
								groupWithInputOpened = group;
							}

							if (!groupWithInputActive && group.isActive(input)) {
								groupWithInputActive = group;
							}
B
Benjamin Pasero 已提交
334
						}
B
Benjamin Pasero 已提交
335 336 337

						if (groupWithInputOpened && groupWithInputActive) {
							break; // we found all groups we wanted
B
Benjamin Pasero 已提交
338
						}
339
					}
B
Benjamin Pasero 已提交
340 341 342

					// Prefer a target group where the input is visible
					targetGroup = groupWithInputActive || groupWithInputOpened;
343 344 345 346 347
				}
			}
		}

		// Fallback to active group if target not valid
348
		if (!targetGroup) {
349
			targetGroup = this.editorGroupService.activeGroup;
350 351
		}

352 353 354
		return targetGroup;
	}

355
	private findSideBySideGroup(): IEditorGroup {
356
		const direction = preferredSideBySideGroupDirection(this.configurationService);
357

358
		let neighbourGroup = this.editorGroupService.findGroup({ direction });
359
		if (!neighbourGroup) {
360
			neighbourGroup = this.editorGroupService.addGroup(this.editorGroupService.activeGroup, direction);
361 362 363
		}

		return neighbourGroup;
364 365 366 367 368 369 370 371
	}

	private toOptions(options?: IEditorOptions | EditorOptions): EditorOptions {
		if (!options || options instanceof EditorOptions) {
			return options as EditorOptions;
		}

		const textOptions: ITextEditorOptions = options;
B
Benjamin Pasero 已提交
372
		if (textOptions.selection || textOptions.viewState) {
373 374 375 376 377 378
			return TextEditorOptions.create(options);
		}

		return EditorOptions.create(options);
	}

379 380
	//#endregion

381 382
	//#region openEditors()

B
Benjamin Pasero 已提交
383 384 385
	openEditors(editors: IEditorInputWithOptions[], group?: OpenInEditorGroup): Promise<IEditor[]>;
	openEditors(editors: IResourceEditor[], group?: OpenInEditorGroup): Promise<IEditor[]>;
	async openEditors(editors: Array<IEditorInputWithOptions | IResourceEditor>, group?: OpenInEditorGroup): Promise<IEditor[]> {
386 387 388 389 390 391 392 393 394 395 396 397

		// Convert to typed editors and options
		const typedEditors: IEditorInputWithOptions[] = [];
		editors.forEach(editor => {
			if (isEditorInputWithOptions(editor)) {
				typedEditors.push(editor);
			} else {
				typedEditors.push({ editor: this.createInput(editor), options: TextEditorOptions.from(editor) });
			}
		});

		// Find target groups to open
398
		const mapGroupToEditors = new Map<IEditorGroup, IEditorInputWithOptions[]>();
399
		if (group === SIDE_GROUP) {
400
			mapGroupToEditors.set(this.findSideBySideGroup(), typedEditors);
401 402 403 404 405 406 407 408 409 410 411 412 413 414
		} else {
			typedEditors.forEach(typedEditor => {
				const targetGroup = this.findTargetGroup(typedEditor.editor, typedEditor.options, group);

				let targetGroupEditors = mapGroupToEditors.get(targetGroup);
				if (!targetGroupEditors) {
					targetGroupEditors = [];
					mapGroupToEditors.set(targetGroup, targetGroupEditors);
				}

				targetGroupEditors.push(typedEditor);
			});
		}

B
Benjamin Pasero 已提交
415
		// Open in target groups
B
Benjamin Pasero 已提交
416
		const result: Promise<IEditor | null>[] = [];
417
		mapGroupToEditors.forEach((editorsWithOptions, group) => {
418
			result.push(group.openEditors(editorsWithOptions));
419
		});
420

B
Benjamin Pasero 已提交
421
		return coalesce(await Promise.all(result));
422 423 424 425
	}

	//#endregion

426 427
	//#region isOpen()

428
	isOpen(editor: IEditorInput | IResourceInput | IUntitledTextResourceInput): boolean {
429 430 431 432 433 434 435
		return !!this.doGetOpened(editor);
	}

	//#endregion

	//#region getOpend()

436
	getOpened(editor: IResourceInput | IUntitledTextResourceInput): IEditorInput | undefined {
437 438 439
		return this.doGetOpened(editor);
	}

440
	private doGetOpened(editor: IEditorInput | IResourceInput | IUntitledTextResourceInput): IEditorInput | undefined {
441
		if (!(editor instanceof EditorInput)) {
442
			const resourceInput = editor as IResourceInput | IUntitledTextResourceInput;
443
			if (!resourceInput.resource) {
R
Rob Lourens 已提交
444
				return undefined; // we need a resource at least
445 446 447 448
			}
		}

		// For each editor group
B
Benjamin Pasero 已提交
449
		for (const group of this.editorGroupService.groups) {
450 451

			// Typed editor
452
			if (editor instanceof EditorInput) {
453 454 455
				if (group.isOpened(editor)) {
					return editor;
				}
456 457
			}

458 459
			// Resource editor
			else {
460
				for (const editorInGroup of group.editors) {
B
Benjamin Pasero 已提交
461
					const resource = toResource(editorInGroup, { supportSideBySide: SideBySideEditor.MASTER });
B
Benjamin Pasero 已提交
462 463 464
					if (!resource) {
						continue; // need a resource to compare with
					}
465

466
					const resourceInput = editor as IResourceInput | IUntitledTextResourceInput;
467
					if (resourceInput.resource && isEqual(resource, resourceInput.resource)) {
468 469 470 471 472
						return editorInGroup;
					}
				}
			}
		}
473

R
Rob Lourens 已提交
474
		return undefined;
475 476 477 478
	}

	//#endregion

479 480
	//#region replaceEditors()

J
Johannes Rieken 已提交
481 482
	replaceEditors(editors: IResourceEditorReplacement[], group: IEditorGroup | GroupIdentifier): Promise<void>;
	replaceEditors(editors: IEditorReplacement[], group: IEditorGroup | GroupIdentifier): Promise<void>;
483
	replaceEditors(editors: Array<IEditorReplacement | IResourceEditorReplacement>, group: IEditorGroup | GroupIdentifier): Promise<void> {
484 485 486 487 488 489 490
		const typedEditors: IEditorReplacement[] = [];

		editors.forEach(replaceEditorArg => {
			if (replaceEditorArg.editor instanceof EditorInput) {
				typedEditors.push(replaceEditorArg as IEditorReplacement);
			} else {
				const editor = replaceEditorArg.editor as IResourceEditor;
B
Benjamin Pasero 已提交
491
				const replacement = replaceEditorArg.replacement as IResourceEditor;
492
				const typedEditor = this.createInput(editor);
B
Benjamin Pasero 已提交
493
				const typedReplacement = this.createInput(replacement);
494 495 496

				typedEditors.push({
					editor: typedEditor,
B
Benjamin Pasero 已提交
497 498
					replacement: typedReplacement,
					options: this.toOptions(replacement.options)
499 500 501 502
				});
			}
		});

503
		const targetGroup = typeof group === 'number' ? this.editorGroupService.getGroup(group) : group;
B
Benjamin Pasero 已提交
504 505 506 507 508
		if (targetGroup) {
			return targetGroup.replaceEditors(typedEditors);
		}

		return Promise.resolve();
509 510 511 512
	}

	//#endregion

513 514 515
	//#region invokeWithinEditorContext()

	invokeWithinEditorContext<T>(fn: (accessor: ServicesAccessor) => T): T {
516 517 518
		const activeTextEditorWidget = this.activeTextEditorWidget;
		if (isCodeEditor(activeTextEditorWidget)) {
			return activeTextEditorWidget.invokeWithinContext(fn);
519 520
		}

521
		const activeGroup = this.editorGroupService.activeGroup;
522 523 524 525 526
		if (activeGroup) {
			return activeGroup.invokeWithinContext(fn);
		}

		return this.instantiationService.invokeFunction(fn);
527 528 529 530
	}

	//#endregion

531 532
	//#region createInput()

B
Benjamin Pasero 已提交
533
	createInput(input: IEditorInputWithOptions | IEditorInput | IResourceEditor): EditorInput {
534

535
		// Typed Editor Input Support (EditorInput)
536 537 538 539
		if (input instanceof EditorInput) {
			return input;
		}

540 541 542 543 544 545
		// Typed Editor Input Support (IEditorInputWithOptions)
		const editorInputWithOptions = input as IEditorInputWithOptions;
		if (editorInputWithOptions.editor instanceof EditorInput) {
			return editorInputWithOptions.editor;
		}

546
		// Side by Side Support
M
Matt Bierner 已提交
547
		const resourceSideBySideInput = input as IResourceSideBySideInput;
548
		if (resourceSideBySideInput.masterResource && resourceSideBySideInput.detailResource) {
B
Benjamin Pasero 已提交
549 550
			const masterInput = this.createInput({ resource: resourceSideBySideInput.masterResource, forceFile: resourceSideBySideInput.forceFile });
			const detailInput = this.createInput({ resource: resourceSideBySideInput.detailResource, forceFile: resourceSideBySideInput.forceFile });
B
Benjamin Pasero 已提交
551
			const label = resourceSideBySideInput.label || masterInput.getName() || localize('sideBySideLabels', "{0} - {1}", this.toDiffLabel(masterInput), this.toDiffLabel(detailInput));
552 553

			return new SideBySideEditorInput(
B
Benjamin Pasero 已提交
554
				label,
555 556 557 558 559 560 561
				typeof resourceSideBySideInput.description === 'string' ? resourceSideBySideInput.description : masterInput.getDescription(),
				detailInput,
				masterInput
			);
		}

		// Diff Editor Support
M
Matt Bierner 已提交
562
		const resourceDiffInput = input as IResourceDiffInput;
563
		if (resourceDiffInput.leftResource && resourceDiffInput.rightResource) {
B
Benjamin Pasero 已提交
564 565
			const leftInput = this.createInput({ resource: resourceDiffInput.leftResource, forceFile: resourceDiffInput.forceFile });
			const rightInput = this.createInput({ resource: resourceDiffInput.rightResource, forceFile: resourceDiffInput.forceFile });
566
			const label = resourceDiffInput.label || localize('compareLabels', "{0} ↔ {1}", this.toDiffLabel(leftInput), this.toDiffLabel(rightInput));
567

568
			return new DiffEditorInput(label, resourceDiffInput.description, leftInput, rightInput);
569 570 571
		}

		// Untitled file support
572
		const untitledInput = input as IUntitledTextResourceInput;
573
		if (untitledInput.forceUntitled || !untitledInput.resource || (untitledInput.resource && untitledInput.resource.scheme === Schemas.untitled)) {
574
			return this.untitledTextEditorService.createOrGet(untitledInput.resource, untitledInput.mode, untitledInput.contents, untitledInput.encoding);
575 576 577
		}

		// Resource Editor Support
M
Matt Bierner 已提交
578
		const resourceInput = input as IResourceInput;
579 580 581
		if (resourceInput.resource instanceof URI) {
			let label = resourceInput.label;
			if (!label && resourceInput.resource.scheme !== Schemas.data) {
B
Benjamin Pasero 已提交
582
				label = basename(resourceInput.resource); // derive the label from the path (but not for data URIs)
583 584
			}

585
			return this.createOrGet(resourceInput.resource, this.instantiationService, label, resourceInput.description, resourceInput.encoding, resourceInput.mode, resourceInput.forceFile) as EditorInput;
586 587
		}

B
Benjamin Pasero 已提交
588
		throw new Error('Unknown input type');
589 590
	}

B
Benjamin Pasero 已提交
591
	private createOrGet(resource: URI, instantiationService: IInstantiationService, label: string | undefined, description: string | undefined, encoding: string | undefined, mode: string | undefined, forceFile: boolean | undefined): CachedEditorInput {
592
		if (EditorService.CACHE.has(resource)) {
B
Benjamin Pasero 已提交
593
			const input = EditorService.CACHE.get(resource)!;
594
			if (input instanceof ResourceEditorInput) {
B
Benjamin Pasero 已提交
595 596 597 598 599 600 601
				if (label) {
					input.setName(label);
				}

				if (description) {
					input.setDescription(description);
				}
602 603 604 605

				if (mode) {
					input.setPreferredMode(mode);
				}
606
			} else if (!(input instanceof DataUriEditorInput)) {
B
Benjamin Pasero 已提交
607 608 609
				if (encoding) {
					input.setPreferredEncoding(encoding);
				}
610 611 612 613

				if (mode) {
					input.setPreferredMode(mode);
				}
614 615 616 617 618 619
			}

			return input;
		}

		// File
B
Benjamin Pasero 已提交
620
		let input: CachedEditorInput;
B
Benjamin Pasero 已提交
621
		if (forceFile /* fix for https://github.com/Microsoft/vscode/issues/48275 */ || this.fileService.canHandleResource(resource)) {
622
			input = this.fileInputFactory.createFileInput(resource, encoding, mode, instantiationService);
623 624 625 626
		}

		// Data URI
		else if (resource.scheme === Schemas.data) {
B
Benjamin Pasero 已提交
627
			input = instantiationService.createInstance(DataUriEditorInput, label || basename(resource), description, resource);
628 629 630 631
		}

		// Resource
		else {
632
			input = instantiationService.createInstance(ResourceEditorInput, label, description, resource, mode);
633 634
		}

635
		// Add to cache and remove when input gets disposed
636
		EditorService.CACHE.set(resource, input);
637
		Event.once(input.onDispose)(() => EditorService.CACHE.delete(resource));
638 639 640 641

		return input;
	}

B
Benjamin Pasero 已提交
642
	private toDiffLabel(input: EditorInput): string | undefined {
643
		const res = input.getResource();
B
Benjamin Pasero 已提交
644
		if (!res) {
B
Benjamin Pasero 已提交
645
			return undefined;
B
Benjamin Pasero 已提交
646
		}
647

648 649
		// Do not try to extract any paths from simple untitled text editors
		if (res.scheme === Schemas.untitled && !this.untitledTextEditorService.hasAssociatedFilePath(res)) {
650 651 652 653
			return input.getName();
		}

		// Otherwise: for diff labels prefer to see the path as part of the label
654
		return this.labelService.getUriLabel(res, { relative: true });
655
	}
656 657

	//#endregion
658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744

	//#region save

	async save(editors: IEditorIdentifier | IEditorIdentifier[], options?: ISaveEditorsOptions): Promise<boolean> {

		// Convert to array
		if (!Array.isArray(editors)) {
			editors = [editors];
		}

		// Split editors up into a bucket that is saved in parallel
		// and sequentially. Unless "Save As", all non-untitled editors
		// can be saved in parallel to speed up the operation. Remaining
		// editors are potentially bringing up some UI and thus run
		// sequentially.
		const editorsToSaveParallel: IEditorIdentifier[] = [];
		const editorsToSaveAsSequentially: IEditorIdentifier[] = [];
		if (options?.saveAs) {
			editorsToSaveAsSequentially.push(...editors);
		} else {
			for (const { groupId, editor } of editors) {
				if (editor.isUntitled()) {
					editorsToSaveAsSequentially.push({ groupId, editor });
				} else {
					editorsToSaveParallel.push({ groupId, editor });
				}
			}
		}

		// Editors to save in parallel
		await Promise.all(editorsToSaveParallel.map(({ groupId, editor }) => {

			// Use save as a hint to pin the editor
			this.editorGroupService.getGroup(groupId)?.pinEditor(editor);

			// Save
			return editor.save(options);
		}));

		// Editors to save sequentially
		for (const { groupId, editor } of editorsToSaveAsSequentially) {
			if (editor.isDisposed()) {
				continue; // might have been disposed from from the save already
			}

			const result = await editor.saveAs(groupId, options);
			if (!result) {
				return false; // failed or cancelled, abort
			}
		}

		return true;
	}

	saveAll(options?: ISaveAllEditorsOptions): Promise<boolean> {
		const editors: IEditorIdentifier[] = [];

		// Collect all editors in MRU order that are dirty
		this.forEachDirtyEditor(({ groupId, editor }) => {
			if (!editor.isUntitled() || options?.includeUntitled) {
				editors.push({ groupId, editor });
			}
		});

		return this.save(editors, options);
	}

	async revertAll(options?: IRevertOptions): Promise<void> {

		// Revert each editor in MRU order
		const reverts: Promise<boolean>[] = [];
		this.forEachDirtyEditor(({ editor }) => reverts.push(editor.revert(options)));

		await Promise.all(reverts);
	}

	private forEachDirtyEditor(callback: (editor: IEditorIdentifier) => void): void {
		for (const group of this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) {
			for (const editor of group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)) {
				if (editor.isDirty()) {
					callback({ groupId: group.id, editor });
				}
			}
		}
	}

	//#endregion
745 746 747
}

export interface IEditorOpenHandler {
B
Benjamin Pasero 已提交
748 749 750 751 752 753
	(
		delegate: (group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions) => Promise<IEditor | undefined>,
		group: IEditorGroup,
		editor: IEditorInput,
		options?: IEditorOptions | ITextEditorOptions
	): Promise<IEditor | null>;
754 755 756 757 758 759
}

/**
 * The delegating workbench editor service can be used to override the behaviour of the openEditor()
 * method by providing a IEditorOpenHandler.
 */
B
Benjamin Pasero 已提交
760
export class DelegatingEditorService extends EditorService {
761
	private editorOpenHandler: IEditorOpenHandler | undefined;
762 763

	constructor(
764
		@IEditorGroupsService editorGroupService: IEditorGroupsService,
765
		@IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService,
766
		@IInstantiationService instantiationService: IInstantiationService,
I
isidor 已提交
767
		@ILabelService labelService: ILabelService,
768 769 770 771
		@IFileService fileService: IFileService,
		@IConfigurationService configurationService: IConfigurationService
	) {
		super(
772
			editorGroupService,
773
			untitledTextEditorService,
774
			instantiationService,
I
isidor 已提交
775
			labelService,
776 777 778 779 780 781 782 783 784
			fileService,
			configurationService
		);
	}

	setEditorOpenHandler(handler: IEditorOpenHandler): void {
		this.editorOpenHandler = handler;
	}

785
	protected async doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise<IEditor | undefined> {
B
Benjamin Pasero 已提交
786 787 788
		if (!this.editorOpenHandler) {
			return super.doOpenEditor(group, editor, options);
		}
789

B
Benjamin Pasero 已提交
790 791 792 793 794 795 796
		const control = await this.editorOpenHandler(
			(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions) => super.doOpenEditor(group, editor, options),
			group,
			editor,
			options
		);

797 798 799
		if (control) {
			return control; // the opening was handled, so return early
		}
800

801
		return super.doOpenEditor(group, editor, options);
802
	}
I
isidor 已提交
803
}
804

B
Benjamin Pasero 已提交
805
registerSingleton(IEditorService, EditorService);