From cc6ee0efd54189b8cc64667124ecb1a87f012555 Mon Sep 17 00:00:00 2001 From: aamunger Date: Wed, 19 Jul 2023 11:23:24 -0700 Subject: [PATCH 1/6] put cell output content into a11y view --- .../contributedStatusBarItemController.ts | 2 +- .../executionStatusBarItemController.ts | 2 +- .../browser/contrib/find/findModel.ts | 4 +- .../contrib/find/notebookFindWidget.ts | 4 +- .../browser/contrib/navigation/arrow.ts | 2 +- .../browser/contrib/troubleshoot/layout.ts | 2 +- .../contrib/undoRedo/notebookUndoRedo.ts | 4 +- .../browser/controller/cellOperations.ts | 4 +- .../browser/controller/foldingController.ts | 8 +-- .../notebook/browser/notebook.contribution.ts | 71 ++++++++++++++++++- .../notebook/browser/notebookBrowser.ts | 6 +- .../notebook/browser/notebookEditorWidget.ts | 2 +- .../browser/view/cellParts/foldedCellHint.ts | 4 +- .../viewParts/notebookOverviewRuler.ts | 2 +- .../test/browser/testNotebookEditor.ts | 2 +- 15 files changed, 93 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/contributedStatusBarItemController.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/contributedStatusBarItemController.ts index 33ec1598507..de1b477cf67 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/contributedStatusBarItemController.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/contributedStatusBarItemController.ts @@ -47,7 +47,7 @@ export class ContributedStatusBarItemController extends Disposable implements IN added: ICellViewModel[]; removed: { handle: number }[]; }): void { - const vm = this._notebookEditor._getViewModel(); + const vm = this._notebookEditor.getViewModel(); if (!vm) { return; } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.ts index 3aa72fc3bfb..34a10466dd1 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.ts @@ -58,7 +58,7 @@ export class NotebookStatusBarController extends Disposable { } private _updateVisibleCells(e: ICellVisibilityChangeEvent): void { - const vm = this._notebookEditor._getViewModel(); + const vm = this._notebookEditor.getViewModel(); if (!vm) { return; } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts index 5c58ffda1bc..da74cbb1a76 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts @@ -125,7 +125,7 @@ export class FindModel extends Disposable { // we only update cell state if users are using the hybrid mode (both input and preview are enabled) const updateEditingState = () => { - const viewModel = this._notebookEditor._getViewModel() as NotebookViewModel | undefined; + const viewModel = this._notebookEditor.getViewModel() as NotebookViewModel | undefined; if (!viewModel) { return; } @@ -164,7 +164,7 @@ export class FindModel extends Disposable { if (e.isReplaceRevealed && !this._state.isReplaceRevealed) { // replace is hidden, we need to switch all markdown cells to preview mode - const viewModel = this._notebookEditor._getViewModel() as NotebookViewModel | undefined; + const viewModel = this._notebookEditor.getViewModel() as NotebookViewModel | undefined; if (!viewModel) { return; } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts index a5536043a40..e0c09afffbc 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts @@ -187,7 +187,7 @@ class NotebookFindWidget extends SimpleFindReplaceWidget implements INotebookEdi const replacePattern = this.replacePattern; const replaceString = replacePattern.buildReplaceString(match.matches, this._state.preserveCase); - const viewModel = this._notebookEditor._getViewModel(); + const viewModel = this._notebookEditor.getViewModel(); viewModel.replaceOne(cell, match.range, replaceString).then(() => { this._progressBar.stop(); }); @@ -215,7 +215,7 @@ class NotebookFindWidget extends SimpleFindReplaceWidget implements INotebookEdi }); }); - const viewModel = this._notebookEditor._getViewModel(); + const viewModel = this._notebookEditor.getViewModel(); viewModel.replaceAll(this._findModel.findMatches, replaceStrings).then(() => { this._progressBar.stop(); }); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts b/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts index facce42cfcf..6b28f787ee4 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts @@ -431,7 +431,7 @@ registerAction2(class extends NotebookCellAction { function getPageSize(context: INotebookCellActionContext) { const editor = context.notebookEditor; - const layoutInfo = editor._getViewModel().layoutInfo; + const layoutInfo = editor.getViewModel().layoutInfo; const lineHeight = layoutInfo?.fontInfo.lineHeight || 17; return Math.max(1, Math.floor((layoutInfo?.height || 0) / lineHeight) - 2); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts b/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts index ce2b9849c2a..6404536b1df 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts @@ -82,7 +82,7 @@ export class TroubleshootController extends Disposable implements INotebookEdito }); })); - const vm = this._notebookEditor._getViewModel(); + const vm = this._notebookEditor.getViewModel(); let items: INotebookDeltaCellStatusBarItems[] = []; if (this._enabled) { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/notebookUndoRedo.ts b/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/notebookUndoRedo.ts index 22678c6af01..4a74f442f94 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/notebookUndoRedo.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/notebookUndoRedo.ts @@ -21,7 +21,7 @@ class NotebookUndoRedoContribution extends Disposable { const PRIORITY = 105; this._register(UndoCommand.addImplementation(PRIORITY, 'notebook-undo-redo', () => { const editor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); - const viewModel = editor?._getViewModel() as NotebookViewModel | undefined; + const viewModel = editor?.getViewModel() as NotebookViewModel | undefined; if (editor && editor.hasModel() && viewModel) { return viewModel.undo().then(cellResources => { if (cellResources?.length) { @@ -42,7 +42,7 @@ class NotebookUndoRedoContribution extends Disposable { this._register(RedoCommand.addImplementation(PRIORITY, 'notebook-undo-redo', () => { const editor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); - const viewModel = editor?._getViewModel() as NotebookViewModel | undefined; + const viewModel = editor?.getViewModel() as NotebookViewModel | undefined; if (editor && editor.hasModel() && viewModel) { return viewModel.redo().then(cellResources => { diff --git a/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts b/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts index bc8ea045516..651e9a29837 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts @@ -498,7 +498,7 @@ export async function joinNotebookCells(editor: IActiveNotebookEditor, range: IC export async function joinCellsWithSurrounds(bulkEditService: IBulkEditService, context: INotebookCellActionContext, direction: 'above' | 'below'): Promise { const editor = context.notebookEditor; const textModel = editor.textModel; - const viewModel = editor._getViewModel() as NotebookViewModel; + const viewModel = editor.getViewModel() as NotebookViewModel; let ret: { edits: ResourceEdit[]; cell: ICellViewModel; @@ -656,7 +656,7 @@ export function insertCell( initialText: string = '', ui: boolean = false ) { - const viewModel = editor._getViewModel() as NotebookViewModel; + const viewModel = editor.getViewModel() as NotebookViewModel; const activeKernel = editor.activeKernel; if (viewModel.options.isReadOnly) { return null; diff --git a/src/vs/workbench/contrib/notebook/browser/controller/foldingController.ts b/src/vs/workbench/contrib/notebook/browser/controller/foldingController.ts index 0af3a8546ce..349d4e2ea9e 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/foldingController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/foldingController.ts @@ -49,7 +49,7 @@ export class FoldingController extends Disposable implements INotebookEditorCont this._foldingModel = new FoldingModel(); this._localStore.add(this._foldingModel); - this._foldingModel.attachViewModel(this._notebookEditor._getViewModel()); + this._foldingModel.attachViewModel(this._notebookEditor.getViewModel()); this._localStore.add(this._foldingModel.onDidFoldingRegionChanged(() => { this._updateEditorFoldingRanges(); @@ -103,7 +103,7 @@ export class FoldingController extends Disposable implements INotebookEditorCont return; } - const vm = this._notebookEditor._getViewModel() as NotebookViewModel; + const vm = this._notebookEditor.getViewModel() as NotebookViewModel; vm.updateFoldingRanges(this._foldingModel.regions); const hiddenRanges = vm.getHiddenRanges(); @@ -119,7 +119,7 @@ export class FoldingController extends Disposable implements INotebookEditorCont return; } - const viewModel = this._notebookEditor._getViewModel() as NotebookViewModel; + const viewModel = this._notebookEditor.getViewModel() as NotebookViewModel; const target = e.event.target as HTMLElement; if (target.classList.contains('codicon-notebook-collapsed') || target.classList.contains('codicon-notebook-expanded')) { @@ -243,7 +243,7 @@ registerAction2(class extends Action2 { controller.setFoldingStateDown(index, CellFoldingState.Collapsed, levels); } - const viewIndex = editor._getViewModel().getNearestVisibleCellIndexUpwards(index); + const viewIndex = editor.getViewModel().getNearestVisibleCellIndexUpwards(index); editor.focusElement(editor.cellAt(viewIndex)); } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 72cb92908d6..c74f399bff9 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -57,6 +57,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { NotebookRendererMessagingService } from 'vs/workbench/contrib/notebook/browser/services/notebookRendererMessagingServiceImpl'; import { INotebookRendererMessagingService } from 'vs/workbench/contrib/notebook/common/notebookRendererMessagingService'; +import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; // Editor Controller import 'vs/workbench/contrib/notebook/browser/controller/coreActions'; @@ -112,9 +113,10 @@ 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 { AccessibilityHelpAction, AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; +import { NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { runAccessibilityHelpAction } from 'vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp'; +import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; /*--------------------------------------------------------------------------------------------- */ @@ -689,6 +691,70 @@ class NotebookAccessibilityHelpContribution extends Disposable { } } +class NotebookAccessibleViewContribution extends Disposable { + static ID: 'chatAccessibleViewContribution'; + constructor() { + super(); + this._register(AccessibleViewAction.addImplementation(100, 'notebook', accessor => { + const accessibleViewService = accessor.get(IAccessibleViewService); + + const activePane = accessor.get(IEditorService).activeEditorPane; + const notebookEditor = getNotebookEditorFromEditorPane(activePane); + const notebookViewModel = notebookEditor?.getViewModel(); + const selections = notebookViewModel?.getSelections(); + notebookViewModel?.getCellIndex; + const notebookDocument = notebookViewModel?.notebookDocument; + + if (!selections || !notebookDocument || !notebookEditor?.textModel) { + return false; + } + + const viewCell = notebookViewModel.viewCells[selections[0].start]; + let outputContent = ''; + const decoder = new TextDecoder(); + for (let i = 0; i < viewCell.outputsViewModels.length; i++) { + const outputViewModel = viewCell.outputsViewModels[i]; + const outputTextModel = viewCell.model.outputs[i]; + const [mimeTypes, pick] = outputViewModel.resolveMimeTypes(notebookEditor.textModel, undefined); + const mimeType = mimeTypes[pick].mimeType; + const pickedBuffer = outputTextModel.outputs.find(output => output.mime === mimeType)?.data.buffer; + + let text = `${mimeType}\n`; + if (!pickedBuffer || mimeType.startsWith('image')) { + const altBuffer = outputTextModel.outputs.find(output => !output.mime.startsWith('image'))?.data.buffer; + if (altBuffer) { + text = decoder.decode(altBuffer); + } + } else { + text = decoder.decode(pickedBuffer); + } + + outputContent = outputContent.concat(`${text}\n`); + } + + if (!outputContent) { + return false; + } + + accessibleViewService.show({ + verbositySettingKey: 'notebook', + provideContent(): string { return outputContent; }, + onClose() { + notebookEditor?.setFocus(selections[0]); + activePane?.focus(); + }, + options: { + ariaLabel: nls.localize('NotebookCellOutputAccessibleView', "Notebook Cell Output Accessible View"), + language: 'plaintext', + type: AccessibleViewType.View + } + }); + return true; + }, NOTEBOOK_OUTPUT_FOCUSED)); + } +} + + const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(NotebookContribution, LifecyclePhase.Starting); workbenchContributionsRegistry.registerWorkbenchContribution(CellContentProvider, LifecyclePhase.Starting); @@ -698,6 +764,7 @@ workbenchContributionsRegistry.registerWorkbenchContribution(NotebookEditorManag workbenchContributionsRegistry.registerWorkbenchContribution(NotebookLanguageSelectorScoreRefine, LifecyclePhase.Ready); workbenchContributionsRegistry.registerWorkbenchContribution(SimpleNotebookWorkingCopyEditorHandler, LifecyclePhase.Ready); workbenchContributionsRegistry.registerWorkbenchContribution(NotebookAccessibilityHelpContribution, LifecyclePhase.Eventually); +workbenchContributionsRegistry.registerWorkbenchContribution(NotebookAccessibleViewContribution, LifecyclePhase.Eventually); registerSingleton(INotebookService, NotebookService, InstantiationType.Delayed); registerSingleton(INotebookEditorWorkerService, NotebookEditorWorkerServiceImpl, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 5166e317991..fa9048616fc 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -476,7 +476,7 @@ export interface INotebookEditor { setFocus(focus: ICellRange): void; getId(): string; - _getViewModel(): INotebookViewModel | undefined; + getViewModel(): INotebookViewModel | undefined; hasModel(): this is IActiveNotebookEditor; dispose(): void; getDomNode(): HTMLElement; @@ -684,7 +684,7 @@ export interface INotebookEditor { } export interface IActiveNotebookEditor extends INotebookEditor { - _getViewModel(): INotebookViewModel; + getViewModel(): INotebookViewModel; textModel: NotebookTextModel; getFocus(): ICellRange; cellAt(index: number): ICellViewModel; @@ -730,7 +730,7 @@ export interface INotebookEditorDelegate extends INotebookEditor { } export interface IActiveNotebookEditorDelegate extends INotebookEditorDelegate { - _getViewModel(): INotebookViewModel; + getViewModel(): INotebookViewModel; textModel: NotebookTextModel; getFocus(): ICellRange; cellAt(index: number): ICellViewModel; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 71f9504ed23..bf2276f2bf9 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -442,7 +442,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return this._uuid; } - _getViewModel(): NotebookViewModel | undefined { + getViewModel(): NotebookViewModel | undefined { return this.viewModel; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/foldedCellHint.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/foldedCellHint.ts index d5dff6359c7..2fe72e05af8 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/foldedCellHint.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/foldedCellHint.ts @@ -33,8 +33,8 @@ export class FoldedCellHint extends CellContentPart { if (element.isInputCollapsed || element.getEditState() === CellEditState.Editing) { DOM.hide(this._container); } else if (element.foldingState === CellFoldingState.Collapsed) { - const idx = this._notebookEditor._getViewModel().getCellIndex(element); - const length = this._notebookEditor._getViewModel().getFoldedLength(idx); + const idx = this._notebookEditor.getViewModel().getCellIndex(element); + const length = this._notebookEditor.getViewModel().getFoldedLength(idx); DOM.reset(this._container, this.getHiddenCellsLabel(length), this.getHiddenCellHintButton(element)); DOM.show(this._container); diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookOverviewRuler.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookOverviewRuler.ts index 498635a2fc6..ce31818ac27 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookOverviewRuler.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookOverviewRuler.ts @@ -46,7 +46,7 @@ export class NotebookOverviewRuler extends Themable { } private _render(ctx: CanvasRenderingContext2D, width: number, height: number, scrollHeight: number, ratio: number) { - const viewModel = this.notebookEditor._getViewModel(); + const viewModel = this.notebookEditor.getViewModel(); const fontInfo = this.notebookEditor.getLayoutInfo().fontInfo; const laneWidth = width / this._lanes; diff --git a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts index fd3cf5fdcb9..2c3ffb72e73 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts @@ -221,7 +221,7 @@ function _createTestNotebookEditor(instantiationService: TestInstantiationServic override notebookOptions = notebookOptions; override onDidChangeModel: Event = new Emitter().event; override onDidChangeCellState: Event = new Emitter().event; - override _getViewModel(): NotebookViewModel { + override getViewModel(): NotebookViewModel { return viewModel; } override textModel = viewModel.notebookDocument; From eb7ed2b2aa8e6ba90ce655f6903b563ac7a2101d Mon Sep 17 00:00:00 2001 From: aamunger Date: Wed, 19 Jul 2023 11:41:51 -0700 Subject: [PATCH 2/6] label output indexes if more than one --- .../contrib/notebook/browser/notebook.contribution.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index c74f399bff9..e65e41fcfb8 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -728,8 +728,10 @@ class NotebookAccessibleViewContribution extends Disposable { } else { text = decoder.decode(pickedBuffer); } - - outputContent = outputContent.concat(`${text}\n`); + const index = viewCell.outputsViewModels.length > 1 + ? `Cell output ${i + 1} of ${viewCell.outputsViewModels.length}\n` + : ''; + outputContent = outputContent.concat(`${index}${text}\n`); } if (!outputContent) { From 82b4f2bf4999a3627dbce7a119aabf656e6bab6c Mon Sep 17 00:00:00 2001 From: aamunger Date: Thu, 20 Jul 2023 09:14:37 -0700 Subject: [PATCH 3/6] make error output legible --- .../contrib/notebook/browser/notebook.contribution.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index e65e41fcfb8..93541c74a44 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -731,6 +731,9 @@ class NotebookAccessibleViewContribution extends Disposable { const index = viewCell.outputsViewModels.length > 1 ? `Cell output ${i + 1} of ${viewCell.outputsViewModels.length}\n` : ''; + if (mimeType.endsWith('error')) { + text = text.replace(/\\u001b\[[0-9;]*m/gi, '').replace('\\n', '\\\n'); + } outputContent = outputContent.concat(`${index}${text}\n`); } From c3261e574910a0e0de0cbd6ec5f11e8246c41f42 Mon Sep 17 00:00:00 2001 From: aamunger Date: Thu, 20 Jul 2023 09:27:59 -0700 Subject: [PATCH 4/6] fix newline replace --- .../workbench/contrib/notebook/browser/notebook.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 93541c74a44..97040a2d4c1 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -732,7 +732,7 @@ class NotebookAccessibleViewContribution extends Disposable { ? `Cell output ${i + 1} of ${viewCell.outputsViewModels.length}\n` : ''; if (mimeType.endsWith('error')) { - text = text.replace(/\\u001b\[[0-9;]*m/gi, '').replace('\\n', '\\\n'); + text = text.replace(/\\u001b\[[0-9;]*m/gi, '').replaceAll('\\n', '\n'); } outputContent = outputContent.concat(`${index}${text}\n`); } From db6c9a92559b868baf834c404fd25c1c7fbf84e1 Mon Sep 17 00:00:00 2001 From: aamunger Date: Thu, 20 Jul 2023 10:27:54 -0700 Subject: [PATCH 5/6] limit the amount of data --- .../notebook/browser/notebook.contribution.ts | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 97040a2d4c1..4f9f98e4d81 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -717,23 +717,29 @@ class NotebookAccessibleViewContribution extends Disposable { const outputTextModel = viewCell.model.outputs[i]; const [mimeTypes, pick] = outputViewModel.resolveMimeTypes(notebookEditor.textModel, undefined); const mimeType = mimeTypes[pick].mimeType; - const pickedBuffer = outputTextModel.outputs.find(output => output.mime === mimeType)?.data.buffer; + let buffer = outputTextModel.outputs.find(output => output.mime === mimeType); - let text = `${mimeType}\n`; - if (!pickedBuffer || mimeType.startsWith('image')) { - const altBuffer = outputTextModel.outputs.find(output => !output.mime.startsWith('image'))?.data.buffer; - if (altBuffer) { - text = decoder.decode(altBuffer); - } - } else { - text = decoder.decode(pickedBuffer); + if (!buffer || mimeType.startsWith('image')) { + buffer = outputTextModel.outputs.find(output => !output.mime.startsWith('image')); } + + let text = `${mimeType}`; // default in case we can't get the text value for some reason. + if (buffer) { + const charLimit = 100_000; + text = decoder.decode(buffer.data.slice(0, charLimit).buffer); + + if (buffer.data.byteLength > charLimit) { + text = text + '...(truncated)'; + } + + if (mimeType.endsWith('error')) { + text = text.replace(/\\u001b\[[0-9;]*m/gi, '').replaceAll('\\n', '\n'); + } + } + const index = viewCell.outputsViewModels.length > 1 ? `Cell output ${i + 1} of ${viewCell.outputsViewModels.length}\n` : ''; - if (mimeType.endsWith('error')) { - text = text.replace(/\\u001b\[[0-9;]*m/gi, '').replaceAll('\\n', '\n'); - } outputContent = outputContent.concat(`${index}${text}\n`); } From 587ce364552dd6b1e5c210e3feca942f109c7215 Mon Sep 17 00:00:00 2001 From: aamunger Date: Thu, 20 Jul 2023 11:59:15 -0700 Subject: [PATCH 6/6] move function to helper file --- .../notebook/browser/notebook.contribution.ts | 71 ++----------------- ...bilityHelp.ts => notebookAccessibility.ts} | 67 +++++++++++++++++ 2 files changed, 71 insertions(+), 67 deletions(-) rename src/vs/workbench/contrib/notebook/browser/{notebookAccessibilityHelp.ts => notebookAccessibility.ts} (62%) diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 4f9f98e4d81..ae426f620be 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -57,7 +57,6 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { NotebookRendererMessagingService } from 'vs/workbench/contrib/notebook/browser/services/notebookRendererMessagingServiceImpl'; import { INotebookRendererMessagingService } from 'vs/workbench/contrib/notebook/common/notebookRendererMessagingService'; -import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; // Editor Controller import 'vs/workbench/contrib/notebook/browser/controller/coreActions'; @@ -115,8 +114,8 @@ import { NotebookLoggingService } from 'vs/workbench/contrib/notebook/browser/se import product from 'vs/platform/product/common/product'; import { AccessibilityHelpAction, AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; import { NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; -import { runAccessibilityHelpAction } from 'vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp'; -import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { runAccessibilityHelpAction, showAccessibleOutput } from 'vs/workbench/contrib/notebook/browser/notebookAccessibility'; +import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; /*--------------------------------------------------------------------------------------------- */ @@ -697,75 +696,13 @@ class NotebookAccessibleViewContribution extends Disposable { super(); this._register(AccessibleViewAction.addImplementation(100, 'notebook', accessor => { const accessibleViewService = accessor.get(IAccessibleViewService); + const editorService = accessor.get(IEditorService); - const activePane = accessor.get(IEditorService).activeEditorPane; - const notebookEditor = getNotebookEditorFromEditorPane(activePane); - const notebookViewModel = notebookEditor?.getViewModel(); - const selections = notebookViewModel?.getSelections(); - notebookViewModel?.getCellIndex; - const notebookDocument = notebookViewModel?.notebookDocument; - - if (!selections || !notebookDocument || !notebookEditor?.textModel) { - return false; - } - - const viewCell = notebookViewModel.viewCells[selections[0].start]; - let outputContent = ''; - const decoder = new TextDecoder(); - for (let i = 0; i < viewCell.outputsViewModels.length; i++) { - const outputViewModel = viewCell.outputsViewModels[i]; - const outputTextModel = viewCell.model.outputs[i]; - const [mimeTypes, pick] = outputViewModel.resolveMimeTypes(notebookEditor.textModel, undefined); - const mimeType = mimeTypes[pick].mimeType; - let buffer = outputTextModel.outputs.find(output => output.mime === mimeType); - - if (!buffer || mimeType.startsWith('image')) { - buffer = outputTextModel.outputs.find(output => !output.mime.startsWith('image')); - } - - let text = `${mimeType}`; // default in case we can't get the text value for some reason. - if (buffer) { - const charLimit = 100_000; - text = decoder.decode(buffer.data.slice(0, charLimit).buffer); - - if (buffer.data.byteLength > charLimit) { - text = text + '...(truncated)'; - } - - if (mimeType.endsWith('error')) { - text = text.replace(/\\u001b\[[0-9;]*m/gi, '').replaceAll('\\n', '\n'); - } - } - - const index = viewCell.outputsViewModels.length > 1 - ? `Cell output ${i + 1} of ${viewCell.outputsViewModels.length}\n` - : ''; - outputContent = outputContent.concat(`${index}${text}\n`); - } - - if (!outputContent) { - return false; - } - - accessibleViewService.show({ - verbositySettingKey: 'notebook', - provideContent(): string { return outputContent; }, - onClose() { - notebookEditor?.setFocus(selections[0]); - activePane?.focus(); - }, - options: { - ariaLabel: nls.localize('NotebookCellOutputAccessibleView', "Notebook Cell Output Accessible View"), - language: 'plaintext', - type: AccessibleViewType.View - } - }); - return true; + return showAccessibleOutput(accessibleViewService, editorService); }, NOTEBOOK_OUTPUT_FOCUSED)); } } - const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(NotebookContribution, LifecyclePhase.Starting); workbenchContributionsRegistry.registerWorkbenchContribution(CellContentProvider, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts b/src/vs/workbench/contrib/notebook/browser/notebookAccessibility.ts similarity index 62% rename from src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts rename to src/vs/workbench/contrib/notebook/browser/notebookAccessibility.ts index c5671a24877..6c90a297a6a 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookAccessibility.ts @@ -10,6 +10,8 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; export function getAccessibilityHelpText(accessor: ServicesAccessor): string { const keybindingService = accessor.get(IKeybindingService); @@ -55,3 +57,68 @@ export async function runAccessibilityHelpAction(accessor: ServicesAccessor, edi options: { type: AccessibleViewType.HelpMenu, ariaLabel: 'Notebook accessibility help' } }); } + +export function showAccessibleOutput(accessibleViewService: IAccessibleViewService, editorService: IEditorService) { + const activePane = editorService.activeEditorPane; + const notebookEditor = getNotebookEditorFromEditorPane(activePane); + const notebookViewModel = notebookEditor?.getViewModel(); + const selections = notebookViewModel?.getSelections(); + const notebookDocument = notebookViewModel?.notebookDocument; + + if (!selections || !notebookDocument || !notebookEditor?.textModel) { + return false; + } + + const viewCell = notebookViewModel.viewCells[selections[0].start]; + let outputContent = ''; + const decoder = new TextDecoder(); + for (let i = 0; i < viewCell.outputsViewModels.length; i++) { + const outputViewModel = viewCell.outputsViewModels[i]; + const outputTextModel = viewCell.model.outputs[i]; + const [mimeTypes, pick] = outputViewModel.resolveMimeTypes(notebookEditor.textModel, undefined); + const mimeType = mimeTypes[pick].mimeType; + let buffer = outputTextModel.outputs.find(output => output.mime === mimeType); + + if (!buffer || mimeType.startsWith('image')) { + buffer = outputTextModel.outputs.find(output => !output.mime.startsWith('image')); + } + + let text = `${mimeType}`; // default in case we can't get the text value for some reason. + if (buffer) { + const charLimit = 100_000; + text = decoder.decode(buffer.data.slice(0, charLimit).buffer); + + if (buffer.data.byteLength > charLimit) { + text = text + '...(truncated)'; + } + + if (mimeType.endsWith('error')) { + text = text.replace(/\\u001b\[[0-9;]*m/gi, '').replaceAll('\\n', '\n'); + } + } + + const index = viewCell.outputsViewModels.length > 1 + ? `Cell output ${i + 1} of ${viewCell.outputsViewModels.length}\n` + : ''; + outputContent = outputContent.concat(`${index}${text}\n`); + } + + if (!outputContent) { + return false; + } + + accessibleViewService.show({ + verbositySettingKey: AccessibilityVerbositySettingId.Notebook, + provideContent(): string { return outputContent; }, + onClose() { + notebookEditor?.setFocus(selections[0]); + activePane?.focus(); + }, + options: { + ariaLabel: localize('NotebookCellOutputAccessibleView', "Notebook Cell Output Accessible View"), + language: 'plaintext', + type: AccessibleViewType.View + } + }); + return true; +}