editorService.ts 43.7 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 { SideBySideEditor as SideBySideEditorChoice, IEditorInput, IEditor, GroupIdentifier, IFileEditorInput, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, EditorInput, SideBySideEditorInput, IEditorInputWithOptions, isEditorInputWithOptions, EditorOptions, TextEditorOptions, IEditorIdentifier, IEditorCloseEvent, ITextEditor, ITextDiffEditor, ITextSideBySideEditor, IRevertOptions, SaveReason, EditorsOrder, isTextEditor, IWorkbenchEditorConfiguration, toResource } 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
import { IFileService, FileOperationEvent, FileOperation, FileChangesEvent, FileChangeType, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
14
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, isEqualOrParent, joinPath } 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, distinct } 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
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { timeout } from 'vs/base/common/async';
35
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
36
import { indexOfPath } from 'vs/base/common/extpath';
37

38
type CachedEditorInput = ResourceEditorInput | IFileEditorInput | UntitledTextEditorInput;
B
Benjamin Pasero 已提交
39
type OpenInEditorGroup = IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE;
40

B
Benjamin Pasero 已提交
41
export class EditorService extends Disposable implements EditorServiceImpl {
42

43
	_serviceBrand: undefined;
44

45 46
	//#region events

47 48
	private readonly _onDidActiveEditorChange = this._register(new Emitter<void>());
	readonly onDidActiveEditorChange = this._onDidActiveEditorChange.event;
49

50 51
	private readonly _onDidVisibleEditorsChange = this._register(new Emitter<void>());
	readonly onDidVisibleEditorsChange = this._onDidVisibleEditorsChange.event;
52

53 54
	private readonly _onDidCloseEditor = this._register(new Emitter<IEditorCloseEvent>());
	readonly onDidCloseEditor = this._onDidCloseEditor.event;
55

56 57
	private readonly _onDidOpenEditorFail = this._register(new Emitter<IEditorIdentifier>());
	readonly onDidOpenEditorFail = this._onDidOpenEditorFail.event;
58

59 60 61
	private readonly _onDidMostRecentlyActiveEditorsChange = this._register(new Emitter<void>());
	readonly onDidMostRecentlyActiveEditorsChange = this._onDidMostRecentlyActiveEditorsChange.event;

62 63
	//#endregion

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

76 77
		this.onConfigurationUpdated(configurationService.getValue<IWorkbenchEditorConfiguration>());

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
		// Out of workspace file watchers
		this._register(this.onDidVisibleEditorsChange(() => this.handleVisibleEditorsChange()));

92
		// File changes & operations
93 94 95 96
		// Note: there is some duplication with the two file event handlers- Since we cannot always rely on the disk events
		// carrying all necessary data in all environments, we also use the file operation events to make sure operations are handled.
		// In any case there is no guarantee if the local event is fired first or the disk one. Thus, code must handle the case
		// that the event ordering is random as well as might not carry all information needed.
97 98 99 100 101
		this._register(this.fileService.onDidRunOperation(e => this.onDidRunFileOperation(e)));
		this._register(this.fileService.onDidFilesChange(e => this.onDidFilesChange(e)));

		// Configuration
		this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue<IWorkbenchEditorConfiguration>())));
102 103
	}

104 105 106 107
	//#region Editor & group event handlers

	private lastActiveEditor: IEditorInput | undefined = undefined;

108 109 110 111 112 113 114
	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) {
115
			this.doHandleActiveEditorChangeEvent();
116 117 118 119
			this._onDidVisibleEditorsChange.fire();
		}
	}

120
	private handleActiveEditorChange(group: IEditorGroup): void {
121
		if (group !== this.editorGroupService.activeGroup) {
122
			return; // ignore if not the active group
123 124 125 126 127 128
		}

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

129
		this.doHandleActiveEditorChangeEvent();
130 131
	}

132
	private doHandleActiveEditorChangeEvent(): void {
133

134 135
		// Remember as last active
		const activeGroup = this.editorGroupService.activeGroup;
B
Benjamin Pasero 已提交
136
		this.lastActiveEditor = withNullAsUndefined(activeGroup.activeEditor);
137

138
		// Fire event to outside parties
139 140 141
		this._onDidActiveEditorChange.fire();
	}

142
	private registerGroupListeners(group: IEditorGroupView): void {
B
Benjamin Pasero 已提交
143
		const groupDisposables = new DisposableStore();
144

B
Benjamin Pasero 已提交
145
		groupDisposables.add(group.onDidGroupChange(e => {
146 147 148 149
			if (e.kind === GroupChangeKind.EDITOR_ACTIVE) {
				this.handleActiveEditorChange(group);
				this._onDidVisibleEditorsChange.fire();
			}
150 151
		}));

B
Benjamin Pasero 已提交
152
		groupDisposables.add(group.onDidCloseEditor(event => {
B
Benjamin Pasero 已提交
153
			this._onDidCloseEditor.fire(event);
154 155
		}));

B
Benjamin Pasero 已提交
156
		groupDisposables.add(group.onWillOpenEditor(event => {
157
			this.onGroupWillOpenEditor(group, event);
158 159
		}));

B
Benjamin Pasero 已提交
160
		groupDisposables.add(group.onDidOpenEditorFail(editor => {
I
isidor 已提交
161
			this._onDidOpenEditorFail.fire({ editor, groupId: group.id });
162 163
		}));

J
Joao Moreno 已提交
164
		Event.once(group.onWillDispose)(() => {
B
Benjamin Pasero 已提交
165
			dispose(groupDisposables);
166 167 168
		});
	}

169
	//#endregion
170

171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
	//#region Visible Editors Change: Install file watchers for out of workspace resources that became visible

	private readonly activeOutOfWorkspaceWatchers = new ResourceMap<IDisposable>();

	private handleVisibleEditorsChange(): void {
		const visibleOutOfWorkspaceResources = new ResourceMap<URI>();

		for (const editor of this.visibleEditors) {
			const resources = distinct(coalesce([
				toResource(editor, { supportSideBySide: SideBySideEditorChoice.MASTER }),
				toResource(editor, { supportSideBySide: SideBySideEditorChoice.DETAILS })
			]), resource => resource.toString());

			for (const resource of resources) {
				if (this.fileService.canHandleResource(resource) && !this.contextService.isInsideWorkspace(resource)) {
					visibleOutOfWorkspaceResources.set(resource, resource);
				}
188 189
			}
		}
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205

		// Handle no longer visible out of workspace resources
		this.activeOutOfWorkspaceWatchers.keys().forEach(resource => {
			if (!visibleOutOfWorkspaceResources.get(resource)) {
				dispose(this.activeOutOfWorkspaceWatchers.get(resource));
				this.activeOutOfWorkspaceWatchers.delete(resource);
			}
		});

		// Handle newly visible out of workspace resources
		visibleOutOfWorkspaceResources.forEach(resource => {
			if (!this.activeOutOfWorkspaceWatchers.get(resource)) {
				const disposable = this.fileService.watch(resource);
				this.activeOutOfWorkspaceWatchers.set(resource, disposable);
			}
		});
206 207
	}

208 209
	//#endregion

210
	//#region File Changes: Move & Deletes to move or close opend editors
211 212 213 214 215 216 217 218 219 220 221 222 223 224

	private onDidRunFileOperation(e: FileOperationEvent): void {

		// Handle moves specially when file is opened
		if (e.isOperation(FileOperation.MOVE)) {
			this.handleMovedFile(e.resource, e.target.resource);
		}

		// Handle deletes
		if (e.isOperation(FileOperation.DELETE) || e.isOperation(FileOperation.MOVE)) {
			this.handleDeletedFile(e.resource, false, e.target ? e.target.resource : undefined);
		}
	}

225 226 227 228 229 230 231
	private onDidFilesChange(e: FileChangesEvent): void {
		if (e.gotDeleted()) {
			this.handleDeletedFile(e, true);
		}
	}

	private handleMovedFile(source: URI, target: URI): void {
232 233 234 235 236
		for (const group of this.editorGroupService.groups) {
			let replacements: (IResourceEditorReplacement | IEditorReplacement)[] = [];

			for (const editor of group.editors) {
				const resource = editor.resource;
237
				if (!resource || !isEqualOrParent(resource, source)) {
238 239 240 241 242
					continue; // not matching our resource
				}

				// Determine new resulting target resource
				let targetResource: URI;
243 244
				if (source.toString() === resource.toString()) {
					targetResource = target; // file got moved
245 246
				} else {
					const ignoreCase = !this.fileService.hasCapability(resource, FileSystemProviderCapabilities.PathCaseSensitive);
247 248
					const index = indexOfPath(resource.path, source.path, ignoreCase);
					targetResource = joinPath(target, resource.path.substr(index + source.path.length + 1)); // parent folder got moved
249 250 251 252 253 254 255 256
				}

				// Delegate move() to editor instance
				const moveResult = editor.move(group.id, targetResource);
				if (!moveResult) {
					return; // not target - ignore
				}

257
				const optionOverrides = {
258 259 260
					preserveFocus: true,
					pinned: group.isPinned(editor),
					index: group.getIndexOfEditor(editor),
261
					inactive: !group.isActive(editor)
262 263 264 265 266 267 268 269 270
				};

				// Construct a replacement with our extra options mixed in
				if (moveResult.editor instanceof EditorInput) {
					replacements.push({
						editor,
						replacement: moveResult.editor,
						options: {
							...moveResult.options,
271
							...optionOverrides
272 273 274 275 276 277 278 279
						}
					});
				} else {
					replacements.push({
						editor: { resource: editor.resource },
						replacement: {
							...moveResult.editor,
							options: {
B
Benjamin Pasero 已提交
280
								...moveResult.editor.options,
281
								...optionOverrides
282 283 284 285 286 287 288 289 290 291 292 293 294
							}
						}
					});
				}
			}

			// Apply replacements
			if (replacements.length) {
				this.replaceEditors(replacements, group);
			}
		}
	}

295
	private closeOnFileDelete: boolean = false;
296
	private fileInputFactory = Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).getFileInputFactory();
297 298 299 300 301 302 303 304
	private onConfigurationUpdated(configuration: IWorkbenchEditorConfiguration): void {
		if (typeof configuration.workbench?.editor?.closeOnFileDelete === 'boolean') {
			this.closeOnFileDelete = configuration.workbench.editor.closeOnFileDelete;
		} else {
			this.closeOnFileDelete = false; // default
		}
	}

305
	private handleDeletedFile(arg1: URI | FileChangesEvent, isExternal: boolean, movedTo?: URI): void {
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387
		for (const editor of this.getAllNonDirtyEditors({ includeUntitled: false, supportSideBySide: true })) {
			(async () => {
				const resource = editor.resource;
				if (!resource) {
					return;
				}

				// Handle deletes in opened editors depending on:
				// - the user has not disabled the setting closeOnFileDelete
				// - the file change is local
				// - the input is  a file that is not resolved (we need to dispose because we cannot restore otherwise since we do not have the contents)
				if (this.closeOnFileDelete || !isExternal || (this.fileInputFactory.isFileInput(editor) && !editor.isResolved())) {

					// Do NOT close any opened editor that matches the resource path (either equal or being parent) of the
					// resource we move to (movedTo). Otherwise we would close a resource that has been renamed to the same
					// path but different casing.
					if (movedTo && isEqualOrParent(resource, movedTo)) {
						return;
					}

					let matches = false;
					if (arg1 instanceof FileChangesEvent) {
						matches = arg1.contains(resource, FileChangeType.DELETED);
					} else {
						matches = isEqualOrParent(resource, arg1);
					}

					if (!matches) {
						return;
					}

					// We have received reports of users seeing delete events even though the file still
					// exists (network shares issue: https://github.com/Microsoft/vscode/issues/13665).
					// Since we do not want to close an editor without reason, we have to check if the
					// file is really gone and not just a faulty file event.
					// This only applies to external file events, so we need to check for the isExternal
					// flag.
					let exists = false;
					if (isExternal && this.fileService.canHandleResource(resource)) {
						await timeout(100);
						exists = await this.fileService.exists(resource);
					}

					if (!exists && !editor.isDisposed()) {
						editor.dispose();
					} else if (this.environmentService.verbose) {
						console.warn(`File exists even though we received a delete event: ${resource.toString()}`);
					}
				}
			})();
		}
	}

	private getAllNonDirtyEditors(options: { includeUntitled: boolean, supportSideBySide: boolean }): IEditorInput[] {
		const editors: IEditorInput[] = [];

		function conditionallyAddEditor(editor: IEditorInput): void {
			if (editor.isUntitled() && !options.includeUntitled) {
				return;
			}

			if (editor.isDirty()) {
				return;
			}

			editors.push(editor);
		}

		for (const editor of this.editors) {
			if (options.supportSideBySide && editor instanceof SideBySideEditorInput) {
				conditionallyAddEditor(editor.master);
				conditionallyAddEditor(editor.details);
			} else {
				conditionallyAddEditor(editor);
			}
		}

		return editors;
	}

	//#endregion

388 389 390 391
	//#region Editor accessors

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

392
	get activeControl(): IVisibleEditor | undefined {
M
Matt Bierner 已提交
393
		return this.editorGroupService.activeGroup?.activeControl;
394 395
	}

396
	get activeTextEditorWidget(): ICodeEditor | IDiffEditor | undefined {
397 398 399
		const activeControl = this.activeControl;
		if (activeControl) {
			const activeControlWidget = activeControl.getControl();
400
			if (isCodeEditor(activeControlWidget) || isDiffEditor(activeControlWidget)) {
401 402 403 404
				return activeControlWidget;
			}
		}

R
Rob Lourens 已提交
405
		return undefined;
406 407
	}

408 409 410 411 412 413 414 415 416 417 418 419 420
	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 已提交
421 422 423 424
	get count(): number {
		return this.editorsObserver.count;
	}

425
	get editors(): IEditorInput[] {
B
Benjamin Pasero 已提交
426 427 428 429 430 431 432 433 434 435 436 437
		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 })));
438 439 440 441 442
		});

		return editors;
	}

443
	get activeEditor(): IEditorInput | undefined {
444
		const activeGroup = this.editorGroupService.activeGroup;
445

M
Matt Bierner 已提交
446
		return activeGroup ? withNullAsUndefined(activeGroup.activeEditor) : undefined;
447 448
	}

M
Matt Bierner 已提交
449
	get visibleControls(): IVisibleEditor[] {
450
		return coalesce(this.editorGroupService.groups.map(group => group.activeControl));
451 452
	}

453
	get visibleTextEditorWidgets(): Array<ICodeEditor | IDiffEditor> {
454
		return this.visibleControls.map(control => control.getControl() as ICodeEditor | IDiffEditor).filter(widget => isCodeEditor(widget) || isDiffEditor(widget));
455 456
	}

457
	get visibleEditors(): IEditorInput[] {
458
		return coalesce(this.editorGroupService.groups.map(group => group.activeEditor));
459 460
	}

461 462
	//#endregion

463 464
	//#region preventOpenEditor()

465 466
	private readonly openEditorHandlers: IOpenEditorOverrideHandler[] = [];

467 468 469 470 471 472 473 474 475 476 477
	overrideOpenEditor(handler: IOpenEditorOverrideHandler): IDisposable {
		this.openEditorHandlers.push(handler);

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

478 479 480 481 482 483 484 485 486 487 488 489 490 491 492
	private onGroupWillOpenEditor(group: IEditorGroup, event: IEditorOpeningEvent): void {
		if (event.options && event.options.ignoreOverrides) {
			return;
		}

		for (const handler of this.openEditorHandlers) {
			const result = handler(event.editor, event.options, group);
			const override = result?.override;
			if (override) {
				event.prevent((() => override.then(editor => withNullAsUndefined(editor))));
				break;
			}
		}
	}

493 494
	//#endregion

495 496
	//#region openEditor()

B
Benjamin Pasero 已提交
497
	openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: OpenInEditorGroup): Promise<IEditor | undefined>;
498
	openEditor(editor: IResourceInput | IUntitledTextResourceInput, group?: OpenInEditorGroup): Promise<ITextEditor | undefined>;
B
Benjamin Pasero 已提交
499 500 501
	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> {
502 503 504 505 506 507 508 509 510 511 512
		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 已提交
513 514 515
		let resolvedGroup: IEditorGroup | undefined;
		let candidateGroup: OpenInEditorGroup | undefined;

B
Benjamin Pasero 已提交
516 517
		let typedEditor: EditorInput | undefined;
		let typedOptions: EditorOptions | undefined;
518 519 520

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

B
Benjamin Pasero 已提交
524 525
			candidateGroup = group;
			resolvedGroup = this.findTargetGroup(typedEditor, typedOptions, candidateGroup);
526 527 528
		}

		// Untyped Text Editor Support
B
Benjamin Pasero 已提交
529 530 531 532 533
		else {
			const textInput = <IResourceEditor>editor;
			typedEditor = this.createInput(textInput);
			if (typedEditor) {
				typedOptions = TextEditorOptions.from(textInput);
534

B
Benjamin Pasero 已提交
535 536 537 538 539 540
				candidateGroup = optionsOrGroup as OpenInEditorGroup;
				resolvedGroup = this.findTargetGroup(typedEditor, typedOptions, candidateGroup);
			}
		}

		if (typedEditor && resolvedGroup) {
B
Benjamin Pasero 已提交
541
			if (
542 543 544
				this.editorGroupService.activeGroup !== resolvedGroup && 	// only if target group is not already active
				typedOptions && !typedOptions.inactive &&					// never for inactive editors
				typedOptions.preserveFocus &&								// only if preserveFocus
545
				typeof typedOptions.activation !== 'number' &&				// only if activation is not already defined (either true or false)
546
				candidateGroup !== SIDE_GROUP								// never for the SIDE_GROUP
B
Benjamin Pasero 已提交
547
			) {
548 549 550
				// 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
551
				// is already providing `activation`.
552 553 554 555 556
				//
				// 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.
557
				typedOptions.overwrite({ activation: EditorActivation.ACTIVATE });
B
Benjamin Pasero 已提交
558
			}
B
Benjamin Pasero 已提交
559

560
			return [resolvedGroup, typedEditor, typedOptions];
561 562
		}

B
Benjamin Pasero 已提交
563
		return undefined;
564
	}
565

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

B
Benjamin Pasero 已提交
569 570
		// Group: Instance of Group
		if (group && typeof group !== 'number') {
B
Benjamin Pasero 已提交
571
			targetGroup = group;
B
Benjamin Pasero 已提交
572 573
		}

574
		// Group: Side by Side
B
Benjamin Pasero 已提交
575
		else if (group === SIDE_GROUP) {
576
			targetGroup = this.findSideBySideGroup();
577 578
		}

579
		// Group: Specific Group
B
Benjamin Pasero 已提交
580
		else if (typeof group === 'number' && group >= 0) {
581
			targetGroup = this.editorGroupService.getGroup(group);
582 583
		}

584 585
		// Group: Unspecified without a specific index to open
		else if (!options || typeof options.index !== 'number') {
586
			const groupsByLastActive = this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE);
587 588

			// Respect option to reveal an editor if it is already visible in any group
B
Benjamin Pasero 已提交
589
			if (options?.revealIfVisible) {
590
				for (const group of groupsByLastActive) {
B
Benjamin Pasero 已提交
591
					if (group.isActive(input)) {
592 593 594 595 596 597 598
						targetGroup = group;
						break;
					}
				}
			}

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

B
Benjamin Pasero 已提交
605
					for (const group of groupsByLastActive) {
B
Benjamin Pasero 已提交
606 607 608 609 610 611 612 613
						if (group.isOpened(input)) {
							if (!groupWithInputOpened) {
								groupWithInputOpened = group;
							}

							if (!groupWithInputActive && group.isActive(input)) {
								groupWithInputActive = group;
							}
B
Benjamin Pasero 已提交
614
						}
B
Benjamin Pasero 已提交
615 616 617

						if (groupWithInputOpened && groupWithInputActive) {
							break; // we found all groups we wanted
B
Benjamin Pasero 已提交
618
						}
619
					}
B
Benjamin Pasero 已提交
620 621 622

					// Prefer a target group where the input is visible
					targetGroup = groupWithInputActive || groupWithInputOpened;
623 624 625 626 627
				}
			}
		}

		// Fallback to active group if target not valid
628
		if (!targetGroup) {
629
			targetGroup = this.editorGroupService.activeGroup;
630 631
		}

632 633 634
		return targetGroup;
	}

635
	private findSideBySideGroup(): IEditorGroup {
636
		const direction = preferredSideBySideGroupDirection(this.configurationService);
637

638
		let neighbourGroup = this.editorGroupService.findGroup({ direction });
639
		if (!neighbourGroup) {
640
			neighbourGroup = this.editorGroupService.addGroup(this.editorGroupService.activeGroup, direction);
641 642 643
		}

		return neighbourGroup;
644 645
	}

B
Benjamin Pasero 已提交
646
	private toOptions(options?: IEditorOptions | ITextEditorOptions | EditorOptions): EditorOptions {
647 648 649 650 651
		if (!options || options instanceof EditorOptions) {
			return options as EditorOptions;
		}

		const textOptions: ITextEditorOptions = options;
B
Benjamin Pasero 已提交
652
		if (textOptions.selection || textOptions.viewState) {
653 654 655 656 657 658
			return TextEditorOptions.create(options);
		}

		return EditorOptions.create(options);
	}

659 660
	//#endregion

661 662
	//#region openEditors()

B
Benjamin Pasero 已提交
663 664 665
	openEditors(editors: IEditorInputWithOptions[], group?: OpenInEditorGroup): Promise<IEditor[]>;
	openEditors(editors: IResourceEditor[], group?: OpenInEditorGroup): Promise<IEditor[]>;
	async openEditors(editors: Array<IEditorInputWithOptions | IResourceEditor>, group?: OpenInEditorGroup): Promise<IEditor[]> {
666 667 668 669 670 671 672 673 674 675 676 677

		// 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
678
		const mapGroupToEditors = new Map<IEditorGroup, IEditorInputWithOptions[]>();
679
		if (group === SIDE_GROUP) {
680
			mapGroupToEditors.set(this.findSideBySideGroup(), typedEditors);
681 682 683 684 685 686 687 688 689 690 691 692 693 694
		} 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 已提交
695
		// Open in target groups
B
Benjamin Pasero 已提交
696
		const result: Promise<IEditor | null>[] = [];
697
		mapGroupToEditors.forEach((editorsWithOptions, group) => {
698
			result.push(group.openEditors(editorsWithOptions));
699
		});
700

B
Benjamin Pasero 已提交
701
		return coalesce(await Promise.all(result));
702 703 704 705
	}

	//#endregion

706 707
	//#region isOpen()

708 709
	isOpen(editor: IEditorInput): boolean {
		return this.editorGroupService.groups.some(group => group.isOpened(editor));
710 711 712 713
	}

	//#endregion

714 715
	//#region replaceEditors()

J
Johannes Rieken 已提交
716 717
	replaceEditors(editors: IResourceEditorReplacement[], group: IEditorGroup | GroupIdentifier): Promise<void>;
	replaceEditors(editors: IEditorReplacement[], group: IEditorGroup | GroupIdentifier): Promise<void>;
718
	replaceEditors(editors: Array<IEditorReplacement | IResourceEditorReplacement>, group: IEditorGroup | GroupIdentifier): Promise<void> {
719 720 721 722
		const typedEditors: IEditorReplacement[] = [];

		editors.forEach(replaceEditorArg => {
			if (replaceEditorArg.editor instanceof EditorInput) {
B
Benjamin Pasero 已提交
723 724 725 726 727 728 729
				const replacementArg = replaceEditorArg as IEditorReplacement;

				typedEditors.push({
					editor: replacementArg.editor,
					replacement: replacementArg.replacement,
					options: this.toOptions(replacementArg.options)
				});
730
			} else {
B
Benjamin Pasero 已提交
731
				const replacementArg = replaceEditorArg as IResourceEditorReplacement;
732 733

				typedEditors.push({
B
Benjamin Pasero 已提交
734 735 736
					editor: this.createInput(replacementArg.editor),
					replacement: this.createInput(replacementArg.replacement),
					options: this.toOptions(replacementArg.replacement.options)
737 738 739 740
				});
			}
		});

741
		const targetGroup = typeof group === 'number' ? this.editorGroupService.getGroup(group) : group;
B
Benjamin Pasero 已提交
742 743 744 745 746
		if (targetGroup) {
			return targetGroup.replaceEditors(typedEditors);
		}

		return Promise.resolve();
747 748 749 750
	}

	//#endregion

751 752 753
	//#region invokeWithinEditorContext()

	invokeWithinEditorContext<T>(fn: (accessor: ServicesAccessor) => T): T {
754 755 756
		const activeTextEditorWidget = this.activeTextEditorWidget;
		if (isCodeEditor(activeTextEditorWidget)) {
			return activeTextEditorWidget.invokeWithinContext(fn);
757 758
		}

759
		const activeGroup = this.editorGroupService.activeGroup;
760 761 762 763 764
		if (activeGroup) {
			return activeGroup.invokeWithinContext(fn);
		}

		return this.instantiationService.invokeFunction(fn);
765 766 767 768
	}

	//#endregion

769 770
	//#region createInput()

771 772
	private readonly editorInputCache = new ResourceMap<CachedEditorInput>();

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

775
		// Typed Editor Input Support (EditorInput)
776 777 778 779
		if (input instanceof EditorInput) {
			return input;
		}

780 781 782 783 784 785
		// Typed Editor Input Support (IEditorInputWithOptions)
		const editorInputWithOptions = input as IEditorInputWithOptions;
		if (editorInputWithOptions.editor instanceof EditorInput) {
			return editorInputWithOptions.editor;
		}

786
		// Side by Side Support
M
Matt Bierner 已提交
787
		const resourceSideBySideInput = input as IResourceSideBySideInput;
788
		if (resourceSideBySideInput.masterResource && resourceSideBySideInput.detailResource) {
B
Benjamin Pasero 已提交
789 790
			const masterInput = this.createInput({ resource: resourceSideBySideInput.masterResource, forceFile: resourceSideBySideInput.forceFile });
			const detailInput = this.createInput({ resource: resourceSideBySideInput.detailResource, forceFile: resourceSideBySideInput.forceFile });
791 792

			return new SideBySideEditorInput(
793 794
				resourceSideBySideInput.label || this.toSideBySideLabel(detailInput, masterInput, '-'),
				resourceSideBySideInput.description,
795 796 797 798 799 800
				detailInput,
				masterInput
			);
		}

		// Diff Editor Support
M
Matt Bierner 已提交
801
		const resourceDiffInput = input as IResourceDiffInput;
802
		if (resourceDiffInput.leftResource && resourceDiffInput.rightResource) {
B
Benjamin Pasero 已提交
803 804
			const leftInput = this.createInput({ resource: resourceDiffInput.leftResource, forceFile: resourceDiffInput.forceFile });
			const rightInput = this.createInput({ resource: resourceDiffInput.rightResource, forceFile: resourceDiffInput.forceFile });
805

806 807 808 809 810 811
			return new DiffEditorInput(
				resourceDiffInput.label || this.toSideBySideLabel(leftInput, rightInput, ''),
				resourceDiffInput.description,
				leftInput,
				rightInput
			);
812 813 814
		}

		// Untitled file support
815
		const untitledInput = input as IUntitledTextResourceInput;
816
		if (untitledInput.forceUntitled || !untitledInput.resource || (untitledInput.resource && untitledInput.resource.scheme === Schemas.untitled)) {
B
Benjamin Pasero 已提交
817
			const untitledOptions = {
B
Benjamin Pasero 已提交
818 819 820
				mode: untitledInput.mode,
				initialValue: untitledInput.contents,
				encoding: untitledInput.encoding
B
Benjamin Pasero 已提交
821 822 823
			};

			// Untitled resource: use as hint for an existing untitled editor
824
			let untitledModel: IUntitledTextEditorModel;
B
Benjamin Pasero 已提交
825
			if (untitledInput.resource?.scheme === Schemas.untitled) {
826
				untitledModel = this.untitledTextEditorService.create({ untitledResource: untitledInput.resource, ...untitledOptions });
B
Benjamin Pasero 已提交
827 828 829 830
			}

			// Other resource: use as hint for associated filepath
			else {
831
				untitledModel = this.untitledTextEditorService.create({ associatedResource: untitledInput.resource, ...untitledOptions });
B
Benjamin Pasero 已提交
832
			}
833 834 835 836 837 838 839

			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
840 841 842 843
				// 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.
844 845 846 847
				Event.once(input.onDispose)(() => untitledModel.dispose());

				return input;
			}) as EditorInput;
848 849 850
		}

		// Resource Editor Support
M
Matt Bierner 已提交
851
		const resourceInput = input as IResourceInput;
852 853
		if (resourceInput.resource instanceof URI) {
			let label = resourceInput.label;
854 855
			if (!label) {
				label = basename(resourceInput.resource); // derive the label from the path
856 857
			}

858
			return this.createOrGetCached(resourceInput.resource, () => {
859

860 861 862
				// 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 已提交
863 864
				}

865 866 867
				// Resource
				return this.instantiationService.createInstance(ResourceEditorInput, resourceInput.label, resourceInput.description, resourceInput.resource, resourceInput.mode);
			}, cachedInput => {
868

869 870 871
				// Untitled
				if (cachedInput instanceof UntitledTextEditorInput) {
					return;
B
Benjamin Pasero 已提交
872
				}
873

874 875 876 877 878 879 880 881 882
				// Files
				else if (!(cachedInput instanceof ResourceEditorInput)) {
					if (resourceInput.encoding) {
						cachedInput.setPreferredEncoding(resourceInput.encoding);
					}

					if (resourceInput.mode) {
						cachedInput.setPreferredMode(resourceInput.mode);
					}
883
				}
884

885 886 887 888 889
				// Resources
				else {
					if (label) {
						cachedInput.setName(label);
					}
890

891 892 893 894 895 896 897 898 899
					if (resourceInput.description) {
						cachedInput.setDescription(resourceInput.description);
					}

					if (resourceInput.mode) {
						cachedInput.setPreferredMode(resourceInput.mode);
					}
				}
			}) as EditorInput;
900 901
		}

902 903 904 905 906 907 908 909 910 911 912 913 914
		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;
915 916
		}

917 918 919 920
		// Otherwise create and add to cache
		input = factoryFn();
		this.editorInputCache.set(resource, input);
		Event.once(input.onDispose)(() => this.editorInputCache.delete(resource));
921 922 923 924

		return input;
	}

925
	private toSideBySideLabel(leftInput: EditorInput, rightInput: EditorInput, divider: string): string | undefined {
926 927
		const leftResource = leftInput.resource;
		const rightResource = rightInput.resource;
928 929 930

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

934 935 936 937 938
		// 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 })}`;
939 940
		}

941 942
		// Signal back that the label should be computed from within the editor
		return undefined;
943
	}
944 945

	//#endregion
946

B
Benjamin Pasero 已提交
947
	//#region save/revert
948 949 950 951 952 953 954 955 956 957 958 959 960 961

	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[] = [];
962
		const editorsToSaveSequentially: IEditorIdentifier[] = [];
963
		if (options?.saveAs) {
964
			editorsToSaveSequentially.push(...editors);
965 966 967
		} else {
			for (const { groupId, editor } of editors) {
				if (editor.isUntitled()) {
968
					editorsToSaveSequentially.push({ groupId, editor });
969 970 971 972 973 974 975 976 977
				} else {
					editorsToSaveParallel.push({ groupId, editor });
				}
			}
		}

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

978 979 980 981
			// Use save as a hint to pin the editor if used explicitly
			if (options?.reason === SaveReason.EXPLICIT) {
				this.editorGroupService.getGroup(groupId)?.pinEditor(editor);
			}
982 983

			// Save
984
			return editor.save(groupId, options);
985 986 987
		}));

		// Editors to save sequentially
988
		for (const { groupId, editor } of editorsToSaveSequentially) {
989
			if (editor.isDisposed()) {
990
				continue; // might have been disposed from the save already
991 992
			}

993 994 995 996 997 998 999 1000
			// 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();
			}
1001

1002
			const result = options?.saveAs ? await editor.saveAs(groupId, options) : await editor.save(groupId, options);
1003 1004 1005
			if (!result) {
				return false; // failed or cancelled, abort
			}
1006 1007 1008 1009 1010 1011 1012 1013 1014 1015

			// 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);
				}
			}
1016 1017 1018 1019 1020 1021
		}

		return true;
	}

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

B
Benjamin Pasero 已提交
1025 1026 1027 1028 1029 1030 1031 1032
	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 }) => {
1033 1034 1035
			if (editor.isDisposed()) {
				return true; // might have been disposed from from the revert already
			}
B
Benjamin Pasero 已提交
1036 1037 1038 1039

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

B
Benjamin Pasero 已提交
1040
			return editor.revert(groupId, options);
B
Benjamin Pasero 已提交
1041
		}));
1042

B
Benjamin Pasero 已提交
1043
		return result.every(success => !!success);
1044 1045
	}

B
Benjamin Pasero 已提交
1046
	async revertAll(options?: IRevertAllEditorsOptions): Promise<boolean> {
B
Benjamin Pasero 已提交
1047
		return this.revert(this.getAllDirtyEditors(options), options);
B
Benjamin Pasero 已提交
1048 1049
	}

B
Benjamin Pasero 已提交
1050
	private getAllDirtyEditors(options?: IBaseSaveRevertAllEditorOptions): IEditorIdentifier[] {
B
Benjamin Pasero 已提交
1051 1052
		const editors: IEditorIdentifier[] = [];

1053 1054
		for (const group of this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) {
			for (const editor of group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)) {
B
Benjamin Pasero 已提交
1055
				if (editor.isDirty() && (!editor.isUntitled() || !!options?.includeUntitled)) {
B
Benjamin Pasero 已提交
1056
					editors.push({ groupId: group.id, editor });
1057 1058 1059
				}
			}
		}
B
Benjamin Pasero 已提交
1060 1061

		return editors;
1062 1063 1064
	}

	//#endregion
1065 1066 1067 1068 1069 1070 1071 1072

	dispose(): void {
		super.dispose();

		// Dispose remaining watchers if any
		this.activeOutOfWorkspaceWatchers.forEach(disposable => dispose(disposable));
		this.activeOutOfWorkspaceWatchers.clear();
	}
1073 1074 1075
}

export interface IEditorOpenHandler {
B
Benjamin Pasero 已提交
1076
	(
1077
		delegate: (group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions) => Promise<IEditor | null>,
B
Benjamin Pasero 已提交
1078 1079 1080 1081
		group: IEditorGroup,
		editor: IEditorInput,
		options?: IEditorOptions | ITextEditorOptions
	): Promise<IEditor | null>;
1082 1083 1084 1085
}

/**
 * The delegating workbench editor service can be used to override the behaviour of the openEditor()
1086 1087
 * method by providing a IEditorOpenHandler. All calls are being delegated to the existing editor
 * service otherwise.
1088
 */
1089 1090 1091
export class DelegatingEditorService implements IEditorService {

	_serviceBrand: undefined;
1092 1093

	constructor(
1094 1095 1096
		private editorOpenHandler: IEditorOpenHandler,
		@IEditorService private editorService: EditorService
	) { }
1097

1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117
	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
			}
1118

1119
			return withNullAsUndefined(await resolvedGroup.openEditor(resolvedEditor, resolvedOptions));
B
Benjamin Pasero 已提交
1120
		}
1121

1122 1123
		return undefined;
	}
B
Benjamin Pasero 已提交
1124

1125
	//#region Delegate to IEditorService
1126

1127 1128 1129 1130 1131 1132
	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; }
1133
	get activeTextEditorMode(): string | undefined { return this.editorService.activeTextEditorMode; }
1134 1135 1136 1137
	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 已提交
1138 1139 1140
	get count(): number { return this.editorService.count; }

	getEditors(order: EditorsOrder): ReadonlyArray<IEditorIdentifier> { return this.editorService.getEditors(order); }
1141 1142 1143 1144 1145

	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);
1146
	}
1147 1148 1149 1150 1151 1152 1153

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

1154
	isOpen(editor: IEditorInput): boolean { return this.editorService.isOpen(editor); }
1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168

	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 已提交
1169
}
1170

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