diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d4db6e73ea4..37d7cb8328bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,8 @@ ### Fixes +1. Fixed the focus on the interactive window when pressing ctrl + 1/ ctrl + 2 + ([#9693](https://github.com/microsoft/vscode-python/issues/9693)) 1. Fix variable explorer in Interactive and Notebook editors from interfering with execution. ([#5980](https://github.com/Microsoft/vscode-python/issues/5980)) 1. Fix a crash when using pytest to discover doctests with unknown line number. diff --git a/src/datascience-ui/history-react/interactiveCell.tsx b/src/datascience-ui/history-react/interactiveCell.tsx index 8329fcf7867a..a32cfb087634 100644 --- a/src/datascience-ui/history-react/interactiveCell.tsx +++ b/src/datascience-ui/history-react/interactiveCell.tsx @@ -40,6 +40,7 @@ interface IInteractiveCellBaseProps { editorMeasureClassName?: string; font: IFont; settings: IDataScienceExtraSettings; + focusPending: number; } type IInteractiveCellProps = IInteractiveCellBaseProps & typeof actionCreators; @@ -266,6 +267,7 @@ export class InteractiveCell extends React.Component { keyDown={this.isEditCell() ? this.onEditCellKeyDown : undefined} showLineNumbers={this.props.cellVM.showLineNumbers} font={this.props.font} + focusPending={this.props.focusPending} /> ); } diff --git a/src/datascience-ui/history-react/interactivePanel.tsx b/src/datascience-ui/history-react/interactivePanel.tsx index 7ce18dd0759d..6c7aa04b3f5f 100644 --- a/src/datascience-ui/history-react/interactivePanel.tsx +++ b/src/datascience-ui/history-react/interactivePanel.tsx @@ -216,6 +216,7 @@ export class InteractivePanel extends React.Component { monacoTheme={this.props.monacoTheme} font={this.props.font} settings={this.props.settings} + focusPending={this.props.activateCount} /> @@ -279,6 +280,7 @@ export class InteractivePanel extends React.Component { monacoTheme={this.props.monacoTheme} font={this.props.font} settings={this.props.settings} + focusPending={this.props.activateCount} /> diff --git a/src/datascience-ui/interactive-common/cellInput.tsx b/src/datascience-ui/interactive-common/cellInput.tsx index 5ff000057b6f..744fba7dcde6 100644 --- a/src/datascience-ui/interactive-common/cellInput.tsx +++ b/src/datascience-ui/interactive-common/cellInput.tsx @@ -26,6 +26,7 @@ interface ICellInputProps { editorMeasureClassName?: string; showLineNumbers?: boolean; font: IFont; + focusPending: number; onCodeChange(changes: monacoEditor.editor.IModelContentChange[], cellId: string, modelId: string): void; onCodeCreated(code: string, file: string, cellId: string, modelId: string): void; openLink(uri: monacoEditor.Uri): void; @@ -110,6 +111,7 @@ export class CellInput extends React.Component { showLineNumbers={this.props.showLineNumbers} useQuickEdit={this.props.cellVM.useQuickEdit} font={this.props.font} + focusPending={this.props.focusPending} /> ); diff --git a/src/datascience-ui/interactive-common/code.tsx b/src/datascience-ui/interactive-common/code.tsx index 2159047cb11a..4294a22fa343 100644 --- a/src/datascience-ui/interactive-common/code.tsx +++ b/src/datascience-ui/interactive-common/code.tsx @@ -26,6 +26,7 @@ export interface ICodeProps { font: IFont; hasFocus: boolean; cursorPos: CursorPos; + focusPending: number; onCreated(code: string, modelId: string): void; onChange(changes: monacoEditor.editor.IModelContentChange[], modelId: string): void; openLink(uri: monacoEditor.Uri): void; @@ -46,6 +47,12 @@ export class Code extends React.Component { this.state = { allowWatermark: true }; } + public componentDidUpdate(prevProps: ICodeProps) { + if (prevProps.focusPending !== this.props.focusPending) { + this.giveFocus(CursorPos.Current); + } + } + public render() { const readOnly = this.props.readOnly; const waterMarkClass = this.props.showWatermark && this.state.allowWatermark && !readOnly ? 'code-watermark' : 'hide'; diff --git a/src/datascience-ui/native-editor/nativeCell.tsx b/src/datascience-ui/native-editor/nativeCell.tsx index c3158de886b9..9414c2c8b3e9 100644 --- a/src/datascience-ui/native-editor/nativeCell.tsx +++ b/src/datascience-ui/native-editor/nativeCell.tsx @@ -42,6 +42,7 @@ interface INativeCellBaseProps { enableGather: boolean | undefined; editorOptions: monacoEditor.editor.IEditorOptions; themeMatplotlibPlots: boolean | undefined; + focusPending: number; } type INativeCellProps = INativeCellBaseProps & typeof actionCreators; @@ -597,6 +598,7 @@ export class NativeCell extends React.Component { keyDown={this.keyDownInput} showLineNumbers={this.props.cellVM.showLineNumbers} font={this.props.font} + focusPending={this.props.focusPending} /> ); diff --git a/src/datascience-ui/native-editor/nativeEditor.tsx b/src/datascience-ui/native-editor/nativeEditor.tsx index d099dc1136e1..f57998de1d8b 100644 --- a/src/datascience-ui/native-editor/nativeEditor.tsx +++ b/src/datascience-ui/native-editor/nativeEditor.tsx @@ -396,6 +396,7 @@ export class NativeEditor extends React.Component { editorOptions={this.props.editorOptions} enableGather={this.props.settings.enableGather} themeMatplotlibPlots={this.props.settings.themeMatplotlibPlots} + focusPending={this.props.activateCount} /> {lastLine} diff --git a/src/test/datascience/dataScienceIocContainer.ts b/src/test/datascience/dataScienceIocContainer.ts index 203ca7b6a742..ff30d3e10c2e 100644 --- a/src/test/datascience/dataScienceIocContainer.ts +++ b/src/test/datascience/dataScienceIocContainer.ts @@ -898,17 +898,7 @@ export class DataScienceIocContainer extends UnitTestIocContainer { } } - private findPythonPath(): string { - try { - // Give preference to the CI test python (could also be set in launch.json for debugging). - const output = child_process.execFileSync(process.env.CI_PYTHON_PATH || 'python', ['-c', 'import sys;print(sys.executable)'], { encoding: 'utf8' }); - return output.replace(/\r?\n/g, ''); - } catch (ex) { - return 'python'; - } - } - - private postMessageToWebPanel(msg: any) { + public postMessageToWebPanel(msg: any) { if (this.webPanelListener) { this.webPanelListener.onMessage(msg.type, msg.payload); } else { @@ -923,6 +913,16 @@ export class DataScienceIocContainer extends UnitTestIocContainer { } } + private findPythonPath(): string { + try { + // Give preference to the CI test python (could also be set in launch.json for debugging). + const output = child_process.execFileSync(process.env.CI_PYTHON_PATH || 'python', ['-c', 'import sys;print(sys.executable)'], { encoding: 'utf8' }); + return output.replace(/\r?\n/g, ''); + } catch (ex) { + return 'python'; + } + } + private mountReactControl(mount: () => ReactWrapper, React.Component>) { // This is a remount (or first time). Clear out messages that were sent // by the last mount diff --git a/src/test/datascience/interactiveWindow.functional.test.tsx b/src/test/datascience/interactiveWindow.functional.test.tsx index 6eb58ef0d44d..eaa78876de64 100644 --- a/src/test/datascience/interactiveWindow.functional.test.tsx +++ b/src/test/datascience/interactiveWindow.functional.test.tsx @@ -26,7 +26,7 @@ import { defaultDataScienceSettings } from './helpers'; import { addCode, getInteractiveCellResults, getOrCreateInteractiveWindow, runMountedTest } from './interactiveWindowTestHelpers'; import { MockDocumentManager } from './mockDocumentManager'; import { MockEditor } from './mockTextEditor'; -import { waitForUpdate } from './reactHelpers'; +import { createMessageEvent, waitForUpdate } from './reactHelpers'; import { addContinuousMockData, addInputMockData, @@ -36,9 +36,12 @@ import { enterInput, escapePath, findButton, + getInteractiveEditor, getLastOutputCell, srcDirectory, + submitInput, toggleCellExpansion, + typeCode, verifyHtmlOnCell, verifyLastCellInputState, waitForMessage, @@ -143,6 +146,47 @@ suite('DataScience Interactive Window output tests', () => { } ); + runMountedTest( + 'Ctrl + 1/Ctrl + 2', + async wrapper => { + // Create an interactive window so that it listens to the results. + const interactiveWindow = await getOrCreateInteractiveWindow(ioc); + await interactiveWindow.show(); + + // Type in the input box + const editor = getInteractiveEditor(wrapper); + typeCode(editor, 'a=1\na'); + + // Give focus to a random div + const reactDiv = wrapper + .find('div') + .first() + .getDOMNode(); + + const domDiv = reactDiv.querySelector('div'); + + if (domDiv && ioc.postMessage) { + domDiv.tabIndex = -1; + domDiv.focus(); + + // send the ctrl + 1/2 message, this should put focus back on the input box + const message = createMessageEvent({ type: InteractiveWindowMessages.Activate, payload: undefined }); + ioc.postMessage(message); + + // Then enter press shift + enter on the active element + const activeElement = document.activeElement; + if (activeElement) { + await submitInput(ioc, activeElement as HTMLTextAreaElement); + } + } + + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); + }, + () => { + return ioc; + } + ); + runMountedTest( 'Collapse / expand cell', async wrapper => { diff --git a/src/test/datascience/testHelpers.tsx b/src/test/datascience/testHelpers.tsx index 805e8d2a5745..3ba826d55c8a 100644 --- a/src/test/datascience/testHelpers.tsx +++ b/src/test/datascience/testHelpers.tsx @@ -471,7 +471,7 @@ export function simulateKey(domNode: HTMLTextAreaElement, key: string, shiftDown } } -async function submitInput(ioc: DataScienceIocContainer, textArea: HTMLTextAreaElement): Promise { +export async function submitInput(ioc: DataScienceIocContainer, textArea: HTMLTextAreaElement): Promise { // Get a render promise with the expected number of renders (how many updates a the shift + enter will cause) // Should be 6 - 1 for the shift+enter and 5 for the new cell. const renderPromise = waitForMessage(ioc, InteractiveWindowMessages.ExecutionRendered);