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