editorService.ts 32.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, 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 } from 'vs/base/common/resources';
18
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
B
Benjamin Pasero 已提交
19
import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, GroupChangeKind, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService';
B
Benjamin Pasero 已提交
20
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';
21
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
22
import { Disposable, IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
23
import { coalesce } from 'vs/base/common/arrays';
24
import { isCodeEditor, isDiffEditor, ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser';
25
import { IEditorGroupView, IEditorOpeningEvent, EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor';
I
isidor 已提交
26
import { ILabelService } from 'vs/platform/label/common/label';
27
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
28
import { withNullAsUndefined } from 'vs/base/common/types';
29
import { EditorsObserver } from 'vs/workbench/browser/parts/editor/editorsObserver';
30

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

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

36
	_serviceBrand: undefined;
37

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

40 41
	//#region events

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

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

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

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

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

57 58
	//#endregion

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

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

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

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

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

		this.registerListeners();
	}

	private registerListeners(): void {
82 83

		// Editor & group changes
84
		this.editorGroupService.whenRestored.then(() => this.onEditorsRestored());
85
		this.editorGroupService.onDidActiveGroupChange(group => this.handleActiveEditorChange(group));
86
		this.editorGroupService.onDidAddGroup(group => this.registerGroupListeners(group as IEditorGroupView));
87
		this.editorsObserver.onDidChange(() => this._onDidMostRecentlyActiveEditorsChange.fire());
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) {
97
			this.doHandleActiveEditorChangeEvent();
98 99 100 101
			this._onDidVisibleEditorsChange.fire();
		}
	}

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

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

111
		this.doHandleActiveEditorChangeEvent();
112 113
	}

114
	private doHandleActiveEditorChangeEvent(): void {
115

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

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

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

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

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

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

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

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

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

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

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

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

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

182 183 184 185 186 187 188 189 190 191 192 193 194
	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 已提交
195 196 197 198
	get count(): number {
		return this.editorsObserver.count;
	}

199
	get editors(): IEditorInput[] {
B
Benjamin Pasero 已提交
200 201 202 203 204 205 206 207 208 209 210 211
		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 })));
212 213 214 215 216
		});

		return editors;
	}

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

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

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

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

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

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

250 251
	//#region openEditor()

B
Benjamin Pasero 已提交
252
	openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: OpenInEditorGroup): Promise<IEditor | undefined>;
253
	openEditor(editor: IResourceInput | IUntitledTextResourceInput, group?: OpenInEditorGroup): Promise<ITextEditor | undefined>;
B
Benjamin Pasero 已提交
254 255 256
	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> {
257 258 259 260 261 262 263 264 265 266 267
		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 已提交
268 269 270
		let resolvedGroup: IEditorGroup | undefined;
		let candidateGroup: OpenInEditorGroup | undefined;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

387 388 389
		return targetGroup;
	}

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

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

		return neighbourGroup;
399 400
	}

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

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

		return EditorOptions.create(options);
	}

414 415
	//#endregion

416 417
	//#region openEditors()

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

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

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

	//#endregion

461 462
	//#region isOpen()

463 464
	isOpen(editor: IEditorInput): boolean {
		return this.editorGroupService.groups.some(group => group.isOpened(editor));
465 466 467 468
	}

	//#endregion

469 470
	//#region replaceEditors()

J
Johannes Rieken 已提交
471 472
	replaceEditors(editors: IResourceEditorReplacement[], group: IEditorGroup | GroupIdentifier): Promise<void>;
	replaceEditors(editors: IEditorReplacement[], group: IEditorGroup | GroupIdentifier): Promise<void>;
473
	replaceEditors(editors: Array<IEditorReplacement | IResourceEditorReplacement>, group: IEditorGroup | GroupIdentifier): Promise<void> {
474 475 476 477
		const typedEditors: IEditorReplacement[] = [];

		editors.forEach(replaceEditorArg => {
			if (replaceEditorArg.editor instanceof EditorInput) {
B
Benjamin Pasero 已提交
478 479 480 481 482 483 484
				const replacementArg = replaceEditorArg as IEditorReplacement;

				typedEditors.push({
					editor: replacementArg.editor,
					replacement: replacementArg.replacement,
					options: this.toOptions(replacementArg.options)
				});
485
			} else {
B
Benjamin Pasero 已提交
486
				const replacementArg = replaceEditorArg as IResourceEditorReplacement;
487 488

				typedEditors.push({
B
Benjamin Pasero 已提交
489 490 491
					editor: this.createInput(replacementArg.editor),
					replacement: this.createInput(replacementArg.replacement),
					options: this.toOptions(replacementArg.replacement.options)
492 493 494 495
				});
			}
		});

496
		const targetGroup = typeof group === 'number' ? this.editorGroupService.getGroup(group) : group;
B
Benjamin Pasero 已提交
497 498 499 500 501
		if (targetGroup) {
			return targetGroup.replaceEditors(typedEditors);
		}

		return Promise.resolve();
502 503 504 505
	}

	//#endregion

506 507 508
	//#region invokeWithinEditorContext()

	invokeWithinEditorContext<T>(fn: (accessor: ServicesAccessor) => T): T {
509 510 511
		const activeTextEditorWidget = this.activeTextEditorWidget;
		if (isCodeEditor(activeTextEditorWidget)) {
			return activeTextEditorWidget.invokeWithinContext(fn);
512 513
		}

514
		const activeGroup = this.editorGroupService.activeGroup;
515 516 517 518 519
		if (activeGroup) {
			return activeGroup.invokeWithinContext(fn);
		}

		return this.instantiationService.invokeFunction(fn);
520 521 522 523
	}

	//#endregion

524 525
	//#region createInput()

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

528
		// Typed Editor Input Support (EditorInput)
529 530 531 532
		if (input instanceof EditorInput) {
			return input;
		}

533 534 535 536 537 538
		// Typed Editor Input Support (IEditorInputWithOptions)
		const editorInputWithOptions = input as IEditorInputWithOptions;
		if (editorInputWithOptions.editor instanceof EditorInput) {
			return editorInputWithOptions.editor;
		}

539
		// Side by Side Support
M
Matt Bierner 已提交
540
		const resourceSideBySideInput = input as IResourceSideBySideInput;
541
		if (resourceSideBySideInput.masterResource && resourceSideBySideInput.detailResource) {
B
Benjamin Pasero 已提交
542 543
			const masterInput = this.createInput({ resource: resourceSideBySideInput.masterResource, forceFile: resourceSideBySideInput.forceFile });
			const detailInput = this.createInput({ resource: resourceSideBySideInput.detailResource, forceFile: resourceSideBySideInput.forceFile });
544 545

			return new SideBySideEditorInput(
546 547
				resourceSideBySideInput.label || this.toSideBySideLabel(detailInput, masterInput, '-'),
				resourceSideBySideInput.description,
548 549 550 551 552 553
				detailInput,
				masterInput
			);
		}

		// Diff Editor Support
M
Matt Bierner 已提交
554
		const resourceDiffInput = input as IResourceDiffInput;
555
		if (resourceDiffInput.leftResource && resourceDiffInput.rightResource) {
B
Benjamin Pasero 已提交
556 557
			const leftInput = this.createInput({ resource: resourceDiffInput.leftResource, forceFile: resourceDiffInput.forceFile });
			const rightInput = this.createInput({ resource: resourceDiffInput.rightResource, forceFile: resourceDiffInput.forceFile });
558

559 560 561 562 563 564
			return new DiffEditorInput(
				resourceDiffInput.label || this.toSideBySideLabel(leftInput, rightInput, ''),
				resourceDiffInput.description,
				leftInput,
				rightInput
			);
565 566 567
		}

		// Untitled file support
568
		const untitledInput = input as IUntitledTextResourceInput;
569
		if (untitledInput.forceUntitled || !untitledInput.resource || (untitledInput.resource && untitledInput.resource.scheme === Schemas.untitled)) {
B
Benjamin Pasero 已提交
570
			const untitledOptions = {
B
Benjamin Pasero 已提交
571 572 573
				mode: untitledInput.mode,
				initialValue: untitledInput.contents,
				encoding: untitledInput.encoding
B
Benjamin Pasero 已提交
574 575 576 577 578 579 580 581 582 583 584
			};

			// Untitled resource: use as hint for an existing untitled editor
			if (untitledInput.resource?.scheme === Schemas.untitled) {
				return this.untitledTextEditorService.create({ untitledResource: untitledInput.resource, ...untitledOptions });
			}

			// Other resource: use as hint for associated filepath
			else {
				return this.untitledTextEditorService.create({ associatedResource: untitledInput.resource, ...untitledOptions });
			}
585 586 587
		}

		// Resource Editor Support
M
Matt Bierner 已提交
588
		const resourceInput = input as IResourceInput;
589 590
		if (resourceInput.resource instanceof URI) {
			let label = resourceInput.label;
591 592
			if (!label) {
				label = basename(resourceInput.resource); // derive the label from the path
593 594
			}

595
			return this.createOrGet(resourceInput.resource, this.instantiationService, label, resourceInput.description, resourceInput.encoding, resourceInput.mode, resourceInput.forceFile) as EditorInput;
596 597
		}

B
Benjamin Pasero 已提交
598
		throw new Error('Unknown input type');
599 600
	}

B
Benjamin Pasero 已提交
601
	private createOrGet(resource: URI, instantiationService: IInstantiationService, label: string | undefined, description: string | undefined, encoding: string | undefined, mode: string | undefined, forceFile: boolean | undefined): CachedEditorInput {
602
		if (EditorService.CACHE.has(resource)) {
B
Benjamin Pasero 已提交
603
			const input = EditorService.CACHE.get(resource)!;
604
			if (input instanceof ResourceEditorInput) {
B
Benjamin Pasero 已提交
605 606 607 608 609 610 611
				if (label) {
					input.setName(label);
				}

				if (description) {
					input.setDescription(description);
				}
612 613 614 615

				if (mode) {
					input.setPreferredMode(mode);
				}
616
			} else {
B
Benjamin Pasero 已提交
617 618 619
				if (encoding) {
					input.setPreferredEncoding(encoding);
				}
620 621 622 623

				if (mode) {
					input.setPreferredMode(mode);
				}
624 625 626 627 628 629
			}

			return input;
		}

		// File
B
Benjamin Pasero 已提交
630
		let input: CachedEditorInput;
B
Benjamin Pasero 已提交
631
		if (forceFile /* fix for https://github.com/Microsoft/vscode/issues/48275 */ || this.fileService.canHandleResource(resource)) {
632
			input = this.fileInputFactory.createFileInput(resource, encoding, mode, instantiationService);
633 634 635 636
		}

		// Resource
		else {
637
			input = instantiationService.createInstance(ResourceEditorInput, label, description, resource, mode);
638 639
		}

640
		// Add to cache and remove when input gets disposed
641
		EditorService.CACHE.set(resource, input);
642
		Event.once(input.onDispose)(() => EditorService.CACHE.delete(resource));
643 644 645 646

		return input;
	}

647 648 649 650 651 652
	private toSideBySideLabel(leftInput: EditorInput, rightInput: EditorInput, divider: string): string | undefined {
		const leftResource = leftInput.getResource();
		const rightResource = rightInput.getResource();

		// Without any resource, do not try to compute a label
		if (!leftResource || !rightResource) {
B
Benjamin Pasero 已提交
653
			return undefined;
B
Benjamin Pasero 已提交
654
		}
655

656 657 658 659 660
		// If both editors are file inputs, we produce an optimized label
		// by adding the relative path of both inputs to the label. This
		// makes it easier to understand a file-based comparison.
		if (this.fileInputFactory.isFileInput(leftInput) && this.fileInputFactory.isFileInput(rightInput)) {
			return `${this.labelService.getUriLabel(leftResource, { relative: true })} ${divider} ${this.labelService.getUriLabel(rightResource, { relative: true })}`;
661 662
		}

663 664
		// Signal back that the label should be computed from within the editor
		return undefined;
665
	}
666 667

	//#endregion
668

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

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

700 701 702 703
			// Use save as a hint to pin the editor if used explicitly
			if (options?.reason === SaveReason.EXPLICIT) {
				this.editorGroupService.getGroup(groupId)?.pinEditor(editor);
			}
704 705

			// Save
706
			return editor.save(groupId, options);
707 708 709 710 711 712 713 714
		}));

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

715
			const result = options?.saveAs ? await editor.saveAs(groupId, options) : await editor.save(groupId, options);
716 717 718 719 720 721 722 723 724
			if (!result) {
				return false; // failed or cancelled, abort
			}
		}

		return true;
	}

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

B
Benjamin Pasero 已提交
728 729 730 731 732 733 734 735 736 737 738 739
	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);

B
Benjamin Pasero 已提交
740
			return editor.revert(groupId, options);
B
Benjamin Pasero 已提交
741
		}));
742

B
Benjamin Pasero 已提交
743
		return result.every(success => !!success);
744 745
	}

B
Benjamin Pasero 已提交
746
	async revertAll(options?: IRevertAllEditorsOptions): Promise<boolean> {
B
Benjamin Pasero 已提交
747
		return this.revert(this.getAllDirtyEditors(options), options);
B
Benjamin Pasero 已提交
748 749
	}

B
Benjamin Pasero 已提交
750
	private getAllDirtyEditors(options?: IBaseSaveRevertAllEditorOptions): IEditorIdentifier[] {
B
Benjamin Pasero 已提交
751 752
		const editors: IEditorIdentifier[] = [];

753 754
		for (const group of this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) {
			for (const editor of group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)) {
B
Benjamin Pasero 已提交
755
				if (editor.isDirty() && (!editor.isUntitled() || !!options?.includeUntitled)) {
B
Benjamin Pasero 已提交
756
					editors.push({ groupId: group.id, editor });
757 758 759
				}
			}
		}
B
Benjamin Pasero 已提交
760 761

		return editors;
762 763 764
	}

	//#endregion
765 766 767
}

export interface IEditorOpenHandler {
B
Benjamin Pasero 已提交
768
	(
769
		delegate: (group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions) => Promise<IEditor | null>,
B
Benjamin Pasero 已提交
770 771 772 773
		group: IEditorGroup,
		editor: IEditorInput,
		options?: IEditorOptions | ITextEditorOptions
	): Promise<IEditor | null>;
774 775 776 777
}

/**
 * The delegating workbench editor service can be used to override the behaviour of the openEditor()
778 779
 * method by providing a IEditorOpenHandler. All calls are being delegated to the existing editor
 * service otherwise.
780
 */
781 782 783
export class DelegatingEditorService implements IEditorService {

	_serviceBrand: undefined;
784 785

	constructor(
786 787 788
		private editorOpenHandler: IEditorOpenHandler,
		@IEditorService private editorService: EditorService
	) { }
789

790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809
	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
			}
810

811
			return withNullAsUndefined(await resolvedGroup.openEditor(resolvedEditor, resolvedOptions));
B
Benjamin Pasero 已提交
812
		}
813

814 815
		return undefined;
	}
B
Benjamin Pasero 已提交
816

817
	//#region Delegate to IEditorService
818

819 820 821 822 823 824
	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; }
825
	get activeTextEditorMode(): string | undefined { return this.editorService.activeTextEditorMode; }
826 827 828 829
	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 已提交
830 831 832
	get count(): number { return this.editorService.count; }

	getEditors(order: EditorsOrder): ReadonlyArray<IEditorIdentifier> { return this.editorService.getEditors(order); }
833 834 835 836 837

	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);
838
	}
839 840 841 842 843 844 845

	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);
	}

846
	isOpen(editor: IEditorInput): boolean { return this.editorService.isOpen(editor); }
847 848 849 850 851 852 853 854 855 856 857 858 859 860

	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 已提交
861
}
862

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