From 0a6ab0815b304542216b755cd8d1382cf3184fd1 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Fri, 22 Nov 2024 03:51:20 -0800 Subject: [PATCH 1/3] draft and start prompt native repl in terminal --- src/client/common/utils/localize.ts | 4 ++ src/client/extensionActivation.ts | 2 +- .../codeExecution/terminalCodeExecution.ts | 41 +++++++++++++++++-- .../codeExecution/terminalReplWatcher.ts | 16 +++++++- 4 files changed, 57 insertions(+), 6 deletions(-) diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index 3e11b1ca177b..2185fe363114 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -101,6 +101,10 @@ export namespace AttachProcess { export namespace Repl { export const disableSmartSend = l10n.t('Disable Smart Send'); + // TODO: get feedback on text message below: + export const terminalSuggestNativeReplPrompt = l10n.t( + 'The Python extension now includes an editor based native Python REPL with Intellisense, syntax highlighting. Would you like to try this out?', + ); } export namespace Pylance { export const remindMeLater = l10n.t('Remind me later'); diff --git a/src/client/extensionActivation.ts b/src/client/extensionActivation.ts index 38f2d6a56277..f2eb0d5e6b64 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -111,7 +111,7 @@ export function activateFeatures(ext: ExtensionState, _components: Components): ); const executionHelper = ext.legacyIOC.serviceContainer.get(ICodeExecutionHelper); const commandManager = ext.legacyIOC.serviceContainer.get(ICommandManager); - registerTriggerForTerminalREPL(ext.disposables); + registerTriggerForTerminalREPL(commandManager, ext.disposables); registerStartNativeReplCommand(ext.disposables, interpreterService); registerReplCommands(ext.disposables, interpreterService, executionHelper, commandManager); registerReplExecuteOnEnter(ext.disposables, interpreterService, commandManager); diff --git a/src/client/terminals/codeExecution/terminalCodeExecution.ts b/src/client/terminals/codeExecution/terminalCodeExecution.ts index ea444af4d89e..b6bdb90b23a0 100644 --- a/src/client/terminals/codeExecution/terminalCodeExecution.ts +++ b/src/client/terminals/codeExecution/terminalCodeExecution.ts @@ -5,20 +5,23 @@ import { inject, injectable } from 'inversify'; import * as path from 'path'; -import { Disposable, Uri } from 'vscode'; +import { Disposable, TerminalShellExecutionStartEvent, Uri } from 'vscode'; import { IApplicationShell, ICommandManager, IWorkspaceService } from '../../common/application/types'; import '../../common/extensions'; import { IPlatformService } from '../../common/platform/types'; import { ITerminalService, ITerminalServiceFactory } from '../../common/terminal/types'; import { IConfigurationService, IDisposable, IDisposableRegistry, Resource } from '../../common/types'; -import { Diagnostics, Repl } from '../../common/utils/localize'; -import { showWarningMessage } from '../../common/vscodeApis/windowApis'; +import { Common, Diagnostics, Repl } from '../../common/utils/localize'; +import { onDidStartTerminalShellExecution, showWarningMessage } from '../../common/vscodeApis/windowApis'; import { IInterpreterService } from '../../interpreter/contracts'; import { traceInfo } from '../../logging'; import { buildPythonExecInfo, PythonExecInfo } from '../../pythonEnvironments/exec'; import { ICodeExecutionService } from '../../terminals/types'; import { EventName } from '../../telemetry/constants'; import { sendTelemetryEvent } from '../../telemetry'; +import { getActiveInterpreter } from '../../repl/replUtils'; +import { getNativeRepl } from '../../repl/nativeRepl'; +import { checkREPLCommand } from './terminalReplWatcher'; @injectable() export class TerminalCodeExecutionProvider implements ICodeExecutionService { @@ -63,11 +66,43 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { } } + public suggestNativeRepl(resource: Resource) { + this.disposables.push( + onDidStartTerminalShellExecution(async (e: TerminalShellExecutionStartEvent) => { + if (e.execution.commandLine.isTrusted && checkREPLCommand(e.execution.commandLine.value)) { + sendTelemetryEvent(EventName.REPL, undefined, { replType: 'manualTerminal' }); + const selection = await showWarningMessage( + Repl.terminalSuggestNativeReplPrompt, + Common.doNotShowAgain, + ); + if (selection === Repl.terminalSuggestNativeReplPrompt) { + sendTelemetryEvent(EventName.REPL, undefined, { replType: 'Native' }); + const interpreter = await getActiveInterpreter(resource as Uri, this.interpreterService); + if (interpreter) { + const nativeRepl = await getNativeRepl(interpreter, this.disposables); + await nativeRepl.sendToNativeRepl(undefined, false); + } + } + } + }), + ); + } + public async initializeRepl(resource: Resource) { const terminalService = this.getTerminalService(resource); + if (!this.replActive) { + this.suggestNativeRepl(resource); + } if (this.replActive && (await this.replActive)) { await terminalService.show(); return; + } else { + // Suggest launch of Native REPL + const interpreter = await getActiveInterpreter(resource!, this.interpreterService); + if (interpreter) { + const nativeRepl = await getNativeRepl(interpreter, this.disposables); + await nativeRepl.sendToNativeRepl(undefined, false); + } } sendTelemetryEvent(EventName.REPL, undefined, { replType: 'Terminal' }); this.replActive = new Promise(async (resolve) => { diff --git a/src/client/terminals/codeExecution/terminalReplWatcher.ts b/src/client/terminals/codeExecution/terminalReplWatcher.ts index bab70cb2f654..502dedf018f3 100644 --- a/src/client/terminals/codeExecution/terminalReplWatcher.ts +++ b/src/client/terminals/codeExecution/terminalReplWatcher.ts @@ -2,17 +2,29 @@ import { Disposable, TerminalShellExecutionStartEvent } from 'vscode'; import { onDidStartTerminalShellExecution } from '../../common/vscodeApis/windowApis'; import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; +import { ICommandManager } from '../../common/application/types'; +import { Commands } from '../../common/constants'; -function checkREPLCommand(command: string): boolean { +export function checkREPLCommand(command: string): boolean { const lower = command.toLowerCase().trimStart(); return lower.startsWith('python') || lower.startsWith('py '); } -export function registerTriggerForTerminalREPL(disposables: Disposable[]): void { +export async function registerTriggerForTerminalREPL( + commandManager: ICommandManager, + disposables: Disposable[], +): Promise { disposables.push( onDidStartTerminalShellExecution(async (e: TerminalShellExecutionStartEvent) => { if (e.execution.commandLine.isTrusted && checkREPLCommand(e.execution.commandLine.value)) { sendTelemetryEvent(EventName.REPL, undefined, { replType: 'manualTerminal' }); + // TODO: Prompt user to start Native REPL + + // If yes, then launch native REPL + await commandManager.executeCommand(Commands.Start_Native_REPL, undefined); + + // TODO: Decide whether we want everytime, or once per workspace, or once per terminal + // How do I even track all terminal instances. } }), ); From 09c6052ab25048292880a3441547c5b9744c1430 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Fri, 22 Nov 2024 14:05:53 -0800 Subject: [PATCH 2/3] add global native repl suggestion tracker. TODO: workspace tracker --- src/client/extensionActivation.ts | 2 +- .../codeExecution/terminalReplWatcher.ts | 31 +++++++++++++++---- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/client/extensionActivation.ts b/src/client/extensionActivation.ts index f2eb0d5e6b64..b57eacd2159b 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -111,7 +111,7 @@ export function activateFeatures(ext: ExtensionState, _components: Components): ); const executionHelper = ext.legacyIOC.serviceContainer.get(ICodeExecutionHelper); const commandManager = ext.legacyIOC.serviceContainer.get(ICommandManager); - registerTriggerForTerminalREPL(commandManager, ext.disposables); + registerTriggerForTerminalREPL(commandManager, ext.context, ext.disposables); registerStartNativeReplCommand(ext.disposables, interpreterService); registerReplCommands(ext.disposables, interpreterService, executionHelper, commandManager); registerReplExecuteOnEnter(ext.disposables, interpreterService, commandManager); diff --git a/src/client/terminals/codeExecution/terminalReplWatcher.ts b/src/client/terminals/codeExecution/terminalReplWatcher.ts index 502dedf018f3..7da36d89bc13 100644 --- a/src/client/terminals/codeExecution/terminalReplWatcher.ts +++ b/src/client/terminals/codeExecution/terminalReplWatcher.ts @@ -1,9 +1,14 @@ import { Disposable, TerminalShellExecutionStartEvent } from 'vscode'; -import { onDidStartTerminalShellExecution } from '../../common/vscodeApis/windowApis'; +import { onDidStartTerminalShellExecution, showWarningMessage } from '../../common/vscodeApis/windowApis'; import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; import { ICommandManager } from '../../common/application/types'; import { Commands } from '../../common/constants'; +import { Common, Repl } from '../../common/utils/localize'; +import { IExtensionContext } from '../../common/types'; +import { getGlobalStorage } from '../../common/persistentState'; + +export const SUGGEST_NATIVE_REPL = 'suggestNativeRepl'; export function checkREPLCommand(command: string): boolean { const lower = command.toLowerCase().trimStart(); @@ -12,19 +17,33 @@ export function checkREPLCommand(command: string): boolean { export async function registerTriggerForTerminalREPL( commandManager: ICommandManager, + context: IExtensionContext, disposables: Disposable[], ): Promise { disposables.push( onDidStartTerminalShellExecution(async (e: TerminalShellExecutionStartEvent) => { if (e.execution.commandLine.isTrusted && checkREPLCommand(e.execution.commandLine.value)) { sendTelemetryEvent(EventName.REPL, undefined, { replType: 'manualTerminal' }); - // TODO: Prompt user to start Native REPL - // If yes, then launch native REPL - await commandManager.executeCommand(Commands.Start_Native_REPL, undefined); + // Plan: + // Global memento to disable show of prompt entirely - global memento + // workspace memento to track and only show suggest of native REPL once. + const globalSuggestNativeRepl = getGlobalStorage(context, SUGGEST_NATIVE_REPL); + if (globalSuggestNativeRepl.get()) { + // Prompt user to start Native REPL + const selection = await showWarningMessage( + Repl.terminalSuggestNativeReplPrompt, + 'Launch Native REPL', + Common.doNotShowAgain, + ); - // TODO: Decide whether we want everytime, or once per workspace, or once per terminal - // How do I even track all terminal instances. + if (selection === 'Launch Native REPL') { + await commandManager.executeCommand(Commands.Start_Native_REPL, undefined); + } else { + // Update global suggest to disable Native REPL suggestion in future even after reload. + await globalSuggestNativeRepl.set(false); + } + } } }), ); From c225f6aec61f3900b3c8c01e5fca2a4e2f3cab5b Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Fri, 22 Nov 2024 14:20:51 -0800 Subject: [PATCH 3/3] add workspace level too --- .../terminals/codeExecution/terminalReplWatcher.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/client/terminals/codeExecution/terminalReplWatcher.ts b/src/client/terminals/codeExecution/terminalReplWatcher.ts index 7da36d89bc13..76629c792a54 100644 --- a/src/client/terminals/codeExecution/terminalReplWatcher.ts +++ b/src/client/terminals/codeExecution/terminalReplWatcher.ts @@ -6,7 +6,7 @@ import { ICommandManager } from '../../common/application/types'; import { Commands } from '../../common/constants'; import { Common, Repl } from '../../common/utils/localize'; import { IExtensionContext } from '../../common/types'; -import { getGlobalStorage } from '../../common/persistentState'; +import { getGlobalStorage, getWorkspaceStateValue, updateWorkspaceStateValue } from '../../common/persistentState'; export const SUGGEST_NATIVE_REPL = 'suggestNativeRepl'; @@ -20,6 +20,8 @@ export async function registerTriggerForTerminalREPL( context: IExtensionContext, disposables: Disposable[], ): Promise { + // When extension reloads via user triggering reloading of VS Code, reset to suggest Native REPL on workspace level. + await updateWorkspaceStateValue(SUGGEST_NATIVE_REPL, true); disposables.push( onDidStartTerminalShellExecution(async (e: TerminalShellExecutionStartEvent) => { if (e.execution.commandLine.isTrusted && checkREPLCommand(e.execution.commandLine.value)) { @@ -28,8 +30,9 @@ export async function registerTriggerForTerminalREPL( // Plan: // Global memento to disable show of prompt entirely - global memento // workspace memento to track and only show suggest of native REPL once. - const globalSuggestNativeRepl = getGlobalStorage(context, SUGGEST_NATIVE_REPL); - if (globalSuggestNativeRepl.get()) { + const globalSuggestNativeRepl = getGlobalStorage(context, SUGGEST_NATIVE_REPL, true); + const workspaceSuggestNativeRepl = getWorkspaceStateValue(SUGGEST_NATIVE_REPL, true); + if (globalSuggestNativeRepl.get() && workspaceSuggestNativeRepl) { // Prompt user to start Native REPL const selection = await showWarningMessage( Repl.terminalSuggestNativeReplPrompt, @@ -44,6 +47,8 @@ export async function registerTriggerForTerminalREPL( await globalSuggestNativeRepl.set(false); } } + // Update workspace native repl suggestion value after the first 'python' in terminal. + await updateWorkspaceStateValue(SUGGEST_NATIVE_REPL, false); } }), );