fileEditorInput.ts 9.2 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';

7
import { localize } from 'vs/nls';
J
Johannes Rieken 已提交
8
import { TPromise } from 'vs/base/common/winjs.base';
E
Erich Gamma 已提交
9 10 11
import paths = require('vs/base/common/paths');
import labels = require('vs/base/common/labels');
import URI from 'vs/base/common/uri';
12
import { EncodingMode, ConfirmResult, EditorInput, IFileEditorInput } from 'vs/workbench/common/editor';
13
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
J
Johannes Rieken 已提交
14
import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
15
import { IFileOperationResult, FileOperationResult } from 'vs/platform/files/common/files';
16
import { BINARY_FILE_EDITOR_ID, TEXT_FILE_EDITOR_ID, FILE_EDITOR_INPUT_ID } from 'vs/workbench/parts/files/common/files';
17
import { ITextFileService, AutoSaveMode, ModelState, TextFileModelChangeEvent } from 'vs/workbench/services/textfile/common/textfiles';
J
Johannes Rieken 已提交
18 19
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
20
import { IDisposable, dispose, IReference } from 'vs/base/common/lifecycle';
21
import { telemetryURIDescriptor } from 'vs/platform/telemetry/common/telemetryUtils';
22 23
import { Verbosity } from 'vs/platform/editor/common/editor';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
24
import { ITextModelResolverService } from "vs/editor/common/services/resolverService";
E
Erich Gamma 已提交
25 26 27 28

/**
 * A file editor input is the input type for the file editor of file system resources.
 */
29
export class FileEditorInput extends EditorInput implements IFileEditorInput {
E
Erich Gamma 已提交
30 31
	private resource: URI;
	private preferredEncoding: string;
32
	private forceOpenAsBinary: boolean;
E
Erich Gamma 已提交
33

34 35
	private textModelReference: TPromise<IReference<TextFileEditorModel>>;

E
Erich Gamma 已提交
36 37
	private name: string;
	private description: string;
38 39 40 41

	private shortTitle: string;
	private mediumTitle: string;
	private longTitle: string;
E
Erich Gamma 已提交
42

43 44
	private toUnbind: IDisposable[];

E
Erich Gamma 已提交
45
	/**
46
	 * An editor input who's contents are retrieved from file services.
E
Erich Gamma 已提交
47 48 49 50 51
	 */
	constructor(
		resource: URI,
		preferredEncoding: string,
		@IInstantiationService private instantiationService: IInstantiationService,
52
		@IWorkspaceContextService private contextService: IWorkspaceContextService,
53
		@ITextFileService private textFileService: ITextFileService,
54 55
		@IEnvironmentService private environmentService: IEnvironmentService,
		@ITextModelResolverService private textModelResolverService: ITextModelResolverService
E
Erich Gamma 已提交
56 57 58
	) {
		super();

59 60
		this.toUnbind = [];

61 62
		this.resource = resource;
		this.preferredEncoding = preferredEncoding;
63 64 65 66 67

		this.registerListeners();
	}

	private registerListeners(): void {
68 69

		// Model changes
70 71 72 73
		this.toUnbind.push(this.textFileService.models.onModelDirty(e => this.onDirtyStateChange(e)));
		this.toUnbind.push(this.textFileService.models.onModelSaveError(e => this.onDirtyStateChange(e)));
		this.toUnbind.push(this.textFileService.models.onModelSaved(e => this.onDirtyStateChange(e)));
		this.toUnbind.push(this.textFileService.models.onModelReverted(e => this.onDirtyStateChange(e)));
74
		this.toUnbind.push(this.textFileService.models.onModelOrphanedChanged(e => this.onModelOrphanedChanged(e)));
75 76
	}

77
	private onDirtyStateChange(e: TextFileModelChangeEvent): void {
78
		if (e.resource.toString() === this.resource.toString()) {
79 80
			this._onDidChangeDirty.fire();
		}
E
Erich Gamma 已提交
81 82
	}

83
	private onModelOrphanedChanged(e: TextFileModelChangeEvent): void {
84
		if (e.resource.toString() === this.resource.toString()) {
85 86 87 88
			this._onDidChangeLabel.fire();
		}
	}

E
Erich Gamma 已提交
89 90 91 92
	public getResource(): URI {
		return this.resource;
	}

93 94 95 96
	public setPreferredEncoding(encoding: string): void {
		this.preferredEncoding = encoding;
	}

E
Erich Gamma 已提交
97
	public getEncoding(): string {
B
Benjamin Pasero 已提交
98
		const textModel = this.textFileService.models.get(this.resource);
E
Erich Gamma 已提交
99 100 101 102 103 104 105
		if (textModel) {
			return textModel.getEncoding();
		}

		return this.preferredEncoding;
	}

106 107 108 109
	public getPreferredEncoding(): string {
		return this.preferredEncoding;
	}

E
Erich Gamma 已提交
110 111 112
	public setEncoding(encoding: string, mode: EncodingMode): void {
		this.preferredEncoding = encoding;

B
Benjamin Pasero 已提交
113
		const textModel = this.textFileService.models.get(this.resource);
E
Erich Gamma 已提交
114 115 116 117 118
		if (textModel) {
			textModel.setEncoding(encoding, mode);
		}
	}

119 120 121 122
	public setForceOpenAsBinary(): void {
		this.forceOpenAsBinary = true;
	}

123
	public getTypeId(): string {
E
Erich Gamma 已提交
124 125 126 127 128 129 130 131
		return FILE_EDITOR_INPUT_ID;
	}

	public getName(): string {
		if (!this.name) {
			this.name = paths.basename(this.resource.fsPath);
		}

132
		return this.decorateOrphanedFiles(this.name);
E
Erich Gamma 已提交
133 134
	}

135 136
	public getDescription(): string {
		if (!this.description) {
137
			this.description = labels.getPathLabel(paths.dirname(this.resource.fsPath), this.contextService, this.environmentService);
E
Erich Gamma 已提交
138 139
		}

140 141
		return this.description;
	}
E
Erich Gamma 已提交
142

143
	public getTitle(verbosity: Verbosity): string {
144
		let title: string;
145 146
		switch (verbosity) {
			case Verbosity.SHORT:
147 148
				title = this.shortTitle ? this.shortTitle : (this.shortTitle = this.getName());
				break;
149
			case Verbosity.MEDIUM:
150
				title = this.mediumTitle ? this.mediumTitle : (this.mediumTitle = labels.getPathLabel(this.resource, this.contextService, this.environmentService));
151
				break;
152
			case Verbosity.LONG:
153
				title = this.longTitle ? this.longTitle : (this.longTitle = labels.getPathLabel(this.resource, void 0, this.environmentService));
154 155 156 157 158 159 160 161 162 163
				break;
		}

		return this.decorateOrphanedFiles(title);
	}

	private decorateOrphanedFiles(label: string): string {
		const model = this.textFileService.models.get(this.resource);
		if (model && model.hasState(ModelState.ORPHAN)) {
			return localize('orphanedFile', "{0} (deleted from disk)", label);
164
		}
165 166

		return label;
E
Erich Gamma 已提交
167 168
	}

169
	public isDirty(): boolean {
170
		const model = this.textFileService.models.get(this.resource);
B
Benjamin Pasero 已提交
171 172 173 174
		if (!model) {
			return false;
		}

175 176
		if (model.hasState(ModelState.CONFLICT) || model.hasState(ModelState.ERROR)) {
			return true; // always indicate dirty state if we are in conflict or error state
B
Benjamin Pasero 已提交
177 178
		}

B
Benjamin Pasero 已提交
179
		if (this.textFileService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) {
B
Benjamin Pasero 已提交
180
			return false; // fast auto save enabled so we do not declare dirty
B
Benjamin Pasero 已提交
181 182
		}

B
Benjamin Pasero 已提交
183
		return model.isDirty();
184 185 186 187 188 189 190 191 192 193 194 195 196 197
	}

	public confirmSave(): ConfirmResult {
		return this.textFileService.confirmSave([this.resource]);
	}

	public save(): TPromise<boolean> {
		return this.textFileService.save(this.resource);
	}

	public revert(): TPromise<boolean> {
		return this.textFileService.revert(this.resource);
	}

E
Erich Gamma 已提交
198
	public getPreferredEditorId(candidates: string[]): string {
199
		return this.forceOpenAsBinary ? BINARY_FILE_EDITOR_ID : TEXT_FILE_EDITOR_ID;
E
Erich Gamma 已提交
200 201
	}

202 203 204 205 206 207 208 209
	public resolve(refresh?: boolean): TPromise<TextFileEditorModel | BinaryEditorModel> {

		// Resolve as binary
		if (this.forceOpenAsBinary) {
			return this.resolveAsBinary();
		}

		// Resolve as text
210 211 212 213 214 215 216 217 218 219 220 221
		return this.textFileService.models.loadOrCreate(this.resource, { encoding: this.preferredEncoding, reload: refresh }).then(model => {

			// TODO@Ben this is a bit ugly, because we first resolve the model and then resolve a model reference. the reason being that binary
			// or very large files do not resolve to a text file model but should be opened as binary files without text. First calling into
			// loadOrCreate ensures we are not creating model references for these kind of resources.
			// In addition we have a bit of payload to take into account (encoding, reload) that the text resolver does not handle yet.
			if (!this.textModelReference) {
				this.textModelReference = this.textModelResolverService.createModelReference(this.resource);
			}

			return this.textModelReference.then(ref => ref.object);
		}, error => {
E
Erich Gamma 已提交
222

223 224
			// In case of an error that indicates that the file is binary or too large, just return with the binary editor model
			if ((<IFileOperationResult>error).fileOperationResult === FileOperationResult.FILE_IS_BINARY || (<IFileOperationResult>error).fileOperationResult === FileOperationResult.FILE_TOO_LARGE) {
225
				return this.resolveAsBinary();
E
Erich Gamma 已提交
226 227
			}

228
			// Bubble any other error up
229
			return TPromise.wrapError(error);
E
Erich Gamma 已提交
230 231 232
		});
	}

233
	private resolveAsBinary(): TPromise<BinaryEditorModel> {
234 235 236
		return this.instantiationService.createInstance(BinaryEditorModel, this.resource, this.getName())
			.load()
			.then(x => x as BinaryEditorModel);
237 238
	}

239 240 241 242
	public isResolved(): boolean {
		return !!this.textFileService.models.get(this.resource);
	}

B
Benjamin Pasero 已提交
243
	public getTelemetryDescriptor(): object {
244 245 246 247 248 249
		const descriptor = super.getTelemetryDescriptor();
		descriptor['resource'] = telemetryURIDescriptor(this.getResource());

		return descriptor;
	}

250
	public dispose(): void {
E
Erich Gamma 已提交
251

252 253 254 255 256 257
		// Model reference
		if (this.textModelReference) {
			this.textModelReference.done(ref => ref.dispose());
			this.textModelReference = null;
		}

258
		// Listeners
259
		this.toUnbind = dispose(this.toUnbind);
260

E
Erich Gamma 已提交
261 262 263 264 265 266 267 268 269
		super.dispose();
	}

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

		if (otherInput) {
270
			return otherInput instanceof FileEditorInput && otherInput.resource.toString() === this.resource.toString();
E
Erich Gamma 已提交
271 272 273 274 275
		}

		return false;
	}
}