Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Ctrl+A in notebook outputs #207548

Merged
merged 9 commits into from
Mar 17, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/vs/platform/webview/common/webviewContextKeys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';


// WebView Commands
export const WEBVIEW_FOCUSED = new RawContextKey<boolean>('webviewFocused', false);
3 changes: 2 additions & 1 deletion src/vs/workbench/browser/actions/listCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { isActiveElement } from 'vs/base/browser/dom';
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { localize, localize2 } from 'vs/nls';
import { WEBVIEW_FOCUSED } from 'vs/platform/webview/common/webviewContextKeys';

function ensureDOMFocus(widget: ListWidget | undefined): void {
// it can happen that one of the commands is executed while
Expand Down Expand Up @@ -600,7 +601,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.selectAll',
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(WorkbenchListFocusContextKey, WorkbenchListSupportsMultiSelectContextKey),
when: ContextKeyExpr.and(WorkbenchListFocusContextKey, WorkbenchListSupportsMultiSelectContextKey, WEBVIEW_FOCUSED.negate()),
DonJayamanne marked this conversation as resolved.
Show resolved Hide resolved
primary: KeyMod.CtrlCmd | KeyCode.KeyA,
handler: (accessor) => {
const focused = accessor.get(IListService).lastFocusedList;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { RedoCommand, UndoCommand } from 'vs/editor/browser/editorExtensions';
import { RedoCommand, SelectAllCommand, UndoCommand } from 'vs/editor/browser/editorExtensions';
import { IWebview } from 'vs/workbench/contrib/webview/browser/webview';
import { Categories } from 'vs/platform/action/common/actionCommonCategories';
import { ILogService } from 'vs/platform/log/common/log';
Expand All @@ -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);
Expand All @@ -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;
}

Expand All @@ -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 => {
Expand All @@ -101,6 +113,25 @@ CutAction?.addImplementation(PRIORITY, 'notebook-webview', accessor => {
return withWebview(accessor, webview => webview.cut());
});

SelectAllCommand.addImplementation(PRIORITY, 'notebook-webview', accessor => {
return withEditor(accessor, editor => {
if (!editor.hasEditorFocus()) {
return false;
}
// From here on, always return true, else generic webview handler
// code will select all outputs in all cells.
if (editor.hasEditorFocus() && !editor.hasWebviewFocus()) {
return true;
}
const cell = editor.getActiveCell();
if (!cell || !cell.outputIsFocused || !editor.hasWebviewFocus()) {
return true;
}
editor.selectOutputContent(cell);
return true;
});
});


export function runPasteCells(editor: INotebookEditor, activeCell: ICellViewModel | undefined, pasteCells: {
items: NotebookCellTextModel[];
Expand Down
5 changes: 5 additions & 0 deletions src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,11 @@ export interface INotebookEditor {
* Copy the image in the specific cell output to the clipboard
*/
copyOutputImage(cellOutput: ICellOutputViewModel): Promise<void>;
/**
* 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<INotebookWebviewMessage>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1980,6 +1980,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
}
}

selectOutputContent(cell: ICellViewModel) {
this._webview?.selectOutputContents(cell);
}

onWillHide() {
this._isVisible = false;
this._editorFocus.set(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1681,6 +1681,18 @@ export class BackLayerWebView<T extends ICommonCellInfo> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -555,7 +560,8 @@ export type ToWebviewMessage = IClearMessage |
IFindHighlightCurrentMessage |
IFindUnHighlightCurrentMessage |
IFindStopMessage |
IReturnOutputItemMessage;
IReturnOutputItemMessage |
ISelectOutputItemMessage;


export type AnyMessage = FromWebviewMessage | ToWebviewMessage;
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,21 @@ async function webviewPreloads(ctx: PreloadContext) {
postNotebookMessage<webviewMessages.IOutputFocusMessage>('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<webviewMessages.IClickedDataUrlMessage>('clicked-data-url', {
Expand Down Expand Up @@ -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) {
Expand Down
7 changes: 5 additions & 2 deletions src/vs/workbench/contrib/webview/browser/webviewElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { localize } from 'vs/nls';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { MenuId } from 'vs/platform/actions/common/actions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextKeyService, type IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
Expand All @@ -38,6 +38,7 @@ import { FromWebviewMessage, KeyEvent, ToWebviewMessage } from 'vs/workbench/con
import { decodeAuthority, webviewGenericCspSource, webviewRootResourceAuthority } from 'vs/workbench/contrib/webview/common/webview';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { CodeWindow } from 'vs/base/browser/window';
import { WEBVIEW_FOCUSED } from 'vs/platform/webview/common/webviewContextKeys';

interface WebviewContent {
readonly html: string;
Expand Down Expand Up @@ -144,7 +145,7 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD

private _disposed = false;


private _focusedContextKey?: IContextKey<boolean>;
public extension: WebviewExtensionDescription | undefined;
private readonly _options: WebviewOptions;

Expand Down Expand Up @@ -339,6 +340,7 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD

setContextKeyService(contextKeyService: IContextKeyService) {
this._contextKeyService = contextKeyService;
this._focusedContextKey = WEBVIEW_FOCUSED.bindTo(contextKeyService);
}

private readonly _onMissingCsp = this._register(new Emitter<ExtensionIdentifier>());
Expand Down Expand Up @@ -679,6 +681,7 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD

protected handleFocusChange(isFocused: boolean): void {
this._focused = isFocused;
this._focusedContextKey?.set(isFocused);
DonJayamanne marked this conversation as resolved.
Show resolved Hide resolved
if (isFocused) {
this._onDidFocus.fire();
} else {
Expand Down
Loading