editor.ts 29.7 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';
B
Benjamin Pasero 已提交
14
import { IEditorInput, IEditorModel, IEditorOptions, ITextEditorOptions, IBaseResourceInput, Position, Verbosity, 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
export interface IEditorOpeningEvent {
B
Benjamin Pasero 已提交
277
	editor: IEditorInput;
278
	options?: IEditorOptions;
B
Benjamin Pasero 已提交
279
	group: INextEditorGroup;
280 281 282 283 284 285 286 287

	/**
	 * 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.
	 */
B
Benjamin Pasero 已提交
288
	prevent(callback: () => Thenable<any>): void;
289 290
}

291
export class EditorOpeningEvent implements IEditorOpeningEvent {
B
Benjamin Pasero 已提交
292
	private override: () => Thenable<any>;
293

B
Benjamin Pasero 已提交
294
	constructor(private _group: INextEditorGroup, private _editor: IEditorInput, private _options: IEditorOptions) {
295 296
	}

B
Benjamin Pasero 已提交
297 298
	public get group(): INextEditorGroup {
		return this._group;
299 300
	}

B
Benjamin Pasero 已提交
301 302
	public get editor(): IEditorInput {
		return this._editor;
303 304
	}

B
Benjamin Pasero 已提交
305 306
	public get options(): IEditorOptions {
		return this._options;
307 308
	}

B
Benjamin Pasero 已提交
309
	public prevent(callback: () => Thenable<any>): void {
310 311 312
		this.override = callback;
	}

B
Benjamin Pasero 已提交
313
	public isPrevented(): () => Thenable<any> {
314 315 316 317
		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
	}
}

B
Benjamin Pasero 已提交
522 523 524 525 526 527 528 529 530 531 532
export interface IEditorInputWithOptions {
	editor: IEditorInput;
	options?: IEditorOptions;
}

export function isEditorInputWithOptions(obj: any): obj is IEditorInputWithOptions {
	const editorInputWithOptions = obj as IEditorInputWithOptions;

	return !!editorInputWithOptions && !!editorInputWithOptions.editor;
}

E
Erich Gamma 已提交
533 534 535 536 537 538 539 540
/**
 * 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.
	 */
541
	public static create(settings: IEditorOptions): EditorOptions {
542
		const options = new EditorOptions();
543

E
Erich Gamma 已提交
544 545
		options.preserveFocus = settings.preserveFocus;
		options.forceOpen = settings.forceOpen;
546
		options.revealIfVisible = settings.revealIfVisible;
547
		options.revealIfOpened = settings.revealIfOpened;
B
Benjamin Pasero 已提交
548 549
		options.pinned = settings.pinned;
		options.index = settings.index;
550
		options.inactive = settings.inactive;
E
Erich Gamma 已提交
551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567

		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;

568
	/**
569
	 * Will reveal the editor if it is already opened and visible in any of the opened editor groups.
570
	 */
571
	public revealIfVisible: boolean;
572

573 574 575 576 577
	/**
	 * 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 已提交
578
	/**
B
Benjamin Pasero 已提交
579 580
	 * 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 已提交
581 582
	 */
	public pinned: boolean;
B
Benjamin Pasero 已提交
583 584 585 586

	/**
	 * The index in the document stack where to insert the editor into when opening.
	 */
B
Benjamin Pasero 已提交
587
	public index: number;
588 589 590 591 592 593

	/**
	 * 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 已提交
594 595 596 597 598 599
}

/**
 * Base Text Editor Options.
 */
export class TextEditorOptions extends EditorOptions {
600 601 602 603
	private startLineNumber: number;
	private startColumn: number;
	private endLineNumber: number;
	private endColumn: number;
604

I
isidor 已提交
605
	private revealInCenterIfOutsideViewport: boolean;
E
Erich Gamma 已提交
606 607
	private editorViewState: IEditorViewState;

608
	public static from(input?: IBaseResourceInput): TextEditorOptions {
609 610 611 612
		if (!input || !input.options) {
			return null;
		}

613 614
		return TextEditorOptions.create(input.options);
	}
E
Erich Gamma 已提交
615

616 617 618 619 620
	/**
	 * Helper to convert options bag to real class
	 */
	public static create(options: ITextEditorOptions = Object.create(null)): TextEditorOptions {
		const textEditorOptions = new TextEditorOptions();
E
Erich Gamma 已提交
621

622 623 624
		if (options.selection) {
			const selection = options.selection;
			textEditorOptions.selection(selection.startLineNumber, selection.startColumn, selection.endLineNumber, selection.endColumn);
625
		}
E
Erich Gamma 已提交
626

627 628
		if (options.viewState) {
			textEditorOptions.editorViewState = options.viewState as IEditorViewState;
629
		}
630

631 632
		if (options.forceOpen) {
			textEditorOptions.forceOpen = true;
633
		}
634

635 636
		if (options.revealIfVisible) {
			textEditorOptions.revealIfVisible = true;
637
		}
638

639 640
		if (options.revealIfOpened) {
			textEditorOptions.revealIfOpened = true;
641
		}
642

643 644
		if (options.preserveFocus) {
			textEditorOptions.preserveFocus = true;
645
		}
646

647 648
		if (options.revealInCenterIfOutsideViewport) {
			textEditorOptions.revealInCenterIfOutsideViewport = true;
649
		}
I
isidor 已提交
650

651 652
		if (options.pinned) {
			textEditorOptions.pinned = true;
653
		}
B
Benjamin Pasero 已提交
654

655 656
		if (options.inactive) {
			textEditorOptions.inactive = true;
E
Erich Gamma 已提交
657 658
		}

659 660
		if (typeof options.index === 'number') {
			textEditorOptions.index = options.index;
E
Erich Gamma 已提交
661 662
		}

663
		return textEditorOptions;
E
Erich Gamma 已提交
664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685
	}

	/**
	 * 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;
	}

	/**
686
	 * Create a TextEditorOptions inline to be used when the editor is opening.
E
Erich Gamma 已提交
687
	 */
688 689
	public static fromEditor(editor: IEditor, settings?: IEditorOptions): TextEditorOptions {
		const options = TextEditorOptions.create(settings);
690 691

		// View state
692
		options.editorViewState = editor.saveViewState();
693

694
		return options;
E
Erich Gamma 已提交
695 696 697 698 699 700 701
	}

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

		// View state
705
		return this.applyViewState(editor, scrollType);
706 707
	}

708
	private applyViewState(editor: IEditor, scrollType: ScrollType): boolean {
E
Erich Gamma 已提交
709 710 711 712
		let gotApplied = false;

		// First try viewstate
		if (this.editorViewState) {
713
			editor.restoreViewState(this.editorViewState);
E
Erich Gamma 已提交
714 715 716 717 718 719 720 721
			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)) {
722
				const range = {
E
Erich Gamma 已提交
723 724 725 726 727
					startLineNumber: this.startLineNumber,
					startColumn: this.startColumn,
					endLineNumber: this.endLineNumber,
					endColumn: this.endColumn
				};
728
				editor.setSelection(range);
I
isidor 已提交
729
				if (this.revealInCenterIfOutsideViewport) {
730
					editor.revealRangeInCenterIfOutsideViewport(range, scrollType);
I
isidor 已提交
731
				} else {
732
					editor.revealRangeInCenter(range, scrollType);
I
isidor 已提交
733
				}
E
Erich Gamma 已提交
734 735 736 737
			}

			// Reveal
			else {
738
				const pos = {
E
Erich Gamma 已提交
739 740 741
					lineNumber: this.startLineNumber,
					column: this.startColumn
				};
742
				editor.setPosition(pos);
I
isidor 已提交
743
				if (this.revealInCenterIfOutsideViewport) {
744
					editor.revealPositionInCenterIfOutsideViewport(pos, scrollType);
I
isidor 已提交
745
				} else {
746
					editor.revealPositionInCenter(pos, scrollType);
I
isidor 已提交
747
				}
E
Erich Gamma 已提交
748 749 750 751 752 753 754 755 756
			}

			gotApplied = true;
		}

		return gotApplied;
	}
}

757 758 759 760 761 762
export interface IStacksModelChangeEvent {
	group: IEditorGroup;
	editor?: IEditorInput;
	structural?: boolean;
}

763 764
export interface IEditorStacksModel {

765
	onModelChanged: Event<IStacksModelChangeEvent>;
766

B
Benjamin Pasero 已提交
767 768
	onWillCloseEditor: Event<IEditorCloseEvent>;
	onEditorClosed: Event<IEditorCloseEvent>;
769 770 771

	groups: IEditorGroup[];
	activeGroup: IEditorGroup;
B
Benjamin Pasero 已提交
772
	isActive(group: IEditorGroup): boolean;
773 774 775 776 777 778

	getGroup(id: GroupIdentifier): IEditorGroup;

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

779 780
	next(jumpGroups: boolean, cycleAtEnd?: boolean): IEditorIdentifier;
	previous(jumpGroups: boolean, cycleAtStart?: boolean): IEditorIdentifier;
781
	last(): IEditorIdentifier;
782 783 784 785 786 787 788 789 790 791 792 793 794

	isOpen(resource: URI): boolean;

	toString(): string;
}

export interface IEditorGroup {
	id: GroupIdentifier;
	count: number;
	activeEditor: IEditorInput;
	previewEditor: IEditorInput;

	getEditor(index: number): IEditorInput;
B
Benjamin Pasero 已提交
795
	getEditor(resource: URI): IEditorInput;
796 797
	indexOf(editor: IEditorInput): number;

798
	contains(editorOrResource: IEditorInput | URI): boolean;
799 800 801 802

	getEditors(mru?: boolean): IEditorInput[];
	isActive(editor: IEditorInput): boolean;
	isPreview(editor: IEditorInput): boolean;
803
	isPinned(index: number): boolean;
804 805 806 807
	isPinned(editor: IEditorInput): boolean;
}

export interface IEditorIdentifier {
808
	group: IEditorGroup; // TODO@grid this should be the group identifier instead
I
isidor 已提交
809
	editor: IEditorInput;
810 811
}

812 813 814 815 816 817 818 819 820
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 已提交
821 822 823 824 825 826 827 828 829 830
/**
 * 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 已提交
831
export interface IEditorCloseEvent extends IEditorIdentifier {
832
	replaced: boolean;
833
	index: number;
834 835
}

836 837
export type GroupIdentifier = number;

838 839 840
export const EditorOpenPositioning = {
	LEFT: 'left',
	RIGHT: 'right',
841 842
	FIRST: 'first',
	LAST: 'last'
843 844
};

845 846
export const OPEN_POSITIONING_CONFIG = 'workbench.editor.openPositioning';

847 848
export interface IWorkbenchEditorConfiguration {
	workbench: {
849
		editor: IWorkbenchEditorPartConfiguration,
B
Benjamin Pasero 已提交
850
		iconTheme: string;
851
	};
S
Sandeep Somavarapu 已提交
852 853
}

854 855 856 857 858 859 860 861 862 863 864 865 866 867
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 已提交
868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884
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;
885
	value?: number;
S
Sandeep Somavarapu 已提交
886 887
}

888
export const EditorCommands = {
889
	MoveActiveEditor: 'moveActiveEditor'
890 891 892 893
};

export interface IResourceOptions {
	supportSideBySide?: boolean;
894
	filter?: string | string[];
895 896 897 898 899 900 901 902 903 904 905 906
}

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 已提交
907
	const resource = editor.getResource();
908 909 910 911 912 913 914 915 916 917 918
	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)) {
919 920
		includeFiles = (options.filter.indexOf(Schemas.file) >= 0);
		includeUntitled = (options.filter.indexOf(Schemas.untitled) >= 0);
921
	} else {
922 923
		includeFiles = (options.filter === Schemas.file);
		includeUntitled = (options.filter === Schemas.untitled);
924 925
	}

926
	if (includeFiles && resource.scheme === Schemas.file) {
927 928 929
		return resource;
	}

930
	if (includeUntitled && resource.scheme === Schemas.untitled) {
931 932 933 934 935 936
		return resource;
	}

	return null;
}

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 1036 1037 1038 1039 1040 1041 1042 1043 1044
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();
	}
}

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 1086 1087 1088 1089 1090 1091 1092 1093 1094
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 已提交
1095
Registry.add(Extensions.EditorInputFactories, new EditorInputFactoryRegistry());