diff --git a/news/2 Fixes/10023.md b/news/2 Fixes/10023.md new file mode 100644 index 000000000000..d335b8eaf47b --- /dev/null +++ b/news/2 Fixes/10023.md @@ -0,0 +1 @@ +Jupyter autocompletion will only show magic commands on empty lines, preventing them of appearing in functions. diff --git a/src/client/datascience/interactive-common/intellisense/intellisenseProvider.ts b/src/client/datascience/interactive-common/intellisense/intellisenseProvider.ts index 9a4821ca2057..b9882cfa32d7 100644 --- a/src/client/datascience/interactive-common/intellisense/intellisenseProvider.ts +++ b/src/client/datascience/interactive-common/intellisense/intellisenseProvider.ts @@ -36,7 +36,8 @@ import { IInteractiveWindowListener, IInteractiveWindowProvider, IJupyterExecution, - INotebook + INotebook, + INotebookCompletion } from '../../types'; import { ICancelIntellisenseRequest, @@ -377,6 +378,7 @@ export class IntellisenseProvider implements IInteractiveWindowListener { request.cellId, cancelSource.token ); + const jupyterCompletions = this.provideJupyterCompletionItems( request.position, request.context, @@ -469,6 +471,8 @@ export class IntellisenseProvider implements IInteractiveWindowListener { const jupyterResults = await activeNotebook.getCompletion(data.text, offsetInCode, cancelToken); if (jupyterResults && jupyterResults.matches) { + const filteredMatches = this.filterJupyterMatches(document, jupyterResults, cellId, position); + const baseOffset = data.offset; const basePosition = document.positionAt(baseOffset); const startPosition = document.positionAt(jupyterResults.cursor.start + baseOffset); @@ -480,11 +484,7 @@ export class IntellisenseProvider implements IInteractiveWindowListener { endColumn: endPosition.character + 1 }; return { - suggestions: convertStringsToSuggestions( - jupyterResults.matches, - range, - jupyterResults.metadata - ), + suggestions: convertStringsToSuggestions(filteredMatches, range, jupyterResults.metadata), incomplete: false }; } @@ -502,6 +502,23 @@ export class IntellisenseProvider implements IInteractiveWindowListener { }; } + // The suggestions that the kernel is giving always include magic commands. That is confusing to the user. + // This function is called by provideJupyterCompletionItems to filter those magic commands when not in an empty line of code. + private filterJupyterMatches( + document: IntellisenseDocument, + jupyterResults: INotebookCompletion, + cellId: string, + position: monacoEditor.Position + ) { + // If the line we're analyzing is empty or a whitespace, we filter out the magic commands + // as its confusing to see them appear after a . or inside (). + const pos = document.convertToDocumentPosition(cellId, position.lineNumber, position.column); + const line = document.lineAt(pos); + return line.isEmptyOrWhitespace + ? jupyterResults.matches + : jupyterResults.matches.filter(match => !match.startsWith('%')); + } + private postTimedResponse( promises: Promise[], message: T, diff --git a/src/test/datascience/intellisense.functional.test.tsx b/src/test/datascience/intellisense.functional.test.tsx index bf4a8843ff4f..c8b781b263bb 100644 --- a/src/test/datascience/intellisense.functional.test.tsx +++ b/src/test/datascience/intellisense.functional.test.tsx @@ -12,7 +12,7 @@ import { MonacoEditor } from '../../datascience-ui/react-common/monacoEditor'; import { noop } from '../core'; import { DataScienceIocContainer } from './dataScienceIocContainer'; import { getOrCreateInteractiveWindow, runMountedTest } from './interactiveWindowTestHelpers'; -import { getInteractiveEditor, typeCode } from './testHelpers'; +import { enterEditorKey, getInteractiveEditor, typeCode } from './testHelpers'; // tslint:disable:max-func-body-length trailing-comma no-any no-multiline-string suite('DataScience Intellisense tests', () => { @@ -69,6 +69,14 @@ suite('DataScience Intellisense tests', () => { assert.ok(innerTexts.includes(expectedSpan), 'Intellisense row not matching'); } + function verifyIntellisenseNotVisible( + wrapper: ReactWrapper, React.Component>, + expectedSpan: string + ) { + const innerTexts = getIntellisenseTextLines(wrapper); + assert.ok(!innerTexts.includes(expectedSpan), 'Intellisense row is showing'); + } + function waitForSuggestion( wrapper: ReactWrapper, React.Component> ): { disposable: IDisposable; promise: Promise } { @@ -230,4 +238,60 @@ suite('DataScience Intellisense tests', () => { return ioc; } ); + + runMountedTest( + 'Filtered Jupyter autocomplete, verify magic commands appear', + async wrapper => { + if (ioc.mockJupyter) { + // This test only works when mocking. + + // Create an interactive window so that it listens to the results. + const interactiveWindow = await getOrCreateInteractiveWindow(ioc); + await interactiveWindow.show(); + + // Then enter some code. Don't submit, we're just testing that autocomplete appears + const suggestion = waitForSuggestion(wrapper); + typeCode(getInteractiveEditor(wrapper), 'print'); + enterEditorKey(wrapper, { code: ' ', ctrlKey: true }); + await suggestion.promise; + suggestion.disposable.dispose(); + verifyIntellisenseNotVisible(wrapper, '%%bash'); + + // Force suggestion box to disappear so that shutdown doesn't try to generate suggestions + // while we're destroying the editor. + clearEditor(wrapper); + } + }, + () => { + return ioc; + } + ); + + runMountedTest( + 'Filtered Jupyter autocomplete, verify magic commands are filtered', + async wrapper => { + if (ioc.mockJupyter) { + // This test only works when mocking. + + // Create an interactive window so that it listens to the results. + const interactiveWindow = await getOrCreateInteractiveWindow(ioc); + await interactiveWindow.show(); + + // Then enter some code. Don't submit, we're just testing that autocomplete appears + const suggestion = waitForSuggestion(wrapper); + typeCode(getInteractiveEditor(wrapper), ' '); + enterEditorKey(wrapper, { code: ' ', ctrlKey: true }); + await suggestion.promise; + suggestion.disposable.dispose(); + verifyIntellisenseVisible(wrapper, '%%bash'); + + // Force suggestion box to disappear so that shutdown doesn't try to generate suggestions + // while we're destroying the editor. + clearEditor(wrapper); + } + }, + () => { + return ioc; + } + ); }); diff --git a/src/test/datascience/mockJupyterSession.ts b/src/test/datascience/mockJupyterSession.ts index 1b6485006d44..16c0caf30608 100644 --- a/src/test/datascience/mockJupyterSession.ts +++ b/src/test/datascience/mockJupyterSession.ts @@ -142,7 +142,7 @@ export class MockJupyterSession implements IJupyterSession { return { content: { - matches: ['printly'], // This keeps this in the intellisense when the editor pairs down results + matches: ['printly', '%%bash'], // This keeps this in the intellisense when the editor pairs down results cursor_start: 0, cursor_end: 7, status: 'ok',