mirror of
https://github.com/Microsoft/vscode
synced 2024-09-18 01:58:27 +00:00
promote filter action to view pane (#163132)
promote filter to view pane
This commit is contained in:
parent
6483e39fb7
commit
bc647873e0
|
@ -140,6 +140,10 @@ export class ToolBar extends Disposable {
|
|||
return this.element;
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this.actionBar.focus();
|
||||
}
|
||||
|
||||
getItemsWidth(): number {
|
||||
let itemsWidth = 0;
|
||||
for (let i = 0; i < this.actionBar.length(); i++) {
|
||||
|
|
|
@ -264,7 +264,7 @@ export class MenuWorkbenchToolBar extends WorkbenchToolBar {
|
|||
super(container, { resetMenu: menuId, ...options }, menuService, contextKeyService, contextMenuService, keybindingService, telemetryService);
|
||||
|
||||
// update logic
|
||||
const menu = this._store.add(menuService.createMenu(menuId, contextKeyService));
|
||||
const menu = this._store.add(menuService.createMenu(menuId, contextKeyService, { emitEventsForSubmenuChanges: true }));
|
||||
const updateToolbar = () => {
|
||||
const primary: IAction[] = [];
|
||||
const secondary: IAction[] = [];
|
||||
|
|
|
@ -20,6 +20,7 @@ import { ViewPaneContainer, ViewsSubMenu } from 'vs/workbench/browser/parts/view
|
|||
import { IPaneComposite } from 'vs/workbench/common/panecomposite';
|
||||
import { IView } from 'vs/workbench/common/views';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { VIEWPANE_FILTER_ACTION } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
|
||||
export abstract class PaneComposite extends Composite implements IPaneComposite {
|
||||
|
||||
|
@ -89,7 +90,11 @@ export abstract class PaneComposite extends Composite implements IPaneComposite
|
|||
if (this.viewPaneContainer?.menuActions) {
|
||||
result.push(...this.viewPaneContainer.menuActions.getPrimaryActions());
|
||||
if (this.viewPaneContainer.isViewMergedWithContainer()) {
|
||||
result.push(...this.viewPaneContainer.panes[0].menuActions.getPrimaryActions());
|
||||
const viewPane = this.viewPaneContainer.panes[0];
|
||||
if (viewPane.shouldShowFilterInHeader()) {
|
||||
result.push(VIEWPANE_FILTER_ACTION);
|
||||
}
|
||||
result.push(...viewPane.menuActions.getPrimaryActions());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
|
|
@ -201,3 +201,76 @@
|
|||
.customview-tree .monaco-list .monaco-list-row.focused .custom-view-tree-node-item .actions {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* filter view pane */
|
||||
|
||||
.viewpane-filter-container {
|
||||
cursor: default;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.viewpane-filter-container.grow {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.viewpane-filter-container > .viewpane-filter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.viewpane-filter-container > .viewpane-filter .monaco-inputbox {
|
||||
height: 24px;
|
||||
font-size: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.pane-header .viewpane-filter-container > .viewpane-filter .monaco-inputbox .monaco-inputbox {
|
||||
height: 20px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.monaco-workbench.vs .viewpane-filter-container > .viewpane-filter .monaco-inputbox {
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.viewpane-filter-container > .viewpane-filter > .viewpane-filter-controls {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0;
|
||||
right: 0px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.viewpane-filter-container > .viewpane-filter > .viewpane-filter-controls > .viewpane-filter-badge {
|
||||
margin: 4px 0px;
|
||||
padding: 0px 8px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.viewpane-filter > .viewpane-filter-controls > .viewpane-filter-badge.hidden,
|
||||
.viewpane-filter.small > .viewpane-filter-controls > .viewpane-filter-badge {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.viewpane-filter > .viewpane-filter-controls > .monaco-action-bar .action-item .action-label.codicon.filter {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.panel > .title .monaco-action-bar .action-item.viewpane-filter-container {
|
||||
max-width: 400px;
|
||||
min-width: 300px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.pane-body .viewpane-filter-container:not(:empty) {
|
||||
flex: 1;
|
||||
margin: 10px 20px;
|
||||
height: initial;
|
||||
}
|
||||
|
||||
.pane-body .viewpane-filter-container > .viewpane-filter > .viewpane-filter-controls .monaco-action-bar .action-item {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
|
257
src/vs/workbench/browser/parts/views/viewFilter.ts
Normal file
257
src/vs/workbench/browser/parts/views/viewFilter.ts
Normal file
|
@ -0,0 +1,257 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Delayer } from 'vs/base/common/async';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { HistoryInputBox } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IThemeService, registerThemingParticipant, ICssStyleCollector, IColorTheme } from 'vs/platform/theme/common/themeService';
|
||||
import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler';
|
||||
import { toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { badgeBackground, badgeForeground, contrastBorder, inputActiveOptionBorder, inputActiveOptionBackground, inputActiveOptionForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ContextScopedHistoryInputBox } from 'vs/platform/history/browser/contextScopedHistoryWidget';
|
||||
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { showHistoryKeybindingHint } from 'vs/platform/history/browser/historyWidgetKeybindingHint';
|
||||
import { MenuId, MenuRegistry, SubmenuItemAction } from 'vs/platform/actions/common/actions';
|
||||
import { HiddenItemStrategy, MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar';
|
||||
import { SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
|
||||
const viewFilterMenu = new MenuId('menu.view.filter');
|
||||
export const viewFilterSubmenu = new MenuId('submenu.view.filter');
|
||||
MenuRegistry.appendMenuItem(viewFilterMenu, {
|
||||
submenu: viewFilterSubmenu,
|
||||
title: localize('more filters', "More Filters..."),
|
||||
group: 'navigation',
|
||||
icon: Codicon.filter,
|
||||
});
|
||||
|
||||
class MoreFiltersActionViewItem extends SubmenuEntryActionViewItem {
|
||||
|
||||
private _checked: boolean = false;
|
||||
set checked(checked: boolean) {
|
||||
if (this._checked !== checked) {
|
||||
this._checked = checked;
|
||||
this.updateChecked();
|
||||
}
|
||||
}
|
||||
|
||||
protected override updateChecked(): void {
|
||||
if (this.element) {
|
||||
this.element.classList.toggle('checked', this._checked);
|
||||
}
|
||||
}
|
||||
|
||||
override render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
this.updateChecked();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface IFilterWidgetOptions {
|
||||
readonly text?: string;
|
||||
readonly placeholder?: string;
|
||||
readonly ariaLabel?: string;
|
||||
readonly history?: string[];
|
||||
readonly focusContextKey?: string;
|
||||
}
|
||||
|
||||
export class FilterWidget extends Widget {
|
||||
|
||||
readonly element: HTMLElement;
|
||||
private readonly delayedFilterUpdate: Delayer<void>;
|
||||
private readonly filterInputBox: HistoryInputBox;
|
||||
private readonly filterBadge: HTMLElement;
|
||||
private readonly toolbar: MenuWorkbenchToolBar;
|
||||
private readonly focusContextKey: IContextKey<boolean> | undefined;
|
||||
|
||||
private readonly _onDidChangeFilterText = this._register(new Emitter<string>());
|
||||
readonly onDidChangeFilterText = this._onDidChangeFilterText.event;
|
||||
|
||||
private moreFiltersActionViewItem: MoreFiltersActionViewItem | undefined;
|
||||
private isMoreFiltersChecked: boolean = false;
|
||||
|
||||
constructor(
|
||||
private readonly options: IFilterWidgetOptions,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IContextViewService private readonly contextViewService: IContextViewService,
|
||||
@IThemeService private readonly themeService: IThemeService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IKeybindingService private readonly keybindingService: IKeybindingService
|
||||
) {
|
||||
super();
|
||||
this.delayedFilterUpdate = new Delayer<void>(400);
|
||||
this._register(toDisposable(() => this.delayedFilterUpdate.cancel()));
|
||||
|
||||
if (options.focusContextKey) {
|
||||
this.focusContextKey = new RawContextKey(options.focusContextKey, false).bindTo(contextKeyService);
|
||||
}
|
||||
|
||||
this.element = DOM.$('.viewpane-filter');
|
||||
this.filterInputBox = this.createInput(this.element);
|
||||
|
||||
const controlsContainer = DOM.append(this.element, DOM.$('.viewpane-filter-controls'));
|
||||
this.filterBadge = this.createBadge(controlsContainer);
|
||||
this.toolbar = this._register(this.createToolBar(controlsContainer));
|
||||
|
||||
this.adjustInputBox();
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this.filterInputBox.focus();
|
||||
}
|
||||
|
||||
blur(): void {
|
||||
this.filterInputBox.blur();
|
||||
}
|
||||
|
||||
updateBadge(message: string | undefined): void {
|
||||
this.filterBadge.classList.toggle('hidden', !message);
|
||||
this.filterBadge.textContent = message || '';
|
||||
this.adjustInputBox();
|
||||
}
|
||||
|
||||
setFilterText(filterText: string): void {
|
||||
this.filterInputBox.value = filterText;
|
||||
}
|
||||
|
||||
getFilterText(): string {
|
||||
return this.filterInputBox.value;
|
||||
}
|
||||
|
||||
getHistory(): string[] {
|
||||
return this.filterInputBox.getHistory();
|
||||
}
|
||||
|
||||
layout(width: number): void {
|
||||
this.element.parentElement?.classList.toggle('grow', width > 700);
|
||||
this.element.classList.toggle('small', width < 400);
|
||||
this.adjustInputBox();
|
||||
}
|
||||
|
||||
checkMoreFilters(checked: boolean): void {
|
||||
this.isMoreFiltersChecked = checked;
|
||||
if (this.moreFiltersActionViewItem) {
|
||||
this.moreFiltersActionViewItem.checked = checked;
|
||||
}
|
||||
}
|
||||
|
||||
private createInput(container: HTMLElement): ContextScopedHistoryInputBox {
|
||||
const inputBox = this._register(this.instantiationService.createInstance(ContextScopedHistoryInputBox, container, this.contextViewService, {
|
||||
placeholder: this.options.placeholder,
|
||||
ariaLabel: this.options.ariaLabel,
|
||||
history: this.options.history || [],
|
||||
showHistoryHint: () => showHistoryKeybindingHint(this.keybindingService)
|
||||
}));
|
||||
this._register(attachInputBoxStyler(inputBox, this.themeService));
|
||||
if (this.options.text) {
|
||||
inputBox.value = this.options.text;
|
||||
}
|
||||
this._register(inputBox.onDidChange(filter => this.delayedFilterUpdate.trigger(() => this.onDidInputChange(inputBox!))));
|
||||
this._register(DOM.addStandardDisposableListener(inputBox.inputElement, DOM.EventType.KEY_DOWN, (e: any) => this.onInputKeyDown(e, inputBox!)));
|
||||
this._register(DOM.addStandardDisposableListener(container, DOM.EventType.KEY_DOWN, this.handleKeyboardEvent));
|
||||
this._register(DOM.addStandardDisposableListener(container, DOM.EventType.KEY_UP, this.handleKeyboardEvent));
|
||||
this._register(DOM.addStandardDisposableListener(inputBox.inputElement, DOM.EventType.CLICK, (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}));
|
||||
|
||||
const focusTracker = this._register(DOM.trackFocus(inputBox.inputElement));
|
||||
if (this.focusContextKey) {
|
||||
this._register(focusTracker.onDidFocus(() => this.focusContextKey!.set(true)));
|
||||
this._register(focusTracker.onDidBlur(() => this.focusContextKey!.set(false)));
|
||||
this._register(toDisposable(() => this.focusContextKey!.reset()));
|
||||
}
|
||||
return inputBox;
|
||||
}
|
||||
|
||||
private createBadge(container: HTMLElement): HTMLElement {
|
||||
const filterBadge = DOM.append(container, DOM.$('.viewpane-filter-badge.hidden'));
|
||||
this._register(attachStylerCallback(this.themeService, { badgeBackground, badgeForeground, contrastBorder }, colors => {
|
||||
const background = colors.badgeBackground ? colors.badgeBackground.toString() : '';
|
||||
const foreground = colors.badgeForeground ? colors.badgeForeground.toString() : '';
|
||||
const border = colors.contrastBorder ? colors.contrastBorder.toString() : '';
|
||||
|
||||
filterBadge.style.backgroundColor = background;
|
||||
|
||||
filterBadge.style.borderWidth = border ? '1px' : '';
|
||||
filterBadge.style.borderStyle = border ? 'solid' : '';
|
||||
filterBadge.style.borderColor = border;
|
||||
filterBadge.style.color = foreground;
|
||||
}));
|
||||
return filterBadge;
|
||||
}
|
||||
|
||||
private createToolBar(container: HTMLElement): MenuWorkbenchToolBar {
|
||||
return this.instantiationService.createInstance(MenuWorkbenchToolBar, container, viewFilterMenu,
|
||||
{
|
||||
hiddenItemStrategy: HiddenItemStrategy.NoHide,
|
||||
actionViewItemProvider: (action: IAction) => {
|
||||
if (action instanceof SubmenuItemAction && action.item.submenu.id === viewFilterSubmenu.id) {
|
||||
this.moreFiltersActionViewItem = this.instantiationService.createInstance(MoreFiltersActionViewItem, action, undefined);
|
||||
this.moreFiltersActionViewItem.checked = this.isMoreFiltersChecked;
|
||||
return this.moreFiltersActionViewItem;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private onDidInputChange(inputbox: HistoryInputBox) {
|
||||
inputbox.addToHistory();
|
||||
this._onDidChangeFilterText.fire(inputbox.value);
|
||||
}
|
||||
|
||||
private adjustInputBox(): void {
|
||||
this.filterInputBox.inputElement.style.paddingRight = this.element.classList.contains('small') || this.filterBadge.classList.contains('hidden') ? '25px' : '150px';
|
||||
}
|
||||
|
||||
// Action toolbar is swallowing some keys for action items which should not be for an input box
|
||||
private handleKeyboardEvent(event: StandardKeyboardEvent) {
|
||||
if (event.equals(KeyCode.Space)
|
||||
|| event.equals(KeyCode.LeftArrow)
|
||||
|| event.equals(KeyCode.RightArrow)
|
||||
) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
private onInputKeyDown(event: StandardKeyboardEvent, filterInputBox: HistoryInputBox) {
|
||||
let handled = false;
|
||||
if (event.equals(KeyCode.Tab)) {
|
||||
this.toolbar.focus();
|
||||
handled = true;
|
||||
}
|
||||
if (handled) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||
const inputActiveOptionBorderColor = theme.getColor(inputActiveOptionBorder);
|
||||
if (inputActiveOptionBorderColor) {
|
||||
collector.addRule(`.viewpane-filter > .viewpane-filter-controls .monaco-action-bar .action-label.codicon.codicon-filter.checked { border-color: ${inputActiveOptionBorderColor}; }`);
|
||||
}
|
||||
const inputActiveOptionForegroundColor = theme.getColor(inputActiveOptionForeground);
|
||||
if (inputActiveOptionForegroundColor) {
|
||||
collector.addRule(`.viewpane-filter > .viewpane-filter-controls .monaco-action-bar .action-label.codicon.codicon-filter.checked { color: ${inputActiveOptionForegroundColor}; }`);
|
||||
}
|
||||
const inputActiveOptionBackgroundColor = theme.getColor(inputActiveOptionBackground);
|
||||
if (inputActiveOptionBackgroundColor) {
|
||||
collector.addRule(`.viewpane-filter > .viewpane-filter-controls .monaco-action-bar .action-label.codicon.codicon-filter.checked { background-color: ${inputActiveOptionBackgroundColor}; }`);
|
||||
}
|
||||
});
|
|
@ -9,9 +9,9 @@ import { Event, Emitter } from 'vs/base/common/event';
|
|||
import { foreground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { attachButtonStyler, attachProgressBarStyler } from 'vs/platform/theme/common/styler';
|
||||
import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { after, append, $, trackFocus, EventType, addDisposableListener, createCSSRule, asCSSUrl } from 'vs/base/browser/dom';
|
||||
import { after, append, $, trackFocus, EventType, addDisposableListener, createCSSRule, asCSSUrl, Dimension, reset } from 'vs/base/browser/dom';
|
||||
import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IAction, IActionRunner } from 'vs/base/common/actions';
|
||||
import { Action, IAction, IActionRunner } from 'vs/base/common/actions';
|
||||
import { ActionsOrientation, IActionViewItem, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
|
@ -42,6 +42,9 @@ import { Codicon } from 'vs/base/common/codicons';
|
|||
import { CompositeMenuActions } from 'vs/workbench/browser/actions';
|
||||
import { IDropdownMenuActionViewItemOptions } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem';
|
||||
import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar';
|
||||
import { FilterWidget, IFilterWidgetOptions } from 'vs/workbench/browser/parts/views/viewFilter';
|
||||
import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
|
||||
export interface IViewPaneOptions extends IPaneOptions {
|
||||
id: string;
|
||||
|
@ -50,6 +53,12 @@ export interface IViewPaneOptions extends IPaneOptions {
|
|||
donotForwardArgs?: boolean;
|
||||
}
|
||||
|
||||
export interface IFilterViewPaneOptions extends IViewPaneOptions {
|
||||
filterOptions: IFilterWidgetOptions;
|
||||
}
|
||||
|
||||
export const VIEWPANE_FILTER_ACTION = new Action('viewpane.action.filter');
|
||||
|
||||
type WelcomeActionClassification = {
|
||||
owner: 'joaomoreno';
|
||||
viewId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The view ID in which the welcome view button was clicked.' };
|
||||
|
@ -501,7 +510,11 @@ export abstract class ViewPane extends Pane implements IView {
|
|||
|
||||
private setActions(): void {
|
||||
if (this.toolbar) {
|
||||
this.toolbar.setActions(prepareActions(this.menuActions.getPrimaryActions()), prepareActions(this.menuActions.getSecondaryActions()));
|
||||
const primaryActions = [...this.menuActions.getPrimaryActions()];
|
||||
if (this.shouldShowFilterInHeader()) {
|
||||
primaryActions.unshift(VIEWPANE_FILTER_ACTION);
|
||||
}
|
||||
this.toolbar.setActions(prepareActions(primaryActions), prepareActions(this.menuActions.getSecondaryActions()));
|
||||
this.toolbar.context = this.getActionsContext();
|
||||
}
|
||||
}
|
||||
|
@ -520,6 +533,18 @@ export abstract class ViewPane extends Pane implements IView {
|
|||
}
|
||||
|
||||
getActionViewItem(action: IAction, options?: IDropdownMenuActionViewItemOptions): IActionViewItem | undefined {
|
||||
if (action.id === VIEWPANE_FILTER_ACTION.id) {
|
||||
const that = this;
|
||||
return new class extends BaseActionViewItem {
|
||||
constructor() { super(null, action); }
|
||||
override setFocusable(): void { /* noop input elements are focusable by default */ }
|
||||
override get trapsArrowNavigation(): boolean { return true; }
|
||||
override render(container: HTMLElement): void {
|
||||
container.classList.add('viewpane-filter-container');
|
||||
append(container, that.getFilterWidget()!.element);
|
||||
}
|
||||
};
|
||||
}
|
||||
return createActionViewItem(this.instantiationService, action, { ...options, ...{ menuAsChild: action instanceof SubmenuItemAction } });
|
||||
}
|
||||
|
||||
|
@ -626,6 +651,75 @@ export abstract class ViewPane extends Pane implements IView {
|
|||
shouldShowWelcome(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
getFilterWidget(): FilterWidget | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
shouldShowFilterInHeader(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class FilterViewPane extends ViewPane {
|
||||
|
||||
readonly filterWidget: FilterWidget;
|
||||
private dimension: Dimension | undefined;
|
||||
private filterContainer: HTMLElement | undefined;
|
||||
|
||||
constructor(
|
||||
options: IFilterViewPaneOptions,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IOpenerService openerService: IOpenerService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
) {
|
||||
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
|
||||
const scopedContextKeyService = this._register(contextKeyService.createScoped(this.element));
|
||||
scopedContextKeyService.createKey('view', options.id);
|
||||
this.filterWidget = this._register(instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])).createInstance(FilterWidget, options.filterOptions));
|
||||
}
|
||||
|
||||
override getFilterWidget(): FilterWidget {
|
||||
return this.filterWidget;
|
||||
}
|
||||
|
||||
protected override renderBody(container: HTMLElement): void {
|
||||
super.renderBody(container);
|
||||
this.filterContainer = append(container, $('.viewpane-filter-container'));
|
||||
}
|
||||
|
||||
protected override layoutBody(height: number, width: number): void {
|
||||
super.layoutBody(height, width);
|
||||
|
||||
this.dimension = new Dimension(width, height);
|
||||
const wasFilterShownInHeader = !this.filterContainer?.hasChildNodes();
|
||||
const shouldShowFilterInHeader = this.shouldShowFilterInHeader();
|
||||
if (wasFilterShownInHeader !== shouldShowFilterInHeader) {
|
||||
if (shouldShowFilterInHeader) {
|
||||
reset(this.filterContainer!);
|
||||
}
|
||||
this.updateActions();
|
||||
if (!shouldShowFilterInHeader) {
|
||||
append(this.filterContainer!, this.filterWidget.element);
|
||||
height = height - 44;
|
||||
}
|
||||
}
|
||||
this.filterWidget.layout(width);
|
||||
this.layoutBodyContent(height, width);
|
||||
}
|
||||
|
||||
override shouldShowFilterInHeader(): boolean {
|
||||
return !(this.dimension && this.dimension.width < 600 && this.dimension.height > 100);
|
||||
}
|
||||
|
||||
protected abstract layoutBodyContent(height: number, width: number): void;
|
||||
|
||||
}
|
||||
|
||||
export abstract class ViewAction<T extends IView> extends Action2 {
|
||||
|
|
|
@ -3,20 +3,15 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IView } from 'vs/workbench/common/views';
|
||||
import { CommentsFilters } from 'vs/workbench/contrib/comments/browser/commentsViewActions';
|
||||
|
||||
export const CommentsViewFilterFocusContextKey = new RawContextKey<boolean>('commentsFilterFocus', false);
|
||||
export const CommentsViewSmallLayoutContextKey = new RawContextKey<boolean>(`commentsView.smallLayout`, false);
|
||||
|
||||
export interface ICommentsView extends IView {
|
||||
|
||||
readonly onDidFocusFilter: Event<void>;
|
||||
readonly onDidClearFilterText: Event<void>;
|
||||
readonly filters: CommentsFilters;
|
||||
readonly onDidChangeFilterStats: Event<{ total: number; filtered: number }>;
|
||||
focusFilter(): void;
|
||||
clearFilterText(): void;
|
||||
getFilterStats(): { total: number; filtered: number };
|
||||
|
|
|
@ -18,7 +18,7 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
|||
import { textLinkForeground, textLinkActiveForeground, focusBorder, textPreformatForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ResourceLabels } from 'vs/workbench/browser/labels';
|
||||
import { CommentsList, COMMENTS_VIEW_ID, COMMENTS_VIEW_TITLE, Filter } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer';
|
||||
import { ViewPane, IViewPaneOptions, ViewAction } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { IViewPaneOptions, ViewAction, FilterViewPane } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { IViewDescriptorService, IViewsService } from 'vs/workbench/common/views';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
@ -31,11 +31,8 @@ import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
|
|||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { IEditor } from 'vs/editor/common/editorCommon';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { ActionBar, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { CommentsViewSmallLayoutContextKey, ICommentsView } from 'vs/workbench/contrib/comments/browser/comments';
|
||||
import { CommentsFilterActionViewItem, CommentsFilters, CommentsFiltersChangeEvent } from 'vs/workbench/contrib/comments/browser/commentsViewActions';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { CommentsViewFilterFocusContextKey, ICommentsView } from 'vs/workbench/contrib/comments/browser/comments';
|
||||
import { CommentsFilters, CommentsFiltersChangeEvent } from 'vs/workbench/contrib/comments/browser/commentsViewActions';
|
||||
import { Memento, MementoObject } from 'vs/workbench/common/memento';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { FilterOptions } from 'vs/workbench/contrib/comments/browser/commentsFilterOptions';
|
||||
|
@ -43,7 +40,7 @@ import { FilterOptions } from 'vs/workbench/contrib/comments/browser/commentsFil
|
|||
const CONTEXT_KEY_HAS_COMMENTS = new RawContextKey<boolean>('commentsView.hasComments', false);
|
||||
const VIEW_STORAGE_ID = 'commentsViewState';
|
||||
|
||||
export class CommentsPanel extends ViewPane implements ICommentsView {
|
||||
export class CommentsPanel extends FilterViewPane implements ICommentsView {
|
||||
private treeLabels!: ResourceLabels;
|
||||
private tree: CommentsList | undefined;
|
||||
private treeContainer!: HTMLElement;
|
||||
|
@ -51,10 +48,8 @@ export class CommentsPanel extends ViewPane implements ICommentsView {
|
|||
private commentsModel!: CommentsModel;
|
||||
private totalComments: number = 0;
|
||||
private readonly hasCommentsContextKey: IContextKey<boolean>;
|
||||
private readonly smallLayoutContextKey: IContextKey<boolean>;
|
||||
private readonly filter: Filter;
|
||||
readonly filters: CommentsFilters;
|
||||
private filterActionBar: ActionBar | undefined;
|
||||
|
||||
private currentHeight = 0;
|
||||
private currentWidth = 0;
|
||||
|
@ -64,13 +59,6 @@ export class CommentsPanel extends ViewPane implements ICommentsView {
|
|||
|
||||
readonly onDidChangeVisibility = this.onDidChangeBodyVisibility;
|
||||
|
||||
private readonly _onDidFocusFilter: Emitter<void> = this._register(new Emitter<void>());
|
||||
readonly onDidFocusFilter: Event<void> = this._onDidFocusFilter.event;
|
||||
private readonly _onDidClearFilterText: Emitter<void> = this._register(new Emitter<void>());
|
||||
readonly onDidClearFilterText: Event<void> = this._onDidClearFilterText.event;
|
||||
private _onDidChangeFilterStats = this._register(new Emitter<{ total: number; filtered: number }>());
|
||||
readonly onDidChangeFilterStats: Event<{ total: number; filtered: number }> = this._onDidChangeFilterStats.event;
|
||||
|
||||
constructor(
|
||||
options: IViewPaneOptions,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
|
@ -87,20 +75,27 @@ export class CommentsPanel extends ViewPane implements ICommentsView {
|
|||
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
|
||||
@IStorageService readonly storageService: IStorageService
|
||||
) {
|
||||
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
|
||||
const stateMemento = new Memento(VIEW_STORAGE_ID, storageService);
|
||||
const viewState = stateMemento.getMemento(StorageScope.WORKSPACE, StorageTarget.USER);
|
||||
super({
|
||||
...options,
|
||||
filterOptions: {
|
||||
placeholder: nls.localize('comments.filter.placeholder', "Filter (e.g. text, author)"),
|
||||
ariaLabel: nls.localize('comments.filter.ariaLabel', "Filter comments"),
|
||||
history: viewState['filterHistory'] || [],
|
||||
text: viewState['filter'] || '',
|
||||
focusContextKey: CommentsViewFilterFocusContextKey.key
|
||||
}
|
||||
}, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
|
||||
this.hasCommentsContextKey = CONTEXT_KEY_HAS_COMMENTS.bindTo(contextKeyService);
|
||||
this.smallLayoutContextKey = CommentsViewSmallLayoutContextKey.bindTo(this.contextKeyService);
|
||||
this.stateMemento = new Memento(VIEW_STORAGE_ID, storageService);
|
||||
this.viewState = this.stateMemento.getMemento(StorageScope.WORKSPACE, StorageTarget.USER);
|
||||
this.stateMemento = stateMemento;
|
||||
this.viewState = viewState;
|
||||
|
||||
this.filters = this._register(new CommentsFilters({
|
||||
filterText: this.viewState['filter'] || '',
|
||||
filterHistory: this.viewState['filterHistory'] || [],
|
||||
showResolved: this.viewState['showResolved'] !== false,
|
||||
showUnresolved: this.viewState['showUnresolved'] !== false,
|
||||
layout: new dom.Dimension(0, 0)
|
||||
}));
|
||||
this.filter = new Filter(new FilterOptions(this.filters.filterText, this.filters.showResolved, this.filters.showUnresolved));
|
||||
}, this.contextKeyService));
|
||||
this.filter = new Filter(new FilterOptions(this.filterWidget.getFilterText(), this.filters.showResolved, this.filters.showUnresolved));
|
||||
|
||||
this._register(this.commentService.onDidSetAllCommentThreads(e => {
|
||||
this.totalComments = e.commentThreads.length;
|
||||
|
@ -112,15 +107,16 @@ export class CommentsPanel extends ViewPane implements ICommentsView {
|
|||
}));
|
||||
|
||||
this._register(this.filters.onDidChange((event: CommentsFiltersChangeEvent) => {
|
||||
if (event.filterText || event.showResolved || event.showUnresolved) {
|
||||
if (event.showResolved || event.showUnresolved) {
|
||||
this.updateFilter();
|
||||
}
|
||||
}));
|
||||
this._register(this.filterWidget.onDidChangeFilterText(() => this.updateFilter()));
|
||||
}
|
||||
|
||||
override saveState(): void {
|
||||
this.viewState['filter'] = this.filters.filterText;
|
||||
this.viewState['filterHistory'] = this.filters.filterHistory;
|
||||
this.viewState['filter'] = this.filterWidget.getFilterText();
|
||||
this.viewState['filterHistory'] = this.filterWidget.getHistory();
|
||||
this.viewState['showResolved'] = this.filters.showResolved;
|
||||
this.viewState['showUnresolved'] = this.filters.showUnresolved;
|
||||
this.stateMemento.saveMemento();
|
||||
|
@ -128,11 +124,11 @@ export class CommentsPanel extends ViewPane implements ICommentsView {
|
|||
}
|
||||
|
||||
public focusFilter(): void {
|
||||
this._onDidFocusFilter.fire();
|
||||
this.filterWidget.focus();
|
||||
}
|
||||
|
||||
public clearFilterText(): void {
|
||||
this._onDidClearFilterText.fire();
|
||||
this.filterWidget.setFilterText('');
|
||||
}
|
||||
|
||||
public getFilterStats(): { total: number; filtered: number } {
|
||||
|
@ -147,30 +143,21 @@ export class CommentsPanel extends ViewPane implements ICommentsView {
|
|||
}
|
||||
|
||||
private updateFilter() {
|
||||
this.filter.options = new FilterOptions(this.filters.filterText, this.filters.showResolved, this.filters.showUnresolved);
|
||||
this.filter.options = new FilterOptions(this.filterWidget.getFilterText(), this.filters.showResolved, this.filters.showUnresolved);
|
||||
this.tree?.filterComments();
|
||||
|
||||
this.cachedFilterStats = undefined;
|
||||
this._onDidChangeFilterStats.fire(this.getFilterStats());
|
||||
const { total, filtered } = this.getFilterStats();
|
||||
this.filterWidget.updateBadge(total === filtered || total === 0 ? undefined : nls.localize('showing filtered results', "Showing {0} of {1}", filtered, total));
|
||||
this.filterWidget.checkMoreFilters(!this.filters.showResolved || !this.filters.showUnresolved);
|
||||
}
|
||||
|
||||
private createFilterActionBar(parent: HTMLElement): void {
|
||||
this.filterActionBar = this._register(new ActionBar(parent, { actionViewItemProvider: action => this.getActionViewItem(action) }));
|
||||
this.filterActionBar.getContainer().classList.add('comments-panel-filter-container');
|
||||
this.filterActionBar.getContainer().classList.toggle('hide', !this.smallLayout);
|
||||
}
|
||||
|
||||
private get smallLayout(): boolean { return !!this.smallLayoutContextKey.get(); }
|
||||
private set smallLayout(smallLayout: boolean) { this.smallLayoutContextKey.set(smallLayout); }
|
||||
|
||||
public override renderBody(container: HTMLElement): void {
|
||||
super.renderBody(container);
|
||||
|
||||
container.classList.add('comments-panel');
|
||||
|
||||
const domContainer = dom.append(container, dom.$('.comments-panel-container'));
|
||||
this.createFilterActionBar(domContainer);
|
||||
this.filterActionBar!.push(new Action(`workbench.actions.treeView.${this.id}.filter`));
|
||||
|
||||
this.treeContainer = dom.append(domContainer, dom.$('.tree-container'));
|
||||
this.treeContainer.classList.add('file-icon-themable-tree', 'show-file-icons');
|
||||
|
@ -255,20 +242,11 @@ export class CommentsPanel extends ViewPane implements ICommentsView {
|
|||
return !!this.tree;
|
||||
}
|
||||
|
||||
public override layoutBody(height: number = this.currentHeight, width: number = this.currentWidth): void {
|
||||
super.layoutBody(height, width);
|
||||
const wasSmallLayout = this.smallLayout;
|
||||
this.smallLayout = width < 600 && height > 100;
|
||||
if (this.smallLayout !== wasSmallLayout) {
|
||||
this.filterActionBar?.getContainer().classList.toggle('hide', !this.smallLayout);
|
||||
}
|
||||
const contentHeight = this.smallLayout ? height - 44 : height;
|
||||
public override layoutBodyContent(height: number = this.currentHeight, width: number = this.currentWidth): void {
|
||||
if (this.messageBoxContainer) {
|
||||
this.messageBoxContainer.style.height = `${contentHeight}px`;
|
||||
this.messageBoxContainer.style.height = `${height}px`;
|
||||
}
|
||||
this.tree?.layout(contentHeight, width);
|
||||
this.filters.layout = new dom.Dimension(this.smallLayout ? width : width - 200, height);
|
||||
|
||||
this.tree?.layout(height, width);
|
||||
this.currentHeight = height;
|
||||
this.currentWidth = width;
|
||||
}
|
||||
|
@ -421,12 +399,6 @@ export class CommentsPanel extends ViewPane implements ICommentsView {
|
|||
}
|
||||
}
|
||||
|
||||
public override getActionViewItem(action: IAction): IActionViewItem | undefined {
|
||||
if (action.id === `workbench.actions.treeView.${this.id}.filter`) {
|
||||
return this.instantiationService.createInstance(CommentsFilterActionViewItem, action, this);
|
||||
}
|
||||
return super.getActionViewItem(action);
|
||||
}
|
||||
}
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
|
|
|
@ -3,50 +3,31 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Delayer } from 'vs/base/common/async';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { Action, IAction, IActionRunner } from 'vs/base/common/actions';
|
||||
import { HistoryInputBox } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IThemeService, registerThemingParticipant, ICssStyleCollector, IColorTheme, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler';
|
||||
import { toDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { badgeBackground, badgeForeground, contrastBorder, inputActiveOptionBorder, inputActiveOptionBackground, inputActiveOptionForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ContextScopedHistoryInputBox } from 'vs/platform/history/browser/contextScopedHistoryWidget';
|
||||
import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
|
||||
import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem';
|
||||
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { showHistoryKeybindingHint } from 'vs/platform/history/browser/historyWidgetKeybindingHint';
|
||||
import { CommentsViewFilterFocusContextKey, CommentsViewSmallLayoutContextKey, ICommentsView } from 'vs/workbench/contrib/comments/browser/comments';
|
||||
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { CommentsViewFilterFocusContextKey, ICommentsView } from 'vs/workbench/contrib/comments/browser/comments';
|
||||
import { registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { COMMENTS_VIEW_ID } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer';
|
||||
import { FocusedViewContext } from 'vs/workbench/common/contextkeys';
|
||||
import { viewFilterSubmenu } from 'vs/workbench/browser/parts/views/viewFilter';
|
||||
|
||||
const CONTEXT_KEY_SHOW_RESOLVED = new RawContextKey<boolean>('commentsView.showResolvedFilter', true);
|
||||
const CONTEXT_KEY_SHOW_UNRESOLVED = new RawContextKey<boolean>('commentsView.showUnResolvedFilter', true);
|
||||
|
||||
export interface CommentsFiltersChangeEvent {
|
||||
filterText?: boolean;
|
||||
showResolved?: boolean;
|
||||
showUnresolved?: boolean;
|
||||
layout?: boolean;
|
||||
}
|
||||
|
||||
export interface CommentsFiltersOptions {
|
||||
filterText: string;
|
||||
filterHistory: string[];
|
||||
showResolved: boolean;
|
||||
showUnresolved: boolean;
|
||||
layout: DOM.Dimension;
|
||||
}
|
||||
|
||||
export class CommentsFilters extends Disposable {
|
||||
|
@ -54,323 +35,34 @@ export class CommentsFilters extends Disposable {
|
|||
private readonly _onDidChange: Emitter<CommentsFiltersChangeEvent> = this._register(new Emitter<CommentsFiltersChangeEvent>());
|
||||
readonly onDidChange: Event<CommentsFiltersChangeEvent> = this._onDidChange.event;
|
||||
|
||||
constructor(options: CommentsFiltersOptions) {
|
||||
constructor(options: CommentsFiltersOptions, private readonly contextKeyService: IContextKeyService) {
|
||||
super();
|
||||
this._filterText = options.filterText;
|
||||
this._showResolved = options.showResolved;
|
||||
this._showUnresolved = options.showUnresolved;
|
||||
this.filterHistory = options.filterHistory;
|
||||
this._layout = options.layout;
|
||||
this._showResolved.set(options.showResolved);
|
||||
this._showUnresolved.set(options.showUnresolved);
|
||||
}
|
||||
|
||||
private _filterText: string;
|
||||
get filterText(): string {
|
||||
return this._filterText;
|
||||
}
|
||||
set filterText(filterText: string) {
|
||||
if (this._filterText !== filterText) {
|
||||
this._filterText = filterText;
|
||||
this._onDidChange.fire({ filterText: true });
|
||||
}
|
||||
}
|
||||
|
||||
filterHistory: string[];
|
||||
|
||||
private _showUnresolved: boolean = true;
|
||||
private readonly _showUnresolved = CONTEXT_KEY_SHOW_UNRESOLVED.bindTo(this.contextKeyService);
|
||||
get showUnresolved(): boolean {
|
||||
return this._showUnresolved;
|
||||
return !!this._showUnresolved.get();
|
||||
}
|
||||
set showUnresolved(showUnresolved: boolean) {
|
||||
if (this._showUnresolved !== showUnresolved) {
|
||||
this._showUnresolved = showUnresolved;
|
||||
if (this._showUnresolved.get() !== showUnresolved) {
|
||||
this._showUnresolved.set(showUnresolved);
|
||||
this._onDidChange.fire(<CommentsFiltersChangeEvent>{ showUnresolved: true });
|
||||
}
|
||||
}
|
||||
|
||||
private _showResolved: boolean = true;
|
||||
private _showResolved = CONTEXT_KEY_SHOW_RESOLVED.bindTo(this.contextKeyService);
|
||||
get showResolved(): boolean {
|
||||
return this._showResolved;
|
||||
return !!this._showResolved.get();
|
||||
}
|
||||
set showResolved(showResolved: boolean) {
|
||||
if (this._showResolved !== showResolved) {
|
||||
this._showResolved = showResolved;
|
||||
if (this._showResolved.get() !== showResolved) {
|
||||
this._showResolved.set(showResolved);
|
||||
this._onDidChange.fire(<CommentsFiltersChangeEvent>{ showResolved: true });
|
||||
}
|
||||
}
|
||||
|
||||
private _layout: DOM.Dimension = new DOM.Dimension(0, 0);
|
||||
get layout(): DOM.Dimension {
|
||||
return this._layout;
|
||||
}
|
||||
set layout(layout: DOM.Dimension) {
|
||||
if (this._layout.width !== layout.width || this._layout.height !== layout.height) {
|
||||
this._layout = layout;
|
||||
this._onDidChange.fire(<CommentsFiltersChangeEvent>{ layout: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FiltersDropdownMenuActionViewItem extends DropdownMenuActionViewItem {
|
||||
|
||||
constructor(
|
||||
action: IAction, private filters: CommentsFilters, actionRunner: IActionRunner,
|
||||
@IContextMenuService contextMenuService: IContextMenuService
|
||||
) {
|
||||
super(action,
|
||||
{ getActions: () => this.getActions() },
|
||||
contextMenuService,
|
||||
{
|
||||
actionRunner,
|
||||
classNames: action.class,
|
||||
anchorAlignmentProvider: () => AnchorAlignment.RIGHT,
|
||||
menuAsChild: true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
override render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
this.updateChecked();
|
||||
}
|
||||
|
||||
private getActions(): IAction[] {
|
||||
return [
|
||||
{
|
||||
checked: this.filters.showResolved,
|
||||
class: undefined,
|
||||
enabled: true,
|
||||
id: 'showResolved',
|
||||
label: localize('showResolved', "Show Resolved"),
|
||||
run: async () => this.filters.showResolved = !this.filters.showResolved,
|
||||
tooltip: ''
|
||||
},
|
||||
{
|
||||
checked: this.filters.showUnresolved,
|
||||
class: undefined,
|
||||
enabled: true,
|
||||
id: 'showUnresolved',
|
||||
label: localize('showUnresolved', "Show Unresolved"),
|
||||
run: async () => this.filters.showUnresolved = !this.filters.showUnresolved,
|
||||
tooltip: ''
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
override updateChecked(): void {
|
||||
this.element!.classList.toggle('checked', this._action.checked);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const filterIcon = registerIcon('comments-view-filter', Codicon.filter, localize('comments.filterIcon', 'Icon for the filter configuration in the Comments view.'));
|
||||
|
||||
export class CommentsFilterActionViewItem extends BaseActionViewItem {
|
||||
|
||||
private delayedFilterUpdate: Delayer<void>;
|
||||
private container: HTMLElement | null = null;
|
||||
private filterInputBox: HistoryInputBox | null = null;
|
||||
private filterBadge: HTMLElement | null = null;
|
||||
private focusContextKey: IContextKey<boolean>;
|
||||
private readonly filtersAction: IAction;
|
||||
private actionbar: ActionBar | null = null;
|
||||
private keybindingService;
|
||||
|
||||
constructor(
|
||||
action: IAction,
|
||||
private commentsView: ICommentsView,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IContextViewService private readonly contextViewService: IContextViewService,
|
||||
@IThemeService private readonly themeService: IThemeService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IKeybindingService keybindingService: IKeybindingService
|
||||
) {
|
||||
super(null, action);
|
||||
this.keybindingService = keybindingService;
|
||||
this.focusContextKey = CommentsViewFilterFocusContextKey.bindTo(contextKeyService);
|
||||
this.delayedFilterUpdate = new Delayer<void>(400);
|
||||
this._register(toDisposable(() => this.delayedFilterUpdate.cancel()));
|
||||
this._register(commentsView.onDidFocusFilter(() => this.focus()));
|
||||
this._register(commentsView.onDidClearFilterText(() => this.clearFilterText()));
|
||||
this.filtersAction = new Action('commentsFiltersAction', localize('commentsFiltersAction', "More Filters..."), 'comments-filters ' + ThemeIcon.asClassName(filterIcon));
|
||||
this.filtersAction.checked = this.hasFiltersChanged();
|
||||
this._register(commentsView.filters.onDidChange(e => this.onDidFiltersChange(e)));
|
||||
}
|
||||
|
||||
override render(container: HTMLElement): void {
|
||||
this.container = container;
|
||||
this.container.classList.add('comments-panel-action-filter-container');
|
||||
|
||||
this.element = DOM.append(this.container, DOM.$(''));
|
||||
this.element.className = this.class;
|
||||
this.createInput(this.element);
|
||||
this.createControls(this.element);
|
||||
this.updateClass();
|
||||
|
||||
this.adjustInputBox();
|
||||
}
|
||||
|
||||
override focus(): void {
|
||||
if (this.filterInputBox) {
|
||||
this.filterInputBox.focus();
|
||||
}
|
||||
}
|
||||
|
||||
override blur(): void {
|
||||
if (this.filterInputBox) {
|
||||
this.filterInputBox.blur();
|
||||
}
|
||||
}
|
||||
|
||||
override setFocusable(): void {
|
||||
// noop input elements are focusable by default
|
||||
}
|
||||
|
||||
override get trapsArrowNavigation(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
private clearFilterText(): void {
|
||||
if (this.filterInputBox) {
|
||||
this.filterInputBox.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
private onDidFiltersChange(e: CommentsFiltersChangeEvent): void {
|
||||
this.filtersAction.checked = this.hasFiltersChanged();
|
||||
if (e.layout) {
|
||||
this.updateClass();
|
||||
}
|
||||
}
|
||||
|
||||
private hasFiltersChanged(): boolean {
|
||||
return !this.commentsView.filters.showResolved || !this.commentsView.filters.showUnresolved;
|
||||
}
|
||||
|
||||
private createInput(container: HTMLElement): void {
|
||||
this.filterInputBox = this._register(this.instantiationService.createInstance(ContextScopedHistoryInputBox, container, this.contextViewService, {
|
||||
placeholder: localize('comments.filter.placeholder', "Filter (e.g. text, author)"),
|
||||
ariaLabel: localize('comments.filter.ariaLabel', "Filter comments"),
|
||||
history: this.commentsView.filters.filterHistory,
|
||||
showHistoryHint: () => showHistoryKeybindingHint(this.keybindingService)
|
||||
}));
|
||||
this._register(attachInputBoxStyler(this.filterInputBox, this.themeService));
|
||||
this.filterInputBox.value = this.commentsView.filters.filterText;
|
||||
this._register(this.filterInputBox.onDidChange(filter => this.delayedFilterUpdate.trigger(() => this.onDidInputChange(this.filterInputBox!))));
|
||||
this._register(this.commentsView.filters.onDidChange((event: CommentsFiltersChangeEvent) => {
|
||||
if (event.filterText) {
|
||||
this.filterInputBox!.value = this.commentsView.filters.filterText;
|
||||
}
|
||||
}));
|
||||
this._register(DOM.addStandardDisposableListener(this.filterInputBox.inputElement, DOM.EventType.KEY_DOWN, (e: any) => this.onInputKeyDown(e, this.filterInputBox!)));
|
||||
this._register(DOM.addStandardDisposableListener(container, DOM.EventType.KEY_DOWN, this.handleKeyboardEvent));
|
||||
this._register(DOM.addStandardDisposableListener(container, DOM.EventType.KEY_UP, this.handleKeyboardEvent));
|
||||
this._register(DOM.addStandardDisposableListener(this.filterInputBox.inputElement, DOM.EventType.CLICK, (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}));
|
||||
|
||||
const focusTracker = this._register(DOM.trackFocus(this.filterInputBox.inputElement));
|
||||
this._register(focusTracker.onDidFocus(() => this.focusContextKey.set(true)));
|
||||
this._register(focusTracker.onDidBlur(() => this.focusContextKey.set(false)));
|
||||
this._register(toDisposable(() => this.focusContextKey.reset()));
|
||||
}
|
||||
|
||||
private createControls(container: HTMLElement): void {
|
||||
const controlsContainer = DOM.append(container, DOM.$('.comments-panel-filter-controls'));
|
||||
this.createBadge(controlsContainer);
|
||||
this.createFilters(controlsContainer);
|
||||
}
|
||||
|
||||
private createBadge(container: HTMLElement): void {
|
||||
const filterBadge = this.filterBadge = DOM.append(container, DOM.$('.comments-panel-filter-badge'));
|
||||
this._register(attachStylerCallback(this.themeService, { badgeBackground, badgeForeground, contrastBorder }, colors => {
|
||||
const background = colors.badgeBackground ? colors.badgeBackground.toString() : '';
|
||||
const foreground = colors.badgeForeground ? colors.badgeForeground.toString() : '';
|
||||
const border = colors.contrastBorder ? colors.contrastBorder.toString() : '';
|
||||
|
||||
filterBadge.style.backgroundColor = background;
|
||||
|
||||
filterBadge.style.borderWidth = border ? '1px' : '';
|
||||
filterBadge.style.borderStyle = border ? 'solid' : '';
|
||||
filterBadge.style.borderColor = border;
|
||||
filterBadge.style.color = foreground;
|
||||
}));
|
||||
this.updateBadge();
|
||||
this._register(this.commentsView.onDidChangeFilterStats(() => this.updateBadge()));
|
||||
}
|
||||
|
||||
private createFilters(container: HTMLElement): void {
|
||||
this.actionbar = this._register(new ActionBar(container, {
|
||||
actionViewItemProvider: action => {
|
||||
if (action.id === this.filtersAction.id) {
|
||||
return this.instantiationService.createInstance(FiltersDropdownMenuActionViewItem, action, this.commentsView.filters, this.actionRunner);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}));
|
||||
this.actionbar.push(this.filtersAction, { icon: true, label: false });
|
||||
}
|
||||
|
||||
private onDidInputChange(inputbox: HistoryInputBox) {
|
||||
inputbox.addToHistory();
|
||||
this.commentsView.filters.filterText = inputbox.value;
|
||||
this.commentsView.filters.filterHistory = inputbox.getHistory();
|
||||
}
|
||||
|
||||
private updateBadge(): void {
|
||||
if (this.filterBadge) {
|
||||
const { total, filtered } = this.commentsView.getFilterStats();
|
||||
this.filterBadge.classList.toggle('hidden', (total === filtered && !this.filterInputBox?.value) || total === 0);
|
||||
this.filterBadge.textContent = localize('showing filtered comments', "Showing {0} of {1}", filtered, total);
|
||||
this.adjustInputBox();
|
||||
}
|
||||
}
|
||||
|
||||
private adjustInputBox(): void {
|
||||
if (this.element && this.filterInputBox && this.filterBadge) {
|
||||
this.filterInputBox.inputElement.style.paddingRight = this.element.classList.contains('small') || this.filterBadge.classList.contains('hidden') ? '25px' : '150px';
|
||||
}
|
||||
}
|
||||
|
||||
// Action toolbar is swallowing some keys for action items which should not be for an input box
|
||||
private handleKeyboardEvent(event: StandardKeyboardEvent) {
|
||||
if (event.equals(KeyCode.Space)
|
||||
|| event.equals(KeyCode.LeftArrow)
|
||||
|| event.equals(KeyCode.RightArrow)
|
||||
) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
private onInputKeyDown(event: StandardKeyboardEvent, filterInputBox: HistoryInputBox) {
|
||||
let handled = false;
|
||||
if (event.equals(KeyCode.Tab)) {
|
||||
this.actionbar?.focus();
|
||||
handled = true;
|
||||
}
|
||||
if (handled) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
protected override updateClass(): void {
|
||||
if (this.element && this.container) {
|
||||
this.element.className = this.class;
|
||||
this.container.classList.toggle('grow', this.element.classList.contains('grow'));
|
||||
this.adjustInputBox();
|
||||
}
|
||||
}
|
||||
|
||||
protected get class(): string {
|
||||
if (this.commentsView.filters.layout.width > 600) {
|
||||
return 'comments-panel-action-filter grow';
|
||||
} else if (this.commentsView.filters.layout.width < 400) {
|
||||
return 'comments-panel-action-filter small';
|
||||
} else {
|
||||
return 'comments-panel-action-filter';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerAction2(class extends ViewAction<ICommentsView> {
|
||||
|
@ -409,23 +101,6 @@ registerAction2(class extends ViewAction<ICommentsView> {
|
|||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
_isFakeAction: true,
|
||||
id: `workbench.actions.treeView.${COMMENTS_VIEW_ID}.filter`,
|
||||
title: localize('filter', "Filter"),
|
||||
menu: {
|
||||
id: MenuId.ViewTitle,
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.equals('view', COMMENTS_VIEW_ID), CommentsViewSmallLayoutContextKey.negate()),
|
||||
group: 'navigation',
|
||||
order: 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
async run(): Promise<void> { }
|
||||
});
|
||||
|
||||
registerAction2(class extends ViewAction<ICommentsView> {
|
||||
constructor() {
|
||||
super({
|
||||
|
@ -444,17 +119,52 @@ registerAction2(class extends ViewAction<ICommentsView> {
|
|||
}
|
||||
});
|
||||
|
||||
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||
const inputActiveOptionBorderColor = theme.getColor(inputActiveOptionBorder);
|
||||
if (inputActiveOptionBorderColor) {
|
||||
collector.addRule(`.comments-panel-action-filter > .comments-panel-filter-controls > .monaco-action-bar .action-label.comments-filters.checked { border-color: ${inputActiveOptionBorderColor}; }`);
|
||||
registerAction2(class extends ViewAction<ICommentsView> {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.${COMMENTS_VIEW_ID}.toggleUnResolvedComments`,
|
||||
title: localize('toggle unresolved', "Toggle Unresolved Comments"),
|
||||
category: localize('comments', "Comments"),
|
||||
toggled: {
|
||||
condition: CONTEXT_KEY_SHOW_UNRESOLVED,
|
||||
title: localize('unresolved', "Show Unresolved"),
|
||||
},
|
||||
menu: {
|
||||
id: viewFilterSubmenu,
|
||||
group: '1_filter',
|
||||
when: ContextKeyExpr.equals('view', COMMENTS_VIEW_ID),
|
||||
order: 1
|
||||
},
|
||||
viewId: COMMENTS_VIEW_ID
|
||||
});
|
||||
}
|
||||
const inputActiveOptionForegroundColor = theme.getColor(inputActiveOptionForeground);
|
||||
if (inputActiveOptionForegroundColor) {
|
||||
collector.addRule(`.comments-panel-action-filter > .comments-panel-filter-controls > .monaco-action-bar .action-label.comments-filters.checked { color: ${inputActiveOptionForegroundColor}; }`);
|
||||
}
|
||||
const inputActiveOptionBackgroundColor = theme.getColor(inputActiveOptionBackground);
|
||||
if (inputActiveOptionBackgroundColor) {
|
||||
collector.addRule(`.comments-panel-action-filter > .comments-panel-filter-controls > .monaco-action-bar .action-label.comments-filters.checked { background-color: ${inputActiveOptionBackgroundColor}; }`);
|
||||
|
||||
async runInView(serviceAccessor: ServicesAccessor, view: ICommentsView): Promise<void> {
|
||||
view.filters.showUnresolved = !view.filters.showUnresolved;
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends ViewAction<ICommentsView> {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.${COMMENTS_VIEW_ID}.toggleResolvedComments`,
|
||||
title: localize('toggle resolved', "Toggle Resolved Comments"),
|
||||
category: localize('comments', "Comments"),
|
||||
toggled: {
|
||||
condition: CONTEXT_KEY_SHOW_RESOLVED,
|
||||
title: localize('resolved', "Show Resolved"),
|
||||
},
|
||||
menu: {
|
||||
id: viewFilterSubmenu,
|
||||
group: '1_filter',
|
||||
when: ContextKeyExpr.equals('view', COMMENTS_VIEW_ID),
|
||||
order: 1
|
||||
},
|
||||
viewId: COMMENTS_VIEW_ID
|
||||
});
|
||||
}
|
||||
|
||||
async runInView(serviceAccessor: ServicesAccessor, view: ICommentsView): Promise<void> {
|
||||
view.filters.showResolved = !view.filters.showResolved;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -109,61 +109,6 @@
|
|||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.comments-panel-container .monaco-action-bar.comments-panel-filter-container .action-item.comments-panel-action-filter-container,
|
||||
.panel > .title .monaco-action-bar .action-item.comments-panel-action-filter-container.grow {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.comments-panel-container .monaco-action-bar.comments-panel-filter-container {
|
||||
margin: 10px 20px;
|
||||
height: initial;
|
||||
}
|
||||
|
||||
.comments-panel-action-filter > .comments-panel-filter-controls {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0;
|
||||
right: 0px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.comments-panel-action-filter > .comments-panel-filter-controls > .comments-panel-filter-badge {
|
||||
margin: 4px 0px;
|
||||
padding: 0px 8px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.comments-panel-action-filter > .comments-panel-filter-controls > .comments-panel-filter-badge.hidden,
|
||||
.comments-panel-action-filter.small > .comments-panel-filter-controls > .comments-panel-filter-badge {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.comments-panel-action-filter > .comments-panel-filter-controls > .monaco-action-bar .action-item .action-label.codicon.comments-filters {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.panel > .title .monaco-action-bar .action-item.comments-panel-action-filter-container {
|
||||
max-width: 400px;
|
||||
min-width: 300px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.monaco-action-bar .comments-panel-action-filter .monaco-inputbox {
|
||||
height: 24px;
|
||||
font-size: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.pane-header .monaco-action-bar .comments-panel-action-filter .monaco-inputbox {
|
||||
height: 20px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.monaco-workbench.vs .monaco-action-bar .comments-panel-action-filter .monaco-inputbox {
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.comments-panel .hide {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -142,53 +142,3 @@
|
|||
}
|
||||
.monaco-workbench .repl .repl-tree .output.expression .code-subscript { vertical-align: sub; font-size: smaller; line-height: normal; }
|
||||
.monaco-workbench .repl .repl-tree .output.expression .code-superscript { vertical-align: super; font-size: smaller; line-height: normal; }
|
||||
|
||||
.monaco-action-bar .action-item.repl-panel-filter-container {
|
||||
cursor: default;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.monaco-action-bar .panel-action-tree-filter{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.monaco-action-bar .panel-action-tree-filter .monaco-inputbox {
|
||||
height: 24px;
|
||||
font-size: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.pane-header .monaco-action-bar .panel-action-tree-filter .monaco-inputbox {
|
||||
height: 20px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.monaco-workbench.vs .monaco-action-bar .panel-action-tree-filter .monaco-inputbox {
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.panel > .title .monaco-action-bar .action-item.repl-panel-filter-container {
|
||||
min-width: 300px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.repl-panel-filter-container .repl-panel-filter-controls {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0;
|
||||
right: 0px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.repl-panel-filter-container .repl-panel-filter-controls > .repl-panel-filter-badge {
|
||||
margin: 4px;
|
||||
padding: 0px 8px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.repl-panel-filter-container .repl-panel-filter-controls > .repl-panel-filter-badge.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -44,7 +44,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
|||
import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { registerAndCreateHistoryNavigationContext } from 'vs/platform/history/browser/contextScopedHistoryWidget';
|
||||
import { showHistoryKeybindingHint } from 'vs/platform/history/browser/historyWidgetKeybindingHint';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
|
@ -55,13 +54,13 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag
|
|||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { editorForeground, resolveColorValue } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { IViewPaneOptions, ViewAction, ViewPane } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { FilterViewPane, IViewPaneOptions, ViewAction } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { IViewDescriptorService, IViewsService } from 'vs/workbench/common/views';
|
||||
import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions';
|
||||
import { FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems';
|
||||
import { debugConsoleClearAll, debugConsoleEvaluationPrompt } from 'vs/workbench/contrib/debug/browser/debugIcons';
|
||||
import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector';
|
||||
import { ReplFilter, ReplFilterActionViewItem, ReplFilterState } from 'vs/workbench/contrib/debug/browser/replFilter';
|
||||
import { ReplFilter } from 'vs/workbench/contrib/debug/browser/replFilter';
|
||||
import { ReplAccessibilityProvider, ReplDataSource, ReplDelegate, ReplEvaluationInputsRenderer, ReplEvaluationResultsRenderer, ReplGroupRenderer, ReplRawObjectsRenderer, ReplSimpleElementsRenderer, ReplVariablesRenderer } from 'vs/workbench/contrib/debug/browser/replViewer';
|
||||
import { CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_REPL, CONTEXT_MULTI_SESSION_REPL, DEBUG_SCHEME, getStateLabel, IDebugConfiguration, IDebugService, IDebugSession, IReplConfiguration, IReplElement, IReplOptions, REPL_VIEW_ID, State } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { Variable } from 'vs/workbench/contrib/debug/common/debugModel';
|
||||
|
@ -74,7 +73,6 @@ const HISTORY_STORAGE_KEY = 'debug.repl.history';
|
|||
const FILTER_HISTORY_STORAGE_KEY = 'debug.repl.filterHistory';
|
||||
const FILTER_VALUE_STORAGE_KEY = 'debug.repl.filterValue';
|
||||
const DECORATION_KEY = 'replinputdecoration';
|
||||
const FILTER_ACTION_ID = `workbench.actions.treeView.repl.filter`;
|
||||
|
||||
function revealLastElement(tree: WorkbenchAsyncDataTree<any, any, any>) {
|
||||
tree.scrollTop = tree.scrollHeight - tree.renderHeight;
|
||||
|
@ -84,7 +82,7 @@ function revealLastElement(tree: WorkbenchAsyncDataTree<any, any, any>) {
|
|||
const sessionsToIgnore = new Set<IDebugSession>();
|
||||
const identityProvider = { getId: (element: IReplElement) => element.getId() };
|
||||
|
||||
export class Repl extends ViewPane implements IHistoryNavigationWidget {
|
||||
export class Repl extends FilterViewPane implements IHistoryNavigationWidget {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private static readonly REFRESH_DELAY = 50; // delay in ms to refresh the repl for new elements to show
|
||||
|
@ -99,7 +97,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
|
|||
private treeContainer!: HTMLElement;
|
||||
private replInput!: CodeEditorWidget;
|
||||
private replInputContainer!: HTMLElement;
|
||||
private dimension!: dom.Dimension;
|
||||
private bodyContentDimension: dom.Dimension | undefined;
|
||||
private replInputLineCount = 1;
|
||||
private model: ITextModel | undefined;
|
||||
private setHistoryNavigationEnablement!: (enabled: boolean) => void;
|
||||
|
@ -109,8 +107,6 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
|
|||
private completionItemProvider: IDisposable | undefined;
|
||||
private modelChangeListener: IDisposable = Disposable.None;
|
||||
private filter: ReplFilter;
|
||||
private filterState: ReplFilterState;
|
||||
private filterActionViewItem: ReplFilterActionViewItem | undefined;
|
||||
private multiSessionRepl: IContextKey<boolean>;
|
||||
private menu: IMenu;
|
||||
|
||||
|
@ -134,14 +130,21 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
|
|||
@IMenuService menuService: IMenuService,
|
||||
@ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService,
|
||||
) {
|
||||
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
|
||||
const filterText = storageService.get(FILTER_VALUE_STORAGE_KEY, StorageScope.WORKSPACE, '');
|
||||
super({
|
||||
...options,
|
||||
filterOptions: {
|
||||
placeholder: localize({ key: 'workbench.debug.filter.placeholder', comment: ['Text in the brackets after e.g. is not localizable'] }, "Filter (e.g. text, !exclude)"),
|
||||
text: filterText,
|
||||
history: JSON.parse(storageService.get(FILTER_HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')) as string[],
|
||||
}
|
||||
}, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
|
||||
|
||||
this.menu = menuService.createMenu(MenuId.DebugConsoleContext, contextKeyService);
|
||||
this._register(this.menu);
|
||||
this.history = new HistoryNavigator(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')), 50);
|
||||
this.filter = new ReplFilter();
|
||||
this.filterState = new ReplFilterState(this);
|
||||
this.filter.filterQuery = this.filterState.filterText = this.storageService.get(FILTER_VALUE_STORAGE_KEY, StorageScope.WORKSPACE, '');
|
||||
this.filter.filterQuery = filterText;
|
||||
this.multiSessionRepl = CONTEXT_MULTI_SESSION_REPL.bindTo(contextKeyService);
|
||||
this.replOptions = this._register(this.instantiationService.createInstance(ReplOptions, this.id, () => this.getBackgroundColor()));
|
||||
this._register(this.replOptions.onDidChange(() => this.onDidStyleChange()));
|
||||
|
@ -186,7 +189,6 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
|
|||
this.replInput.setModel(this.model);
|
||||
this.updateInputDecoration();
|
||||
this.refreshReplElements(true);
|
||||
this.layoutBody(this.dimension.height, this.dimension.width);
|
||||
}
|
||||
}));
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => {
|
||||
|
@ -208,8 +210,8 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
|
|||
this.setMode();
|
||||
}));
|
||||
|
||||
this._register(this.filterState.onDidChange(() => {
|
||||
this.filter.filterQuery = this.filterState.filterText;
|
||||
this._register(this.filterWidget.onDidChangeFilterText(() => {
|
||||
this.filter.filterQuery = this.filterWidget.getFilterText();
|
||||
this.tree.refilter();
|
||||
revealLastElement(this.tree);
|
||||
}));
|
||||
|
@ -318,7 +320,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
|
|||
}
|
||||
|
||||
focusFilter(): void {
|
||||
this.filterActionViewItem?.focus();
|
||||
this.filterWidget.focus();
|
||||
}
|
||||
|
||||
private setMode(): void {
|
||||
|
@ -364,8 +366,8 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
|
|||
|
||||
this.tree.rerender();
|
||||
|
||||
if (this.dimension) {
|
||||
this.layoutBody(this.dimension.height, this.dimension.width);
|
||||
if (this.bodyContentDimension) {
|
||||
this.layoutBodyContent(this.bodyContentDimension.height, this.bodyContentDimension.width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -431,9 +433,9 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
|
|||
this.replInput.setValue('');
|
||||
const shouldRelayout = this.replInputLineCount > 1;
|
||||
this.replInputLineCount = 1;
|
||||
if (shouldRelayout) {
|
||||
if (shouldRelayout && this.bodyContentDimension) {
|
||||
// Trigger a layout to shrink a potential multi line input
|
||||
this.layoutBody(this.dimension.height, this.dimension.width);
|
||||
this.layoutBodyContent(this.bodyContentDimension.height, this.bodyContentDimension.width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -458,9 +460,8 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
|
|||
return removeAnsiEscapeCodes(text);
|
||||
}
|
||||
|
||||
protected override layoutBody(height: number, width: number): void {
|
||||
super.layoutBody(height, width);
|
||||
this.dimension = new dom.Dimension(width, height);
|
||||
protected override layoutBodyContent(height: number, width: number): void {
|
||||
this.bodyContentDimension = new dom.Dimension(width, height);
|
||||
const replInputHeight = Math.min(this.replInput.getContentHeight(), height);
|
||||
if (this.tree) {
|
||||
const lastElementVisible = this.tree.scrollTop + this.tree.renderHeight >= this.tree.scrollHeight;
|
||||
|
@ -476,6 +477,10 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
|
|||
this.replInput.layout({ width: width - 30, height: replInputHeight });
|
||||
}
|
||||
|
||||
override shouldShowFilterInHeader(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
collapseAll(): void {
|
||||
this.tree.collapseAll();
|
||||
}
|
||||
|
@ -492,11 +497,6 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
|
|||
if (action.id === selectReplCommandId) {
|
||||
const session = (this.tree ? this.tree.getInput() : undefined) ?? this.debugService.getViewModel().focusedSession;
|
||||
return this.instantiationService.createInstance(SelectReplActionViewItem, action, session);
|
||||
} else if (action.id === FILTER_ACTION_ID) {
|
||||
const filterHistory = JSON.parse(this.storageService.get(FILTER_HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')) as string[];
|
||||
this.filterActionViewItem = this.instantiationService.createInstance(ReplFilterActionViewItem, action,
|
||||
localize({ key: 'workbench.debug.filter.placeholder', comment: ['Text in the brackets after e.g. is not localizable'] }, "Filter (e.g. text, !exclude)"), this.filterState, filterHistory, () => showHistoryKeybindingHint(this.keybindingService));
|
||||
return this.filterActionViewItem;
|
||||
}
|
||||
|
||||
return super.getActionViewItem(action);
|
||||
|
@ -538,7 +538,8 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
|
|||
await autoExpandElements(session.getReplElements());
|
||||
}
|
||||
// Repl elements count changed, need to update filter stats on the badge
|
||||
this.filterState.updateFilterStats();
|
||||
const { total, filtered } = this.getFilterStats();
|
||||
this.filterWidget.updateBadge(total === filtered || total === 0 ? undefined : localize('showing filtered repl lines', "Showing {0} of {1}", filtered, total));
|
||||
}, Repl.REFRESH_DELAY);
|
||||
}
|
||||
|
||||
|
@ -645,7 +646,9 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
|
|||
const lineCount = model ? Math.min(10, model.getLineCount()) : 1;
|
||||
if (lineCount !== this.replInputLineCount) {
|
||||
this.replInputLineCount = lineCount;
|
||||
this.layoutBody(this.dimension.height, this.dimension.width);
|
||||
if (this.bodyContentDimension) {
|
||||
this.layoutBodyContent(this.bodyContentDimension.height, this.bodyContentDimension.width);
|
||||
}
|
||||
}
|
||||
}));
|
||||
// We add the input decoration only when the focus is in the input #61126
|
||||
|
@ -712,19 +715,17 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
|
|||
} else {
|
||||
this.storageService.remove(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE);
|
||||
}
|
||||
if (this.filterActionViewItem) {
|
||||
const filterHistory = this.filterActionViewItem.getHistory();
|
||||
if (filterHistory.length) {
|
||||
this.storageService.store(FILTER_HISTORY_STORAGE_KEY, JSON.stringify(filterHistory), StorageScope.WORKSPACE, StorageTarget.USER);
|
||||
} else {
|
||||
this.storageService.remove(FILTER_HISTORY_STORAGE_KEY, StorageScope.WORKSPACE);
|
||||
}
|
||||
const filterValue = this.filterState.filterText;
|
||||
if (filterValue) {
|
||||
this.storageService.store(FILTER_VALUE_STORAGE_KEY, filterValue, StorageScope.WORKSPACE, StorageTarget.USER);
|
||||
} else {
|
||||
this.storageService.remove(FILTER_VALUE_STORAGE_KEY, StorageScope.WORKSPACE);
|
||||
}
|
||||
const filterHistory = this.filterWidget.getHistory();
|
||||
if (filterHistory.length) {
|
||||
this.storageService.store(FILTER_HISTORY_STORAGE_KEY, JSON.stringify(filterHistory), StorageScope.WORKSPACE, StorageTarget.USER);
|
||||
} else {
|
||||
this.storageService.remove(FILTER_HISTORY_STORAGE_KEY, StorageScope.WORKSPACE);
|
||||
}
|
||||
const filterValue = this.filterWidget.getFilterText();
|
||||
if (filterValue) {
|
||||
this.storageService.store(FILTER_VALUE_STORAGE_KEY, filterValue, StorageScope.WORKSPACE, StorageTarget.USER);
|
||||
} else {
|
||||
this.storageService.remove(FILTER_VALUE_STORAGE_KEY, StorageScope.WORKSPACE);
|
||||
}
|
||||
|
||||
super.saveState();
|
||||
|
@ -876,27 +877,6 @@ function getReplView(viewsService: IViewsService): Repl | undefined {
|
|||
return viewsService.getActiveViewWithId(REPL_VIEW_ID) as Repl ?? undefined;
|
||||
}
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
_isFakeAction: true,
|
||||
id: FILTER_ACTION_ID,
|
||||
title: localize('filter', "Filter"),
|
||||
f1: false,
|
||||
menu: {
|
||||
id: MenuId.ViewTitle,
|
||||
group: 'navigation',
|
||||
when: ContextKeyExpr.equals('view', REPL_VIEW_ID),
|
||||
order: 10
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
run(_accessor: ServicesAccessor) {
|
||||
// noop this action is just a placeholder for the filter action view item
|
||||
}
|
||||
});
|
||||
|
||||
const selectReplCommandId = 'workbench.action.debug.selectRepl';
|
||||
registerAction2(class extends ViewAction<Repl> {
|
||||
constructor() {
|
||||
|
|
|
@ -7,23 +7,7 @@ import { matchesFuzzy } from 'vs/base/common/filters';
|
|||
import { splitGlobAware } from 'vs/base/common/glob';
|
||||
import { ITreeFilter, TreeVisibility, TreeFilterResult } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IReplElement } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
|
||||
import { Delayer } from 'vs/base/common/async';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { HistoryInputBox } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { ContextScopedHistoryInputBox } from 'vs/platform/history/browser/contextScopedHistoryWidget';
|
||||
import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { badgeBackground, badgeForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ReplEvaluationResult, ReplEvaluationInput } from 'vs/workbench/contrib/debug/common/replModel';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Variable } from 'vs/workbench/contrib/debug/common/debugModel';
|
||||
|
||||
|
||||
|
@ -79,184 +63,3 @@ export class ReplFilter implements ITreeFilter<IReplElement> {
|
|||
return includeQueryPresent ? includeQueryMatched : (typeof parentVisibility !== 'undefined' ? parentVisibility : TreeVisibility.Visible);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IFilterStatsProvider {
|
||||
getFilterStats(): { total: number; filtered: number };
|
||||
}
|
||||
|
||||
export class ReplFilterState {
|
||||
|
||||
constructor(private filterStatsProvider: IFilterStatsProvider) { }
|
||||
|
||||
private readonly _onDidChange: Emitter<void> = new Emitter<void>();
|
||||
get onDidChange(): Event<void> {
|
||||
return this._onDidChange.event;
|
||||
}
|
||||
|
||||
private readonly _onDidStatsChange: Emitter<void> = new Emitter<void>();
|
||||
get onDidStatsChange(): Event<void> {
|
||||
return this._onDidStatsChange.event;
|
||||
}
|
||||
|
||||
private _filterText = '';
|
||||
private _stats = { total: 0, filtered: 0 };
|
||||
|
||||
get filterText(): string {
|
||||
return this._filterText;
|
||||
}
|
||||
|
||||
get filterStats(): { total: number; filtered: number } {
|
||||
return this._stats;
|
||||
}
|
||||
|
||||
set filterText(filterText: string) {
|
||||
if (this._filterText !== filterText) {
|
||||
this._filterText = filterText;
|
||||
this._onDidChange.fire();
|
||||
this.updateFilterStats();
|
||||
}
|
||||
}
|
||||
|
||||
updateFilterStats(): void {
|
||||
const { total, filtered } = this.filterStatsProvider.getFilterStats();
|
||||
if (this._stats.total !== total || this._stats.filtered !== filtered) {
|
||||
this._stats = { total, filtered };
|
||||
this._onDidStatsChange.fire();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ReplFilterActionViewItem extends BaseActionViewItem {
|
||||
|
||||
private delayedFilterUpdate: Delayer<void>;
|
||||
private container!: HTMLElement;
|
||||
private filterBadge!: HTMLElement;
|
||||
private filterInputBox!: HistoryInputBox;
|
||||
|
||||
constructor(
|
||||
action: IAction,
|
||||
private placeholder: string,
|
||||
private filters: ReplFilterState,
|
||||
private history: string[],
|
||||
private showHistoryHint: () => boolean,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IThemeService private readonly themeService: IThemeService,
|
||||
@IContextViewService private readonly contextViewService: IContextViewService) {
|
||||
super(null, action);
|
||||
this.delayedFilterUpdate = new Delayer<void>(400);
|
||||
this._register(toDisposable(() => this.delayedFilterUpdate.cancel()));
|
||||
}
|
||||
|
||||
override render(container: HTMLElement): void {
|
||||
this.container = container;
|
||||
this.container.classList.add('repl-panel-filter-container');
|
||||
|
||||
this.element = DOM.append(this.container, DOM.$(''));
|
||||
this.element.className = this.class;
|
||||
this.createInput(this.element);
|
||||
this.createBadge(this.element);
|
||||
this.updateClass();
|
||||
}
|
||||
|
||||
override focus(): void {
|
||||
this.filterInputBox?.focus();
|
||||
}
|
||||
|
||||
override blur(): void {
|
||||
this.filterInputBox?.blur();
|
||||
}
|
||||
|
||||
override setFocusable(): void {
|
||||
// noop input elements are focusable by default
|
||||
}
|
||||
|
||||
getHistory(): string[] {
|
||||
return this.filterInputBox.getHistory();
|
||||
}
|
||||
|
||||
override get trapsArrowNavigation(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
private clearFilterText(): void {
|
||||
this.filterInputBox.value = '';
|
||||
}
|
||||
|
||||
private createInput(container: HTMLElement): void {
|
||||
this.filterInputBox = this._register(this.instantiationService.createInstance(ContextScopedHistoryInputBox, container, this.contextViewService, {
|
||||
placeholder: this.placeholder,
|
||||
history: this.history,
|
||||
showHistoryHint: this.showHistoryHint
|
||||
}));
|
||||
this._register(attachInputBoxStyler(this.filterInputBox, this.themeService));
|
||||
this.filterInputBox.value = this.filters.filterText;
|
||||
|
||||
this._register(this.filterInputBox.onDidChange(() => this.delayedFilterUpdate.trigger(() => this.onDidInputChange(this.filterInputBox!))));
|
||||
this._register(this.filters.onDidChange(() => {
|
||||
this.filterInputBox.value = this.filters.filterText;
|
||||
}));
|
||||
this._register(DOM.addStandardDisposableListener(this.filterInputBox.inputElement, DOM.EventType.KEY_DOWN, (e: any) => this.onInputKeyDown(e)));
|
||||
this._register(DOM.addStandardDisposableListener(container, DOM.EventType.KEY_DOWN, this.handleKeyboardEvent));
|
||||
this._register(DOM.addStandardDisposableListener(container, DOM.EventType.KEY_UP, this.handleKeyboardEvent));
|
||||
this._register(DOM.addStandardDisposableListener(this.filterInputBox.inputElement, DOM.EventType.CLICK, (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}));
|
||||
}
|
||||
|
||||
private onDidInputChange(inputbox: HistoryInputBox) {
|
||||
inputbox.addToHistory();
|
||||
this.filters.filterText = inputbox.value;
|
||||
}
|
||||
|
||||
// Action toolbar is swallowing some keys for action items which should not be for an input box
|
||||
private handleKeyboardEvent(event: StandardKeyboardEvent) {
|
||||
if (event.equals(KeyCode.Space)
|
||||
|| event.equals(KeyCode.LeftArrow)
|
||||
|| event.equals(KeyCode.RightArrow)
|
||||
|| event.equals(KeyCode.Escape)
|
||||
) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
private onInputKeyDown(event: StandardKeyboardEvent) {
|
||||
if (event.equals(KeyCode.Escape)) {
|
||||
this.clearFilterText();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
private createBadge(container: HTMLElement): void {
|
||||
const controlsContainer = DOM.append(container, DOM.$('.repl-panel-filter-controls'));
|
||||
const filterBadge = this.filterBadge = DOM.append(controlsContainer, DOM.$('.repl-panel-filter-badge'));
|
||||
this._register(attachStylerCallback(this.themeService, { badgeBackground, badgeForeground, contrastBorder }, colors => {
|
||||
const background = colors.badgeBackground ? colors.badgeBackground.toString() : '';
|
||||
const foreground = colors.badgeForeground ? colors.badgeForeground.toString() : '';
|
||||
const border = colors.contrastBorder ? colors.contrastBorder.toString() : '';
|
||||
|
||||
filterBadge.style.backgroundColor = background;
|
||||
|
||||
filterBadge.style.borderWidth = border ? '1px' : '';
|
||||
filterBadge.style.borderStyle = border ? 'solid' : '';
|
||||
filterBadge.style.borderColor = border;
|
||||
filterBadge.style.color = foreground;
|
||||
}));
|
||||
this.updateBadge();
|
||||
this._register(this.filters.onDidStatsChange(() => this.updateBadge()));
|
||||
}
|
||||
|
||||
private updateBadge(): void {
|
||||
const { total, filtered } = this.filters.filterStats;
|
||||
const filterBadgeHidden = total === filtered || total === 0;
|
||||
|
||||
this.filterBadge.classList.toggle('hidden', filterBadgeHidden);
|
||||
this.filterBadge.textContent = localize('showing filtered repl lines', "Showing {0} of {1}", filtered, total);
|
||||
this.filterInputBox.inputElement.style.paddingRight = filterBadgeHidden ? '4px' : '150px';
|
||||
}
|
||||
|
||||
protected get class(): string {
|
||||
return 'panel-action-tree-filter';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ import { Codicon } from 'vs/base/common/codicons';
|
|||
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
|
||||
import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
import { viewFilterSubmenu } from 'vs/workbench/browser/parts/views/viewFilter';
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: Markers.MARKER_OPEN_ACTION_ID,
|
||||
|
@ -196,6 +197,131 @@ registerAction2(class extends ViewAction<IMarkersView> {
|
|||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends ViewAction<IMarkersView> {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.${Markers.MARKERS_VIEW_ID}.toggleErrors`,
|
||||
title: localize('toggle errors', "Toggle Errors"),
|
||||
category: localize('problems', "Problems"),
|
||||
toggled: {
|
||||
condition: MarkersContextKeys.ShowErrorsFilterContextKey,
|
||||
title: localize('errors', "Show Errors")
|
||||
},
|
||||
menu: {
|
||||
id: viewFilterSubmenu,
|
||||
group: '1_filter',
|
||||
when: ContextKeyExpr.equals('view', Markers.MARKERS_VIEW_ID),
|
||||
order: 1
|
||||
},
|
||||
viewId: Markers.MARKERS_VIEW_ID
|
||||
});
|
||||
}
|
||||
|
||||
async runInView(serviceAccessor: ServicesAccessor, view: IMarkersView): Promise<void> {
|
||||
view.filters.showErrors = !view.filters.showErrors;
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends ViewAction<IMarkersView> {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.${Markers.MARKERS_VIEW_ID}.toggleWarnings`,
|
||||
title: localize('toggle warnings', "Toggle Warnings"),
|
||||
category: localize('problems', "Problems"),
|
||||
toggled: {
|
||||
condition: MarkersContextKeys.ShowWarningsFilterContextKey,
|
||||
title: localize('warnings', "Show Warnings")
|
||||
},
|
||||
menu: {
|
||||
id: viewFilterSubmenu,
|
||||
group: '1_filter',
|
||||
when: ContextKeyExpr.equals('view', Markers.MARKERS_VIEW_ID),
|
||||
order: 2
|
||||
},
|
||||
viewId: Markers.MARKERS_VIEW_ID
|
||||
});
|
||||
}
|
||||
|
||||
async runInView(serviceAccessor: ServicesAccessor, view: IMarkersView): Promise<void> {
|
||||
view.filters.showWarnings = !view.filters.showWarnings;
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends ViewAction<IMarkersView> {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.${Markers.MARKERS_VIEW_ID}.toggleInfos`,
|
||||
title: localize('toggle infos', "Toggle Infos"),
|
||||
category: localize('problems', "Problems"),
|
||||
toggled: {
|
||||
condition: MarkersContextKeys.ShowInfoFilterContextKey,
|
||||
title: localize('Infos', "Show Infos")
|
||||
},
|
||||
menu: {
|
||||
id: viewFilterSubmenu,
|
||||
group: '1_filter',
|
||||
when: ContextKeyExpr.equals('view', Markers.MARKERS_VIEW_ID),
|
||||
order: 3
|
||||
},
|
||||
viewId: Markers.MARKERS_VIEW_ID
|
||||
});
|
||||
}
|
||||
|
||||
async runInView(serviceAccessor: ServicesAccessor, view: IMarkersView): Promise<void> {
|
||||
view.filters.showInfos = !view.filters.showInfos;
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends ViewAction<IMarkersView> {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.${Markers.MARKERS_VIEW_ID}.toggleActiveFile`,
|
||||
title: localize('toggle active file', "Toggle Active File"),
|
||||
category: localize('problems', "Problems"),
|
||||
toggled: {
|
||||
condition: MarkersContextKeys.ShowActiveFileFilterContextKey,
|
||||
title: localize('Active File', "Show Active File Only")
|
||||
},
|
||||
menu: {
|
||||
id: viewFilterSubmenu,
|
||||
group: '2_filter',
|
||||
when: ContextKeyExpr.equals('view', Markers.MARKERS_VIEW_ID),
|
||||
order: 1
|
||||
},
|
||||
viewId: Markers.MARKERS_VIEW_ID
|
||||
});
|
||||
}
|
||||
|
||||
async runInView(serviceAccessor: ServicesAccessor, view: IMarkersView): Promise<void> {
|
||||
view.filters.activeFile = !view.filters.activeFile;
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends ViewAction<IMarkersView> {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.${Markers.MARKERS_VIEW_ID}.toggleExcludedFiles`,
|
||||
title: localize('toggle Excluded Files', "Toggle Excluded Files"),
|
||||
category: localize('problems', "Problems"),
|
||||
toggled: {
|
||||
condition: MarkersContextKeys.ShowExcludedFilesFilterContextKey,
|
||||
title: localize('Excluded Files', "Hide Excluded Files")
|
||||
},
|
||||
menu: {
|
||||
id: viewFilterSubmenu,
|
||||
group: '2_filter',
|
||||
when: ContextKeyExpr.equals('view', Markers.MARKERS_VIEW_ID),
|
||||
order: 2
|
||||
},
|
||||
viewId: Markers.MARKERS_VIEW_ID
|
||||
});
|
||||
}
|
||||
|
||||
async runInView(serviceAccessor: ServicesAccessor, view: IMarkersView): Promise<void> {
|
||||
view.filters.excludedFiles = !view.filters.excludedFiles;
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
|
@ -406,23 +532,6 @@ registerAction2(class extends ViewAction<IMarkersView> {
|
|||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
_isFakeAction: true,
|
||||
id: `workbench.actions.treeView.${Markers.MARKERS_VIEW_ID}.filter`,
|
||||
title: localize('filter', "Filter"),
|
||||
menu: {
|
||||
id: MenuId.ViewTitle,
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.equals('view', Markers.MARKERS_VIEW_ID), MarkersContextKeys.MarkersViewSmallLayoutContextKey.negate()),
|
||||
group: 'navigation',
|
||||
order: 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
async run(): Promise<void> { }
|
||||
});
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
|
|
|
@ -4,17 +4,13 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { MarkersFilters } from 'vs/workbench/contrib/markers/browser/markersViewActions';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IView } from 'vs/workbench/common/views';
|
||||
import { MarkerElement, ResourceMarkers } from 'vs/workbench/contrib/markers/browser/markersModel';
|
||||
import { MarkersViewMode } from 'vs/workbench/contrib/markers/common/markers';
|
||||
|
||||
export interface IMarkersView extends IView {
|
||||
|
||||
readonly onDidFocusFilter: Event<void>;
|
||||
readonly onDidClearFilterText: Event<void>;
|
||||
readonly filters: MarkersFilters;
|
||||
readonly onDidChangeFilterStats: Event<{ total: number; filtered: number }>;
|
||||
focusFilter(): void;
|
||||
clearFilterText(): void;
|
||||
getFilterStats(): { total: number; filtered: number };
|
||||
|
|
|
@ -7,12 +7,12 @@ import 'vs/css!./media/markers';
|
|||
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { IAction, Action, Separator } from 'vs/base/common/actions';
|
||||
import { IAction, Separator } from 'vs/base/common/actions';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { Marker, ResourceMarkers, RelatedInformation, MarkerChangesEvent, MarkersModel, compareMarkersByUri, MarkerElement, MarkerTableItem } from 'vs/workbench/contrib/markers/browser/markersModel';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { MarkersFilterActionViewItem, MarkersFilters, IMarkersFiltersChangeEvent } from 'vs/workbench/contrib/markers/browser/markersViewActions';
|
||||
import { MarkersFilters, IMarkersFiltersChangeEvent } from 'vs/workbench/contrib/markers/browser/markersViewActions';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import Messages from 'vs/workbench/contrib/markers/browser/messages';
|
||||
import { RangeHighlightDecorations } from 'vs/workbench/browser/codeeditor';
|
||||
|
@ -23,7 +23,7 @@ import { localize } from 'vs/nls';
|
|||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { ITreeElement, ITreeNode, ITreeContextMenuEvent, ITreeRenderer, ITreeEvent } from 'vs/base/browser/ui/tree/tree';
|
||||
import { Relay, Event, Emitter } from 'vs/base/common/event';
|
||||
import { Relay, Event } from 'vs/base/common/event';
|
||||
import { WorkbenchObjectTree, IListService, IWorkbenchObjectTreeOptions, IOpenEvent } from 'vs/platform/list/browser/listService';
|
||||
import { FilterOptions } from 'vs/workbench/contrib/markers/browser/markersFilterOptions';
|
||||
import { IExpression } from 'vs/base/common/glob';
|
||||
|
@ -31,7 +31,6 @@ import { deepClone } from 'vs/base/common/objects';
|
|||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { FilterData, Filter, VirtualDelegate, ResourceMarkersRenderer, MarkerRenderer, RelatedInformationRenderer, MarkersWidgetAccessibilityProvider, MarkersViewModel } from 'vs/workbench/contrib/markers/browser/markersTreeViewer';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { ActionBar, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
|
@ -42,7 +41,7 @@ import { MementoObject, Memento } from 'vs/workbench/common/memento';
|
|||
import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { editorLightBulbForeground, editorLightBulbAutoFixForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { IViewPaneOptions, FilterViewPane } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { IOpenerService, withSelection } from 'vs/platform/opener/common/opener';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
|
@ -94,7 +93,7 @@ export interface IProblemsWidget {
|
|||
updateMarker(marker: Marker): void;
|
||||
}
|
||||
|
||||
export class MarkersView extends ViewPane implements IMarkersView {
|
||||
export class MarkersView extends FilterViewPane implements IMarkersView {
|
||||
|
||||
private lastSelectedRelativeTop: number = 0;
|
||||
private currentActiveResource: URI | null = null;
|
||||
|
@ -109,7 +108,6 @@ export class MarkersView extends ViewPane implements IMarkersView {
|
|||
private widgetContainer!: HTMLElement;
|
||||
private widgetIdentityProvider: IIdentityProvider<MarkerElement | MarkerTableItem>;
|
||||
private widgetAccessibilityProvider: MarkersWidgetAccessibilityProvider;
|
||||
private filterActionBar: ActionBar | undefined;
|
||||
private messageBoxContainer: HTMLElement | undefined;
|
||||
private ariaLabelElement: HTMLElement | undefined;
|
||||
readonly filters: MarkersFilters;
|
||||
|
@ -118,24 +116,13 @@ export class MarkersView extends ViewPane implements IMarkersView {
|
|||
private currentWidth = 0;
|
||||
private readonly panelState: MementoObject;
|
||||
|
||||
private _onDidChangeFilterStats = this._register(new Emitter<{ total: number; filtered: number }>());
|
||||
readonly onDidChangeFilterStats: Event<{ total: number; filtered: number }> = this._onDidChangeFilterStats.event;
|
||||
private cachedFilterStats: { total: number; filtered: number } | undefined = undefined;
|
||||
|
||||
private currentResourceGotAddedToMarkersData: boolean = false;
|
||||
private readonly markersViewModel: MarkersViewModel;
|
||||
private readonly smallLayoutContextKey: IContextKey<boolean>;
|
||||
private get smallLayout(): boolean { return !!this.smallLayoutContextKey.get(); }
|
||||
private set smallLayout(smallLayout: boolean) { this.smallLayoutContextKey.set(smallLayout); }
|
||||
|
||||
readonly onDidChangeVisibility = this.onDidChangeBodyVisibility;
|
||||
|
||||
private readonly _onDidFocusFilter: Emitter<void> = this._register(new Emitter<void>());
|
||||
readonly onDidFocusFilter: Event<void> = this._onDidFocusFilter.event;
|
||||
|
||||
private readonly _onDidClearFilterText: Emitter<void> = this._register(new Emitter<void>());
|
||||
readonly onDidClearFilterText: Event<void> = this._onDidClearFilterText.event;
|
||||
|
||||
constructor(
|
||||
options: IViewPaneOptions,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
|
@ -153,9 +140,18 @@ export class MarkersView extends ViewPane implements IMarkersView {
|
|||
@IOpenerService openerService: IOpenerService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
) {
|
||||
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
|
||||
this.smallLayoutContextKey = MarkersContextKeys.MarkersViewSmallLayoutContextKey.bindTo(this.contextKeyService);
|
||||
this.panelState = new Memento(Markers.MARKERS_VIEW_STORAGE_ID, storageService).getMemento(StorageScope.WORKSPACE, StorageTarget.USER);
|
||||
const panelState = new Memento(Markers.MARKERS_VIEW_STORAGE_ID, storageService).getMemento(StorageScope.WORKSPACE, StorageTarget.USER);
|
||||
super({
|
||||
...options,
|
||||
filterOptions: {
|
||||
ariaLabel: Messages.MARKERS_PANEL_FILTER_ARIA_LABEL,
|
||||
placeholder: Messages.MARKERS_PANEL_FILTER_PLACEHOLDER,
|
||||
focusContextKey: MarkersContextKeys.MarkerViewFilterFocusContextKey.key,
|
||||
text: panelState['filter'] || '',
|
||||
history: panelState['filterHistory'] || []
|
||||
}
|
||||
}, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
|
||||
this.panelState = panelState;
|
||||
|
||||
this.markersModel = this._register(instantiationService.createInstance(MarkersModel));
|
||||
this.markersViewModel = this._register(instantiationService.createInstance(MarkersViewModel, this.panelState['multiline'], this.panelState['viewMode'] ?? this.getDefaultViewMode()));
|
||||
|
@ -171,15 +167,13 @@ export class MarkersView extends ViewPane implements IMarkersView {
|
|||
this.rangeHighlightDecorations = this._register(this.instantiationService.createInstance(RangeHighlightDecorations));
|
||||
|
||||
this.filters = this._register(new MarkersFilters({
|
||||
filterText: this.panelState['filter'] || '',
|
||||
filterHistory: this.panelState['filterHistory'] || [],
|
||||
showErrors: this.panelState['showErrors'] !== false,
|
||||
showWarnings: this.panelState['showWarnings'] !== false,
|
||||
showInfos: this.panelState['showInfos'] !== false,
|
||||
excludedFiles: !!this.panelState['useFilesExclude'],
|
||||
activeFile: !!this.panelState['activeFile'],
|
||||
layout: new dom.Dimension(0, 0)
|
||||
}));
|
||||
}, this.contextKeyService));
|
||||
|
||||
// Update filter, whenever the "files.exclude" setting is changed
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => {
|
||||
|
@ -203,9 +197,6 @@ export class MarkersView extends ViewPane implements IMarkersView {
|
|||
|
||||
this.createArialLabelElement(panelContainer);
|
||||
|
||||
this.createFilterActionBar(panelContainer);
|
||||
this.filterActionBar!.push(new Action(`workbench.actions.treeView.${this.id}.filter`));
|
||||
|
||||
this.createMessageBox(panelContainer);
|
||||
|
||||
this.widgetContainer = dom.append(panelContainer, dom.$('.widget-container'));
|
||||
|
@ -219,19 +210,11 @@ export class MarkersView extends ViewPane implements IMarkersView {
|
|||
return Messages.MARKERS_PANEL_TITLE_PROBLEMS;
|
||||
}
|
||||
|
||||
public override layoutBody(height: number = this.currentHeight, width: number = this.currentWidth): void {
|
||||
super.layoutBody(height, width);
|
||||
const wasSmallLayout = this.smallLayout;
|
||||
this.smallLayout = width < 600 && height > 100;
|
||||
if (this.smallLayout !== wasSmallLayout) {
|
||||
this.filterActionBar?.getContainer().classList.toggle('hide', !this.smallLayout);
|
||||
}
|
||||
const contentHeight = this.smallLayout ? height - 44 : height;
|
||||
public override layoutBodyContent(height: number = this.currentHeight, width: number = this.currentWidth): void {
|
||||
if (this.messageBoxContainer) {
|
||||
this.messageBoxContainer.style.height = `${contentHeight}px`;
|
||||
this.messageBoxContainer.style.height = `${height}px`;
|
||||
}
|
||||
this.widget.layout(contentHeight, width);
|
||||
this.filters.layout = new dom.Dimension(this.smallLayout ? width : width - 200, height);
|
||||
this.widget.layout(height, width);
|
||||
|
||||
this.currentHeight = height;
|
||||
this.currentWidth = width;
|
||||
|
@ -251,11 +234,19 @@ export class MarkersView extends ViewPane implements IMarkersView {
|
|||
}
|
||||
|
||||
public focusFilter(): void {
|
||||
this._onDidFocusFilter.fire();
|
||||
this.filterWidget.focus();
|
||||
}
|
||||
|
||||
public updateBadge(total: number, filtered: number): void {
|
||||
this.filterWidget.updateBadge(total === filtered || total === 0 ? undefined : localize('showing filtered problems', "Showing {0} of {1}", filtered, total));
|
||||
}
|
||||
|
||||
public checkMoreFilters(): void {
|
||||
this.filterWidget.checkMoreFilters(!this.filters.showErrors || !this.filters.showWarnings || !this.filters.showInfos || this.filters.excludedFiles || this.filters.activeFile);
|
||||
}
|
||||
|
||||
public clearFilterText(): void {
|
||||
this._onDidClearFilterText.fire();
|
||||
this.filterWidget.setFilterText('');
|
||||
}
|
||||
|
||||
public showQuickFixes(marker: Marker): void {
|
||||
|
@ -323,7 +314,8 @@ export class MarkersView extends ViewPane implements IMarkersView {
|
|||
this.toggleVisibility(total === 0 || filtered === 0);
|
||||
this.renderMessage();
|
||||
|
||||
this._onDidChangeFilterStats.fire(this.getFilterStats());
|
||||
this.updateBadge(total, filtered);
|
||||
this.checkMoreFilters();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -336,7 +328,7 @@ export class MarkersView extends ViewPane implements IMarkersView {
|
|||
}
|
||||
|
||||
private updateFilter() {
|
||||
this.filter.options = new FilterOptions(this.filters.filterText, this.getFilesExcludeExpressions(), this.filters.showWarnings, this.filters.showErrors, this.filters.showInfos, this.uriIdentityService);
|
||||
this.filter.options = new FilterOptions(this.filterWidget.getFilterText(), this.getFilesExcludeExpressions(), this.filters.showWarnings, this.filters.showErrors, this.filters.showInfos, this.uriIdentityService);
|
||||
this.widget.filterMarkers(this.getResourceMarkers(), this.filter.options);
|
||||
|
||||
this.cachedFilterStats = undefined;
|
||||
|
@ -344,7 +336,8 @@ export class MarkersView extends ViewPane implements IMarkersView {
|
|||
this.toggleVisibility(total === 0 || filtered === 0);
|
||||
this.renderMessage();
|
||||
|
||||
this._onDidChangeFilterStats.fire(this.getFilterStats());
|
||||
this.updateBadge(total, filtered);
|
||||
this.checkMoreFilters();
|
||||
}
|
||||
|
||||
private getDefaultViewMode(): MarkersViewMode {
|
||||
|
@ -389,12 +382,6 @@ export class MarkersView extends ViewPane implements IMarkersView {
|
|||
return resourceMarkers;
|
||||
}
|
||||
|
||||
private createFilterActionBar(parent: HTMLElement): void {
|
||||
this.filterActionBar = this._register(new ActionBar(parent, { actionViewItemProvider: action => this.getActionViewItem(action) }));
|
||||
this.filterActionBar.getContainer().classList.add('markers-panel-filter-container');
|
||||
this.filterActionBar.getContainer().classList.toggle('hide', !this.smallLayout);
|
||||
}
|
||||
|
||||
private createMessageBox(parent: HTMLElement): void {
|
||||
this.messageBoxContainer = dom.append(parent, dom.$('.message-box-container'));
|
||||
this.messageBoxContainer.setAttribute('aria-labelledby', 'markers-panel-arialabel');
|
||||
|
@ -558,10 +545,11 @@ export class MarkersView extends ViewPane implements IMarkersView {
|
|||
disposables.push(this.filters.onDidChange((event: IMarkersFiltersChangeEvent) => {
|
||||
if (event.activeFile) {
|
||||
this.refreshPanel();
|
||||
} else if (event.filterText || event.excludedFiles || event.showWarnings || event.showErrors || event.showInfos) {
|
||||
} else if (event.excludedFiles || event.showWarnings || event.showErrors || event.showInfos) {
|
||||
this.updateFilter();
|
||||
}
|
||||
}));
|
||||
disposables.push(this.filterWidget.onDidChangeFilterText(e => this.updateFilter()));
|
||||
disposables.push(toDisposable(() => { this.cachedFilterStats = undefined; }));
|
||||
|
||||
disposables.push(toDisposable(() => this.rangeHighlightDecorations.removeHighlightRange()));
|
||||
|
@ -744,7 +732,7 @@ export class MarkersView extends ViewPane implements IMarkersView {
|
|||
}
|
||||
|
||||
private clearFilters(): void {
|
||||
this.filters.filterText = '';
|
||||
this.filterWidget.setFilterText('');
|
||||
this.filters.excludedFiles = false;
|
||||
this.filters.showErrors = true;
|
||||
this.filters.showWarnings = true;
|
||||
|
@ -862,13 +850,6 @@ export class MarkersView extends ViewPane implements IMarkersView {
|
|||
return this.markersModel.resourceMarkers;
|
||||
}
|
||||
|
||||
public override getActionViewItem(action: IAction): IActionViewItem | undefined {
|
||||
if (action.id === `workbench.actions.treeView.${this.id}.filter`) {
|
||||
return this.instantiationService.createInstance(MarkersFilterActionViewItem, action, this);
|
||||
}
|
||||
return super.getActionViewItem(action);
|
||||
}
|
||||
|
||||
getFilterStats(): { total: number; filtered: number } {
|
||||
if (!this.cachedFilterStats) {
|
||||
this.cachedFilterStats = {
|
||||
|
@ -882,11 +863,11 @@ export class MarkersView extends ViewPane implements IMarkersView {
|
|||
|
||||
private toggleVisibility(hide: boolean): void {
|
||||
this.widget.toggleVisibility(hide);
|
||||
this.layoutBody();
|
||||
this.layoutBodyContent();
|
||||
}
|
||||
|
||||
override saveState(): void {
|
||||
this.panelState['filter'] = this.filters.filterText;
|
||||
this.panelState['filter'] = this.filterWidget.getFilterText();
|
||||
this.panelState['filterHistory'] = this.filters.filterHistory;
|
||||
this.panelState['showErrors'] = this.filters.showErrors;
|
||||
this.panelState['showWarnings'] = this.filters.showWarnings;
|
||||
|
|
|
@ -3,54 +3,35 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Delayer } from 'vs/base/common/async';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { Action, IAction, IActionRunner, Separator } from 'vs/base/common/actions';
|
||||
import { HistoryInputBox } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import Messages from 'vs/workbench/contrib/markers/browser/messages';
|
||||
import { IThemeService, registerThemingParticipant, ICssStyleCollector, IColorTheme, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler';
|
||||
import { toDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { badgeBackground, badgeForeground, contrastBorder, inputActiveOptionBorder, inputActiveOptionBackground, inputActiveOptionForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ContextScopedHistoryInputBox } from 'vs/platform/history/browser/contextScopedHistoryWidget';
|
||||
import { registerThemingParticipant, ICssStyleCollector, IColorTheme } from 'vs/platform/theme/common/themeService';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { inputActiveOptionBorder, inputActiveOptionBackground, inputActiveOptionForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { Marker } from 'vs/workbench/contrib/markers/browser/markersModel';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { BaseActionViewItem, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
|
||||
import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem';
|
||||
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
|
||||
import { IMarkersView } from 'vs/workbench/contrib/markers/browser/markers';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { showHistoryKeybindingHint } from 'vs/platform/history/browser/historyWidgetKeybindingHint';
|
||||
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
|
||||
import { MarkersContextKeys } from 'vs/workbench/contrib/markers/common/markers';
|
||||
|
||||
export interface IMarkersFiltersChangeEvent {
|
||||
filterText?: boolean;
|
||||
excludedFiles?: boolean;
|
||||
showWarnings?: boolean;
|
||||
showErrors?: boolean;
|
||||
showInfos?: boolean;
|
||||
activeFile?: boolean;
|
||||
layout?: boolean;
|
||||
}
|
||||
|
||||
export interface IMarkersFiltersOptions {
|
||||
filterText: string;
|
||||
filterHistory: string[];
|
||||
showErrors: boolean;
|
||||
showWarnings: boolean;
|
||||
showInfos: boolean;
|
||||
excludedFiles: boolean;
|
||||
activeFile: boolean;
|
||||
layout: DOM.Dimension;
|
||||
}
|
||||
|
||||
export class MarkersFilters extends Disposable {
|
||||
|
@ -58,384 +39,74 @@ export class MarkersFilters extends Disposable {
|
|||
private readonly _onDidChange: Emitter<IMarkersFiltersChangeEvent> = this._register(new Emitter<IMarkersFiltersChangeEvent>());
|
||||
readonly onDidChange: Event<IMarkersFiltersChangeEvent> = this._onDidChange.event;
|
||||
|
||||
constructor(options: IMarkersFiltersOptions) {
|
||||
constructor(options: IMarkersFiltersOptions, private readonly contextKeyService: IContextKeyService) {
|
||||
super();
|
||||
this._filterText = options.filterText;
|
||||
this._showErrors = options.showErrors;
|
||||
this._showWarnings = options.showWarnings;
|
||||
this._showInfos = options.showInfos;
|
||||
this._excludedFiles = options.excludedFiles;
|
||||
this._activeFile = options.activeFile;
|
||||
this.filterHistory = options.filterHistory;
|
||||
this._layout = options.layout;
|
||||
}
|
||||
|
||||
private _filterText: string;
|
||||
get filterText(): string {
|
||||
return this._filterText;
|
||||
}
|
||||
set filterText(filterText: string) {
|
||||
if (this._filterText !== filterText) {
|
||||
this._filterText = filterText;
|
||||
this._onDidChange.fire({ filterText: true });
|
||||
}
|
||||
this._showErrors.set(options.showErrors);
|
||||
this._showWarnings.set(options.showWarnings);
|
||||
this._showInfos.set(options.showInfos);
|
||||
this._excludedFiles.set(options.excludedFiles);
|
||||
this._activeFile.set(options.activeFile);
|
||||
this.filterHistory = options.filterHistory;
|
||||
}
|
||||
|
||||
filterHistory: string[];
|
||||
|
||||
private _excludedFiles: boolean;
|
||||
private readonly _excludedFiles = MarkersContextKeys.ShowExcludedFilesFilterContextKey.bindTo(this.contextKeyService);
|
||||
get excludedFiles(): boolean {
|
||||
return this._excludedFiles;
|
||||
return !!this._excludedFiles.get();
|
||||
}
|
||||
set excludedFiles(filesExclude: boolean) {
|
||||
if (this._excludedFiles !== filesExclude) {
|
||||
this._excludedFiles = filesExclude;
|
||||
if (this._excludedFiles.get() !== filesExclude) {
|
||||
this._excludedFiles.set(filesExclude);
|
||||
this._onDidChange.fire(<IMarkersFiltersChangeEvent>{ excludedFiles: true });
|
||||
}
|
||||
}
|
||||
|
||||
private _activeFile: boolean;
|
||||
private readonly _activeFile = MarkersContextKeys.ShowActiveFileFilterContextKey.bindTo(this.contextKeyService);
|
||||
get activeFile(): boolean {
|
||||
return this._activeFile;
|
||||
return !!this._activeFile.get();
|
||||
}
|
||||
set activeFile(activeFile: boolean) {
|
||||
if (this._activeFile !== activeFile) {
|
||||
this._activeFile = activeFile;
|
||||
if (this._activeFile.get() !== activeFile) {
|
||||
this._activeFile.set(activeFile);
|
||||
this._onDidChange.fire(<IMarkersFiltersChangeEvent>{ activeFile: true });
|
||||
}
|
||||
}
|
||||
|
||||
private _showWarnings: boolean = true;
|
||||
private readonly _showWarnings = MarkersContextKeys.ShowWarningsFilterContextKey.bindTo(this.contextKeyService);
|
||||
get showWarnings(): boolean {
|
||||
return this._showWarnings;
|
||||
return !!this._showWarnings.get();
|
||||
}
|
||||
set showWarnings(showWarnings: boolean) {
|
||||
if (this._showWarnings !== showWarnings) {
|
||||
this._showWarnings = showWarnings;
|
||||
if (this._showWarnings.get() !== showWarnings) {
|
||||
this._showWarnings.set(showWarnings);
|
||||
this._onDidChange.fire(<IMarkersFiltersChangeEvent>{ showWarnings: true });
|
||||
}
|
||||
}
|
||||
|
||||
private _showErrors: boolean = true;
|
||||
private readonly _showErrors = MarkersContextKeys.ShowErrorsFilterContextKey.bindTo(this.contextKeyService);
|
||||
get showErrors(): boolean {
|
||||
return this._showErrors;
|
||||
return !!this._showErrors.get();
|
||||
}
|
||||
set showErrors(showErrors: boolean) {
|
||||
if (this._showErrors !== showErrors) {
|
||||
this._showErrors = showErrors;
|
||||
if (this._showErrors.get() !== showErrors) {
|
||||
this._showErrors.set(showErrors);
|
||||
this._onDidChange.fire(<IMarkersFiltersChangeEvent>{ showErrors: true });
|
||||
}
|
||||
}
|
||||
|
||||
private _showInfos: boolean = true;
|
||||
private readonly _showInfos = MarkersContextKeys.ShowInfoFilterContextKey.bindTo(this.contextKeyService);
|
||||
get showInfos(): boolean {
|
||||
return this._showInfos;
|
||||
return !!this._showInfos.get();
|
||||
}
|
||||
set showInfos(showInfos: boolean) {
|
||||
if (this._showInfos !== showInfos) {
|
||||
this._showInfos = showInfos;
|
||||
if (this._showInfos.get() !== showInfos) {
|
||||
this._showInfos.set(showInfos);
|
||||
this._onDidChange.fire(<IMarkersFiltersChangeEvent>{ showInfos: true });
|
||||
}
|
||||
}
|
||||
|
||||
private _layout: DOM.Dimension = new DOM.Dimension(0, 0);
|
||||
get layout(): DOM.Dimension {
|
||||
return this._layout;
|
||||
}
|
||||
set layout(layout: DOM.Dimension) {
|
||||
if (this._layout.width !== layout.width || this._layout.height !== layout.height) {
|
||||
this._layout = layout;
|
||||
this._onDidChange.fire(<IMarkersFiltersChangeEvent>{ layout: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FiltersDropdownMenuActionViewItem extends DropdownMenuActionViewItem {
|
||||
|
||||
constructor(
|
||||
action: IAction, private filters: MarkersFilters, actionRunner: IActionRunner,
|
||||
@IContextMenuService contextMenuService: IContextMenuService
|
||||
) {
|
||||
super(action,
|
||||
{ getActions: () => this.getActions() },
|
||||
contextMenuService,
|
||||
{
|
||||
actionRunner,
|
||||
classNames: action.class,
|
||||
anchorAlignmentProvider: () => AnchorAlignment.RIGHT,
|
||||
menuAsChild: true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
override render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
this.updateChecked();
|
||||
}
|
||||
|
||||
private getActions(): IAction[] {
|
||||
return [
|
||||
{
|
||||
checked: this.filters.showErrors,
|
||||
class: undefined,
|
||||
enabled: true,
|
||||
id: 'showErrors',
|
||||
label: Messages.MARKERS_PANEL_FILTER_LABEL_SHOW_ERRORS,
|
||||
run: async () => this.filters.showErrors = !this.filters.showErrors,
|
||||
tooltip: ''
|
||||
},
|
||||
{
|
||||
checked: this.filters.showWarnings,
|
||||
class: undefined,
|
||||
enabled: true,
|
||||
id: 'showWarnings',
|
||||
label: Messages.MARKERS_PANEL_FILTER_LABEL_SHOW_WARNINGS,
|
||||
run: async () => this.filters.showWarnings = !this.filters.showWarnings,
|
||||
tooltip: ''
|
||||
},
|
||||
{
|
||||
checked: this.filters.showInfos,
|
||||
class: undefined,
|
||||
enabled: true,
|
||||
id: 'showInfos',
|
||||
label: Messages.MARKERS_PANEL_FILTER_LABEL_SHOW_INFOS,
|
||||
run: async () => this.filters.showInfos = !this.filters.showInfos,
|
||||
tooltip: ''
|
||||
},
|
||||
new Separator(),
|
||||
{
|
||||
checked: this.filters.activeFile,
|
||||
class: undefined,
|
||||
enabled: true,
|
||||
id: 'activeFile',
|
||||
label: Messages.MARKERS_PANEL_FILTER_LABEL_ACTIVE_FILE,
|
||||
run: async () => this.filters.activeFile = !this.filters.activeFile,
|
||||
tooltip: ''
|
||||
},
|
||||
{
|
||||
checked: this.filters.excludedFiles,
|
||||
class: undefined,
|
||||
enabled: true,
|
||||
id: 'useFilesExclude',
|
||||
label: Messages.MARKERS_PANEL_FILTER_LABEL_EXCLUDED_FILES,
|
||||
run: async () => this.filters.excludedFiles = !this.filters.excludedFiles,
|
||||
tooltip: ''
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
override updateChecked(): void {
|
||||
this.element!.classList.toggle('checked', this._action.checked);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
const filterIcon = registerIcon('markers-view-filter', Codicon.filter, localize('filterIcon', 'Icon for the filter configuration in the markers view.'));
|
||||
|
||||
export class MarkersFilterActionViewItem extends BaseActionViewItem {
|
||||
|
||||
private delayedFilterUpdate: Delayer<void>;
|
||||
private container: HTMLElement | null = null;
|
||||
private filterInputBox: HistoryInputBox | null = null;
|
||||
private filterBadge: HTMLElement | null = null;
|
||||
private focusContextKey: IContextKey<boolean>;
|
||||
private readonly filtersAction: IAction;
|
||||
private actionbar: ActionBar | null = null;
|
||||
private keybindingService;
|
||||
|
||||
constructor(
|
||||
action: IAction,
|
||||
private markersView: IMarkersView,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IContextViewService private readonly contextViewService: IContextViewService,
|
||||
@IThemeService private readonly themeService: IThemeService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IKeybindingService keybindingService: IKeybindingService
|
||||
) {
|
||||
super(null, action);
|
||||
this.keybindingService = keybindingService;
|
||||
this.focusContextKey = MarkersContextKeys.MarkerViewFilterFocusContextKey.bindTo(contextKeyService);
|
||||
this.delayedFilterUpdate = new Delayer<void>(400);
|
||||
this._register(toDisposable(() => this.delayedFilterUpdate.cancel()));
|
||||
this._register(markersView.onDidFocusFilter(() => this.focus()));
|
||||
this._register(markersView.onDidClearFilterText(() => this.clearFilterText()));
|
||||
this.filtersAction = new Action('markersFiltersAction', Messages.MARKERS_PANEL_ACTION_TOOLTIP_MORE_FILTERS, 'markers-filters ' + ThemeIcon.asClassName(filterIcon));
|
||||
this.filtersAction.checked = this.hasFiltersChanged();
|
||||
this._register(markersView.filters.onDidChange(e => this.onDidFiltersChange(e)));
|
||||
}
|
||||
|
||||
override render(container: HTMLElement): void {
|
||||
this.container = container;
|
||||
this.container.classList.add('markers-panel-action-filter-container');
|
||||
|
||||
this.element = DOM.append(this.container, DOM.$(''));
|
||||
this.element.className = this.class;
|
||||
this.createInput(this.element);
|
||||
this.createControls(this.element);
|
||||
this.updateClass();
|
||||
|
||||
this.adjustInputBox();
|
||||
}
|
||||
|
||||
override focus(): void {
|
||||
this.filterInputBox?.focus();
|
||||
}
|
||||
|
||||
override blur(): void {
|
||||
this.filterInputBox?.blur();
|
||||
}
|
||||
|
||||
override setFocusable(): void {
|
||||
// noop input elements are focusable by default
|
||||
}
|
||||
|
||||
override get trapsArrowNavigation(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
private clearFilterText(): void {
|
||||
if (this.filterInputBox) {
|
||||
this.filterInputBox.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
private onDidFiltersChange(e: IMarkersFiltersChangeEvent): void {
|
||||
this.filtersAction.checked = this.hasFiltersChanged();
|
||||
if (e.layout) {
|
||||
this.updateClass();
|
||||
}
|
||||
}
|
||||
|
||||
private hasFiltersChanged(): boolean {
|
||||
return !this.markersView.filters.showErrors || !this.markersView.filters.showWarnings || !this.markersView.filters.showInfos || this.markersView.filters.excludedFiles || this.markersView.filters.activeFile;
|
||||
}
|
||||
|
||||
private createInput(container: HTMLElement): void {
|
||||
this.filterInputBox = this._register(this.instantiationService.createInstance(ContextScopedHistoryInputBox, container, this.contextViewService, {
|
||||
placeholder: Messages.MARKERS_PANEL_FILTER_PLACEHOLDER,
|
||||
ariaLabel: Messages.MARKERS_PANEL_FILTER_ARIA_LABEL,
|
||||
history: this.markersView.filters.filterHistory,
|
||||
showHistoryHint: () => showHistoryKeybindingHint(this.keybindingService)
|
||||
}));
|
||||
this._register(attachInputBoxStyler(this.filterInputBox, this.themeService));
|
||||
this.filterInputBox.value = this.markersView.filters.filterText;
|
||||
this._register(this.filterInputBox.onDidChange(filter => this.delayedFilterUpdate.trigger(() => this.onDidInputChange(this.filterInputBox!))));
|
||||
this._register(this.markersView.filters.onDidChange((event: IMarkersFiltersChangeEvent) => {
|
||||
if (event.filterText) {
|
||||
this.filterInputBox!.value = this.markersView.filters.filterText;
|
||||
}
|
||||
}));
|
||||
this._register(DOM.addStandardDisposableListener(this.filterInputBox.inputElement, DOM.EventType.KEY_DOWN, (e: any) => this.onInputKeyDown(e, this.filterInputBox!)));
|
||||
this._register(DOM.addStandardDisposableListener(container, DOM.EventType.KEY_DOWN, this.handleKeyboardEvent));
|
||||
this._register(DOM.addStandardDisposableListener(container, DOM.EventType.KEY_UP, this.handleKeyboardEvent));
|
||||
this._register(DOM.addStandardDisposableListener(this.filterInputBox.inputElement, DOM.EventType.CLICK, (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}));
|
||||
|
||||
const focusTracker = this._register(DOM.trackFocus(this.filterInputBox.inputElement));
|
||||
this._register(focusTracker.onDidFocus(() => this.focusContextKey.set(true)));
|
||||
this._register(focusTracker.onDidBlur(() => this.focusContextKey.set(false)));
|
||||
this._register(toDisposable(() => this.focusContextKey.reset()));
|
||||
}
|
||||
|
||||
private createControls(container: HTMLElement): void {
|
||||
const controlsContainer = DOM.append(container, DOM.$('.markers-panel-filter-controls'));
|
||||
this.createBadge(controlsContainer);
|
||||
this.createFilters(controlsContainer);
|
||||
}
|
||||
|
||||
private createBadge(container: HTMLElement): void {
|
||||
const filterBadge = this.filterBadge = DOM.append(container, DOM.$('.markers-panel-filter-badge'));
|
||||
this._register(attachStylerCallback(this.themeService, { badgeBackground, badgeForeground, contrastBorder }, colors => {
|
||||
const background = colors.badgeBackground ? colors.badgeBackground.toString() : '';
|
||||
const foreground = colors.badgeForeground ? colors.badgeForeground.toString() : '';
|
||||
const border = colors.contrastBorder ? colors.contrastBorder.toString() : '';
|
||||
|
||||
filterBadge.style.backgroundColor = background;
|
||||
|
||||
filterBadge.style.borderWidth = border ? '1px' : '';
|
||||
filterBadge.style.borderStyle = border ? 'solid' : '';
|
||||
filterBadge.style.borderColor = border;
|
||||
filterBadge.style.color = foreground;
|
||||
}));
|
||||
this.updateBadge();
|
||||
this._register(this.markersView.onDidChangeFilterStats(() => this.updateBadge()));
|
||||
}
|
||||
|
||||
private createFilters(container: HTMLElement): void {
|
||||
this.actionbar = this._register(new ActionBar(container, {
|
||||
actionViewItemProvider: action => {
|
||||
if (action.id === this.filtersAction.id) {
|
||||
return this.instantiationService.createInstance(FiltersDropdownMenuActionViewItem, action, this.markersView.filters, this.actionRunner);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}));
|
||||
this.actionbar.push(this.filtersAction, { icon: true, label: false });
|
||||
}
|
||||
|
||||
private onDidInputChange(inputbox: HistoryInputBox) {
|
||||
inputbox.addToHistory();
|
||||
this.markersView.filters.filterText = inputbox.value;
|
||||
this.markersView.filters.filterHistory = inputbox.getHistory();
|
||||
}
|
||||
|
||||
private updateBadge(): void {
|
||||
if (this.filterBadge) {
|
||||
const { total, filtered } = this.markersView.getFilterStats();
|
||||
this.filterBadge.classList.toggle('hidden', total === filtered || total === 0);
|
||||
this.filterBadge.textContent = localize('showing filtered problems', "Showing {0} of {1}", filtered, total);
|
||||
this.adjustInputBox();
|
||||
}
|
||||
}
|
||||
|
||||
private adjustInputBox(): void {
|
||||
if (this.element && this.filterInputBox && this.filterBadge) {
|
||||
this.filterInputBox.inputElement.style.paddingRight = this.element.classList.contains('small') || this.filterBadge.classList.contains('hidden') ? '25px' : '150px';
|
||||
}
|
||||
}
|
||||
|
||||
// Action toolbar is swallowing some keys for action items which should not be for an input box
|
||||
private handleKeyboardEvent(event: StandardKeyboardEvent) {
|
||||
if (event.equals(KeyCode.Space)
|
||||
|| event.equals(KeyCode.LeftArrow)
|
||||
|| event.equals(KeyCode.RightArrow)
|
||||
) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
private onInputKeyDown(event: StandardKeyboardEvent, filterInputBox: HistoryInputBox) {
|
||||
let handled = false;
|
||||
if (event.equals(KeyCode.Tab)) {
|
||||
this.actionbar?.focus();
|
||||
handled = true;
|
||||
}
|
||||
if (handled) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
protected override updateClass(): void {
|
||||
if (this.element && this.container) {
|
||||
this.element.className = this.class;
|
||||
this.container.classList.toggle('grow', this.element.classList.contains('grow'));
|
||||
this.adjustInputBox();
|
||||
}
|
||||
}
|
||||
|
||||
protected get class(): string {
|
||||
if (this.markersView.filters.layout.width > 600) {
|
||||
return 'markers-panel-action-filter grow';
|
||||
} else if (this.markersView.filters.layout.width < 400) {
|
||||
return 'markers-panel-action-filter small';
|
||||
} else {
|
||||
return 'markers-panel-action-filter';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class QuickFixAction extends Action {
|
||||
|
|
|
@ -3,67 +3,6 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-action-bar .action-item.markers-panel-action-filter-container {
|
||||
cursor: default;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.monaco-action-bar .markers-panel-action-filter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.monaco-action-bar .markers-panel-action-filter .monaco-inputbox {
|
||||
height: 24px;
|
||||
font-size: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.pane-header .monaco-action-bar .markers-panel-action-filter .monaco-inputbox {
|
||||
height: 20px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.monaco-workbench.vs .monaco-action-bar .markers-panel-action-filter .monaco-inputbox {
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.markers-panel-action-filter > .markers-panel-filter-controls {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0;
|
||||
right: 0px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-badge {
|
||||
margin: 4px 0px;
|
||||
padding: 0px 8px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-badge.hidden,
|
||||
.markers-panel-action-filter.small > .markers-panel-filter-controls > .markers-panel-filter-badge {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.markers-panel-action-filter > .markers-panel-filter-controls > .monaco-action-bar .action-item .action-label.codicon.markers-filters {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.panel > .title .monaco-action-bar .action-item.markers-panel-action-filter-container {
|
||||
max-width: 400px;
|
||||
min-width: 300px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.markers-panel-container .monaco-action-bar.markers-panel-filter-container .action-item.markers-panel-action-filter-container,
|
||||
.panel > .title .monaco-action-bar .action-item.markers-panel-action-filter-container.grow {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.markers-panel .markers-panel-container {
|
||||
height: 100%;
|
||||
}
|
||||
|
@ -72,11 +11,6 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
.markers-panel-container .monaco-action-bar.markers-panel-filter-container {
|
||||
margin: 10px 20px;
|
||||
height: initial;
|
||||
}
|
||||
|
||||
.markers-panel .markers-panel-container .message-box-container {
|
||||
line-height: 22px;
|
||||
padding-left: 20px;
|
||||
|
|
|
@ -31,9 +31,13 @@ export namespace Markers {
|
|||
|
||||
export namespace MarkersContextKeys {
|
||||
export const MarkersViewModeContextKey = new RawContextKey<MarkersViewMode>('problemsViewMode', MarkersViewMode.Tree);
|
||||
export const MarkersViewSmallLayoutContextKey = new RawContextKey<boolean>(`problemsView.smallLayout`, false);
|
||||
export const MarkersTreeVisibilityContextKey = new RawContextKey<boolean>('problemsVisibility', false);
|
||||
export const MarkerFocusContextKey = new RawContextKey<boolean>('problemFocus', false);
|
||||
export const MarkerViewFilterFocusContextKey = new RawContextKey<boolean>('problemsFilterFocus', false);
|
||||
export const RelatedInformationFocusContextKey = new RawContextKey<boolean>('relatedInformationFocus', false);
|
||||
export const ShowErrorsFilterContextKey = new RawContextKey<boolean>('problems.filter.errors', true);
|
||||
export const ShowWarningsFilterContextKey = new RawContextKey<boolean>('problems.filter.warnings', true);
|
||||
export const ShowInfoFilterContextKey = new RawContextKey<boolean>('problems.filter.info', true);
|
||||
export const ShowActiveFileFilterContextKey = new RawContextKey<boolean>('problems.filter.activeFile', false);
|
||||
export const ShowExcludedFilesFilterContextKey = new RawContextKey<boolean>('problems.filter.excludedFiles', true);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue