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

6
import { URI } from 'vs/base/common/uri';
J
Johannes Rieken 已提交
7
import { suggestFilename } from 'vs/base/common/mime';
I
isidor 已提交
8
import { createMemoizer } from 'vs/base/common/decorators';
J
Johannes Rieken 已提交
9
import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
10 11
import { basenameOrAuthority, dirname, toLocalResource } from 'vs/base/common/resources';
import { IEncodingSupport, EncodingMode, Verbosity, IModeSupport, TextEditorInput, GroupIdentifier, IRevertOptions } from 'vs/workbench/common/editor';
12
import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel';
J
Johannes Rieken 已提交
13
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
14 15
import { Emitter } from 'vs/base/common/event';
import { ITextFileService, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles';
I
isidor 已提交
16
import { ILabelService } from 'vs/platform/label/common/label';
M
Matt Bierner 已提交
17
import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService';
18 19
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
20
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
21

E
Erich Gamma 已提交
22 23 24
/**
 * An editor input to be used for untitled text buffers.
 */
25
export class UntitledTextEditorInput extends TextEditorInput implements IEncodingSupport, IModeSupport {
E
Erich Gamma 已提交
26

B
Benjamin Pasero 已提交
27
	static readonly ID: string = 'workbench.editors.untitledEditorInput';
28

I
isidor 已提交
29
	private static readonly MEMOIZER = createMemoizer();
E
Erich Gamma 已提交
30

31 32
	private readonly _onDidModelChangeEncoding = this._register(new Emitter<void>());
	readonly onDidModelChangeEncoding = this._onDidModelChangeEncoding.event;
33

34
	private cachedModel: UntitledTextEditorModel | null = null;
35

36 37 38 39
	private modelResolve: Promise<UntitledTextEditorModel & IResolvedTextEditorModel> | null = null;

	private preferredMode: string | undefined;

E
Erich Gamma 已提交
40
	constructor(
41
		resource: URI,
42
		private readonly _hasAssociatedFilePath: boolean,
43
		preferredMode: string | undefined,
44 45
		private readonly initialValue: string | undefined,
		private preferredEncoding: string | undefined,
46
		@IInstantiationService private readonly instantiationService: IInstantiationService,
47 48 49
		@ITextFileService textFileService: ITextFileService,
		@ILabelService private readonly labelService: ILabelService,
		@IEditorService editorService: IEditorService,
50 51
		@IEditorGroupsService editorGroupService: IEditorGroupsService,
		@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
E
Erich Gamma 已提交
52
	) {
53 54
		super(resource, editorService, editorGroupService, textFileService);

55 56 57 58
		if (preferredMode) {
			this.setMode(preferredMode);
		}

59 60 61 62
		this.registerListeners();
	}

	private registerListeners(): void {
63
		this._register(this.labelService.onDidChangeFormatters(() => UntitledTextEditorInput.MEMOIZER.clear()));
64 65
	}

B
Benjamin Pasero 已提交
66
	get hasAssociatedFilePath(): boolean {
B
Benjamin Pasero 已提交
67 68 69
		return this._hasAssociatedFilePath;
	}

B
Benjamin Pasero 已提交
70
	getTypeId(): string {
71
		return UntitledTextEditorInput.ID;
E
Erich Gamma 已提交
72 73
	}

B
Benjamin Pasero 已提交
74
	getName(): string {
75 76
		if (this.cachedModel) {
			return this.cachedModel.name;
77 78
		}

B
Benjamin Pasero 已提交
79
		return this.hasAssociatedFilePath ? basenameOrAuthority(this.resource) : this.resource.path;
E
Erich Gamma 已提交
80 81
	}

82
	@UntitledTextEditorInput.MEMOIZER
83
	private get shortDescription(): string {
I
isidor 已提交
84
		return this.labelService.getUriBasenameLabel(dirname(this.resource));
85 86
	}

87
	@UntitledTextEditorInput.MEMOIZER
88
	private get mediumDescription(): string {
B
Benjamin Pasero 已提交
89
		return this.labelService.getUriLabel(dirname(this.resource), { relative: true });
90 91
	}

92
	@UntitledTextEditorInput.MEMOIZER
93
	private get longDescription(): string {
B
Benjamin Pasero 已提交
94
		return this.labelService.getUriLabel(dirname(this.resource));
95 96
	}

97
	getDescription(verbosity: Verbosity = Verbosity.MEDIUM): string | undefined {
98
		if (!this.hasAssociatedFilePath) {
99
			return undefined;
100 101 102 103
		}

		switch (verbosity) {
			case Verbosity.SHORT:
104
				return this.shortDescription;
105
			case Verbosity.LONG:
106
				return this.longDescription;
107 108
			case Verbosity.MEDIUM:
			default:
109
				return this.mediumDescription;
110 111 112
		}
	}

113
	@UntitledTextEditorInput.MEMOIZER
114 115 116 117
	private get shortTitle(): string {
		return this.getName();
	}

118
	@UntitledTextEditorInput.MEMOIZER
119
	private get mediumTitle(): string {
120
		return this.labelService.getUriLabel(this.resource, { relative: true });
121 122
	}

123
	@UntitledTextEditorInput.MEMOIZER
124
	private get longTitle(): string {
I
isidor 已提交
125
		return this.labelService.getUriLabel(this.resource);
126 127
	}

B
Benjamin Pasero 已提交
128
	getTitle(verbosity: Verbosity): string {
129 130 131 132 133 134
		if (!this.hasAssociatedFilePath) {
			return this.getName();
		}

		switch (verbosity) {
			case Verbosity.SHORT:
135
				return this.shortTitle;
136
			case Verbosity.MEDIUM:
137
				return this.mediumTitle;
138
			case Verbosity.LONG:
139
				return this.longTitle;
140
		}
E
Erich Gamma 已提交
141 142
	}

143 144 145 146 147 148 149 150
	isReadonly(): boolean {
		return false;
	}

	isUntitled(): boolean {
		return true;
	}

B
Benjamin Pasero 已提交
151
	isDirty(): boolean {
152 153

		// Always trust the model first if existing
B
Benjamin Pasero 已提交
154 155 156 157
		if (this.cachedModel) {
			return this.cachedModel.isDirty();
		}

D
Daniel Imms 已提交
158 159 160 161 162
		// A disposed input is never dirty, even if it was restored from backup
		if (this.isDisposed()) {
			return false;
		}

163 164 165 166 167 168 169
		// A input with initial value is always dirty
		if (this.initialValue && this.initialValue.length > 0) {
			return true;
		}

		// A input with associated path is always dirty because it is the intent
		// of the user to create a new file at that location through saving
B
Benjamin Pasero 已提交
170
		return this.hasAssociatedFilePath;
E
Erich Gamma 已提交
171 172
	}

173
	save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise<boolean> {
174
		return this.doSaveAs(group, options, async () => {
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191

			// With associated file path, save to the path that is
			// associated. Make sure to convert the result using
			// remote authority properly.
			if (this.hasAssociatedFilePath) {
				if (await this.textFileService.save(this.resource, options)) {
					return toLocalResource(this.resource, this.environmentService.configuration.remoteAuthority);
				}

				return;
			}

			// Without associated file path, do a normal "Save As"
			return this.textFileService.saveAs(this.resource, undefined, options);
		}, true /* replace editor across all groups */);
	}

192
	saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise<boolean> {
193
		return this.doSaveAs(group, options, () => this.textFileService.saveAs(this.resource, undefined, options), true /* replace editor across all groups */);
194 195
	}

B
Benjamin Pasero 已提交
196
	async revert(group: GroupIdentifier, options?: IRevertOptions): Promise<boolean> {
B
Benjamin Pasero 已提交
197 198 199
		if (this.cachedModel) {
			this.cachedModel.revert();
		}
200

201
		this.dispose(); // a reverted untitled text editor is no longer valid, so we dispose it
202

B
Benjamin Pasero 已提交
203
		return true;
204 205
	}

B
Benjamin Pasero 已提交
206
	suggestFileName(): string {
E
Erich Gamma 已提交
207
		if (!this.hasAssociatedFilePath) {
208
			if (this.cachedModel) {
209 210 211
				const mode = this.cachedModel.getMode();
				if (mode !== PLAINTEXT_MODE_ID) { // do not suggest when the mode ID is simple plain text
					return suggestFilename(mode, this.getName());
212
				}
E
Erich Gamma 已提交
213 214 215 216 217 218
			}
		}

		return this.getName();
	}

219
	getEncoding(): string | undefined {
E
Erich Gamma 已提交
220 221 222 223
		if (this.cachedModel) {
			return this.cachedModel.getEncoding();
		}

224
		return this.preferredEncoding;
E
Erich Gamma 已提交
225 226
	}

B
Benjamin Pasero 已提交
227
	setEncoding(encoding: string, mode: EncodingMode /* ignored, we only have Encode */): void {
228 229
		this.preferredEncoding = encoding;

E
Erich Gamma 已提交
230 231 232 233 234
		if (this.cachedModel) {
			this.cachedModel.setEncoding(encoding);
		}
	}

235
	setMode(mode: string): void {
236
		let actualMode: string | undefined = undefined;
237 238
		if (mode === '${activeEditorLanguage}') {
			// support the special '${activeEditorLanguage}' mode by
239 240 241 242 243 244
			// looking up the language mode from the currently
			// active text editor if any
			actualMode = this.editorService.activeTextEditorMode;
		} else {
			actualMode = mode;
		}
245

246 247 248 249
		this.preferredMode = actualMode;

		if (this.preferredMode && this.cachedModel) {
			this.cachedModel.setMode(this.preferredMode);
250 251 252 253 254 255 256 257 258 259 260
		}
	}

	getMode(): string | undefined {
		if (this.cachedModel) {
			return this.cachedModel.getMode();
		}

		return this.preferredMode;
	}

261
	resolve(): Promise<UntitledTextEditorModel & IResolvedTextEditorModel> {
E
Erich Gamma 已提交
262

263 264 265
		// Join a model resolve if we have had one before
		if (this.modelResolve) {
			return this.modelResolve;
E
Erich Gamma 已提交
266 267
		}

268 269
		// Otherwise Create Model and load
		this.cachedModel = this.createModel();
270
		this.modelResolve = this.cachedModel.load();
B
Benjamin Pasero 已提交
271

272
		return this.modelResolve;
E
Erich Gamma 已提交
273 274
	}

275 276
	private createModel(): UntitledTextEditorModel {
		const model = this._register(this.instantiationService.createInstance(UntitledTextEditorModel, this.preferredMode, this.resource, this.hasAssociatedFilePath, this.initialValue, this.preferredEncoding));
277

278 279 280 281 282 283 284
		this.registerModelListeners(model);

		return model;
	}

	private registerModelListeners(model: UntitledTextEditorModel): void {

285
		// re-emit some events from the model
B
Benjamin Pasero 已提交
286 287
		this._register(model.onDidChangeDirty(() => this._onDidChangeDirty.fire()));
		this._register(model.onDidChangeEncoding(() => this._onDidModelChangeEncoding.fire()));
288
		this._register(model.onDidChangeName(() => this._onDidChangeLabel.fire()));
E
Erich Gamma 已提交
289 290
	}

291
	matches(otherInput: unknown): boolean {
E
Erich Gamma 已提交
292 293 294 295
		if (super.matches(otherInput) === true) {
			return true;
		}

B
Benjamin Pasero 已提交
296
		// Otherwise compare by properties
297
		if (otherInput instanceof UntitledTextEditorInput) {
B
Benjamin Pasero 已提交
298
			return otherInput.resource.toString() === this.resource.toString();
E
Erich Gamma 已提交
299 300 301 302 303
		}

		return false;
	}

B
Benjamin Pasero 已提交
304
	dispose(): void {
305 306
		this.cachedModel = null;
		this.modelResolve = null;
307

308
		super.dispose();
E
Erich Gamma 已提交
309
	}
310
}