testing: accessibility round 2

For #114653
This commit is contained in:
Connor Peet 2021-01-25 16:31:12 -08:00
parent 500d514ecf
commit 88af66bcee
No known key found for this signature in database
GPG key ID: CF8FD2EA0DBC61BD
6 changed files with 86 additions and 41 deletions

View file

@ -37,7 +37,7 @@ export class TestLocationStore<T extends { location?: ModeLocation, depth: numbe
const range = test.location?.range;
return range
&& new Position(range.startLineNumber, range.startColumn).isBeforeOrEqual(position)
&& position.isBefore(new Position(
&& position.isBeforeOrEqual(new Position(
range.endLineNumber ?? range.startLineNumber,
range.endColumn ?? range.startColumn,
));

View file

@ -3,10 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Codicon } from 'vs/base/common/codicons';
import { KeyCode } from 'vs/base/common/keyCodes';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction2, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { localize } from 'vs/nls';
import { registerAction2 } from 'vs/platform/actions/common/actions';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
@ -14,7 +11,6 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
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';
@ -22,7 +18,7 @@ import { testingViewIcon } from 'vs/workbench/contrib/testing/browser/icons';
import { TestingDecorations } from 'vs/workbench/contrib/testing/browser/testingDecorations';
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 { CloseTestPeek, 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';
@ -95,6 +91,7 @@ registerAction2(Action.CollapseAllAction);
registerAction2(Action.RunAllAction);
registerAction2(Action.DebugAllAction);
registerAction2(Action.EditFocusedTest);
registerAction2(CloseTestPeek);
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(TestingContentProvider, LifecyclePhase.Eventually);
@ -124,25 +121,3 @@ CommandsRegistry.registerCommand({
await new Action.ShowTestView().run(accessor);
}
});
registerAction2(class CloseTestPeek extends EditorAction2 {
constructor() {
super({
id: 'editor.closeTestPeek',
title: localize('close', 'Close'),
icon: Codicon.close,
precondition: ContextKeyExpr.and(
TestingContextKeys.peekVisible,
ContextKeyExpr.not('config.editor.stablePeek')
),
keybinding: {
weight: KeybindingWeight.WorkbenchContrib + 10,
primary: KeyCode.Escape
}
});
}
runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor): void {
TestingOutputPeekController.get(editor).removePeek();
}
});

View file

@ -30,6 +30,9 @@ export interface ITestExplorerFilterState {
readonly onDidRequestReveal: Event<string[]>;
value: string;
reveal: string[] | undefined;
readonly onDidRequestInputFocus: Event<void>;
focusInput(): void;
}
export const ITestExplorerFilterState = createDecorator<ITestExplorerFilterState>('testingFilterState');
@ -38,9 +41,11 @@ export class TestExplorerFilterState implements ITestExplorerFilterState {
declare _serviceBrand: undefined;
private readonly revealRequest = new Emitter<string[]>();
private readonly changeEmitter = new Emitter<string>();
private readonly focusEmitter = new Emitter<void>();
private _value = '';
private _reveal?: string[];
public readonly onDidRequestInputFocus = this.focusEmitter.event;
public readonly onDidRequestReveal = this.revealRequest.event;
public readonly onDidChange = this.changeEmitter.event;
@ -65,6 +70,10 @@ export class TestExplorerFilterState implements ITestExplorerFilterState {
this.changeEmitter.fire(v);
}
}
public focusInput() {
this.focusEmitter.fire();
}
}
export class TestingExplorerFilter extends BaseActionViewItem {
@ -106,6 +115,10 @@ export class TestingExplorerFilter extends BaseActionViewItem {
input.value = newValue;
}));
this._register(this.state.onDidRequestInputFocus(() => {
input.focus();
}));
this._register(input.onDidChange(() => updateDelayer.trigger(() => {
input.addToHistory();
this.state.value = input.value;

View file

@ -6,7 +6,7 @@
import * as dom from 'vs/base/browser/dom';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { DefaultKeyboardNavigationDelegate, 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';
@ -268,6 +268,13 @@ export class TestingExplorerViewModel extends Disposable {
}) as WorkbenchObjectTree<ITestTreeElement, FuzzyScore>;
this._register(this.tree);
this._register(dom.addStandardDisposableListener(this.tree.getHTMLElement(), 'keydown', evt => {
if (DefaultKeyboardNavigationDelegate.mightProducePrintableCharacter(evt)) {
filterState.value = evt.browserEvent.key;
filterState.focusInput();
}
}));
this.updatePreferredProjection();
this.onDidChangeSelection = this.tree.onDidChangeSelection;
@ -356,7 +363,10 @@ export class TestingExplorerViewModel extends Disposable {
const pane = await this.editorService.openEditor({
resource: location.uri,
options: { selection: location.range, preserveFocus },
options: {
selection: { startColumn: location.range.startColumn, startLineNumber: location.range.startLineNumber },
preserveFocus,
},
});
// if the user selected a failed test and now they didn't, hide the peek

View file

@ -4,19 +4,25 @@
*--------------------------------------------------------------------------------------------*/
import * as dom from 'vs/base/browser/dom';
import { Codicon } from 'vs/base/common/codicons';
import { Color } from 'vs/base/common/color';
import { KeyCode } from 'vs/base/common/keyCodes';
import { Disposable, IReference, MutableDisposable } from 'vs/base/common/lifecycle';
import { clamp } from 'vs/base/common/numbers';
import { count } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction2 } from 'vs/editor/browser/editorExtensions';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget';
import { IDiffEditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService';
import { IPeekViewService, peekViewTitleBackground, peekViewTitleForeground, peekViewTitleInfoForeground, PeekViewWidget } from 'vs/editor/contrib/peekView/peekView';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { getOuterEditor, IPeekViewService, peekViewTitleBackground, peekViewTitleForeground, peekViewTitleInfoForeground, PeekViewWidget } from 'vs/editor/contrib/peekView/peekView';
import { localize } from 'vs/nls';
import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService';
import { EditorModel } from 'vs/workbench/common/editor';
import { testingPeekBorder } from 'vs/workbench/contrib/testing/browser/theme';
@ -35,6 +41,9 @@ interface ITestDto {
messageUri: URI;
}
/**
* Adds output/message peek functionality to code editors.
*/
export class TestingOutputPeekController extends Disposable implements IEditorContribution {
/**
* Gets the controller associated with the given code editor.
@ -61,7 +70,7 @@ export class TestingOutputPeekController extends Disposable implements IEditorCo
@IContextKeyService contextKeyService: IContextKeyService,
) {
super();
this.visible = TestingContextKeys.peekVisible.bindTo(contextKeyService);
this.visible = TestingContextKeys.isPeekVisible.bindTo(contextKeyService);
this._register(editor.onDidChangeModel(() => this.peek.clear()));
}
@ -145,11 +154,13 @@ abstract class TestingOutputPeek extends PeekViewWidget {
editor: ICodeEditor,
@IThemeService themeService: IThemeService,
@IPeekViewService peekViewService: IPeekViewService,
@IContextKeyService contextKeyService: IContextKeyService,
@IInstantiationService protected readonly instantiationService: IInstantiationService,
@ITextModelService protected readonly modelService: ITextModelService,
) {
super(editor, { showFrame: false, showArrow: true, isResizeable: true, isAccessible: true, className: 'test-output-peek' }, instantiationService);
TestingContextKeys.isInPeek.bindTo(contextKeyService);
this._disposables.add(themeService.onDidColorThemeChange(this.applyTheme, this));
this._disposables.add(this.model);
this.applyTheme(themeService.getColorTheme());
@ -326,3 +337,38 @@ class SimpleDiffEditorModel extends EditorModel {
this._modified.dispose();
}
}
function getOuterEditorFromDiffEditor(accessor: ServicesAccessor): ICodeEditor | null {
const diffEditors = accessor.get(ICodeEditorService).listDiffEditors();
for (const diffEditor of diffEditors) {
if (diffEditor.hasTextFocus() && diffEditor instanceof EmbeddedDiffEditorWidget) {
return diffEditor.getParentEditor();
}
}
return getOuterEditor(accessor);
}
export class CloseTestPeek extends EditorAction2 {
constructor() {
super({
id: 'editor.closeTestPeek',
title: localize('close', 'Close'),
icon: Codicon.close,
precondition: ContextKeyExpr.and(
ContextKeyExpr.or(TestingContextKeys.isInPeek, TestingContextKeys.isPeekVisible),
ContextKeyExpr.not('config.editor.stablePeek')
),
keybinding: {
weight: KeybindingWeight.EditorContrib - 101,
primary: KeyCode.Escape
}
});
}
runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor): void {
const parent = getOuterEditorFromDiffEditor(accessor);
TestingOutputPeekController.get(parent ?? editor).removePeek();
}
}

View file

@ -8,10 +8,11 @@ import { ViewContainerLocation } from 'vs/workbench/common/views';
import { TestExplorerViewMode, TestExplorerViewGrouping } from 'vs/workbench/contrib/testing/common/constants';
export namespace TestingContextKeys {
export const providerCount = new RawContextKey('testingProviderCount', 0);
export const viewMode = new RawContextKey('testExplorerViewMode', TestExplorerViewMode.List);
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);
export const providerCount = new RawContextKey('testing.providerCount', 0);
export const viewMode = new RawContextKey('testing.explorerViewMode', TestExplorerViewMode.List);
export const viewGrouping = new RawContextKey('testing.explorerViewGrouping', TestExplorerViewGrouping.ByLocation);
export const isRunning = new RawContextKey('testing.isRunning', false);
export const isInPeek = new RawContextKey('testing.isInPeek', true);
export const isPeekVisible = new RawContextKey('testing.isPeekVisible', false);
export const explorerLocation = new RawContextKey('testing.explorerLocation', ViewContainerLocation.Sidebar);
}