Skip to content

Commit 6722b81

Browse files
authored
Support cmd+arrow/enter when focus is outside of chat widget. (microsoft#205260)
1 parent 3ce7ccf commit 6722b81

File tree

7 files changed

+171
-15
lines changed

7 files changed

+171
-15
lines changed

src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation
1818
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
1919
import { Registry } from 'vs/platform/registry/common/platform';
2020
import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController';
21+
import { CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext';
2122
import { INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, findTargetCellEditor } from 'vs/workbench/contrib/notebook/browser/controller/coreActions';
2223
import { CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
2324
import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon';
@@ -196,12 +197,18 @@ registerAction2(class extends NotebookAction {
196197
super({
197198
id: NOTEBOOK_FOCUS_TOP,
198199
title: localize('focusFirstCell', 'Focus First Cell'),
199-
keybinding: {
200-
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)),
201-
primary: KeyMod.CtrlCmd | KeyCode.Home,
202-
mac: { primary: KeyMod.CtrlCmd | KeyCode.UpArrow },
203-
weight: KeybindingWeight.WorkbenchContrib
204-
},
200+
keybinding: [
201+
{
202+
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)),
203+
primary: KeyMod.CtrlCmd | KeyCode.Home,
204+
weight: KeybindingWeight.WorkbenchContrib
205+
},
206+
{
207+
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey), CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('')),
208+
mac: { primary: KeyMod.CtrlCmd | KeyCode.UpArrow },
209+
weight: KeybindingWeight.WorkbenchContrib
210+
}
211+
],
205212
});
206213
}
207214

@@ -221,12 +228,19 @@ registerAction2(class extends NotebookAction {
221228
super({
222229
id: NOTEBOOK_FOCUS_BOTTOM,
223230
title: localize('focusLastCell', 'Focus Last Cell'),
224-
keybinding: {
225-
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)),
226-
primary: KeyMod.CtrlCmd | KeyCode.End,
227-
mac: { primary: KeyMod.CtrlCmd | KeyCode.DownArrow },
228-
weight: KeybindingWeight.WorkbenchContrib
229-
},
231+
keybinding: [
232+
{
233+
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)),
234+
primary: KeyMod.CtrlCmd | KeyCode.End,
235+
mac: undefined,
236+
weight: KeybindingWeight.WorkbenchContrib
237+
},
238+
{
239+
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey), CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('')),
240+
mac: { primary: KeyMod.CtrlCmd | KeyCode.DownArrow },
241+
weight: KeybindingWeight.WorkbenchContrib
242+
}
243+
],
230244
});
231245
}
232246

src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkey
1414
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
1515
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
1616
import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseFeedbackKind, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
17-
import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext';
17+
import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext';
1818
import { NotebookChatController } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController';
1919
import { INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, getEditorFromArgsOrActivePane } from 'vs/workbench/contrib/notebook/browser/controller/coreActions';
2020
import { CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
@@ -232,6 +232,15 @@ registerAction2(class extends NotebookAction {
232232
when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_USER_DID_EDIT),
233233
weight: KeybindingWeight.EditorCore + 10,
234234
primary: KeyCode.Escape
235+
},
236+
{
237+
when: ContextKeyExpr.and(
238+
NOTEBOOK_EDITOR_FOCUSED,
239+
ContextKeyExpr.not(InputFocusedContextKey),
240+
CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('below')
241+
),
242+
primary: KeyMod.CtrlCmd | KeyCode.Enter,
243+
weight: KeybindingWeight.WorkbenchContrib
235244
}
236245
],
237246
menu: [
@@ -483,3 +492,82 @@ MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, {
483492
ContextKeyExpr.equals(`config.${NotebookSetting.cellChat}`, true)
484493
)
485494
});
495+
496+
registerAction2(class extends NotebookAction {
497+
constructor() {
498+
super({
499+
id: 'notebook.cell.chat.focus',
500+
title: localize('focusNotebookChat', 'Focus Chat'),
501+
keybinding: [
502+
{
503+
when: ContextKeyExpr.and(
504+
NOTEBOOK_EDITOR_FOCUSED,
505+
ContextKeyExpr.not(InputFocusedContextKey),
506+
CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('above')
507+
),
508+
primary: KeyMod.CtrlCmd | KeyCode.DownArrow,
509+
weight: KeybindingWeight.WorkbenchContrib
510+
},
511+
{
512+
when: ContextKeyExpr.and(
513+
NOTEBOOK_EDITOR_FOCUSED,
514+
ContextKeyExpr.not(InputFocusedContextKey),
515+
CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('below')
516+
),
517+
primary: KeyMod.CtrlCmd | KeyCode.UpArrow,
518+
weight: KeybindingWeight.WorkbenchContrib
519+
}
520+
],
521+
});
522+
}
523+
524+
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise<void> {
525+
NotebookChatController.get(context.notebookEditor)?.focus();
526+
}
527+
});
528+
529+
registerAction2(class extends NotebookAction {
530+
constructor() {
531+
super({
532+
id: 'notebook.cell.chat.focusNextCell',
533+
title: localize('focusNextCell', 'Focus Next Cell'),
534+
keybinding: [
535+
{
536+
when: ContextKeyExpr.and(
537+
CTX_NOTEBOOK_CELL_CHAT_FOCUSED,
538+
CTX_INLINE_CHAT_FOCUSED,
539+
),
540+
primary: KeyMod.CtrlCmd | KeyCode.DownArrow,
541+
weight: KeybindingWeight.WorkbenchContrib
542+
}
543+
],
544+
});
545+
}
546+
547+
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise<void> {
548+
NotebookChatController.get(context.notebookEditor)?.focusNext();
549+
}
550+
});
551+
552+
registerAction2(class extends NotebookAction {
553+
constructor() {
554+
super({
555+
id: 'notebook.cell.chat.focusPreviousCell',
556+
title: localize('focusPreviousCell', 'Focus Previous Cell'),
557+
keybinding: [
558+
{
559+
when: ContextKeyExpr.and(
560+
CTX_NOTEBOOK_CELL_CHAT_FOCUSED,
561+
CTX_INLINE_CHAT_FOCUSED,
562+
),
563+
primary: KeyMod.CtrlCmd | KeyCode.UpArrow,
564+
weight: KeybindingWeight.WorkbenchContrib
565+
}
566+
],
567+
});
568+
}
569+
570+
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise<void> {
571+
NotebookChatController.get(context.notebookEditor)?.focusAbove();
572+
}
573+
});

src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
1010
export const CTX_NOTEBOOK_CELL_CHAT_FOCUSED = new RawContextKey<boolean>('notebookCellChatFocused', false, localize('notebookCellChatFocused', "Whether the cell chat editor is focused"));
1111
export const CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST = new RawContextKey<boolean>('notebookChatHasActiveRequest', false, localize('notebookChatHasActiveRequest', "Whether the cell chat editor has an active request"));
1212
export const CTX_NOTEBOOK_CHAT_USER_DID_EDIT = new RawContextKey<boolean>('notebookChatUserDidEdit', false, localize('notebookChatUserDidEdit', "Whether the user did changes ontop of the notebook cell chat"));
13+
export const CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION = new RawContextKey<'above' | 'below' | ''>('notebookChatOuterFocusPosition', '', localize('notebookChatOuterFocusPosition', "Whether the focus of the notebook editor is above or below the cell chat"));
14+
1315
export const MENU_CELL_CHAT_INPUT = MenuId.for('cellChatInput');
1416
export const MENU_CELL_CHAT_WIDGET = MenuId.for('cellChatWidget');
1517
export const MENU_CELL_CHAT_WIDGET_STATUS = MenuId.for('cellChatWidget.status');

src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import { IInlineChatMessageAppender, InlineChatWidget } from 'vs/workbench/contr
3939
import { asProgressiveEdit, performAsyncTextEdit } from 'vs/workbench/contrib/inlineChat/browser/utils';
4040
import { CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, EditMode, IInlineChatProgressItem, IInlineChatRequest, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
4141
import { insertCell, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations';
42-
import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext';
42+
import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext';
4343
import { INotebookEditor, INotebookEditorContribution, INotebookViewZone, ScrollToRevealBehavior } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
4444
import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions';
4545
import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl';
@@ -180,6 +180,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito
180180
private readonly _ctxHasActiveRequest: IContextKey<boolean>;
181181
private readonly _ctxCellWidgetFocused: IContextKey<boolean>;
182182
private readonly _ctxUserDidEdit: IContextKey<boolean>;
183+
private readonly _ctxOuterFocusPosition: IContextKey<'above' | 'below' | ''>;
183184
private readonly _userEditingDisposables = this._register(new DisposableStore());
184185
private readonly _ctxLastResponseType: IContextKey<undefined | InlineChatResponseType>;
185186
private _widget: NotebookChatWidget | undefined;
@@ -203,6 +204,29 @@ export class NotebookChatController extends Disposable implements INotebookEdito
203204
this._ctxCellWidgetFocused = CTX_NOTEBOOK_CELL_CHAT_FOCUSED.bindTo(this._contextKeyService);
204205
this._ctxLastResponseType = CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.bindTo(this._contextKeyService);
205206
this._ctxUserDidEdit = CTX_NOTEBOOK_CHAT_USER_DID_EDIT.bindTo(this._contextKeyService);
207+
this._ctxOuterFocusPosition = CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.bindTo(this._contextKeyService);
208+
209+
this._registerFocusTracker();
210+
}
211+
212+
private _registerFocusTracker() {
213+
this._register(this._notebookEditor.onDidChangeFocus(() => {
214+
if (!this._widget) {
215+
this._ctxOuterFocusPosition.set('');
216+
return;
217+
}
218+
219+
const widgetIndex = this._widget.afterModelPosition;
220+
const focus = this._notebookEditor.getFocus().start;
221+
222+
if (focus + 1 === widgetIndex) {
223+
this._ctxOuterFocusPosition.set('above');
224+
} else if (focus === widgetIndex) {
225+
this._ctxOuterFocusPosition.set('below');
226+
} else {
227+
this._ctxOuterFocusPosition.set('');
228+
}
229+
}));
206230
}
207231

208232
run(index: number, input: string | undefined, autoSend: boolean | undefined): void {
@@ -672,6 +696,25 @@ export class NotebookChatController extends Disposable implements INotebookEdito
672696
this.dismiss();
673697
}
674698

699+
async focusAbove() {
700+
if (!this._widget) {
701+
return;
702+
}
703+
704+
const index = this._widget.afterModelPosition;
705+
const prev = index - 1;
706+
if (prev < 0) {
707+
return;
708+
}
709+
710+
const cell = this._notebookEditor.cellAt(prev);
711+
if (!cell) {
712+
return;
713+
}
714+
715+
await this._notebookEditor.focusNotebookCell(cell, 'editor');
716+
}
717+
675718
async focusNext() {
676719
if (!this._widget) {
677720
return;
@@ -686,6 +729,10 @@ export class NotebookChatController extends Disposable implements INotebookEdito
686729
await this._notebookEditor.focusNotebookCell(cell, 'editor');
687730
}
688731

732+
focus() {
733+
this._focusWidget();
734+
}
735+
689736
focusNearestWidget(index: number, direction: 'above' | 'below') {
690737
switch (direction) {
691738
case 'above':

src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { INotebookActionContext, NotebookAction } from 'vs/workbench/contrib/not
1717
import { NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_EDITOR_EDITABLE } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
1818
import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl';
1919
import { CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon';
20+
import { CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext';
2021

2122
const INSERT_CODE_CELL_ABOVE_COMMAND_ID = 'notebook.cell.insertCodeCellAbove';
2223
const INSERT_CODE_CELL_BELOW_COMMAND_ID = 'notebook.cell.insertCodeCellBelow';
@@ -110,7 +111,7 @@ registerAction2(class InsertCodeCellBelowAction extends InsertCellCommand {
110111
title: localize('notebookActions.insertCodeCellBelow', "Insert Code Cell Below"),
111112
keybinding: {
112113
primary: KeyMod.CtrlCmd | KeyCode.Enter,
113-
when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, InputFocusedContext.toNegated()),
114+
when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, InputFocusedContext.toNegated(), CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('')),
114115
weight: KeybindingWeight.WorkbenchContrib
115116
},
116117
menu: {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,7 @@ export interface INotebookEditor {
467467
readonly onDidChangeViewCells: Event<INotebookViewCellsUpdateEvent>;
468468
readonly onDidChangeVisibleRanges: Event<void>;
469469
readonly onDidChangeSelection: Event<void>;
470+
readonly onDidChangeFocus: Event<void>;
470471
/**
471472
* An event emitted when the model of this editor has changed.
472473
*/

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
154154
readonly onDidScroll: Event<void> = this._onDidScroll.event;
155155
private readonly _onDidChangeActiveCell = this._register(new Emitter<void>());
156156
readonly onDidChangeActiveCell: Event<void> = this._onDidChangeActiveCell.event;
157+
private readonly _onDidChangeFocus = this._register(new Emitter<void>());
158+
readonly onDidChangeFocus: Event<void> = this._onDidChangeFocus.event;
157159
private readonly _onDidChangeSelection = this._register(new Emitter<void>());
158160
readonly onDidChangeSelection: Event<void> = this._onDidChangeSelection.event;
159161
private readonly _onDidChangeVisibleRanges = this._register(new Emitter<void>());
@@ -1010,6 +1012,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
10101012
this._register(this._list.onDidChangeFocus(_e => {
10111013
this._onDidChangeActiveEditor.fire(this);
10121014
this._onDidChangeActiveCell.fire();
1015+
this._onDidChangeFocus.fire();
10131016
this._cursorNavMode.set(false);
10141017
}));
10151018

0 commit comments

Comments
 (0)