outputServices.ts 24.7 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6 7
import * as nls from 'vs/nls';
import * as paths from 'vs/base/common/paths';
8 9
import * as strings from 'vs/base/common/strings';
import * as extfs from 'vs/base/node/extfs';
10
import * as fs from 'fs';
J
Johannes Rieken 已提交
11 12
import { TPromise } from 'vs/base/common/winjs.base';
import Event, { Emitter } from 'vs/base/common/event';
13
import URI from 'vs/base/common/uri';
14
import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle';
J
Johannes Rieken 已提交
15 16
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
17
import { Registry } from 'vs/platform/registry/common/platform';
J
Johannes Rieken 已提交
18
import { EditorOptions } from 'vs/workbench/common/editor';
S
Sandeep Somavarapu 已提交
19
import { IOutputChannelIdentifier, IOutputChannel, IOutputService, Extensions, OUTPUT_PANEL_ID, IOutputChannelRegistry, OUTPUT_SCHEME, OUTPUT_MIME, MAX_OUTPUT_LENGTH, LOG_SCHEME, LOG_MIME } from 'vs/workbench/parts/output/common/output';
J
Johannes Rieken 已提交
20 21 22 23 24
import { OutputPanel } from 'vs/workbench/parts/output/browser/outputPanel';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { OutputLinkProvider } from 'vs/workbench/parts/output/common/outputLinkProvider';
25
import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService';
A
Alex Dima 已提交
26
import { ITextModel } from 'vs/editor/common/model';
27
import { IModeService } from 'vs/editor/common/services/modeService';
28
import { RunOnceScheduler, ThrottledDelayer } from 'vs/base/common/async';
29 30
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { Position } from 'vs/editor/common/core/position';
31
import { IFileService } from 'vs/platform/files/common/files';
32 33
import { IPanel } from 'vs/workbench/common/panel';
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
34 35 36
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { RotatingLogger } from 'spdlog';
import { toLocalISOString } from 'vs/base/common/date';
37
import { IWindowService } from 'vs/platform/windows/common/windows';
38 39 40
import { ILogService } from 'vs/platform/log/common/log';
import { binarySearch } from 'vs/base/common/arrays';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
41
import { Schemas } from 'vs/base/common/network';
S
Sandeep Somavarapu 已提交
42
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
43 44

const OUTPUT_ACTIVE_CHANNEL_KEY = 'output.activechannel';
E
Erich Gamma 已提交
45

46
let watchingOutputDir = false;
47
let callbacks: ((eventType: string, fileName: string) => void)[] = [];
48 49 50
function watchOutputDirectory(outputDir: string, logService: ILogService, onChange: (eventType: string, fileName: string) => void): IDisposable {
	callbacks.push(onChange);
	if (!watchingOutputDir) {
51 52 53 54 55 56 57 58 59 60 61
		const watcher = extfs.watch(outputDir, (eventType, fileName) => {
			for (const callback of callbacks) {
				callback(eventType, fileName);
			}
		}, (error: string) => {
			logService.error(error);
		});
		watchingOutputDir = true;
		return toDisposable(() => {
			callbacks = [];
			if (watcher) {
62 63
				watcher.removeAllListeners();
				watcher.close();
64 65
			}
		});
66
	}
67
	return toDisposable(() => { });
68 69
}

70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
const fileWatchers: Map<string, any[]> = new Map<string, any[]>();
function watchFile(file: string, callback: () => void): IDisposable {

	const onFileChange = (file: string) => {
		for (const callback of fileWatchers.get(file)) {
			callback();
		}
	};

	let callbacks = fileWatchers.get(file);
	if (!callbacks) {
		callbacks = [];
		fileWatchers.set(file, callbacks);
		fs.watchFile(file, { interval: 1000 }, (current, previous) => {
			if ((previous && !current) || (!previous && !current)) {
				onFileChange(file);
				return;
			}
			if (previous && current && previous.mtime !== current.mtime) {
				onFileChange(file);
				return;
			}
		});
	}
	callbacks.push(callback);
	return toDisposable(() => {
		let allCallbacks = fileWatchers.get(file);
		allCallbacks.splice(allCallbacks.indexOf(callback), 1);
		if (!allCallbacks.length) {
			fs.unwatchFile(file);
			fileWatchers.delete(file);
		}
	});
}

function unWatchAllFiles(): void {
	fileWatchers.forEach((value, file) => fs.unwatchFile(file));
	fileWatchers.clear();
}
109

110
interface OutputChannel extends IOutputChannel {
S
Sandeep Somavarapu 已提交
111
	readonly file: URI;
112
	readonly onDidAppendedContent: Event<void>;
S
Sandeep Somavarapu 已提交
113
	readonly onDispose: Event<void>;
A
Alex Dima 已提交
114
	loadModel(): TPromise<ITextModel>;
115
}
116

117
abstract class AbstractFileOutputChannel extends Disposable {
118 119

	scrollLock: boolean = false;
120

121 122 123
	protected _onDidAppendedContent: Emitter<void> = new Emitter<void>();
	readonly onDidAppendedContent: Event<void> = this._onDidAppendedContent.event;

124 125 126
	protected _onDispose: Emitter<void> = new Emitter<void>();
	readonly onDispose: Event<void> = this._onDispose.event;

127
	protected modelUpdater: RunOnceScheduler;
A
Alex Dima 已提交
128
	protected model: ITextModel;
S
Sandeep Somavarapu 已提交
129
	readonly file: URI;
S
Sandeep Somavarapu 已提交
130

S
Sandeep Somavarapu 已提交
131
	protected startOffset: number = 0;
132
	protected endOffset: number = 0;
133 134

	constructor(
135
		protected readonly outputChannelIdentifier: IOutputChannelIdentifier,
136
		private readonly modelUri: URI,
S
Sandeep Somavarapu 已提交
137
		private mimeType: string,
138
		protected fileService: IFileService,
139 140
		protected modelService: IModelService,
		protected modeService: IModeService,
141 142
	) {
		super();
143
		this.file = this.outputChannelIdentifier.file;
144
		this.modelUpdater = new RunOnceScheduler(() => this.updateModel(), 300);
145
		this._register(toDisposable(() => this.modelUpdater.cancel()));
146 147 148 149 150 151 152 153 154 155
	}

	get id(): string {
		return this.outputChannelIdentifier.id;
	}

	get label(): string {
		return this.outputChannelIdentifier.label;
	}

156
	clear(): void {
157 158 159
		if (this.modelUpdater.isScheduled()) {
			this.modelUpdater.cancel();
		}
160 161
		if (this.model) {
			this.model.setValue('');
162
		}
163
		this.startOffset = this.endOffset;
164 165
	}

S
Sandeep Somavarapu 已提交
166
	protected createModel(content: string): ITextModel {
167
		if (this.model) {
S
Sandeep Somavarapu 已提交
168 169
			this.model.setValue(content);
		} else {
S
Sandeep Somavarapu 已提交
170
			this.model = this.modelService.createModel(content, this.modeService.getOrCreateMode(this.mimeType), this.modelUri);
S
Sandeep Somavarapu 已提交
171 172 173 174 175 176 177
			this.onModelCreated(this.model);
			const disposables: IDisposable[] = [];
			disposables.push(this.model.onWillDispose(() => {
				this.onModelWillDispose(this.model);
				this.model = null;
				dispose(disposables);
			}));
178
		}
S
Sandeep Somavarapu 已提交
179
		return this.model;
180 181 182 183 184 185 186
	}

	appendToModel(content: string): void {
		if (this.model && content) {
			const lastLine = this.model.getLineCount();
			const lastLineMaxColumn = this.model.getLineMaxColumn(lastLine);
			this.model.applyEdits([EditOperation.insert(new Position(lastLine, lastLineMaxColumn), content)]);
187
			this._onDidAppendedContent.fire();
188
		}
189 190
	}

A
Alex Dima 已提交
191 192
	protected onModelCreated(model: ITextModel) { }
	protected onModelWillDispose(model: ITextModel) { }
193 194 195 196 197
	protected updateModel() { }

	dispose(): void {
		this._onDispose.fire();
		super.dispose();
198
	}
199 200
}

201 202 203 204
/**
 * An output channel that stores appended messages in a backup file.
 */
class OutputChannelBackedByFile extends AbstractFileOutputChannel implements OutputChannel {
205

206 207 208
	private outputWriter: RotatingLogger;
	private appendedMessage = '';
	private loadingFromFileInProgress: boolean = false;
209
	private resettingDelayer: ThrottledDelayer<void>;
210
	private readonly rotatingFilePath: string;
211 212 213

	constructor(
		outputChannelIdentifier: IOutputChannelIdentifier,
214
		outputDir: string,
215
		modelUri: URI,
216 217 218
		@IFileService fileService: IFileService,
		@IModelService modelService: IModelService,
		@IModeService modeService: IModeService,
219
		@ILogService logService: ILogService
220
	) {
S
Sandeep Somavarapu 已提交
221
		super({ ...outputChannelIdentifier, file: URI.file(paths.join(outputDir, `${outputChannelIdentifier.id}.log`)) }, modelUri, OUTPUT_MIME, fileService, modelService, modeService);
222

223
		// Use one rotating file to check for main file reset
224
		this.outputWriter = new RotatingLogger(this.id, this.file.fsPath, 1024 * 1024 * 30, 1);
225
		this.outputWriter.clearFormatters();
226
		this.rotatingFilePath = `${outputChannelIdentifier.id}.1.log`;
227
		this._register(watchOutputDirectory(paths.dirname(this.file.fsPath), logService, (eventType, file) => this.onFileChangedInOutputDirector(eventType, file)));
228 229

		this.resettingDelayer = new ThrottledDelayer<void>(50);
230 231 232
	}

	append(message: string): void {
S
Sandeep Somavarapu 已提交
233
		// update end offset always as message is read
234
		this.endOffset = this.endOffset + Buffer.from(message).byteLength;
235 236 237
		if (this.loadingFromFileInProgress) {
			this.appendedMessage += message;
		} else {
238
			this.write(message);
239 240 241 242 243 244 245 246 247 248 249 250 251 252
			if (this.model) {
				this.appendedMessage += message;
				if (!this.modelUpdater.isScheduled()) {
					this.modelUpdater.schedule();
				}
			}
		}
	}

	clear(): void {
		super.clear();
		this.appendedMessage = '';
	}

A
Alex Dima 已提交
253
	loadModel(): TPromise<ITextModel> {
S
Sandeep Somavarapu 已提交
254 255 256 257 258 259 260
		this.loadingFromFileInProgress = true;
		if (this.modelUpdater.isScheduled()) {
			this.modelUpdater.cancel();
		}
		this.appendedMessage = '';
		return this.loadFile()
			.then(content => {
261
				if (this.endOffset !== this.startOffset + Buffer.from(content).byteLength) {
S
Sandeep Somavarapu 已提交
262 263 264 265 266 267 268 269 270 271 272 273 274 275
					// Queue content is not written into the file
					// Flush it and load file again
					this.flush();
					return this.loadFile();
				}
				return content;
			})
			.then(content => {
				if (this.appendedMessage) {
					this.write(this.appendedMessage);
					this.appendedMessage = '';
				}
				this.loadingFromFileInProgress = false;
				return this.createModel(content);
276
			});
277 278
	}

S
Sandeep Somavarapu 已提交
279 280 281 282 283
	private resetModel(): TPromise<void> {
		this.startOffset = 0;
		this.endOffset = 0;
		if (this.model) {
			return this.loadModel() as TPromise;
284
		}
S
Sandeep Somavarapu 已提交
285
		return TPromise.as(null);
286 287
	}

S
Sandeep Somavarapu 已提交
288 289 290
	private loadFile(): TPromise<string> {
		return this.fileService.resolveContent(this.file, { position: this.startOffset })
			.then(content => this.appendedMessage ? content.value + this.appendedMessage : content.value);
291 292
	}

S
Sandeep Somavarapu 已提交
293 294
	protected updateModel(): void {
		if (this.model && this.appendedMessage) {
295 296 297
			this.appendToModel(this.appendedMessage);
			this.appendedMessage = '';
		}
298 299
	}

300
	private onFileChangedInOutputDirector(eventType: string, fileName: string): void {
301
		// Check if rotating file has changed. It changes only when the main file exceeds its limit.
302
		if (this.rotatingFilePath === fileName) {
303
			this.resettingDelayer.trigger(() => this.resetModel());
304
		}
305
	}
306 307 308 309 310 311

	private write(content: string): void {
		this.outputWriter.critical(content);
	}

	private flush(): void {
S
Sandeep Somavarapu 已提交
312
		this.outputWriter.flush();
313
	}
314 315
}

316 317
class OutputFileListener extends Disposable {

M
Matt Bierner 已提交
318
	private readonly _onDidChange: Emitter<void> = new Emitter<void>();
319
	readonly onDidContentChange: Event<void> = this._onDidChange.event;
320

321
	private watching: boolean = false;
322 323 324 325 326 327 328 329 330
	private disposables: IDisposable[] = [];

	constructor(
		private readonly file: URI,
	) {
		super();
	}

	watch(): void {
331 332 333 334
		if (!this.watching) {
			this.disposables.push(watchFile(this.file.fsPath, () => this._onDidChange.fire()));
			this.watching = true;
		}
335 336 337
	}

	unwatch(): void {
338 339 340 341
		if (this.watching) {
			this.disposables = dispose(this.disposables);
			this.watching = false;
		}
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
	}

	dispose(): void {
		this.unwatch();
		super.dispose();
	}
}

/**
 * An output channel driven by a file and does not support appending messages.
 */
class FileOutputChannel extends AbstractFileOutputChannel implements OutputChannel {

	private readonly fileHandler: OutputFileListener;

	private updateInProgress: boolean = false;
358 359 360

	constructor(
		outputChannelIdentifier: IOutputChannelIdentifier,
361
		modelUri: URI,
362 363
		@IFileService fileService: IFileService,
		@IModelService modelService: IModelService,
364 365
		@IModeService modeService: IModeService,
		@ILogService logService: ILogService,
366
	) {
S
Sandeep Somavarapu 已提交
367
		super(outputChannelIdentifier, modelUri, LOG_MIME, fileService, modelService, modeService);
368

369
		this.fileHandler = this._register(new OutputFileListener(this.file));
370 371
		this._register(this.fileHandler.onDidContentChange(() => this.onDidContentChange()));
		this._register(toDisposable(() => this.fileHandler.unwatch()));
372 373
	}

S
Sandeep Somavarapu 已提交
374 375 376
	loadModel(): TPromise<ITextModel> {
		return this.fileService.resolveContent(this.file, { position: this.startOffset })
			.then(content => {
377
				this.endOffset = this.startOffset + Buffer.from(content.value).byteLength;
S
Sandeep Somavarapu 已提交
378 379 380 381
				return this.createModel(content.value);
			});
	}

382
	append(message: string): void {
383 384 385 386 387 388 389
		throw new Error('Not supported');
	}

	protected updateModel(): void {
		if (this.model) {
			this.fileService.resolveContent(this.file, { position: this.endOffset })
				.then(content => {
S
Sandeep Somavarapu 已提交
390
					if (content.value) {
391
						this.endOffset = this.endOffset + Buffer.from(content.value).byteLength;
S
Sandeep Somavarapu 已提交
392 393
						this.appendToModel(content.value);
					}
394 395 396 397
					this.updateInProgress = false;
				}, () => this.updateInProgress = false);
		} else {
			this.updateInProgress = false;
398 399 400
		}
	}

A
Alex Dima 已提交
401
	protected onModelCreated(model: ITextModel): void {
402
		this.fileHandler.watch();
403 404
	}

A
Alex Dima 已提交
405
	protected onModelWillDispose(model: ITextModel): void {
406
		this.fileHandler.unwatch();
407 408
	}

409 410 411 412
	private onDidContentChange(): void {
		if (!this.updateInProgress) {
			this.updateInProgress = true;
			this.modelUpdater.schedule();
413 414 415 416
		}
	}
}

417
export class OutputService extends Disposable implements IOutputService, ITextModelContentProvider {
418

419 420 421
	public _serviceBrand: any;

	private channels: Map<string, OutputChannel> = new Map<string, OutputChannel>();
S
Sandeep Somavarapu 已提交
422 423
	private activeChannelIdInStorage: string;
	private activeChannel: IOutputChannel;
424
	private readonly outputDir: string;
425

M
Matt Bierner 已提交
426
	private readonly _onActiveOutputChannel: Emitter<string> = new Emitter<string>();
427 428 429
	readonly onActiveOutputChannel: Event<string> = this._onActiveOutputChannel.event;

	private _outputPanel: OutputPanel;
430 431

	constructor(
432 433 434 435 436
		@IStorageService private storageService: IStorageService,
		@IInstantiationService private instantiationService: IInstantiationService,
		@IPanelService private panelService: IPanelService,
		@IWorkspaceContextService contextService: IWorkspaceContextService,
		@ITextModelService textModelResolverService: ITextModelService,
437 438 439
		@IEnvironmentService environmentService: IEnvironmentService,
		@IWindowService windowService: IWindowService,
		@ITelemetryService private telemetryService: ITelemetryService,
S
Sandeep Somavarapu 已提交
440 441
		@ILogService private logService: ILogService,
		@ILifecycleService private lifecycleService: ILifecycleService,
442
	) {
443
		super();
S
Sandeep Somavarapu 已提交
444 445
		this.activeChannelIdInStorage = this.storageService.get(OUTPUT_ACTIVE_CHANNEL_KEY, StorageScope.WORKSPACE, null);
		this.outputDir = paths.join(environmentService.logsPath, `output_${windowService.getCurrentWindowId()}_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`);
446 447 448

		// Register as text model content provider for output
		textModelResolverService.registerTextModelContentProvider(OUTPUT_SCHEME, this);
S
Sandeep Somavarapu 已提交
449 450 451 452 453 454 455 456
		instantiationService.createInstance(OutputLinkProvider);

		// Create output channels for already registered channels
		const registry = Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels);
		for (const channelIdentifier of registry.getChannels()) {
			this.onDidRegisterChannel(channelIdentifier.id);
		}
		this._register(registry.onDidRegisterChannel(this.onDidRegisterChannel, this));
457 458 459

		panelService.onDidPanelOpen(this.onDidPanelOpen, this);
		panelService.onDidPanelClose(this.onDidPanelClose, this);
460

461
		this._register(toDisposable(() => unWatchAllFiles()));
S
Sandeep Somavarapu 已提交
462 463 464 465 466 467 468 469

		// Set active channel to first channel if not set
		if (!this.activeChannel) {
			const channels = this.getChannels();
			this.activeChannel = channels && channels.length > 0 ? this.getChannel(channels[0].id) : null;
		}

		this.lifecycleService.onShutdown(() => this.onShutdown());
470 471
	}

A
Alex Dima 已提交
472
	provideTextContent(resource: URI): TPromise<ITextModel> {
473
		const channel = <OutputChannel>this.getChannel(resource.fsPath);
474 475 476 477
		if (channel) {
			return channel.loadModel();
		}
		return TPromise.as(null);
478 479 480
	}

	showChannel(id: string, preserveFocus?: boolean): TPromise<void> {
S
Sandeep Somavarapu 已提交
481 482
		const channel = this.getChannel(id);
		if (!channel || this.isChannelShown(channel)) {
483 484 485
			return TPromise.as(null);
		}

S
Sandeep Somavarapu 已提交
486
		this.activeChannel = channel;
487
		let promise = TPromise.as(null);
S
Sandeep Somavarapu 已提交
488
		if (this.isPanelShown()) {
S
Sandeep Somavarapu 已提交
489
			this.doShowChannel(channel, preserveFocus);
490 491
		} else {
			promise = this.panelService.openPanel(OUTPUT_PANEL_ID) as TPromise;
492
		}
493
		return promise.then(() => this._onActiveOutputChannel.fire(id));
494 495
	}

496 497 498 499 500 501
	getChannel(id: string): IOutputChannel {
		return this.channels.get(id);
	}

	getChannels(): IOutputChannelIdentifier[] {
		return Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels).getChannels();
502 503
	}

504
	getActiveChannel(): IOutputChannel {
S
Sandeep Somavarapu 已提交
505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529
		return this.activeChannel;
	}

	private onDidRegisterChannel(channelId: string): void {
		const channel = this.createChannel(channelId);
		this.channels.set(channelId, channel);
		if (this.activeChannelIdInStorage === channelId) {
			this.activeChannel = channel;
			this.onDidPanelOpen(this.panelService.getActivePanel());
		}
	}

	private onDidPanelOpen(panel: IPanel): void {
		if (panel && panel.getId() === OUTPUT_PANEL_ID) {
			this._outputPanel = <OutputPanel>this.panelService.getActivePanel();
			if (this.activeChannel) {
				this.doShowChannel(this.activeChannel, true);
			}
		}
	}

	private onDidPanelClose(panel: IPanel): void {
		if (this._outputPanel && panel.getId() === OUTPUT_PANEL_ID) {
			this._outputPanel.clearInput();
		}
530
	}
531

532
	private createChannel(id: string): OutputChannel {
533
		const channelDisposables: IDisposable[] = [];
534
		const channel = this.instantiateChannel(id);
535 536 537
		channel.onDidAppendedContent(() => {
			if (!channel.scrollLock) {
				const panel = this.panelService.getActivePanel();
S
Sandeep Somavarapu 已提交
538
				if (panel && panel.getId() === OUTPUT_PANEL_ID && this.isChannelShown(channel)) {
539 540 541 542
					(<OutputPanel>panel).revealLastLine();
				}
			}
		}, channelDisposables);
543 544
		channel.onDispose(() => {
			Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels).removeChannel(id);
S
Sandeep Somavarapu 已提交
545
			if (this.activeChannel === channel) {
546
				const channels = this.getChannels();
S
Sandeep Somavarapu 已提交
547 548 549
				if (this.isPanelShown() && channels.length) {
					this.doShowChannel(this.getChannel(channels[0].id), true);
					this._onActiveOutputChannel.fire(channels[0].id);
550 551 552 553 554 555 556 557 558 559 560 561
				} else {
					this._onActiveOutputChannel.fire(void 0);
				}
			}
			dispose(channelDisposables);
		}, channelDisposables);

		return channel;
	}

	private instantiateChannel(id: string): OutputChannel {
		const channelData = Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels).getChannel(id);
S
Sandeep Somavarapu 已提交
562 563 564 565 566
		if (!channelData) {
			this.logService.error(`Channel '${id}' is not registered yet`);
			throw new Error(`Channel '${id}' is not registered yet`);
		}

567
		const uri = URI.from({ scheme: OUTPUT_SCHEME, path: id });
568
		if (channelData && channelData.file) {
569
			return this.instantiationService.createInstance(FileOutputChannel, channelData, uri);
570
		}
571
		try {
572
			return this.instantiationService.createInstance(OutputChannelBackedByFile, { id, label: channelData ? channelData.label : '' }, this.outputDir, uri);
573 574 575 576 577
		} catch (e) {
			this.logService.error(e);
			this.telemetryService.publicLog('output.used.bufferedChannel');
			return this.instantiationService.createInstance(BufferredOutputChannel, { id, label: channelData ? channelData.label : '' });
		}
578
	}
579

S
Sandeep Somavarapu 已提交
580
	private doShowChannel(channel: IOutputChannel, preserveFocus: boolean): void {
581
		if (this._outputPanel) {
S
Sandeep Somavarapu 已提交
582
			this._outputPanel.setInput(this.createInput(channel), EditorOptions.create({ preserveFocus: preserveFocus }));
583 584 585 586 587 588
			if (!preserveFocus) {
				this._outputPanel.focus();
			}
		}
	}

S
Sandeep Somavarapu 已提交
589
	private isChannelShown(channel: IOutputChannel): boolean {
S
Sandeep Somavarapu 已提交
590 591 592 593
		return this.isPanelShown() && this.activeChannel === channel;
	}

	private isPanelShown(): boolean {
S
Sandeep Somavarapu 已提交
594
		const panel = this.panelService.getActivePanel();
S
Sandeep Somavarapu 已提交
595
		return panel && panel.getId() === OUTPUT_PANEL_ID;
S
Sandeep Somavarapu 已提交
596 597 598 599 600 601 602 603 604 605 606 607
	}

	private createInput(channel: IOutputChannel): ResourceEditorInput {
		const resource = URI.from({ scheme: OUTPUT_SCHEME, path: channel.id });
		return this.instantiationService.createInstance(ResourceEditorInput, nls.localize('output', "{0} - Output", channel.label), nls.localize('channel', "Output channel for '{0}'", channel.label), resource);
	}

	onShutdown(): void {
		if (this.activeChannel) {
			this.storageService.store(OUTPUT_ACTIVE_CHANNEL_KEY, this.activeChannel.id, StorageScope.WORKSPACE);
		}
		this.dispose();
608
	}
609 610
}

611 612 613 614 615
export class LogContentProvider {

	private channels: Map<string, OutputChannel> = new Map<string, OutputChannel>();

	constructor(
616
		@IInstantiationService private instantiationService: IInstantiationService
617 618 619
	) {
	}

A
Alex Dima 已提交
620
	provideTextContent(resource: URI): TPromise<ITextModel> {
621 622 623 624 625 626 627 628 629 630 631 632 633
		if (resource.scheme === LOG_SCHEME) {
			let channel = this.getChannel(resource);
			if (channel) {
				return channel.loadModel();
			}
		}
		return TPromise.as(null);
	}

	private getChannel(resource: URI): OutputChannel {
		const id = resource.path;
		let channel = this.channels.get(id);
		if (!channel) {
634
			const channelDisposables: IDisposable[] = [];
635 636 637 638 639 640 641 642
			channel = this.instantiationService.createInstance(FileOutputChannel, { id, label: '', file: resource.with({ scheme: Schemas.file }) }, resource);
			channel.onDispose(() => dispose(channelDisposables), channelDisposables);
			this.channels.set(id, channel);
		}
		return channel;
	}
}

643 644 645 646 647
// Remove this channel when there are no issues using Output channel backed by file
class BufferredOutputChannel extends Disposable implements OutputChannel {

	readonly id: string;
	readonly label: string;
S
Sandeep Somavarapu 已提交
648
	readonly file: URI = null;
649 650
	scrollLock: boolean = false;

651 652 653
	protected _onDidAppendedContent: Emitter<void> = new Emitter<void>();
	readonly onDidAppendedContent: Event<void> = this._onDidAppendedContent.event;

M
Matt Bierner 已提交
654
	private readonly _onDispose: Emitter<void> = new Emitter<void>();
655 656 657
	readonly onDispose: Event<void> = this._onDispose.event;

	private modelUpdater: RunOnceScheduler;
A
Alex Dima 已提交
658
	private model: ITextModel;
659 660 661 662 663 664
	private readonly bufferredContent: BufferedContent;
	private lastReadId: number = void 0;

	constructor(
		protected readonly outputChannelIdentifier: IOutputChannelIdentifier,
		@IModelService private modelService: IModelService,
665
		@IModeService private modeService: IModeService
666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696
	) {
		super();

		this.id = outputChannelIdentifier.id;
		this.label = outputChannelIdentifier.label;

		this.modelUpdater = new RunOnceScheduler(() => this.updateModel(), 300);
		this._register(toDisposable(() => this.modelUpdater.cancel()));

		this.bufferredContent = new BufferedContent();
		this._register(toDisposable(() => this.bufferredContent.clear()));
	}

	append(output: string) {
		this.bufferredContent.append(output);
		if (!this.modelUpdater.isScheduled()) {
			this.modelUpdater.schedule();
		}
	}

	clear(): void {
		if (this.modelUpdater.isScheduled()) {
			this.modelUpdater.cancel();
		}
		if (this.model) {
			this.model.setValue('');
		}
		this.bufferredContent.clear();
		this.lastReadId = void 0;
	}

A
Alex Dima 已提交
697
	loadModel(): TPromise<ITextModel> {
698 699 700 701 702 703 704 705 706 707
		const { value, id } = this.bufferredContent.getDelta(this.lastReadId);
		if (this.model) {
			this.model.setValue(value);
		} else {
			this.model = this.createModel(value);
		}
		this.lastReadId = id;
		return TPromise.as(this.model);
	}

A
Alex Dima 已提交
708
	private createModel(content: string): ITextModel {
709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724
		const model = this.modelService.createModel(content, this.modeService.getOrCreateMode(OUTPUT_MIME), URI.from({ scheme: OUTPUT_SCHEME, path: this.id }));
		const disposables: IDisposable[] = [];
		disposables.push(model.onWillDispose(() => {
			this.model = null;
			dispose(disposables);
		}));
		return model;
	}

	private updateModel(): void {
		if (this.model) {
			const { value, id } = this.bufferredContent.getDelta(this.lastReadId);
			this.lastReadId = id;
			const lastLine = this.model.getLineCount();
			const lastLineMaxColumn = this.model.getLineMaxColumn(lastLine);
			this.model.applyEdits([EditOperation.insert(new Position(lastLine, lastLineMaxColumn), value)]);
725
			this._onDidAppendedContent.fire();
726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781
		}
	}

	dispose(): void {
		this._onDispose.fire();
		super.dispose();
	}
}

class BufferedContent {

	private data: string[] = [];
	private dataIds: number[] = [];
	private idPool = 0;
	private length = 0;

	public append(content: string): void {
		this.data.push(content);
		this.dataIds.push(++this.idPool);
		this.length += content.length;
		this.trim();
	}

	public clear(): void {
		this.data.length = 0;
		this.dataIds.length = 0;
		this.length = 0;
	}

	private trim(): void {
		if (this.length < MAX_OUTPUT_LENGTH * 1.2) {
			return;
		}

		while (this.length > MAX_OUTPUT_LENGTH) {
			this.dataIds.shift();
			const removed = this.data.shift();
			this.length -= removed.length;
		}
	}

	public getDelta(previousId?: number): { value: string, id: number } {
		let idx = -1;
		if (previousId !== void 0) {
			idx = binarySearch(this.dataIds, previousId, (a, b) => a - b);
		}

		const id = this.idPool;
		if (idx >= 0) {
			const value = strings.removeAnsiEscapeCodes(this.data.slice(idx + 1).join(''));
			return { value, id };
		} else {
			const value = strings.removeAnsiEscapeCodes(this.data.join(''));
			return { value, id };
		}
	}
782
}