From 6438f68354a0aef38b4edd512c2ccad52e429e31 Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 9 Mar 2016 16:58:45 +0100 Subject: [PATCH] output: better protect agains spammy output fixes #3896 fixes #3897 fixes #3609 --- .../workbench/parts/output/common/output.ts | 2 + .../parts/output/common/outputEditorInput.ts | 56 ++++++++++++------- .../parts/output/common/outputServices.ts | 11 ++-- 3 files changed, 44 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/parts/output/common/output.ts b/src/vs/workbench/parts/output/common/output.ts index cabdd7dbece..8a1d82a2e7f 100644 --- a/src/vs/workbench/parts/output/common/output.ts +++ b/src/vs/workbench/parts/output/common/output.ts @@ -36,6 +36,8 @@ export const Extensions = { export const OUTPUT_SERVICE_ID = 'outputService'; +export const MAX_OUTPUT_LENGTH = 10000 /* Max. number of output lines to show in output */ * 100 /* Guestimated chars per line */; + /** * The output event informs when new output got received. */ diff --git a/src/vs/workbench/parts/output/common/outputEditorInput.ts b/src/vs/workbench/parts/output/common/outputEditorInput.ts index 3c42ebf019b..82d975cf084 100644 --- a/src/vs/workbench/parts/output/common/outputEditorInput.ts +++ b/src/vs/workbench/parts/output/common/outputEditorInput.ts @@ -5,13 +5,16 @@ 'use strict'; import nls = require('vs/nls'); +import lifecycle = require('vs/base/common/lifecycle'); import {TPromise} from 'vs/base/common/winjs.base'; import {RunOnceScheduler} from 'vs/base/common/async'; import {EditorModel} from 'vs/workbench/common/editor'; import {StringEditorInput} from 'vs/workbench/common/editor/stringEditorInput'; -import {OUTPUT_EDITOR_INPUT_ID, OUTPUT_PANEL_ID, IOutputEvent, OUTPUT_MIME, IOutputService} from 'vs/workbench/parts/output/common/output'; +import {OUTPUT_EDITOR_INPUT_ID, OUTPUT_PANEL_ID, IOutputEvent, OUTPUT_MIME, IOutputService, MAX_OUTPUT_LENGTH} from 'vs/workbench/parts/output/common/output'; import {OutputPanel} from 'vs/workbench/parts/output/browser/outputPanel'; import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; +import {IEventService} from 'vs/platform/event/common/event'; +import {EventType, CompositeEvent} from 'vs/workbench/common/events'; import {IPanelService} from 'vs/workbench/services/panel/common/panelService'; /** @@ -21,12 +24,11 @@ export class OutputEditorInput extends StringEditorInput { private static OUTPUT_DELAY = 300; // delay in ms to accumulate output before emitting an event about it private static instances: { [channel: string]: OutputEditorInput; } = Object.create(null); - private static MAX_OUTPUT_LINES = 10000; // Max. number of output lines to show in output private outputSet: boolean; private channel: string; - private toUnbind: { (): void; }[]; private bufferedOutput: string; + private toDispose: lifecycle.IDisposable[]; private appendOutputScheduler: RunOnceScheduler; public static getInstances(): OutputEditorInput[] { @@ -47,23 +49,32 @@ export class OutputEditorInput extends StringEditorInput { channel: string, @IInstantiationService instantiationService: IInstantiationService, @IOutputService private outputService: IOutputService, - @IPanelService private panelService: IPanelService + @IPanelService private panelService: IPanelService, + @IEventService private eventService: IEventService ) { super(nls.localize('output', "Output"), channel ? nls.localize('outputChannel', "for '{0}'", channel) : '', '', OUTPUT_MIME, true, instantiationService); this.channel = channel; this.bufferedOutput = ''; - this.toUnbind = []; - const listenerUnbind = this.outputService.onOutput(this.onOutputReceived, this); - this.toUnbind.push(() => listenerUnbind.dispose()); + this.toDispose = []; + this.toDispose.push(this.outputService.onOutput(this.onOutputReceived, this)); + this.toDispose.push(this.outputService.onActiveOutputChannel(() => this.scheduleOutputAppend())); + this.toDispose.push(this.eventService.addListener2(EventType.COMPOSITE_OPENED, (e: CompositeEvent) => { + if (e.compositeId === OUTPUT_PANEL_ID) { + this.scheduleOutputAppend(); + } + })); this.appendOutputScheduler = new RunOnceScheduler(() => { - this.append(this.bufferedOutput); - this.trim(OutputEditorInput.MAX_OUTPUT_LINES); + if (this.value.length + this.bufferedOutput.length > MAX_OUTPUT_LENGTH) { + this.setValue(this.outputService.getOutput(this.channel)); + } else { + this.append(this.bufferedOutput); + } this.bufferedOutput = ''; - const panel = this.panelService.getActivePanel(); - if (panel && panel.getId() === OUTPUT_PANEL_ID && this.outputService.getActiveChannel() === this.channel) { + if (this.isVisible()) { + const panel = this.panelService.getActivePanel(); (panel).revealLastLine(); } }, OutputEditorInput.OUTPUT_DELAY); @@ -73,22 +84,30 @@ export class OutputEditorInput extends StringEditorInput { if (this.outputSet && e.channel === this.channel) { if (e.output) { this.bufferedOutput += e.output; - if (!this.appendOutputScheduler.isScheduled()) { - this.appendOutputScheduler.schedule(); - } + this.scheduleOutputAppend(); } else if (e.output === null) { this.clearValue(); // special output indicates we should clear } } } + private isVisible(): boolean { + const panel = this.panelService.getActivePanel(); + return panel && panel.getId() === OUTPUT_PANEL_ID && this.outputService.getActiveChannel() === this.channel; + } + + private scheduleOutputAppend(): void { + if (this.isVisible() && this.bufferedOutput && !this.appendOutputScheduler.isScheduled()) { + this.appendOutputScheduler.schedule(); + } + } + public getId(): string { return OUTPUT_EDITOR_INPUT_ID; } public resolve(refresh?: boolean): TPromise { return super.resolve(refresh).then(model => { - // Just return model if output already set if (this.outputSet) { return model; @@ -117,10 +136,9 @@ export class OutputEditorInput extends StringEditorInput { } public dispose(): void { - while (this.toUnbind.length) { - this.toUnbind.pop()(); - } + this.appendOutputScheduler.dispose(); + this.toDispose = lifecycle.disposeAll(this.toDispose); super.dispose(); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/parts/output/common/outputServices.ts b/src/vs/workbench/parts/output/common/outputServices.ts index 564d3ef2cda..36d377f8e04 100644 --- a/src/vs/workbench/parts/output/common/outputServices.ts +++ b/src/vs/workbench/parts/output/common/outputServices.ts @@ -13,7 +13,7 @@ import {IInstantiationService} from 'vs/platform/instantiation/common/instantiat import {IStorageService, StorageScope} from 'vs/platform/storage/common/storage'; import {Registry} from 'vs/platform/platform'; import {EditorOptions} from 'vs/workbench/common/editor'; -import {IOutputEvent, IOutputService, Extensions, OUTPUT_PANEL_ID, IOutputChannelRegistry} from 'vs/workbench/parts/output/common/output'; +import {IOutputEvent, IOutputService, Extensions, OUTPUT_PANEL_ID, IOutputChannelRegistry, MAX_OUTPUT_LENGTH} from 'vs/workbench/parts/output/common/output'; import {OutputEditorInput} from 'vs/workbench/parts/output/common/outputEditorInput'; import {OutputPanel} from 'vs/workbench/parts/output/browser/outputPanel'; import {IPanelService} from 'vs/workbench/services/panel/common/panelService'; @@ -23,7 +23,6 @@ const OUTPUT_ACTIVE_CHANNEL_KEY = 'output.activechannel'; export class OutputService implements IOutputService { public serviceId = IOutputService; - private static MAX_OUTPUT = 10000 /* Lines */ * 100 /* Guestimated chars per line */; private receivedOutput: { [channel: string]: string; }; private activeChannel: string; @@ -79,18 +78,18 @@ export class OutputService implements IOutputService { let addLength = output.length; // Still below MAX_OUTPUT, so just add - if (addLength + curLength <= OutputService.MAX_OUTPUT) { + if (addLength + curLength <= MAX_OUTPUT_LENGTH) { this.receivedOutput[channel] += output; } else { // New output exceeds MAX_OUTPUT, so trim beginning and use as received output - if (addLength > OutputService.MAX_OUTPUT) { - this.receivedOutput[channel] = '...' + output.substr(addLength - OutputService.MAX_OUTPUT); + if (addLength > MAX_OUTPUT_LENGTH) { + this.receivedOutput[channel] = '...' + output.substr(addLength - MAX_OUTPUT_LENGTH); } // New output + existing output exceeds MAX_OUTPUT, so trim existing output that it fits new output else { - let diff = OutputService.MAX_OUTPUT - addLength; + let diff = MAX_OUTPUT_LENGTH - addLength; this.receivedOutput[channel] = '...' + this.receivedOutput[channel].substr(curLength - diff) + output; } } -- GitLab