Skip to content

Commit a8a3859

Browse files
authored
Disable ctrl+A in notebook renderer and support Ctrl+A for inputs (microsoft#208635)
* Disable ctrl+A in notebook renderer * Select All in input elements in outputs
1 parent 11f4586 commit a8a3859

File tree

9 files changed

+119
-8
lines changed

9 files changed

+119
-8
lines changed

src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@ registerAction2(class extends NotebookCellAction {
597597
title: localize('notebook.cell.output.selectAll', "Select All"),
598598
keybinding: {
599599
primary: KeyMod.CtrlCmd | KeyCode.KeyA,
600-
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey), NOTEBOOK_OUTPUT_FOCUSED),
600+
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED),
601601
weight: NOTEBOOK_OUTPUT_WEBVIEW_ACTION_WEIGHT
602602
}
603603
});
@@ -615,7 +615,11 @@ registerAction2(class extends NotebookCellAction {
615615
if (!cell || !cell.outputIsFocused || !editor.hasWebviewFocus()) {
616616
return true;
617617
}
618-
editor.selectOutputContent(cell);
618+
if (cell.inputInOutputIsFocused) {
619+
editor.selectInputContents(cell);
620+
} else {
621+
editor.selectOutputContent(cell);
622+
}
619623
return true;
620624
});
621625

src/vs/workbench/contrib/notebook/browser/diff/diffNestedCellViewModel.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,15 @@ export class DiffNestedCellViewModel extends Disposable implements IDiffNestedCe
6262
this._onDidChangeState.fire({ outputIsFocusedChanged: true });
6363
}
6464

65+
private _focusInputInOutput: boolean = false;
66+
public get inputInOutputIsFocused(): boolean {
67+
return this._focusInputInOutput;
68+
}
69+
70+
public set inputInOutputIsFocused(v: boolean) {
71+
this._focusInputInOutput = v;
72+
}
73+
6574
private _outputViewModels: ICellOutputViewModel[];
6675

6776
get outputsViewModels() {

src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ export interface IGenericCellViewModel {
128128
metadata: NotebookCellMetadata;
129129
outputIsHovered: boolean;
130130
outputIsFocused: boolean;
131+
inputInOutputIsFocused: boolean;
131132
outputsViewModels: ICellOutputViewModel[];
132133
getOutputOffset(index: number): number;
133134
updateOutputHeight(index: number, height: number, source?: string): void;
@@ -587,6 +588,11 @@ export interface INotebookEditor {
587588
* Implementation of Ctrl+A for an output item.
588589
*/
589590
selectOutputContent(cell: ICellViewModel): void;
591+
/**
592+
* Select the active input element of the first focused output of the cell.
593+
* Implementation of Ctrl+A for an input element in an output item.
594+
*/
595+
selectInputContents(cell: ICellViewModel): void;
590596

591597
readonly onDidReceiveMessage: Event<INotebookWebviewMessage>;
592598

src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1961,6 +1961,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
19611961
this._webview?.selectOutputContents(cell);
19621962
}
19631963

1964+
selectInputContents(cell: ICellViewModel) {
1965+
this._webview?.selectInputContents(cell);
1966+
}
1967+
19641968
onWillHide() {
19651969
this._isVisible = false;
19661970
this._editorFocus.set(false);
@@ -2402,12 +2406,14 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
24022406
return;
24032407
}
24042408

2405-
const focusElementId = options?.outputId ?? cell.id;
2409+
const firstOutputId = cell.outputsViewModels.find(o => o.model.alternativeOutputId)?.model.alternativeOutputId;
2410+
const focusElementId = options?.outputId ?? firstOutputId ?? cell.id;
24062411
this._webview.focusOutput(focusElementId, options?.altOutputId, options?.outputWebviewFocused || this._webviewFocused);
24072412

24082413
cell.updateEditState(CellEditState.Preview, 'focusNotebookCell');
24092414
cell.focusMode = CellFocusMode.Output;
24102415
cell.focusedOutputId = options?.outputId;
2416+
this._outputFocus.set(true);
24112417
if (!options?.skipReveal) {
24122418
this.revealInCenterIfOutsideViewport(cell);
24132419
}

src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,7 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Themable {
686686
const latestCell = this.notebookEditor.getCellByInfo(resolvedResult.cellInfo);
687687
if (latestCell) {
688688
latestCell.outputIsFocused = false;
689+
latestCell.inputInOutputIsFocused = false;
689690
}
690691
}
691692
break;
@@ -911,6 +912,13 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Themable {
911912
break;
912913
}
913914
case 'outputInputFocus': {
915+
const resolvedResult = this.resolveOutputId(data.id);
916+
if (resolvedResult) {
917+
const latestCell = this.notebookEditor.getCellByInfo(resolvedResult.cellInfo);
918+
if (latestCell) {
919+
latestCell.inputInOutputIsFocused = data.inputFocused;
920+
}
921+
}
914922
this.notebookEditor.didFocusOutputInputChange(data.inputFocused);
915923
}
916924
}
@@ -1693,6 +1701,18 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Themable {
16931701
});
16941702
}
16951703

1704+
selectInputContents(cell: ICellViewModel) {
1705+
if (this._disposed) {
1706+
return;
1707+
}
1708+
const output = cell.outputsViewModels.find(o => o.model.outputId === cell.focusedOutputId);
1709+
const outputId = output ? this.insetMapping.get(output)?.outputId : undefined;
1710+
this._sendMessageToWebview({
1711+
type: 'select-input-contents',
1712+
cellOrOutputId: outputId || cell.id
1713+
});
1714+
}
1715+
16961716
focusOutput(cellOrOutputId: string, alternateId: string | undefined, viewFocused: boolean) {
16971717
if (this._disposed) {
16981718
return;

src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export interface IOutputBlurMessage extends BaseToWebviewMessage {
5050
export interface IOutputInputFocusMessage extends BaseToWebviewMessage {
5151
readonly type: 'outputInputFocus';
5252
readonly inputFocused: boolean;
53+
readonly id: string;
5354
}
5455

5556
export interface IScrollToRevealMessage extends BaseToWebviewMessage {
@@ -463,6 +464,10 @@ export interface ISelectOutputItemMessage {
463464
readonly type: 'select-output-contents';
464465
readonly cellOrOutputId: string;
465466
}
467+
export interface ISelectInputOutputItemMessage {
468+
readonly type: 'select-input-contents';
469+
readonly cellOrOutputId: string;
470+
}
466471

467472
export interface ILogRendererDebugMessage extends BaseToWebviewMessage {
468473
readonly type: 'logRendererDebugMessage';
@@ -544,7 +549,8 @@ export type ToWebviewMessage = IClearMessage |
544549
IFindUnHighlightCurrentMessage |
545550
IFindStopMessage |
546551
IReturnOutputItemMessage |
547-
ISelectOutputItemMessage;
552+
ISelectOutputItemMessage |
553+
ISelectInputOutputItemMessage;
548554

549555

550556
export type AnyMessage = FromWebviewMessage | ToWebviewMessage;

src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,11 +194,12 @@ async function webviewPreloads(ctx: PreloadContext) {
194194
return;
195195
}
196196

197-
if (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA') {
198-
postNotebookMessage<webviewMessages.IOutputInputFocusMessage>('outputInputFocus', { inputFocused: true });
197+
const id = lastFocusedOutput?.id;
198+
if (id && (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA')) {
199+
postNotebookMessage<webviewMessages.IOutputInputFocusMessage>('outputInputFocus', { inputFocused: true, id });
199200

200201
activeElement.addEventListener('blur', () => {
201-
postNotebookMessage<webviewMessages.IOutputInputFocusMessage>('outputInputFocus', { inputFocused: false });
202+
postNotebookMessage<webviewMessages.IOutputInputFocusMessage>('outputInputFocus', { inputFocused: false, id });
202203
}, { once: true });
203204
}
204205
};
@@ -286,6 +287,17 @@ async function webviewPreloads(ctx: PreloadContext) {
286287

287288
};
288289

290+
const selectInputContents = (cellOrOutputId: string) => {
291+
const cellOutputContainer = window.document.getElementById(cellOrOutputId);
292+
if (!cellOutputContainer) {
293+
return;
294+
}
295+
const activeElement = window.document.activeElement;
296+
if (activeElement?.tagName === 'INPUT' || activeElement?.tagName === 'TEXTAREA') {
297+
(activeElement as HTMLInputElement).select();
298+
}
299+
};
300+
289301
const onPageUpDownSelectionHandler = (e: KeyboardEvent) => {
290302
if (!lastFocusedOutput?.id || !e.shiftKey) {
291303
return;
@@ -299,6 +311,11 @@ async function webviewPreloads(ctx: PreloadContext) {
299311
if (!outputContainer || !selection?.anchorNode) {
300312
return;
301313
}
314+
const activeElement = window.document.activeElement;
315+
if (activeElement?.tagName === 'INPUT' || activeElement?.tagName === 'TEXTAREA') {
316+
// Leave for default behavior.
317+
return;
318+
}
302319

303320
// These should change the scroll position, not adjust the selected cell in the notebook
304321
e.stopPropagation(); // We don't want the notebook to handle this.
@@ -318,6 +335,22 @@ async function webviewPreloads(ctx: PreloadContext) {
318335
selection.addRange(range);
319336
};
320337

338+
const disableNativeSelectAll = (e: KeyboardEvent) => {
339+
if (!lastFocusedOutput?.id) {
340+
return;
341+
}
342+
const activeElement = window.document.activeElement;
343+
if (activeElement?.tagName === 'INPUT' || activeElement?.tagName === 'TEXTAREA') {
344+
e.preventDefault(); // We will handle selection in editor code.
345+
return;
346+
}
347+
348+
if ((e.key === 'a' && e.ctrlKey) || (e.metaKey && e.key === 'a')) {
349+
e.preventDefault(); // We will handle selection in editor code.
350+
return;
351+
}
352+
};
353+
321354
const handleDataUrl = async (data: string | ArrayBuffer | null, downloadName: string) => {
322355
postNotebookMessage<webviewMessages.IClickedDataUrlMessage>('clicked-data-url', {
323356
data,
@@ -343,6 +376,7 @@ async function webviewPreloads(ctx: PreloadContext) {
343376
window.document.body.addEventListener('focusin', checkOutputInputFocus);
344377
window.document.body.addEventListener('focusout', handleOutputFocusOut);
345378
window.document.body.addEventListener('keydown', onPageUpDownSelectionHandler);
379+
window.document.body.addEventListener('keydown', disableNativeSelectAll);
346380

347381
interface RendererContext extends rendererApi.RendererContext<unknown> {
348382
readonly onDidChangeSettings: Event<RenderOptions>;
@@ -633,13 +667,19 @@ async function webviewPreloads(ctx: PreloadContext) {
633667
if (cellOutputContainer.contains(window.document.activeElement)) {
634668
return;
635669
}
636-
670+
const id = cellOutputContainer.id;
637671
let focusableElement = cellOutputContainer.querySelector('[tabindex="0"], [href], button, input, option, select, textarea') as HTMLElement | null;
638672
if (!focusableElement) {
639673
focusableElement = cellOutputContainer;
640674
focusableElement.tabIndex = -1;
675+
postNotebookMessage<webviewMessages.IOutputInputFocusMessage>('outputInputFocus', { inputFocused: false, id });
676+
} else {
677+
const inputFocused = focusableElement.tagName === 'INPUT' || focusableElement.tagName === 'TEXTAREA';
678+
postNotebookMessage<webviewMessages.IOutputInputFocusMessage>('outputInputFocus', { inputFocused, id });
641679
}
642680

681+
lastFocusedOutput = cellOutputContainer;
682+
postNotebookMessage<webviewMessages.IOutputFocusMessage>('outputFocus', { id: cellOutputContainer.id });
643683
focusableElement.focus();
644684
}
645685
}
@@ -1695,6 +1735,9 @@ async function webviewPreloads(ctx: PreloadContext) {
16951735
case 'select-output-contents':
16961736
selectOutputContents(event.data.cellOrOutputId);
16971737
break;
1738+
case 'select-input-contents':
1739+
selectInputContents(event.data.cellOrOutputId);
1740+
break;
16981741
case 'decorations': {
16991742
let outputContainer = window.document.getElementById(event.data.cellId);
17001743
if (!outputContainer) {

src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,15 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod
115115
this._onDidChangeState.fire({ outputIsFocusedChanged: true });
116116
}
117117

118+
private _focusInputInOutput: boolean = false;
119+
public get inputInOutputIsFocused(): boolean {
120+
return this._focusInputInOutput;
121+
}
122+
123+
public set inputInOutputIsFocused(v: boolean) {
124+
this._focusInputInOutput = v;
125+
}
126+
118127
private _outputMinHeight: number = 0;
119128

120129
private get outputMinHeight() {

src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,14 @@ export class MarkupCellViewModel extends BaseCellViewModel implements ICellViewM
9393
this._focusOnOutput = v;
9494
}
9595

96+
public get inputInOutputIsFocused(): boolean {
97+
return false;
98+
}
99+
100+
public set inputInOutputIsFocused(_: boolean) {
101+
//
102+
}
103+
96104
private _hoveringCell = false;
97105
public get cellIsHovered(): boolean {
98106
return this._hoveringCell;

0 commit comments

Comments
 (0)