editorService.ts 33.5 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';
B
Benjamin Pasero 已提交
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, IRevertOptions, SaveReason, EditorsOrder } from 'vs/workbench/common/editor';
9 10 11
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
import { Registry } from 'vs/platform/registry/common/platform';
import { ResourceMap } from 'vs/base/common/map';
12
import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
13 14
import { IFileService } from 'vs/platform/files/common/files';
import { Schemas } from 'vs/base/common/network';
J
Joao Moreno 已提交
15
import { Event, Emitter } from 'vs/base/common/event';
16
import { URI } from 'vs/base/common/uri';
17
import { basename, isEqual } from 'vs/base/common/resources';
18 19
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { localize } from 'vs/nls';
B
Benjamin Pasero 已提交
20
import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, GroupChangeKind, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService';
B
Benjamin Pasero 已提交
21
import { IResourceEditor, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler, IVisibleEditor, IEditorService, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, ISaveEditorsOptions, ISaveAllEditorsOptions, IRevertAllEditorsOptions, IBaseSaveRevertAllEditorOptions } from 'vs/workbench/services/editor/common/editorService';
22
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
23
import { Disposable, IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
24
import { coalesce } from 'vs/base/common/arrays';
25
import { isCodeEditor, isDiffEditor, ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser';
26
import { IEditorGroupView, IEditorOpeningEvent, EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor';
I
isidor 已提交
27
import { ILabelService } from 'vs/platform/label/common/label';
28
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
29
import { withNullAsUndefined } from 'vs/base/common/types';
30
import { EditorsObserver } from 'vs/workbench/browser/parts/editor/editorsObserver';
31

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

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

37
	_serviceBrand: undefined;
38

39
	private static CACHE = new ResourceMap<CachedEditorInput>();
40

41 42
	//#region events

43 44
	private readonly _onDidActiveEditorChange = this._register(new Emitter<void>());
	readonly onDidActiveEditorChange = this._onDidActiveEditorChange.event;
45

46 47
	private readonly _onDidVisibleEditorsChange = this._register(new Emitter<void>());
	readonly onDidVisibleEditorsChange = this._onDidVisibleEditorsChange.event;
48

49 50
	private readonly _onDidCloseEditor = this._register(new Emitter<IEditorCloseEvent>());
	readonly onDidCloseEditor = this._onDidCloseEditor.event;
51

52 53
	private readonly _onDidOpenEditorFail = this._register(new Emitter<IEditorIdentifier>());
	readonly onDidOpenEditorFail = this._onDidOpenEditorFail.event;
54

55 56 57
	private readonly _onDidMostRecentlyActiveEditorsChange = this._register(new Emitter<void>());
	readonly onDidMostRecentlyActiveEditorsChange = this._onDidMostRecentlyActiveEditorsChange.event;

58 59
	//#endregion

60
	private fileInputFactory: IFileInputFactory;
61
	private readonly openEditorHandlers: IOpenEditorOverrideHandler[] = [];
B
Benjamin Pasero 已提交
62

B
Benjamin Pasero 已提交
63
	private lastActiveEditor: IEditorInput | undefined = undefined;
64

65 66
	private readonly editorsObserver = this._register(this.instantiationService.createInstance(EditorsObserver));

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

77
		this.fileInputFactory = Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).getFileInputFactory();
78 79 80 81 82

		this.registerListeners();
	}

	private registerListeners(): void {
83 84

		// Editor & group changes
85
		this.editorGroupService.whenRestored.then(() => this.onEditorsRestored());
86
		this.editorGroupService.onDidActiveGroupChange(group => this.handleActiveEditorChange(group));
87
		this.editorGroupService.onDidAddGroup(group => this.registerGroupListeners(group as IEditorGroupView));
88
		this.editorsObserver.onDidChange(() => this._onDidMostRecentlyActiveEditorsChange.fire());
89 90
	}

91 92 93 94 95 96 97
	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) {
98
			this.doHandleActiveEditorChangeEvent();
99 100 101 102
			this._onDidVisibleEditorsChange.fire();
		}
	}

103
	private handleActiveEditorChange(group: IEditorGroup): void {
104
		if (group !== this.editorGroupService.activeGroup) {
105
			return; // ignore if not the active group
106 107 108 109 110 111
		}

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

112
		this.doHandleActiveEditorChangeEvent();
113 114
	}

115
	private doHandleActiveEditorChangeEvent(): void {
116

117 118
		// Remember as last active
		const activeGroup = this.editorGroupService.activeGroup;
B
Benjamin Pasero 已提交
119
		this.lastActiveEditor = withNullAsUndefined(activeGroup.activeEditor);
120

121
		// Fire event to outside parties
122 123 124
		this._onDidActiveEditorChange.fire();
	}

125
	private registerGroupListeners(group: IEditorGroupView): void {
B
Benjamin Pasero 已提交
126
		const groupDisposables = new DisposableStore();
127

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

B
Benjamin Pasero 已提交
135
		groupDisposables.add(group.onDidCloseEditor(event => {
B
Benjamin Pasero 已提交
136
			this._onDidCloseEditor.fire(event);
137 138
		}));

B
Benjamin Pasero 已提交
139
		groupDisposables.add(group.onWillOpenEditor(event => {
140
			this.onGroupWillOpenEditor(group, event);
141 142
		}));

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

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

152
	private onGroupWillOpenEditor(group: IEditorGroup, event: IEditorOpeningEvent): void {
153 154 155 156
		if (event.options && event.options.ignoreOverrides) {
			return;
		}

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

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

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

R
Rob Lourens 已提交
180
		return undefined;
181 182
	}

183 184 185 186 187 188 189 190 191 192 193 194 195
	get activeTextEditorMode(): string | undefined {
		let activeCodeEditor: ICodeEditor | undefined = undefined;

		const activeTextEditorWidget = this.activeTextEditorWidget;
		if (isDiffEditor(activeTextEditorWidget)) {
			activeCodeEditor = activeTextEditorWidget.getModifiedEditor();
		} else {
			activeCodeEditor = activeTextEditorWidget;
		}

		return activeCodeEditor?.getModel()?.getLanguageIdentifier().language;
	}

B
Benjamin Pasero 已提交
196 197 198 199
	get count(): number {
		return this.editorsObserver.count;
	}

200
	get editors(): IEditorInput[] {
B
Benjamin Pasero 已提交
201 202 203 204 205 206 207 208 209 210 211 212
		return this.getEditors(EditorsOrder.SEQUENTIAL).map(({ editor }) => editor);
	}

	getEditors(order: EditorsOrder): ReadonlyArray<IEditorIdentifier> {
		if (order === EditorsOrder.MOST_RECENTLY_ACTIVE) {
			return this.editorsObserver.editors;
		}

		const editors: IEditorIdentifier[] = [];

		this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE).forEach(group => {
			editors.push(...group.getEditors(EditorsOrder.SEQUENTIAL).map(editor => ({ editor, groupId: group.id })));
213 214 215 216 217
		});

		return editors;
	}

218
	get activeEditor(): IEditorInput | undefined {
219
		const activeGroup = this.editorGroupService.activeGroup;
220

M
Matt Bierner 已提交
221
		return activeGroup ? withNullAsUndefined(activeGroup.activeEditor) : undefined;
222 223
	}

M
Matt Bierner 已提交
224
	get visibleControls(): IVisibleEditor[] {
225
		return coalesce(this.editorGroupService.groups.map(group => group.activeControl));
226 227
	}

228
	get visibleTextEditorWidgets(): Array<ICodeEditor | IDiffEditor> {
229
		return this.visibleControls.map(control => control.getControl() as ICodeEditor | IDiffEditor).filter(widget => isCodeEditor(widget) || isDiffEditor(widget));
230 231
	}

232
	get visibleEditors(): IEditorInput[] {
233
		return coalesce(this.editorGroupService.groups.map(group => group.activeEditor));
234 235
	}

236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
	//#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

251 252
	//#region openEditor()

B
Benjamin Pasero 已提交
253
	openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: OpenInEditorGroup): Promise<IEditor | undefined>;
254
	openEditor(editor: IResourceInput | IUntitledTextResourceInput, group?: OpenInEditorGroup): Promise<ITextEditor | undefined>;
B
Benjamin Pasero 已提交
255 256 257
	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> {
258 259 260 261 262 263 264 265 266 267 268
		const result = this.doResolveEditorOpenRequest(editor, optionsOrGroup, group);
		if (result) {
			const [resolvedGroup, resolvedEditor, resolvedOptions] = result;

			return withNullAsUndefined(await resolvedGroup.openEditor(resolvedEditor, resolvedOptions));
		}

		return undefined;
	}

	doResolveEditorOpenRequest(editor: IEditorInput | IResourceEditor, optionsOrGroup?: IEditorOptions | ITextEditorOptions | OpenInEditorGroup, group?: OpenInEditorGroup): [IEditorGroup, EditorInput, EditorOptions | undefined] | undefined {
B
Benjamin Pasero 已提交
269 270 271
		let resolvedGroup: IEditorGroup | undefined;
		let candidateGroup: OpenInEditorGroup | undefined;

B
Benjamin Pasero 已提交
272 273
		let typedEditor: EditorInput | undefined;
		let typedOptions: EditorOptions | undefined;
274 275 276

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

B
Benjamin Pasero 已提交
280 281
			candidateGroup = group;
			resolvedGroup = this.findTargetGroup(typedEditor, typedOptions, candidateGroup);
282 283 284
		}

		// Untyped Text Editor Support
B
Benjamin Pasero 已提交
285 286 287 288 289
		else {
			const textInput = <IResourceEditor>editor;
			typedEditor = this.createInput(textInput);
			if (typedEditor) {
				typedOptions = TextEditorOptions.from(textInput);
290

B
Benjamin Pasero 已提交
291 292 293 294 295 296
				candidateGroup = optionsOrGroup as OpenInEditorGroup;
				resolvedGroup = this.findTargetGroup(typedEditor, typedOptions, candidateGroup);
			}
		}

		if (typedEditor && resolvedGroup) {
B
Benjamin Pasero 已提交
297
			if (
298 299 300
				this.editorGroupService.activeGroup !== resolvedGroup && 	// only if target group is not already active
				typedOptions && !typedOptions.inactive &&					// never for inactive editors
				typedOptions.preserveFocus &&								// only if preserveFocus
301
				typeof typedOptions.activation !== 'number' &&				// only if activation is not already defined (either true or false)
302
				candidateGroup !== SIDE_GROUP								// never for the SIDE_GROUP
B
Benjamin Pasero 已提交
303
			) {
304 305 306
				// 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
307
				// is already providing `activation`.
308 309 310 311 312
				//
				// 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.
313
				typedOptions.overwrite({ activation: EditorActivation.ACTIVATE });
B
Benjamin Pasero 已提交
314
			}
B
Benjamin Pasero 已提交
315

316
			return [resolvedGroup, typedEditor, typedOptions];
317 318
		}

B
Benjamin Pasero 已提交
319
		return undefined;
320
	}
321

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

B
Benjamin Pasero 已提交
325 326
		// Group: Instance of Group
		if (group && typeof group !== 'number') {
B
Benjamin Pasero 已提交
327
			targetGroup = group;
B
Benjamin Pasero 已提交
328 329
		}

330
		// Group: Side by Side
B
Benjamin Pasero 已提交
331
		else if (group === SIDE_GROUP) {
332
			targetGroup = this.findSideBySideGroup();
333 334
		}

335
		// Group: Specific Group
B
Benjamin Pasero 已提交
336
		else if (typeof group === 'number' && group >= 0) {
337
			targetGroup = this.editorGroupService.getGroup(group);
338 339
		}

340 341
		// Group: Unspecified without a specific index to open
		else if (!options || typeof options.index !== 'number') {
342
			const groupsByLastActive = this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE);
343 344

			// Respect option to reveal an editor if it is already visible in any group
B
Benjamin Pasero 已提交
345
			if (options?.revealIfVisible) {
346
				for (const group of groupsByLastActive) {
B
Benjamin Pasero 已提交
347
					if (group.isActive(input)) {
348 349 350 351 352 353 354
						targetGroup = group;
						break;
					}
				}
			}

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

B
Benjamin Pasero 已提交
361
					for (const group of groupsByLastActive) {
B
Benjamin Pasero 已提交
362 363 364 365 366 367 368 369
						if (group.isOpened(input)) {
							if (!groupWithInputOpened) {
								groupWithInputOpened = group;
							}

							if (!groupWithInputActive && group.isActive(input)) {
								groupWithInputActive = group;
							}
B
Benjamin Pasero 已提交
370
						}
B
Benjamin Pasero 已提交
371 372 373

						if (groupWithInputOpened && groupWithInputActive) {
							break; // we found all groups we wanted
B
Benjamin Pasero 已提交
374
						}
375
					}
B
Benjamin Pasero 已提交
376 377 378

					// Prefer a target group where the input is visible
					targetGroup = groupWithInputActive || groupWithInputOpened;
379 380 381 382 383
				}
			}
		}

		// Fallback to active group if target not valid
384
		if (!targetGroup) {
385
			targetGroup = this.editorGroupService.activeGroup;
386 387
		}

388 389 390
		return targetGroup;
	}

391
	private findSideBySideGroup(): IEditorGroup {
392
		const direction = preferredSideBySideGroupDirection(this.configurationService);
393

394
		let neighbourGroup = this.editorGroupService.findGroup({ direction });
395
		if (!neighbourGroup) {
396
			neighbourGroup = this.editorGroupService.addGroup(this.editorGroupService.activeGroup, direction);
397 398 399
		}

		return neighbourGroup;
400 401
	}

B
Benjamin Pasero 已提交
402
	private toOptions(options?: IEditorOptions | ITextEditorOptions | EditorOptions): EditorOptions {
403 404 405 406 407
		if (!options || options instanceof EditorOptions) {
			return options as EditorOptions;
		}

		const textOptions: ITextEditorOptions = options;
B
Benjamin Pasero 已提交
408
		if (textOptions.selection || textOptions.viewState) {
409 410 411 412 413 414
			return TextEditorOptions.create(options);
		}

		return EditorOptions.create(options);
	}

415 416
	//#endregion

417 418
	//#region openEditors()

B
Benjamin Pasero 已提交
419 420 421
	openEditors(editors: IEditorInputWithOptions[], group?: OpenInEditorGroup): Promise<IEditor[]>;
	openEditors(editors: IResourceEditor[], group?: OpenInEditorGroup): Promise<IEditor[]>;
	async openEditors(editors: Array<IEditorInputWithOptions | IResourceEditor>, group?: OpenInEditorGroup): Promise<IEditor[]> {
422 423 424 425 426 427 428 429 430 431 432 433

		// 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
434
		const mapGroupToEditors = new Map<IEditorGroup, IEditorInputWithOptions[]>();
435
		if (group === SIDE_GROUP) {
436
			mapGroupToEditors.set(this.findSideBySideGroup(), typedEditors);
437 438 439 440 441 442 443 444 445 446 447 448 449 450
		} 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 已提交
451
		// Open in target groups
B
Benjamin Pasero 已提交
452
		const result: Promise<IEditor | null>[] = [];
453
		mapGroupToEditors.forEach((editorsWithOptions, group) => {
454
			result.push(group.openEditors(editorsWithOptions));
455
		});
456

B
Benjamin Pasero 已提交
457
		return coalesce(await Promise.all(result));
458 459 460 461
	}

	//#endregion

462 463
	//#region isOpen()

464
	isOpen(editor: IEditorInput | IResourceInput | IUntitledTextResourceInput): boolean {
465 466 467 468 469 470 471
		return !!this.doGetOpened(editor);
	}

	//#endregion

	//#region getOpend()

472
	getOpened(editor: IResourceInput | IUntitledTextResourceInput): IEditorInput | undefined {
473 474 475
		return this.doGetOpened(editor);
	}

476
	private doGetOpened(editor: IEditorInput | IResourceInput | IUntitledTextResourceInput): IEditorInput | undefined {
477
		if (!(editor instanceof EditorInput)) {
478
			const resourceInput = editor as IResourceInput | IUntitledTextResourceInput;
479
			if (!resourceInput.resource) {
R
Rob Lourens 已提交
480
				return undefined; // we need a resource at least
481 482 483 484
			}
		}

		// For each editor group
B
Benjamin Pasero 已提交
485
		for (const group of this.editorGroupService.groups) {
486 487

			// Typed editor
488
			if (editor instanceof EditorInput) {
489 490 491
				if (group.isOpened(editor)) {
					return editor;
				}
492 493
			}

494 495
			// Resource editor
			else {
496
				for (const editorInGroup of group.editors) {
B
Benjamin Pasero 已提交
497
					const resource = toResource(editorInGroup, { supportSideBySide: SideBySideEditor.MASTER });
B
Benjamin Pasero 已提交
498 499 500
					if (!resource) {
						continue; // need a resource to compare with
					}
501

502
					const resourceInput = editor as IResourceInput | IUntitledTextResourceInput;
503
					if (resourceInput.resource && isEqual(resource, resourceInput.resource)) {
504 505 506 507 508
						return editorInGroup;
					}
				}
			}
		}
509

R
Rob Lourens 已提交
510
		return undefined;
511 512 513 514
	}

	//#endregion

515 516
	//#region replaceEditors()

J
Johannes Rieken 已提交
517 518
	replaceEditors(editors: IResourceEditorReplacement[], group: IEditorGroup | GroupIdentifier): Promise<void>;
	replaceEditors(editors: IEditorReplacement[], group: IEditorGroup | GroupIdentifier): Promise<void>;
519
	replaceEditors(editors: Array<IEditorReplacement | IResourceEditorReplacement>, group: IEditorGroup | GroupIdentifier): Promise<void> {
520 521 522 523
		const typedEditors: IEditorReplacement[] = [];

		editors.forEach(replaceEditorArg => {
			if (replaceEditorArg.editor instanceof EditorInput) {
B
Benjamin Pasero 已提交
524 525 526 527 528 529 530
				const replacementArg = replaceEditorArg as IEditorReplacement;

				typedEditors.push({
					editor: replacementArg.editor,
					replacement: replacementArg.replacement,
					options: this.toOptions(replacementArg.options)
				});
531
			} else {
B
Benjamin Pasero 已提交
532
				const replacementArg = replaceEditorArg as IResourceEditorReplacement;
533 534

				typedEditors.push({
B
Benjamin Pasero 已提交
535 536 537
					editor: this.createInput(replacementArg.editor),
					replacement: this.createInput(replacementArg.replacement),
					options: this.toOptions(replacementArg.replacement.options)
538 539 540 541
				});
			}
		});

542
		const targetGroup = typeof group === 'number' ? this.editorGroupService.getGroup(group) : group;
B
Benjamin Pasero 已提交
543 544 545 546 547
		if (targetGroup) {
			return targetGroup.replaceEditors(typedEditors);
		}

		return Promise.resolve();
548 549 550 551
	}

	//#endregion

552 553 554
	//#region invokeWithinEditorContext()

	invokeWithinEditorContext<T>(fn: (accessor: ServicesAccessor) => T): T {
555 556 557
		const activeTextEditorWidget = this.activeTextEditorWidget;
		if (isCodeEditor(activeTextEditorWidget)) {
			return activeTextEditorWidget.invokeWithinContext(fn);
558 559
		}

560
		const activeGroup = this.editorGroupService.activeGroup;
561 562 563 564 565
		if (activeGroup) {
			return activeGroup.invokeWithinContext(fn);
		}

		return this.instantiationService.invokeFunction(fn);
566 567 568 569
	}

	//#endregion

570 571
	//#region createInput()

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

574
		// Typed Editor Input Support (EditorInput)
575 576 577 578
		if (input instanceof EditorInput) {
			return input;
		}

579 580 581 582 583 584
		// Typed Editor Input Support (IEditorInputWithOptions)
		const editorInputWithOptions = input as IEditorInputWithOptions;
		if (editorInputWithOptions.editor instanceof EditorInput) {
			return editorInputWithOptions.editor;
		}

585
		// Side by Side Support
M
Matt Bierner 已提交
586
		const resourceSideBySideInput = input as IResourceSideBySideInput;
587
		if (resourceSideBySideInput.masterResource && resourceSideBySideInput.detailResource) {
B
Benjamin Pasero 已提交
588 589
			const masterInput = this.createInput({ resource: resourceSideBySideInput.masterResource, forceFile: resourceSideBySideInput.forceFile });
			const detailInput = this.createInput({ resource: resourceSideBySideInput.detailResource, forceFile: resourceSideBySideInput.forceFile });
B
Benjamin Pasero 已提交
590
			const label = resourceSideBySideInput.label || masterInput.getName() || localize('sideBySideLabels', "{0} - {1}", this.toDiffLabel(masterInput), this.toDiffLabel(detailInput));
591 592

			return new SideBySideEditorInput(
B
Benjamin Pasero 已提交
593
				label,
594 595 596 597 598 599 600
				typeof resourceSideBySideInput.description === 'string' ? resourceSideBySideInput.description : masterInput.getDescription(),
				detailInput,
				masterInput
			);
		}

		// Diff Editor Support
M
Matt Bierner 已提交
601
		const resourceDiffInput = input as IResourceDiffInput;
602
		if (resourceDiffInput.leftResource && resourceDiffInput.rightResource) {
B
Benjamin Pasero 已提交
603 604
			const leftInput = this.createInput({ resource: resourceDiffInput.leftResource, forceFile: resourceDiffInput.forceFile });
			const rightInput = this.createInput({ resource: resourceDiffInput.rightResource, forceFile: resourceDiffInput.forceFile });
605
			const label = resourceDiffInput.label || localize('compareLabels', "{0} ↔ {1}", this.toDiffLabel(leftInput), this.toDiffLabel(rightInput));
606

607
			return new DiffEditorInput(label, resourceDiffInput.description, leftInput, rightInput);
608 609 610
		}

		// Untitled file support
611
		const untitledInput = input as IUntitledTextResourceInput;
612
		if (untitledInput.forceUntitled || !untitledInput.resource || (untitledInput.resource && untitledInput.resource.scheme === Schemas.untitled)) {
B
Benjamin Pasero 已提交
613 614 615 616 617 618
			return this.untitledTextEditorService.create({
				resource: untitledInput.resource,
				mode: untitledInput.mode,
				initialValue: untitledInput.contents,
				encoding: untitledInput.encoding
			});
619 620 621
		}

		// Resource Editor Support
M
Matt Bierner 已提交
622
		const resourceInput = input as IResourceInput;
623 624
		if (resourceInput.resource instanceof URI) {
			let label = resourceInput.label;
625 626
			if (!label) {
				label = basename(resourceInput.resource); // derive the label from the path
627 628
			}

629
			return this.createOrGet(resourceInput.resource, this.instantiationService, label, resourceInput.description, resourceInput.encoding, resourceInput.mode, resourceInput.forceFile) as EditorInput;
630 631
		}

B
Benjamin Pasero 已提交
632
		throw new Error('Unknown input type');
633 634
	}

B
Benjamin Pasero 已提交
635
	private createOrGet(resource: URI, instantiationService: IInstantiationService, label: string | undefined, description: string | undefined, encoding: string | undefined, mode: string | undefined, forceFile: boolean | undefined): CachedEditorInput {
636
		if (EditorService.CACHE.has(resource)) {
B
Benjamin Pasero 已提交
637
			const input = EditorService.CACHE.get(resource)!;
638
			if (input instanceof ResourceEditorInput) {
B
Benjamin Pasero 已提交
639 640 641 642 643 644 645
				if (label) {
					input.setName(label);
				}

				if (description) {
					input.setDescription(description);
				}
646 647 648 649

				if (mode) {
					input.setPreferredMode(mode);
				}
650
			} else {
B
Benjamin Pasero 已提交
651 652 653
				if (encoding) {
					input.setPreferredEncoding(encoding);
				}
654 655 656 657

				if (mode) {
					input.setPreferredMode(mode);
				}
658 659 660 661 662 663
			}

			return input;
		}

		// File
B
Benjamin Pasero 已提交
664
		let input: CachedEditorInput;
B
Benjamin Pasero 已提交
665
		if (forceFile /* fix for https://github.com/Microsoft/vscode/issues/48275 */ || this.fileService.canHandleResource(resource)) {
666
			input = this.fileInputFactory.createFileInput(resource, encoding, mode, instantiationService);
667 668 669 670
		}

		// Resource
		else {
671
			input = instantiationService.createInstance(ResourceEditorInput, label, description, resource, mode);
672 673
		}

674
		// Add to cache and remove when input gets disposed
675
		EditorService.CACHE.set(resource, input);
676
		Event.once(input.onDispose)(() => EditorService.CACHE.delete(resource));
677 678 679 680

		return input;
	}

B
Benjamin Pasero 已提交
681
	private toDiffLabel(input: EditorInput): string | undefined {
682
		const res = input.getResource();
B
Benjamin Pasero 已提交
683
		if (!res) {
B
Benjamin Pasero 已提交
684
			return undefined;
B
Benjamin Pasero 已提交
685
		}
686

687 688
		// Do not try to extract any paths from simple untitled text editors
		if (res.scheme === Schemas.untitled && !this.untitledTextEditorService.hasAssociatedFilePath(res)) {
689 690 691 692
			return input.getName();
		}

		// Otherwise: for diff labels prefer to see the path as part of the label
693
		return this.labelService.getUriLabel(res, { relative: true });
694
	}
695 696

	//#endregion
697

B
Benjamin Pasero 已提交
698
	//#region save/revert
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

	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 }) => {

729 730 731 732
			// Use save as a hint to pin the editor if used explicitly
			if (options?.reason === SaveReason.EXPLICIT) {
				this.editorGroupService.getGroup(groupId)?.pinEditor(editor);
			}
733 734

			// Save
735
			return editor.save(groupId, options);
736 737 738 739 740 741 742 743
		}));

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

744
			const result = options?.saveAs ? await editor.saveAs(groupId, options) : await editor.save(groupId, options);
745 746 747 748 749 750 751 752 753
			if (!result) {
				return false; // failed or cancelled, abort
			}
		}

		return true;
	}

	saveAll(options?: ISaveAllEditorsOptions): Promise<boolean> {
B
Benjamin Pasero 已提交
754
		return this.save(this.getAllDirtyEditors(options), options);
755 756
	}

B
Benjamin Pasero 已提交
757 758 759 760 761 762 763 764 765 766 767 768 769 770
	async revert(editors: IEditorIdentifier | IEditorIdentifier[], options?: IRevertOptions): Promise<boolean> {

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

		const result = await Promise.all(editors.map(async ({ groupId, editor }) => {

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

			return editor.revert(options);
		}));
771

B
Benjamin Pasero 已提交
772
		return result.every(success => !!success);
773 774
	}

B
Benjamin Pasero 已提交
775
	async revertAll(options?: IRevertAllEditorsOptions): Promise<boolean> {
B
Benjamin Pasero 已提交
776
		return this.revert(this.getAllDirtyEditors(options), options);
B
Benjamin Pasero 已提交
777 778
	}

B
Benjamin Pasero 已提交
779
	private getAllDirtyEditors(options?: IBaseSaveRevertAllEditorOptions): IEditorIdentifier[] {
B
Benjamin Pasero 已提交
780 781
		const editors: IEditorIdentifier[] = [];

782 783
		for (const group of this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) {
			for (const editor of group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)) {
B
Benjamin Pasero 已提交
784
				if (editor.isDirty() && (!editor.isUntitled() || !!options?.includeUntitled)) {
B
Benjamin Pasero 已提交
785
					editors.push({ groupId: group.id, editor });
786 787 788
				}
			}
		}
B
Benjamin Pasero 已提交
789 790

		return editors;
791 792 793
	}

	//#endregion
794 795 796
}

export interface IEditorOpenHandler {
B
Benjamin Pasero 已提交
797
	(
798
		delegate: (group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions) => Promise<IEditor | null>,
B
Benjamin Pasero 已提交
799 800 801 802
		group: IEditorGroup,
		editor: IEditorInput,
		options?: IEditorOptions | ITextEditorOptions
	): Promise<IEditor | null>;
803 804 805 806
}

/**
 * The delegating workbench editor service can be used to override the behaviour of the openEditor()
807 808
 * method by providing a IEditorOpenHandler. All calls are being delegated to the existing editor
 * service otherwise.
809
 */
810 811 812
export class DelegatingEditorService implements IEditorService {

	_serviceBrand: undefined;
813 814

	constructor(
815 816 817
		private editorOpenHandler: IEditorOpenHandler,
		@IEditorService private editorService: EditorService
	) { }
818

819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838
	openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: OpenInEditorGroup): Promise<IEditor | undefined>;
	openEditor(editor: IResourceInput | IUntitledTextResourceInput, group?: OpenInEditorGroup): Promise<ITextEditor | undefined>;
	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> {
		const result = this.editorService.doResolveEditorOpenRequest(editor, optionsOrGroup, group);
		if (result) {
			const [resolvedGroup, resolvedEditor, resolvedOptions] = result;

			// Pass on to editor open handler
			const control = await this.editorOpenHandler(
				(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions) => group.openEditor(editor, options),
				resolvedGroup,
				resolvedEditor,
				resolvedOptions
			);

			if (control) {
				return control; // the opening was handled, so return early
			}
839

840
			return withNullAsUndefined(await resolvedGroup.openEditor(resolvedEditor, resolvedOptions));
B
Benjamin Pasero 已提交
841
		}
842

843 844
		return undefined;
	}
B
Benjamin Pasero 已提交
845

846
	//#region Delegate to IEditorService
847

848 849 850 851 852 853
	get onDidActiveEditorChange(): Event<void> { return this.editorService.onDidActiveEditorChange; }
	get onDidVisibleEditorsChange(): Event<void> { return this.editorService.onDidVisibleEditorsChange; }

	get activeEditor(): IEditorInput | undefined { return this.editorService.activeEditor; }
	get activeControl(): IVisibleEditor | undefined { return this.editorService.activeControl; }
	get activeTextEditorWidget(): ICodeEditor | IDiffEditor | undefined { return this.editorService.activeTextEditorWidget; }
854
	get activeTextEditorMode(): string | undefined { return this.editorService.activeTextEditorMode; }
855 856 857 858
	get visibleEditors(): ReadonlyArray<IEditorInput> { return this.editorService.visibleEditors; }
	get visibleControls(): ReadonlyArray<IVisibleEditor> { return this.editorService.visibleControls; }
	get visibleTextEditorWidgets(): ReadonlyArray<ICodeEditor | IDiffEditor> { return this.editorService.visibleTextEditorWidgets; }
	get editors(): ReadonlyArray<IEditorInput> { return this.editorService.editors; }
B
Benjamin Pasero 已提交
859 860 861
	get count(): number { return this.editorService.count; }

	getEditors(order: EditorsOrder): ReadonlyArray<IEditorIdentifier> { return this.editorService.getEditors(order); }
862 863 864 865 866

	openEditors(editors: IEditorInputWithOptions[], group?: OpenInEditorGroup): Promise<IEditor[]>;
	openEditors(editors: IResourceEditor[], group?: OpenInEditorGroup): Promise<IEditor[]>;
	openEditors(editors: Array<IEditorInputWithOptions | IResourceEditor>, group?: OpenInEditorGroup): Promise<IEditor[]> {
		return this.editorService.openEditors(editors, group);
867
	}
868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891

	replaceEditors(editors: IResourceEditorReplacement[], group: IEditorGroup | GroupIdentifier): Promise<void>;
	replaceEditors(editors: IEditorReplacement[], group: IEditorGroup | GroupIdentifier): Promise<void>;
	replaceEditors(editors: Array<IEditorReplacement | IResourceEditorReplacement>, group: IEditorGroup | GroupIdentifier): Promise<void> {
		return this.editorService.replaceEditors(editors as IResourceEditorReplacement[] /* TS fail */, group);
	}

	isOpen(editor: IEditorInput | IResourceInput | IUntitledTextResourceInput): boolean { return this.editorService.isOpen(editor); }

	getOpened(editor: IResourceInput | IUntitledTextResourceInput): IEditorInput | undefined { return this.editorService.getOpened(editor); }

	overrideOpenEditor(handler: IOpenEditorOverrideHandler): IDisposable { return this.editorService.overrideOpenEditor(handler); }

	invokeWithinEditorContext<T>(fn: (accessor: ServicesAccessor) => T): T { return this.editorService.invokeWithinEditorContext(fn); }

	createInput(input: IResourceEditor): IEditorInput { return this.editorService.createInput(input); }

	save(editors: IEditorIdentifier | IEditorIdentifier[], options?: ISaveEditorsOptions): Promise<boolean> { return this.editorService.save(editors, options); }
	saveAll(options?: ISaveAllEditorsOptions): Promise<boolean> { return this.editorService.saveAll(options); }

	revert(editors: IEditorIdentifier | IEditorIdentifier[], options?: IRevertOptions): Promise<boolean> { return this.editorService.revert(editors, options); }
	revertAll(options?: IRevertAllEditorsOptions): Promise<boolean> { return this.editorService.revertAll(options); }

	//#endregion
I
isidor 已提交
892
}
893

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