提交 5eaf9f05 编写于 作者: A Alex Dima

Adopt ModelBuilder in workbench

上级 a4ac000b
......@@ -20,7 +20,6 @@ import 'vs/base/common/uri';
// platform common
import 'vs/platform/platform';
import 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import 'vs/platform/files/common/files';
import 'vs/platform/request/common/request';
import 'vs/platform/workspace/common/workspace';
import 'vs/platform/telemetry/common/telemetry';
......
......@@ -4,16 +4,12 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import {IDisposable} from 'vs/base/common/lifecycle';
import {IStringStream} from 'vs/platform/files/common/files';
import * as crypto from 'crypto';
import {DefaultEndOfLine, ITextModelCreationOptions, ITextModelResolvedOptions, IRawText} from 'vs/editor/common/editorCommon';
import * as strings from 'vs/base/common/strings';
import {guessIndentation} from 'vs/editor/common/model/indentationGuesser';
export interface IStringStream {
onData(listener: (chunk:string)=>void): IDisposable;
onEnd(listener: ()=>void): IDisposable;
}
import {TPromise} from 'vs/base/common/winjs.base';
export class ModelBuilderResult {
rawText: IRawText;
......@@ -111,6 +107,31 @@ export class ModelBuilder {
private lineBasedBuilder: ModelLineBasedBuilder;
private totalLength: number;
public static fromStringStream(stream:IStringStream, options:ITextModelCreationOptions): TPromise<ModelBuilderResult> {
return new TPromise<ModelBuilderResult>((c, e, p) => {
let done = false;
let builder = new ModelBuilder();
stream.on('data', (chunk) => {
builder.acceptChunk(chunk);
});
stream.on('error', (error) => {
if (!done) {
done = true;
e(error);
}
});
stream.on('end', () => {
if (!done) {
done = true;
c(builder.finish(options));
}
});
});
}
constructor() {
this.leftoverPrevChunk = '';
this.leftoverEndsInCR = false;
......
......@@ -41,6 +41,13 @@ export interface IFileService {
*/
resolveContent(resource: URI, options?: IResolveContentOptions): winjs.TPromise<IContent>;
/**
* Resolve the contents of a file identified by the resource.
*
* The returned object contains properties of the file and the value as a readable stream.
*/
resolveStreamContent(resource: URI, options?: IResolveContentOptions): winjs.TPromise<IStreamContent>;
/**
* Returns the contents of all files by the given array of file resources.
*/
......@@ -346,6 +353,32 @@ export interface IContent extends IBaseStat {
encoding: string;
}
/**
* 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:'end', callback:()=>void): void;
on(event:string, callback:any): void;
}
/**
* Streamable content and meta information of a file.
*/
export interface IStreamContent extends IBaseStat {
/**
* The streamable content of a text file.
*/
value: IStringStream;
/**
* The encoding of the content if known.
*/
encoding: string;
}
export interface IResolveContentOptions {
/**
......
......@@ -5,7 +5,7 @@
'use strict';
import {TPromise} from 'vs/base/common/winjs.base';
import {EndOfLinePreference, IModel} from 'vs/editor/common/editorCommon';
import {EndOfLinePreference, IModel, IRawText} from 'vs/editor/common/editorCommon';
import {IMode} from 'vs/editor/common/modes';
import {EditorModel} from 'vs/workbench/common/editor';
import URI from 'vs/base/common/uri';
......@@ -55,43 +55,53 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd
/**
* Creates the text editor model with the provided value, mime (can be comma separated for multiple values) and optional resource URL.
*/
protected createTextEditorModel(value: string, resource?: URI, mime?: string): TPromise<EditorModel> {
protected createTextEditorModel(value: string | IRawText, resource?: URI, mime?: string): TPromise<EditorModel> {
let firstLineText = this.getFirstLineText(value);
let mode = this.getOrCreateMode(this.modeService, mime, firstLineText);
// To avoid flickering, give the mode at most 50ms to load. If the mode doesn't load in 50ms, proceed creating the model with a mode promise
return TPromise.any<any>([TPromise.timeout(50), this.getOrCreateMode(this.modeService, mime, firstLineText)]).then(() => {
let model = resource && this.modelService.getModel(resource);
let mode = this.getOrCreateMode(this.modeService, mime, firstLineText);
if (!model) {
model = this.modelService.createModel(value, mode, resource);
this.createdEditorModel = true;
} else {
return TPromise.any<any>([TPromise.timeout(50), mode]).then(() => {
return this._createTextEditorModelNow(value, mode, resource);
});
}
private _createTextEditorModelNow(value: string | IRawText, mode: TPromise<IMode>, resource: URI): EditorModel {
let model = resource && this.modelService.getModel(resource);
if (!model) {
model = this.modelService.createModel(value, mode, resource);
this.createdEditorModel = true;
} else {
if (typeof value === 'string') {
model.setValue(value);
model.setMode(mode);
} else {
model.setValueFromRawText(value);
}
model.setMode(mode);
}
this.textEditorModelHandle = model.uri;
this.textEditorModelHandle = model.uri;
return this;
});
return this;
}
private getFirstLineText(value: string): string {
let firstLineText = value.substr(0, 100);
private getFirstLineText(value: string | IRawText): string {
if (typeof value === 'string') {
let firstLineText = value.substr(0, 100);
let crIndex = firstLineText.indexOf('\r');
if (crIndex < 0) {
crIndex = firstLineText.length;
}
let lfIndex = firstLineText.indexOf('\n');
if (lfIndex < 0) {
lfIndex = firstLineText.length;
}
let crIndex = firstLineText.indexOf('\r');
if (crIndex < 0) {
crIndex = firstLineText.length;
}
firstLineText = firstLineText.substr(0, Math.min(crIndex, lfIndex));
let lfIndex = firstLineText.indexOf('\n');
if (lfIndex < 0) {
lfIndex = firstLineText.length;
}
return firstLineText;
return firstLineText.substr(0, Math.min(crIndex, lfIndex));
} else {
return value.lines[0].substr(0, 100);
}
}
/**
......@@ -106,12 +116,17 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd
/**
* Updates the text editor model with the provided value. If the value is the same as the model has, this is a no-op.
*/
protected updateTextEditorModel(newValue: string): void {
protected updateTextEditorModel(newValue: string | IRawText): void {
if (!this.textEditorModel) {
return;
}
let rawText = RawText.fromStringWithModelOptions(newValue, this.textEditorModel);
let rawText: IRawText;
if (typeof newValue === 'string') {
rawText = RawText.fromStringWithModelOptions(newValue, this.textEditorModel);
} else {
rawText = newValue;
}
// Return early if the text is already set in that form
if (this.textEditorModel.equals(rawText)) {
......
......@@ -22,7 +22,7 @@ import {IFileService, IFileOperationResult, FileOperationResult} from 'vs/platfo
import {TextFileEditorModel, ISaveErrorHandler} from 'vs/workbench/parts/files/common/editors/textFileEditorModel';
import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService';
import {IEventService} from 'vs/platform/event/common/event';
import {EventType as FileEventType, TextFileChangeEvent} from 'vs/workbench/parts/files/common/files';
import {EventType as FileEventType, TextFileChangeEvent, ITextFileService} from 'vs/workbench/parts/files/common/files';
import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
import {IMessageService, IMessageWithAction, Severity, CancelAction} from 'vs/platform/message/common/message';
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
......@@ -167,7 +167,8 @@ export class FileOnDiskEditorInput extends ResourceEditorInput {
@IModelService modelService: IModelService,
@IModeService private modeService: IModeService,
@IInstantiationService instantiationService: IInstantiationService,
@IFileService private fileService: IFileService
@IFileService private fileService: IFileService,
@ITextFileService private textFileService: ITextFileService
) {
// We create a new resource URI here that is different from the file resource because we represent the state of
// the file as it is on disk and not as it is (potentially cached) in Code. That allows us to have a different
......@@ -185,7 +186,7 @@ export class FileOnDiskEditorInput extends ResourceEditorInput {
public resolve(refresh?: boolean): TPromise<EditorModel> {
// Make sure our file from disk is resolved up to date
return this.fileService.resolveContent(this.fileResource).then(content => {
return this.textFileService.resolveTextContent(this.fileResource).then(content => {
this.lastModified = content.mtime;
const codeEditorModel = this.modelService.getModel(this.resource);
......@@ -193,7 +194,7 @@ export class FileOnDiskEditorInput extends ResourceEditorInput {
this.modelService.createModel(content.value, this.modeService.getOrCreateMode(this.mime), this.resource);
this.createdEditorModel = true;
} else {
codeEditorModel.setValue(content.value);
codeEditorModel.setValueFromRawText(content.value);
}
return super.resolve(refresh);
......
......@@ -10,16 +10,17 @@ import errors = require('vs/base/common/errors');
import Event, {Emitter} from 'vs/base/common/event';
import {FileEditorInput} from 'vs/workbench/parts/files/browser/editors/fileEditorInput';
import {CACHE, TextFileEditorModel} from 'vs/workbench/parts/files/common/editors/textFileEditorModel';
import {IResult, ITextFileOperationResult, ITextFileService, IAutoSaveConfiguration, AutoSaveMode} from 'vs/workbench/parts/files/common/files';
import {IResult, ITextFileOperationResult, ITextFileService, IRawTextContent, IAutoSaveConfiguration, AutoSaveMode} from 'vs/workbench/parts/files/common/files';
import {ConfirmResult} from 'vs/workbench/common/editor';
import {IWorkspaceContextService} from 'vs/workbench/services/workspace/common/contextService';
import {IFilesConfiguration, IFileOperationResult, FileOperationResult, AutoSaveConfiguration} from 'vs/platform/files/common/files';
import {IStringStream, IFileService, IResolveContentOptions, IFilesConfiguration, IFileOperationResult, FileOperationResult, AutoSaveConfiguration} from 'vs/platform/files/common/files';
import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
import {IEventService} from 'vs/platform/event/common/event';
import {IConfigurationService} from 'vs/platform/configuration/common/configuration';
import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry';
import {IDisposable, dispose} from 'vs/base/common/lifecycle';
import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService';
import {IRawText} from 'vs/editor/common/editorCommon';
import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService';
/**
......@@ -45,7 +46,8 @@ export abstract class TextFileService implements ITextFileService {
@ITelemetryService private telemetryService: ITelemetryService,
@IWorkbenchEditorService protected editorService: IWorkbenchEditorService,
@IEditorGroupService private editorGroupService: IEditorGroupService,
@IEventService private eventService: IEventService
@IEventService private eventService: IEventService,
@IFileService protected fileService: IFileService
) {
this.listenerToUnbind = [];
this._onAutoSaveConfigurationChange = new Emitter<IAutoSaveConfiguration>();
......@@ -60,6 +62,26 @@ export abstract class TextFileService implements ITextFileService {
this.telemetryService.publicLog('autoSave', this.getAutoSaveConfiguration());
}
public resolveTextContent(resource: URI, options?: IResolveContentOptions): TPromise<IRawTextContent> {
return this.fileService.resolveStreamContent(resource, options).then((streamContent) => {
return this.stringStreamToRawText(streamContent.value).then((res) => {
let r:IRawTextContent = {
resource: streamContent.resource,
name: streamContent.name,
mtime: streamContent.mtime,
etag: streamContent.etag,
mime: streamContent.mime,
encoding: streamContent.encoding,
value: res.rawText,
valueLogicalHash: res.hash
};
return r;
});
});
}
protected abstract stringStreamToRawText(stream:IStringStream): TPromise<{rawText:IRawText; hash:string;}>;
public get onAutoSaveConfigurationChange(): Event<IAutoSaveConfiguration> {
return this._onAutoSaveConfigurationChange.event;
}
......
......@@ -18,7 +18,7 @@ import {EventType as WorkbenchEventType, ResourceEvent} from 'vs/workbench/commo
import {EventType as FileEventType, TextFileChangeEvent, ITextFileService, IAutoSaveConfiguration, ModelState} from 'vs/workbench/parts/files/common/files';
import {EncodingMode, EditorModel, IEncodingSupport} from 'vs/workbench/common/editor';
import {BaseTextEditorModel} from 'vs/workbench/common/editor/textEditorModel';
import {IFileService, IFileStat, IFileOperationResult, FileOperationResult, IContent} from 'vs/platform/files/common/files';
import {IFileService, IFileStat, IFileOperationResult, FileOperationResult} from 'vs/platform/files/common/files';
import {IEventService} from 'vs/platform/event/common/event';
import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
import {IMessageService, Severity} from 'vs/platform/message/common/message';
......@@ -208,7 +208,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements IEncodin
}
// Resolve Content
return this.fileService.resolveContent(this.resource, { acceptTextOnly: true, etag: etag, encoding: this.preferredEncoding }).then((content: IContent) => {
return this.textFileService.resolveTextContent(this.resource, { acceptTextOnly: true, etag: etag, encoding: this.preferredEncoding }).then((content) => {
diag('load() - resolved content', this.resource, new Date());
// Telemetry
......
......@@ -8,10 +8,10 @@ import {TPromise} from 'vs/base/common/winjs.base';
import {Event as BaseEvent, PropertyChangeEvent} from 'vs/base/common/events';
import URI from 'vs/base/common/uri';
import Event from 'vs/base/common/event';
import {IModel, IEditorOptions} from 'vs/editor/common/editorCommon';
import {IModel, IEditorOptions, IRawText} from 'vs/editor/common/editorCommon';
import {IDisposable} from 'vs/base/common/lifecycle';
import {EncodingMode, EditorInput, IFileEditorInput, ConfirmResult, IWorkbenchEditorConfiguration} from 'vs/workbench/common/editor';
import {IFileStat, IFilesConfiguration} from 'vs/platform/files/common/files';
import {IFileStat, IFilesConfiguration, IBaseStat, IResolveContentOptions} from 'vs/platform/files/common/files';
import {createDecorator, ServiceIdentifier} from 'vs/platform/instantiation/common/instantiation';
import {FileStat} from 'vs/workbench/parts/files/common/explorerViewModel';
......@@ -246,9 +246,31 @@ export enum AutoSaveMode {
export var ITextFileService = createDecorator<ITextFileService>(TEXT_FILE_SERVICE_ID);
export interface IRawTextContent extends IBaseStat {
/**
* The line grouped content of a text file.
*/
value: IRawText;
/**
* The line grouped logical hash of a text file.
*/
valueLogicalHash: string;
/**
* The encoding of the content if known.
*/
encoding: string;
}
export interface ITextFileService extends IDisposable {
serviceId: ServiceIdentifier<any>;
/**
* Resolve the contents of a file identified by the resource.
*/
resolveTextContent(resource: URI, options?: IResolveContentOptions): TPromise<IRawTextContent>;
/**
* A resource is dirty if it has unsaved changes or is an untitled file not yet saved.
*
......
......@@ -18,7 +18,7 @@ import {TextFileService as AbstractTextFileService} from 'vs/workbench/parts/fil
import {CACHE, TextFileEditorModel} from 'vs/workbench/parts/files/common/editors/textFileEditorModel';
import {ITextFileOperationResult, AutoSaveMode} from 'vs/workbench/parts/files/common/files';
import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService';
import {IFileService} from 'vs/platform/files/common/files';
import {IFileService, IStringStream} from 'vs/platform/files/common/files';
import {BinaryEditorModel} from 'vs/workbench/common/editor/binaryEditorModel';
import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
import {IWorkspaceContextService} from 'vs/workbench/services/workspace/common/contextService';
......@@ -29,13 +29,15 @@ import {IModeService} from 'vs/editor/common/services/modeService';
import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService';
import {IWindowService} from 'vs/workbench/services/window/electron-browser/windowService';
import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService';
import {IModelService} from 'vs/editor/common/services/modelService';
import {ModelBuilder, ModelBuilderResult} from 'vs/editor/node/model/modelBuilder';
export class TextFileService extends AbstractTextFileService {
constructor(
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IInstantiationService instantiationService: IInstantiationService,
@IFileService private fileService: IFileService,
@IFileService fileService: IFileService,
@IUntitledEditorService private untitledEditorService: IUntitledEditorService,
@ILifecycleService private lifecycleService: ILifecycleService,
@ITelemetryService telemetryService: ITelemetryService,
......@@ -44,9 +46,10 @@ export class TextFileService extends AbstractTextFileService {
@IModeService private modeService: IModeService,
@IWorkbenchEditorService editorService: IWorkbenchEditorService,
@IEditorGroupService editorGroupService: IEditorGroupService,
@IWindowService private windowService: IWindowService
@IWindowService private windowService: IWindowService,
@IModelService private modelService: IModelService
) {
super(contextService, instantiationService, configurationService, telemetryService, editorService, editorGroupService, eventService);
super(contextService, instantiationService, configurationService, telemetryService, editorService, editorGroupService, eventService, fileService);
this.init();
}
......@@ -111,6 +114,10 @@ export class TextFileService extends AbstractTextFileService {
super.dispose();
}
protected stringStreamToRawText(stream:IStringStream): TPromise<ModelBuilderResult> {
return ModelBuilder.fromStringStream(stream, this.modelService.getCreationOptions());
}
public revertAll(resources?: URI[], force?: boolean): TPromise<ITextFileOperationResult> {
// Revert files
......
......@@ -24,9 +24,8 @@ import {InstantiationService} from 'vs/platform/instantiation/common/instantiati
import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService';
import {IPartService} from 'vs/workbench/services/part/common/partService';
import {ITextFileService} from 'vs/workbench/parts/files/common/files';
import {TextFileService} from 'vs/workbench/parts/files/browser/textFileServices';
import {FileTracker} from 'vs/workbench/parts/files/browser/fileTracker';
import {TestEditorGroupService, TestHistoryService, TestFileService, TestEditorService, TestPartService, TestConfigurationService, TestEventService, TestContextService, TestQuickOpenService, TestStorageService} from 'vs/workbench/test/common/servicesTestUtils';
import {TestTextFileService, TestEditorGroupService, TestHistoryService, TestFileService, TestEditorService, TestPartService, TestConfigurationService, TestEventService, TestContextService, TestQuickOpenService, TestStorageService} from 'vs/workbench/test/common/servicesTestUtils';
import {createMockModelService, createMockModeService} from 'vs/editor/test/common/servicesTestUtils';
import {IHistoryService} from 'vs/workbench/services/history/common/history';
import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService';
......@@ -58,7 +57,7 @@ suite('Files - FileEditorInput', () => {
services.set(IEditorGroupService, new TestEditorGroupService());
services.set(ILifecycleService, NullLifecycleService);
services.set(IConfigurationService, new TestConfigurationService());
services.set(ITextFileService, <ITextFileService> instantiationService.createInstance(<any> TextFileService));
services.set(ITextFileService, <ITextFileService> instantiationService.createInstance(<any> TestTextFileService));
let input = instantiationService.createInstance(FileEditorInput, toResource('/foo/bar/file.js'), 'text/javascript', void 0);
let otherInput = instantiationService.createInstance(FileEditorInput, toResource('foo/bar/otherfile.js'), 'text/javascript', void 0);
......@@ -128,7 +127,7 @@ suite('Files - FileEditorInput', () => {
services.set(IEventService, eventService);
services.set(IWorkspaceContextService, contextService);
services.set(ITextFileService, <ITextFileService> instantiationService.createInstance(<any> TextFileService));
services.set(ITextFileService, <ITextFileService> instantiationService.createInstance(<any> TestTextFileService));
let fileEditorInput = instantiationService.createInstance(FileEditorInput, toResource('/foo/bar/updatefile.js'), 'text/javascript', void 0);
let contentEditorInput2 = instantiationService.createInstance(FileEditorInput, toResource('/foo/bar/updatefile.js'), 'text/javascript', void 0);
......@@ -162,7 +161,7 @@ suite('Files - FileEditorInput', () => {
services.set(ITelemetryService, telemetryService);
services.set(ILifecycleService, NullLifecycleService);
services.set(IConfigurationService, new TestConfigurationService());
services.set(ITextFileService, <ITextFileService> instantiationService.createInstance(<any> TextFileService));
services.set(ITextFileService, <ITextFileService> instantiationService.createInstance(<any> TestTextFileService));
let tracker = instantiationService.createInstance(FileTracker);
......@@ -208,7 +207,7 @@ suite('Files - FileEditorInput', () => {
services.set(ILifecycleService, NullLifecycleService);
services.set(IConfigurationService, new TestConfigurationService());
services.set(IHistoryService, new TestHistoryService());
services.set(ITextFileService, <ITextFileService> instantiationService.createInstance(<any> TextFileService));
services.set(ITextFileService, <ITextFileService> instantiationService.createInstance(<any> TestTextFileService));
let tracker = instantiationService.createInstance(FileTracker);
......
......@@ -27,9 +27,8 @@ import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/unti
import {InstantiationService} from 'vs/platform/instantiation/common/instantiationService';
import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService';
import PartService = require('vs/workbench/services/part/common/partService');
import {TextFileService} from 'vs/workbench/parts/files/browser/textFileServices';
import {ITextFileService, EventType} from 'vs/workbench/parts/files/common/files';
import {TestFileService, TestPartService, TestEditorService, TestConfigurationService, TestUntitledEditorService, TestStorageService, TestContextService, TestMessageService, TestEventService} from 'vs/workbench/test/common/servicesTestUtils';
import {TestTextFileService, TestFileService, TestPartService, TestEditorService, TestConfigurationService, TestUntitledEditorService, TestStorageService, TestContextService, TestMessageService, TestEventService} from 'vs/workbench/test/common/servicesTestUtils';
import {createMockModelService, createMockModeService} from 'vs/editor/test/common/servicesTestUtils';
function toResource(path) {
......@@ -39,7 +38,7 @@ function toResource(path) {
let baseInstantiationService: IInstantiationService;
let messageService: TestMessageService;
let eventService: TestEventService;
let textFileService: TextFileService;
let textFileService: TestTextFileService;
suite('Files - TextFileEditorModel', () => {
......@@ -64,7 +63,7 @@ suite('Files - TextFileEditorModel', () => {
services.set(IConfigurationService, new TestConfigurationService());
baseInstantiationService = new InstantiationService(services);
textFileService = <any>baseInstantiationService.createInstance(<any>TextFileService);
textFileService = <any>baseInstantiationService.createInstance(<any>TestTextFileService);
services.set(ITextFileService, textFileService);
});
......
......@@ -13,13 +13,12 @@ import {Registry} from 'vs/platform/platform';
import {SyncDescriptor} from 'vs/platform/instantiation/common/descriptors';
import {FileEditorInput} from 'vs/workbench/parts/files/browser/editors/fileEditorInput';
import {Extensions} from 'vs/workbench/browser/parts/editor/baseEditor';
import {TestEventService, TestContextService} from 'vs/workbench/test/common/servicesTestUtils';
import {TestTextFileService, TestEventService, TestContextService} from 'vs/workbench/test/common/servicesTestUtils';
import {IEventService} from 'vs/platform/event/common/event';
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
import {ServiceCollection} from 'vs/platform/instantiation/common/serviceCollection';
import {InstantiationService} from 'vs/platform/instantiation/common/instantiationService';
import {ITextFileService} from 'vs/workbench/parts/files/common/files';
import {TextFileService} from 'vs/workbench/parts/files/browser/textFileServices';
const ExtensionId = Extensions.Editors;
......@@ -52,7 +51,7 @@ suite('Files - TextFileEditor', () => {
services.set(IEventService, eventService);
services.set(IWorkspaceContextService, contextService);
services.set(ITextFileService, <ITextFileService> instantiationService.createInstance(<any> TextFileService));
services.set(ITextFileService, <ITextFileService> instantiationService.createInstance(<any> TestTextFileService));
strictEqual(Registry.as(ExtensionId).getEditor(instantiationService.createInstance(FileEditorInput, URI.file(join('C:\\', '/foo/bar/foobar.html')), 'test-text/html', void 0)), d1);
strictEqual(Registry.as(ExtensionId).getEditor(instantiationService.createInstance(FileEditorInput, URI.file(join('C:\\', '/foo/bar/foobar.js')), 'test-text/javascript', void 0)), d1);
......
......@@ -13,7 +13,7 @@ import errors = require('vs/base/common/errors');
import strings = require('vs/base/common/strings');
import uri from 'vs/base/common/uri';
import timer = require('vs/base/common/timer');
import {IFileService, IFilesConfiguration, IResolveFileOptions, IFileStat, IContent, IImportResult, IResolveContentOptions, IUpdateContentOptions} from 'vs/platform/files/common/files';
import {IFileService, IFilesConfiguration, IResolveFileOptions, IFileStat, IContent, IStreamContent, IImportResult, IResolveContentOptions, IUpdateContentOptions} from 'vs/platform/files/common/files';
import {FileService as NodeFileService, IFileServiceOptions, IEncodingOverride} from 'vs/workbench/services/files/node/fileService';
import {IConfigurationService} from 'vs/platform/configuration/common/configuration';
import {IEventService} from 'vs/platform/event/common/event';
......@@ -128,6 +128,17 @@ export class FileService implements IFileService {
});
}
public resolveStreamContent(resource: uri, options?: IResolveContentOptions): TPromise<IStreamContent> {
let contentId = resource.toString();
let timerEvent = timer.start(timer.Topic.WORKBENCH, strings.format('Load {0}', contentId));
return this.raw.resolveStreamContent(resource, options).then((result) => {
timerEvent.stop();
return result;
});
}
public resolveContents(resources: uri[]): TPromise<IContent[]> {
return this.raw.resolveContents(resources);
}
......
......@@ -141,10 +141,18 @@ export class FileService implements files.IFileService {
}
public resolveContent(resource: uri, options?: files.IResolveContentOptions): TPromise<files.IContent> {
return this._resolveContent(resource, options, (resource, etag, enc) => this.resolveFileContent(resource, etag, enc));
}
public resolveStreamContent(resource: uri, options?: files.IResolveContentOptions): TPromise<files.IStreamContent> {
return this._resolveContent(resource, options, (resource, etag, enc) => this.resolveFileStreamContent(resource, etag, enc));
}
private _resolveContent<T extends files.IBaseStat>(resource: uri, options: files.IResolveContentOptions, contentResolver:(resource: uri, etag?: string, enc?: string) => TPromise<T>): TPromise<T> {
let absolutePath = this.toAbsolutePath(resource);
// 1.) detect mimes
return nfcall(mime.detectMimesFromFile, absolutePath).then((detected: mime.IMimeAndEncoding) => {
return nfcall(mime.detectMimesFromFile, absolutePath).then((detected: mime.IMimeAndEncoding):TPromise<T> => {
let isText = detected.mimes.indexOf(baseMime.MIME_BINARY) === -1;
// Return error early if client only accepts text and this is not text
......@@ -173,7 +181,7 @@ export class FileService implements files.IFileService {
}
// 2.) get content
return this.resolveFileContent(resource, options && options.etag, preferredEncoding).then((content) => {
return contentResolver(resource, options && options.etag, preferredEncoding).then((content) => {
// set our knowledge about the mime on the content obj
content.mime = detected.mimes.join(', ');
......@@ -421,11 +429,11 @@ export class FileService implements files.IFileService {
});
}
private resolveFileContent(resource: uri, etag?: string, enc?: string): TPromise<files.IContent> {
private resolveFileStreamContent(resource: uri, etag?: string, enc?: string): TPromise<files.IStreamContent> {
let absolutePath = this.toAbsolutePath(resource);
// 1.) stat
return this.resolve(resource).then((model) => {
return this.resolve(resource).then((model): TPromise<files.IStreamContent> => {
// Return early if file not modified since
if (etag && etag === model.etag) {
......@@ -441,29 +449,38 @@ export class FileService implements files.IFileService {
});
}
let fileEncoding = this.getEncoding(model.resource, enc);
const reader = fs.createReadStream(absolutePath).pipe(encoding.decodeStream(fileEncoding)); // decode takes care of stripping any BOMs from the file content
let content: files.IStreamContent = <any>model;
content.value = reader;
content.encoding = fileEncoding; // make sure to store the encoding in the model to restore it later when writing
return TPromise.as(content);
});
}
private resolveFileContent(resource: uri, etag?: string, enc?: string): TPromise<files.IContent> {
return this.resolveFileStreamContent(resource, etag, enc).then((streamContent) => {
// 2.) read contents
return new TPromise<files.IContent>((c, e) => {
let done = false;
let chunks: NodeBuffer[] = [];
let fileEncoding = this.getEncoding(model.resource, enc);
const reader = fs.createReadStream(absolutePath).pipe(encoding.decodeStream(fileEncoding)); // decode takes care of stripping any BOMs from the file content
let chunks: string[] = [];
reader.on('data', (buf) => {
streamContent.value.on('data', (buf) => {
chunks.push(buf);
});
reader.on('error', (error) => {
streamContent.value.on('error', (error) => {
if (!done) {
done = true;
e(error);
}
});
reader.on('end', () => {
let content: files.IContent = <any>model;
streamContent.value.on('end', () => {
let content: files.IContent = <any>streamContent;
content.value = chunks.join('');
content.encoding = fileEncoding; // make sure to store the encoding in the model to restore it later when writing
if (!done) {
done = true;
......
......@@ -28,8 +28,7 @@ import {StringEditorModel} from 'vs/workbench/common/editor/stringEditorModel';
import {FileEditorInput} from 'vs/workbench/parts/files/browser/editors/fileEditorInput';
import {TextFileEditorModel} from 'vs/workbench/parts/files/common/editors/textFileEditorModel';
import {ITextFileService} from 'vs/workbench/parts/files/common/files';
import {TextFileService} from 'vs/workbench/parts/files/browser/textFileServices';
import {TestEventService, TestPartService, TestStorageService, TestConfigurationService, TestRequestService, TestContextService, TestWorkspace, TestEditorService, MockRequestService} from 'vs/workbench/test/common/servicesTestUtils';
import {TestTextFileService, TestEventService, TestPartService, TestStorageService, TestConfigurationService, TestRequestService, TestContextService, TestWorkspace, TestEditorService, MockRequestService} from 'vs/workbench/test/common/servicesTestUtils';
import {Viewlet} from 'vs/workbench/browser/viewlet';
import {EventType} from 'vs/workbench/common/events';
import {ITelemetryService, NullTelemetryService} from 'vs/platform/telemetry/common/telemetry';
......@@ -218,6 +217,27 @@ suite('Workbench UI Services', () => {
});
},
resolveStreamContent: function (resource) {
return TPromise.as({
resource: resource,
value: {
on: (event:string, callback:Function): void => {
if (event === 'data') {
callback('Hello Html');
}
if (event === 'end') {
callback();
}
}
},
etag: 'index.txt',
mime: 'text/plain',
encoding: 'utf8',
mtime: new Date().getTime(),
name: paths.basename(resource.fsPath)
});
},
updateContent: function (res) {
return TPromise.timeout(1).then(() => {
return {
......@@ -267,7 +287,7 @@ suite('Workbench UI Services', () => {
services.set(ILifecycleService, NullLifecycleService);
services.set(IFileService, <any> TestFileService);
services.set(ITextFileService, <ITextFileService>inst.createInstance(<any>TextFileService));
services.set(ITextFileService, <ITextFileService>inst.createInstance(<any>TestTextFileService));
services['instantiationService'] = inst;
let activeInput: EditorInput = inst.createInstance(FileEditorInput, toResource('/something.js'), 'text/javascript', void 0);
......@@ -349,7 +369,7 @@ suite('Workbench UI Services', () => {
services.set(PartService.IPartService, new TestPartService());
services.set(ILifecycleService, NullLifecycleService);
services.set(IConfigurationService, new TestConfigurationService());
services.set(ITextFileService, <ITextFileService> inst.createInstance(<any>TextFileService));
services.set(ITextFileService, <ITextFileService> inst.createInstance(<any>TestTextFileService));
let activeInput: EditorInput = inst.createInstance(FileEditorInput, toResource('/something.js'), 'text/javascript', void 0);
let testEditorPart = new TestEditorPart();
......
......@@ -9,7 +9,7 @@ import {Promise, TPromise} from 'vs/base/common/winjs.base';
import EventEmitter = require('vs/base/common/eventEmitter');
import Paths = require('vs/base/common/paths');
import URI from 'vs/base/common/uri';
import {NullTelemetryService} from 'vs/platform/telemetry/common/telemetry';
import {NullTelemetryService, ITelemetryService} from 'vs/platform/telemetry/common/telemetry';
import Storage = require('vs/workbench/common/storage');
import {EditorInputEvent} from 'vs/workbench/common/editor';
import Event, {Emitter} from 'vs/base/common/event';
......@@ -34,6 +34,11 @@ import {EditorStacksModel} from 'vs/workbench/common/editor/editorStacksModel';
import {ServiceCollection} from 'vs/platform/instantiation/common/serviceCollection';
import {InstantiationService} from 'vs/platform/instantiation/common/instantiationService';
import {IEditorGroupService, GroupArrangement} from 'vs/workbench/services/group/common/groupService';
import {TextFileService} from 'vs/workbench/parts/files/browser/textFileServices';
import {ModelBuilder, ModelBuilderResult} from 'vs/editor/node/model/modelBuilder';
import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
import {IStringStream, IFileService} from 'vs/platform/files/common/files';
import {IModelService} from 'vs/editor/common/services/modelService';
export const TestWorkspace: IWorkspace = {
resource: URI.file('C:\\testWorkspace'),
......@@ -125,6 +130,28 @@ export class TestContextService implements WorkspaceContextService.IWorkspaceCon
}
}
export abstract class TestTextFileService extends TextFileService {
constructor(
@WorkspaceContextService.IWorkspaceContextService contextService: WorkspaceContextService.IWorkspaceContextService,
@IInstantiationService instantiationService: IInstantiationService,
@IConfigurationService configurationService: IConfigurationService,
@ITelemetryService telemetryService: ITelemetryService,
@WorkbenchEditorService.IWorkbenchEditorService editorService: WorkbenchEditorService.IWorkbenchEditorService,
@IEditorGroupService editorGroupService: IEditorGroupService,
@IEventService eventService: IEventService,
@IFileService fileService: IFileService,
@IModelService private modelService: IModelService
) {
super(contextService, instantiationService, configurationService, telemetryService, editorService, editorGroupService, eventService, fileService);
}
protected stringStreamToRawText(stream:IStringStream): TPromise<ModelBuilderResult> {
return ModelBuilder.fromStringStream(stream, this.modelService.getCreationOptions());
}
}
export class TestMessageService implements IMessageService {
public serviceId = IMessageService;
......@@ -528,6 +555,27 @@ export const TestFileService = {
});
},
resolveStreamContent: function (resource) {
return TPromise.as({
resource: resource,
value: {
on: (event:string, callback:Function): void => {
if (event === 'data') {
callback('Hello Html');
}
if (event === 'end') {
callback();
}
}
},
etag: 'index.txt',
mime: 'text/plain',
encoding: 'utf8',
mtime: new Date().getTime(),
name: Paths.basename(resource.fsPath)
});
},
updateContent: function (res) {
return TPromise.timeout(1).then(() => {
return {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册