mirror of
https://github.com/microsoft/vscode.git
synced 2026-09-30 06:58:55 +01:00
237 lines
7.4 KiB
TypeScript
237 lines
7.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, dispose } from 'vs/base/common/lifecycle';
|
|
import { TPromise } from 'vs/base/common/winjs.base';
|
|
import { IEncodingSupport } from 'vs/workbench/common/editor';
|
|
import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel';
|
|
import URI from 'vs/base/common/uri';
|
|
import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
|
|
import { CONTENT_CHANGE_EVENT_BUFFER_DELAY } from 'vs/platform/files/common/files';
|
|
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';
|
|
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
|
|
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
|
|
import { ITextBufferFactory } from 'vs/editor/common/model';
|
|
import { createTextBufferFactory } from 'vs/editor/common/model/textModel';
|
|
|
|
export class UntitledEditorModel extends BaseTextEditorModel implements IEncodingSupport {
|
|
|
|
public static DEFAULT_CONTENT_CHANGE_BUFFER_DELAY = CONTENT_CHANGE_EVENT_BUFFER_DELAY;
|
|
|
|
private toDispose: IDisposable[];
|
|
|
|
private dirty: boolean;
|
|
private _onDidChangeContent: Emitter<void>;
|
|
private _onDidChangeDirty: Emitter<void>;
|
|
private _onDidChangeEncoding: Emitter<void>;
|
|
|
|
private versionId: number;
|
|
|
|
private contentChangeEventScheduler: RunOnceScheduler;
|
|
|
|
private configuredEncoding: string;
|
|
|
|
constructor(
|
|
private modeId: string,
|
|
private resource: URI,
|
|
private hasAssociatedFilePath: boolean,
|
|
private initialValue: string,
|
|
private preferredEncoding: string,
|
|
@IModeService modeService: IModeService,
|
|
@IModelService modelService: IModelService,
|
|
@IBackupFileService private backupFileService: IBackupFileService,
|
|
@ITextResourceConfigurationService private configurationService: ITextResourceConfigurationService
|
|
) {
|
|
super(modelService, modeService);
|
|
|
|
this.dirty = false;
|
|
this.versionId = 0;
|
|
this.toDispose = [];
|
|
|
|
this._onDidChangeContent = new Emitter<void>();
|
|
this.toDispose.push(this._onDidChangeContent);
|
|
|
|
this._onDidChangeDirty = new Emitter<void>();
|
|
this.toDispose.push(this._onDidChangeDirty);
|
|
|
|
this._onDidChangeEncoding = new Emitter<void>();
|
|
this.toDispose.push(this._onDidChangeEncoding);
|
|
|
|
this.contentChangeEventScheduler = new RunOnceScheduler(() => this._onDidChangeContent.fire(), UntitledEditorModel.DEFAULT_CONTENT_CHANGE_BUFFER_DELAY);
|
|
this.toDispose.push(this.contentChangeEventScheduler);
|
|
|
|
this.registerListeners();
|
|
}
|
|
|
|
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.toDispose.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChange()));
|
|
}
|
|
|
|
private onConfigurationChange(): void {
|
|
const configuredEncoding = this.configurationService.getValue<string>(this.resource, 'files.encoding');
|
|
|
|
if (this.configuredEncoding !== configuredEncoding) {
|
|
this.configuredEncoding = configuredEncoding;
|
|
|
|
if (!this.preferredEncoding) {
|
|
this._onDidChangeEncoding.fire(); // do not fire event if we have a preferred encoding set
|
|
}
|
|
}
|
|
}
|
|
|
|
public getVersionId(): number {
|
|
return this.versionId;
|
|
}
|
|
|
|
public getModeId(): string {
|
|
if (this.textEditorModel) {
|
|
return this.textEditorModel.getLanguageIdentifier().language;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
private setDirty(dirty: boolean): void {
|
|
if (this.dirty === dirty) {
|
|
return;
|
|
}
|
|
|
|
this.dirty = dirty;
|
|
this._onDidChangeDirty.fire();
|
|
}
|
|
|
|
public getResource(): URI {
|
|
return this.resource;
|
|
}
|
|
|
|
public revert(): void {
|
|
this.setDirty(false);
|
|
|
|
// Handle content change event buffered
|
|
this.contentChangeEventScheduler.schedule();
|
|
}
|
|
|
|
public load(): TPromise<UntitledEditorModel> {
|
|
|
|
// Check for backups first
|
|
return this.backupFileService.loadBackupResource(this.resource).then(backupResource => {
|
|
if (backupResource) {
|
|
return this.backupFileService.resolveBackupContent(backupResource);
|
|
}
|
|
|
|
return null;
|
|
}).then(backupTextBufferFactory => {
|
|
const hasBackup = !!backupTextBufferFactory;
|
|
|
|
// untitled associated to file path are dirty right away as well as untitled with content
|
|
this.setDirty(this.hasAssociatedFilePath || hasBackup);
|
|
|
|
let untitledContents: ITextBufferFactory;
|
|
if (backupTextBufferFactory) {
|
|
untitledContents = backupTextBufferFactory;
|
|
} else {
|
|
untitledContents = createTextBufferFactory(this.initialValue || '');
|
|
}
|
|
|
|
return this.doLoad(untitledContents).then(model => {
|
|
// Encoding
|
|
this.configuredEncoding = this.configurationService.getValue<string>(this.resource, 'files.encoding');
|
|
|
|
// Listen to content changes
|
|
this.toDispose.push(this.textEditorModel.onDidChangeContent(() => this.onModelContentChanged()));
|
|
|
|
// Listen to mode changes
|
|
this.toDispose.push(this.textEditorModel.onDidChangeLanguage(() => this.onConfigurationChange())); // mode change can have impact on config
|
|
|
|
return model;
|
|
});
|
|
});
|
|
}
|
|
|
|
private doLoad(content: ITextBufferFactory): TPromise<UntitledEditorModel> {
|
|
|
|
// Create text editor model if not yet done
|
|
if (!this.textEditorModel) {
|
|
return this.createTextEditorModel(content, this.resource, this.modeId).then(model => this);
|
|
}
|
|
|
|
// Otherwise update
|
|
else {
|
|
this.updateTextEditorModel(content);
|
|
}
|
|
|
|
return TPromise.as<UntitledEditorModel>(this);
|
|
}
|
|
|
|
private onModelContentChanged(): void {
|
|
this.versionId++;
|
|
|
|
// 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) === '') {
|
|
this.setDirty(false);
|
|
}
|
|
|
|
// turn dirty otherwise
|
|
else {
|
|
this.setDirty(true);
|
|
}
|
|
|
|
// Handle content change event buffered
|
|
this.contentChangeEventScheduler.schedule();
|
|
}
|
|
|
|
public dispose(): void {
|
|
super.dispose();
|
|
|
|
this.toDispose = dispose(this.toDispose);
|
|
}
|
|
}
|