未验证 提交 3e55989c 编写于 作者: C Connor Peet

testing: move test filter to action bar

上级 2c19f7fb
......@@ -9,11 +9,6 @@
flex-direction: column;
}
.test-explorer > .monaco-inputbox {
flex-shrink: 0;
margin: 4px 12px;
}
.test-explorer > .test-explorer-tree {
flex-grow: 1;
height: 0px;
......@@ -75,3 +70,25 @@
.monaco-editor .zone-widget.test-output-peek .zone-widget-container.peekview-widget .peekview-title .filename {
max-height: 29px;
}
/** -- filter */
.testing-filter-action-bar {
flex-shrink: 0;
margin: 4px 12px;
}
.testing-filter-action-item {
display: flex !important;
flex-grow: 1;
max-width: 400px;
align-items: center;
}
.testing-filter-action-bar .testing-filter-action-item {
max-width: none;
}
.testing-filter-action-item .testing-filter-wrapper {
flex-grow: 1;
}
......@@ -20,23 +20,25 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
import { Extensions as ViewContainerExtensions, IViewContainersRegistry, IViewsRegistry, ViewContainerLocation } from 'vs/workbench/common/views';
import { testingViewIcon } from 'vs/workbench/contrib/testing/browser/icons';
import { ITestExplorerFilterState, TestExplorerFilterState } from 'vs/workbench/contrib/testing/browser/testingExplorerFilter';
import { TestingExplorerView } from 'vs/workbench/contrib/testing/browser/testingExplorerView';
import { TestingOutputPeekController } from 'vs/workbench/contrib/testing/browser/testingOutputPeek';
import { TestingViewPaneContainer } from 'vs/workbench/contrib/testing/browser/testingViewPaneContainer';
import { Testing } from 'vs/workbench/contrib/testing/common/constants';
import { TestIdWithProvider } from 'vs/workbench/contrib/testing/common/testCollection';
import { IWorkspaceTestCollectionService, WorkspaceTestCollectionService } from 'vs/workbench/contrib/testing/common/workspaceTestCollectionService';
import { TestingContentProvider } from 'vs/workbench/contrib/testing/common/testingContentProvider';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
import { ITestResultService, TestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
import { TestService } from 'vs/workbench/contrib/testing/common/testServiceImpl';
import { IWorkspaceTestCollectionService, WorkspaceTestCollectionService } from 'vs/workbench/contrib/testing/common/workspaceTestCollectionService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import * as Action from './testExplorerActions';
import { ITestResultService, TestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
registerSingleton(ITestService, TestService);
registerSingleton(ITestResultService, TestResultService);
registerSingleton(ITestExplorerFilterState, TestExplorerFilterState);
registerSingleton(IWorkspaceTestCollectionService, WorkspaceTestCollectionService);
const viewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
......
......@@ -3,23 +3,40 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { addStandardDisposableListener, EventType } from 'vs/base/browser/dom';
import * as dom from 'vs/base/browser/dom';
import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { HistoryInputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { Widget } from 'vs/base/browser/ui/widget';
import { IAction } from 'vs/base/common/actions';
import { Delayer } from 'vs/base/common/async';
import { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { KeyCode } from 'vs/base/common/keyCodes';
import { localize } from 'vs/nls';
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
import { ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget';
import { ContextKeyEqualsExpr, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ViewContainerLocation } from 'vs/workbench/common/views';
import { Testing } from 'vs/workbench/contrib/testing/common/constants';
import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
export interface ITestExplorerFilterState {
_serviceBrand: undefined;
readonly onDidChange: Event<string>;
value: string;
}
export const ITestExplorerFilterState = createDecorator<ITestExplorerFilterState>('testingFilterState');
export class TestExplorerFilterState implements ITestExplorerFilterState {
declare _serviceBrand: undefined;
export class TestingFilterState {
private readonly changeEmitter = new Emitter<string>();
private _value = '';
public readonly onDidChange = this.changeEmitter.event;
......@@ -33,12 +50,10 @@ export class TestingFilterState {
this.changeEmitter.fire(v);
}
}
constructor(private _value = '') { }
}
export class TestingExplorerFilter extends Widget {
private readonly input: HistoryInputBox;
export class TestingExplorerFilter extends BaseActionViewItem {
private input!: HistoryInputBox;
private readonly history: StoredValue<string[]> = this.instantiationService.createInstance(StoredValue, {
key: 'testing.filterHistory',
scope: StorageScope.WORKSPACE,
......@@ -46,24 +61,33 @@ export class TestingExplorerFilter extends Widget {
});
constructor(
container: HTMLElement,
private readonly state: TestingFilterState,
@IContextViewService contextViewService: IContextViewService,
@IThemeService themeService: IThemeService,
action: IAction,
@ITestExplorerFilterState private readonly state: ITestExplorerFilterState,
@IContextViewService private readonly contextViewService: IContextViewService,
@IThemeService private readonly themeService: IThemeService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
) {
super();
super(null, action);
}
/**
* @override
*/
public render(container: HTMLElement) {
container.classList.add('testing-filter-action-item');
const updateDelayer = this._register(new Delayer<void>(400));
const wrapper = dom.$('.testing-filter-wrapper');
container.appendChild(wrapper);
const input = this.input = this._register(instantiationService.createInstance(ContextScopedHistoryInputBox, container, contextViewService, {
const input = this.input = this._register(this.instantiationService.createInstance(ContextScopedHistoryInputBox, wrapper, this.contextViewService, {
placeholder: localize('testExplorerFilter', "Filter (e.g. text, !exclude)"),
history: this.history.get([]),
}));
input.value = state.value;
this._register(attachInputBoxStyler(input, themeService));
input.value = this.state.value;
this._register(attachInputBoxStyler(input, this.themeService));
this._register(state.onDidChange(newValue => {
this._register(this.state.onDidChange(newValue => {
input.value = newValue;
}));
......@@ -72,7 +96,7 @@ export class TestingExplorerFilter extends Widget {
this.state.value = input.value;
})));
this._register(addStandardDisposableListener(input.inputElement, EventType.KEY_DOWN, e => {
this._register(dom.addStandardDisposableListener(input.inputElement, dom.EventType.KEY_DOWN, e => {
if (e.equals(KeyCode.Escape)) {
input.value = '';
e.stopPropagation();
......@@ -100,4 +124,28 @@ export class TestingExplorerFilter extends Widget {
this.history.delete();
}
}
/**
* @override
*/
public dispose() {
this.saveState();
super.dispose();
}
}
registerAction2(class extends Action2 {
constructor() {
super({
id: Testing.FilterActionId,
title: localize('filter', "Filter"),
menu: {
id: MenuId.ViewTitle,
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', Testing.ExplorerViewId), TestingContextKeys.explorerLocation.isEqualTo(ViewContainerLocation.Panel)),
group: 'navigation',
order: 1,
},
});
}
async run(): Promise<void> { }
});
......@@ -10,6 +10,7 @@ import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree';
import { ITreeEvent, ITreeFilter, ITreeNode, ITreeRenderer, ITreeSorter, TreeFilterResult, TreeVisibility } from 'vs/base/browser/ui/tree/tree';
import { Action, IAction, IActionViewItem } from 'vs/base/common/actions';
import { DeferredPromise } from 'vs/base/common/async';
import { Color, RGBA } from 'vs/base/common/color';
import { throttle } from 'vs/base/common/decorators';
......@@ -41,7 +42,7 @@ import { TestRunState } from 'vs/workbench/api/common/extHostTypes';
import { IResourceLabel, IResourceLabelOptions, IResourceLabelProps, ResourceLabels } from 'vs/workbench/browser/labels';
import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane';
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { IViewDescriptorService } from 'vs/workbench/common/views';
import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views';
import { ITestTreeElement, ITestTreeProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections';
import { HierarchicalByLocationProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByLocation';
import { HierarchicalByNameProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByName';
......@@ -50,7 +51,7 @@ import { StateByLocationProjection } from 'vs/workbench/contrib/testing/browser/
import { StateByNameProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections/stateByName';
import { StateElement } from 'vs/workbench/contrib/testing/browser/explorerProjections/stateNodes';
import { testingStatesToIcons } from 'vs/workbench/contrib/testing/browser/icons';
import { TestingExplorerFilter, TestingFilterState } from 'vs/workbench/contrib/testing/browser/testingExplorerFilter';
import { ITestExplorerFilterState, TestingExplorerFilter, TestExplorerFilterState } from 'vs/workbench/contrib/testing/browser/testingExplorerFilter';
import { TestingOutputPeekController } from 'vs/workbench/contrib/testing/browser/testingOutputPeek';
import { TestExplorerViewGrouping, TestExplorerViewMode, Testing } from 'vs/workbench/contrib/testing/common/constants';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
......@@ -64,11 +65,11 @@ import { DebugAction, RunAction } from './testExplorerActions';
export class TestingExplorerView extends ViewPane {
public viewModel!: TestingExplorerViewModel;
private readonly filterState = new TestingFilterState();
private filter!: TestingExplorerFilter;
private filterActionBar = this._register(new MutableDisposable());
private currentSubscription?: TestSubscriptionListener;
private container!: HTMLElement;
private finishDiscovery?: () => void;
private readonly location = TestingContextKeys.explorerLocation.bindTo(this.contextKeyService);;
constructor(
options: IViewletViewOptions,
......@@ -87,6 +88,7 @@ export class TestingExplorerView extends ViewPane {
) {
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
this._register(testService.onDidChangeProviders(() => this._onDidChangeViewWelcomeState.fire()));
this.location.set(viewDescriptorService.getViewLocationById(Testing.ExplorerViewId) ?? ViewContainerLocation.Sidebar);
}
/**
......@@ -103,14 +105,16 @@ export class TestingExplorerView extends ViewPane {
super.renderBody(container);
this.container = dom.append(container, dom.$('.test-explorer'));
this.filter = this.instantiationService.createInstance(TestingExplorerFilter, this.container, this.filterState);
this._register(this.filter);
if (this.location.get() === ViewContainerLocation.Sidebar) {
this.filterActionBar.value = this.createFilterActionBar();
}
const messagesContainer = dom.append(this.container, dom.$('.test-explorer-messages'));
this._register(this.instantiationService.createInstance(TestRunProgress, messagesContainer, this.getProgressLocation()));
const listContainer = dom.append(this.container, dom.$('.test-explorer-tree'));
this.viewModel = this.instantiationService.createInstance(TestingExplorerViewModel, listContainer, this.onDidChangeBodyVisibility, this.currentSubscription, this.filterState);
this.viewModel = this.instantiationService.createInstance(TestingExplorerViewModel, listContainer, this.onDidChangeBodyVisibility, this.currentSubscription);
this._register(this.viewModel);
this._register(this.onDidChangeBodyVisibility(visible => {
......@@ -125,12 +129,29 @@ export class TestingExplorerView extends ViewPane {
}));
}
/**
* @override
*/
public getActionViewItem(action: IAction): IActionViewItem | undefined {
if (action.id === Testing.FilterActionId) {
return this.instantiationService.createInstance(TestingExplorerFilter, action);
}
return super.getActionViewItem(action);
}
/**
* @override
*/
public saveState() {
super.saveState();
this.filter.saveState();
}
private createFilterActionBar() {
const bar = new ActionBar(this.container, { actionViewItemProvider: action => this.getActionViewItem(action) });
bar.push(new Action(Testing.FilterActionId));
bar.getContainer().classList.add('testing-filter-action-bar');
return bar;
}
private updateDiscoveryProgress(busy: number) {
......@@ -205,7 +226,7 @@ export class TestingExplorerViewModel extends Disposable {
listContainer: HTMLElement,
onDidChangeVisibility: Event<boolean>,
private listener: TestSubscriptionListener | undefined,
filterState: TestingFilterState,
@ITestExplorerFilterState filterState: TestExplorerFilterState,
@IInstantiationService instantiationService: IInstantiationService,
@IEditorService private readonly editorService: IEditorService,
@ICodeEditorService codeEditorService: ICodeEditorService,
......@@ -220,6 +241,7 @@ export class TestingExplorerViewModel extends Disposable {
const labels = this._register(instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: onDidChangeVisibility }));
this.filter = new TestsFilter(filterState.value);
this._register(filterState.onDidChange(text => {
this.filter.setFilter(text);
this.tree.refilter();
......
......@@ -11,6 +11,7 @@ export const enum Testing {
ViewletId = 'workbench.view.extension.test',
ExplorerViewId = 'workbench.view.testing',
OutputPeekContributionId = 'editor.contrib.testingOutputPeek',
FilterActionId = 'workbench.actions.treeView.testExplorer.filter',
}
export const enum TestExplorerViewMode {
......
......@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { ViewContainerLocation } from 'vs/workbench/common/views';
import { TestExplorerViewMode, TestExplorerViewGrouping } from 'vs/workbench/contrib/testing/common/constants';
export namespace TestingContextKeys {
......@@ -12,4 +13,5 @@ export namespace TestingContextKeys {
export const viewGrouping = new RawContextKey('testExplorerViewGrouping', TestExplorerViewGrouping.ByLocation);
export const isRunning = new RawContextKey('testIsrunning', false);
export const peekVisible = new RawContextKey('testPeekVisible', false);
export const explorerLocation = new RawContextKey('testExplorerLocation', ViewContainerLocation.Sidebar);
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册