files2 - first cut ITextFileService.read()
This commit is contained in:
parent
cc7ec60f62
commit
66b2e1816e
11 changed files with 117 additions and 26 deletions
|
@ -33,6 +33,7 @@ import { BracketsUtils, RichEditBracket, RichEditBrackets } from 'vs/editor/comm
|
|||
import { IStringStream, ITextSnapshot } from 'vs/platform/files/common/files';
|
||||
import { ITheme, ThemeColor } from 'vs/platform/theme/common/themeService';
|
||||
import { withUndefinedAsNull } from 'vs/base/common/types';
|
||||
import { VSBufferReadableStream, VSBuffer } from 'vs/base/common/buffer';
|
||||
|
||||
const CHEAP_TOKENIZATION_LENGTH_LIMIT = 2048;
|
||||
|
||||
|
@ -46,30 +47,41 @@ export function createTextBufferFactory(text: string): model.ITextBufferFactory
|
|||
return builder.finish();
|
||||
}
|
||||
|
||||
export function createTextBufferFactoryFromStream(stream: IStringStream, filter?: (chunk: string) => string): Promise<model.ITextBufferFactory> {
|
||||
return new Promise<model.ITextBufferFactory>((c, e) => {
|
||||
let done = false;
|
||||
let builder = createTextBufferBuilder();
|
||||
export function createTextBufferFactoryFromStream(stream: IStringStream, filter?: (chunk: string) => string, validator?: (chunk: string) => Error | undefined): Promise<model.ITextBufferFactory>;
|
||||
export function createTextBufferFactoryFromStream(stream: VSBufferReadableStream, filter?: (chunk: VSBuffer) => VSBuffer, validator?: (chunk: VSBuffer) => Error | undefined): Promise<model.ITextBufferFactory>;
|
||||
export function createTextBufferFactoryFromStream(stream: IStringStream | VSBufferReadableStream, filter?: (chunk: any) => string | VSBuffer, validator?: (chunk: any) => Error | undefined): Promise<model.ITextBufferFactory> {
|
||||
return new Promise<model.ITextBufferFactory>((resolve, reject) => {
|
||||
const builder = createTextBufferBuilder();
|
||||
|
||||
let done = false;
|
||||
|
||||
stream.on('data', (chunk: string | VSBuffer) => {
|
||||
if (validator) {
|
||||
const error = validator(chunk);
|
||||
if (error) {
|
||||
done = true;
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
stream.on('data', (chunk) => {
|
||||
if (filter) {
|
||||
chunk = filter(chunk);
|
||||
}
|
||||
|
||||
builder.acceptChunk(chunk);
|
||||
builder.acceptChunk((typeof chunk === 'string') ? chunk : chunk.toString());
|
||||
});
|
||||
|
||||
stream.on('error', (error) => {
|
||||
if (!done) {
|
||||
done = true;
|
||||
e(error);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
if (!done) {
|
||||
done = true;
|
||||
c(builder.finish());
|
||||
resolve(builder.finish());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -656,8 +656,8 @@ export interface IContentData {
|
|||
* A Stream emitting strings.
|
||||
*/
|
||||
export interface IStringStream {
|
||||
on(event: 'data', callback: (chunk: string) => void): void;
|
||||
on(event: 'error', callback: (err: any) => void): void;
|
||||
on(event: 'data', callback: (data: string) => void): void;
|
||||
on(event: 'error', callback: (err: Error) => void): void;
|
||||
on(event: 'end', callback: () => void): void;
|
||||
on(event: string, callback: any): void;
|
||||
}
|
||||
|
|
|
@ -176,7 +176,7 @@ export class FileOnDiskContentProvider implements ITextModelContentProvider {
|
|||
private resolveEditorModel(resource: URI, createAsNeeded: boolean = true): Promise<ITextModel | null> {
|
||||
const savedFileResource = toLocalResource(resource, this.environmentService.configuration.remoteAuthority);
|
||||
|
||||
return this.textFileService.read(savedFileResource).then(content => {
|
||||
return this.textFileService.legacyRead(savedFileResource).then(content => {
|
||||
let codeEditorModel = this.modelService.getModel(resource);
|
||||
if (codeEditorModel) {
|
||||
this.modelService.updateModel(codeEditorModel, content.value);
|
||||
|
|
|
@ -35,7 +35,7 @@ export class WalkThroughContentProvider implements ITextModelContentProvider, IW
|
|||
reject(err);
|
||||
}
|
||||
});
|
||||
}) : this.textFileService.read(URI.file(resource.fsPath)).then(content => content.value));
|
||||
}) : this.textFileService.legacyRead(URI.file(resource.fsPath)).then(content => content.value));
|
||||
return content.then(content => {
|
||||
let codeEditorModel = this.modelService.getModel(resource);
|
||||
if (!codeEditorModel) {
|
||||
|
@ -61,7 +61,7 @@ export class WalkThroughSnippetContentProvider implements ITextModelContentProvi
|
|||
}
|
||||
|
||||
public provideTextContent(resource: URI): Promise<ITextModel> {
|
||||
return this.textFileService.read(URI.file(resource.fsPath)).then(content => {
|
||||
return this.textFileService.legacyRead(URI.file(resource.fsPath)).then(content => {
|
||||
let codeEditorModel = this.modelService.getModel(resource);
|
||||
if (!codeEditorModel) {
|
||||
const j = parseInt(resource.fragment);
|
||||
|
|
|
@ -306,7 +306,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
|||
|
||||
// Resolve Content
|
||||
try {
|
||||
const content = await this.textFileService.read(this.resource, { acceptTextOnly: !allowBinary, etag, encoding: this.preferredEncoding });
|
||||
const content = await this.textFileService.legacyRead(this.resource, { acceptTextOnly: !allowBinary, etag, encoding: this.preferredEncoding });
|
||||
|
||||
// Clear orphaned state when loading was successful
|
||||
this.setOrphaned(false);
|
||||
|
|
|
@ -37,6 +37,7 @@ 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';
|
||||
|
||||
/**
|
||||
* The workbench file service implementation implements the raw file service spec and adds additional methods on top.
|
||||
|
@ -366,9 +367,36 @@ export abstract class TextFileService extends Disposable implements ITextFileSer
|
|||
|
||||
//#endregion
|
||||
|
||||
//#region primitives (resolve, create, move, delete, update)
|
||||
//#region primitives (read, create, move, delete, update)
|
||||
|
||||
async read(resource: URI, options?: IReadTextFileOptions): Promise<ITextFileContent> {
|
||||
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
|
||||
let checkedForBinary = false;
|
||||
const throwOnBinary = (data: VSBuffer): Error | undefined => {
|
||||
if (!checkedForBinary) {
|
||||
checkedForBinary = true;
|
||||
|
||||
for (let i = 0; i < data.byteLength && i < 512; i++) {
|
||||
if (data.readUint8(i) === 0) {
|
||||
throw new FileOperationError(nls.localize('fileBinaryError', "File seems to be binary and cannot be opened as text"), FileOperationResult.FILE_IS_BINARY, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
return {
|
||||
...stream,
|
||||
encoding: 'utf8',
|
||||
value: await createTextBufferFactoryFromStream(stream.value, undefined, options && options.acceptTextOnly ? throwOnBinary : undefined)
|
||||
};
|
||||
}
|
||||
|
||||
async legacyRead(resource: URI, options?: IReadTextFileOptions): Promise<ITextFileContent> {
|
||||
const streamContent = await this.fileService.resolveStreamContent(resource, options);
|
||||
const value = await createTextBufferFactoryFromStream(streamContent.value);
|
||||
|
||||
|
|
|
@ -100,6 +100,11 @@ export interface ITextFileService extends IDisposable {
|
|||
*/
|
||||
create(resource: URI, contents?: string | ITextSnapshot, options?: { overwrite?: boolean }): Promise<IFileStatWithMetadata>;
|
||||
|
||||
/**
|
||||
* @deprecated use read() instead
|
||||
*/
|
||||
legacyRead(resource: URI, options?: IReadTextFileOptions): Promise<ITextFileContent>;
|
||||
|
||||
/**
|
||||
* Read the contents of a file identified by the resource.
|
||||
*/
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import { tmpdir } from 'os';
|
||||
import { localize } from 'vs/nls';
|
||||
import { TextFileService } from 'vs/workbench/services/textfile/common/textFileService';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ITextFileService, ITextFileContent } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ITextSnapshot, IWriteTextFileOptions, IFileStatWithMetadata, IResourceEncoding, IReadTextFileOptions, stringToSnapshot, ICreateFileOptions, FileOperationError, FileOperationResult, IResourceEncodings } from 'vs/platform/files/common/files';
|
||||
|
@ -37,6 +37,10 @@ export class NodeTextFileService extends TextFileService {
|
|||
return this._encoding;
|
||||
}
|
||||
|
||||
async read(resource: URI, options?: IReadTextFileOptions): Promise<ITextFileContent> {
|
||||
return super.read(resource, options);
|
||||
}
|
||||
|
||||
protected async doCreate(resource: URI, value?: string, options?: ICreateFileOptions): Promise<IFileStatWithMetadata> {
|
||||
|
||||
// check for encoding
|
||||
|
|
BIN
src/vs/workbench/services/textfile/test/fixtures/binary.txt
vendored
Normal file
BIN
src/vs/workbench/services/textfile/test/fixtures/binary.txt
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 274 B |
|
@ -9,7 +9,7 @@ import { workbenchInstantiationService, TestLifecycleService, TestTextFileServic
|
|||
import { IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||
import { IFileService, ITextSnapshot, snapshotToString } from 'vs/platform/files/common/files';
|
||||
import { IFileService, ITextSnapshot, snapshotToString, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
|
||||
import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
|
@ -24,7 +24,7 @@ import { getRandomTestPath } from 'vs/base/test/node/testUtils';
|
|||
import { tmpdir } from 'os';
|
||||
import { DiskFileSystemProvider } from 'vs/workbench/services/files2/node/diskFileSystemProvider';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { join, basename } from 'vs/base/common/path';
|
||||
import { getPathFromAmdModule } from 'vs/base/common/amd';
|
||||
import { detectEncodingByBOM, UTF16be, UTF16le, UTF8_with_bom, UTF8 } from 'vs/base/node/encoding';
|
||||
import { NodeTextFileService, EncodingOracle, IEncodingOverride } from 'vs/workbench/services/textfile/node/textFileService';
|
||||
|
@ -32,6 +32,7 @@ import { LegacyFileService } from 'vs/workbench/services/files/node/fileService'
|
|||
import { DefaultEndOfLine } from 'vs/editor/common/model';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { readFileSync, statSync } from 'fs';
|
||||
|
||||
class ServiceAccessor {
|
||||
constructor(
|
||||
|
@ -246,7 +247,7 @@ suite('Files - TextFileService i/o', () => {
|
|||
const detectedEncoding = await detectEncodingByBOM(resource.fsPath);
|
||||
assert.equal(detectedEncoding, encoding);
|
||||
|
||||
const resolved = await service.read(resource);
|
||||
const resolved = await service.legacyRead(resource);
|
||||
assert.equal(resolved.encoding, encoding);
|
||||
|
||||
assert.equal(snapshotToString(resolved.value.create(isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).createSnapshot(false)), expectedContent);
|
||||
|
@ -273,18 +274,18 @@ suite('Files - TextFileService i/o', () => {
|
|||
});
|
||||
|
||||
async function testEncodingKeepsData(resource: URI, encoding: string, expected: string) {
|
||||
let resolved = await service.read(resource, { encoding });
|
||||
let resolved = await service.legacyRead(resource, { encoding });
|
||||
const content = snapshotToString(resolved.value.create(isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).createSnapshot(false));
|
||||
assert.equal(content, expected);
|
||||
|
||||
await service.write(resource, content, { encoding });
|
||||
|
||||
resolved = await service.read(resource, { encoding });
|
||||
resolved = await service.legacyRead(resource, { encoding });
|
||||
assert.equal(snapshotToString(resolved.value.create(DefaultEndOfLine.CRLF).createSnapshot(false)), content);
|
||||
|
||||
await service.write(resource, TextModel.createFromString(content).createSnapshot(), { encoding });
|
||||
|
||||
resolved = await service.read(resource, { encoding });
|
||||
resolved = await service.legacyRead(resource, { encoding });
|
||||
assert.equal(snapshotToString(resolved.value.create(DefaultEndOfLine.CRLF).createSnapshot(false)), content);
|
||||
}
|
||||
|
||||
|
@ -295,7 +296,7 @@ suite('Files - TextFileService i/o', () => {
|
|||
|
||||
await service.write(resource, content);
|
||||
|
||||
const resolved = await service.read(resource);
|
||||
const resolved = await service.legacyRead(resource);
|
||||
assert.equal(snapshotToString(resolved.value.create(isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).createSnapshot(false)), content);
|
||||
});
|
||||
|
||||
|
@ -306,14 +307,14 @@ suite('Files - TextFileService i/o', () => {
|
|||
|
||||
await service.write(resource, TextModel.createFromString(content).createSnapshot());
|
||||
|
||||
const resolved = await service.read(resource);
|
||||
const resolved = await service.legacyRead(resource);
|
||||
assert.equal(snapshotToString(resolved.value.create(isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).createSnapshot(false)), content);
|
||||
});
|
||||
|
||||
test('write - encoding preserved (UTF 16 LE) - content as string', async () => {
|
||||
const resource = URI.file(join(testDir, 'some_utf16le.css'));
|
||||
|
||||
const resolved = await service.read(resource);
|
||||
const resolved = await service.legacyRead(resource);
|
||||
assert.equal(resolved.encoding, UTF16le);
|
||||
|
||||
await testEncoding(URI.file(join(testDir, 'some_utf16le.css')), UTF16le, 'Hello\nWorld', 'Hello\nWorld');
|
||||
|
@ -322,7 +323,7 @@ suite('Files - TextFileService i/o', () => {
|
|||
test('write - encoding preserved (UTF 16 LE) - content as snapshot', async () => {
|
||||
const resource = URI.file(join(testDir, 'some_utf16le.css'));
|
||||
|
||||
const resolved = await service.read(resource);
|
||||
const resolved = await service.legacyRead(resource);
|
||||
assert.equal(resolved.encoding, UTF16le);
|
||||
|
||||
await testEncoding(URI.file(join(testDir, 'some_utf16le.css')), UTF16le, TextModel.createFromString('Hello\nWorld').createSnapshot(), 'Hello\nWorld');
|
||||
|
@ -412,4 +413,41 @@ suite('Files - TextFileService i/o', () => {
|
|||
let detectedEncoding = await detectEncodingByBOM(resource.fsPath);
|
||||
assert.equal(detectedEncoding, UTF8);
|
||||
});
|
||||
|
||||
test('read - small text', async () => {
|
||||
const resource = URI.file(join(testDir, 'small.txt'));
|
||||
|
||||
await testReadFile(resource);
|
||||
});
|
||||
|
||||
test('read - large text', async () => {
|
||||
const resource = URI.file(join(testDir, 'lorem.txt'));
|
||||
|
||||
await testReadFile(resource);
|
||||
});
|
||||
|
||||
async function testReadFile(resource: URI): Promise<void> {
|
||||
const result = await service.read(resource);
|
||||
assert.equal(result.name, basename(resource.fsPath));
|
||||
assert.equal(result.size, statSync(resource.fsPath).size);
|
||||
|
||||
assert.equal(snapshotToString(result.value.create(DefaultEndOfLine.LF).createSnapshot(false)), readFileSync(resource.fsPath));
|
||||
}
|
||||
|
||||
test('read - FILE_IS_BINARY', async () => {
|
||||
const resource = URI.file(join(testDir, 'binary.txt'));
|
||||
|
||||
let error: FileOperationError | undefined = undefined;
|
||||
try {
|
||||
await service.read(resource, { acceptTextOnly: true });
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
||||
assert.ok(error);
|
||||
assert.equal(error!.fileOperationResult, FileOperationResult.FILE_IS_BINARY);
|
||||
|
||||
const result = await service.read(URI.file(join(testDir, 'small.txt')), { acceptTextOnly: true });
|
||||
assert.equal(result.name, 'small.txt');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -235,6 +235,10 @@ export class TestTextFileService extends BrowserTextFileService {
|
|||
this.resolveTextContentError = error;
|
||||
}
|
||||
|
||||
public legacyRead(resource: URI, options?: IReadTextFileOptions): Promise<ITextFileContent> {
|
||||
return this.read(resource, options);
|
||||
}
|
||||
|
||||
public read(resource: URI, options?: IReadTextFileOptions): Promise<ITextFileContent> {
|
||||
if (this.resolveTextContentError) {
|
||||
const error = this.resolveTextContentError;
|
||||
|
|
Loading…
Reference in a new issue