diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index ed15677163a..1766a450421 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -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(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); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts b/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts new file mode 100644 index 00000000000..0bd04764dd6 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts @@ -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 { + 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'); +} diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 8a9adbe0ad3..ba9403015a5 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -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);