Merge pull request #186229 from microsoft/aamunger/accessibility

notebook accessibility help
This commit is contained in:
Aaron Munger
2023-06-26 15:51:33 -07:00
committed by GitHub
3 changed files with 76 additions and 27 deletions
@@ -112,6 +112,9 @@ import { NotebookKernelHistoryService } from 'vs/workbench/contrib/notebook/brow
import { INotebookLoggingService } from 'vs/workbench/contrib/notebook/common/notebookLoggingService';
import { NotebookLoggingService } from 'vs/workbench/contrib/notebook/browser/services/notebookLoggingServiceImpl';
import product from 'vs/platform/product/common/product';
import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution';
import { NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
import { runAccessibilityHelpAction } from 'vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp';
/*--------------------------------------------------------------------------------------------- */
@@ -672,6 +675,20 @@ class NotebookLanguageSelectorScoreRefine {
}
}
class NotebookAccessibilityHelpContribution extends Disposable {
static ID: 'chatAccessibilityHelpContribution';
constructor() {
super();
this._register(AccessibilityHelpAction.addImplementation(105, 'notebook', async accessor => {
const codeEditor = accessor.get(ICodeEditorService).getActiveCodeEditor() || accessor.get(ICodeEditorService).getFocusedCodeEditor();
if (!codeEditor) {
return;
}
runAccessibilityHelpAction(accessor, codeEditor);
}, NOTEBOOK_IS_ACTIVE_EDITOR));
}
}
const workbenchContributionsRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchContributionsRegistry.registerWorkbenchContribution(NotebookContribution, LifecyclePhase.Starting);
workbenchContributionsRegistry.registerWorkbenchContribution(CellContentProvider, LifecyclePhase.Starting);
@@ -680,6 +697,7 @@ workbenchContributionsRegistry.registerWorkbenchContribution(RegisterSchemasCont
workbenchContributionsRegistry.registerWorkbenchContribution(NotebookEditorManager, LifecyclePhase.Ready);
workbenchContributionsRegistry.registerWorkbenchContribution(NotebookLanguageSelectorScoreRefine, LifecyclePhase.Ready);
workbenchContributionsRegistry.registerWorkbenchContribution(SimpleNotebookWorkingCopyEditorHandler, LifecyclePhase.Ready);
workbenchContributionsRegistry.registerWorkbenchContribution(NotebookAccessibilityHelpContribution, LifecyclePhase.Eventually);
registerSingleton(INotebookService, NotebookService, InstantiationType.Delayed);
registerSingleton(INotebookEditorWorkerService, NotebookEditorWorkerServiceImpl, InstantiationType.Delayed);
@@ -0,0 +1,58 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { format } from 'vs/base/common/strings';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
export function getAccessibilityHelpText(accessor: ServicesAccessor): string {
const keybindingService = accessor.get(IKeybindingService);
const content = [];
content.push(localize('notebook.overview', 'The notebook view is a collection of code and markdown cells. Code cells can be executed and will produce output directly below the cell.'));
content.push(descriptionForCommand('notebook.cell.edit',
localize('notebook.cell.edit', 'The Edit Cell command ({0}) will focus on the cell input.'),
localize('notebook.cell.editNoKb', 'The Edit Cell command will focus on the cell input and is currently not triggerable by a keybinding.'), keybindingService));
content.push(descriptionForCommand('notebook.cell.quitEdit',
localize('notebook.cell.quitEdit', 'The Quit Edit command ({0}) will set focus on the cell container. The default (Escape) key may need to be pressed twice first exit the virtual cursor if active.'),
localize('notebook.cell.quitEditNoKb', 'The Quit Edit command will set focus on the cell container and is currently not triggerable by a keybinding.'), keybindingService));
content.push(descriptionForCommand('notebook.cell.focusInOutput',
localize('notebook.cell.focusInOutput', 'The Focus Output command ({0}) will set focus in the cell\'s output.'),
localize('notebook.cell.focusInOutputNoKb', 'The Quit Edit command will set focus in the cell\'s output and is currently not triggerable by a keybinding.'), keybindingService));
content.push(localize('notebook.cellNavigation', 'The up and down arrows will move focus between cells while focused on the outer cell container'));
content.push(descriptionForCommand('notebook.cell.executeAndFocusContainer',
localize('notebook.cell.executeAndFocusContainer', 'The Execute Cell command ({0}) executes the cell that currently has focus.',),
localize('notebook.cell.executeAndFocusContainerNoKb', 'The Execute Cell command executes the cell that currently has focus and is currently not triggerable by a keybinding.'), keybindingService));
content.push(localize('notebook.cell.insertCodeCellBelowAndFocusContainer', 'The Insert Cell Above/Below commands will create new empty code cells'));
content.push(localize('notebook.changeCellType', 'The Change Cell to Code/Markdown commands are used to switch between cell types.'));
return content.join('\n');
}
function descriptionForCommand(commandId: string, msg: string, noKbMsg: string, keybindingService: IKeybindingService): string {
const kb = keybindingService.lookupKeybinding(commandId);
if (kb) {
return format(msg, kb.getAriaLabel());
}
return format(noKbMsg, commandId);
}
export async function runAccessibilityHelpAction(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
const accessibleViewService = accessor.get(IAccessibleViewService);
const helpText = getAccessibilityHelpText(accessor);
const provider = accessibleViewService.registerProvider({
id: 'notebook',
provideContent: () => helpText,
onClose: () => {
editor.focus();
provider.dispose();
},
options: { type: AccessibleViewType.HelpMenu, ariaLabel: 'Notebook accessibility help' }
});
accessibleViewService.show('notebook');
}
@@ -15,7 +15,6 @@ import 'vs/css!./media/notebookCellOutput';
import { PixelRatio } from 'vs/base/browser/browser';
import * as DOM from 'vs/base/browser/dom';
import { IMouseWheelEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import * as aria from 'vs/base/browser/ui/aria/aria';
import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list';
import { DeferredPromise, runWhenIdle, SequencerByKey } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
@@ -91,7 +90,6 @@ import { INotebookLoggingService } from 'vs/workbench/contrib/notebook/common/no
import { Schemas } from 'vs/base/common/network';
import { DropIntoEditorController } from 'vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorController';
import { CopyPasteController } from 'vs/editor/contrib/dropOrPasteInto/browser/copyPasteController';
import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution';
const $ = DOM.$;
@@ -2239,28 +2237,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
return undefined;
}
private _cellFocusAria(cell: ICellViewModel, focusItem: 'editor' | 'container' | 'output') {
const index = this._notebookViewModel?.getCellIndex(cell);
const verboseLabel = this.configurationService.getValue(AccessibilityVerbositySettingId.Notebook);
if (index !== undefined && index >= 0) {
let position = '';
switch (focusItem) {
case 'editor':
position = `the inner ${cell.cellKind === CellKind.Markup ? 'markdown' : 'code'} editor is focused` + (verboseLabel ? `, press escape to focus the cell container` : '');
break;
case 'output':
position = `the cell output is focused` + (verboseLabel ? `, press escape to focus the cell container` : '');
break;
case 'container':
position = `the ${cell.cellKind === CellKind.Markup ? 'markdown preview' : 'cell container'} is focused` + (verboseLabel ? `, press enter to focus the inner ${cell.cellKind === CellKind.Markup ? 'markdown' : 'code'} editor` : '');
break;
default:
break;
}
aria.alert(`Cell ${this._notebookViewModel?.getCellIndex(cell)}, ${position} `);
}
}
private _toggleNotebookCellSelection(selectedCell: ICellViewModel, selectFromPrevious: boolean): void {
const currentSelections = this._list.getSelectedElements();
const isSelected = currentSelections.includes(selectedCell);
@@ -2300,7 +2276,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
if (focusItem === 'editor') {
this.focusElement(cell);
this._cellFocusAria(cell, focusItem);
this._list.focusView();
cell.updateEditState(CellEditState.Editing, 'focusNotebookCell');
@@ -2330,7 +2305,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
}
} else if (focusItem === 'output') {
this.focusElement(cell);
this._cellFocusAria(cell, focusItem);
if (!this.hasEditorFocus()) {
this._list.focusView();
@@ -2359,7 +2333,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
cell.focusMode = CellFocusMode.Container;
this.focusElement(cell);
this._cellFocusAria(cell, focusItem);
if (!options?.skipReveal) {
if (typeof options?.focusEditorLine === 'number') {
this._cursorNavMode.set(true);