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