Skip to content

Commit

Permalink
Support filtering in output view (#237581)
Browse files Browse the repository at this point in the history
* implement log filtering as a editor contrib

* show all log levels by default
  • Loading branch information
sandy081 authored Jan 9, 2025
1 parent e85a666 commit 77027e7
Show file tree
Hide file tree
Showing 6 changed files with 488 additions and 21 deletions.
2 changes: 1 addition & 1 deletion src/vs/workbench/browser/parts/views/viewFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export class FilterWidget extends Widget {
@IKeybindingService private readonly keybindingService: IKeybindingService
) {
super();
this.delayedFilterUpdate = new Delayer<void>(400);
this.delayedFilterUpdate = new Delayer<void>(300);
this._register(toDisposable(() => this.delayedFilterUpdate.cancel()));

if (options.focusContextKey) {
Expand Down
79 changes: 77 additions & 2 deletions src/vs/workbench/contrib/output/browser/output.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Registry } from '../../../../platform/registry/common/platform.js';
import { MenuId, registerAction2, Action2, MenuRegistry } from '../../../../platform/actions/common/actions.js';
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
import { OutputService } from './outputServices.js';
import { OUTPUT_MODE_ID, OUTPUT_MIME, OUTPUT_VIEW_ID, IOutputService, CONTEXT_IN_OUTPUT, LOG_MODE_ID, LOG_MIME, CONTEXT_ACTIVE_FILE_OUTPUT, CONTEXT_OUTPUT_SCROLL_LOCK, IOutputChannelDescriptor, IFileOutputChannelDescriptor, ACTIVE_OUTPUT_CHANNEL_CONTEXT, CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE, IOutputChannelRegistry, Extensions, CONTEXT_ACTIVE_OUTPUT_LEVEL, CONTEXT_ACTIVE_OUTPUT_LEVEL_IS_DEFAULT } from '../../../services/output/common/output.js';
import { OUTPUT_MODE_ID, OUTPUT_MIME, OUTPUT_VIEW_ID, IOutputService, CONTEXT_IN_OUTPUT, LOG_MODE_ID, LOG_MIME, CONTEXT_ACTIVE_FILE_OUTPUT, CONTEXT_OUTPUT_SCROLL_LOCK, IOutputChannelDescriptor, IFileOutputChannelDescriptor, 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 } from '../../../services/output/common/output.js';
import { OutputViewPane } from './outputView.js';
import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from '../../../common/contributions.js';
Expand All @@ -23,7 +23,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur
import { IQuickPickItem, IQuickInputService, IQuickPickSeparator, QuickPickInput } from '../../../../platform/quickinput/common/quickInput.js';
import { AUX_WINDOW_GROUP, AUX_WINDOW_GROUP_TYPE, IEditorService } from '../../../services/editor/common/editorService.js';
import { assertIsDefined } from '../../../../base/common/types.js';
import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
import { ContextKeyExpr, ContextKeyExpression } from '../../../../platform/contextkey/common/contextkey.js';
import { Codicon } from '../../../../base/common/codicons.js';
import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js';
import { Categories } from '../../../../platform/action/common/actionCommonCategories.js';
Expand All @@ -37,6 +37,9 @@ import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.j
import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from '../../../../platform/accessibility/common/accessibility.js';
import { IsWindowsContext } from '../../../../platform/contextkey/common/contextkeys.js';
import { FocusedViewContext } from '../../../common/contextkeys.js';
import { localize, localize2 } from '../../../../nls.js';
import { viewFilterSubmenu } from '../../../browser/parts/views/viewFilter.js';
import { ViewAction } from '../../../browser/parts/views/viewPane.js';

// Register Service
registerSingleton(IOutputService, OutputService, InstantiationType.Delayed);
Expand Down Expand Up @@ -107,6 +110,7 @@ class OutputContribution extends Disposable implements IWorkbenchContribution {
this.registerShowLogsAction();
this.registerOpenLogFileAction();
this.registerConfigureActiveOutputLogLevelAction();
this.registerFilterActions();
}

private registerSwitchOutputAction(): void {
Expand Down Expand Up @@ -527,6 +531,77 @@ class OutputContribution extends Disposable implements IWorkbenchContribution {
}));
}

private registerFilterActions(): void {
let order = 0;
const registerLogLevel = (logLevel: LogLevel, toggled: ContextKeyExpression) => {
this._register(registerAction2(class extends ViewAction<OutputViewPane> {
constructor() {
super({
id: `workbench.actions.${OUTPUT_VIEW_ID}.toggle.${LogLevelToString(logLevel)}`,
title: LogLevelToLocalizedString(logLevel).value,
metadata: {
description: localize2('toggleTraceDescription', "Show or hide {0} messages in the output", LogLevelToString(logLevel))
},
toggled,
menu: {
id: viewFilterSubmenu,
group: '1_filter',
when: ContextKeyExpr.and(ContextKeyExpr.equals('view', OUTPUT_VIEW_ID), CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE),
order: order++
},
viewId: OUTPUT_VIEW_ID
});
}
async runInView(serviceAccessor: ServicesAccessor, view: OutputViewPane): Promise<void> {
this.toggleLogLevelFilter(serviceAccessor.get(IOutputService), logLevel);
}
private toggleLogLevelFilter(outputService: IOutputService, logLevel: LogLevel): void {
switch (logLevel) {
case LogLevel.Trace:
outputService.filters.trace = !outputService.filters.trace;
break;
case LogLevel.Debug:
outputService.filters.debug = !outputService.filters.debug;
break;
case LogLevel.Info:
outputService.filters.info = !outputService.filters.info;
break;
case LogLevel.Warning:
outputService.filters.warning = !outputService.filters.warning;
break;
case LogLevel.Error:
outputService.filters.error = !outputService.filters.error;
break;
}
}
}));
};

registerLogLevel(LogLevel.Trace, SHOW_TRACE_FILTER_CONTEXT);
registerLogLevel(LogLevel.Debug, SHOW_DEBUG_FILTER_CONTEXT);
registerLogLevel(LogLevel.Info, SHOW_INFO_FILTER_CONTEXT);
registerLogLevel(LogLevel.Warning, SHOW_WARNING_FILTER_CONTEXT);
registerLogLevel(LogLevel.Error, SHOW_ERROR_FILTER_CONTEXT);

this._register(registerAction2(class extends ViewAction<OutputViewPane> {
constructor() {
super({
id: `workbench.actions.${OUTPUT_VIEW_ID}.clearFilterText`,
title: localize('clearFiltersText', "Clear filters text"),
keybinding: {
when: OUTPUT_FILTER_FOCUS_CONTEXT,
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyCode.Escape
},
viewId: OUTPUT_VIEW_ID
});
}
async runInView(serviceAccessor: ServicesAccessor, outputView: OutputViewPane): Promise<void> {
outputView.clearFilterText();
}
}));
}

}

Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(OutputContribution, LifecyclePhase.Restored);
Expand Down
111 changes: 110 additions & 1 deletion src/vs/workbench/contrib/output/browser/outputServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Disposable } from '../../../../base/common/lifecycle.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
import { Registry } from '../../../../platform/registry/common/platform.js';
import { IOutputChannel, IOutputService, OUTPUT_VIEW_ID, LOG_MIME, OUTPUT_MIME, OutputChannelUpdateMode, IOutputChannelDescriptor, Extensions, IOutputChannelRegistry, ACTIVE_OUTPUT_CHANNEL_CONTEXT, CONTEXT_ACTIVE_FILE_OUTPUT, CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE, CONTEXT_ACTIVE_OUTPUT_LEVEL, CONTEXT_ACTIVE_OUTPUT_LEVEL_IS_DEFAULT } from '../../../services/output/common/output.js';
import { IOutputChannel, IOutputService, OUTPUT_VIEW_ID, LOG_MIME, OUTPUT_MIME, OutputChannelUpdateMode, IOutputChannelDescriptor, Extensions, IOutputChannelRegistry, ACTIVE_OUTPUT_CHANNEL_CONTEXT, CONTEXT_ACTIVE_FILE_OUTPUT, CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE, CONTEXT_ACTIVE_OUTPUT_LEVEL, CONTEXT_ACTIVE_OUTPUT_LEVEL_IS_DEFAULT, IOutputViewFilters, SHOW_DEBUG_FILTER_CONTEXT, SHOW_ERROR_FILTER_CONTEXT, SHOW_INFO_FILTER_CONTEXT, SHOW_TRACE_FILTER_CONTEXT, SHOW_WARNING_FILTER_CONTEXT } from '../../../services/output/common/output.js';
import { OutputLinkProvider } from './outputLinkProvider.js';
import { ITextModelService, ITextModelContentProvider } from '../../../../editor/common/services/resolverService.js';
import { ITextModel } from '../../../../editor/common/model.js';
Expand Down Expand Up @@ -64,6 +64,104 @@ class OutputChannel extends Disposable implements IOutputChannel {
}
}

interface IOutputFilterOptions {
filterHistory: string[];
trace: boolean;
debug: boolean;
info: boolean;
warning: boolean;
error: boolean;
}

class OutputViewFilters extends Disposable implements IOutputViewFilters {

private readonly _onDidChange = this._register(new Emitter<void>());
readonly onDidChange = this._onDidChange.event;

constructor(
options: IOutputFilterOptions,
private readonly contextKeyService: IContextKeyService
) {
super();

this._trace.set(options.trace);
this._debug.set(options.debug);
this._info.set(options.info);
this._warning.set(options.warning);
this._error.set(options.error);

this.filterHistory = options.filterHistory;
}

filterHistory: string[];

private _filterText = '';
get text(): string {
return this._filterText;
}
set text(filterText: string) {
if (this._filterText !== filterText) {
this._filterText = filterText;
this._onDidChange.fire();
}
}

private readonly _trace = SHOW_TRACE_FILTER_CONTEXT.bindTo(this.contextKeyService);
get trace(): boolean {
return !!this._trace.get();
}
set trace(trace: boolean) {
if (this._trace.get() !== trace) {
this._trace.set(trace);
this._onDidChange.fire();
}
}

private readonly _debug = SHOW_DEBUG_FILTER_CONTEXT.bindTo(this.contextKeyService);
get debug(): boolean {
return !!this._debug.get();
}
set debug(debug: boolean) {
if (this._debug.get() !== debug) {
this._debug.set(debug);
this._onDidChange.fire();
}
}

private readonly _info = SHOW_INFO_FILTER_CONTEXT.bindTo(this.contextKeyService);
get info(): boolean {
return !!this._info.get();
}
set info(info: boolean) {
if (this._info.get() !== info) {
this._info.set(info);
this._onDidChange.fire();
}
}

private readonly _warning = SHOW_WARNING_FILTER_CONTEXT.bindTo(this.contextKeyService);
get warning(): boolean {
return !!this._warning.get();
}
set warning(warning: boolean) {
if (this._warning.get() !== warning) {
this._warning.set(warning);
this._onDidChange.fire();
}
}

private readonly _error = SHOW_ERROR_FILTER_CONTEXT.bindTo(this.contextKeyService);
get error(): boolean {
return !!this._error.get();
}
set error(error: boolean) {
if (this._error.get() !== error) {
this._error.set(error);
this._onDidChange.fire();
}
}
}

export class OutputService extends Disposable implements IOutputService, ITextModelContentProvider {

declare readonly _serviceBrand: undefined;
Expand All @@ -81,6 +179,8 @@ export class OutputService extends Disposable implements IOutputService, ITextMo
private readonly activeOutputChannelLevelContext: IContextKey<string>;
private readonly activeOutputChannelLevelIsDefaultContext: IContextKey<boolean>;

readonly filters: OutputViewFilters;

constructor(
@IStorageService private readonly storageService: IStorageService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
Expand Down Expand Up @@ -135,6 +235,15 @@ export class OutputService extends Disposable implements IOutputService, ITextMo
}));

this._register(this.lifecycleService.onDidShutdown(() => this.dispose()));

this.filters = this._register(new OutputViewFilters({
filterHistory: [],
trace: true,
debug: true,
info: true,
warning: true,
error: true
}, contextKeyService));
}

provideTextContent(resource: URI): Promise<ITextModel> | null {
Expand Down
Loading

0 comments on commit 77027e7

Please sign in to comment.