editorService.ts 34.8 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, isTextEditor } 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
import { IEditorViewState } from 'vs/editor/common/editorCommon';
31 32
import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel';
import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput';
33

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

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

39
	_serviceBrand: undefined;
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 68
	private readonly editorInputCache = new ResourceMap<CachedEditorInput>();

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

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

		this.registerListeners();
	}

	private registerListeners(): void {
85 86

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

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

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

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

114
		this.doHandleActiveEditorChangeEvent();
115 116
	}

117
	private doHandleActiveEditorChangeEvent(): void {
118

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

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

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

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

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

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

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

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

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

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

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

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

R
Rob Lourens 已提交
182
		return undefined;
183 184
	}

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

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

		return editors;
	}

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

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

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

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

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

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

253 254
	//#region openEditor()

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

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

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

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

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

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

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

318
			return [resolvedGroup, typedEditor, typedOptions];
319 320
		}

B
Benjamin Pasero 已提交
321
		return undefined;
322
	}
323

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

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

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

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

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

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

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

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

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

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

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

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

390 391 392
		return targetGroup;
	}

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

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

		return neighbourGroup;
402 403
	}

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

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

		return EditorOptions.create(options);
	}

417 418
	//#endregion

419 420
	//#region openEditors()

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

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

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

	//#endregion

464 465
	//#region isOpen()

466 467
	isOpen(editor: IEditorInput): boolean {
		return this.editorGroupService.groups.some(group => group.isOpened(editor));
468 469 470 471
	}

	//#endregion

472 473
	//#region replaceEditors()

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

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

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

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

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

		return Promise.resolve();
505 506 507 508
	}

	//#endregion

509 510 511
	//#region invokeWithinEditorContext()

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

517
		const activeGroup = this.editorGroupService.activeGroup;
518 519 520 521 522
		if (activeGroup) {
			return activeGroup.invokeWithinContext(fn);
		}

		return this.instantiationService.invokeFunction(fn);
523 524 525 526
	}

	//#endregion

527 528
	//#region createInput()

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

531
		// Typed Editor Input Support (EditorInput)
532 533 534 535
		if (input instanceof EditorInput) {
			return input;
		}

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

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

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

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

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

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

			// Untitled resource: use as hint for an existing untitled editor
580
			let untitledModel: IUntitledTextEditorModel;
B
Benjamin Pasero 已提交
581
			if (untitledInput.resource?.scheme === Schemas.untitled) {
582
				untitledModel = this.untitledTextEditorService.create({ untitledResource: untitledInput.resource, ...untitledOptions });
B
Benjamin Pasero 已提交
583 584 585 586
			}

			// Other resource: use as hint for associated filepath
			else {
587
				untitledModel = this.untitledTextEditorService.create({ associatedResource: untitledInput.resource, ...untitledOptions });
B
Benjamin Pasero 已提交
588
			}
589 590 591 592 593 594 595

			return this.createOrGetCached(untitledModel.resource, () => {

				// Factory function for new untitled editor
				const input = this.instantiationService.createInstance(UntitledTextEditorInput, untitledModel);

				// We dispose the untitled model once the editor
596 597 598 599
				// is being disposed. Even though we may have not
				// created the model initially, the lifecycle for
				// untitled is tightly coupled with the editor
				// lifecycle for now.
600 601 602 603
				Event.once(input.onDispose)(() => untitledModel.dispose());

				return input;
			}) as EditorInput;
604 605 606
		}

		// Resource Editor Support
M
Matt Bierner 已提交
607
		const resourceInput = input as IResourceInput;
608 609
		if (resourceInput.resource instanceof URI) {
			let label = resourceInput.label;
610 611
			if (!label) {
				label = basename(resourceInput.resource); // derive the label from the path
612 613
			}

614
			return this.createOrGetCached(resourceInput.resource, () => {
615

616 617 618
				// File
				if (resourceInput.forceFile /* fix for https://github.com/Microsoft/vscode/issues/48275 */ || this.fileService.canHandleResource(resourceInput.resource)) {
					return this.fileInputFactory.createFileInput(resourceInput.resource, resourceInput.encoding, resourceInput.mode, this.instantiationService);
B
Benjamin Pasero 已提交
619 620
				}

621 622 623
				// Resource
				return this.instantiationService.createInstance(ResourceEditorInput, resourceInput.label, resourceInput.description, resourceInput.resource, resourceInput.mode);
			}, cachedInput => {
624

625 626 627
				// Untitled
				if (cachedInput instanceof UntitledTextEditorInput) {
					return;
B
Benjamin Pasero 已提交
628
				}
629

630 631 632 633 634 635 636 637 638
				// Files
				else if (!(cachedInput instanceof ResourceEditorInput)) {
					if (resourceInput.encoding) {
						cachedInput.setPreferredEncoding(resourceInput.encoding);
					}

					if (resourceInput.mode) {
						cachedInput.setPreferredMode(resourceInput.mode);
					}
639
				}
640

641 642 643 644 645
				// Resources
				else {
					if (label) {
						cachedInput.setName(label);
					}
646

647 648 649 650 651 652 653 654 655
					if (resourceInput.description) {
						cachedInput.setDescription(resourceInput.description);
					}

					if (resourceInput.mode) {
						cachedInput.setPreferredMode(resourceInput.mode);
					}
				}
			}) as EditorInput;
656 657
		}

658 659 660 661 662 663 664 665 666 667 668 669 670
		throw new Error('Unknown input type');
	}

	private createOrGetCached(resource: URI, factoryFn: () => CachedEditorInput, cachedFn?: (input: CachedEditorInput) => void): CachedEditorInput {

		// Return early if already cached
		let input = this.editorInputCache.get(resource);
		if (input) {
			if (cachedFn) {
				cachedFn(input);
			}

			return input;
671 672
		}

673 674 675 676
		// Otherwise create and add to cache
		input = factoryFn();
		this.editorInputCache.set(resource, input);
		Event.once(input.onDispose)(() => this.editorInputCache.delete(resource));
677 678 679 680

		return input;
	}

681 682 683 684 685 686
	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 已提交
687
			return undefined;
B
Benjamin Pasero 已提交
688
		}
689

690 691 692 693 694
		// 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 })}`;
695 696
		}

697 698
		// Signal back that the label should be computed from within the editor
		return undefined;
699
	}
700 701

	//#endregion
702

B
Benjamin Pasero 已提交
703
	//#region save/revert
704 705 706 707 708 709 710 711 712 713 714 715 716 717

	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[] = [];
718
		const editorsToSaveSequentially: IEditorIdentifier[] = [];
719
		if (options?.saveAs) {
720
			editorsToSaveSequentially.push(...editors);
721 722 723
		} else {
			for (const { groupId, editor } of editors) {
				if (editor.isUntitled()) {
724
					editorsToSaveSequentially.push({ groupId, editor });
725 726 727 728 729 730 731 732 733
				} else {
					editorsToSaveParallel.push({ groupId, editor });
				}
			}
		}

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

734 735 736 737
			// Use save as a hint to pin the editor if used explicitly
			if (options?.reason === SaveReason.EXPLICIT) {
				this.editorGroupService.getGroup(groupId)?.pinEditor(editor);
			}
738 739

			// Save
740
			return editor.save(groupId, options);
741 742 743
		}));

		// Editors to save sequentially
744
		for (const { groupId, editor } of editorsToSaveSequentially) {
745
			if (editor.isDisposed()) {
746
				continue; // might have been disposed from the save already
747 748
			}

749 750 751 752 753 754 755 756
			// Preserve view state by opening the editor first if the editor
			// is untitled or we "Save As". This also allows the user to review
			// the contents of the editor before making a decision.
			let viewState: IEditorViewState | undefined = undefined;
			const control = await this.openEditor(editor, undefined, groupId);
			if (isTextEditor(control)) {
				viewState = control.getViewState();
			}
757

758
			const result = options?.saveAs ? await editor.saveAs(groupId, options) : await editor.save(groupId, options);
759 760 761
			if (!result) {
				return false; // failed or cancelled, abort
			}
762 763 764 765 766 767 768 769 770 771

			// Replace editor preserving viewstate (either across all groups or
			// only selected group) if the resulting editor is different from the
			// current one.
			if (!result.matches(editor)) {
				const targetGroups = editor.isUntitled() ? this.editorGroupService.groups.map(group => group.id) /* untitled replaces across all groups */ : [groupId];
				for (const group of targetGroups) {
					await this.replaceEditors([{ editor, replacement: result, options: { pinned: true, viewState } }], group);
				}
			}
772 773 774 775 776 777
		}

		return true;
	}

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

B
Benjamin Pasero 已提交
781 782 783 784 785 786 787 788
	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 }) => {
789 790 791
			if (editor.isDisposed()) {
				return true; // might have been disposed from from the revert already
			}
B
Benjamin Pasero 已提交
792 793 794 795

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

B
Benjamin Pasero 已提交
796
			return editor.revert(groupId, options);
B
Benjamin Pasero 已提交
797
		}));
798

B
Benjamin Pasero 已提交
799
		return result.every(success => !!success);
800 801
	}

B
Benjamin Pasero 已提交
802
	async revertAll(options?: IRevertAllEditorsOptions): Promise<boolean> {
B
Benjamin Pasero 已提交
803
		return this.revert(this.getAllDirtyEditors(options), options);
B
Benjamin Pasero 已提交
804 805
	}

B
Benjamin Pasero 已提交
806
	private getAllDirtyEditors(options?: IBaseSaveRevertAllEditorOptions): IEditorIdentifier[] {
B
Benjamin Pasero 已提交
807 808
		const editors: IEditorIdentifier[] = [];

809 810
		for (const group of this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) {
			for (const editor of group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)) {
B
Benjamin Pasero 已提交
811
				if (editor.isDirty() && (!editor.isUntitled() || !!options?.includeUntitled)) {
B
Benjamin Pasero 已提交
812
					editors.push({ groupId: group.id, editor });
813 814 815
				}
			}
		}
B
Benjamin Pasero 已提交
816 817

		return editors;
818 819 820
	}

	//#endregion
821 822 823
}

export interface IEditorOpenHandler {
B
Benjamin Pasero 已提交
824
	(
825
		delegate: (group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions) => Promise<IEditor | null>,
B
Benjamin Pasero 已提交
826 827 828 829
		group: IEditorGroup,
		editor: IEditorInput,
		options?: IEditorOptions | ITextEditorOptions
	): Promise<IEditor | null>;
830 831 832 833
}

/**
 * The delegating workbench editor service can be used to override the behaviour of the openEditor()
834 835
 * method by providing a IEditorOpenHandler. All calls are being delegated to the existing editor
 * service otherwise.
836
 */
837 838 839
export class DelegatingEditorService implements IEditorService {

	_serviceBrand: undefined;
840 841

	constructor(
842 843 844
		private editorOpenHandler: IEditorOpenHandler,
		@IEditorService private editorService: EditorService
	) { }
845

846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865
	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
			}
866

867
			return withNullAsUndefined(await resolvedGroup.openEditor(resolvedEditor, resolvedOptions));
B
Benjamin Pasero 已提交
868
		}
869

870 871
		return undefined;
	}
B
Benjamin Pasero 已提交
872

873
	//#region Delegate to IEditorService
874

875 876 877 878 879 880
	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; }
881
	get activeTextEditorMode(): string | undefined { return this.editorService.activeTextEditorMode; }
882 883 884 885
	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 已提交
886 887 888
	get count(): number { return this.editorService.count; }

	getEditors(order: EditorsOrder): ReadonlyArray<IEditorIdentifier> { return this.editorService.getEditors(order); }
889 890 891 892 893

	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);
894
	}
895 896 897 898 899 900 901

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

902
	isOpen(editor: IEditorInput): boolean { return this.editorService.isOpen(editor); }
903 904 905 906 907 908 909 910 911 912 913 914 915 916

	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 已提交
917
}
918

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