diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts index 4f96858e907c1..7746c3a24008d 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts @@ -7,7 +7,7 @@ import { localize, localize2 } from 'vs/nls'; import { Disposable } from 'vs/base/common/lifecycle'; import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { NOTEBOOK_CELL_EDITABLE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; +import { NOTEBOOK_CELL_EDITABLE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { cellRangeToViewCells, expandCellRangesWithHiddenCells, getNotebookEditorFromEditorPane, ICellViewModel, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CopyAction, CutAction, PasteAction } from 'vs/editor/contrib/clipboard/browser/clipboard'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -16,7 +16,7 @@ import { CellEditType, ICellEditOperation, ISelectionState, SelectionStateType } import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import * as platform from 'vs/base/common/platform'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; -import { CellOverflowToolbarGroups, INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; +import { CellOverflowToolbarGroups, INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, NOTEBOOK_OUTPUT_WEBVIEW_ACTION_WEIGHT } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; @@ -41,7 +41,7 @@ function _log(loggerService: ILogService, str: string) { } } -function getFocusedWebviewDelegate(accessor: ServicesAccessor): IWebview | undefined { +function getFocusedEditor(accessor: ServicesAccessor) { const loggerService = accessor.get(ILogService); const editorService = accessor.get(IEditorService); const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); @@ -65,8 +65,15 @@ function getFocusedWebviewDelegate(accessor: ServicesAccessor): IWebview | undef return; } - const webview = editor.getInnerWebview(); - _log(loggerService, '[Revive Webview] Notebook editor backlayer webview is focused'); + return { editor, loggerService }; +} +function getFocusedWebviewDelegate(accessor: ServicesAccessor): IWebview | undefined { + const result = getFocusedEditor(accessor); + if (!result) { + return; + } + const webview = result.editor.getInnerWebview(); + _log(result.loggerService, '[Revive Webview] Notebook editor backlayer webview is focused'); return webview; } @@ -79,6 +86,11 @@ function withWebview(accessor: ServicesAccessor, f: (webviewe: IWebview) => void return false; } +function withEditor(accessor: ServicesAccessor, f: (editor: INotebookEditor) => boolean) { + const result = getFocusedEditor(accessor); + return result ? f(result.editor) : false; +} + const PRIORITY = 105; UndoCommand.addImplementation(PRIORITY, 'notebook-webview', accessor => { @@ -101,7 +113,6 @@ CutAction?.addImplementation(PRIORITY, 'notebook-webview', accessor => { return withWebview(accessor, webview => webview.cut()); }); - export function runPasteCells(editor: INotebookEditor, activeCell: ICellViewModel | undefined, pasteCells: { items: NotebookCellTextModel[]; isCopy: boolean; @@ -573,3 +584,37 @@ registerAction2(class extends Action2 { } } }); + + +registerAction2(class extends NotebookCellAction { + constructor() { + super( + { + id: 'notebook.cell.output.selectAll', + title: localize('notebook.cell.output.selectAll', "Select All"), + keybinding: { + primary: KeyMod.CtrlCmd | KeyCode.KeyA, + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey), NOTEBOOK_OUTPUT_FOCUSED), + weight: NOTEBOOK_OUTPUT_WEBVIEW_ACTION_WEIGHT + } + }); + } + + async runWithContext(accessor: ServicesAccessor, _context: INotebookCellActionContext) { + withEditor(accessor, editor => { + if (!editor.hasEditorFocus()) { + return false; + } + if (editor.hasEditorFocus() && !editor.hasWebviewFocus()) { + return true; + } + const cell = editor.getActiveCell(); + if (!cell || !cell.outputIsFocused || !editor.hasWebviewFocus()) { + return true; + } + editor.selectOutputContent(cell); + return true; + }); + + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts index 087c143716c83..3cc7faf8354db 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts @@ -31,6 +31,7 @@ export const CELL_TITLE_CELL_GROUP_ID = 'inline/cell'; export const CELL_TITLE_OUTPUT_GROUP_ID = 'inline/output'; export const NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT = KeybindingWeight.EditorContrib; // smaller than Suggest Widget, etc +export const NOTEBOOK_OUTPUT_WEBVIEW_ACTION_WEIGHT = KeybindingWeight.WorkbenchContrib + 1; // higher than Workbench contribution (such as Notebook List View), etc export const enum CellToolbarOrder { EditCell, diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 960f2a292d7dc..a502b32b7084b 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -582,6 +582,11 @@ export interface INotebookEditor { * Copy the image in the specific cell output to the clipboard */ copyOutputImage(cellOutput: ICellOutputViewModel): Promise; + /** + * Select the contents of the first focused output of the cell. + * Implementation of Ctrl+A for an output item. + */ + selectOutputContent(cell: ICellViewModel): void; readonly onDidReceiveMessage: Event; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 9f7b81ac1e23b..7c9258a30e0f2 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -1976,6 +1976,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } } + selectOutputContent(cell: ICellViewModel) { + this._webview?.selectOutputContents(cell); + } + onWillHide() { this._isVisible = false; this._editorFocus.set(false); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 6bb75a310e9cf..a005797d87efc 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -1681,6 +1681,18 @@ export class BackLayerWebView extends Themable { this.webview?.focus(); } + selectOutputContents(cell: ICellViewModel) { + if (this._disposed) { + return; + } + const output = cell.outputsViewModels.find(o => o.model.outputId === cell.focusedOutputId); + const outputId = output ? this.insetMapping.get(output)?.outputId : undefined; + this._sendMessageToWebview({ + type: 'select-output-contents', + cellOrOutputId: outputId || cell.id + }); + } + focusOutput(cellOrOutputId: string, alternateId: string | undefined, viewFocused: boolean) { if (this._disposed) { return; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts index b46964be30748..731e5dd508a32 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts @@ -476,6 +476,11 @@ export interface IReturnOutputItemMessage { readonly output: OutputItemEntry | undefined; } +export interface ISelectOutputItemMessage { + readonly type: 'select-output-contents'; + readonly cellOrOutputId: string; +} + export interface ILogRendererDebugMessage extends BaseToWebviewMessage { readonly type: 'logRendererDebugMessage'; readonly message: string; @@ -555,7 +560,8 @@ export type ToWebviewMessage = IClearMessage | IFindHighlightCurrentMessage | IFindUnHighlightCurrentMessage | IFindStopMessage | - IReturnOutputItemMessage; + IReturnOutputItemMessage | + ISelectOutputItemMessage; export type AnyMessage = FromWebviewMessage | ToWebviewMessage; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index ad32fd500c28a..81b19159bb60f 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -270,6 +270,21 @@ async function webviewPreloads(ctx: PreloadContext) { postNotebookMessage('outputFocus', outputFocus); } }; + const selectOutputContents = (cellOrOutputId: string) => { + const selection = window.getSelection(); + if (!selection) { + return; + } + const cellOutputContainer = window.document.getElementById(cellOrOutputId); + if (!cellOutputContainer) { + return; + } + selection.removeAllRanges(); + const range = document.createRange(); + range.selectNode(cellOutputContainer); + selection.addRange(range); + + }; const handleDataUrl = async (data: string | ArrayBuffer | null, downloadName: string) => { postNotebookMessage('clicked-data-url', { @@ -1609,6 +1624,9 @@ async function webviewPreloads(ctx: PreloadContext) { case 'focus-output': focusFirstFocusableOrContainerInOutput(event.data.cellOrOutputId, event.data.alternateId); break; + case 'select-output-contents': + selectOutputContents(event.data.cellOrOutputId); + break; case 'decorations': { let outputContainer = window.document.getElementById(event.data.cellId); if (!outputContainer) {