editor.ts 29.4 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';

J
Johannes Rieken 已提交
7
import { TPromise } from 'vs/base/common/winjs.base';
M
Matt Bierner 已提交
8
import { Event, Emitter, once } from 'vs/base/common/event';
9
import * as objects from 'vs/base/common/objects';
10
import * as types from 'vs/base/common/types';
E
Erich Gamma 已提交
11
import URI from 'vs/base/common/uri';
S
Sandeep Somavarapu 已提交
12
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
13
import { IEditor, IEditorViewState, ScrollType } from 'vs/editor/common/editorCommon';
14
import { IEditorInput, IEditorModel, IEditorOptions, ITextEditorOptions, IBaseResourceInput, Position, Verbosity, IEditor as IBaseEditor, IRevertOptions } from 'vs/platform/editor/common/editor';
J
Johannes Rieken 已提交
15
import { IInstantiationService, IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation';
16
import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
17
import { Registry } from 'vs/platform/registry/common/platform';
A
Alex Dima 已提交
18
import { ITextModel } from 'vs/editor/common/model';
19
import { Schemas } from 'vs/base/common/network';
20
import { LRUCache } from 'vs/base/common/map';
21
import { INextEditorGroupsService, INextEditorGroup } from 'vs/workbench/services/group/common/nextEditorGroupsService';
22

23 24 25 26 27
export const EditorsVisibleContext = new RawContextKey<boolean>('editorIsOpen', false);
export const NoEditorsVisibleContext: ContextKeyExpr = EditorsVisibleContext.toNegated();
export const TextCompareEditorVisibleContext = new RawContextKey<boolean>('textCompareEditorVisible', false);
export const ActiveEditorGroupEmptyContext = new RawContextKey<boolean>('activeEditorGroupEmpty', false);
export const MultipleEditorGroupsContext = new RawContextKey<boolean>('multipleEditorGroups', false);
B
Benjamin Pasero 已提交
28
export const SingleEditorGroupsContext = MultipleEditorGroupsContext.toNegated();
29
export const InEditorZenModeContext = new RawContextKey<boolean>('inZenMode', false);
E
Erich Gamma 已提交
30

31 32 33 34 35 36
export enum ConfirmResult {
	SAVE,
	DONT_SAVE,
	CANCEL
}

37 38 39 40 41 42 43 44 45 46
/**
 * Text diff editor id.
 */
export const TEXT_DIFF_EDITOR_ID = 'workbench.editors.textDiffEditor';

/**
 * Binary diff editor id.
 */
export const BINARY_DIFF_EDITOR_ID = 'workbench.editors.binaryResourceDiffEditor';

47
export interface IFileInputFactory {
48

49
	createFileInput(resource: URI, encoding: string, instantiationService: IInstantiationService): IFileEditorInput;
50 51

	isFileInput(obj: any): obj is IFileEditorInput;
52 53
}

54
export interface IEditorInputFactoryRegistry {
B
Benjamin Pasero 已提交
55 56

	/**
57
	 * Registers the file input factory to use for file inputs.
B
Benjamin Pasero 已提交
58
	 */
59
	registerFileInputFactory(factory: IFileInputFactory): void;
B
Benjamin Pasero 已提交
60 61

	/**
62
	 * Returns the file input factory to use for file inputs.
B
Benjamin Pasero 已提交
63
	 */
64
	getFileInputFactory(): IFileInputFactory;
B
Benjamin Pasero 已提交
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99

	/**
	 * Registers a editor input factory for the given editor input to the registry. An editor input factory
	 * is capable of serializing and deserializing editor inputs from string data.
	 *
	 * @param editorInputId the identifier of the editor input
	 * @param factory the editor input factory for serialization/deserialization
	 */
	registerEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0<IEditorInputFactory>): void;

	/**
	 * Returns the editor input factory for the given editor input.
	 *
	 * @param editorInputId the identifier of the editor input
	 */
	getEditorInputFactory(editorInputId: string): IEditorInputFactory;

	setInstantiationService(service: IInstantiationService): void;
}

export interface IEditorInputFactory {

	/**
	 * Returns a string representation of the provided editor input that contains enough information
	 * to deserialize back to the original editor input from the deserialize() method.
	 */
	serialize(editorInput: EditorInput): string;

	/**
	 * Returns an editor input from the provided serialized form of the editor input. This form matches
	 * the value returned from the serialize() method.
	 */
	deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput;
}

E
Erich Gamma 已提交
100 101 102 103
/**
 * Editor inputs are lightweight objects that can be passed to the workbench API to open inside the editor part.
 * Each editor input is mapped to an editor that is capable of opening it through the Platform facade.
 */
104
export abstract class EditorInput implements IEditorInput {
M
Matt Bierner 已提交
105
	private readonly _onDispose: Emitter<void>;
106
	protected _onDidChangeDirty: Emitter<void>;
B
Benjamin Pasero 已提交
107
	protected _onDidChangeLabel: Emitter<void>;
108

E
Erich Gamma 已提交
109 110
	private disposed: boolean;

111
	constructor() {
112
		this._onDidChangeDirty = new Emitter<void>();
B
Benjamin Pasero 已提交
113
		this._onDidChangeLabel = new Emitter<void>();
114 115
		this._onDispose = new Emitter<void>();

116 117 118
		this.disposed = false;
	}

119 120 121 122 123 124 125
	/**
	 * Fired when the dirty state of this input changes.
	 */
	public get onDidChangeDirty(): Event<void> {
		return this._onDidChangeDirty.event;
	}

B
Benjamin Pasero 已提交
126 127 128 129 130 131 132
	/**
	 * Fired when the label this input changes.
	 */
	public get onDidChangeLabel(): Event<void> {
		return this._onDidChangeLabel.event;
	}

133 134 135 136 137 138 139
	/**
	 * Fired when the model gets disposed.
	 */
	public get onDispose(): Event<void> {
		return this._onDispose.event;
	}

B
Benjamin Pasero 已提交
140 141 142 143 144 145 146
	/**
	 * Returns the associated resource of this input if any.
	 */
	public getResource(): URI {
		return null;
	}

E
Erich Gamma 已提交
147 148 149 150 151 152 153 154 155 156 157 158
	/**
	 * Returns the name of this input that can be shown to the user. Examples include showing the name of the input
	 * above the editor area when the input is shown.
	 */
	public getName(): string {
		return null;
	}

	/**
	 * Returns the description of this input that can be shown to the user. Examples include showing the description of
	 * the input above the editor area to the side of the name of the input.
	 */
159
	public getDescription(verbosity?: Verbosity): string {
E
Erich Gamma 已提交
160 161 162
		return null;
	}

163 164 165 166
	public getTitle(verbosity?: Verbosity): string {
		return this.getName();
	}

167 168 169 170 171
	/**
	 * Returns the unique type identifier of this input.
	 */
	public abstract getTypeId(): string;

E
Erich Gamma 已提交
172 173 174 175 176 177 178 179 180 181 182 183
	/**
	 * Returns the preferred editor for this input. A list of candidate editors is passed in that whee registered
	 * for the input. This allows subclasses to decide late which editor to use for the input on a case by case basis.
	 */
	public getPreferredEditorId(candidates: string[]): string {
		if (candidates && candidates.length > 0) {
			return candidates[0];
		}

		return null;
	}

184 185 186 187 188
	/**
	 * Returns a descriptor suitable for telemetry events or null if none is available.
	 *
	 * Subclasses should extend if they can contribute.
	 */
B
Benjamin Pasero 已提交
189
	public getTelemetryDescriptor(): object {
K
kieferrm 已提交
190
		/* __GDPR__FRAGMENT__
K
kieferrm 已提交
191 192 193 194
			"EditorTelemetryDescriptor" : {
				"typeId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
			}
		*/
195 196 197
		return { typeId: this.getTypeId() };
	}

E
Erich Gamma 已提交
198 199 200 201 202 203
	/**
	 * Returns a type of EditorModel that represents the resolved input. Subclasses should
	 * override to provide a meaningful model. The optional second argument allows to specify
	 * if the EditorModel should be refreshed before returning it. Depending on the implementation
	 * this could mean to refresh the editor model contents with the version from disk.
	 */
204
	public abstract resolve(refresh?: boolean): TPromise<IEditorModel>;
E
Erich Gamma 已提交
205

206 207 208 209 210 211 212 213 214 215
	/**
	 * An editor that is dirty will be asked to be saved once it closes.
	 */
	public isDirty(): boolean {
		return false;
	}

	/**
	 * Subclasses should bring up a proper dialog for the user if the editor is dirty and return the result.
	 */
216 217
	public confirmSave(): TPromise<ConfirmResult> {
		return TPromise.wrap(ConfirmResult.DONT_SAVE);
218 219 220 221 222 223 224 225 226 227 228 229
	}

	/**
	 * Saves the editor if it is dirty. Subclasses return a promise with a boolean indicating the success of the operation.
	 */
	public save(): TPromise<boolean> {
		return TPromise.as(true);
	}

	/**
	 * Reverts the editor if it is dirty. Subclasses return a promise with a boolean indicating the success of the operation.
	 */
230
	public revert(options?: IRevertOptions): TPromise<boolean> {
231 232 233 234
		return TPromise.as(true);
	}

	/**
235
	 * Called when this input is no longer opened in any editor. Subclasses can free resources as needed.
236 237
	 */
	public close(): void {
238 239 240
		this.dispose();
	}

241 242 243 244 245 246 247
	/**
	 * Subclasses can set this to false if it does not make sense to split the editor input.
	 */
	public supportsSplitEditor(): boolean {
		return true;
	}

248 249 250 251 252
	/**
	 * Returns true if this input is identical to the otherInput.
	 */
	public matches(otherInput: any): boolean {
		return this === otherInput;
253 254
	}

E
Erich Gamma 已提交
255 256 257 258 259 260
	/**
	 * Called when an editor input is no longer needed. Allows to free up any resources taken by
	 * resolving the editor input.
	 */
	public dispose(): void {
		this.disposed = true;
261
		this._onDispose.fire();
E
Erich Gamma 已提交
262

263
		this._onDidChangeDirty.dispose();
B
Benjamin Pasero 已提交
264
		this._onDidChangeLabel.dispose();
265
		this._onDispose.dispose();
E
Erich Gamma 已提交
266 267 268
	}

	/**
P
Pascal Borreli 已提交
269
	 * Returns whether this input was disposed or not.
E
Erich Gamma 已提交
270 271 272 273 274 275
	 */
	public isDisposed(): boolean {
		return this.disposed;
	}
}

276 277
export interface IEditorOpeningEvent {
	input: IEditorInput;
278
	options?: IEditorOptions;
279 280 281 282 283 284 285 286 287 288 289 290
	position: Position;

	/**
	 * Allows to prevent the opening of an editor by providing a callback
	 * that will be executed instead. By returning another editor promise
	 * it is possible to override the opening with another editor. It is ok
	 * to return a promise that resolves to NULL to prevent the opening
	 * altogether.
	 */
	prevent(callback: () => TPromise<IBaseEditor>): void;
}

291
export class EditorOpeningEvent implements IEditorOpeningEvent {
292 293
	private override: () => TPromise<IBaseEditor>;

294
	constructor(private _input: IEditorInput, private _options: IEditorOptions, private _position: Position) {
295 296 297
	}

	public get input(): IEditorInput {
298 299 300 301 302
		return this._input;
	}

	public get options(): IEditorOptions {
		return this._options;
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
	}

	public get position(): Position {
		return this._position;
	}

	public prevent(callback: () => TPromise<IBaseEditor>): void {
		this.override = callback;
	}

	public isPrevented(): () => TPromise<IBaseEditor> {
		return this.override;
	}
}

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
export enum EncodingMode {

	/**
	 * Instructs the encoding support to encode the current input with the provided encoding
	 */
	Encode,

	/**
	 * Instructs the encoding support to decode the current input with the provided encoding
	 */
	Decode
}

export interface IEncodingSupport {

	/**
	 * Gets the encoding of the input if known.
	 */
	getEncoding(): string;

	/**
	 * Sets the encoding for the input for saving.
	 */
	setEncoding(encoding: string, mode: EncodingMode): void;
}

/**
 * This is a tagging interface to declare an editor input being capable of dealing with files. It is only used in the editor registry
 * to register this kind of input to the platform.
 */
348
export interface IFileEditorInput extends IEditorInput, IEncodingSupport {
349

350 351 352 353
	/**
	 * Sets the preferred encodingt to use for this input.
	 */
	setPreferredEncoding(encoding: string): void;
354 355 356 357 358

	/**
	 * Forces this file input to open as binary instead of text.
	 */
	setForceOpenAsBinary(): void;
359 360
}

E
Erich Gamma 已提交
361
/**
S
Sandeep Somavarapu 已提交
362
 * Side by side editor inputs that have a master and details side.
E
Erich Gamma 已提交
363
 */
S
Sandeep Somavarapu 已提交
364
export class SideBySideEditorInput extends EditorInput {
E
Erich Gamma 已提交
365

M
Matt Bierner 已提交
366
	public static readonly ID: string = 'workbench.editorinputs.sidebysideEditorInput';
367

S
Sandeep Somavarapu 已提交
368
	private _toUnbind: IDisposable[];
E
Erich Gamma 已提交
369

S
Sandeep Somavarapu 已提交
370
	constructor(private name: string, private description: string, private _details: EditorInput, private _master: EditorInput) {
S
Sandeep Somavarapu 已提交
371 372 373
		super();
		this._toUnbind = [];
		this.registerListeners();
E
Erich Gamma 已提交
374 375
	}

S
Sandeep Somavarapu 已提交
376 377
	get master(): EditorInput {
		return this._master;
E
Erich Gamma 已提交
378 379
	}

S
Sandeep Somavarapu 已提交
380 381
	get details(): EditorInput {
		return this._details;
E
Erich Gamma 已提交
382 383
	}

384
	public isDirty(): boolean {
S
Sandeep Somavarapu 已提交
385
		return this.master.isDirty();
386 387
	}

388
	public confirmSave(): TPromise<ConfirmResult> {
S
Sandeep Somavarapu 已提交
389
		return this.master.confirmSave();
390 391 392
	}

	public save(): TPromise<boolean> {
S
Sandeep Somavarapu 已提交
393
		return this.master.save();
394 395 396
	}

	public revert(): TPromise<boolean> {
S
Sandeep Somavarapu 已提交
397
		return this.master.revert();
398
	}
399

B
Benjamin Pasero 已提交
400
	public getTelemetryDescriptor(): object {
S
Sandeep Somavarapu 已提交
401
		const descriptor = this.master.getTelemetryDescriptor();
402 403
		return objects.assign(descriptor, super.getTelemetryDescriptor());
	}
S
Sandeep Somavarapu 已提交
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435

	private registerListeners(): void {

		// When the details or master input gets disposed, dispose this diff editor input
		const onceDetailsDisposed = once(this.details.onDispose);
		this._toUnbind.push(onceDetailsDisposed(() => {
			if (!this.isDisposed()) {
				this.dispose();
			}
		}));

		const onceMasterDisposed = once(this.master.onDispose);
		this._toUnbind.push(onceMasterDisposed(() => {
			if (!this.isDisposed()) {
				this.dispose();
			}
		}));

		// Reemit some events from the master side to the outside
		this._toUnbind.push(this.master.onDidChangeDirty(() => this._onDidChangeDirty.fire()));
		this._toUnbind.push(this.master.onDidChangeLabel(() => this._onDidChangeLabel.fire()));
	}

	public get toUnbind() {
		return this._toUnbind;
	}

	public resolve(refresh?: boolean): TPromise<EditorModel> {
		return TPromise.as(null);
	}

	getTypeId(): string {
436
		return SideBySideEditorInput.ID;
S
Sandeep Somavarapu 已提交
437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471
	}

	public getName(): string {
		return this.name;
	}

	public getDescription(): string {
		return this.description;
	}

	public supportsSplitEditor(): boolean {
		return false;
	}

	public matches(otherInput: any): boolean {
		if (super.matches(otherInput) === true) {
			return true;
		}

		if (otherInput) {
			if (!(otherInput instanceof SideBySideEditorInput)) {
				return false;
			}

			const otherDiffInput = <SideBySideEditorInput>otherInput;
			return this.details.matches(otherDiffInput.details) && this.master.matches(otherDiffInput.master);
		}

		return false;
	}

	public dispose(): void {
		this._toUnbind = dispose(this._toUnbind);
		super.dispose();
	}
E
Erich Gamma 已提交
472 473
}

474
export interface ITextEditorModel extends IEditorModel {
A
Alex Dima 已提交
475
	textEditorModel: ITextModel;
476 477
}

E
Erich Gamma 已提交
478 479 480 481 482
/**
 * The editor model is the heavyweight counterpart of editor input. Depending on the editor input, it
 * connects to the disk to retrieve content and may allow for saving it back or reverting it. Editor models
 * are typically cached for some while because they are expensive to construct.
 */
S
Sandeep Somavarapu 已提交
483
export class EditorModel extends Disposable implements IEditorModel {
M
Matt Bierner 已提交
484
	private readonly _onDispose: Emitter<void>;
485 486

	constructor() {
S
Sandeep Somavarapu 已提交
487
		super();
488 489 490 491 492 493 494 495 496
		this._onDispose = new Emitter<void>();
	}

	/**
	 * Fired when the model gets disposed.
	 */
	public get onDispose(): Event<void> {
		return this._onDispose.event;
	}
E
Erich Gamma 已提交
497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515

	/**
	 * Causes this model to load returning a promise when loading is completed.
	 */
	public load(): TPromise<EditorModel> {
		return TPromise.as(this);
	}

	/**
	 * Returns whether this model was loaded or not.
	 */
	public isResolved(): boolean {
		return true;
	}

	/**
	 * Subclasses should implement to free resources that have been claimed through loading.
	 */
	public dispose(): void {
516 517
		this._onDispose.fire();
		this._onDispose.dispose();
S
Sandeep Somavarapu 已提交
518
		super.dispose();
E
Erich Gamma 已提交
519 520 521 522 523 524 525 526 527 528 529
	}
}

/**
 * The editor options is the base class of options that can be passed in when opening an editor.
 */
export class EditorOptions implements IEditorOptions {

	/**
	 * Helper to create EditorOptions inline.
	 */
530
	public static create(settings: IEditorOptions): EditorOptions {
531
		const options = new EditorOptions();
532

E
Erich Gamma 已提交
533 534
		options.preserveFocus = settings.preserveFocus;
		options.forceOpen = settings.forceOpen;
535
		options.revealIfVisible = settings.revealIfVisible;
536
		options.revealIfOpened = settings.revealIfOpened;
B
Benjamin Pasero 已提交
537 538
		options.pinned = settings.pinned;
		options.index = settings.index;
539
		options.inactive = settings.inactive;
E
Erich Gamma 已提交
540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556

		return options;
	}

	/**
	 * Tells the editor to not receive keyboard focus when the editor is being opened. By default,
	 * the editor will receive keyboard focus on open.
	 */
	public preserveFocus: boolean;

	/**
	 * Tells the editor to replace the editor input in the editor even if it is identical to the one
	 * already showing. By default, the editor will not replace the input if it is identical to the
	 * one showing.
	 */
	public forceOpen: boolean;

557
	/**
558
	 * Will reveal the editor if it is already opened and visible in any of the opened editor groups.
559
	 */
560
	public revealIfVisible: boolean;
561

562 563 564 565 566
	/**
	 * Will reveal the editor if it is already opened (even when not visible) in any of the opened editor groups.
	 */
	public revealIfOpened: boolean;

B
Benjamin Pasero 已提交
567
	/**
B
Benjamin Pasero 已提交
568 569
	 * An editor that is pinned remains in the editor stack even when another editor is being opened.
	 * An editor that is not pinned will always get replaced by another editor that is not pinned.
B
Benjamin Pasero 已提交
570 571
	 */
	public pinned: boolean;
B
Benjamin Pasero 已提交
572 573 574 575

	/**
	 * The index in the document stack where to insert the editor into when opening.
	 */
B
Benjamin Pasero 已提交
576
	public index: number;
577 578 579 580 581 582

	/**
	 * An active editor that is opened will show its contents directly. Set to true to open an editor
	 * in the background.
	 */
	public inactive: boolean;
E
Erich Gamma 已提交
583 584 585 586 587 588
}

/**
 * Base Text Editor Options.
 */
export class TextEditorOptions extends EditorOptions {
589 590 591 592
	private startLineNumber: number;
	private startColumn: number;
	private endLineNumber: number;
	private endColumn: number;
593

I
isidor 已提交
594
	private revealInCenterIfOutsideViewport: boolean;
E
Erich Gamma 已提交
595 596
	private editorViewState: IEditorViewState;

597
	public static from(input?: IBaseResourceInput): TextEditorOptions {
598 599 600 601
		if (!input || !input.options) {
			return null;
		}

602 603
		return TextEditorOptions.create(input.options);
	}
E
Erich Gamma 已提交
604

605 606 607 608 609
	/**
	 * Helper to convert options bag to real class
	 */
	public static create(options: ITextEditorOptions = Object.create(null)): TextEditorOptions {
		const textEditorOptions = new TextEditorOptions();
E
Erich Gamma 已提交
610

611 612 613
		if (options.selection) {
			const selection = options.selection;
			textEditorOptions.selection(selection.startLineNumber, selection.startColumn, selection.endLineNumber, selection.endColumn);
614
		}
E
Erich Gamma 已提交
615

616 617
		if (options.viewState) {
			textEditorOptions.editorViewState = options.viewState as IEditorViewState;
618
		}
619

620 621
		if (options.forceOpen) {
			textEditorOptions.forceOpen = true;
622
		}
623

624 625
		if (options.revealIfVisible) {
			textEditorOptions.revealIfVisible = true;
626
		}
627

628 629
		if (options.revealIfOpened) {
			textEditorOptions.revealIfOpened = true;
630
		}
631

632 633
		if (options.preserveFocus) {
			textEditorOptions.preserveFocus = true;
634
		}
635

636 637
		if (options.revealInCenterIfOutsideViewport) {
			textEditorOptions.revealInCenterIfOutsideViewport = true;
638
		}
I
isidor 已提交
639

640 641
		if (options.pinned) {
			textEditorOptions.pinned = true;
642
		}
B
Benjamin Pasero 已提交
643

644 645
		if (options.inactive) {
			textEditorOptions.inactive = true;
E
Erich Gamma 已提交
646 647
		}

648 649
		if (typeof options.index === 'number') {
			textEditorOptions.index = options.index;
E
Erich Gamma 已提交
650 651
		}

652
		return textEditorOptions;
E
Erich Gamma 已提交
653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674
	}

	/**
	 * Returns if this options object has objects defined for the editor.
	 */
	public hasOptionsDefined(): boolean {
		return !!this.editorViewState || (!types.isUndefinedOrNull(this.startLineNumber) && !types.isUndefinedOrNull(this.startColumn));
	}

	/**
	 * Tells the editor to set show the given selection when the editor is being opened.
	 */
	public selection(startLineNumber: number, startColumn: number, endLineNumber: number = startLineNumber, endColumn: number = startColumn): EditorOptions {
		this.startLineNumber = startLineNumber;
		this.startColumn = startColumn;
		this.endLineNumber = endLineNumber;
		this.endColumn = endColumn;

		return this;
	}

	/**
675
	 * Create a TextEditorOptions inline to be used when the editor is opening.
E
Erich Gamma 已提交
676
	 */
677 678
	public static fromEditor(editor: IEditor, settings?: IEditorOptions): TextEditorOptions {
		const options = TextEditorOptions.create(settings);
679 680

		// View state
681
		options.editorViewState = editor.saveViewState();
682

683
		return options;
E
Erich Gamma 已提交
684 685 686 687 688 689 690
	}

	/**
	 * Apply the view state or selection to the given editor.
	 *
	 * @return if something was applied
	 */
691
	public apply(editor: IEditor, scrollType: ScrollType): boolean {
692 693

		// View state
694
		return this.applyViewState(editor, scrollType);
695 696
	}

697
	private applyViewState(editor: IEditor, scrollType: ScrollType): boolean {
E
Erich Gamma 已提交
698 699 700 701
		let gotApplied = false;

		// First try viewstate
		if (this.editorViewState) {
702
			editor.restoreViewState(this.editorViewState);
E
Erich Gamma 已提交
703 704 705 706 707 708 709 710
			gotApplied = true;
		}

		// Otherwise check for selection
		else if (!types.isUndefinedOrNull(this.startLineNumber) && !types.isUndefinedOrNull(this.startColumn)) {

			// Select
			if (!types.isUndefinedOrNull(this.endLineNumber) && !types.isUndefinedOrNull(this.endColumn)) {
711
				const range = {
E
Erich Gamma 已提交
712 713 714 715 716
					startLineNumber: this.startLineNumber,
					startColumn: this.startColumn,
					endLineNumber: this.endLineNumber,
					endColumn: this.endColumn
				};
717
				editor.setSelection(range);
I
isidor 已提交
718
				if (this.revealInCenterIfOutsideViewport) {
719
					editor.revealRangeInCenterIfOutsideViewport(range, scrollType);
I
isidor 已提交
720
				} else {
721
					editor.revealRangeInCenter(range, scrollType);
I
isidor 已提交
722
				}
E
Erich Gamma 已提交
723 724 725 726
			}

			// Reveal
			else {
727
				const pos = {
E
Erich Gamma 已提交
728 729 730
					lineNumber: this.startLineNumber,
					column: this.startColumn
				};
731
				editor.setPosition(pos);
I
isidor 已提交
732
				if (this.revealInCenterIfOutsideViewport) {
733
					editor.revealPositionInCenterIfOutsideViewport(pos, scrollType);
I
isidor 已提交
734
				} else {
735
					editor.revealPositionInCenter(pos, scrollType);
I
isidor 已提交
736
				}
E
Erich Gamma 已提交
737 738 739 740 741 742 743 744 745
			}

			gotApplied = true;
		}

		return gotApplied;
	}
}

746 747 748 749 750 751
export interface IStacksModelChangeEvent {
	group: IEditorGroup;
	editor?: IEditorInput;
	structural?: boolean;
}

752 753
export interface IEditorStacksModel {

754
	onModelChanged: Event<IStacksModelChangeEvent>;
755

B
Benjamin Pasero 已提交
756 757
	onWillCloseEditor: Event<IEditorCloseEvent>;
	onEditorClosed: Event<IEditorCloseEvent>;
758 759 760

	groups: IEditorGroup[];
	activeGroup: IEditorGroup;
B
Benjamin Pasero 已提交
761
	isActive(group: IEditorGroup): boolean;
762 763 764 765 766 767

	getGroup(id: GroupIdentifier): IEditorGroup;

	positionOfGroup(group: IEditorGroup): Position;
	groupAt(position: Position): IEditorGroup;

768 769
	next(jumpGroups: boolean, cycleAtEnd?: boolean): IEditorIdentifier;
	previous(jumpGroups: boolean, cycleAtStart?: boolean): IEditorIdentifier;
770
	last(): IEditorIdentifier;
771 772 773 774 775 776 777 778 779 780 781 782 783 784 785

	isOpen(resource: URI): boolean;

	toString(): string;
}

export interface IEditorGroup {

	id: GroupIdentifier;
	label: string;
	count: number;
	activeEditor: IEditorInput;
	previewEditor: IEditorInput;

	getEditor(index: number): IEditorInput;
B
Benjamin Pasero 已提交
786
	getEditor(resource: URI): IEditorInput;
787 788
	indexOf(editor: IEditorInput): number;

789
	contains(editorOrResource: IEditorInput | URI): boolean;
790 791 792 793

	getEditors(mru?: boolean): IEditorInput[];
	isActive(editor: IEditorInput): boolean;
	isPreview(editor: IEditorInput): boolean;
794
	isPinned(index: number): boolean;
795 796 797 798
	isPinned(editor: IEditorInput): boolean;
}

export interface IEditorIdentifier {
799
	group: IEditorGroup; // TODO@grid this should be the group identifier instead
I
isidor 已提交
800
	editor: IEditorInput;
801 802
}

803 804 805 806 807 808 809 810 811
export function groupFromContext(context: GroupIdentifier, editorGroupService: INextEditorGroupsService): INextEditorGroup {
	let group: INextEditorGroup;
	if (typeof context === 'number') {
		group = editorGroupService.getGroup(context);
	}

	return group || editorGroupService.activeGroup;
}

B
Benjamin Pasero 已提交
812 813 814 815 816 817 818 819 820 821
/**
 * The editor commands context is used for editor commands (e.g. in the editor title)
 * and we must ensure that the context is serializable because it potentially travels
 * to the extension host!
 */
export interface IEditorCommandsContext {
	groupId: GroupIdentifier;
	editorIndex?: number;
}

B
Benjamin Pasero 已提交
822
export interface IEditorCloseEvent extends IEditorIdentifier {
823
	replaced: boolean;
824
	index: number;
825 826
}

827 828
export type GroupIdentifier = number;

829 830 831
export const EditorOpenPositioning = {
	LEFT: 'left',
	RIGHT: 'right',
832 833
	FIRST: 'first',
	LAST: 'last'
834 835
};

836 837
export const OPEN_POSITIONING_CONFIG = 'workbench.editor.openPositioning';

838 839
export interface IWorkbenchEditorConfiguration {
	workbench: {
840
		editor: IWorkbenchEditorPartConfiguration,
B
Benjamin Pasero 已提交
841
		iconTheme: string;
842
	};
S
Sandeep Somavarapu 已提交
843 844
}

845 846 847 848 849 850 851 852 853 854 855 856 857 858
export interface IWorkbenchEditorPartConfiguration {
	showTabs?: boolean;
	tabCloseButton?: 'left' | 'right' | 'off';
	tabSizing?: 'fit' | 'shrink';
	showIcons?: boolean;
	enablePreview?: boolean;
	enablePreviewFromQuickOpen?: boolean;
	closeOnFileDelete?: boolean;
	openPositioning?: 'left' | 'right' | 'first' | 'last';
	revealIfOpen?: boolean;
	swipeToNavigate?: boolean;
	labelFormat?: 'default' | 'short' | 'medium' | 'long';
}

S
Sandeep Somavarapu 已提交
859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875
export const ActiveEditorMovePositioning = {
	FIRST: 'first',
	LAST: 'last',
	LEFT: 'left',
	RIGHT: 'right',
	CENTER: 'center',
	POSITION: 'position',
};

export const ActiveEditorMovePositioningBy = {
	TAB: 'tab',
	GROUP: 'group'
};

export interface ActiveEditorMoveArguments {
	to?: string;
	by?: string;
876
	value?: number;
S
Sandeep Somavarapu 已提交
877 878
}

879
export const EditorCommands = {
880
	MoveActiveEditor: 'moveActiveEditor'
881 882 883 884
};

export interface IResourceOptions {
	supportSideBySide?: boolean;
885
	filter?: string | string[];
886 887 888 889 890 891 892 893 894 895 896 897
}

export function toResource(editor: IEditorInput, options?: IResourceOptions): URI {
	if (!editor) {
		return null;
	}

	// Check for side by side if we are asked to
	if (options && options.supportSideBySide && editor instanceof SideBySideEditorInput) {
		editor = editor.master;
	}

B
Benjamin Pasero 已提交
898
	const resource = editor.getResource();
899 900 901 902 903 904 905 906 907 908 909
	if (!options || !options.filter) {
		return resource; // return early if no filter is specified
	}

	if (!resource) {
		return null;
	}

	let includeFiles: boolean;
	let includeUntitled: boolean;
	if (Array.isArray(options.filter)) {
910 911
		includeFiles = (options.filter.indexOf(Schemas.file) >= 0);
		includeUntitled = (options.filter.indexOf(Schemas.untitled) >= 0);
912
	} else {
913 914
		includeFiles = (options.filter === Schemas.file);
		includeUntitled = (options.filter === Schemas.untitled);
915 916
	}

917
	if (includeFiles && resource.scheme === Schemas.file) {
918 919 920
		return resource;
	}

921
	if (includeUntitled && resource.scheme === Schemas.untitled) {
922 923 924 925 926 927
		return resource;
	}

	return null;
}

928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035
export interface IEditorViewStates<T> {
	[Position.ONE]?: T;
	[Position.TWO]?: T;
	[Position.THREE]?: T;
}

export class EditorViewStateMemento<T> {
	private cache: LRUCache<string, IEditorViewStates<T>>;

	constructor(private memento: object, private key: string, private limit: number = 10) { }

	public saveState(resource: URI, position: Position, state: T): void;
	public saveState(editor: EditorInput, position: Position, state: T): void;
	public saveState(resourceOrEditor: URI | EditorInput, position: Position, state: T): void {
		if (typeof position !== 'number') {
			return; // we need a position at least
		}

		const resource = this.doGetResource(resourceOrEditor);
		if (resource) {
			const cache = this.doLoad();

			let viewStates = cache.get(resource.toString());
			if (!viewStates) {
				viewStates = Object.create(null) as IEditorViewStates<T>;
				cache.set(resource.toString(), viewStates);
			}

			viewStates[position] = state;

			// Automatically clear when editor input gets disposed if any
			if (resourceOrEditor instanceof EditorInput) {
				once(resourceOrEditor.onDispose)(() => {
					this.clearState(resource);
				});
			}
		}
	}

	public loadState(resource: URI, position: Position): T;
	public loadState(editor: EditorInput, position: Position): T;
	public loadState(resourceOrEditor: URI | EditorInput, position: Position): T {
		if (typeof position !== 'number') {
			return void 0; // we need a position at least
		}

		const resource = this.doGetResource(resourceOrEditor);
		if (resource) {
			const cache = this.doLoad();

			const viewStates = cache.get(resource.toString());
			if (viewStates) {
				return viewStates[position];
			}
		}

		return void 0;
	}

	public clearState(resource: URI): void;
	public clearState(editor: EditorInput): void;
	public clearState(resourceOrEditor: URI | EditorInput): void {
		const resource = this.doGetResource(resourceOrEditor);
		if (resource) {
			const cache = this.doLoad();
			cache.delete(resource.toString());
		}
	}

	private doGetResource(resourceOrEditor: URI | EditorInput): URI {
		if (resourceOrEditor instanceof EditorInput) {
			return resourceOrEditor.getResource();
		}

		return resourceOrEditor;
	}

	private doLoad(): LRUCache<string, IEditorViewStates<T>> {
		if (!this.cache) {
			this.cache = new LRUCache<string, T>(this.limit);

			// Restore from serialized map state
			const rawViewState = this.memento[this.key];
			if (Array.isArray(rawViewState)) {
				this.cache.fromJSON(rawViewState);
			}

			// Migration from old object state
			else if (rawViewState) {
				const keys = Object.keys(rawViewState);
				keys.forEach((key, index) => {
					if (index < this.limit) {
						this.cache.set(key, rawViewState[key]);
					}
				});
			}
		}

		return this.cache;
	}

	public save(): void {
		const cache = this.doLoad();

		this.memento[this.key] = cache.toJSON();
	}
}

1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085
class EditorInputFactoryRegistry implements IEditorInputFactoryRegistry {
	private instantiationService: IInstantiationService;
	private fileInputFactory: IFileInputFactory;
	private editorInputFactoryConstructors: { [editorInputId: string]: IConstructorSignature0<IEditorInputFactory> } = Object.create(null);
	private editorInputFactoryInstances: { [editorInputId: string]: IEditorInputFactory } = Object.create(null);

	constructor() {
	}

	public setInstantiationService(service: IInstantiationService): void {
		this.instantiationService = service;

		for (let key in this.editorInputFactoryConstructors) {
			const element = this.editorInputFactoryConstructors[key];
			this.createEditorInputFactory(key, element);
		}

		this.editorInputFactoryConstructors = {};
	}

	private createEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0<IEditorInputFactory>): void {
		const instance = this.instantiationService.createInstance(ctor);
		this.editorInputFactoryInstances[editorInputId] = instance;
	}

	public registerFileInputFactory(factory: IFileInputFactory): void {
		this.fileInputFactory = factory;
	}

	public getFileInputFactory(): IFileInputFactory {
		return this.fileInputFactory;
	}

	public registerEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0<IEditorInputFactory>): void {
		if (!this.instantiationService) {
			this.editorInputFactoryConstructors[editorInputId] = ctor;
		} else {
			this.createEditorInputFactory(editorInputId, ctor);
		}
	}

	public getEditorInputFactory(editorInputId: string): IEditorInputFactory {
		return this.editorInputFactoryInstances[editorInputId];
	}
}

export const Extensions = {
	EditorInputFactories: 'workbench.contributions.editor.inputFactories'
};

I
isidor 已提交
1086
Registry.add(Extensions.EditorInputFactories, new EditorInputFactoryRegistry());