From 14373df929b404fa463e1db512b38c331e52d052 Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 6 Mar 2024 10:56:48 -0800 Subject: [PATCH] Reserve focus for cell chat widget. --- .../controller/chat/notebookChatController.ts | 51 ++++++++++++++----- .../browser/controller/executeActions.ts | 22 +++++++- .../notebook/browser/notebookBrowser.ts | 5 ++ .../notebook/browser/notebookEditorWidget.ts | 6 +-- .../notebook/browser/view/notebookCellList.ts | 8 ++- 5 files changed, 73 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index d4b59189404a4..d42706aca683f 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -40,7 +40,7 @@ import { asProgressiveEdit, performAsyncTextEdit } from 'vs/workbench/contrib/in import { CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, EditMode, IInlineChatProgressItem, IInlineChatRequest, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { insertCell, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; 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'; -import { INotebookEditor, INotebookEditorContribution, INotebookViewZone, ScrollToRevealBehavior } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { INotebookEditor, INotebookEditorContribution, INotebookViewZone } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -96,21 +96,35 @@ class NotebookChatWidget extends Disposable implements INotebookViewZone { this._layoutWidget(inlineChatWidget, widgetContainer); } + hasFocus() { + return this.inlineChatWidget.hasFocus(); + } + focus() { + this.updateNotebookEditorFocusNSelections(); this.inlineChatWidget.focus(); } + updateNotebookEditorFocusNSelections() { + this._notebookEditor.focusContainer(true); + this._notebookEditor.setFocus({ start: this.afterModelPosition, end: this.afterModelPosition }); + this._notebookEditor.setSelections([{ + start: this.afterModelPosition, + end: this.afterModelPosition + }]); + } + getEditingCell() { return this._editingCell; } async getOrCreateEditingCell(): Promise<{ cell: CellViewModel; editor: IActiveCodeEditor } | undefined> { if (this._editingCell) { - await this._notebookEditor.focusNotebookCell(this._editingCell, 'editor'); - if (this._notebookEditor.activeCodeEditor?.hasModel()) { + const codeEditor = this._notebookEditor.codeEditors.find(ce => ce[0] === this._editingCell)?.[1]; + if (codeEditor?.hasModel()) { return { cell: this._editingCell, - editor: this._notebookEditor.activeCodeEditor + editor: codeEditor }; } else { return undefined; @@ -121,17 +135,25 @@ class NotebookChatWidget extends Disposable implements INotebookViewZone { return undefined; } + const widgetHasFocus = this.inlineChatWidget.hasFocus(); + this._editingCell = insertCell(this._languageService, this._notebookEditor, this.afterModelPosition, CellKind.Code, 'above'); if (!this._editingCell) { return undefined; } - await this._notebookEditor.focusNotebookCell(this._editingCell, 'editor', { revealBehavior: ScrollToRevealBehavior.firstLine }); - if (this._notebookEditor.activeCodeEditor?.hasModel()) { + await this._notebookEditor.revealFirstLineIfOutsideViewport(this._editingCell); + + if (widgetHasFocus) { + this.focus(); + } + + const codeEditor = this._notebookEditor.codeEditors.find(ce => ce[0] === this._editingCell)?.[1]; + if (codeEditor?.hasModel()) { return { cell: this._editingCell, - editor: this._notebookEditor.activeCodeEditor + editor: codeEditor }; } @@ -389,12 +411,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito return; } - this._notebookEditor.focusContainer(true); - this._notebookEditor.setFocus({ start: this._widget.afterModelPosition, end: this._widget.afterModelPosition }); - this._notebookEditor.setSelections([{ - start: this._widget.afterModelPosition, - end: this._widget.afterModelPosition - }]); + this._widget.updateNotebookEditorFocusNSelections(); } async acceptInput() { @@ -738,6 +755,10 @@ export class NotebookChatController extends Disposable implements INotebookEdito await this._notebookEditor.focusNotebookCell(cell, 'editor'); } + hasFocus() { + return this._widget?.hasFocus() ?? false; + } + focus() { this._focusWidget(); } @@ -768,6 +789,10 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._activeRequestCts?.cancel(); } + getEditingCell() { + return this._widget?.getEditingCell(); + } + discard() { this._strategy?.cancel(); this._activeRequestCts?.cancel(); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts index 2ca62a92f6fd3..ce41420deba05 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts @@ -20,6 +20,8 @@ import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import { CTX_INLINE_CHAT_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; +import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; +import { NotebookChatController } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController'; import { CELL_TITLE_CELL_GROUP_ID, CellToolbarOrder, INotebookActionContext, INotebookCellActionContext, INotebookCellToolbarActionContext, INotebookCommandContext, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, NotebookAction, NotebookCellAction, NotebookMultiCellAction, cellExecutionArgs, executeNotebookCondition, getContextFromActiveEditor, getContextFromUri, parseMultiCellExecutionArgs } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID, IFocusNotebookCellOptions, ScrollToRevealBehavior } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; @@ -198,7 +200,10 @@ registerAction2(class ExecuteCell extends NotebookMultiCellAction { precondition: executeThisCellCondition, title: localize('notebookActions.execute', "Execute Cell"), keybinding: { - when: NOTEBOOK_CELL_LIST_FOCUSED, + when: ContextKeyExpr.or( + NOTEBOOK_CELL_LIST_FOCUSED, + ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED) + ), primary: KeyMod.WinCtrl | KeyCode.Enter, win: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter @@ -229,6 +234,21 @@ registerAction2(class ExecuteCell extends NotebookMultiCellAction { await context.notebookEditor.focusNotebookCell(context.cell, 'container', { skipReveal: true }); } + const chatController = NotebookChatController.get(context.notebookEditor); + const editingCell = chatController?.getEditingCell(); + if (chatController?.hasFocus() && editingCell) { + const group = editorGroupsService.activeGroup; + + if (group) { + if (group.activeEditor) { + group.pinEditor(group.activeEditor); + } + } + + await context.notebookEditor.executeNotebookCells([editingCell]); + return; + } + await runCell(editorGroupsService, context); } }); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 4f6205e00ccdb..8c8d3a7f64b26 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -629,6 +629,11 @@ export interface INotebookEditor { */ revealInCenterIfOutsideViewport(cell: ICellViewModel): Promise; + /** + * Reveal the first line of the cell into the view if the cell is outside of the viewport. + */ + revealFirstLineIfOutsideViewport(cell: ICellViewModel): Promise; + /** * Reveal a line in notebook cell into viewport with minimal scrolling. */ diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index cdffe911b5d91..8ce50f30af09a 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -2122,8 +2122,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD await this._list.revealCell(cell, CellRevealType.CenterIfOutsideViewport); } - revealFirstLineIfOutsideViewport(cell: ICellViewModel) { - this._list.revealCell(cell, CellRevealType.FirstLineIfOutsideViewport); + async revealFirstLineIfOutsideViewport(cell: ICellViewModel) { + await this._list.revealCell(cell, CellRevealType.FirstLineIfOutsideViewport); } async revealLineInViewAsync(cell: ICellViewModel, line: number): Promise { @@ -2446,7 +2446,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._cursorNavMode.set(true); await this.revealInView(cell); } else if (options?.revealBehavior === ScrollToRevealBehavior.firstLine) { - this.revealFirstLineIfOutsideViewport(cell); + await this.revealFirstLineIfOutsideViewport(cell); } else if (options?.revealBehavior === ScrollToRevealBehavior.fullCell) { await this.revealInView(cell); } else { diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index bd47eb879e98e..377857dd4cef8 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -925,8 +925,12 @@ export class NotebookCellList extends WorkbenchList implements ID break; } - // wait for the editor to be created only if the cell is in editing mode (meaning it has an editor and will focus the editor) - if (cell.getEditState() === CellEditState.Editing && !cell.editorAttached) { + if (( + // wait for the editor to be created if the cell is in editing mode + cell.getEditState() === CellEditState.Editing + // wait for the editor to be created if we are revealing the first line of the cell + || revealType === CellRevealType.FirstLineIfOutsideViewport + ) && !cell.editorAttached) { return getEditorAttachedPromise(cell); }