提交 66b2e181 编写于 作者: B Benjamin Pasero

files2 - first cut ITextFileService.read()

上级 cc7ec60f
......@@ -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) => {
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;
let builder = createTextBufferBuilder();
stream.on('data', (chunk) => {
stream.on('data', (chunk: string | VSBuffer) => {
if (validator) {
const error = validator(chunk);
if (error) {
done = true;
reject(error);
}
}
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
......
......@@ -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;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册