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

files2 - first cut ITextFileService.read()

上级 cc7ec60f
...@@ -33,6 +33,7 @@ import { BracketsUtils, RichEditBracket, RichEditBrackets } from 'vs/editor/comm ...@@ -33,6 +33,7 @@ import { BracketsUtils, RichEditBracket, RichEditBrackets } from 'vs/editor/comm
import { IStringStream, ITextSnapshot } from 'vs/platform/files/common/files'; import { IStringStream, ITextSnapshot } from 'vs/platform/files/common/files';
import { ITheme, ThemeColor } from 'vs/platform/theme/common/themeService'; import { ITheme, ThemeColor } from 'vs/platform/theme/common/themeService';
import { withUndefinedAsNull } from 'vs/base/common/types'; import { withUndefinedAsNull } from 'vs/base/common/types';
import { VSBufferReadableStream, VSBuffer } from 'vs/base/common/buffer';
const CHEAP_TOKENIZATION_LENGTH_LIMIT = 2048; const CHEAP_TOKENIZATION_LENGTH_LIMIT = 2048;
...@@ -46,30 +47,41 @@ export function createTextBufferFactory(text: string): model.ITextBufferFactory ...@@ -46,30 +47,41 @@ export function createTextBufferFactory(text: string): model.ITextBufferFactory
return builder.finish(); return builder.finish();
} }
export function createTextBufferFactoryFromStream(stream: IStringStream, filter?: (chunk: string) => string): Promise<model.ITextBufferFactory> { export function createTextBufferFactoryFromStream(stream: IStringStream, filter?: (chunk: string) => string, validator?: (chunk: string) => Error | undefined): Promise<model.ITextBufferFactory>;
return new Promise<model.ITextBufferFactory>((c, e) => { 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 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) { if (filter) {
chunk = filter(chunk); chunk = filter(chunk);
} }
builder.acceptChunk(chunk); builder.acceptChunk((typeof chunk === 'string') ? chunk : chunk.toString());
}); });
stream.on('error', (error) => { stream.on('error', (error) => {
if (!done) { if (!done) {
done = true; done = true;
e(error); reject(error);
} }
}); });
stream.on('end', () => { stream.on('end', () => {
if (!done) { if (!done) {
done = true; done = true;
c(builder.finish()); resolve(builder.finish());
} }
}); });
}); });
......
...@@ -656,8 +656,8 @@ export interface IContentData { ...@@ -656,8 +656,8 @@ export interface IContentData {
* A Stream emitting strings. * A Stream emitting strings.
*/ */
export interface IStringStream { export interface IStringStream {
on(event: 'data', callback: (chunk: string) => void): void; on(event: 'data', callback: (data: string) => void): void;
on(event: 'error', callback: (err: any) => void): void; on(event: 'error', callback: (err: Error) => void): void;
on(event: 'end', callback: () => void): void; on(event: 'end', callback: () => void): void;
on(event: string, callback: any): void; on(event: string, callback: any): void;
} }
......
...@@ -176,7 +176,7 @@ export class FileOnDiskContentProvider implements ITextModelContentProvider { ...@@ -176,7 +176,7 @@ export class FileOnDiskContentProvider implements ITextModelContentProvider {
private resolveEditorModel(resource: URI, createAsNeeded: boolean = true): Promise<ITextModel | null> { private resolveEditorModel(resource: URI, createAsNeeded: boolean = true): Promise<ITextModel | null> {
const savedFileResource = toLocalResource(resource, this.environmentService.configuration.remoteAuthority); 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); let codeEditorModel = this.modelService.getModel(resource);
if (codeEditorModel) { if (codeEditorModel) {
this.modelService.updateModel(codeEditorModel, content.value); this.modelService.updateModel(codeEditorModel, content.value);
......
...@@ -35,7 +35,7 @@ export class WalkThroughContentProvider implements ITextModelContentProvider, IW ...@@ -35,7 +35,7 @@ export class WalkThroughContentProvider implements ITextModelContentProvider, IW
reject(err); 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 => { return content.then(content => {
let codeEditorModel = this.modelService.getModel(resource); let codeEditorModel = this.modelService.getModel(resource);
if (!codeEditorModel) { if (!codeEditorModel) {
...@@ -61,7 +61,7 @@ export class WalkThroughSnippetContentProvider implements ITextModelContentProvi ...@@ -61,7 +61,7 @@ export class WalkThroughSnippetContentProvider implements ITextModelContentProvi
} }
public provideTextContent(resource: URI): Promise<ITextModel> { 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); let codeEditorModel = this.modelService.getModel(resource);
if (!codeEditorModel) { if (!codeEditorModel) {
const j = parseInt(resource.fragment); const j = parseInt(resource.fragment);
......
...@@ -306,7 +306,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil ...@@ -306,7 +306,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
// Resolve Content // Resolve Content
try { 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 // Clear orphaned state when loading was successful
this.setOrphaned(false); this.setOrphaned(false);
......
...@@ -37,6 +37,7 @@ import { IModeService } from 'vs/editor/common/services/modeService'; ...@@ -37,6 +37,7 @@ import { IModeService } from 'vs/editor/common/services/modeService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { coalesce } from 'vs/base/common/arrays'; import { coalesce } from 'vs/base/common/arrays';
import { trim } from 'vs/base/common/strings'; 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. * 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 ...@@ -366,9 +367,36 @@ export abstract class TextFileService extends Disposable implements ITextFileSer
//#endregion //#endregion
//#region primitives (resolve, create, move, delete, update) //#region primitives (read, create, move, delete, update)
async read(resource: URI, options?: IReadTextFileOptions): Promise<ITextFileContent> { 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 streamContent = await this.fileService.resolveStreamContent(resource, options);
const value = await createTextBufferFactoryFromStream(streamContent.value); const value = await createTextBufferFactoryFromStream(streamContent.value);
......
...@@ -100,6 +100,11 @@ export interface ITextFileService extends IDisposable { ...@@ -100,6 +100,11 @@ export interface ITextFileService extends IDisposable {
*/ */
create(resource: URI, contents?: string | ITextSnapshot, options?: { overwrite?: boolean }): Promise<IFileStatWithMetadata>; 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. * Read the contents of a file identified by the resource.
*/ */
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
import { tmpdir } from 'os'; import { tmpdir } from 'os';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { TextFileService } from 'vs/workbench/services/textfile/common/textFileService'; 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 { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { ITextSnapshot, IWriteTextFileOptions, IFileStatWithMetadata, IResourceEncoding, IReadTextFileOptions, stringToSnapshot, ICreateFileOptions, FileOperationError, FileOperationResult, IResourceEncodings } from 'vs/platform/files/common/files'; 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 { ...@@ -37,6 +37,10 @@ export class NodeTextFileService extends TextFileService {
return this._encoding; 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> { protected async doCreate(resource: URI, value?: string, options?: ICreateFileOptions): Promise<IFileStatWithMetadata> {
// check for encoding // check for encoding
......
...@@ -9,7 +9,7 @@ import { workbenchInstantiationService, TestLifecycleService, TestTextFileServic ...@@ -9,7 +9,7 @@ import { workbenchInstantiationService, TestLifecycleService, TestTextFileServic
import { IWindowsService } from 'vs/platform/windows/common/windows'; import { IWindowsService } from 'vs/platform/windows/common/windows';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; 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 { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IModelService } from 'vs/editor/common/services/modelService'; import { IModelService } from 'vs/editor/common/services/modelService';
...@@ -24,7 +24,7 @@ import { getRandomTestPath } from 'vs/base/test/node/testUtils'; ...@@ -24,7 +24,7 @@ import { getRandomTestPath } from 'vs/base/test/node/testUtils';
import { tmpdir } from 'os'; import { tmpdir } from 'os';
import { DiskFileSystemProvider } from 'vs/workbench/services/files2/node/diskFileSystemProvider'; import { DiskFileSystemProvider } from 'vs/workbench/services/files2/node/diskFileSystemProvider';
import { generateUuid } from 'vs/base/common/uuid'; 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 { getPathFromAmdModule } from 'vs/base/common/amd';
import { detectEncodingByBOM, UTF16be, UTF16le, UTF8_with_bom, UTF8 } from 'vs/base/node/encoding'; import { detectEncodingByBOM, UTF16be, UTF16le, UTF8_with_bom, UTF8 } from 'vs/base/node/encoding';
import { NodeTextFileService, EncodingOracle, IEncodingOverride } from 'vs/workbench/services/textfile/node/textFileService'; import { NodeTextFileService, EncodingOracle, IEncodingOverride } from 'vs/workbench/services/textfile/node/textFileService';
...@@ -32,6 +32,7 @@ import { LegacyFileService } from 'vs/workbench/services/files/node/fileService' ...@@ -32,6 +32,7 @@ import { LegacyFileService } from 'vs/workbench/services/files/node/fileService'
import { DefaultEndOfLine } from 'vs/editor/common/model'; import { DefaultEndOfLine } from 'vs/editor/common/model';
import { TextModel } from 'vs/editor/common/model/textModel'; import { TextModel } from 'vs/editor/common/model/textModel';
import { isWindows } from 'vs/base/common/platform'; import { isWindows } from 'vs/base/common/platform';
import { readFileSync, statSync } from 'fs';
class ServiceAccessor { class ServiceAccessor {
constructor( constructor(
...@@ -246,7 +247,7 @@ suite('Files - TextFileService i/o', () => { ...@@ -246,7 +247,7 @@ suite('Files - TextFileService i/o', () => {
const detectedEncoding = await detectEncodingByBOM(resource.fsPath); const detectedEncoding = await detectEncodingByBOM(resource.fsPath);
assert.equal(detectedEncoding, encoding); assert.equal(detectedEncoding, encoding);
const resolved = await service.read(resource); const resolved = await service.legacyRead(resource);
assert.equal(resolved.encoding, encoding); assert.equal(resolved.encoding, encoding);
assert.equal(snapshotToString(resolved.value.create(isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).createSnapshot(false)), expectedContent); assert.equal(snapshotToString(resolved.value.create(isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).createSnapshot(false)), expectedContent);
...@@ -273,18 +274,18 @@ suite('Files - TextFileService i/o', () => { ...@@ -273,18 +274,18 @@ suite('Files - TextFileService i/o', () => {
}); });
async function testEncodingKeepsData(resource: URI, encoding: string, expected: string) { 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)); const content = snapshotToString(resolved.value.create(isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).createSnapshot(false));
assert.equal(content, expected); assert.equal(content, expected);
await service.write(resource, content, { encoding }); 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); assert.equal(snapshotToString(resolved.value.create(DefaultEndOfLine.CRLF).createSnapshot(false)), content);
await service.write(resource, TextModel.createFromString(content).createSnapshot(), { encoding }); 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); assert.equal(snapshotToString(resolved.value.create(DefaultEndOfLine.CRLF).createSnapshot(false)), content);
} }
...@@ -295,7 +296,7 @@ suite('Files - TextFileService i/o', () => { ...@@ -295,7 +296,7 @@ suite('Files - TextFileService i/o', () => {
await service.write(resource, content); 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); assert.equal(snapshotToString(resolved.value.create(isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).createSnapshot(false)), content);
}); });
...@@ -306,14 +307,14 @@ suite('Files - TextFileService i/o', () => { ...@@ -306,14 +307,14 @@ suite('Files - TextFileService i/o', () => {
await service.write(resource, TextModel.createFromString(content).createSnapshot()); 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); 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 () => { test('write - encoding preserved (UTF 16 LE) - content as string', async () => {
const resource = URI.file(join(testDir, 'some_utf16le.css')); 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); assert.equal(resolved.encoding, UTF16le);
await testEncoding(URI.file(join(testDir, 'some_utf16le.css')), UTF16le, 'Hello\nWorld', 'Hello\nWorld'); await testEncoding(URI.file(join(testDir, 'some_utf16le.css')), UTF16le, 'Hello\nWorld', 'Hello\nWorld');
...@@ -322,7 +323,7 @@ suite('Files - TextFileService i/o', () => { ...@@ -322,7 +323,7 @@ suite('Files - TextFileService i/o', () => {
test('write - encoding preserved (UTF 16 LE) - content as snapshot', async () => { test('write - encoding preserved (UTF 16 LE) - content as snapshot', async () => {
const resource = URI.file(join(testDir, 'some_utf16le.css')); 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); assert.equal(resolved.encoding, UTF16le);
await testEncoding(URI.file(join(testDir, 'some_utf16le.css')), UTF16le, TextModel.createFromString('Hello\nWorld').createSnapshot(), 'Hello\nWorld'); 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', () => { ...@@ -412,4 +413,41 @@ suite('Files - TextFileService i/o', () => {
let detectedEncoding = await detectEncodingByBOM(resource.fsPath); let detectedEncoding = await detectEncodingByBOM(resource.fsPath);
assert.equal(detectedEncoding, UTF8); 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 { ...@@ -235,6 +235,10 @@ export class TestTextFileService extends BrowserTextFileService {
this.resolveTextContentError = error; this.resolveTextContentError = error;
} }
public legacyRead(resource: URI, options?: IReadTextFileOptions): Promise<ITextFileContent> {
return this.read(resource, options);
}
public read(resource: URI, options?: IReadTextFileOptions): Promise<ITextFileContent> { public read(resource: URI, options?: IReadTextFileOptions): Promise<ITextFileContent> {
if (this.resolveTextContentError) { if (this.resolveTextContentError) {
const error = this.resolveTextContentError; const error = this.resolveTextContentError;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册