-
Notifications
You must be signed in to change notification settings - Fork 28k
/
textfiles.ts
570 lines (453 loc) · 16.3 KB
/
textfiles.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { ISaveOptions, IRevertOptions, SaveReason } from 'vs/workbench/common/editor';
import { ReadableStream } from 'vs/base/common/stream';
import { IBaseFileStatWithMetadata, IFileStatWithMetadata, IWriteFileOptions, FileOperationError, FileOperationResult, IReadFileStreamOptions } 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, VSBufferReadableStream } from 'vs/base/common/buffer';
import { areFunctions, isUndefinedOrNull } from 'vs/base/common/types';
import { IWorkingCopy, IWorkingCopySaveEvent } from 'vs/workbench/services/workingCopy/common/workingCopy';
import { IUntitledTextEditorModelManager } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress';
import { IFileOperationUndoRedoInfo } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
export const ITextFileService = createDecorator<ITextFileService>('textFileService');
export interface ITextFileService extends IDisposable {
readonly _serviceBrand: undefined;
/**
* 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;
/**
* 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?: ITextFileSaveAsOptions): 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<void>;
/**
* 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 files. If the file exists it will be overwritten with the contents if
* the options enable to overwrite.
*/
create(operations: { resource: URI; value?: string | ITextSnapshot; options?: { overwrite?: boolean } }[], undoInfo?: IFileOperationUndoRedoInfo): Promise<readonly IFileStatWithMetadata[]>;
/**
* Returns the readable that uses the appropriate encoding. This method should
* be used whenever a `string` or `ITextSnapshot` is being persisted to the
* file system.
*/
getEncodedReadable(resource: URI, value: ITextSnapshot, options?: IWriteTextFileOptions): Promise<VSBufferReadable>;
getEncodedReadable(resource: URI, value: string, options?: IWriteTextFileOptions): Promise<VSBuffer>;
getEncodedReadable(resource: URI, value?: ITextSnapshot, options?: IWriteTextFileOptions): Promise<VSBufferReadable | undefined>;
getEncodedReadable(resource: URI, value?: string, options?: IWriteTextFileOptions): Promise<VSBuffer | undefined>;
getEncodedReadable(resource: URI, value?: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise<VSBuffer | VSBufferReadable | undefined>;
/**
* Returns a stream of strings that uses the appropriate encoding. This method should
* be used whenever a `VSBufferReadableStream` is being loaded from the file system.
*
* Will throw an error if `acceptTextOnly: true` for resources that seem to be binary.
*/
getDecodedStream(resource: URI, value: VSBufferReadableStream, options?: IReadTextFileEncodingOptions): Promise<ReadableStream<string>>;
}
export interface IReadTextFileEncodingOptions {
/**
* The optional encoding parameter allows to specify the desired encoding when resolving
* the contents of the file.
*/
readonly encoding?: string;
/**
* The optional guessEncoding parameter allows to guess encoding from content of the file.
*/
readonly autoGuessEncoding?: boolean;
/**
* The optional acceptTextOnly parameter allows to fail this request early if the file
* contents are not textual.
*/
readonly acceptTextOnly?: boolean;
}
export interface IReadTextFileOptions extends IReadTextFileEncodingOptions, IReadFileStreamOptions { }
export interface IWriteTextFileOptions extends IWriteFileOptions {
/**
* The encoding to use when updating a file.
*/
readonly encoding?: string;
/**
* 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.
*/
readonly writeElevated?: boolean;
}
export const enum TextFileOperationResult {
FILE_IS_BINARY
}
export class TextFileOperationError extends FileOperationError {
static isTextFileOperationError(obj: unknown): obj is TextFileOperationError {
return obj instanceof Error && !isUndefinedOrNull((obj as TextFileOperationError).textFileOperationResult);
}
override readonly options?: IReadTextFileOptions & IWriteTextFileOptions;
constructor(
message: string,
public textFileOperationResult: TextFileOperationResult,
options?: IReadTextFileOptions & IWriteTextFileOptions
) {
super(message, FileOperationResult.FILE_OTHER_ERROR);
this.options = options;
}
}
export interface IResourceEncodings {
getPreferredReadEncoding(resource: URI): Promise<IResourceEncoding>;
getPreferredWriteEncoding(resource: URI, preferredEncoding?: string): Promise<IResourceEncoding>;
}
export interface IResourceEncoding {
readonly encoding: string;
readonly 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;
}
/**
* States the text file editor model can be in.
*/
export const enum TextFileEditorModelState {
/**
* 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 const enum TextFileResolveReason {
EDITOR = 1,
REFERENCE = 2,
OTHER = 3
}
interface IBaseTextFileContent extends IBaseFileStatWithMetadata {
/**
* The encoding of the content if known.
*/
readonly encoding: string;
}
export interface ITextFileContent extends IBaseTextFileContent {
/**
* The content of a text file.
*/
readonly value: string;
}
export interface ITextFileStreamContent extends IBaseTextFileContent {
/**
* The line grouped content of a text file.
*/
readonly value: ITextBufferFactory;
}
export interface ITextFileEditorModelResolveOrCreateOptions extends ITextFileResolveOptions {
/**
* The language id to use for the model text content.
*/
readonly languageId?: string;
/**
* The encoding to use when resolving the model text content.
*/
readonly encoding?: string;
/**
* If the model was already resolved before, allows to trigger
* a reload of it to fetch the latest contents.
*/
readonly reload?: {
/**
* Controls whether the reload happens in the background
* or whether `resolve` will await the reload to happen.
*/
readonly async: boolean;
};
}
export interface ITextFileSaveEvent extends ITextFileEditorModelSaveEvent {
/**
* The model that was saved.
*/
readonly model: ITextFileEditorModel;
}
export interface ITextFileResolveEvent {
/**
* The model that was resolved.
*/
readonly model: ITextFileEditorModel;
/**
* The reason why the model was resolved.
*/
readonly reason: TextFileResolveReason;
}
export interface ITextFileSaveParticipant {
/**
* Participate in a save of a model. Allows to change the model
* before it is being saved to disk.
*/
participate(
model: ITextFileEditorModel,
context: { reason: SaveReason },
progress: IProgress<IProgressStep>,
token: CancellationToken
): Promise<void>;
}
export interface ITextFileEditorModelManager {
readonly onDidCreate: Event<ITextFileEditorModel>;
readonly onDidResolve: Event<ITextFileResolveEvent>;
readonly onDidChangeDirty: Event<ITextFileEditorModel>;
readonly onDidChangeReadonly: Event<ITextFileEditorModel>;
readonly onDidRemove: Event<URI>;
readonly onDidChangeOrphaned: Event<ITextFileEditorModel>;
readonly onDidChangeEncoding: Event<ITextFileEditorModel>;
readonly onDidSaveError: Event<ITextFileEditorModel>;
readonly onDidSave: Event<ITextFileSaveEvent>;
readonly onDidRevert: Event<ITextFileEditorModel>;
/**
* Access to all text file editor models in memory.
*/
readonly models: ITextFileEditorModel[];
/**
* Allows to configure the error handler that is called on save errors.
*/
saveErrorHandler: ISaveErrorHandler;
/**
* Returns the text file editor model for the provided resource
* or undefined if none.
*/
get(resource: URI): ITextFileEditorModel | undefined;
/**
* Allows to resolve a text file model from disk.
*/
resolve(resource: URI, options?: ITextFileEditorModelResolveOrCreateOptions): Promise<ITextFileEditorModel>;
/**
* Adds a participant for saving text file models.
*/
addSaveParticipant(participant: ITextFileSaveParticipant): IDisposable;
/**
* Runs the registered save participants on the provided model.
*/
runSaveParticipants(model: ITextFileEditorModel, context: { reason: SaveReason }, token: CancellationToken): Promise<void>;
/**
* Waits for the model to be ready to be disposed. There may be conditions
* under which the model cannot be disposed, e.g. when it is dirty. Once the
* promise is settled, it is safe to dispose the model.
*/
canDispose(model: ITextFileEditorModel): true | Promise<true>;
}
export interface ITextFileSaveOptions extends ISaveOptions {
/**
* Save the file with an attempt to unlock it.
*/
readonly writeUnlock?: boolean;
/**
* Save the file with elevated privileges.
*
* Note: This may not be supported in all environments.
*/
readonly writeElevated?: boolean;
/**
* Allows to write to a file even if it has been modified on disk.
*/
readonly ignoreModifiedSince?: boolean;
/**
* If set, will bubble up the error to the caller instead of handling it.
*/
readonly ignoreErrorHandler?: boolean;
}
export interface ITextFileSaveAsOptions extends ITextFileSaveOptions {
/**
* Optional URI to use as suggested file path to save as.
*/
readonly suggestedTarget?: URI;
}
export interface ITextFileResolveOptions {
/**
* The contents to use for the model if known. If not
* provided, the contents will be retrieved from the
* underlying resource or backup if present.
*/
readonly contents?: ITextBufferFactory;
/**
* Go to file bypassing any cache of the model if any.
*/
readonly forceReadFromFile?: boolean;
/**
* Allow to resolve a model even if we think it is a binary file.
*/
readonly allowBinary?: boolean;
/**
* Context why the model is being resolved.
*/
readonly reason?: TextFileResolveReason;
}
export const enum EncodingMode {
/**
* Instructs the encoding support to encode the object with the provided encoding
*/
Encode,
/**
* Instructs the encoding support to decode the object with the provided encoding
*/
Decode
}
export interface IEncodingSupport {
/**
* Gets the encoding of the object if known.
*/
getEncoding(): string | undefined;
/**
* Sets the encoding for the object for saving.
*/
setEncoding(encoding: string, mode: EncodingMode): Promise<void>;
}
export interface ILanguageSupport {
/**
* Sets the language id of the object.
*/
setLanguageId(languageId: string, setExplicitly?: boolean): void;
}
export interface ITextFileEditorModelSaveEvent extends IWorkingCopySaveEvent {
/**
* The resolved stat from the save operation.
*/
readonly stat: IFileStatWithMetadata;
}
export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport, ILanguageSupport, IWorkingCopy {
readonly onDidSave: Event<ITextFileEditorModelSaveEvent>;
readonly onDidSaveError: Event<void>;
readonly onDidChangeOrphaned: Event<void>;
readonly onDidChangeReadonly: Event<void>;
readonly onDidChangeEncoding: Event<void>;
hasState(state: TextFileEditorModelState): boolean;
joinState(state: TextFileEditorModelState.PENDING_SAVE): Promise<void>;
updatePreferredEncoding(encoding: string | undefined): void;
save(options?: ITextFileSaveOptions): Promise<boolean>;
revert(options?: IRevertOptions): Promise<void>;
resolve(options?: ITextFileResolveOptions): Promise<void>;
isDirty(): this is IResolvedTextFileEditorModel;
getLanguageId(): string | undefined;
isResolved(): this is IResolvedTextFileEditorModel;
}
export function isTextFileEditorModel(model: ITextEditorModel): model is ITextFileEditorModel {
const candidate = model as ITextFileEditorModel;
return areFunctions(candidate.setEncoding, candidate.getEncoding, candidate.save, candidate.revert, candidate.isDirty, candidate.getLanguageId);
}
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 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 {
read: () => {
const chunk = value.read();
if (typeof chunk === 'string') {
return VSBuffer.fromString(chunk);
}
return null;
}
};
}