Merge from vscode 05fc61ffb1aee9fd19173c32113daed079f9b7bd (#5074)

* Merge from vscode 05fc61ffb1aee9fd19173c32113daed079f9b7bd

* fix tests
This commit is contained in:
Anthony Dresser
2019-04-16 22:11:30 -07:00
committed by GitHub
parent 2f8519cb6b
commit 8956b591f7
217 changed files with 5120 additions and 3926 deletions

View File

@@ -12,7 +12,7 @@ import { URI } from 'vs/base/common/uri';
import { isUndefinedOrNull, withUndefinedAsNull } from 'vs/base/common/types';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ITextFileService, IAutoSaveConfiguration, ModelState, ITextFileEditorModel, ISaveOptions, ISaveErrorHandler, ISaveParticipant, StateChange, SaveReason, IRawTextContent, ILoadOptions, LoadReason, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextFileService, IAutoSaveConfiguration, ModelState, ITextFileEditorModel, ISaveOptions, ISaveErrorHandler, ISaveParticipant, StateChange, SaveReason, ITextFileStreamContent, ILoadOptions, LoadReason, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
import { EncodingMode } from 'vs/workbench/common/editor';
import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
@@ -266,14 +266,14 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
// If we have a backup, continue loading with it
if (!!backup) {
const content: IRawTextContent = {
const content: ITextFileStreamContent = {
resource: this.resource,
name: basename(this.resource),
mtime: Date.now(),
size: 0,
etag: etag(Date.now(), 0),
value: createTextBufferFactory(''), /* will be filled later from backup */
encoding: this.fileService.encoding.getWriteEncoding(this.resource, this.preferredEncoding).encoding,
encoding: this.textFileService.encoding.getPreferredWriteEncoding(this.resource, this.preferredEncoding).encoding,
isReadonly: false
};
@@ -306,7 +306,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
// Resolve Content
try {
const content = await this.textFileService.resolve(this.resource, { acceptTextOnly: !allowBinary, etag, encoding: this.preferredEncoding });
const content = await this.textFileService.readStream(this.resource, { acceptTextOnly: !allowBinary, etag, encoding: this.preferredEncoding });
// Clear orphaned state when loading was successful
this.setOrphaned(false);
@@ -346,34 +346,33 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
}
}
private loadWithContent(content: IRawTextContent, options?: ILoadOptions, backup?: URI): Promise<TextFileEditorModel> {
return this.doLoadWithContent(content, backup).then(model => {
private async loadWithContent(content: ITextFileStreamContent, options?: ILoadOptions, backup?: URI): Promise<TextFileEditorModel> {
const model = await this.doLoadWithContent(content, backup);
// Telemetry: We log the fileGet telemetry event after the model has been loaded to ensure a good mimetype
const settingsType = this.getTypeIfSettings();
if (settingsType) {
/* __GDPR__
"settingsRead" : {
"settingsType": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('settingsRead', { settingsType }); // Do not log read to user settings.json and .vscode folder as a fileGet event as it ruins our JSON usage data
} else {
/* __GDPR__
"fileGet" : {
"${include}": [
"${FileTelemetryData}"
]
}
*/
this.telemetryService.publicLog('fileGet', this.getTelemetryData(options && options.reason ? options.reason : LoadReason.OTHER));
}
// Telemetry: We log the fileGet telemetry event after the model has been loaded to ensure a good mimetype
const settingsType = this.getTypeIfSettings();
if (settingsType) {
/* __GDPR__
"settingsRead" : {
"settingsType": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('settingsRead', { settingsType }); // Do not log read to user settings.json and .vscode folder as a fileGet event as it ruins our JSON usage data
} else {
/* __GDPR__
"fileGet" : {
"${include}": [
"${FileTelemetryData}"
]
}
*/
this.telemetryService.publicLog('fileGet', this.getTelemetryData(options && options.reason ? options.reason : LoadReason.OTHER));
}
return model;
});
return model;
}
private doLoadWithContent(content: IRawTextContent, backup?: URI): Promise<TextFileEditorModel> {
private doLoadWithContent(content: ITextFileStreamContent, backup?: URI): Promise<TextFileEditorModel> {
this.logService.trace('load() - resolved content', this.resource);
// Update our resolved disk stat model

View File

@@ -154,6 +154,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
// Model does not exist
else {
const newModel = model = this.instantiationService.createInstance(TextFileEditorModel, resource, options ? options.encoding : undefined);
model = newModel;
modelPromise = model.load(options);
// Install state change listener
@@ -208,7 +209,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
} catch (error) {
// Free resources of this invalid model
if (model) {
if (model && typeof model.dispose === 'function') { // workaround for https://github.com/Microsoft/vscode/issues/72404
model.dispose();
}

View File

@@ -11,11 +11,11 @@ import { Event, Emitter } from 'vs/base/common/event';
import * as platform from 'vs/base/common/platform';
import { IWindowsService } from 'vs/platform/windows/common/windows';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IResult, ITextFileOperationResult, ITextFileService, IRawTextContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ISaveOptions, AutoSaveContext, IWillMoveEvent } from 'vs/workbench/services/textfile/common/textfiles';
import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ISaveOptions, AutoSaveContext, IWillMoveEvent, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles';
import { ConfirmResult, IRevertOptions } from 'vs/workbench/common/editor';
import { ILifecycleService, ShutdownReason, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IFileService, IResolveContentOptions, IFilesConfiguration, FileOperationError, FileOperationResult, AutoSaveConfiguration, HotExitConfiguration, ITextSnapshot, IWriteTextFileOptions, IFileStatWithMetadata, toBufferOrReadable, ICreateFileOptions } from 'vs/platform/files/common/files';
import { IFileService, IFilesConfiguration, FileOperationError, FileOperationResult, AutoSaveConfiguration, HotExitConfiguration, IFileStatWithMetadata, ICreateFileOptions } from 'vs/platform/files/common/files';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Disposable } from 'vs/base/common/lifecycle';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
@@ -37,11 +37,13 @@ import { IModeService } from 'vs/editor/common/services/modeService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { coalesce } from 'vs/base/common/arrays';
import { trim } from 'vs/base/common/strings';
import { VSBuffer } from 'vs/base/common/buffer';
import { ITextSnapshot } from 'vs/editor/common/model';
/**
* The workbench file service implementation implements the raw file service spec and adds additional methods on top.
*/
export class TextFileService extends Disposable implements ITextFileService {
export abstract class TextFileService extends Disposable implements ITextFileService {
_serviceBrand: ServiceIdentifier<any>;
@@ -57,6 +59,8 @@ export class TextFileService extends Disposable implements ITextFileService {
private _models: TextFileEditorModelManager;
get models(): ITextFileEditorModelManager { return this._models; }
abstract get encoding(): IResourceEncodings;
private currentFilesAssociationConfig: { [key: string]: string; };
private configuredAutoSaveDelay?: number;
private configuredAutoSaveOnFocusChange: boolean;
@@ -364,24 +368,62 @@ export class TextFileService extends Disposable implements ITextFileService {
//#endregion
//#region primitives (resolve, create, move, delete, update)
//#region primitives (read, create, move, delete, update)
async resolve(resource: URI, options?: IResolveContentOptions): Promise<IRawTextContent> {
const streamContent = await this.fileService.resolveStreamContent(resource, options);
const value = await createTextBufferFactoryFromStream(streamContent.value);
async read(resource: URI, options?: IReadTextFileOptions): Promise<ITextFileContent> {
const content = await this.fileService.readFile(resource, options);
// in case of acceptTextOnly: true, we check the first
// chunk for possibly being binary by looking for 0-bytes
// we limit this check to the first 512 bytes
this.validateBinary(content.value, options);
return {
resource: streamContent.resource,
name: streamContent.name,
mtime: streamContent.mtime,
etag: streamContent.etag,
encoding: streamContent.encoding,
isReadonly: streamContent.isReadonly,
size: streamContent.size,
value
...content,
encoding: 'utf8',
value: content.value.toString()
};
}
async readStream(resource: URI, options?: IReadTextFileOptions): Promise<ITextFileStreamContent> {
const stream = await this.fileService.readFileStream(resource, options);
// in case of acceptTextOnly: true, we check the first
// chunk for possibly being binary by looking for 0-bytes
// we limit this check to the first 512 bytes
let checkedForBinary = false;
const throwOnBinary = (data: VSBuffer): Error | undefined => {
if (!checkedForBinary) {
checkedForBinary = true;
this.validateBinary(data, options);
}
return undefined;
};
return {
...stream,
encoding: 'utf8',
value: await createTextBufferFactoryFromStream(stream.value, undefined, options && options.acceptTextOnly ? throwOnBinary : undefined)
};
}
private validateBinary(buffer: VSBuffer, options?: IReadTextFileOptions): void {
if (!options || !options.acceptTextOnly) {
return; // no validation needed
}
// in case of acceptTextOnly: true, we check the first
// chunk for possibly being binary by looking for 0-bytes
// we limit this check to the first 512 bytes
for (let i = 0; i < buffer.byteLength && i < 512; i++) {
if (buffer.readUInt8(i) === 0) {
throw new TextFileOperationError(nls.localize('fileBinaryError', "File seems to be binary and cannot be opened as text"), TextFileOperationResult.FILE_IS_BINARY, options);
}
}
}
async create(resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions): Promise<IFileStatWithMetadata> {
const stat = await this.doCreate(resource, value, options);
@@ -844,7 +886,10 @@ export class TextFileService extends Disposable implements ITextFileService {
} catch (error) {
// binary model: delete the file and run the operation again
if ((<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_IS_BINARY || (<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_TOO_LARGE) {
if (
(<TextFileOperationError>error).textFileOperationResult === TextFileOperationResult.FILE_IS_BINARY ||
(<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_TOO_LARGE
) {
await this.fileService.del(target);
return this.doSaveTextFileAs(sourceModel, resource, target, options);

View File

@@ -7,274 +7,24 @@ import { URI } from 'vs/base/common/uri';
import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IEncodingSupport, ConfirmResult, IRevertOptions } from 'vs/workbench/common/editor';
import { IResolveContentOptions, ITextSnapshot, IBaseStatWithMetadata, IWriteTextFileOptions, IFileStatWithMetadata } from 'vs/platform/files/common/files';
import { IBaseStatWithMetadata, IFileStatWithMetadata, IReadFileOptions, IWriteFileOptions, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
import { ITextEditorModel } from 'vs/editor/common/services/resolverService';
import { ITextBufferFactory, ITextModel } from 'vs/editor/common/model';
import { ITextBufferFactory, ITextModel, ITextSnapshot } from 'vs/editor/common/model';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer';
import { isUndefinedOrNull } from 'vs/base/common/types';
/**
* The save error handler can be installed on the text file editor model to install code that executes when save errors occur.
*/
export interface ISaveErrorHandler {
/**
* Called whenever a save fails.
*/
onSaveError(error: Error, model: ITextFileEditorModel): void;
}
export interface ISaveParticipant {
/**
* Participate in a save of a model. Allows to change the model before it is being saved to disk.
*/
participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise<void>;
}
/**
* States the text file editor model can be in.
*/
export const enum ModelState {
SAVED,
DIRTY,
PENDING_SAVE,
/**
* A model is in conflict mode when changes cannot be saved because the
* underlying file has changed. Models in conflict mode are always dirty.
*/
CONFLICT,
/**
* A model is in orphan state when the underlying file has been deleted.
*/
ORPHAN,
/**
* Any error that happens during a save that is not causing the CONFLICT state.
* Models in error mode are always diry.
*/
ERROR
}
export const enum StateChange {
DIRTY,
SAVING,
SAVE_ERROR,
SAVED,
REVERTED,
ENCODING,
CONTENT_CHANGE,
ORPHANED_CHANGE
}
export class TextFileModelChangeEvent {
private _resource: URI;
private _kind: StateChange;
constructor(model: ITextFileEditorModel, kind: StateChange) {
this._resource = model.getResource();
this._kind = kind;
}
get resource(): URI {
return this._resource;
}
get kind(): StateChange {
return this._kind;
}
}
export const TEXT_FILE_SERVICE_ID = 'textFileService';
export const AutoSaveContext = new RawContextKey<string>('config.files.autoSave', undefined);
export interface ITextFileOperationResult {
results: IResult[];
}
export interface IResult {
source: URI;
target?: URI;
success?: boolean;
}
export interface IAutoSaveConfiguration {
autoSaveDelay?: number;
autoSaveFocusChange: boolean;
autoSaveApplicationChange: boolean;
}
export const enum AutoSaveMode {
OFF,
AFTER_SHORT_DELAY,
AFTER_LONG_DELAY,
ON_FOCUS_CHANGE,
ON_WINDOW_CHANGE
}
export const enum SaveReason {
EXPLICIT = 1,
AUTO = 2,
FOCUS_CHANGE = 3,
WINDOW_CHANGE = 4
}
export const enum LoadReason {
EDITOR = 1,
REFERENCE = 2,
OTHER = 3
}
export const ITextFileService = createDecorator<ITextFileService>(TEXT_FILE_SERVICE_ID);
export interface IRawTextContent extends IBaseStatWithMetadata {
/**
* The line grouped content of a text file.
*/
value: ITextBufferFactory;
/**
* The encoding of the content if known.
*/
encoding: string;
}
export interface IModelLoadOrCreateOptions {
/**
* Context why the model is being loaded or created.
*/
reason?: LoadReason;
/**
* The encoding to use when resolving the model text content.
*/
encoding?: string;
/**
* If the model was already loaded before, allows to trigger
* a reload of it to fetch the latest contents:
* - async: loadOrCreate() will return immediately and trigger
* a reload that will run in the background.
* - sync: loadOrCreate() will only return resolved when the
* model has finished reloading.
*/
reload?: {
async: boolean
};
/**
* Allow to load a model even if we think it is a binary file.
*/
allowBinary?: boolean;
}
export interface ITextFileEditorModelManager {
onModelDisposed: Event<URI>;
onModelContentChanged: Event<TextFileModelChangeEvent>;
onModelEncodingChanged: Event<TextFileModelChangeEvent>;
onModelDirty: Event<TextFileModelChangeEvent>;
onModelSaveError: Event<TextFileModelChangeEvent>;
onModelSaved: Event<TextFileModelChangeEvent>;
onModelReverted: Event<TextFileModelChangeEvent>;
onModelOrphanedChanged: Event<TextFileModelChangeEvent>;
onModelsDirty: Event<TextFileModelChangeEvent[]>;
onModelsSaveError: Event<TextFileModelChangeEvent[]>;
onModelsSaved: Event<TextFileModelChangeEvent[]>;
onModelsReverted: Event<TextFileModelChangeEvent[]>;
get(resource: URI): ITextFileEditorModel | undefined;
getAll(resource?: URI): ITextFileEditorModel[];
loadOrCreate(resource: URI, options?: IModelLoadOrCreateOptions): Promise<ITextFileEditorModel>;
disposeModel(model: ITextFileEditorModel): void;
}
export interface ISaveOptions {
force?: boolean;
reason?: SaveReason;
overwriteReadonly?: boolean;
overwriteEncoding?: boolean;
skipSaveParticipants?: boolean;
writeElevated?: boolean;
}
export interface ILoadOptions {
/**
* Go to disk bypassing any cache of the model if any.
*/
forceReadFromDisk?: boolean;
/**
* Allow to load a model even if we think it is a binary file.
*/
allowBinary?: boolean;
/**
* Context why the model is being loaded.
*/
reason?: LoadReason;
}
export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport {
onDidContentChange: Event<StateChange>;
onDidStateChange: Event<StateChange>;
getVersionId(): number;
getResource(): URI;
hasState(state: ModelState): boolean;
getETag(): string | null;
updatePreferredEncoding(encoding: string): void;
save(options?: ISaveOptions): Promise<void>;
load(options?: ILoadOptions): Promise<ITextFileEditorModel>;
revert(soft?: boolean): Promise<void>;
createSnapshot(): ITextSnapshot | null;
isDirty(): boolean;
isResolved(): boolean;
isDisposed(): boolean;
}
export interface IResolvedTextFileEditorModel extends ITextFileEditorModel {
readonly textEditorModel: ITextModel;
createSnapshot(): ITextSnapshot;
}
export interface IWillMoveEvent {
oldResource: URI;
newResource: URI;
waitUntil(p: Promise<unknown>): void;
}
export const ITextFileService = createDecorator<ITextFileService>('textFileService');
export interface ITextFileService extends IDisposable {
_serviceBrand: ServiceIdentifier<any>;
readonly onWillMove: Event<IWillMoveEvent>;
readonly onAutoSaveConfigurationChange: Event<IAutoSaveConfiguration>;
readonly onFilesAssociationChange: Event<void>;
readonly isHotExitEnabled: boolean;
@@ -284,6 +34,11 @@ export interface ITextFileService extends IDisposable {
*/
readonly models: ITextFileEditorModelManager;
/**
* Helper to determine encoding for resources.
*/
readonly encoding: IResourceEncodings;
/**
* A resource is dirty if it has unsaved changes or is an untitled file not yet saved.
*
@@ -348,9 +103,14 @@ export interface ITextFileService extends IDisposable {
create(resource: URI, contents?: string | ITextSnapshot, options?: { overwrite?: boolean }): Promise<IFileStatWithMetadata>;
/**
* Resolve the contents of a file identified by the resource.
* Read the contents of a file identified by the resource.
*/
resolve(resource: URI, options?: IResolveContentOptions): Promise<IRawTextContent>;
read(resource: URI, options?: IReadTextFileOptions): Promise<ITextFileContent>;
/**
* Read the contents of a file identified by the resource as stream.
*/
readStream(resource: URI, options?: IReadTextFileOptions): Promise<ITextFileStreamContent>;
/**
* Update a file with given contents.
@@ -385,3 +145,659 @@ export interface ITextFileService extends IDisposable {
*/
getAutoSaveConfiguration(): IAutoSaveConfiguration;
}
export interface IReadTextFileOptions extends IReadFileOptions {
/**
* The optional acceptTextOnly parameter allows to fail this request early if the file
* contents are not textual.
*/
acceptTextOnly?: boolean;
/**
* The optional encoding parameter allows to specify the desired encoding when resolving
* the contents of the file.
*/
encoding?: string;
/**
* The optional guessEncoding parameter allows to guess encoding from content of the file.
*/
autoGuessEncoding?: boolean;
}
export interface IWriteTextFileOptions extends IWriteFileOptions {
/**
* The encoding to use when updating a file.
*/
encoding?: string;
/**
* If set to true, will enforce the selected encoding and not perform any detection using BOMs.
*/
overwriteEncoding?: boolean;
/**
* Whether to overwrite a file even if it is readonly.
*/
overwriteReadonly?: boolean;
/**
* Wether to write to the file as elevated (admin) user. When setting this option a prompt will
* ask the user to authenticate as super user.
*/
writeElevated?: boolean;
}
export const enum TextFileOperationResult {
FILE_IS_BINARY
}
export class TextFileOperationError extends FileOperationError {
constructor(message: string, public textFileOperationResult: TextFileOperationResult, public options?: IReadTextFileOptions & IWriteTextFileOptions) {
super(message, FileOperationResult.FILE_OTHER_ERROR);
}
static isTextFileOperationError(obj: unknown): obj is TextFileOperationError {
return obj instanceof Error && !isUndefinedOrNull((obj as TextFileOperationError).textFileOperationResult);
}
}
export interface IResourceEncodings {
getPreferredWriteEncoding(resource: URI, preferredEncoding?: string): IResourceEncoding;
}
export interface IResourceEncoding {
encoding: string;
hasBOM: boolean;
}
/**
* The save error handler can be installed on the text file editor model to install code that executes when save errors occur.
*/
export interface ISaveErrorHandler {
/**
* Called whenever a save fails.
*/
onSaveError(error: Error, model: ITextFileEditorModel): void;
}
export interface ISaveParticipant {
/**
* Participate in a save of a model. Allows to change the model before it is being saved to disk.
*/
participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise<void>;
}
/**
* States the text file editor model can be in.
*/
export const enum ModelState {
/**
* A model is saved.
*/
SAVED,
/**
* A model is dirty.
*/
DIRTY,
/**
* A model is transitioning from dirty to saved.
*/
PENDING_SAVE,
/**
* A model is in conflict mode when changes cannot be saved because the
* underlying file has changed. Models in conflict mode are always dirty.
*/
CONFLICT,
/**
* A model is in orphan state when the underlying file has been deleted.
*/
ORPHAN,
/**
* Any error that happens during a save that is not causing the CONFLICT state.
* Models in error mode are always diry.
*/
ERROR
}
export const enum StateChange {
DIRTY,
SAVING,
SAVE_ERROR,
SAVED,
REVERTED,
ENCODING,
CONTENT_CHANGE,
ORPHANED_CHANGE
}
export class TextFileModelChangeEvent {
private _resource: URI;
constructor(model: ITextFileEditorModel, private _kind: StateChange) {
this._resource = model.getResource();
}
get resource(): URI {
return this._resource;
}
get kind(): StateChange {
return this._kind;
}
}
export const AutoSaveContext = new RawContextKey<string>('config.files.autoSave', undefined);
export interface ITextFileOperationResult {
results: IResult[];
}
export interface IResult {
source: URI;
target?: URI;
success?: boolean;
}
export interface IAutoSaveConfiguration {
autoSaveDelay?: number;
autoSaveFocusChange: boolean;
autoSaveApplicationChange: boolean;
}
export const enum AutoSaveMode {
OFF,
AFTER_SHORT_DELAY,
AFTER_LONG_DELAY,
ON_FOCUS_CHANGE,
ON_WINDOW_CHANGE
}
export const enum SaveReason {
EXPLICIT = 1,
AUTO = 2,
FOCUS_CHANGE = 3,
WINDOW_CHANGE = 4
}
export const enum LoadReason {
EDITOR = 1,
REFERENCE = 2,
OTHER = 3
}
interface IBaseTextFileContent extends IBaseStatWithMetadata {
/**
* The encoding of the content if known.
*/
encoding: string;
}
export interface ITextFileContent extends IBaseTextFileContent {
/**
* The content of a text file.
*/
value: string;
}
export interface ITextFileStreamContent extends IBaseTextFileContent {
/**
* The line grouped content of a text file.
*/
value: ITextBufferFactory;
}
export interface IModelLoadOrCreateOptions {
/**
* Context why the model is being loaded or created.
*/
reason?: LoadReason;
/**
* The encoding to use when resolving the model text content.
*/
encoding?: string;
/**
* If the model was already loaded before, allows to trigger
* a reload of it to fetch the latest contents:
* - async: loadOrCreate() will return immediately and trigger
* a reload that will run in the background.
* - sync: loadOrCreate() will only return resolved when the
* model has finished reloading.
*/
reload?: {
async: boolean
};
/**
* Allow to load a model even if we think it is a binary file.
*/
allowBinary?: boolean;
}
export interface ITextFileEditorModelManager {
readonly onModelDisposed: Event<URI>;
readonly onModelContentChanged: Event<TextFileModelChangeEvent>;
readonly onModelEncodingChanged: Event<TextFileModelChangeEvent>;
readonly onModelDirty: Event<TextFileModelChangeEvent>;
readonly onModelSaveError: Event<TextFileModelChangeEvent>;
readonly onModelSaved: Event<TextFileModelChangeEvent>;
readonly onModelReverted: Event<TextFileModelChangeEvent>;
readonly onModelOrphanedChanged: Event<TextFileModelChangeEvent>;
readonly onModelsDirty: Event<TextFileModelChangeEvent[]>;
readonly onModelsSaveError: Event<TextFileModelChangeEvent[]>;
readonly onModelsSaved: Event<TextFileModelChangeEvent[]>;
readonly onModelsReverted: Event<TextFileModelChangeEvent[]>;
get(resource: URI): ITextFileEditorModel | undefined;
getAll(resource?: URI): ITextFileEditorModel[];
loadOrCreate(resource: URI, options?: IModelLoadOrCreateOptions): Promise<ITextFileEditorModel>;
disposeModel(model: ITextFileEditorModel): void;
}
export interface ISaveOptions {
force?: boolean;
reason?: SaveReason;
overwriteReadonly?: boolean;
overwriteEncoding?: boolean;
skipSaveParticipants?: boolean;
writeElevated?: boolean;
}
export interface ILoadOptions {
/**
* Go to disk bypassing any cache of the model if any.
*/
forceReadFromDisk?: boolean;
/**
* Allow to load a model even if we think it is a binary file.
*/
allowBinary?: boolean;
/**
* Context why the model is being loaded.
*/
reason?: LoadReason;
}
export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport {
readonly onDidContentChange: Event<StateChange>;
readonly onDidStateChange: Event<StateChange>;
getVersionId(): number;
getResource(): URI;
hasState(state: ModelState): boolean;
getETag(): string | null;
updatePreferredEncoding(encoding: string): void;
save(options?: ISaveOptions): Promise<void>;
load(options?: ILoadOptions): Promise<ITextFileEditorModel>;
revert(soft?: boolean): Promise<void>;
createSnapshot(): ITextSnapshot | null;
isDirty(): boolean;
isResolved(): boolean;
isDisposed(): boolean;
}
export interface IResolvedTextFileEditorModel extends ITextFileEditorModel {
readonly textEditorModel: ITextModel;
createSnapshot(): ITextSnapshot;
}
export interface IWillMoveEvent {
oldResource: URI;
newResource: URI;
waitUntil(p: Promise<unknown>): void;
}
/**
* Helper method to convert a snapshot into its full string form.
*/
export function snapshotToString(snapshot: ITextSnapshot): string {
const chunks: string[] = [];
let chunk: string | null;
while (typeof (chunk = snapshot.read()) === 'string') {
chunks.push(chunk);
}
return chunks.join('');
}
export function stringToSnapshot(value: string): ITextSnapshot {
let done = false;
return {
read(): string | null {
if (!done) {
done = true;
return value;
}
return null;
}
};
}
export class TextSnapshotReadable implements VSBufferReadable {
private preambleHandled: boolean;
constructor(private snapshot: ITextSnapshot, private preamble?: string) { }
read(): VSBuffer | null {
let value = this.snapshot.read();
// Handle preamble if provided
if (!this.preambleHandled) {
this.preambleHandled = true;
if (typeof this.preamble === 'string') {
if (typeof value === 'string') {
value = this.preamble + value;
} else {
value = this.preamble;
}
}
}
if (typeof value === 'string') {
return VSBuffer.fromString(value);
}
return null;
}
}
export function toBufferOrReadable(value: string): VSBuffer;
export function toBufferOrReadable(value: ITextSnapshot): VSBufferReadable;
export function toBufferOrReadable(value: string | ITextSnapshot): VSBuffer | VSBufferReadable;
export function toBufferOrReadable(value: string | ITextSnapshot | undefined): VSBuffer | VSBufferReadable | undefined;
export function toBufferOrReadable(value: string | ITextSnapshot | undefined): VSBuffer | VSBufferReadable | undefined {
if (typeof value === 'undefined') {
return undefined;
}
if (typeof value === 'string') {
return VSBuffer.fromString(value);
}
return new TextSnapshotReadable(value);
}
export const SUPPORTED_ENCODINGS: { [encoding: string]: { labelLong: string; labelShort: string; order: number; encodeOnly?: boolean; alias?: string } } = {
utf8: {
labelLong: 'UTF-8',
labelShort: 'UTF-8',
order: 1,
alias: 'utf8bom'
},
utf8bom: {
labelLong: 'UTF-8 with BOM',
labelShort: 'UTF-8 with BOM',
encodeOnly: true,
order: 2,
alias: 'utf8'
},
utf16le: {
labelLong: 'UTF-16 LE',
labelShort: 'UTF-16 LE',
order: 3
},
utf16be: {
labelLong: 'UTF-16 BE',
labelShort: 'UTF-16 BE',
order: 4
},
windows1252: {
labelLong: 'Western (Windows 1252)',
labelShort: 'Windows 1252',
order: 5
},
iso88591: {
labelLong: 'Western (ISO 8859-1)',
labelShort: 'ISO 8859-1',
order: 6
},
iso88593: {
labelLong: 'Western (ISO 8859-3)',
labelShort: 'ISO 8859-3',
order: 7
},
iso885915: {
labelLong: 'Western (ISO 8859-15)',
labelShort: 'ISO 8859-15',
order: 8
},
macroman: {
labelLong: 'Western (Mac Roman)',
labelShort: 'Mac Roman',
order: 9
},
cp437: {
labelLong: 'DOS (CP 437)',
labelShort: 'CP437',
order: 10
},
windows1256: {
labelLong: 'Arabic (Windows 1256)',
labelShort: 'Windows 1256',
order: 11
},
iso88596: {
labelLong: 'Arabic (ISO 8859-6)',
labelShort: 'ISO 8859-6',
order: 12
},
windows1257: {
labelLong: 'Baltic (Windows 1257)',
labelShort: 'Windows 1257',
order: 13
},
iso88594: {
labelLong: 'Baltic (ISO 8859-4)',
labelShort: 'ISO 8859-4',
order: 14
},
iso885914: {
labelLong: 'Celtic (ISO 8859-14)',
labelShort: 'ISO 8859-14',
order: 15
},
windows1250: {
labelLong: 'Central European (Windows 1250)',
labelShort: 'Windows 1250',
order: 16
},
iso88592: {
labelLong: 'Central European (ISO 8859-2)',
labelShort: 'ISO 8859-2',
order: 17
},
cp852: {
labelLong: 'Central European (CP 852)',
labelShort: 'CP 852',
order: 18
},
windows1251: {
labelLong: 'Cyrillic (Windows 1251)',
labelShort: 'Windows 1251',
order: 19
},
cp866: {
labelLong: 'Cyrillic (CP 866)',
labelShort: 'CP 866',
order: 20
},
iso88595: {
labelLong: 'Cyrillic (ISO 8859-5)',
labelShort: 'ISO 8859-5',
order: 21
},
koi8r: {
labelLong: 'Cyrillic (KOI8-R)',
labelShort: 'KOI8-R',
order: 22
},
koi8u: {
labelLong: 'Cyrillic (KOI8-U)',
labelShort: 'KOI8-U',
order: 23
},
iso885913: {
labelLong: 'Estonian (ISO 8859-13)',
labelShort: 'ISO 8859-13',
order: 24
},
windows1253: {
labelLong: 'Greek (Windows 1253)',
labelShort: 'Windows 1253',
order: 25
},
iso88597: {
labelLong: 'Greek (ISO 8859-7)',
labelShort: 'ISO 8859-7',
order: 26
},
windows1255: {
labelLong: 'Hebrew (Windows 1255)',
labelShort: 'Windows 1255',
order: 27
},
iso88598: {
labelLong: 'Hebrew (ISO 8859-8)',
labelShort: 'ISO 8859-8',
order: 28
},
iso885910: {
labelLong: 'Nordic (ISO 8859-10)',
labelShort: 'ISO 8859-10',
order: 29
},
iso885916: {
labelLong: 'Romanian (ISO 8859-16)',
labelShort: 'ISO 8859-16',
order: 30
},
windows1254: {
labelLong: 'Turkish (Windows 1254)',
labelShort: 'Windows 1254',
order: 31
},
iso88599: {
labelLong: 'Turkish (ISO 8859-9)',
labelShort: 'ISO 8859-9',
order: 32
},
windows1258: {
labelLong: 'Vietnamese (Windows 1258)',
labelShort: 'Windows 1258',
order: 33
},
gbk: {
labelLong: 'Simplified Chinese (GBK)',
labelShort: 'GBK',
order: 34
},
gb18030: {
labelLong: 'Simplified Chinese (GB18030)',
labelShort: 'GB18030',
order: 35
},
cp950: {
labelLong: 'Traditional Chinese (Big5)',
labelShort: 'Big5',
order: 36
},
big5hkscs: {
labelLong: 'Traditional Chinese (Big5-HKSCS)',
labelShort: 'Big5-HKSCS',
order: 37
},
shiftjis: {
labelLong: 'Japanese (Shift JIS)',
labelShort: 'Shift JIS',
order: 38
},
eucjp: {
labelLong: 'Japanese (EUC-JP)',
labelShort: 'EUC-JP',
order: 39
},
euckr: {
labelLong: 'Korean (EUC-KR)',
labelShort: 'EUC-KR',
order: 40
},
windows874: {
labelLong: 'Thai (Windows 874)',
labelShort: 'Windows 874',
order: 41
},
iso885911: {
labelLong: 'Latin/Thai (ISO 8859-11)',
labelShort: 'ISO 8859-11',
order: 42
},
koi8ru: {
labelLong: 'Cyrillic (KOI8-RU)',
labelShort: 'KOI8-RU',
order: 43
},
koi8t: {
labelLong: 'Tajik (KOI8-T)',
labelShort: 'KOI8-T',
order: 44
},
gb2312: {
labelLong: 'Simplified Chinese (GB 2312)',
labelShort: 'GB 2312',
order: 45
},
cp865: {
labelLong: 'Nordic DOS (CP 865)',
labelShort: 'CP 865',
order: 46
},
cp850: {
labelLong: 'Western European DOS (CP 850)',
labelShort: 'CP 850',
order: 47
}
};