Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/output/browser/output.contribution.ts
3296 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 * as nls from '../../../../nls.js';
7
import { KeyMod, KeyChord, KeyCode } from '../../../../base/common/keyCodes.js';
8
import { ModesRegistry } from '../../../../editor/common/languages/modesRegistry.js';
9
import { Registry } from '../../../../platform/registry/common/platform.js';
10
import { MenuId, registerAction2, Action2, MenuRegistry } from '../../../../platform/actions/common/actions.js';
11
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
12
import { OutputService } from './outputServices.js';
13
import { OUTPUT_MODE_ID, OUTPUT_MIME, OUTPUT_VIEW_ID, IOutputService, CONTEXT_IN_OUTPUT, LOG_MODE_ID, LOG_MIME, CONTEXT_OUTPUT_SCROLL_LOCK, IOutputChannelDescriptor, ACTIVE_OUTPUT_CHANNEL_CONTEXT, CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE, IOutputChannelRegistry, Extensions, CONTEXT_ACTIVE_OUTPUT_LEVEL, CONTEXT_ACTIVE_OUTPUT_LEVEL_IS_DEFAULT, SHOW_INFO_FILTER_CONTEXT, SHOW_TRACE_FILTER_CONTEXT, SHOW_DEBUG_FILTER_CONTEXT, SHOW_ERROR_FILTER_CONTEXT, SHOW_WARNING_FILTER_CONTEXT, OUTPUT_FILTER_FOCUS_CONTEXT, CONTEXT_ACTIVE_LOG_FILE_OUTPUT, isSingleSourceOutputChannelDescriptor } from '../../../services/output/common/output.js';
14
import { OutputViewPane } from './outputView.js';
15
import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';
16
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from '../../../common/contributions.js';
17
import { LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js';
18
import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
19
import { ViewContainer, IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions, IViewsRegistry } from '../../../common/views.js';
20
import { IViewsService } from '../../../services/views/common/viewsService.js';
21
import { ViewPaneContainer } from '../../../browser/parts/views/viewPaneContainer.js';
22
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from '../../../../platform/configuration/common/configurationRegistry.js';
23
import { IQuickPickItem, IQuickInputService, IQuickPickSeparator, QuickPickInput } from '../../../../platform/quickinput/common/quickInput.js';
24
import { AUX_WINDOW_GROUP, AUX_WINDOW_GROUP_TYPE, IEditorService } from '../../../services/editor/common/editorService.js';
25
import { ContextKeyExpr, ContextKeyExpression } from '../../../../platform/contextkey/common/contextkey.js';
26
import { Codicon } from '../../../../base/common/codicons.js';
27
import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js';
28
import { Categories } from '../../../../platform/action/common/actionCommonCategories.js';
29
import { Disposable, dispose, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
30
import { AccessibilitySignal, IAccessibilitySignalService } from '../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js';
31
import { ILoggerService, LogLevel, LogLevelToLocalizedString, LogLevelToString } from '../../../../platform/log/common/log.js';
32
import { IDefaultLogLevelsService } from '../../logs/common/defaultLogLevels.js';
33
import { KeybindingsRegistry, KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
34
import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js';
35
import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from '../../../../platform/accessibility/common/accessibility.js';
36
import { IsWindowsContext } from '../../../../platform/contextkey/common/contextkeys.js';
37
import { FocusedViewContext } from '../../../common/contextkeys.js';
38
import { localize, localize2 } from '../../../../nls.js';
39
import { viewFilterSubmenu } from '../../../browser/parts/views/viewFilter.js';
40
import { ViewAction } from '../../../browser/parts/views/viewPane.js';
41
import { INotificationService } from '../../../../platform/notification/common/notification.js';
42
import { IFileDialogService } from '../../../../platform/dialogs/common/dialogs.js';
43
import { basename } from '../../../../base/common/resources.js';
44
45
const IMPORTED_LOG_ID_PREFIX = 'importedLog.';
46
47
// Register Service
48
registerSingleton(IOutputService, OutputService, InstantiationType.Delayed);
49
50
// Register Output Mode
51
ModesRegistry.registerLanguage({
52
id: OUTPUT_MODE_ID,
53
extensions: [],
54
mimetypes: [OUTPUT_MIME]
55
});
56
57
// Register Log Output Mode
58
ModesRegistry.registerLanguage({
59
id: LOG_MODE_ID,
60
extensions: [],
61
mimetypes: [LOG_MIME]
62
});
63
64
// register output container
65
const outputViewIcon = registerIcon('output-view-icon', Codicon.output, nls.localize('outputViewIcon', 'View icon of the output view.'));
66
const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
67
id: OUTPUT_VIEW_ID,
68
title: nls.localize2('output', "Output"),
69
icon: outputViewIcon,
70
order: 1,
71
ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [OUTPUT_VIEW_ID, { mergeViewWithContainerWhenSingleView: true }]),
72
storageId: OUTPUT_VIEW_ID,
73
hideIfEmpty: true,
74
}, ViewContainerLocation.Panel, { doNotRegisterOpenCommand: true });
75
76
Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry).registerViews([{
77
id: OUTPUT_VIEW_ID,
78
name: nls.localize2('output', "Output"),
79
containerIcon: outputViewIcon,
80
canMoveView: true,
81
canToggleVisibility: true,
82
ctorDescriptor: new SyncDescriptor(OutputViewPane),
83
openCommandActionDescriptor: {
84
id: 'workbench.action.output.toggleOutput',
85
mnemonicTitle: nls.localize({ key: 'miToggleOutput', comment: ['&& denotes a mnemonic'] }, "&&Output"),
86
keybindings: {
87
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyU,
88
linux: {
89
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyH) // On Ubuntu Ctrl+Shift+U is taken by some global OS command
90
}
91
},
92
order: 1,
93
}
94
}], VIEW_CONTAINER);
95
96
class OutputContribution extends Disposable implements IWorkbenchContribution {
97
constructor(
98
@IOutputService private readonly outputService: IOutputService,
99
@IEditorService private readonly editorService: IEditorService,
100
) {
101
super();
102
this.registerActions();
103
}
104
105
private registerActions(): void {
106
this.registerSwitchOutputAction();
107
this.registerAddCompoundLogAction();
108
this.registerRemoveLogAction();
109
this.registerShowOutputChannelsAction();
110
this.registerClearOutputAction();
111
this.registerToggleAutoScrollAction();
112
this.registerOpenActiveOutputFileAction();
113
this.registerOpenActiveOutputFileInAuxWindowAction();
114
this.registerSaveActiveOutputAsAction();
115
this.registerShowLogsAction();
116
this.registerOpenLogFileAction();
117
this.registerConfigureActiveOutputLogLevelAction();
118
this.registerLogLevelFilterActions();
119
this.registerClearFilterActions();
120
this.registerExportLogsAction();
121
this.registerImportLogAction();
122
}
123
124
private registerSwitchOutputAction(): void {
125
this._register(registerAction2(class extends Action2 {
126
constructor() {
127
super({
128
id: `workbench.output.action.switchBetweenOutputs`,
129
title: nls.localize('switchBetweenOutputs.label', "Switch Output"),
130
});
131
}
132
async run(accessor: ServicesAccessor, channelId: string): Promise<void> {
133
if (channelId) {
134
accessor.get(IOutputService).showChannel(channelId, true);
135
}
136
}
137
}));
138
const switchOutputMenu = new MenuId('workbench.output.menu.switchOutput');
139
this._register(MenuRegistry.appendMenuItem(MenuId.ViewTitle, {
140
submenu: switchOutputMenu,
141
title: nls.localize('switchToOutput.label', "Switch Output"),
142
group: 'navigation',
143
when: ContextKeyExpr.equals('view', OUTPUT_VIEW_ID),
144
order: 1,
145
isSelection: true
146
}));
147
const registeredChannels = new Map<string, IDisposable>();
148
this._register(toDisposable(() => dispose(registeredChannels.values())));
149
const registerOutputChannels = (channels: IOutputChannelDescriptor[]) => {
150
for (const channel of channels) {
151
const title = channel.label;
152
const group = channel.user ? '2_user_outputchannels' : channel.extensionId ? '0_ext_outputchannels' : '1_core_outputchannels';
153
registeredChannels.set(channel.id, registerAction2(class extends Action2 {
154
constructor() {
155
super({
156
id: `workbench.action.output.show.${channel.id}`,
157
title,
158
toggled: ACTIVE_OUTPUT_CHANNEL_CONTEXT.isEqualTo(channel.id),
159
menu: {
160
id: switchOutputMenu,
161
group,
162
}
163
});
164
}
165
async run(accessor: ServicesAccessor): Promise<void> {
166
return accessor.get(IOutputService).showChannel(channel.id, true);
167
}
168
}));
169
}
170
};
171
registerOutputChannels(this.outputService.getChannelDescriptors());
172
const outputChannelRegistry = Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels);
173
this._register(outputChannelRegistry.onDidRegisterChannel(e => {
174
const channel = this.outputService.getChannelDescriptor(e);
175
if (channel) {
176
registerOutputChannels([channel]);
177
}
178
}));
179
this._register(outputChannelRegistry.onDidRemoveChannel(e => {
180
registeredChannels.get(e.id)?.dispose();
181
registeredChannels.delete(e.id);
182
}));
183
}
184
185
private registerAddCompoundLogAction(): void {
186
this._register(registerAction2(class extends Action2 {
187
constructor() {
188
super({
189
id: 'workbench.action.output.addCompoundLog',
190
title: nls.localize2('addCompoundLog', "Add Compound Log..."),
191
category: nls.localize2('output', "Output"),
192
f1: true,
193
menu: [{
194
id: MenuId.ViewTitle,
195
when: ContextKeyExpr.equals('view', OUTPUT_VIEW_ID),
196
group: '2_add',
197
}],
198
});
199
}
200
async run(accessor: ServicesAccessor): Promise<void> {
201
const outputService = accessor.get(IOutputService);
202
const quickInputService = accessor.get(IQuickInputService);
203
204
const extensionLogs: IOutputChannelDescriptor[] = [], logs: IOutputChannelDescriptor[] = [];
205
for (const channel of outputService.getChannelDescriptors()) {
206
if (channel.log && !channel.user) {
207
if (channel.extensionId) {
208
extensionLogs.push(channel);
209
} else {
210
logs.push(channel);
211
}
212
}
213
}
214
const entries: Array<IOutputChannelDescriptor | IQuickPickSeparator> = [];
215
for (const log of logs.sort((a, b) => a.label.localeCompare(b.label))) {
216
entries.push(log);
217
}
218
if (extensionLogs.length && logs.length) {
219
entries.push({ type: 'separator', label: nls.localize('extensionLogs', "Extension Logs") });
220
}
221
for (const log of extensionLogs.sort((a, b) => a.label.localeCompare(b.label))) {
222
entries.push(log);
223
}
224
const result = await quickInputService.pick(entries, { placeHolder: nls.localize('selectlog', "Select Log"), canPickMany: true });
225
if (result?.length) {
226
outputService.showChannel(outputService.registerCompoundLogChannel(result));
227
}
228
}
229
}));
230
}
231
232
private registerRemoveLogAction(): void {
233
this._register(registerAction2(class extends Action2 {
234
constructor() {
235
super({
236
id: 'workbench.action.output.remove',
237
title: nls.localize2('removeLog', "Remove Output..."),
238
category: nls.localize2('output', "Output"),
239
f1: true
240
});
241
}
242
async run(accessor: ServicesAccessor): Promise<void> {
243
const outputService = accessor.get(IOutputService);
244
const quickInputService = accessor.get(IQuickInputService);
245
const notificationService = accessor.get(INotificationService);
246
const entries: Array<IOutputChannelDescriptor> = outputService.getChannelDescriptors().filter(channel => channel.user);
247
if (entries.length === 0) {
248
notificationService.info(nls.localize('nocustumoutput', "No custom outputs to remove."));
249
return;
250
}
251
const result = await quickInputService.pick(entries, { placeHolder: nls.localize('selectlog', "Select Log"), canPickMany: true });
252
if (!result?.length) {
253
return;
254
}
255
const outputChannelRegistry = Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels);
256
for (const channel of result) {
257
outputChannelRegistry.removeChannel(channel.id);
258
}
259
}
260
}));
261
}
262
263
private registerShowOutputChannelsAction(): void {
264
this._register(registerAction2(class extends Action2 {
265
constructor() {
266
super({
267
id: 'workbench.action.showOutputChannels',
268
title: nls.localize2('showOutputChannels', "Show Output Channels..."),
269
category: nls.localize2('output', "Output"),
270
f1: true
271
});
272
}
273
async run(accessor: ServicesAccessor): Promise<void> {
274
const outputService = accessor.get(IOutputService);
275
const quickInputService = accessor.get(IQuickInputService);
276
const extensionChannels = [], coreChannels = [];
277
for (const channel of outputService.getChannelDescriptors()) {
278
if (channel.extensionId) {
279
extensionChannels.push(channel);
280
} else {
281
coreChannels.push(channel);
282
}
283
}
284
const entries: ({ id: string; label: string } | IQuickPickSeparator)[] = [];
285
for (const { id, label } of extensionChannels) {
286
entries.push({ id, label });
287
}
288
if (extensionChannels.length && coreChannels.length) {
289
entries.push({ type: 'separator' });
290
}
291
for (const { id, label } of coreChannels) {
292
entries.push({ id, label });
293
}
294
const entry = await quickInputService.pick(entries, { placeHolder: nls.localize('selectOutput', "Select Output Channel") });
295
if (entry) {
296
return outputService.showChannel(entry.id);
297
}
298
}
299
}));
300
}
301
302
private registerClearOutputAction(): void {
303
this._register(registerAction2(class extends Action2 {
304
constructor() {
305
super({
306
id: `workbench.output.action.clearOutput`,
307
title: nls.localize2('clearOutput.label', "Clear Output"),
308
category: Categories.View,
309
menu: [{
310
id: MenuId.ViewTitle,
311
when: ContextKeyExpr.equals('view', OUTPUT_VIEW_ID),
312
group: 'navigation',
313
order: 2
314
}, {
315
id: MenuId.CommandPalette
316
}, {
317
id: MenuId.EditorContext,
318
when: CONTEXT_IN_OUTPUT
319
}],
320
icon: Codicon.clearAll
321
});
322
}
323
async run(accessor: ServicesAccessor): Promise<void> {
324
const outputService = accessor.get(IOutputService);
325
const accessibilitySignalService = accessor.get(IAccessibilitySignalService);
326
const activeChannel = outputService.getActiveChannel();
327
if (activeChannel) {
328
activeChannel.clear();
329
accessibilitySignalService.playSignal(AccessibilitySignal.clear);
330
}
331
}
332
}));
333
}
334
335
private registerToggleAutoScrollAction(): void {
336
this._register(registerAction2(class extends Action2 {
337
constructor() {
338
super({
339
id: `workbench.output.action.toggleAutoScroll`,
340
title: nls.localize2('toggleAutoScroll', "Toggle Auto Scrolling"),
341
tooltip: nls.localize('outputScrollOff', "Turn Auto Scrolling Off"),
342
menu: {
343
id: MenuId.ViewTitle,
344
when: ContextKeyExpr.and(ContextKeyExpr.equals('view', OUTPUT_VIEW_ID)),
345
group: 'navigation',
346
order: 3,
347
},
348
icon: Codicon.lock,
349
toggled: {
350
condition: CONTEXT_OUTPUT_SCROLL_LOCK,
351
icon: Codicon.unlock,
352
tooltip: nls.localize('outputScrollOn', "Turn Auto Scrolling On")
353
}
354
});
355
}
356
async run(accessor: ServicesAccessor): Promise<void> {
357
const outputView = accessor.get(IViewsService).getActiveViewWithId<OutputViewPane>(OUTPUT_VIEW_ID)!;
358
outputView.scrollLock = !outputView.scrollLock;
359
}
360
}));
361
}
362
363
private registerOpenActiveOutputFileAction(): void {
364
const that = this;
365
this._register(registerAction2(class extends Action2 {
366
constructor() {
367
super({
368
id: `workbench.action.openActiveLogOutputFile`,
369
title: nls.localize2('openActiveOutputFile', "Open Output in Editor"),
370
menu: [{
371
id: MenuId.ViewTitle,
372
when: ContextKeyExpr.equals('view', OUTPUT_VIEW_ID),
373
group: 'navigation',
374
order: 4,
375
isHiddenByDefault: true
376
}],
377
icon: Codicon.goToFile,
378
});
379
}
380
async run(): Promise<void> {
381
that.openActiveOutput();
382
}
383
}));
384
}
385
386
private registerOpenActiveOutputFileInAuxWindowAction(): void {
387
const that = this;
388
this._register(registerAction2(class extends Action2 {
389
constructor() {
390
super({
391
id: `workbench.action.openActiveLogOutputFileInNewWindow`,
392
title: nls.localize2('openActiveOutputFileInNewWindow', "Open Output in New Window"),
393
menu: [{
394
id: MenuId.ViewTitle,
395
when: ContextKeyExpr.equals('view', OUTPUT_VIEW_ID),
396
group: 'navigation',
397
order: 5,
398
isHiddenByDefault: true
399
}],
400
icon: Codicon.emptyWindow,
401
});
402
}
403
async run(): Promise<void> {
404
that.openActiveOutput(AUX_WINDOW_GROUP);
405
}
406
}));
407
}
408
409
private registerSaveActiveOutputAsAction(): void {
410
this._register(registerAction2(class extends Action2 {
411
constructor() {
412
super({
413
id: `workbench.action.saveActiveLogOutputAs`,
414
title: nls.localize2('saveActiveOutputAs', "Save Output As..."),
415
menu: [{
416
id: MenuId.ViewTitle,
417
when: ContextKeyExpr.equals('view', OUTPUT_VIEW_ID),
418
group: '1_export',
419
order: 1
420
}],
421
});
422
}
423
async run(accessor: ServicesAccessor): Promise<void> {
424
const outputService = accessor.get(IOutputService);
425
const channel = outputService.getActiveChannel();
426
if (channel) {
427
const descriptor = outputService.getChannelDescriptors().find(c => c.id === channel.id);
428
if (descriptor) {
429
await outputService.saveOutputAs(descriptor);
430
}
431
}
432
}
433
}));
434
}
435
436
private async openActiveOutput(group?: AUX_WINDOW_GROUP_TYPE): Promise<void> {
437
const channel = this.outputService.getActiveChannel();
438
if (channel) {
439
await this.editorService.openEditor({
440
resource: channel.uri,
441
options: {
442
pinned: true,
443
},
444
}, group);
445
}
446
}
447
448
private registerConfigureActiveOutputLogLevelAction(): void {
449
const logLevelMenu = new MenuId('workbench.output.menu.logLevel');
450
this._register(MenuRegistry.appendMenuItem(MenuId.ViewTitle, {
451
submenu: logLevelMenu,
452
title: nls.localize('logLevel.label', "Set Log Level..."),
453
group: 'navigation',
454
when: ContextKeyExpr.and(ContextKeyExpr.equals('view', OUTPUT_VIEW_ID), CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE),
455
icon: Codicon.gear,
456
order: 6
457
}));
458
459
let order = 0;
460
const registerLogLevel = (logLevel: LogLevel) => {
461
this._register(registerAction2(class extends Action2 {
462
constructor() {
463
super({
464
id: `workbench.action.output.activeOutputLogLevel.${logLevel}`,
465
title: LogLevelToLocalizedString(logLevel).value,
466
toggled: CONTEXT_ACTIVE_OUTPUT_LEVEL.isEqualTo(LogLevelToString(logLevel)),
467
menu: {
468
id: logLevelMenu,
469
order: order++,
470
group: '0_level'
471
}
472
});
473
}
474
async run(accessor: ServicesAccessor): Promise<void> {
475
const outputService = accessor.get(IOutputService);
476
const channel = outputService.getActiveChannel();
477
if (channel) {
478
const channelDescriptor = outputService.getChannelDescriptor(channel.id);
479
if (channelDescriptor) {
480
outputService.setLogLevel(channelDescriptor, logLevel);
481
}
482
}
483
}
484
}));
485
};
486
487
registerLogLevel(LogLevel.Trace);
488
registerLogLevel(LogLevel.Debug);
489
registerLogLevel(LogLevel.Info);
490
registerLogLevel(LogLevel.Warning);
491
registerLogLevel(LogLevel.Error);
492
registerLogLevel(LogLevel.Off);
493
494
this._register(registerAction2(class extends Action2 {
495
constructor() {
496
super({
497
id: `workbench.action.output.activeOutputLogLevelDefault`,
498
title: nls.localize('logLevelDefault.label', "Set As Default"),
499
menu: {
500
id: logLevelMenu,
501
order,
502
group: '1_default'
503
},
504
precondition: CONTEXT_ACTIVE_OUTPUT_LEVEL_IS_DEFAULT.negate()
505
});
506
}
507
async run(accessor: ServicesAccessor): Promise<void> {
508
const outputService = accessor.get(IOutputService);
509
const loggerService = accessor.get(ILoggerService);
510
const defaultLogLevelsService = accessor.get(IDefaultLogLevelsService);
511
const channel = outputService.getActiveChannel();
512
if (channel) {
513
const channelDescriptor = outputService.getChannelDescriptor(channel.id);
514
if (channelDescriptor && isSingleSourceOutputChannelDescriptor(channelDescriptor)) {
515
const logLevel = loggerService.getLogLevel(channelDescriptor.source.resource);
516
return await defaultLogLevelsService.setDefaultLogLevel(logLevel, channelDescriptor.extensionId);
517
}
518
}
519
}
520
}));
521
}
522
523
private registerShowLogsAction(): void {
524
this._register(registerAction2(class extends Action2 {
525
constructor() {
526
super({
527
id: 'workbench.action.showLogs',
528
title: nls.localize2('showLogs', "Show Logs..."),
529
category: Categories.Developer,
530
menu: {
531
id: MenuId.CommandPalette,
532
},
533
});
534
}
535
async run(accessor: ServicesAccessor): Promise<void> {
536
const outputService = accessor.get(IOutputService);
537
const quickInputService = accessor.get(IQuickInputService);
538
const extensionLogs = [], logs = [];
539
for (const channel of outputService.getChannelDescriptors()) {
540
if (channel.log) {
541
if (channel.extensionId) {
542
extensionLogs.push(channel);
543
} else {
544
logs.push(channel);
545
}
546
}
547
}
548
const entries: ({ id: string; label: string } | IQuickPickSeparator)[] = [];
549
for (const { id, label } of logs) {
550
entries.push({ id, label });
551
}
552
if (extensionLogs.length && logs.length) {
553
entries.push({ type: 'separator', label: nls.localize('extensionLogs', "Extension Logs") });
554
}
555
for (const { id, label } of extensionLogs) {
556
entries.push({ id, label });
557
}
558
const entry = await quickInputService.pick(entries, { placeHolder: nls.localize('selectlog', "Select Log") });
559
if (entry) {
560
return outputService.showChannel(entry.id);
561
}
562
}
563
}));
564
}
565
566
private registerOpenLogFileAction(): void {
567
this._register(registerAction2(class extends Action2 {
568
constructor() {
569
super({
570
id: 'workbench.action.openLogFile',
571
title: nls.localize2('openLogFile', "Open Log..."),
572
category: Categories.Developer,
573
menu: {
574
id: MenuId.CommandPalette,
575
},
576
metadata: {
577
description: 'workbench.action.openLogFile',
578
args: [{
579
name: 'logFile',
580
schema: {
581
markdownDescription: nls.localize('logFile', "The id of the log file to open, for example `\"window\"`. Currently the best way to get this is to get the ID by checking the `workbench.action.output.show.<id>` commands"),
582
type: 'string'
583
}
584
}]
585
},
586
});
587
}
588
async run(accessor: ServicesAccessor, args?: unknown): Promise<void> {
589
const outputService = accessor.get(IOutputService);
590
const quickInputService = accessor.get(IQuickInputService);
591
const editorService = accessor.get(IEditorService);
592
let entry: IQuickPickItem | undefined;
593
const argName = args && typeof args === 'string' ? args : undefined;
594
const extensionChannels: IQuickPickItem[] = [];
595
const coreChannels: IQuickPickItem[] = [];
596
for (const c of outputService.getChannelDescriptors()) {
597
if (c.log) {
598
const e = { id: c.id, label: c.label };
599
if (c.extensionId) {
600
extensionChannels.push(e);
601
} else {
602
coreChannels.push(e);
603
}
604
if (e.id === argName) {
605
entry = e;
606
}
607
}
608
}
609
if (!entry) {
610
const entries: QuickPickInput[] = [...extensionChannels.sort((a, b) => a.label.localeCompare(b.label))];
611
if (entries.length && coreChannels.length) {
612
entries.push({ type: 'separator' });
613
entries.push(...coreChannels.sort((a, b) => a.label.localeCompare(b.label)));
614
}
615
entry = <IQuickPickItem | undefined>await quickInputService.pick(entries, { placeHolder: nls.localize('selectlogFile', "Select Log File") });
616
}
617
if (entry?.id) {
618
const channel = outputService.getChannel(entry.id);
619
if (channel) {
620
await editorService.openEditor({
621
resource: channel.uri,
622
options: {
623
pinned: true,
624
}
625
});
626
}
627
}
628
}
629
}));
630
}
631
632
private registerLogLevelFilterActions(): void {
633
let order = 0;
634
const registerLogLevel = (logLevel: LogLevel, toggled: ContextKeyExpression) => {
635
this._register(registerAction2(class extends ViewAction<OutputViewPane> {
636
constructor() {
637
super({
638
id: `workbench.actions.${OUTPUT_VIEW_ID}.toggle.${LogLevelToString(logLevel)}`,
639
title: LogLevelToLocalizedString(logLevel).value,
640
metadata: {
641
description: localize2('toggleTraceDescription', "Show or hide {0} messages in the output", LogLevelToString(logLevel))
642
},
643
toggled,
644
menu: {
645
id: viewFilterSubmenu,
646
group: '2_log_filter',
647
when: ContextKeyExpr.and(ContextKeyExpr.equals('view', OUTPUT_VIEW_ID), CONTEXT_ACTIVE_LOG_FILE_OUTPUT),
648
order: order++
649
},
650
viewId: OUTPUT_VIEW_ID
651
});
652
}
653
async runInView(serviceAccessor: ServicesAccessor, view: OutputViewPane): Promise<void> {
654
this.toggleLogLevelFilter(serviceAccessor.get(IOutputService), logLevel);
655
}
656
private toggleLogLevelFilter(outputService: IOutputService, logLevel: LogLevel): void {
657
switch (logLevel) {
658
case LogLevel.Trace:
659
outputService.filters.trace = !outputService.filters.trace;
660
break;
661
case LogLevel.Debug:
662
outputService.filters.debug = !outputService.filters.debug;
663
break;
664
case LogLevel.Info:
665
outputService.filters.info = !outputService.filters.info;
666
break;
667
case LogLevel.Warning:
668
outputService.filters.warning = !outputService.filters.warning;
669
break;
670
case LogLevel.Error:
671
outputService.filters.error = !outputService.filters.error;
672
break;
673
}
674
}
675
}));
676
};
677
678
registerLogLevel(LogLevel.Trace, SHOW_TRACE_FILTER_CONTEXT);
679
registerLogLevel(LogLevel.Debug, SHOW_DEBUG_FILTER_CONTEXT);
680
registerLogLevel(LogLevel.Info, SHOW_INFO_FILTER_CONTEXT);
681
registerLogLevel(LogLevel.Warning, SHOW_WARNING_FILTER_CONTEXT);
682
registerLogLevel(LogLevel.Error, SHOW_ERROR_FILTER_CONTEXT);
683
}
684
685
private registerClearFilterActions(): void {
686
this._register(registerAction2(class extends ViewAction<OutputViewPane> {
687
constructor() {
688
super({
689
id: `workbench.actions.${OUTPUT_VIEW_ID}.clearFilterText`,
690
title: localize('clearFiltersText', "Clear filters text"),
691
keybinding: {
692
when: OUTPUT_FILTER_FOCUS_CONTEXT,
693
weight: KeybindingWeight.WorkbenchContrib,
694
primary: KeyCode.Escape
695
},
696
viewId: OUTPUT_VIEW_ID
697
});
698
}
699
async runInView(serviceAccessor: ServicesAccessor, outputView: OutputViewPane): Promise<void> {
700
outputView.clearFilterText();
701
}
702
}));
703
}
704
705
private registerExportLogsAction(): void {
706
this._register(registerAction2(class extends Action2 {
707
constructor() {
708
super({
709
id: `workbench.action.exportLogs`,
710
title: nls.localize2('exportLogs', "Export Logs..."),
711
f1: true,
712
category: Categories.Developer,
713
menu: [{
714
id: MenuId.ViewTitle,
715
when: ContextKeyExpr.equals('view', OUTPUT_VIEW_ID),
716
group: '1_export',
717
order: 2,
718
}],
719
});
720
}
721
async run(accessor: ServicesAccessor): Promise<void> {
722
const outputService = accessor.get(IOutputService);
723
const quickInputService = accessor.get(IQuickInputService);
724
const extensionLogs: IOutputChannelDescriptor[] = [], logs: IOutputChannelDescriptor[] = [], userLogs: IOutputChannelDescriptor[] = [];
725
for (const channel of outputService.getChannelDescriptors()) {
726
if (channel.log) {
727
if (channel.extensionId) {
728
extensionLogs.push(channel);
729
} else if (channel.user) {
730
userLogs.push(channel);
731
} else {
732
logs.push(channel);
733
}
734
}
735
}
736
const entries: Array<IOutputChannelDescriptor | IQuickPickSeparator> = [];
737
for (const log of logs.sort((a, b) => a.label.localeCompare(b.label))) {
738
entries.push(log);
739
}
740
if (extensionLogs.length && logs.length) {
741
entries.push({ type: 'separator', label: nls.localize('extensionLogs', "Extension Logs") });
742
}
743
for (const log of extensionLogs.sort((a, b) => a.label.localeCompare(b.label))) {
744
entries.push(log);
745
}
746
if (userLogs.length && (extensionLogs.length || logs.length)) {
747
entries.push({ type: 'separator', label: nls.localize('userLogs', "User Logs") });
748
}
749
for (const log of userLogs.sort((a, b) => a.label.localeCompare(b.label))) {
750
entries.push(log);
751
}
752
const result = await quickInputService.pick(entries, { placeHolder: nls.localize('selectlog', "Select Log"), canPickMany: true });
753
if (result?.length) {
754
await outputService.saveOutputAs(...result);
755
}
756
}
757
}));
758
}
759
760
private registerImportLogAction(): void {
761
this._register(registerAction2(class extends Action2 {
762
constructor() {
763
super({
764
id: `workbench.action.importLog`,
765
title: nls.localize2('importLog', "Import Log..."),
766
f1: true,
767
category: Categories.Developer,
768
menu: [{
769
id: MenuId.ViewTitle,
770
when: ContextKeyExpr.equals('view', OUTPUT_VIEW_ID),
771
group: '2_add',
772
order: 2,
773
}],
774
});
775
}
776
async run(accessor: ServicesAccessor): Promise<void> {
777
const outputService = accessor.get(IOutputService);
778
const fileDialogService = accessor.get(IFileDialogService);
779
const result = await fileDialogService.showOpenDialog({
780
title: nls.localize('importLogFile', "Import Log File"),
781
canSelectFiles: true,
782
canSelectFolders: false,
783
canSelectMany: true,
784
filters: [{
785
name: nls.localize('logFiles', "Log Files"),
786
extensions: ['log']
787
}]
788
});
789
790
if (result?.length) {
791
const channelName = basename(result[0]);
792
const channelId = `${IMPORTED_LOG_ID_PREFIX}${Date.now()}`;
793
// Register and show the channel
794
Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels).registerChannel({
795
id: channelId,
796
label: channelName,
797
log: true,
798
user: true,
799
source: result.length === 1
800
? { resource: result[0] }
801
: result.map(resource => ({ resource, name: basename(resource).split('.')[0] }))
802
});
803
outputService.showChannel(channelId);
804
}
805
}
806
}));
807
}
808
}
809
810
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(OutputContribution, LifecyclePhase.Restored);
811
812
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).registerConfiguration({
813
id: 'output',
814
order: 30,
815
title: nls.localize('output', "Output"),
816
type: 'object',
817
properties: {
818
'output.smartScroll.enabled': {
819
type: 'boolean',
820
description: nls.localize('output.smartScroll.enabled', "Enable/disable the ability of smart scrolling in the output view. Smart scrolling allows you to lock scrolling automatically when you click in the output view and unlocks when you click in the last line."),
821
default: true,
822
scope: ConfigurationScope.WINDOW,
823
tags: ['output']
824
}
825
}
826
});
827
828
KeybindingsRegistry.registerKeybindingRule({
829
id: 'cursorWordAccessibilityLeft',
830
when: ContextKeyExpr.and(EditorContextKeys.textInputFocus, CONTEXT_ACCESSIBILITY_MODE_ENABLED, IsWindowsContext, ContextKeyExpr.equals(FocusedViewContext.key, OUTPUT_VIEW_ID)),
831
primary: KeyMod.CtrlCmd | KeyCode.LeftArrow,
832
weight: KeybindingWeight.WorkbenchContrib
833
});
834
KeybindingsRegistry.registerKeybindingRule({
835
id: 'cursorWordAccessibilityLeftSelect',
836
when: ContextKeyExpr.and(EditorContextKeys.textInputFocus, CONTEXT_ACCESSIBILITY_MODE_ENABLED, IsWindowsContext, ContextKeyExpr.equals(FocusedViewContext.key, OUTPUT_VIEW_ID)),
837
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.LeftArrow,
838
weight: KeybindingWeight.WorkbenchContrib
839
});
840
KeybindingsRegistry.registerKeybindingRule({
841
id: 'cursorWordAccessibilityRight',
842
when: ContextKeyExpr.and(EditorContextKeys.textInputFocus, CONTEXT_ACCESSIBILITY_MODE_ENABLED, IsWindowsContext, ContextKeyExpr.equals(FocusedViewContext.key, OUTPUT_VIEW_ID)),
843
primary: KeyMod.CtrlCmd | KeyCode.RightArrow,
844
weight: KeybindingWeight.WorkbenchContrib
845
});
846
KeybindingsRegistry.registerKeybindingRule({
847
id: 'cursorWordAccessibilityRightSelect',
848
when: ContextKeyExpr.and(EditorContextKeys.textInputFocus, CONTEXT_ACCESSIBILITY_MODE_ENABLED, IsWindowsContext, ContextKeyExpr.equals(FocusedViewContext.key, OUTPUT_VIEW_ID)),
849
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.RightArrow,
850
weight: KeybindingWeight.WorkbenchContrib
851
});
852
853