From 14057a645f7c8a48fc4626ce1ea22a23fbc13242 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 18 Dec 2017 18:24:55 +0100 Subject: [PATCH] #40196 Write into model directly when a message is appended --- src/typings/spdlog.d.ts | 3 + .../output/electron-browser/outputServices.ts | 210 ++++++++++-------- 2 files changed, 125 insertions(+), 88 deletions(-) diff --git a/src/typings/spdlog.d.ts b/src/typings/spdlog.d.ts index 5417fdd4212..4762b11143f 100644 --- a/src/typings/spdlog.d.ts +++ b/src/typings/spdlog.d.ts @@ -29,6 +29,9 @@ declare module 'spdlog' { critical(message: string); setLevel(level: number); clearFormatters(); + /** + * A synchronous operation to flush the contents into file + */ flush(): void; drop(): void; } diff --git a/src/vs/workbench/parts/output/electron-browser/outputServices.ts b/src/vs/workbench/parts/output/electron-browser/outputServices.ts index d2cfd1b5077..0ebaadb8a17 100644 --- a/src/vs/workbench/parts/output/electron-browser/outputServices.ts +++ b/src/vs/workbench/parts/output/electron-browser/outputServices.ts @@ -35,7 +35,7 @@ import { toLocalISOString } from 'vs/base/common/date'; const OUTPUT_ACTIVE_CHANNEL_KEY = 'output.activechannel'; -class OutputFileHandler extends Disposable { +class OutputFileListener extends Disposable { private _onDidChange: Emitter = new Emitter(); readonly onDidContentChange: Event = this._onDidChange.event; @@ -58,11 +58,6 @@ class OutputFileHandler extends Disposable { })); } - loadContent(from: number): TPromise { - return this.fileService.resolveContent(this.file) - .then(({ value }) => value.substring(from)); - } - unwatch(): void { this.fileService.unwatchFileChanges(this.file); this.disposables = dispose(this.disposables); @@ -76,44 +71,34 @@ class OutputFileHandler extends Disposable { interface OutputChannel extends IOutputChannel { readonly onDispose: Event; - resolve(): TPromise; + createModel(): TPromise; } -class FileOutputChannel extends Disposable implements OutputChannel { +abstract class AbstractOutputChannel extends Disposable { + + scrollLock: boolean = false; protected _onDispose: Emitter = new Emitter(); readonly onDispose: Event = this._onDispose.event; - scrollLock: boolean = false; - protected readonly file: URI; - private readonly fileHandler: OutputFileHandler; - private updateInProgress: boolean = false; - private modelUpdater: RunOnceScheduler; - private startOffset: number; - private endOffset: number; + protected startOffset: number = 0; + protected endOffset: number = 0; + protected modelUpdater: RunOnceScheduler; constructor( - private readonly outputChannelIdentifier: IOutputChannelIdentifier, - @IFileService protected fileService: IFileService, - @IModelService private modelService: IModelService, - @IPanelService private panelService: IPanelService + protected readonly outputChannelIdentifier: IOutputChannelIdentifier, + protected fileService: IFileService, + private modelService: IModelService, + private modeService: IModeService, + private panelService: IPanelService ) { super(); this.file = outputChannelIdentifier.file; - this.startOffset = 0; - this.endOffset = 0; - this.modelUpdater = new RunOnceScheduler(() => this.doUpdate(), 300); + this.modelUpdater = new RunOnceScheduler(() => this.updateModel(), 300); this._register(toDisposable(() => this.modelUpdater.cancel())); - - this.fileHandler = this._register(new OutputFileHandler(this.file, this.fileService)); - this._register(this.fileHandler.onDidContentChange(() => this.onDidContentChange())); - this._register(toDisposable(() => this.fileHandler.unwatch())); - - this._register(this.modelService.onModelAdded(this.onModelAdded, this)); - this._register(this.modelService.onModelRemoved(this.onModelRemoved, this)); } get id(): string { @@ -124,11 +109,10 @@ class FileOutputChannel extends Disposable implements OutputChannel { return this.outputChannelIdentifier.label; } - append(message: string): void { - throw new Error('Not supported'); - } - clear(): void { + if (this.modelUpdater.isScheduled()) { + this.modelUpdater.cancel(); + } this.startOffset = this.endOffset; const model = this.getModel(); if (model) { @@ -136,36 +120,79 @@ class FileOutputChannel extends Disposable implements OutputChannel { } } - resolve(): TPromise { - return this.fileHandler.loadContent(this.startOffset); + createModel(): TPromise { + return this.fileService.resolveContent(this.file, { position: this.startOffset }) + .then(content => { + const model = this.modelService.createModel(content.value, this.modeService.getOrCreateMode(OUTPUT_MIME), URI.from({ scheme: OUTPUT_SCHEME, path: this.id })); + this.endOffset = this.startOffset + new Buffer(model.getValueLength()).byteLength; + this.onModelCreated(model); + const disposables: IDisposable[] = []; + disposables.push(model.onWillDispose(() => { + this.onModelWillDispose(model); + dispose(disposables); + })); + return model; + }); } - private onModelAdded(model: IModel): void { - if (model.uri.fsPath === this.id) { - this.endOffset = this.startOffset + new Buffer(model.getValueLength()).byteLength; - this.fileHandler.watch(); + protected appendContent(content: string): void { + const model = this.getModel(); + if (model && content) { + const lastLine = model.getLineCount(); + const lastLineMaxColumn = model.getLineMaxColumn(lastLine); + model.applyEdits([EditOperation.insert(new Position(lastLine, lastLineMaxColumn), content)]); + this.endOffset = this.endOffset + new Buffer(content).byteLength; + if (!this.scrollLock) { + (this.panelService.getActivePanel()).revealLastLine(); + } } } - private onModelRemoved(model: IModel): void { - if (model.uri.fsPath === this.id) { - this.fileHandler.unwatch(); - } + protected getModel(): IModel { + const model = this.modelService.getModel(URI.from({ scheme: OUTPUT_SCHEME, path: this.id })); + return model && !model.isDisposed() ? model : null; } - private onDidContentChange(): void { - if (!this.updateInProgress) { - this.updateInProgress = true; - this.modelUpdater.schedule(); - } + protected onModelCreated(model: IModel) { } + protected onModelWillDispose(model: IModel) { } + protected updateModel() { } + + dispose(): void { + this._onDispose.fire(); + super.dispose(); } +} + +class FileOutputChannel extends AbstractOutputChannel implements OutputChannel { + + private readonly fileHandler: OutputFileListener; + + private updateInProgress: boolean = false; + + constructor( + outputChannelIdentifier: IOutputChannelIdentifier, + @IFileService fileService: IFileService, + @IModelService modelService: IModelService, + @IModeService modeService: IModeService, + @IPanelService panelService: IPanelService + ) { + super(outputChannelIdentifier, fileService, modelService, modeService, panelService); - private doUpdate(): void { + this.fileHandler = this._register(new OutputFileListener(this.file, fileService)); + this._register(this.fileHandler.onDidContentChange(() => this.onDidContentChange())); + this._register(toDisposable(() => this.fileHandler.unwatch())); + } + + append(message: string): void { + throw new Error('Not supported'); + } + + protected updateModel(): void { let model = this.getModel(); if (model) { - this.fileHandler.loadContent(this.endOffset) + this.fileService.resolveContent(this.file, { position: this.endOffset }) .then(content => { - this.appendContent(content); + this.appendContent(content.value); this.updateInProgress = false; }, () => this.updateInProgress = false); } else { @@ -173,59 +200,69 @@ class FileOutputChannel extends Disposable implements OutputChannel { } } - private appendContent(content: string): void { - const model = this.getModel(); - if (model && content) { - const lastLine = model.getLineCount(); - const lastLineMaxColumn = model.getLineMaxColumn(lastLine); - model.applyEdits([EditOperation.insert(new Position(lastLine, lastLineMaxColumn), content)]); - this.endOffset = this.endOffset + new Buffer(content).byteLength; - if (!this.scrollLock) { - (this.panelService.getActivePanel()).revealLastLine(); - } - } + protected onModelCreated(model: IModel): void { + this.fileHandler.watch(); } - protected getModel(): IModel { - const model = this.modelService.getModel(URI.from({ scheme: OUTPUT_SCHEME, path: this.id })); - return model && !model.isDisposed() ? model : null; + protected onModelWillDispose(model: IModel): void { + this.fileHandler.unwatch(); } - dispose(): void { - this._onDispose.fire(); - super.dispose(); + private onDidContentChange(): void { + if (!this.updateInProgress) { + this.updateInProgress = true; + this.modelUpdater.schedule(); + } } } -class AppendableFileOutoutChannel extends FileOutputChannel implements OutputChannel { +class AppendableFileOutputChannel extends AbstractOutputChannel implements OutputChannel { private outputWriter: RotatingLogger; - private flushScheduler: RunOnceScheduler; + private appendedMessage = ''; constructor( outputChannelIdentifier: IOutputChannelIdentifier, @IFileService fileService: IFileService, @IModelService modelService: IModelService, - @IPanelService panelService: IPanelService, + @IModeService modeService: IModeService, + @IPanelService panelService: IPanelService ) { - super(outputChannelIdentifier, fileService, modelService, panelService); + super(outputChannelIdentifier, fileService, modelService, modeService, panelService); + this.outputWriter = new RotatingLogger(this.id, this.file.fsPath, 1024 * 1024 * 5, 1); this.outputWriter.clearFormatters(); - - this.flushScheduler = new RunOnceScheduler(() => this.outputWriter.flush(), 300); - this._register(toDisposable(() => this.flushScheduler.cancel())); - - this._register(modelService.onModelAdded(model => { - if (model.uri.fsPath === this.id && !this.flushScheduler.isScheduled()) { - this.flushScheduler.schedule(); - } - })); } append(message: string): void { this.outputWriter.critical(message); - if (this.getModel() && !this.flushScheduler.isScheduled()) { - this.flushScheduler.schedule(); + const model = this.getModel(); + if (model) { + this.appendedMessage += message; + if (!this.modelUpdater.isScheduled()) { + this.modelUpdater.schedule(); + } + } + } + + clear(): void { + super.clear(); + this.appendedMessage = ''; + } + + createModel(): TPromise { + this.outputWriter.flush(); + this.appendedMessage = ''; + return super.createModel(); + } + + protected updateModel(): void { + let model = this.getModel(); + if (model) { + if (this.appendedMessage) { + this.appendContent(this.appendedMessage); + this.appendedMessage = ''; + } } } } @@ -247,8 +284,6 @@ export class OutputService implements IOutputService, ITextModelContentProvider @IInstantiationService private instantiationService: IInstantiationService, @IPanelService private panelService: IPanelService, @IWorkspaceContextService contextService: IWorkspaceContextService, - @IModelService private modelService: IModelService, - @IModeService private modeService: IModeService, @ITextModelService textModelResolverService: ITextModelService, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @IEnvironmentService private environmentService: IEnvironmentService @@ -268,8 +303,7 @@ export class OutputService implements IOutputService, ITextModelContentProvider provideTextContent(resource: URI): TPromise { const channel = this.getChannel(resource.fsPath); - return channel.resolve() - .then(content => this.modelService.createModel(content, this.modeService.getOrCreateMode(OUTPUT_MIME), resource)); + return channel.createModel(); } showChannel(id: string, preserveFocus?: boolean): TPromise { @@ -332,7 +366,7 @@ export class OutputService implements IOutputService, ITextModelContentProvider } const sessionId = toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, ''); const file = URI.file(paths.join(this.environmentService.logsPath, 'outputs', `${id}.${sessionId}.log`)); - return this.instantiationService.createInstance(AppendableFileOutoutChannel, { id, label: channelData ? channelData.label : '', file }); + return this.instantiationService.createInstance(AppendableFileOutputChannel, { id, label: channelData ? channelData.label : '', file }); } -- GitLab