mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-01 22:12:26 +01:00
203 lines
6.4 KiB
TypeScript
203 lines
6.4 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
'use strict';
|
|
|
|
import { IDisposable } from 'vs/base/common/lifecycle';
|
|
import { TPromise } from 'vs/base/common/winjs.base';
|
|
import { EditorModel, IEncodingSupport } from 'vs/workbench/common/editor';
|
|
import { StringEditorModel } from 'vs/workbench/common/editor/stringEditorModel';
|
|
import URI from 'vs/base/common/uri';
|
|
import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
|
|
import { EndOfLinePreference } from 'vs/editor/common/editorCommon';
|
|
import { IFilesConfiguration } from 'vs/platform/files/common/files';
|
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
|
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';
|
|
import { RunOnceScheduler } from 'vs/base/common/async';
|
|
|
|
export class UntitledEditorModel extends StringEditorModel implements IEncodingSupport {
|
|
|
|
public static DEFAULT_CONTENT_CHANGE_BUFFER_DELAY = 1000;
|
|
|
|
private textModelChangeListener: IDisposable;
|
|
private configurationChangeListener: IDisposable;
|
|
|
|
private dirty: boolean;
|
|
private _onDidChangeContent: Emitter<void>;
|
|
private _onDidChangeDirty: Emitter<void>;
|
|
private _onDidChangeEncoding: Emitter<void>;
|
|
|
|
private contentChangeEventScheduler: RunOnceScheduler;
|
|
|
|
private configuredEncoding: string;
|
|
private preferredEncoding: string;
|
|
|
|
private hasAssociatedFilePath: boolean;
|
|
|
|
constructor(
|
|
value: string,
|
|
modeId: string,
|
|
resource: URI,
|
|
hasAssociatedFilePath: boolean,
|
|
@IModeService modeService: IModeService,
|
|
@IModelService modelService: IModelService,
|
|
@IConfigurationService private configurationService: IConfigurationService
|
|
) {
|
|
super(value, modeId, resource, modeService, modelService);
|
|
|
|
this.hasAssociatedFilePath = hasAssociatedFilePath;
|
|
this.dirty = hasAssociatedFilePath || !!value; // untitled associated to file path are dirty right away as well as untitled with content
|
|
|
|
this._onDidChangeContent = new Emitter<void>();
|
|
this._onDidChangeDirty = new Emitter<void>();
|
|
this._onDidChangeEncoding = new Emitter<void>();
|
|
|
|
this.contentChangeEventScheduler = new RunOnceScheduler(() => this._onDidChangeContent.fire(), UntitledEditorModel.DEFAULT_CONTENT_CHANGE_BUFFER_DELAY);
|
|
|
|
this.registerListeners();
|
|
|
|
// Indicate dirty state to listeners
|
|
if (this.dirty) {
|
|
setTimeout(() => {
|
|
this._onDidChangeDirty.fire();
|
|
}, 0 /* prevent race condition between creating input and emitting dirty event */);
|
|
}
|
|
}
|
|
|
|
public get onDidChangeContent(): Event<void> {
|
|
return this._onDidChangeContent.event;
|
|
}
|
|
|
|
public get onDidChangeDirty(): Event<void> {
|
|
return this._onDidChangeDirty.event;
|
|
}
|
|
|
|
public get onDidChangeEncoding(): Event<void> {
|
|
return this._onDidChangeEncoding.event;
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
return super.getOrCreateMode(modeService, modeId, firstLineText);
|
|
}
|
|
|
|
private registerListeners(): void {
|
|
|
|
// Config Changes
|
|
this.configurationChangeListener = this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationChange(e.config));
|
|
}
|
|
|
|
private onConfigurationChange(configuration: IFilesConfiguration): void {
|
|
this.configuredEncoding = configuration && configuration.files && configuration.files.encoding;
|
|
}
|
|
|
|
public getValue(): string {
|
|
if (this.textEditorModel) {
|
|
return this.textEditorModel.getValue(EndOfLinePreference.TextDefined, true /* Preserve BOM */);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public getModeId(): string {
|
|
if (this.textEditorModel) {
|
|
return this.textEditorModel.getModeId();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public getEncoding(): string {
|
|
return this.preferredEncoding || this.configuredEncoding;
|
|
}
|
|
|
|
public setEncoding(encoding: string): void {
|
|
const oldEncoding = this.getEncoding();
|
|
this.preferredEncoding = encoding;
|
|
|
|
// Emit if it changed
|
|
if (oldEncoding !== this.preferredEncoding) {
|
|
this._onDidChangeEncoding.fire();
|
|
}
|
|
}
|
|
|
|
public isDirty(): boolean {
|
|
return this.dirty;
|
|
}
|
|
|
|
public getResource(): URI {
|
|
return this.resource;
|
|
}
|
|
|
|
public revert(): void {
|
|
this.dirty = false;
|
|
|
|
// Events
|
|
this._onDidChangeDirty.fire();
|
|
|
|
// Handle content change event buffered
|
|
this.contentChangeEventScheduler.schedule();
|
|
}
|
|
|
|
public load(): TPromise<EditorModel> {
|
|
return super.load().then((model) => {
|
|
const configuration = this.configurationService.getConfiguration<IFilesConfiguration>();
|
|
|
|
// Encoding
|
|
this.configuredEncoding = configuration && configuration.files && configuration.files.encoding;
|
|
|
|
// Listen to content changes
|
|
this.textModelChangeListener = this.textEditorModel.onDidChangeContent(e => this.onModelContentChanged());
|
|
|
|
return model;
|
|
});
|
|
}
|
|
|
|
private onModelContentChanged(): void {
|
|
|
|
// mark the untitled editor as non-dirty once its content becomes empty and we do
|
|
// 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) === '') {
|
|
if (this.dirty) {
|
|
this.dirty = false;
|
|
this._onDidChangeDirty.fire();
|
|
}
|
|
}
|
|
|
|
// turn dirty if we were not
|
|
else if (!this.dirty) {
|
|
this.dirty = true;
|
|
this._onDidChangeDirty.fire();
|
|
}
|
|
|
|
// Handle content change event buffered
|
|
this.contentChangeEventScheduler.schedule();
|
|
}
|
|
|
|
public dispose(): void {
|
|
super.dispose();
|
|
|
|
if (this.textModelChangeListener) {
|
|
this.textModelChangeListener.dispose();
|
|
this.textModelChangeListener = null;
|
|
}
|
|
|
|
if (this.configurationChangeListener) {
|
|
this.configurationChangeListener.dispose();
|
|
this.configurationChangeListener = null;
|
|
}
|
|
|
|
this.contentChangeEventScheduler.dispose();
|
|
|
|
this._onDidChangeContent.dispose();
|
|
this._onDidChangeDirty.dispose();
|
|
this._onDidChangeEncoding.dispose();
|
|
}
|
|
} |