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

B
Benjamin Pasero 已提交
32 33
type CachedEditorInput = ResourceEditorInput | IFileEditorInput | DataUriEditorInput;
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!: ServiceIdentifier<any>;
38

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

41 42
	//#region events

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

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

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

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

55 56
	//#endregion

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

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

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

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

		this.registerListeners();
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

148
	private onGroupWillOpenEditor(group: IEditorGroup, event: IEditorOpeningEvent): void {
149
		for (const handler of this.openEditorHandlers) {
150 151
			const result = handler(event.editor, event.options, group);
			if (result && result.override) {
B
Benjamin Pasero 已提交
152
				event.prevent((() => result.override!.then(editor => withNullAsUndefined(editor))));
153 154 155 156 157
				break;
			}
		}
	}

158
	get activeControl(): IVisibleEditor | undefined {
159
		const activeGroup = this.editorGroupService.activeGroup;
160

R
Rob Lourens 已提交
161
		return activeGroup ? activeGroup.activeControl : undefined;
162 163
	}

164
	get activeTextEditorWidget(): ICodeEditor | IDiffEditor | undefined {
165 166 167
		const activeControl = this.activeControl;
		if (activeControl) {
			const activeControlWidget = activeControl.getControl();
168
			if (isCodeEditor(activeControlWidget) || isDiffEditor(activeControlWidget)) {
169 170 171 172
				return activeControlWidget;
			}
		}

R
Rob Lourens 已提交
173
		return undefined;
174 175
	}

176 177
	get editors(): IEditorInput[] {
		const editors: IEditorInput[] = [];
178
		this.editorGroupService.groups.forEach(group => {
179 180 181 182 183 184
			editors.push(...group.editors);
		});

		return editors;
	}

185
	get activeEditor(): IEditorInput | undefined {
186
		const activeGroup = this.editorGroupService.activeGroup;
187

M
Matt Bierner 已提交
188
		return activeGroup ? withNullAsUndefined(activeGroup.activeEditor) : undefined;
189 190
	}

M
Matt Bierner 已提交
191
	get visibleControls(): IVisibleEditor[] {
192
		return coalesce(this.editorGroupService.groups.map(group => group.activeControl));
193 194
	}

195
	get visibleTextEditorWidgets(): Array<ICodeEditor | IDiffEditor> {
196
		return this.visibleControls.map(control => control.getControl() as ICodeEditor | IDiffEditor).filter(widget => isCodeEditor(widget) || isDiffEditor(widget));
197 198
	}

199
	get visibleEditors(): IEditorInput[] {
200
		return coalesce(this.editorGroupService.groups.map(group => group.activeEditor));
201 202
	}

203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
	//#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

218 219
	//#region openEditor()

B
Benjamin Pasero 已提交
220 221 222 223 224
	openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: OpenInEditorGroup): Promise<IEditor | undefined>;
	openEditor(editor: IResourceInput | IUntitledResourceInput, 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> {
B
Benjamin Pasero 已提交
225 226 227 228 229
		let resolvedGroup: IEditorGroup | undefined;
		let candidateGroup: OpenInEditorGroup | undefined;

		let typedEditor: IEditorInput | undefined;
		let typedOptions: IEditorOptions | undefined;
230 231 232

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

B
Benjamin Pasero 已提交
236 237
			candidateGroup = group;
			resolvedGroup = this.findTargetGroup(typedEditor, typedOptions, candidateGroup);
238 239 240
		}

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

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

		if (typedEditor && resolvedGroup) {
			const control = await this.doOpenEditor(resolvedGroup, typedEditor, typedOptions);

			this.ensureGroupActive(resolvedGroup, candidateGroup);

			return control;
258 259
		}

B
Benjamin Pasero 已提交
260
		return undefined;
261
	}
262

B
Benjamin Pasero 已提交
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
	private ensureGroupActive(resolvedGroup: IEditorGroup, candidateGroup?: OpenInEditorGroup): void {

		// Ensure we activate the group the editor opens in unless already active. Typically
		// an editor always opens in the active group, but there are some cases where the
		// target group is not the active one. If `preserveFocus: true` we do not activate
		// the target group and as such have to do this manually.
		// There is one exception: opening to the side with `preserveFocus: true` will keep
		// the current behaviour for historic reasons. The scenario is that 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.
		if (this.editorGroupService.activeGroup !== resolvedGroup && candidateGroup !== SIDE_GROUP) {
			this.editorGroupService.activateGroup(resolvedGroup);
		}
	}

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

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

B
Benjamin Pasero 已提交
285 286 287 288 289
		// Group: Instance of Group
		if (group && typeof group !== 'number') {
			return group;
		}

290
		// Group: Side by Side
B
Benjamin Pasero 已提交
291
		if (group === SIDE_GROUP) {
292
			targetGroup = this.findSideBySideGroup();
293 294
		}

295
		// Group: Specific Group
B
Benjamin Pasero 已提交
296
		else if (typeof group === 'number' && group >= 0) {
297
			targetGroup = this.editorGroupService.getGroup(group);
298 299
		}

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

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

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

B
Benjamin Pasero 已提交
321
					for (const group of groupsByLastActive) {
B
Benjamin Pasero 已提交
322 323 324 325 326 327 328 329
						if (group.isOpened(input)) {
							if (!groupWithInputOpened) {
								groupWithInputOpened = group;
							}

							if (!groupWithInputActive && group.isActive(input)) {
								groupWithInputActive = group;
							}
B
Benjamin Pasero 已提交
330
						}
B
Benjamin Pasero 已提交
331 332 333

						if (groupWithInputOpened && groupWithInputActive) {
							break; // we found all groups we wanted
B
Benjamin Pasero 已提交
334
						}
335
					}
B
Benjamin Pasero 已提交
336 337 338

					// Prefer a target group where the input is visible
					targetGroup = groupWithInputActive || groupWithInputOpened;
339 340 341 342 343
				}
			}
		}

		// Fallback to active group if target not valid
344
		if (!targetGroup) {
345
			targetGroup = this.editorGroupService.activeGroup;
346 347
		}

348 349 350
		return targetGroup;
	}

351
	private findSideBySideGroup(): IEditorGroup {
352
		const direction = preferredSideBySideGroupDirection(this.configurationService);
353

354
		let neighbourGroup = this.editorGroupService.findGroup({ direction });
355
		if (!neighbourGroup) {
356
			neighbourGroup = this.editorGroupService.addGroup(this.editorGroupService.activeGroup, direction);
357 358 359
		}

		return neighbourGroup;
360 361 362 363 364 365 366 367
	}

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

		const textOptions: ITextEditorOptions = options;
B
Benjamin Pasero 已提交
368
		if (textOptions.selection || textOptions.viewState) {
369 370 371 372 373 374
			return TextEditorOptions.create(options);
		}

		return EditorOptions.create(options);
	}

375 376
	//#endregion

377 378
	//#region openEditors()

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

		// 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
394
		const mapGroupToEditors = new Map<IEditorGroup, IEditorInputWithOptions[]>();
395
		if (group === SIDE_GROUP) {
396
			mapGroupToEditors.set(this.findSideBySideGroup(), typedEditors);
397 398 399 400 401 402 403 404 405 406 407 408 409 410
		} 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 已提交
411
		// Open in target groups
B
Benjamin Pasero 已提交
412
		const result: Promise<IEditor | null>[] = [];
B
Benjamin Pasero 已提交
413
		let firstGroup: IEditorGroup | undefined;
414
		mapGroupToEditors.forEach((editorsWithOptions, group) => {
B
Benjamin Pasero 已提交
415 416 417 418
			if (!firstGroup) {
				firstGroup = group;
			}

419
			result.push(group.openEditors(editorsWithOptions));
420
		});
421

422 423
		const openedEditors = await Promise.all(result);

B
Benjamin Pasero 已提交
424 425 426 427
		if (firstGroup) {
			this.ensureGroupActive(firstGroup, group);
		}

428
		return coalesce(openedEditors);
429 430 431 432
	}

	//#endregion

433 434
	//#region isOpen()

B
Benjamin Pasero 已提交
435
	isOpen(editor: IEditorInput | IResourceInput | IUntitledResourceInput): boolean {
436 437 438 439 440 441 442
		return !!this.doGetOpened(editor);
	}

	//#endregion

	//#region getOpend()

B
Benjamin Pasero 已提交
443
	getOpened(editor: IResourceInput | IUntitledResourceInput): IEditorInput | undefined {
444 445 446
		return this.doGetOpened(editor);
	}

B
Benjamin Pasero 已提交
447
	private doGetOpened(editor: IEditorInput | IResourceInput | IUntitledResourceInput): IEditorInput | undefined {
448 449 450
		if (!(editor instanceof EditorInput)) {
			const resourceInput = editor as IResourceInput | IUntitledResourceInput;
			if (!resourceInput.resource) {
R
Rob Lourens 已提交
451
				return undefined; // we need a resource at least
452 453 454 455
			}
		}

		// For each editor group
B
Benjamin Pasero 已提交
456
		for (const group of this.editorGroupService.groups) {
457 458

			// Typed editor
459
			if (editor instanceof EditorInput) {
460 461 462
				if (group.isOpened(editor)) {
					return editor;
				}
463 464
			}

465 466
			// Resource editor
			else {
467
				for (const editorInGroup of group.editors) {
B
Benjamin Pasero 已提交
468
					const resource = toResource(editorInGroup, { supportSideBySide: SideBySideEditor.MASTER });
B
Benjamin Pasero 已提交
469 470 471
					if (!resource) {
						continue; // need a resource to compare with
					}
472

473
					const resourceInput = editor as IResourceInput | IUntitledResourceInput;
474
					if (resourceInput.resource && resource.toString() === resourceInput.resource.toString()) {
475 476 477 478 479
						return editorInGroup;
					}
				}
			}
		}
480

R
Rob Lourens 已提交
481
		return undefined;
482 483 484 485
	}

	//#endregion

486 487
	//#region replaceEditors()

J
Johannes Rieken 已提交
488 489
	replaceEditors(editors: IResourceEditorReplacement[], group: IEditorGroup | GroupIdentifier): Promise<void>;
	replaceEditors(editors: IEditorReplacement[], group: IEditorGroup | GroupIdentifier): Promise<void>;
490
	replaceEditors(editors: Array<IEditorReplacement | IResourceEditorReplacement>, group: IEditorGroup | GroupIdentifier): Promise<void> {
491 492 493 494 495 496 497
		const typedEditors: IEditorReplacement[] = [];

		editors.forEach(replaceEditorArg => {
			if (replaceEditorArg.editor instanceof EditorInput) {
				typedEditors.push(replaceEditorArg as IEditorReplacement);
			} else {
				const editor = replaceEditorArg.editor as IResourceEditor;
B
Benjamin Pasero 已提交
498
				const replacement = replaceEditorArg.replacement as IResourceEditor;
499
				const typedEditor = this.createInput(editor);
B
Benjamin Pasero 已提交
500
				const typedReplacement = this.createInput(replacement);
501 502 503

				typedEditors.push({
					editor: typedEditor,
B
Benjamin Pasero 已提交
504 505
					replacement: typedReplacement,
					options: this.toOptions(replacement.options)
506 507 508 509
				});
			}
		});

510
		const targetGroup = typeof group === 'number' ? this.editorGroupService.getGroup(group) : group;
B
Benjamin Pasero 已提交
511 512 513 514 515
		if (targetGroup) {
			return targetGroup.replaceEditors(typedEditors);
		}

		return Promise.resolve();
516 517 518 519
	}

	//#endregion

520 521 522
	//#region invokeWithinEditorContext()

	invokeWithinEditorContext<T>(fn: (accessor: ServicesAccessor) => T): T {
523 524 525
		const activeTextEditorWidget = this.activeTextEditorWidget;
		if (isCodeEditor(activeTextEditorWidget)) {
			return activeTextEditorWidget.invokeWithinContext(fn);
526 527
		}

528
		const activeGroup = this.editorGroupService.activeGroup;
529 530 531 532 533
		if (activeGroup) {
			return activeGroup.invokeWithinContext(fn);
		}

		return this.instantiationService.invokeFunction(fn);
534 535 536 537
	}

	//#endregion

538 539
	//#region createInput()

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

542
		// Typed Editor Input Support (EditorInput)
543 544 545 546
		if (input instanceof EditorInput) {
			return input;
		}

547 548 549 550 551 552
		// Typed Editor Input Support (IEditorInputWithOptions)
		const editorInputWithOptions = input as IEditorInputWithOptions;
		if (editorInputWithOptions.editor instanceof EditorInput) {
			return editorInputWithOptions.editor;
		}

553
		// Side by Side Support
M
Matt Bierner 已提交
554
		const resourceSideBySideInput = input as IResourceSideBySideInput;
555
		if (resourceSideBySideInput.masterResource && resourceSideBySideInput.detailResource) {
B
Benjamin Pasero 已提交
556 557
			const masterInput = this.createInput({ resource: resourceSideBySideInput.masterResource, forceFile: resourceSideBySideInput.forceFile });
			const detailInput = this.createInput({ resource: resourceSideBySideInput.detailResource, forceFile: resourceSideBySideInput.forceFile });
B
Benjamin Pasero 已提交
558
			const label = resourceSideBySideInput.label || masterInput.getName() || localize('sideBySideLabels', "{0} - {1}", this.toDiffLabel(masterInput), this.toDiffLabel(detailInput));
559 560

			return new SideBySideEditorInput(
B
Benjamin Pasero 已提交
561
				label,
562 563 564 565 566 567 568
				typeof resourceSideBySideInput.description === 'string' ? resourceSideBySideInput.description : masterInput.getDescription(),
				detailInput,
				masterInput
			);
		}

		// Diff Editor Support
M
Matt Bierner 已提交
569
		const resourceDiffInput = input as IResourceDiffInput;
570
		if (resourceDiffInput.leftResource && resourceDiffInput.rightResource) {
B
Benjamin Pasero 已提交
571 572
			const leftInput = this.createInput({ resource: resourceDiffInput.leftResource, forceFile: resourceDiffInput.forceFile });
			const rightInput = this.createInput({ resource: resourceDiffInput.rightResource, forceFile: resourceDiffInput.forceFile });
573
			const label = resourceDiffInput.label || localize('compareLabels', "{0} ↔ {1}", this.toDiffLabel(leftInput), this.toDiffLabel(rightInput));
574

575
			return new DiffEditorInput(label, resourceDiffInput.description, leftInput, rightInput);
576 577 578
		}

		// Untitled file support
M
Matt Bierner 已提交
579
		const untitledInput = input as IUntitledResourceInput;
580
		if (untitledInput.forceUntitled || !untitledInput.resource || (untitledInput.resource && untitledInput.resource.scheme === Schemas.untitled)) {
581
			return this.untitledEditorService.createOrGet(untitledInput.resource, untitledInput.mode, untitledInput.contents, untitledInput.encoding);
582 583 584
		}

		// Resource Editor Support
M
Matt Bierner 已提交
585
		const resourceInput = input as IResourceInput;
586 587 588
		if (resourceInput.resource instanceof URI) {
			let label = resourceInput.label;
			if (!label && resourceInput.resource.scheme !== Schemas.data) {
B
Benjamin Pasero 已提交
589
				label = basename(resourceInput.resource); // derive the label from the path (but not for data URIs)
590 591
			}

592
			return this.createOrGet(resourceInput.resource, this.instantiationService, label, resourceInput.description, resourceInput.encoding, resourceInput.mode, resourceInput.forceFile) as EditorInput;
593 594
		}

B
Benjamin Pasero 已提交
595
		throw new Error('Unknown input type');
596 597
	}

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

				if (description) {
					input.setDescription(description);
				}
609 610 611 612

				if (mode) {
					input.setPreferredMode(mode);
				}
613
			} else if (!(input instanceof DataUriEditorInput)) {
B
Benjamin Pasero 已提交
614 615 616
				if (encoding) {
					input.setPreferredEncoding(encoding);
				}
617 618 619 620

				if (mode) {
					input.setPreferredMode(mode);
				}
621 622 623 624 625 626
			}

			return input;
		}

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

		// Data URI
		else if (resource.scheme === Schemas.data) {
			input = instantiationService.createInstance(DataUriEditorInput, label, description, resource);
		}

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

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

		return input;
	}

649
	private toDiffLabel(input: EditorInput): string | null {
650
		const res = input.getResource();
B
Benjamin Pasero 已提交
651 652 653
		if (!res) {
			return null;
		}
654 655

		// Do not try to extract any paths from simple untitled editors
B
Benjamin Pasero 已提交
656
		if (res.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(res)) {
657 658 659 660
			return input.getName();
		}

		// Otherwise: for diff labels prefer to see the path as part of the label
661
		return this.labelService.getUriLabel(res, { relative: true });
662
	}
663 664

	//#endregion
665 666 667
}

export interface IEditorOpenHandler {
B
Benjamin Pasero 已提交
668 669 670 671 672 673
	(
		delegate: (group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions) => Promise<IEditor | undefined>,
		group: IEditorGroup,
		editor: IEditorInput,
		options?: IEditorOptions | ITextEditorOptions
	): Promise<IEditor | null>;
674 675 676 677 678 679
}

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

	constructor(
684
		@IEditorGroupsService editorGroupService: IEditorGroupsService,
685 686
		@IUntitledEditorService untitledEditorService: IUntitledEditorService,
		@IInstantiationService instantiationService: IInstantiationService,
I
isidor 已提交
687
		@ILabelService labelService: ILabelService,
688 689 690 691
		@IFileService fileService: IFileService,
		@IConfigurationService configurationService: IConfigurationService
	) {
		super(
692
			editorGroupService,
693 694
			untitledEditorService,
			instantiationService,
I
isidor 已提交
695
			labelService,
696 697 698 699 700 701 702 703 704
			fileService,
			configurationService
		);
	}

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

705
	protected async doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise<IEditor | undefined> {
B
Benjamin Pasero 已提交
706 707 708
		if (!this.editorOpenHandler) {
			return super.doOpenEditor(group, editor, options);
		}
709

B
Benjamin Pasero 已提交
710 711 712 713 714 715 716
		const control = await this.editorOpenHandler(
			(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions) => super.doOpenEditor(group, editor, options),
			group,
			editor,
			options
		);

717 718 719
		if (control) {
			return control; // the opening was handled, so return early
		}
720

721
		return super.doOpenEditor(group, editor, options);
722
	}
I
isidor 已提交
723
}
724

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