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