Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/output/browser/outputServices.ts
5282 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Event, Emitter } from '../../../../base/common/event.js';
7
import { Schemas } from '../../../../base/common/network.js';
8
import { URI } from '../../../../base/common/uri.js';
9
import { Disposable, DisposableMap } from '../../../../base/common/lifecycle.js';
10
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
11
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
12
import { Registry } from '../../../../platform/registry/common/platform.js';
13
import { IOutputChannel, IOutputService, OUTPUT_VIEW_ID, LOG_MIME, OUTPUT_MIME, OutputChannelUpdateMode, IOutputChannelDescriptor, Extensions, IOutputChannelRegistry, ACTIVE_OUTPUT_CHANNEL_CONTEXT, CONTEXT_ACTIVE_FILE_OUTPUT, CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE, CONTEXT_ACTIVE_OUTPUT_LEVEL, CONTEXT_ACTIVE_OUTPUT_LEVEL_IS_DEFAULT, IOutputViewFilters, SHOW_DEBUG_FILTER_CONTEXT, SHOW_ERROR_FILTER_CONTEXT, SHOW_INFO_FILTER_CONTEXT, SHOW_TRACE_FILTER_CONTEXT, SHOW_WARNING_FILTER_CONTEXT, CONTEXT_ACTIVE_LOG_FILE_OUTPUT, IMultiSourceOutputChannelDescriptor, isSingleSourceOutputChannelDescriptor, HIDE_CATEGORY_FILTER_CONTEXT, isMultiSourceOutputChannelDescriptor, ILogEntry } from '../../../services/output/common/output.js';
14
import { OutputLinkProvider } from './outputLinkProvider.js';
15
import { ITextModelService, ITextModelContentProvider } from '../../../../editor/common/services/resolverService.js';
16
import { ITextModel } from '../../../../editor/common/model.js';
17
import { ILogService, ILoggerService, LogLevel, LogLevelToString } from '../../../../platform/log/common/log.js';
18
import { ILifecycleService } from '../../../services/lifecycle/common/lifecycle.js';
19
import { DelegatedOutputChannelModel, FileOutputChannelModel, IOutputChannelModel, MultiFileOutputChannelModel } from '../common/outputChannelModel.js';
20
import { IViewsService } from '../../../services/views/common/viewsService.js';
21
import { OutputViewPane } from './outputView.js';
22
import { ILanguageService } from '../../../../editor/common/languages/language.js';
23
import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
24
import { IFileDialogService } from '../../../../platform/dialogs/common/dialogs.js';
25
import { IFileService } from '../../../../platform/files/common/files.js';
26
import { localize } from '../../../../nls.js';
27
import { joinPath } from '../../../../base/common/resources.js';
28
import { VSBuffer } from '../../../../base/common/buffer.js';
29
import { telemetryLogId } from '../../../../platform/telemetry/common/telemetryUtils.js';
30
import { toLocalISOString } from '../../../../base/common/date.js';
31
import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js';
32
import { IDefaultLogLevelsService } from '../../../services/log/common/defaultLogLevels.js';
33
34
const OUTPUT_ACTIVE_CHANNEL_KEY = 'output.activechannel';
35
36
class OutputChannel extends Disposable implements IOutputChannel {
37
38
scrollLock: boolean = false;
39
readonly model: IOutputChannelModel;
40
readonly id: string;
41
readonly label: string;
42
readonly uri: URI;
43
44
constructor(
45
readonly outputChannelDescriptor: IOutputChannelDescriptor,
46
private readonly outputLocation: URI,
47
private readonly outputDirPromise: Promise<void>,
48
@ILanguageService private readonly languageService: ILanguageService,
49
@IInstantiationService private readonly instantiationService: IInstantiationService,
50
) {
51
super();
52
this.id = outputChannelDescriptor.id;
53
this.label = outputChannelDescriptor.label;
54
this.uri = URI.from({ scheme: Schemas.outputChannel, path: this.id });
55
this.model = this._register(this.createOutputChannelModel(this.uri, outputChannelDescriptor));
56
}
57
58
private createOutputChannelModel(uri: URI, outputChannelDescriptor: IOutputChannelDescriptor): IOutputChannelModel {
59
const language = outputChannelDescriptor.languageId ? this.languageService.createById(outputChannelDescriptor.languageId) : this.languageService.createByMimeType(outputChannelDescriptor.log ? LOG_MIME : OUTPUT_MIME);
60
if (isMultiSourceOutputChannelDescriptor(outputChannelDescriptor)) {
61
return this.instantiationService.createInstance(MultiFileOutputChannelModel, uri, language, [...outputChannelDescriptor.source]);
62
}
63
if (isSingleSourceOutputChannelDescriptor(outputChannelDescriptor)) {
64
return this.instantiationService.createInstance(FileOutputChannelModel, uri, language, outputChannelDescriptor.source);
65
}
66
return this.instantiationService.createInstance(DelegatedOutputChannelModel, this.id, uri, language, this.outputLocation, this.outputDirPromise);
67
}
68
69
getLogEntries(): ReadonlyArray<ILogEntry> {
70
return this.model.getLogEntries();
71
}
72
73
append(output: string): void {
74
this.model.append(output);
75
}
76
77
update(mode: OutputChannelUpdateMode, till?: number): void {
78
this.model.update(mode, till, true);
79
}
80
81
clear(): void {
82
this.model.clear();
83
}
84
85
replace(value: string): void {
86
this.model.replace(value);
87
}
88
}
89
90
interface IOutputFilterOptions {
91
filterHistory: string[];
92
trace: boolean;
93
debug: boolean;
94
info: boolean;
95
warning: boolean;
96
error: boolean;
97
sources: string;
98
}
99
100
class OutputViewFilters extends Disposable implements IOutputViewFilters {
101
102
private readonly _onDidChange = this._register(new Emitter<void>());
103
readonly onDidChange = this._onDidChange.event;
104
105
constructor(
106
options: IOutputFilterOptions,
107
private readonly contextKeyService: IContextKeyService
108
) {
109
super();
110
111
this._trace = SHOW_TRACE_FILTER_CONTEXT.bindTo(this.contextKeyService);
112
this._trace.set(options.trace);
113
114
this._debug = SHOW_DEBUG_FILTER_CONTEXT.bindTo(this.contextKeyService);
115
this._debug.set(options.debug);
116
117
this._info = SHOW_INFO_FILTER_CONTEXT.bindTo(this.contextKeyService);
118
this._info.set(options.info);
119
120
this._warning = SHOW_WARNING_FILTER_CONTEXT.bindTo(this.contextKeyService);
121
this._warning.set(options.warning);
122
123
this._error = SHOW_ERROR_FILTER_CONTEXT.bindTo(this.contextKeyService);
124
this._error.set(options.error);
125
126
this._categories = HIDE_CATEGORY_FILTER_CONTEXT.bindTo(this.contextKeyService);
127
this._categories.set(options.sources);
128
129
this.filterHistory = options.filterHistory;
130
}
131
132
filterHistory: string[];
133
134
private _filterText = '';
135
private _includePatterns: string[] = [];
136
private _excludePatterns: string[] = [];
137
get text(): string {
138
return this._filterText;
139
}
140
set text(filterText: string) {
141
if (this._filterText !== filterText) {
142
this._filterText = filterText;
143
const { includePatterns, excludePatterns } = this.parseText(filterText);
144
this._includePatterns = includePatterns;
145
this._excludePatterns = excludePatterns;
146
this._onDidChange.fire();
147
}
148
}
149
private parseText(filterText: string): { includePatterns: string[]; excludePatterns: string[] } {
150
const includePatterns: string[] = [];
151
const excludePatterns: string[] = [];
152
153
// Parse patterns respecting quoted strings
154
const patterns = this.splitByCommaRespectingQuotes(filterText);
155
156
for (const pattern of patterns) {
157
const trimmed = pattern.trim();
158
if (trimmed.length === 0) {
159
continue;
160
}
161
162
if (trimmed.startsWith('!')) {
163
// Negative filter - remove the ! prefix
164
const negativePattern = trimmed.substring(1).trim();
165
if (negativePattern.length > 0) {
166
excludePatterns.push(negativePattern);
167
}
168
} else {
169
includePatterns.push(trimmed);
170
}
171
}
172
173
return { includePatterns, excludePatterns };
174
}
175
176
get includePatterns(): string[] {
177
return this._includePatterns;
178
}
179
180
get excludePatterns(): string[] {
181
return this._excludePatterns;
182
}
183
184
private splitByCommaRespectingQuotes(text: string): string[] {
185
const patterns: string[] = [];
186
let current = '';
187
let inQuotes = false;
188
let quoteChar = '';
189
190
for (let i = 0; i < text.length; i++) {
191
const char = text[i];
192
193
if (!inQuotes && (char === '"')) {
194
// Start of quoted string
195
inQuotes = true;
196
quoteChar = char;
197
current += char;
198
} else if (inQuotes && char === quoteChar) {
199
// End of quoted string
200
inQuotes = false;
201
current += char;
202
} else if (!inQuotes && char === ',') {
203
// Comma outside quotes - split here
204
if (current.length > 0) {
205
patterns.push(current);
206
}
207
current = '';
208
} else {
209
current += char;
210
}
211
}
212
213
// Add the last pattern
214
if (current.length > 0) {
215
patterns.push(current);
216
}
217
218
return patterns;
219
}
220
221
private readonly _trace: IContextKey<boolean>;
222
get trace(): boolean {
223
return !!this._trace.get();
224
}
225
set trace(trace: boolean) {
226
if (this._trace.get() !== trace) {
227
this._trace.set(trace);
228
this._onDidChange.fire();
229
}
230
}
231
232
private readonly _debug: IContextKey<boolean>;
233
get debug(): boolean {
234
return !!this._debug.get();
235
}
236
set debug(debug: boolean) {
237
if (this._debug.get() !== debug) {
238
this._debug.set(debug);
239
this._onDidChange.fire();
240
}
241
}
242
243
private readonly _info: IContextKey<boolean>;
244
get info(): boolean {
245
return !!this._info.get();
246
}
247
set info(info: boolean) {
248
if (this._info.get() !== info) {
249
this._info.set(info);
250
this._onDidChange.fire();
251
}
252
}
253
254
private readonly _warning: IContextKey<boolean>;
255
get warning(): boolean {
256
return !!this._warning.get();
257
}
258
set warning(warning: boolean) {
259
if (this._warning.get() !== warning) {
260
this._warning.set(warning);
261
this._onDidChange.fire();
262
}
263
}
264
265
private readonly _error: IContextKey<boolean>;
266
get error(): boolean {
267
return !!this._error.get();
268
}
269
set error(error: boolean) {
270
if (this._error.get() !== error) {
271
this._error.set(error);
272
this._onDidChange.fire();
273
}
274
}
275
276
private readonly _categories: IContextKey<string>;
277
get categories(): string {
278
return this._categories.get() || ',';
279
}
280
set categories(categories: string) {
281
this._categories.set(categories);
282
this._onDidChange.fire();
283
}
284
285
toggleCategory(category: string): void {
286
const categories = this.categories;
287
if (this.hasCategory(category)) {
288
this.categories = categories.replace(`,${category},`, ',');
289
} else {
290
this.categories = `${categories}${category},`;
291
}
292
}
293
294
hasCategory(category: string): boolean {
295
if (category === ',') {
296
return false;
297
}
298
return this.categories.includes(`,${category},`);
299
}
300
}
301
302
export class OutputService extends Disposable implements IOutputService, ITextModelContentProvider {
303
304
declare readonly _serviceBrand: undefined;
305
306
private readonly channels = this._register(new DisposableMap<string, OutputChannel>());
307
private activeChannelIdInStorage: string;
308
private activeChannel?: OutputChannel;
309
310
private readonly _onActiveOutputChannel = this._register(new Emitter<string>());
311
readonly onActiveOutputChannel: Event<string> = this._onActiveOutputChannel.event;
312
313
private readonly activeOutputChannelContext: IContextKey<string>;
314
private readonly activeFileOutputChannelContext: IContextKey<boolean>;
315
private readonly activeLogOutputChannelContext: IContextKey<boolean>;
316
private readonly activeOutputChannelLevelSettableContext: IContextKey<boolean>;
317
private readonly activeOutputChannelLevelContext: IContextKey<string>;
318
private readonly activeOutputChannelLevelIsDefaultContext: IContextKey<boolean>;
319
320
private readonly outputLocation: URI;
321
322
readonly filters: OutputViewFilters;
323
324
constructor(
325
@IStorageService private readonly storageService: IStorageService,
326
@IInstantiationService private readonly instantiationService: IInstantiationService,
327
@ITextModelService private readonly textModelService: ITextModelService,
328
@ILogService private readonly logService: ILogService,
329
@ILoggerService private readonly loggerService: ILoggerService,
330
@ILifecycleService private readonly lifecycleService: ILifecycleService,
331
@IViewsService private readonly viewsService: IViewsService,
332
@IContextKeyService contextKeyService: IContextKeyService,
333
@IDefaultLogLevelsService private readonly defaultLogLevelsService: IDefaultLogLevelsService,
334
@IFileDialogService private readonly fileDialogService: IFileDialogService,
335
@IFileService private readonly fileService: IFileService,
336
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService
337
) {
338
super();
339
this.activeChannelIdInStorage = this.storageService.get(OUTPUT_ACTIVE_CHANNEL_KEY, StorageScope.WORKSPACE, '');
340
this.activeOutputChannelContext = ACTIVE_OUTPUT_CHANNEL_CONTEXT.bindTo(contextKeyService);
341
this.activeOutputChannelContext.set(this.activeChannelIdInStorage);
342
this._register(this.onActiveOutputChannel(channel => this.activeOutputChannelContext.set(channel)));
343
344
this.activeFileOutputChannelContext = CONTEXT_ACTIVE_FILE_OUTPUT.bindTo(contextKeyService);
345
this.activeLogOutputChannelContext = CONTEXT_ACTIVE_LOG_FILE_OUTPUT.bindTo(contextKeyService);
346
this.activeOutputChannelLevelSettableContext = CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE.bindTo(contextKeyService);
347
this.activeOutputChannelLevelContext = CONTEXT_ACTIVE_OUTPUT_LEVEL.bindTo(contextKeyService);
348
this.activeOutputChannelLevelIsDefaultContext = CONTEXT_ACTIVE_OUTPUT_LEVEL_IS_DEFAULT.bindTo(contextKeyService);
349
350
this.outputLocation = joinPath(environmentService.windowLogsPath, `output_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`);
351
352
// Register as text model content provider for output
353
this._register(textModelService.registerTextModelContentProvider(Schemas.outputChannel, this));
354
this._register(instantiationService.createInstance(OutputLinkProvider));
355
356
// Create output channels for already registered channels
357
const registry = Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels);
358
for (const channelIdentifier of registry.getChannels()) {
359
this.onDidRegisterChannel(channelIdentifier.id);
360
}
361
this._register(registry.onDidRegisterChannel(id => this.onDidRegisterChannel(id)));
362
this._register(registry.onDidUpdateChannelSources(channel => this.onDidUpdateChannelSources(channel)));
363
this._register(registry.onDidRemoveChannel(channel => this.onDidRemoveChannel(channel)));
364
365
// Set active channel to first channel if not set
366
if (!this.activeChannel) {
367
const channels = this.getChannelDescriptors();
368
this.setActiveChannel(channels && channels.length > 0 ? this.getChannel(channels[0].id) : undefined);
369
}
370
371
this._register(Event.filter(this.viewsService.onDidChangeViewVisibility, e => e.id === OUTPUT_VIEW_ID && e.visible)(() => {
372
if (this.activeChannel) {
373
this.viewsService.getActiveViewWithId<OutputViewPane>(OUTPUT_VIEW_ID)?.showChannel(this.activeChannel, true);
374
}
375
}));
376
377
this._register(this.loggerService.onDidChangeLogLevel(() => {
378
this.setLevelContext();
379
this.setLevelIsDefaultContext();
380
}));
381
this._register(this.defaultLogLevelsService.onDidChangeDefaultLogLevels(() => {
382
this.setLevelIsDefaultContext();
383
}));
384
385
this._register(this.lifecycleService.onDidShutdown(() => this.dispose()));
386
387
this.filters = this._register(new OutputViewFilters({
388
filterHistory: [],
389
trace: true,
390
debug: true,
391
info: true,
392
warning: true,
393
error: true,
394
sources: '',
395
}, contextKeyService));
396
}
397
398
provideTextContent(resource: URI): Promise<ITextModel> | null {
399
const channel = <OutputChannel>this.getChannel(resource.path);
400
if (channel) {
401
return channel.model.loadModel();
402
}
403
return null;
404
}
405
406
async showChannel(id: string, preserveFocus?: boolean): Promise<void> {
407
const channel = this.getChannel(id);
408
if (this.activeChannel?.id !== channel?.id) {
409
this.setActiveChannel(channel);
410
this._onActiveOutputChannel.fire(id);
411
}
412
const outputView = await this.viewsService.openView<OutputViewPane>(OUTPUT_VIEW_ID, !preserveFocus);
413
if (outputView && channel) {
414
outputView.showChannel(channel, !!preserveFocus);
415
}
416
}
417
418
getChannel(id: string): OutputChannel | undefined {
419
return this.channels.get(id);
420
}
421
422
getChannelDescriptor(id: string): IOutputChannelDescriptor | undefined {
423
return Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels).getChannel(id);
424
}
425
426
getChannelDescriptors(): IOutputChannelDescriptor[] {
427
return Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels).getChannels();
428
}
429
430
getActiveChannel(): IOutputChannel | undefined {
431
return this.activeChannel;
432
}
433
434
canSetLogLevel(channel: IOutputChannelDescriptor): boolean {
435
return channel.log && channel.id !== telemetryLogId;
436
}
437
438
getLogLevel(channel: IOutputChannelDescriptor): LogLevel | undefined {
439
if (!channel.log) {
440
return undefined;
441
}
442
const sources = isSingleSourceOutputChannelDescriptor(channel) ? [channel.source] : isMultiSourceOutputChannelDescriptor(channel) ? channel.source : [];
443
if (sources.length === 0) {
444
return undefined;
445
}
446
447
const logLevel = this.loggerService.getLogLevel();
448
return sources.reduce((prev, curr) => Math.min(prev, this.loggerService.getLogLevel(curr.resource) ?? logLevel), LogLevel.Error);
449
}
450
451
setLogLevel(channel: IOutputChannelDescriptor, logLevel: LogLevel): void {
452
if (!channel.log) {
453
return;
454
}
455
const sources = isSingleSourceOutputChannelDescriptor(channel) ? [channel.source] : isMultiSourceOutputChannelDescriptor(channel) ? channel.source : [];
456
if (sources.length === 0) {
457
return;
458
}
459
for (const source of sources) {
460
this.loggerService.setLogLevel(source.resource, logLevel);
461
}
462
}
463
464
registerCompoundLogChannel(descriptors: IOutputChannelDescriptor[]): string {
465
const outputChannelRegistry = Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels);
466
descriptors.sort((a, b) => a.label.localeCompare(b.label));
467
const id = descriptors.map(r => r.id.toLowerCase()).join('-');
468
if (!outputChannelRegistry.getChannel(id)) {
469
outputChannelRegistry.registerChannel({
470
id,
471
label: descriptors.map(r => r.label).join(', '),
472
log: descriptors.some(r => r.log),
473
user: true,
474
source: descriptors.map(descriptor => {
475
if (isSingleSourceOutputChannelDescriptor(descriptor)) {
476
return [{ resource: descriptor.source.resource, name: descriptor.source.name ?? descriptor.label }];
477
}
478
if (isMultiSourceOutputChannelDescriptor(descriptor)) {
479
return descriptor.source;
480
}
481
const channel = this.getChannel(descriptor.id);
482
if (channel) {
483
return channel.model.source;
484
}
485
return [];
486
}).flat(),
487
});
488
}
489
return id;
490
}
491
492
async saveOutputAs(outputPath?: URI, ...channels: IOutputChannelDescriptor[]): Promise<void> {
493
let channel: IOutputChannel | undefined;
494
if (channels.length > 1) {
495
const compoundChannelId = this.registerCompoundLogChannel(channels);
496
channel = this.getChannel(compoundChannelId);
497
} else {
498
channel = this.getChannel(channels[0].id);
499
}
500
501
if (!channel) {
502
return;
503
}
504
505
try {
506
let uri: URI | undefined = outputPath;
507
if (!uri) {
508
const name = channels.length > 1 ? 'output' : channels[0].label;
509
uri = await this.fileDialogService.showSaveDialog({
510
title: localize('saveLog.dialogTitle', "Save Output As"),
511
availableFileSystems: [Schemas.file],
512
defaultUri: joinPath(await this.fileDialogService.defaultFilePath(), `${name}.log`),
513
filters: [{
514
name,
515
extensions: ['log']
516
}]
517
});
518
}
519
520
if (!uri) {
521
return;
522
}
523
524
const modelRef = await this.textModelService.createModelReference(channel.uri);
525
try {
526
await this.fileService.writeFile(uri, VSBuffer.fromString(modelRef.object.textEditorModel.getValue()));
527
} finally {
528
modelRef.dispose();
529
}
530
return;
531
}
532
finally {
533
if (channels.length > 1) {
534
Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels).removeChannel(channel.id);
535
}
536
}
537
}
538
539
private async onDidRegisterChannel(channelId: string): Promise<void> {
540
const channel = this.createChannel(channelId);
541
this.channels.set(channelId, channel);
542
if (!this.activeChannel || this.activeChannelIdInStorage === channelId) {
543
this.setActiveChannel(channel);
544
this._onActiveOutputChannel.fire(channelId);
545
const outputView = this.viewsService.getActiveViewWithId<OutputViewPane>(OUTPUT_VIEW_ID);
546
outputView?.showChannel(channel, true);
547
}
548
}
549
550
private onDidUpdateChannelSources(channel: IMultiSourceOutputChannelDescriptor): void {
551
const outputChannel = this.channels.get(channel.id);
552
if (outputChannel) {
553
outputChannel.model.updateChannelSources(channel.source);
554
}
555
}
556
557
private onDidRemoveChannel(channel: IOutputChannelDescriptor): void {
558
if (this.activeChannel?.id === channel.id) {
559
const channels = this.getChannelDescriptors();
560
if (channels[0]) {
561
this.showChannel(channels[0].id);
562
}
563
}
564
this.channels.deleteAndDispose(channel.id);
565
}
566
567
private createChannel(id: string): OutputChannel {
568
const channel = this.instantiateChannel(id);
569
this._register(Event.once(channel.model.onDispose)(() => {
570
if (this.activeChannel === channel) {
571
const channels = this.getChannelDescriptors();
572
const channel = channels.length ? this.getChannel(channels[0].id) : undefined;
573
if (channel && this.viewsService.isViewVisible(OUTPUT_VIEW_ID)) {
574
this.showChannel(channel.id);
575
} else {
576
this.setActiveChannel(undefined);
577
}
578
}
579
Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels).removeChannel(id);
580
}));
581
582
return channel;
583
}
584
585
private outputFolderCreationPromise: Promise<void> | null = null;
586
private instantiateChannel(id: string): OutputChannel {
587
const channelData = Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels).getChannel(id);
588
if (!channelData) {
589
this.logService.error(`Channel '${id}' is not registered yet`);
590
throw new Error(`Channel '${id}' is not registered yet`);
591
}
592
if (!this.outputFolderCreationPromise) {
593
this.outputFolderCreationPromise = this.fileService.createFolder(this.outputLocation).then(() => undefined);
594
}
595
return this.instantiationService.createInstance(OutputChannel, channelData, this.outputLocation, this.outputFolderCreationPromise);
596
}
597
598
private setLevelContext(): void {
599
const descriptor = this.activeChannel?.outputChannelDescriptor;
600
const channelLogLevel = descriptor ? this.getLogLevel(descriptor) : undefined;
601
this.activeOutputChannelLevelContext.set(channelLogLevel !== undefined ? LogLevelToString(channelLogLevel) : '');
602
}
603
604
private async setLevelIsDefaultContext(): Promise<void> {
605
const descriptor = this.activeChannel?.outputChannelDescriptor;
606
const channelLogLevel = descriptor ? this.getLogLevel(descriptor) : undefined;
607
if (channelLogLevel !== undefined) {
608
const channelDefaultLogLevel = this.defaultLogLevelsService.getDefaultLogLevel(descriptor?.extensionId);
609
this.activeOutputChannelLevelIsDefaultContext.set(channelDefaultLogLevel === channelLogLevel);
610
} else {
611
this.activeOutputChannelLevelIsDefaultContext.set(false);
612
}
613
}
614
615
private setActiveChannel(channel: OutputChannel | undefined): void {
616
this.activeChannel = channel;
617
const descriptor = channel?.outputChannelDescriptor;
618
this.activeFileOutputChannelContext.set(!!descriptor && isSingleSourceOutputChannelDescriptor(descriptor));
619
this.activeLogOutputChannelContext.set(!!descriptor?.log);
620
this.activeOutputChannelLevelSettableContext.set(descriptor !== undefined && this.canSetLogLevel(descriptor));
621
this.setLevelIsDefaultContext();
622
this.setLevelContext();
623
624
if (this.activeChannel) {
625
this.storageService.store(OUTPUT_ACTIVE_CHANNEL_KEY, this.activeChannel.id, StorageScope.WORKSPACE, StorageTarget.MACHINE);
626
} else {
627
this.storageService.remove(OUTPUT_ACTIVE_CHANNEL_KEY, StorageScope.WORKSPACE);
628
}
629
}
630
}
631
632