outputServices.ts 21.1 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
import * as extfs from 'vs/base/node/extfs';
9
import * as fs from 'fs';
J
Johannes Rieken 已提交
10
import { TPromise } from 'vs/base/common/winjs.base';
M
Matt Bierner 已提交
11
import { Event, Emitter } from 'vs/base/common/event';
12
import URI from 'vs/base/common/uri';
13
import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle';
J
Johannes Rieken 已提交
14 15
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
16
import { Registry } from 'vs/platform/registry/common/platform';
J
Johannes Rieken 已提交
17
import { EditorOptions } from 'vs/workbench/common/editor';
18
import { IOutputChannelIdentifier, IOutputChannel, IOutputService, Extensions, OUTPUT_PANEL_ID, IOutputChannelRegistry, OUTPUT_SCHEME, OUTPUT_MIME, LOG_SCHEME, LOG_MIME, CONTEXT_ACTIVE_LOG_OUTPUT } from 'vs/workbench/parts/output/common/output';
J
Johannes Rieken 已提交
19 20 21 22 23
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';
24
import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService';
A
Alex Dima 已提交
25
import { ITextModel } from 'vs/editor/common/model';
26
import { IModeService } from 'vs/editor/common/services/modeService';
27
import { RunOnceScheduler, ThrottledDelayer } from 'vs/base/common/async';
28 29
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { Position } from 'vs/editor/common/core/position';
30
import { IFileService } from 'vs/platform/files/common/files';
31 32
import { IPanel } from 'vs/workbench/common/panel';
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
33 34
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { toLocalISOString } from 'vs/base/common/date';
35
import { IWindowService } from 'vs/platform/windows/common/windows';
B
Benjamin Pasero 已提交
36
import { ILogService, IOutputWriter } from 'vs/platform/log/common/log';
37
import { Schemas } from 'vs/base/common/network';
S
Sandeep Somavarapu 已提交
38
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
S
Sandeep Somavarapu 已提交
39
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
B
Benjamin Pasero 已提交
40
import { createSpdLogOutputWriter } from 'vs/platform/log/node/spdlogService';
41 42

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

44
let watchingOutputDir = false;
45
let callbacks: ((eventType: string, fileName: string) => void)[] = [];
46 47 48
function watchOutputDirectory(outputDir: string, logService: ILogService, onChange: (eventType: string, fileName: string) => void): IDisposable {
	callbacks.push(onChange);
	if (!watchingOutputDir) {
49 50 51 52 53 54 55 56 57 58 59
		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) {
60 61
				watcher.removeAllListeners();
				watcher.close();
62 63
			}
		});
64
	}
65
	return toDisposable(() => { });
66 67
}

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
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();
}
107

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

115
abstract class AbstractFileOutputChannel extends Disposable {
116 117

	scrollLock: boolean = false;
118

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

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

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

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

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

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

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

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

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

	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)]);
185
			this._onDidAppendedContent.fire();
186
		}
187 188
	}

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

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

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

B
Benjamin Pasero 已提交
204
	private outputWriter: IOutputWriter;
205 206
	private appendedMessage = '';
	private loadingFromFileInProgress: boolean = false;
207
	private resettingDelayer: ThrottledDelayer<void>;
208
	private readonly rotatingFilePath: string;
209 210 211

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

221
		// Use one rotating file to check for main file reset
B
Benjamin Pasero 已提交
222
		this.outputWriter = createSpdLogOutputWriter(this.id, this.file.fsPath, 1024 * 1024 * 30, 1);
223
		this.outputWriter.clearFormatters();
224
		this.rotatingFilePath = `${outputChannelIdentifier.id}.1.log`;
225
		this._register(watchOutputDirectory(paths.dirname(this.file.fsPath), logService, (eventType, file) => this.onFileChangedInOutputDirector(eventType, file)));
226 227

		this.resettingDelayer = new ThrottledDelayer<void>(50);
228 229 230
	}

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

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

A
Alex Dima 已提交
251
	loadModel(): TPromise<ITextModel> {
S
Sandeep Somavarapu 已提交
252 253 254 255 256 257 258
		this.loadingFromFileInProgress = true;
		if (this.modelUpdater.isScheduled()) {
			this.modelUpdater.cancel();
		}
		this.appendedMessage = '';
		return this.loadFile()
			.then(content => {
259
				if (this.endOffset !== this.startOffset + Buffer.from(content).byteLength) {
S
Sandeep Somavarapu 已提交
260 261 262 263 264 265 266 267 268 269 270 271 272 273
					// 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);
274
			});
275 276
	}

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

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

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

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

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

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

314 315
class OutputFileListener extends Disposable {

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

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

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

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

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

	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;
356 357 358

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

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

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

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

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

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

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

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

415
export class OutputService extends Disposable implements IOutputService, ITextModelContentProvider {
416

417 418 419
	public _serviceBrand: any;

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

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

	private _outputPanel: OutputPanel;
428 429

	constructor(
430 431 432 433 434
		@IStorageService private storageService: IStorageService,
		@IInstantiationService private instantiationService: IInstantiationService,
		@IPanelService private panelService: IPanelService,
		@IWorkspaceContextService contextService: IWorkspaceContextService,
		@ITextModelService textModelResolverService: ITextModelService,
435 436
		@IEnvironmentService environmentService: IEnvironmentService,
		@IWindowService windowService: IWindowService,
S
Sandeep Somavarapu 已提交
437 438
		@ILogService private logService: ILogService,
		@ILifecycleService private lifecycleService: ILifecycleService,
S
Sandeep Somavarapu 已提交
439
		@IContextKeyService private contextKeyService: IContextKeyService,
440
	) {
441
		super();
S
Sandeep Somavarapu 已提交
442 443
		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, '')}`);
444 445 446

		// Register as text model content provider for output
		textModelResolverService.registerTextModelContentProvider(OUTPUT_SCHEME, this);
S
Sandeep Somavarapu 已提交
447 448 449 450 451 452 453 454
		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));
455 456 457

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

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

		// 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());
468 469
	}

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

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

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

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

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

502
	getActiveChannel(): IOutputChannel {
S
Sandeep Somavarapu 已提交
503 504 505 506 507 508 509 510
		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;
S
Sandeep Somavarapu 已提交
511 512
			this.onDidPanelOpen(this.panelService.getActivePanel())
				.then(() => this._onActiveOutputChannel.fire(channelId));
S
Sandeep Somavarapu 已提交
513 514 515
		}
	}

S
Sandeep Somavarapu 已提交
516
	private onDidPanelOpen(panel: IPanel): TPromise<void> {
S
Sandeep Somavarapu 已提交
517 518 519
		if (panel && panel.getId() === OUTPUT_PANEL_ID) {
			this._outputPanel = <OutputPanel>this.panelService.getActivePanel();
			if (this.activeChannel) {
S
Sandeep Somavarapu 已提交
520
				return this.doShowChannel(this.activeChannel, true);
S
Sandeep Somavarapu 已提交
521 522
			}
		}
S
Sandeep Somavarapu 已提交
523
		return TPromise.as(null);
S
Sandeep Somavarapu 已提交
524 525 526 527
	}

	private onDidPanelClose(panel: IPanel): void {
		if (this._outputPanel && panel.getId() === OUTPUT_PANEL_ID) {
S
Sandeep Somavarapu 已提交
528
			CONTEXT_ACTIVE_LOG_OUTPUT.bindTo(this.contextKeyService).set(false);
S
Sandeep Somavarapu 已提交
529 530
			this._outputPanel.clearInput();
		}
531
	}
532

533
	private createChannel(id: string): OutputChannel {
534
		const channelDisposables: IDisposable[] = [];
535
		const channel = this.instantiateChannel(id);
536 537 538
		channel.onDidAppendedContent(() => {
			if (!channel.scrollLock) {
				const panel = this.panelService.getActivePanel();
S
Sandeep Somavarapu 已提交
539
				if (panel && panel.getId() === OUTPUT_PANEL_ID && this.isChannelShown(channel)) {
540 541 542 543
					(<OutputPanel>panel).revealLastLine();
				}
			}
		}, channelDisposables);
544 545
		channel.onDispose(() => {
			Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels).removeChannel(id);
S
Sandeep Somavarapu 已提交
546
			if (this.activeChannel === channel) {
547
				const channels = this.getChannels();
S
Sandeep Somavarapu 已提交
548 549 550
				if (this.isPanelShown() && channels.length) {
					this.doShowChannel(this.getChannel(channels[0].id), true);
					this._onActiveOutputChannel.fire(channels[0].id);
551 552 553 554 555 556 557 558 559 560 561 562
				} 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 已提交
563 564 565 566 567
		if (!channelData) {
			this.logService.error(`Channel '${id}' is not registered yet`);
			throw new Error(`Channel '${id}' is not registered yet`);
		}

568
		const uri = URI.from({ scheme: OUTPUT_SCHEME, path: id });
569 570 571
		return channelData && channelData.file
			? this.instantiationService.createInstance(FileOutputChannel, channelData, uri)
			: this.instantiationService.createInstance(OutputChannelBackedByFile, { id, label: channelData ? channelData.label : '' }, this.outputDir, uri);
572
	}
573

S
Sandeep Somavarapu 已提交
574
	private doShowChannel(channel: IOutputChannel, preserveFocus: boolean): TPromise<void> {
575
		if (this._outputPanel) {
S
Sandeep Somavarapu 已提交
576 577 578 579 580 581 582
			CONTEXT_ACTIVE_LOG_OUTPUT.bindTo(this.contextKeyService).set(channel instanceof FileOutputChannel);
			return this._outputPanel.setInput(this.createInput(channel), EditorOptions.create({ preserveFocus: preserveFocus }))
				.then(() => {
					if (!preserveFocus) {
						this._outputPanel.focus();
					}
				});
583
		}
S
Sandeep Somavarapu 已提交
584
		return TPromise.as(null);
585 586
	}

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

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

	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();
606
	}
607 608
}

609 610 611 612 613
export class LogContentProvider {

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

	constructor(
614
		@IInstantiationService private instantiationService: IInstantiationService
615 616 617
	) {
	}

A
Alex Dima 已提交
618
	provideTextContent(resource: URI): TPromise<ITextModel> {
619 620 621 622 623 624 625 626 627 628 629 630 631
		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) {
632
			const channelDisposables: IDisposable[] = [];
633 634 635 636 637 638
			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;
	}
639
}