Files
azuredatastudio/src/vs/workbench/services/textfile/common/textfiles.ts
Anthony Dresser 71375808b9 Merge from vscode 4c9161a3f125f5a788aafa73a4ecfcb27dcfc319 (#9143)
* Merge from vscode 4c9161a3f125f5a788aafa73a4ecfcb27dcfc319

* minor spacing fix
2020-02-13 21:20:36 -06:00

768 lines
19 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { Event, IWaitUntil } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IEncodingSupport, IModeSupport, ISaveOptions, IRevertOptions, SaveReason } from 'vs/workbench/common/editor';
import { IBaseStatWithMetadata, IFileStatWithMetadata, IReadFileOptions, IWriteFileOptions, FileOperationError, FileOperationResult, FileOperation } from 'vs/platform/files/common/files';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ITextEditorModel } from 'vs/editor/common/services/resolverService';
import { ITextBufferFactory, ITextModel, ITextSnapshot } from 'vs/editor/common/model';
import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { isNative } from 'vs/base/common/platform';
import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { IUntitledTextEditorModelManager } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
import { CancellationToken } from 'vs/base/common/cancellation';
export const ITextFileService = createDecorator<ITextFileService>('textFileService');
export interface ITextFileService extends IDisposable {
_serviceBrand: undefined;
/**
* An event that is fired before attempting a certain file operation.
*/
readonly onWillRunOperation: Event<FileOperationWillRunEvent>;
/**
* An event that is fired after a file operation has been performed.
*/
readonly onDidRunOperation: Event<FileOperationDidRunEvent>;
/**
* Access to the manager of text file editor models providing further
* methods to work with them.
*/
readonly files: ITextFileEditorModelManager;
/**
* Access to the manager of untitled text editor models providing further
* methods to work with them.
*/
readonly untitled: IUntitledTextEditorModelManager;
/**
* Helper to determine encoding for resources.
*/
readonly encoding: IResourceEncodings;
/**
* The handler that should be called when saving fails. Can be overridden
* to handle save errors in a custom way.
*/
saveErrorHandler: ISaveErrorHandler;
/**
* The save participant if any. By default, no save participant is registered.
*/
saveParticipant: ISaveParticipant | undefined;
/**
* A resource is dirty if it has unsaved changes or is an untitled file not yet saved.
*
* @param resource the resource to check for being dirty
*/
isDirty(resource: URI): boolean;
/**
* Saves the resource.
*
* @param resource the resource to save
* @param options optional save options
* @return Path of the saved resource or undefined if canceled.
*/
save(resource: URI, options?: ITextFileSaveOptions): Promise<URI | undefined>;
/**
* Saves the provided resource asking the user for a file name or using the provided one.
*
* @param resource the resource to save as.
* @param targetResource the optional target to save to.
* @param options optional save options
* @return Path of the saved resource or undefined if canceled.
*/
saveAs(resource: URI, targetResource?: URI, options?: ITextFileSaveOptions): Promise<URI | undefined>;
/**
* Reverts the provided resource.
*
* @param resource the resource of the file to revert.
* @param force to force revert even when the file is not dirty
*/
revert(resource: URI, options?: IRevertOptions): Promise<boolean>;
/**
* Read the contents of a file identified by the resource.
*/
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.
*/
write(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise<IFileStatWithMetadata>;
/**
* Create a file. If the file exists it will be overwritten with the contents if
* the options enable to overwrite.
*/
create(resource: URI, contents?: string | ITextSnapshot, options?: { overwrite?: boolean }): Promise<IFileStatWithMetadata>;
/**
* Move a file. If the file is dirty, its contents will be preserved and restored.
*/
move(source: URI, target: URI, overwrite?: boolean): Promise<IFileStatWithMetadata>;
/**
* Copy a file. If the file is dirty, its contents will be preserved and restored.
*/
copy(source: URI, target: URI, overwrite?: boolean): Promise<IFileStatWithMetadata>;
/**
* Delete a file. If the file is dirty, it will get reverted and then deleted from disk.
*/
delete(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise<void>;
}
export interface FileOperationWillRunEvent extends IWaitUntil {
operation: FileOperation;
target: URI;
source?: URI;
}
export class FileOperationDidRunEvent {
constructor(
readonly operation: FileOperation,
readonly target: URI,
readonly source?: URI | undefined
) { }
}
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;
/**
* Whether 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, context: { reason: SaveReason }, token: CancellationToken): 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 currently being saved but this operation has not completed yet.
*/
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 dirty.
*/
ERROR
}
export interface ITextFileOperationResult {
results: IResult[];
}
export interface IResult {
source: URI;
target?: URI;
error?: boolean;
}
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 language mode to use for the model text content.
*/
mode?: string;
/**
* 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: resolve() will return immediately and trigger
* a reload that will run in the background.
* - sync: resolve() 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 ITextFileModelSaveEvent {
model: ITextFileEditorModel;
reason: SaveReason;
}
export interface ITextFileModelLoadEvent {
model: ITextFileEditorModel;
reason: LoadReason;
}
export interface ITextFileEditorModelManager {
readonly onDidLoad: Event<ITextFileModelLoadEvent>;
readonly onDidChangeDirty: Event<ITextFileEditorModel>;
readonly onDidSaveError: Event<ITextFileEditorModel>;
readonly onDidSave: Event<ITextFileModelSaveEvent>;
readonly onDidRevert: Event<ITextFileEditorModel>;
readonly onDidChangeEncoding: Event<ITextFileEditorModel>;
readonly onDidChangeOrphaned: Event<ITextFileEditorModel>;
get(resource: URI): ITextFileEditorModel | undefined;
getAll(): ITextFileEditorModel[];
resolve(resource: URI, options?: IModelLoadOrCreateOptions): Promise<ITextFileEditorModel>;
disposeModel(model: ITextFileEditorModel): void;
}
export interface ITextFileSaveOptions extends ISaveOptions {
overwriteReadonly?: boolean;
overwriteEncoding?: boolean;
writeElevated?: boolean;
ignoreModifiedSince?: boolean;
ignoreErrorHandler?: 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, IModeSupport, IWorkingCopy {
readonly onDidChangeContent: Event<void>;
readonly onDidLoad: Event<LoadReason>;
readonly onDidSaveError: Event<void>;
readonly onDidSave: Event<SaveReason>;
readonly onDidRevert: Event<void>;
readonly onDidChangeEncoding: Event<void>;
readonly onDidChangeOrphaned: Event<void>;
hasState(state: ModelState): boolean;
updatePreferredEncoding(encoding: string | undefined): void;
save(options?: ITextFileSaveOptions): Promise<boolean>;
load(options?: ILoadOptions): Promise<ITextFileEditorModel>;
revert(options?: IRevertOptions): Promise<boolean>;
isDirty(): boolean; // {{SQL CARBON EDIT}} strict-null-check
setDirty(dirty: boolean): void;
getMode(): string | undefined;
isResolved(): this is IResolvedTextFileEditorModel;
isDisposed(): boolean;
}
export interface IResolvedTextFileEditorModel extends ITextFileEditorModel {
readonly textEditorModel: ITextModel;
createSnapshot(): ITextSnapshot;
}
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 = false;
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 } } =
// Desktop
isNative ?
{
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
}
} :
// Web (https://github.com/microsoft/vscode/issues/79275)
{
utf8: {
labelLong: 'UTF-8',
labelShort: 'UTF-8',
order: 1,
alias: 'utf8bom'
}
};