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

7
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
J
Johannes Rieken 已提交
8
import { TPromise } from 'vs/base/common/winjs.base';
B
Benjamin Pasero 已提交
9
import { IEncodingSupport } from 'vs/workbench/common/editor';
10
import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel';
E
Erich Gamma 已提交
11
import URI from 'vs/base/common/uri';
J
Johannes Rieken 已提交
12
import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
13
import { CONTENT_CHANGE_EVENT_BUFFER_DELAY } from 'vs/platform/files/common/files';
J
Johannes Rieken 已提交
14 15 16 17
import { IModeService } from 'vs/editor/common/services/modeService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IMode } from 'vs/editor/common/modes';
import Event, { Emitter } from 'vs/base/common/event';
18
import { RunOnceScheduler } from 'vs/base/common/async';
19
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
20
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
21 22
import { ITextBufferFactory } from 'vs/editor/common/model';
import { createTextBufferFactory } from 'vs/editor/common/model/textModel';
E
Erich Gamma 已提交
23

24
export class UntitledEditorModel extends BaseTextEditorModel implements IEncodingSupport {
25

26
	public static DEFAULT_CONTENT_CHANGE_BUFFER_DELAY = CONTENT_CHANGE_EVENT_BUFFER_DELAY;
27

28
	private toDispose: IDisposable[];
E
Erich Gamma 已提交
29 30

	private dirty: boolean;
31
	private _onDidChangeContent: Emitter<void>;
32
	private _onDidChangeDirty: Emitter<void>;
33
	private _onDidChangeEncoding: Emitter<void>;
34

35 36
	private versionId: number;

37 38
	private contentChangeEventScheduler: RunOnceScheduler;

E
Erich Gamma 已提交
39
	private configuredEncoding: string;
40

E
Erich Gamma 已提交
41
	constructor(
42 43
		private modeId: string,
		private resource: URI,
44 45 46
		private hasAssociatedFilePath: boolean,
		private initialValue: string,
		private preferredEncoding: string,
E
Erich Gamma 已提交
47 48
		@IModeService modeService: IModeService,
		@IModelService modelService: IModelService,
49
		@IBackupFileService private backupFileService: IBackupFileService,
50
		@ITextResourceConfigurationService private configurationService: ITextResourceConfigurationService
E
Erich Gamma 已提交
51
	) {
52
		super(modelService, modeService);
E
Erich Gamma 已提交
53

54
		this.dirty = false;
55
		this.versionId = 0;
56
		this.toDispose = [];
E
Erich Gamma 已提交
57

58
		this._onDidChangeContent = new Emitter<void>();
59 60
		this.toDispose.push(this._onDidChangeContent);

61
		this._onDidChangeDirty = new Emitter<void>();
62 63
		this.toDispose.push(this._onDidChangeDirty);

64
		this._onDidChangeEncoding = new Emitter<void>();
65
		this.toDispose.push(this._onDidChangeEncoding);
66

67
		this.contentChangeEventScheduler = new RunOnceScheduler(() => this._onDidChangeContent.fire(), UntitledEditorModel.DEFAULT_CONTENT_CHANGE_BUFFER_DELAY);
68
		this.toDispose.push(this.contentChangeEventScheduler);
69

D
Daniel Imms 已提交
70
		this.registerListeners();
71 72
	}

73 74 75 76
	public get onDidChangeContent(): Event<void> {
		return this._onDidChangeContent.event;
	}

77 78 79 80
	public get onDidChangeDirty(): Event<void> {
		return this._onDidChangeDirty.event;
	}

81 82 83 84
	public get onDidChangeEncoding(): Event<void> {
		return this._onDidChangeEncoding.event;
	}

85 86 87
	protected getOrCreateMode(modeService: IModeService, modeId: string, firstLineText?: string): TPromise<IMode> {
		if (!modeId || modeId === PLAINTEXT_MODE_ID) {
			return modeService.getOrCreateModeByFilenameOrFirstLine(this.resource.fsPath, firstLineText); // lookup mode via resource path if the provided modeId is unspecific
88 89
		}

90
		return super.getOrCreateMode(modeService, modeId, firstLineText);
91 92
	}

E
Erich Gamma 已提交
93 94 95
	private registerListeners(): void {

		// Config Changes
96
		this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChange()));
E
Erich Gamma 已提交
97 98
	}

99
	private onConfigurationChange(): void {
100
		const configuredEncoding = this.configurationService.getValue<string>(this.resource, 'files.encoding');
101 102 103 104 105 106 107 108

		if (this.configuredEncoding !== configuredEncoding) {
			this.configuredEncoding = configuredEncoding;

			if (!this.preferredEncoding) {
				this._onDidChangeEncoding.fire(); // do not fire event if we have a preferred encoding set
			}
		}
E
Erich Gamma 已提交
109 110
	}

111 112 113 114
	public getVersionId(): number {
		return this.versionId;
	}

E
Erich Gamma 已提交
115 116
	public getModeId(): string {
		if (this.textEditorModel) {
A
Alex Dima 已提交
117
			return this.textEditorModel.getLanguageIdentifier().language;
E
Erich Gamma 已提交
118 119 120 121 122 123 124 125 126 127
		}

		return null;
	}

	public getEncoding(): string {
		return this.preferredEncoding || this.configuredEncoding;
	}

	public setEncoding(encoding: string): void {
128
		const oldEncoding = this.getEncoding();
E
Erich Gamma 已提交
129 130 131 132
		this.preferredEncoding = encoding;

		// Emit if it changed
		if (oldEncoding !== this.preferredEncoding) {
133
			this._onDidChangeEncoding.fire();
E
Erich Gamma 已提交
134 135 136 137 138 139 140
		}
	}

	public isDirty(): boolean {
		return this.dirty;
	}

141 142 143 144 145 146 147 148 149
	private setDirty(dirty: boolean): void {
		if (this.dirty === dirty) {
			return;
		}

		this.dirty = dirty;
		this._onDidChangeDirty.fire();
	}

D
Daniel Imms 已提交
150 151 152 153
	public getResource(): URI {
		return this.resource;
	}

154
	public revert(): void {
155
		this.setDirty(false);
156 157 158

		// Handle content change event buffered
		this.contentChangeEventScheduler.schedule();
159 160
	}

B
Benjamin Pasero 已提交
161
	public load(): TPromise<UntitledEditorModel> {
E
Erich Gamma 已提交
162

163
		// Check for backups first
B
Benjamin Pasero 已提交
164 165
		return this.backupFileService.loadBackupResource(this.resource).then(backupResource => {
			if (backupResource) {
166
				return this.backupFileService.resolveBackupContent(backupResource);
167 168 169
			}

			return null;
170 171
		}).then(backupTextBufferFactory => {
			const hasBackup = !!backupTextBufferFactory;
E
Erich Gamma 已提交
172

173
			// untitled associated to file path are dirty right away as well as untitled with content
174
			this.setDirty(this.hasAssociatedFilePath || hasBackup);
E
Erich Gamma 已提交
175

176 177 178 179 180 181 182 183
			let untitledContents: ITextBufferFactory;
			if (backupTextBufferFactory) {
				untitledContents = backupTextBufferFactory;
			} else {
				untitledContents = createTextBufferFactory(this.initialValue || '');
			}

			return this.doLoad(untitledContents).then(model => {
184
				// Encoding
185
				this.configuredEncoding = this.configurationService.getValue<string>(this.resource, 'files.encoding');
E
Erich Gamma 已提交
186

187
				// Listen to content changes
188 189 190
				this.toDispose.push(this.textEditorModel.onDidChangeContent(() => this.onModelContentChanged()));

				// Listen to mode changes
B
Benjamin Pasero 已提交
191
				this.toDispose.push(this.textEditorModel.onDidChangeLanguage(() => this.onConfigurationChange())); // mode change can have impact on config
192 193 194

				return model;
			});
E
Erich Gamma 已提交
195 196
		});
	}
197

198
	private doLoad(content: ITextBufferFactory): TPromise<UntitledEditorModel> {
199 200 201

		// Create text editor model if not yet done
		if (!this.textEditorModel) {
B
Benjamin Pasero 已提交
202
			return this.createTextEditorModel(content, this.resource, this.modeId).then(model => this);
203 204 205 206 207 208 209
		}

		// Otherwise update
		else {
			this.updateTextEditorModel(content);
		}

B
Benjamin Pasero 已提交
210
		return TPromise.as<UntitledEditorModel>(this);
211
	}
E
Erich Gamma 已提交
212

B
Benjamin Pasero 已提交
213
	private onModelContentChanged(): void {
214
		this.versionId++;
215 216

		// mark the untitled editor as non-dirty once its content becomes empty and we do
B
Benjamin Pasero 已提交
217 218
		// not have an associated path set. we never want dirty indicator in that case.
		if (!this.hasAssociatedFilePath && this.textEditorModel.getLineCount() === 1 && this.textEditorModel.getLineContent(1) === '') {
219
			this.setDirty(false);
E
Erich Gamma 已提交
220
		}
221

222 223 224
		// turn dirty otherwise
		else {
			this.setDirty(true);
225
		}
226

227 228
		// Handle content change event buffered
		this.contentChangeEventScheduler.schedule();
E
Erich Gamma 已提交
229 230 231 232 233
	}

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

234
		this.toDispose = dispose(this.toDispose);
E
Erich Gamma 已提交
235
	}
236
}