diff --git a/extensions/positron-python/src/client/application/diagnostics/checks/unsupportedPythonVersion.ts b/extensions/positron-python/src/client/application/diagnostics/checks/unsupportedPythonVersion.ts index ddb52762286..56d8b2b93a8 100644 --- a/extensions/positron-python/src/client/application/diagnostics/checks/unsupportedPythonVersion.ts +++ b/extensions/positron-python/src/client/application/diagnostics/checks/unsupportedPythonVersion.ts @@ -126,7 +126,7 @@ export class UnsupportedPythonVersionService extends BaseDiagnosticsService { prompt: Common.selectNewSession, command: commandFactory.createCommand(diagnostic, { type: 'executeVSCCommand', - options: 'workbench.action.language.runtime.openActivePicker', + options: 'workbench.action.language.runtime.selectSession', }), }, { diff --git a/extensions/positron-python/src/client/common/application/commands.ts b/extensions/positron-python/src/client/common/application/commands.ts index 3959d30227e..397f8a30203 100644 --- a/extensions/positron-python/src/client/common/application/commands.ts +++ b/extensions/positron-python/src/client/common/application/commands.ts @@ -44,7 +44,7 @@ interface ICommandNameWithoutArgumentTypeMapping { // --- Start Positron --- [Commands.Show_Interpreter_Debug_Info]: []; // New command that opens the multisession interpreter picker - ['workbench.action.language.runtime.openActivePicker']: []; + ['workbench.action.language.runtime.selectSession']: []; // --- End Positron --- } diff --git a/src/vs/workbench/browser/parts/positronTopActionBar/components/topActionBarSessionManager.tsx b/src/vs/workbench/browser/parts/positronTopActionBar/components/topActionBarSessionManager.tsx index 8fc4d764d24..d6c3ef8fa5a 100644 --- a/src/vs/workbench/browser/parts/positronTopActionBar/components/topActionBarSessionManager.tsx +++ b/src/vs/workbench/browser/parts/positronTopActionBar/components/topActionBarSessionManager.tsx @@ -17,6 +17,7 @@ import { ActionBarCommandButton } from '../../../../../platform/positronActionBa import { CommandCenter } from '../../../../../platform/commandCenter/common/commandCenter.js'; import { ILanguageRuntimeSession } from '../../../../services/runtimeSession/common/runtimeSessionService.js'; import { localize } from '../../../../../nls.js'; +import { LANGUAGE_RUNTIME_SELECT_SESSION_ID, LANGUAGE_RUNTIME_START_NEW_SESSION_ID } from '../../../../contrib/languageRuntime/browser/languageRuntimeActions.js'; const startSession = localize('positron.console.startSession', "Start Session"); @@ -38,8 +39,8 @@ export const TopActionBarSessionManager = () => { const hasActiveConsoleSessions = context.runtimeSessionService.activeSessions.find( session => session.metadata.sessionMode === LanguageRuntimeSessionMode.Console); const command = hasActiveConsoleSessions - ? 'workbench.action.language.runtime.openActivePicker' - : 'workbench.action.language.runtime.openStartPicker'; + ? LANGUAGE_RUNTIME_SELECT_SESSION_ID + : LANGUAGE_RUNTIME_START_NEW_SESSION_ID; // Main useEffect. useEffect(() => { diff --git a/src/vs/workbench/common/contextkeys.ts b/src/vs/workbench/common/contextkeys.ts index 9e8e1991848..0d531cafc58 100644 --- a/src/vs/workbench/common/contextkeys.ts +++ b/src/vs/workbench/common/contextkeys.ts @@ -128,6 +128,7 @@ export const PositronTopActionBarFocused = new RawContextKey('positronT export const PositronTopActionBarVisibleContext = new RawContextKey('positronTopActionBarVisible', true, localize('positronTopActionBarVisible', "Whether Positron Top Action Bar is visible")); export const PositronConsoleFocused = new RawContextKey('positronConsoleFocused', false, localize('positronConsoleFocused', "Whether Positron Console has keyboard focus")); export const PositronConsoleTabFocused = new RawContextKey('positronConsoleTabFocused', false, localize('positronConsoleTabFocused', "Whether Positron Console Tab has keyboard focus")); +export const PositronConsoleInstancesExistContext = new RawContextKey('positronConsoleInstancesExist', false, localize('positronConsoleInstancesExist', "Whether any Positron Console instances exist")); export const PositronVariablesFocused = new RawContextKey('positronVariablesFocused', false, localize('positronVariablesFocused', "Whether Positron Variables has keyboard focus")); export const PositronHelpFocused = new RawContextKey('positronHelpFocused', false, localize('positronHelpFocused', "Whether Positron Help has keyboard focus")); export const PositronDataExplorerFocused = new RawContextKey('positronDataExplorerFocused', false, localize('positronDataExplorerFocused', "Whether Positron Data Explorer has keyboard focus")); diff --git a/src/vs/workbench/contrib/languageRuntime/browser/languageRuntimeActions.ts b/src/vs/workbench/contrib/languageRuntime/browser/languageRuntimeActions.ts index 2506dfec3ad..fb83540dee6 100644 --- a/src/vs/workbench/contrib/languageRuntime/browser/languageRuntimeActions.ts +++ b/src/vs/workbench/contrib/languageRuntime/browser/languageRuntimeActions.ts @@ -9,7 +9,7 @@ import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { ILocalizedString } from '../../../../platform/action/common/action.js'; import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; -import { IQuickInputService, IQuickPickItem, IQuickPickSeparator, QuickPickItem } from '../../../../platform/quickinput/common/quickInput.js'; +import { IQuickInputService, IQuickPickItem, QuickPickItem } from '../../../../platform/quickinput/common/quickInput.js'; import { IKeybindingRule, KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; import { LANGUAGE_RUNTIME_ACTION_CATEGORY } from '../common/languageRuntime.js'; import { IPositronConsoleService, POSITRON_CONSOLE_VIEW_ID } from '../../../services/positronConsole/browser/interfaces/positronConsoleService.js'; @@ -17,7 +17,6 @@ import { ILanguageRuntimeMetadata, ILanguageRuntimeService, LanguageRuntimeSessi import { ILanguageRuntimeSession, IRuntimeClientInstance, IRuntimeSessionService, RuntimeClientType, RuntimeStartMode } from '../../../services/runtimeSession/common/runtimeSessionService.js'; import { INotificationService } from '../../../../platform/notification/common/notification.js'; import { ILanguageService } from '../../../../editor/common/languages/language.js'; -import { groupBy } from '../../../../base/common/collections.js'; import { IRuntimeStartupService } from '../../../services/runtimeStartup/common/runtimeStartupService.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { dispose } from '../../../../base/common/lifecycle.js'; @@ -28,7 +27,7 @@ import { IFileDialogService } from '../../../../platform/dialogs/common/dialogs. import { Codicon } from '../../../../base/common/codicons.js'; import { localize } from '../../../../nls.js'; import { CodeAttributionSource, IConsoleCodeAttribution } from '../../../services/positronConsole/common/positronConsoleCodeExecution.js'; -import { PositronConsoleTabFocused } from '../../../common/contextkeys.js'; +import { PositronConsoleInstancesExistContext, PositronConsoleTabFocused } from '../../../common/contextkeys.js'; // The category for language runtime actions. const category: ILocalizedString = { value: LANGUAGE_RUNTIME_ACTION_CATEGORY, original: 'Interpreter' }; @@ -39,11 +38,14 @@ interface RuntimeClientTypeQuickPickItem extends IQuickPickItem { runtimeClientT interface RuntimeClientInstanceQuickPickItem extends IQuickPickItem { runtimeClientInstance: IRuntimeClientInstance } // Action IDs -export const LANGUAGE_RUNTIME_OPEN_ACTIVE_SESSIONS_ID = 'workbench.action.language.runtime.openActivePicker'; -export const LANGUAGE_RUNTIME_START_SESSION_ID = 'workbench.action.language.runtime.openStartPicker'; +export const LANGUAGE_RUNTIME_SELECT_SESSION_ID = 'workbench.action.language.runtime.selectSession'; +export const LANGUAGE_RUNTIME_START_NEW_SESSION_ID = 'workbench.action.language.runtime.startNewSession'; +export const LANGUAGE_RUNTIME_RESTART_ACTIVE_SESSION_ID = 'workbench.action.language.runtime.restartActiveSession'; export const LANGUAGE_RUNTIME_RENAME_SESSION_ID = 'workbench.action.language.runtime.renameSession'; export const LANGUAGE_RUNTIME_RENAME_ACTIVE_SESSION_ID = 'workbench.action.language.runtime.renameActiveSession'; -export const LANGUAGE_RUNTIME_DUPLICATE_SESSION_ID = 'workbench.action.language.runtime.duplicateSession'; +export const LANGUAGE_RUNTIME_DUPLICATE_ACTIVE_SESSION_ID = 'workbench.action.language.runtime.duplicateActiveSession'; + +export const LANGUAGE_RUNTIME_SELECT_RUNTIME_ID = 'workbench.action.languageRuntime.selectRuntime'; /** * Helper function that askses the user to select a language from the list of registered language @@ -103,18 +105,19 @@ async function selectLanguage(accessor: ServicesAccessor) { /** * Helper function that asks the user to select a language runtime session from - * an array of language runtime sessions. + * an array of existing language runtime sessions. * - * @param quickInputService The quick input service. - * @param sessions The language runtime sessions the user can select from. - * @param placeHolder The placeholder for the quick input. + * @param accessor The service accessor. + * @param options The options for the quick pick. + * @param options.allowStartSession Whether to allow the user to start a new session. + * @param options.title The title of the quick pick. * @returns The runtime session the user selected, or undefined, if the user canceled the operation. */ -export const selectLanguageRuntimeSession = async ( +const selectLanguageRuntimeSession = async ( accessor: ServicesAccessor, options?: { - placeholder?: string; allowStartSession?: boolean; + title?: string; }): Promise => { // Constants @@ -167,7 +170,7 @@ export const selectLanguageRuntimeSession = async ( // Show quick pick to select an active runtime or show all runtimes. const quickPickItems: QuickPickItem[] = [ { - label: localize('positron.languageRuntime.activeSessions', 'Active Sessions'), + label: localize('positron.languageRuntime.activeSessions', 'Active Interpreter Sessions'), type: 'separator', }, ...activeRuntimeItems, @@ -178,13 +181,13 @@ export const selectLanguageRuntimeSession = async ( if (options?.allowStartSession) { quickPickItems.push({ - label: localize('positron.languageRuntime.newSession', 'New Session...'), + label: localize('positron.languageRuntime.newSession', 'New Interpreter Session...'), id: startNewRuntimeId, alwaysShow: true }); } const result = await quickInputService.pick(quickPickItems, { - title: localize('positron.languageRuntime.selectSession', 'Select a Session'), + title: options?.title || localize('positron.languageRuntime.selectSession', 'Select Interpreter Session'), canPickMany: false, activeItem: activeRuntimeItems.filter(item => item.picked)[0] }); @@ -192,7 +195,7 @@ export const selectLanguageRuntimeSession = async ( // Handle the user's selection. if (result?.id === startNewRuntimeId) { // If the user selected "All Runtimes...", execute the command to show all runtimes. - const sessionId: string | undefined = await commandService.executeCommand(LANGUAGE_RUNTIME_START_SESSION_ID); + const sessionId: string | undefined = await commandService.executeCommand(LANGUAGE_RUNTIME_START_NEW_SESSION_ID); if (sessionId) { return runtimeSessionService.activeSessions.find(session => session.sessionId === sessionId); } @@ -204,123 +207,6 @@ export const selectLanguageRuntimeSession = async ( return undefined; }; -/** - * Helper function that asks the user to select a registered language runtime for a language. - * - * @param accessor The service accessor. - * @param languageId The language ID of the language runtimes to select from. - * - * @returns The language runtime the user selected, or undefined, if the user canceled the operation. - */ -const selectLanguageRuntime = async ( - accessor: ServicesAccessor, - languageId: string, - preferredRuntime: ILanguageRuntimeMetadata | undefined, -): Promise => { - - const quickInputService = accessor.get(IQuickInputService); - const languageRuntimeService = accessor.get(ILanguageRuntimeService); - const languageService = accessor.get(ILanguageService); - - // Prompt the user to select a language runtime. - return new Promise((resolve) => { - const input = quickInputService.createQuickPick( - { useSeparators: true } - ); - const runtimePicks = new Map(); - - const addRuntime = (runtimeMetadata: ILanguageRuntimeMetadata) => { - runtimePicks.set(runtimeMetadata.runtimeId, { - id: runtimeMetadata.runtimeId, - label: runtimeMetadata.runtimeName, - description: runtimeMetadata.runtimePath, - runtime: runtimeMetadata - }); - - // Update the quick pick items. - const runtimePicksBySource = groupBy(Array.from(runtimePicks.values()), pick => pick.runtime.runtimeSource); - const sortedSources = Object.keys(runtimePicksBySource).sort(); - const picks = new Array(); - for (const source of sortedSources) { - picks.push({ label: source, type: 'separator' }, ...runtimePicksBySource[source]); - } - input.items = picks; - }; - - const disposables = [ - input, - input.onDidAccept(() => { - resolve(input.activeItems[0]?.runtime); - input.hide(); - }), - input.onDidHide(() => { - dispose(disposables); - resolve(undefined); - }), - languageRuntimeService.onDidRegisterRuntime((runtimeMetadata) => { - if (runtimeMetadata.languageId === languageId) { - addRuntime(runtimeMetadata); - } - }), - ]; - - input.canSelectMany = false; - const languageName = languageService.getLanguageName(languageId); - input.title = nls.localize('positron.languageRuntime.select.selectInterpreter', 'Select {0} Interpreter', languageName); - input.matchOnDescription = true; - - for (const runtimeMetadata of languageRuntimeService.registeredRuntimes) { - if (runtimeMetadata.languageId === languageId) { - addRuntime(runtimeMetadata); - } - } - - if (preferredRuntime) { - input.placeholder = nls.localize('positron.languageRuntime.select.selectedInterpreer', 'Selected Interpreter: {0}', preferredRuntime.runtimeName); - const activeItem = runtimePicks.get(preferredRuntime.runtimeId); - if (activeItem) { - input.activeItems = [activeItem]; - } - } - - input.show(); - - }); -}; - -/** - * Helper function that asks the user to select a running language runtime, if no runtime is - * currently marked as the active runtime. - * - * @param accessor The service accessor. - * @param placeholder The placeholder for the quick input. - * @returns The language runtime the user selected, or undefined, if there are no running language runtimes or the user canceled the operation. - */ -const selectRunningLanguageRuntime = async ( - accessor: ServicesAccessor, - placeholder: string): Promise => { - - // If there's an active language runtime, use that. - // NOTE @samclark2015: Does this even do anything with Multisession??? - // e.g. when would we have sessions running but without an active foreground session? - const runtimeSessionService = accessor.get(IRuntimeSessionService); - const activeSession = runtimeSessionService.foregroundSession; - if (activeSession) { - return activeSession; - } - - // If there isn't an active language runtime, but there are running - // runtimes, ask the user to select one. - const activeSessions = runtimeSessionService.activeSessions; - if (!activeSessions.length) { - alert('No interpreters are currently running.'); - return undefined; - } - - // As the user to select the running language runtime. - return await selectLanguageRuntimeSession(accessor, { placeholder }); -}; - /** * IInterpreterGroup interface. */ @@ -381,6 +267,15 @@ const createInterpreterGroups = ( }); }; +/** + * Helper function that asks the user to select a language runtime from + * the list of registered language runtimes. + * + * This can be used to start a session for a registered language runtime. + * + * @param accessor The service accessor. + * @returns + */ const selectNewLanguageRuntime = async ( accessor: ServicesAccessor ): Promise => { @@ -512,12 +407,12 @@ const selectNewLanguageRuntime = async ( const selectedRuntime = await quickInputService.pick( runtimeItems, { - title: localize('positron.languageRuntime.startSession', 'Start a New Session'), + title: localize('positron.languageRuntime.startSession', 'Start New Interpreter Session'), canPickMany: false } ); - // If the user selected a runtime, set it as the active runtime + // If the user selected a runtime, return the runtime metadata. if (selectedRuntime?.id) { return languageRuntimeService.getRegisteredRuntime(selectedRuntime.id); } @@ -525,6 +420,45 @@ const selectNewLanguageRuntime = async ( return undefined; }; +/** + * Helper function to rename a session. + * + * @param accessor The service accessor. + * @param sessionId The ID of the session to rename. + */ +const renameLanguageRuntimeSession = async ( + sessionService: IRuntimeSessionService, + notificationService: INotificationService, + quickInputService: IQuickInputService, + sessionId: string +) => { + // Prompt the user to enter the new session name. + const sessionName = await quickInputService.input({ + value: '', + placeHolder: '', + prompt: nls.localize('positron.console.renameSession.prompt', "Enter the new session name"), + }); + + // Validate the new session name + const newSessionName = sessionName?.trim(); + if (!newSessionName?.trim()) { + return; + } + + // Attempt to rename the session. + try { + sessionService.updateSessionName(sessionId, newSessionName); + } catch (error) { + notificationService.error( + localize('positron.console.renameSession.error', + "Failed to rename session {0}: {1}", + sessionId, + error + ) + ); + } +}; + /** * Registers language runtime actions. */ @@ -563,11 +497,16 @@ export function registerLanguageRuntimeActions() { }); }; + /** + * Action used to select a registered language runtime (aka interpreter). + * + * NOTE: This is a convenience action that is used by the notebook services + */ registerAction2(class PickInterpreterAction extends Action2 { constructor() { super({ - id: 'workbench.action.languageRuntime.pick', - title: nls.localize2('positron.command.pickInterpreter', "Pick Interpreter"), + id: LANGUAGE_RUNTIME_SELECT_RUNTIME_ID, + title: nls.localize2('positron.command.selectInterpreter', "Select Interpreter"), f1: false, category, }); @@ -579,148 +518,10 @@ export function registerLanguageRuntimeActions() { } }); - // Registers the start language runtime action. - registerAction2(class SelectInterpreterAction extends Action2 { - constructor() { - super({ - id: 'workbench.action.languageRuntime.select', - title: nls.localize2('positron.command.selectInterpreter', "Select Interpreter"), - f1: false, - category, - }); - } - - async run(accessor: ServicesAccessor, languageId: string) { - // Access services. - const commandService = accessor.get(ICommandService); - const runtimeSessionService = accessor.get(IRuntimeSessionService); - const runtimeStartupService = accessor.get(IRuntimeStartupService); - - // Ask the user to select the language runtime to start. If they selected one, start it. - let preferredRuntime: ILanguageRuntimeMetadata | undefined; - try { - preferredRuntime = runtimeStartupService.getPreferredRuntime(languageId); - } catch { - // getPreferredRuntime can error if a workspace-affiliated runtime is not - // yet registered. Do nothing. - } - const languageRuntime = await selectLanguageRuntime(accessor, languageId, preferredRuntime); - - if (languageRuntime) { - // Start the language runtime. - runtimeSessionService.selectRuntime(languageRuntime.runtimeId, `'Select Interpreter' command invoked`); - - // Drive focus into the Positron console. - commandService.executeCommand('workbench.panel.positronConsole.focus'); - } - } - }); - - // Registers the set active language runtime action. - registerLanguageRuntimeAction('workbench.action.languageRuntime.setActive', 'Set Active Interpreter', async accessor => { - // Get the language runtime service. - const runtimeSessionService = accessor.get(IRuntimeSessionService); - - // Have the user select the language runtime they wish to set as the active language runtime. - const session = await selectRunningLanguageRuntime( - accessor, - localize('positron.lanuageRuntime.setActive', 'Set the active language runtime')); - - // If the user selected a language runtime, set it as the active language runtime. - if (session) { - runtimeSessionService.foregroundSession = session; - } - }); - - // Registers the restart language runtime action. - registerLanguageRuntimeAction('workbench.action.languageRuntime.restart', 'Restart Interpreter', async accessor => { - // Access services. - const consoleService = accessor.get(IPositronConsoleService); - const runtimeSessionService = accessor.get(IRuntimeSessionService); - - // The runtime we'll try to restart. - let session: ILanguageRuntimeSession | undefined = undefined; - - // Typically, the restart command should act on the language runtime - // that's active in the Console, so try that first. - const activeConsole = consoleService.activePositronConsoleInstance; - if (activeConsole) { - session = activeConsole.attachedRuntimeSession; - } - - // If there's no active console, try the active language runtime. - if (!session) { - session = runtimeSessionService.foregroundSession; - } - - // If we still don't have an active language runtime, ask the user to - // pick one. - if (!session) { - session = await selectRunningLanguageRuntime( - accessor, - localize( - 'positron.languageRuntime.selectInterpreterRestart', - 'Select the interpreter to restart' - ) - ); - if (!session) { - throw new Error('No interpreter selected'); - } - } - - // Restart the language runtime. - runtimeSessionService.restartSession(session.sessionId, - `'Restart Interpreter' command invoked`); - }, - [ - { - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Numpad0, - secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.F10] - }, - { - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Digit0 - }, - ] - ); - - // Registers the interrupt language runtime action. - registerLanguageRuntimeAction('workbench.action.languageRuntime.interrupt', 'Interrupt Interpreter', async accessor => { - (await selectRunningLanguageRuntime( - accessor, - 'Select the interpreter to interrupt'))?.interrupt(); - }); - - // Registers the shutdown language runtime action. - registerLanguageRuntimeAction('workbench.action.languageRuntime.shutdown', 'Shutdown Interpreter', async accessor => { - (await selectRunningLanguageRuntime( - accessor, - 'Select the interpreter to shutdown'))?.shutdown(); - }); - - // Registers the force quit language runtime action. - registerLanguageRuntimeAction('workbench.action.languageRuntime.forceQuit', 'Force Quit Interpreter', async accessor => { - (await selectRunningLanguageRuntime( - accessor, - 'Select the interpreter to force-quit'))?.forceQuit(); - }); - - // Registers the show output language runtime action. - registerLanguageRuntimeAction('workbench.action.languageRuntime.showOutput', 'Show interpreter output', async accessor => { - (await selectRunningLanguageRuntime( - accessor, - 'Select the interpreter for which to show output'))?.showOutput(); - }); - - registerLanguageRuntimeAction('workbench.action.languageRuntime.showProfile', 'Show interpreter profile report', async accessor => { - (await selectRunningLanguageRuntime( - accessor, - 'Select the interpreter for which to show profile output'))?.showProfile(); - }); - - // Registers the clear affiliated language runtime / clear saved interpreter action. - registerLanguageRuntimeAction('workbench.action.languageRuntime.clearAffiliation', 'Clear Saved Interpreter', async accessor => { + /** + * Action that allows the user to remove a runtime from the list of offiliated runtimes. + */ + registerLanguageRuntimeAction('workbench.action.languageRuntime.clearAffiliatedRuntime', 'Clear Saved Interpreter', async accessor => { const runtimeSessionService = accessor.get(IRuntimeStartupService); const quickInputService = accessor.get(IQuickInputService); const notificationService = accessor.get(INotificationService); @@ -756,74 +557,10 @@ export function registerLanguageRuntimeActions() { notificationService.info(nls.localize('interpreterCleared', 'The {0} interpreter has been cleared from this workspace.', quickPickItem.runtime.runtimeName)); }); - registerLanguageRuntimeAction('workbench.action.language.runtime.openClient', 'Create Runtime Client Widget', async accessor => { - // Access services. - const quickInputService = accessor.get(IQuickInputService); - - // Ask the user to select a running language runtime. - const languageRuntime = await selectRunningLanguageRuntime( - accessor, - localize('positron.languageRuntime.selectRuntime', 'Select the language runtime') - ); - if (!languageRuntime) { - return; - } - - // Prompt the user to select the runtime client type. - const selection = await quickInputService.pick([{ - id: generateUuid(), - label: 'Environment Pane', - runtimeClientType: RuntimeClientType.Variables, - }], { - canPickMany: false, - placeHolder: `Select runtime client for ${languageRuntime.runtimeMetadata.runtimeName}` - }); - - // If the user selected a runtime client type, create the client for it. - if (selection) { - languageRuntime.createClient(selection.runtimeClientType, null); - } - }); - - registerLanguageRuntimeAction('workbench.action.language.runtime.closeClient', 'Close Runtime Client Widget', async accessor => { - // Access services. - // const runtimeSessionService = accessor.get(IRuntimeSessionService); - const quickInputService = accessor.get(IQuickInputService); - - // Ask the user to select a running language runtime. - const languageRuntime = await selectRunningLanguageRuntime(accessor, 'Select the language runtime'); - if (!languageRuntime) { - return; - } - - // Get the runtime client instances for the language runtime. - const runtimeClientInstances = await languageRuntime.listClients(); - if (!runtimeClientInstances.length) { - alert('No clients are currently started.'); - return; - } - - // Create runtime client instance quick pick items. - const runtimeClientInstanceQuickPickItems = runtimeClientInstances.map(runtimeClientInstance => ({ - id: generateUuid(), - label: runtimeClientInstance.getClientType(), - runtimeClientInstance, - } satisfies RuntimeClientInstanceQuickPickItem)); - - // Prompt the user to select a runtime client instance. - const selection = await quickInputService.pick(runtimeClientInstanceQuickPickItems, { - canPickMany: false, - placeHolder: nls.localize('Client Close Selection Placeholder', 'Close Client for {0}', languageRuntime.runtimeMetadata.runtimeName) - }); - - // If the user selected a runtime client instance, dispose it. - if (selection) { - selection.runtimeClientInstance.dispose(); - } - }); - - - registerLanguageRuntimeAction(LANGUAGE_RUNTIME_OPEN_ACTIVE_SESSIONS_ID, 'Session Selector', async accessor => { + /** + * Action that allows the user to change the foreground session. + */ + registerLanguageRuntimeAction(LANGUAGE_RUNTIME_SELECT_SESSION_ID, 'Select Interpreter Session', async accessor => { // Access services. const commandService = accessor.get(ICommandService); const runtimeSessionService = accessor.get(IRuntimeSessionService); @@ -839,14 +576,20 @@ export function registerLanguageRuntimeActions() { } }); + /** + * Action that allows the user to create a new session from a list of registered runtimes. + */ registerAction2(class extends Action2 { + /** + * Constructor. + */ constructor() { super({ icon: Codicon.plus, - id: LANGUAGE_RUNTIME_DUPLICATE_SESSION_ID, + id: LANGUAGE_RUNTIME_START_NEW_SESSION_ID, title: { - value: localize('positron.languageRuntime.duplicate.title', 'Duplicate Session'), - original: 'Duplicate Session' + value: localize('positron.languageRuntime.startSession', 'Start New Interpreter Session'), + original: 'Start New Interpreter Session' }, category, f1: true, @@ -854,90 +597,373 @@ export function registerLanguageRuntimeActions() { group: 'navigation', id: MenuId.ViewTitle, order: 1, - when: ContextKeyExpr.equals('view', POSITRON_CONSOLE_VIEW_ID), + when: ContextKeyExpr.and( + ContextKeyExpr.equals('view', POSITRON_CONSOLE_VIEW_ID), + PositronConsoleInstancesExistContext.negate() + ), }], + keybinding: { + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Slash, + mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Slash }, + weight: KeybindingWeight.WorkbenchContrib + } }); } async run(accessor: ServicesAccessor) { - // Access services + // Access services. const commandService = accessor.get(ICommandService); const runtimeSessionService = accessor.get(IRuntimeSessionService); - const notificationService = accessor.get(INotificationService); - // Get the current foreground session. - const currentSession = runtimeSessionService.foregroundSession; - if (!currentSession) { - await commandService.executeCommand(LANGUAGE_RUNTIME_START_SESSION_ID); - return; - } + // Prompt the user to select a runtime to start + const selectedRuntime = await selectNewLanguageRuntime(accessor); - if (currentSession.metadata.sessionMode !== LanguageRuntimeSessionMode.Console) { - notificationService.error(localize('positron.languageRuntime.duplicate.notConsole', 'Cannot duplicate session. The current session is not a console session.')); - return; - } + // If the user selected a runtime, set it as the active runtime + if (selectedRuntime?.runtimeId) { + // Drive focus into the Positron console. + commandService.executeCommand('workbench.panel.positronConsole.focus'); - // Drive focus into the Positron console. - commandService.executeCommand('workbench.panel.positronConsole.focus'); + return await runtimeSessionService.startNewRuntimeSession( + selectedRuntime.runtimeId, + selectedRuntime.runtimeName, + LanguageRuntimeSessionMode.Console, + undefined, + 'User selected runtime', + RuntimeStartMode.Starting, + true + ); + } + return undefined; + } + }); - // Duplicate the current session with the `startNewRuntimeSession` method. - await runtimeSessionService.startNewRuntimeSession( - currentSession.runtimeMetadata.runtimeId, - currentSession.dynState.sessionName, - currentSession.metadata.sessionMode, - undefined, - `Duplicated session: ${currentSession.dynState.sessionName}`, + /** + * Action that allows the user to create a new session based off the current active session. + * This utilizes the runtime data from the current session to create a new session. + */ + registerAction2(class extends Action2 { + constructor() { + super({ + icon: Codicon.plus, + id: LANGUAGE_RUNTIME_DUPLICATE_ACTIVE_SESSION_ID, + title: { + value: localize('positron.languageRuntime.duplicateSession.title', 'Duplicate Active Interpreter Session'), + original: 'Duplicate Session' + }, + category, + f1: true, + menu: [{ + group: 'navigation', + id: MenuId.ViewTitle, + order: 1, + when: ContextKeyExpr.and( + ContextKeyExpr.equals('view', POSITRON_CONSOLE_VIEW_ID), + PositronConsoleInstancesExistContext + ), + }], + }); + } + + async run(accessor: ServicesAccessor) { + // Access services + const commandService = accessor.get(ICommandService); + const runtimeSessionService = accessor.get(IRuntimeSessionService); + const notificationService = accessor.get(INotificationService); + + // Get the current foreground session. + const currentSession = runtimeSessionService.foregroundSession; + if (!currentSession) { + return; + } + + if (currentSession.metadata.sessionMode !== LanguageRuntimeSessionMode.Console) { + notificationService.error(localize('positron.languageRuntime.duplicate.notConsole', 'Cannot duplicate session. The current session is not a console session.')); + return; + } + + // Drive focus into the Positron console. + commandService.executeCommand('workbench.panel.positronConsole.focus'); + + // Duplicate the current session with the `startNewRuntimeSession` method. + await runtimeSessionService.startNewRuntimeSession( + currentSession.runtimeMetadata.runtimeId, + currentSession.dynState.sessionName, + currentSession.metadata.sessionMode, + undefined, + `Duplicated session: ${currentSession.dynState.sessionName}`, RuntimeStartMode.Starting, true ); } }); + /** + * Action that allows the user to rename an active session. + */ registerAction2(class extends Action2 { + constructor() { + super({ + id: LANGUAGE_RUNTIME_RENAME_SESSION_ID, + title: nls.localize2('positron.console.renameSesison', "Rename Interpreter Session"), + category, + f1: true, + }); + } + /** - * Constructor. + * Renames a session + * + * @param accessor The service accessor + * @returns A promise that resolves when the session has been renamed */ + async run(accessor: ServicesAccessor) { + const sessionService = accessor.get(IRuntimeSessionService); + const notificationService = accessor.get(INotificationService); + const quickInputService = accessor.get(IQuickInputService); + + // Prompt the user to select a session they want to rename. + const session = await selectLanguageRuntimeSession( + accessor, { title: 'Select Interpreter Session To Rename' }); + if (!session) { + return; + } + + await renameLanguageRuntimeSession( + sessionService, + notificationService, + quickInputService, + session.sessionId + ); + } + }); + + /** + * Action that allows the user to rename the foreground session. + * + * Note: This is a convenience action that is used to allow the user to rename + * the currently active session without having to select it via the UI. + */ + registerAction2(class extends Action2 { constructor() { super({ - icon: Codicon.plus, - id: LANGUAGE_RUNTIME_START_SESSION_ID, - title: { - value: localize('workbench.action.language.runtime.openStartPicker', "Start a New Session"), - original: 'Start a New Session' - }, + id: LANGUAGE_RUNTIME_RENAME_ACTIVE_SESSION_ID, + title: nls.localize2('positron.console.renameActiveSesison', "Rename Active Interpreter Session"), category, + f1: true, keybinding: { - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Slash, - mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Slash }, + primary: KeyCode.Enter, + when: PositronConsoleTabFocused, weight: KeybindingWeight.WorkbenchContrib } }); } + /** + * Renames the currently active session + * + * @param accessor The service accessor + * @returns A promise that resolves when the session has been renamed + */ + async run(accessor: ServicesAccessor) { + const sessionService = accessor.get(IRuntimeSessionService); + const notificationService = accessor.get(INotificationService); + const quickInputService = accessor.get(IQuickInputService); + + // Get the active session + const session = sessionService.foregroundSession; + if (!session) { + return; + } + + await renameLanguageRuntimeSession( + sessionService, + notificationService, + quickInputService, + session.sessionId + ); + } + }); + + /** + * Action that allows the user to restart an active session. + */ + registerLanguageRuntimeAction( + LANGUAGE_RUNTIME_RESTART_ACTIVE_SESSION_ID, + 'Restart Active Interpreter Session', + async accessor => { + const sessionService = accessor.get(IRuntimeSessionService); + + // Get the active session + const session = sessionService.foregroundSession; + if (!session) { + return; + } + + // Restart the session + sessionService.restartSession(session.sessionId, + `'Restart Active Interpreter Session' command invoked`); + + }, + [ + { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Numpad0, + secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.F10] + }, + { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Digit0 + }, + ] + ); + + /** + * Action that allows the user to interrupt the active session. + */ + registerLanguageRuntimeAction('workbench.action.languageRuntime.interrupt', 'Interrupt Active Interpreter Session', async accessor => { + const sessionService = accessor.get(IRuntimeSessionService); + + // Get the active session + const session = sessionService.foregroundSession; + if (!session) { + return; + } + + session.interrupt(); + }); + + /** + * Action that allows the user to force-quit the active session. + */ + registerLanguageRuntimeAction('workbench.action.languageRuntime.forceQuit', 'Force Quit Active Interpreter Session', async accessor => { + const sessionService = accessor.get(IRuntimeSessionService); + + // Get the active session + const session = sessionService.foregroundSession; + if (!session) { + return; + } + + session.forceQuit(); + }); + + /** + * Action that allows the user to show the output channel for the active session. + */ + registerLanguageRuntimeAction('workbench.action.languageRuntime.showOutput', 'Show Active Interpreter Session Output', async accessor => { + const sessionService = accessor.get(IRuntimeSessionService); + + // Get the active session + const session = sessionService.foregroundSession; + if (!session) { + return; + } + + session.showOutput(); + }); + + /** + * Action that allows the user to show the profile report for an active session. + */ + registerLanguageRuntimeAction('workbench.action.languageRuntime.showProfile', 'Show Active Interpreter Session Profile Report', async accessor => { + const sessionService = accessor.get(IRuntimeSessionService); + + // Get the active session + const session = sessionService.foregroundSession; + if (!session) { + return; + } + + session.showProfile(); + }); + + registerAction2(class ExecuteCodeInConsoleAction extends Action2 { + + constructor() { + super({ + id: 'workbench.action.language.runtime.openClient', + title: nls.localize2('positron.command.openClient', "Create Runtime Client Widget"), + f1: false, + category + }); + } + async run(accessor: ServicesAccessor) { // Access services. - const commandService = accessor.get(ICommandService); - const runtimeSessionService = accessor.get(IRuntimeSessionService); + const quickInputService = accessor.get(IQuickInputService); - // Prompt the user to select a runtime to start - const selectedRuntime = await selectNewLanguageRuntime(accessor); + // Prompt the user to select a session + const session = await selectLanguageRuntimeSession(accessor); + if (!session) { + return; + } - // If the user selected a runtime, set it as the active runtime - if (selectedRuntime?.runtimeId) { - // Drive focus into the Positron console. - commandService.executeCommand('workbench.panel.positronConsole.focus'); + // Prompt the user to select the runtime client type. + const selection = await quickInputService.pick( + [ + { + id: generateUuid(), + label: 'Environment Pane', + runtimeClientType: RuntimeClientType.Variables, + } + ], + { + canPickMany: false, + placeHolder: `Select runtime client for ${session.runtimeMetadata.runtimeName}` + } + ); - return await runtimeSessionService.startNewRuntimeSession( - selectedRuntime.runtimeId, - selectedRuntime.runtimeName, - LanguageRuntimeSessionMode.Console, - undefined, - 'User selected runtime', - RuntimeStartMode.Starting, - true - ); + // If the user selected a runtime client type, create the client for it. + if (selection) { + session.createClient(selection.runtimeClientType, null); + } + } + }); + + registerAction2(class ExecuteCodeInConsoleAction extends Action2 { + + constructor() { + super({ + id: 'workbench.action.language.runtime.closeClient', + title: nls.localize2('positron.command.closeClient', "Close Runtime Client Widget"), + f1: false, + category + }); + } + + async run(accessor: ServicesAccessor) { + const quickInputService = accessor.get(IQuickInputService); + + // Prompt the user to select a session + const session = await selectLanguageRuntimeSession(accessor); + if (!session) { + return; + } + + // Get the runtime client instances for the session. + const runtimeClientInstances = await session.listClients(); + if (!runtimeClientInstances.length) { + alert('No clients are currently started.'); + return; + } + + // Create runtime client instance quick pick items. + const runtimeClientInstanceQuickPickItems = runtimeClientInstances.map(runtimeClientInstance => ({ + id: generateUuid(), + label: runtimeClientInstance.getClientType(), + runtimeClientInstance, + } satisfies RuntimeClientInstanceQuickPickItem)); + + // Prompt the user to select a runtime client instance. + const selection = await quickInputService.pick(runtimeClientInstanceQuickPickItems, { + canPickMany: false, + placeHolder: nls.localize('Client Close Selection Placeholder', 'Close Client for {0}', session.runtimeMetadata.runtimeName) + }); + + // If the user selected a runtime client instance, dispose it. + if (selection) { + selection.runtimeClientInstance.dispose(); } - return undefined; } }); @@ -993,7 +1019,7 @@ export function registerLanguageRuntimeActions() { const quickInputService = accessor.get(IQuickInputService); let fromPrompt = false; - // TODO: Should this be in a "Developer: " command? + // TODO: Should this be a "Developer: " command? // If no arguments are passed, prompt the user. if (!args) { // Prompt the user to select a language. @@ -1070,6 +1096,8 @@ export function registerLanguageRuntimeActions() { * * Typically used to for code that is executed for its side effects, rather * than for its output. Doesn't auto-start sessions. + * + * Commonly used by users by creating a keyboard shortcut for this action. */ registerAction2(class ExecuteSilentlyAction extends Action2 { private static _counter = 0; @@ -1160,7 +1188,7 @@ registerAction2(class SetWorkingDirectoryCommand extends Action2 { if (!session) { notificationService.info( nls.localize('positron.setWorkingDirectory.noSession', - "No active interpreter session; open the Console and select an interpreter before setting the working directory.")); + "No active interpreter session; open the Console and select an interpreter session before setting the working directory.")); return; } // If no resource was provided, ask the user to select a folder. @@ -1195,119 +1223,3 @@ registerAction2(class SetWorkingDirectoryCommand extends Action2 { } } }); - -/** - * Helper function to rename a language runtime session. - * - * @param accessor The service accessor. - * @param sessionId The ID of the session to rename. - */ -const renameLanguageRuntimeSession = async ( - sessionService: IRuntimeSessionService, - notificationService: INotificationService, - quickInputService: IQuickInputService, - sessionId: string -) => { - // Prompt the user to enter the new session name. - const sessionName = await quickInputService.input({ - value: '', - placeHolder: '', - prompt: nls.localize('positron.console.renameSession.prompt', "Enter the new session name"), - }); - - // Validate the new session name - const newSessionName = sessionName?.trim(); - if (!newSessionName?.trim()) { - return; - } - - // Attempt to rename the session. - try { - sessionService.updateSessionName(sessionId, newSessionName); - } catch (error) { - notificationService.error( - localize('positron.console.renameSession.error', - "Failed to rename session {0}: {1}", - sessionId, - error - ) - ); - } -}; - -registerAction2(class extends Action2 { - constructor() { - super({ - id: LANGUAGE_RUNTIME_RENAME_SESSION_ID, - title: nls.localize2('positron.console.renameSesison', "Rename Session"), - category, - f1: true, - }); - } - - /** - * Renames a session - * - * @param accessor The service accessor - * @returns A promise that resolves when the session has been renamed - */ - async run(accessor: ServicesAccessor) { - const sessionService = accessor.get(IRuntimeSessionService); - const notificationService = accessor.get(INotificationService); - const quickInputService = accessor.get(IQuickInputService); - - // Prompt the user to select a session they want to rename. - const session = await selectLanguageRuntimeSession(accessor); - if (!session) { - return; - } - - await renameLanguageRuntimeSession( - sessionService, - notificationService, - quickInputService, - session.sessionId - ); - } -}); - -registerAction2(class extends Action2 { - constructor() { - super({ - id: LANGUAGE_RUNTIME_RENAME_ACTIVE_SESSION_ID, - title: nls.localize2('positron.console.renameActiveSesison', "Rename the Currently Active Session"), - category, - f1: true, - keybinding: { - primary: KeyCode.Enter, - when: PositronConsoleTabFocused, - weight: KeybindingWeight.WorkbenchContrib - } - }); - } - - /** - * Renames the currently active session - * - * @param accessor The service accessor - * @returns A promise that resolves when the session has been renamed - */ - async run(accessor: ServicesAccessor) { - const sessionService = accessor.get(IRuntimeSessionService); - const notificationService = accessor.get(INotificationService); - const quickInputService = accessor.get(IQuickInputService); - - // Get the active session - const session = sessionService.foregroundSession; - if (!session) { - return; - } - - await renameLanguageRuntimeSession( - sessionService, - notificationService, - quickInputService, - session.sessionId - ); - } -}); diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts index 2e35b735458..d9a0f946cef 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts @@ -40,6 +40,7 @@ import { areSameExtensions } from '../../../../../platform/extensionManagement/c // eslint-disable-next-line no-duplicate-imports import { DeferredPromise } from '../../../../../base/common/async.js'; import { POSITRON_RUNTIME_NOTEBOOK_KERNELS_EXTENSION_ID } from '../../../runtimeNotebookKernel/common/runtimeNotebookKernelConfig.js'; +import { LANGUAGE_RUNTIME_SELECT_RUNTIME_ID } from '../../../languageRuntime/browser/languageRuntimeActions.js'; // --- End Positron --- type KernelPick = IQuickPickItem & { kernel: INotebookKernel }; function isKernelPick(item: QuickPickInput): item is KernelPick { @@ -566,7 +567,7 @@ export class KernelPickerMRUStrategy extends KernelPickerStrategyBase { // return this.displaySelectAnotherQuickPick(editor, items.length === 1 && items[0] === pick); // } - if (pick.id === 'workbench.action.languageRuntime.pick') { + if (pick.id === LANGUAGE_RUNTIME_SELECT_RUNTIME_ID) { this._handleKernelQuickPick(pick, editor); } // --- End Positron --- diff --git a/src/vs/workbench/contrib/positronConsole/browser/components/emptyConsole.tsx b/src/vs/workbench/contrib/positronConsole/browser/components/emptyConsole.tsx index 95b530fd1ce..c5017a387cf 100644 --- a/src/vs/workbench/contrib/positronConsole/browser/components/emptyConsole.tsx +++ b/src/vs/workbench/contrib/positronConsole/browser/components/emptyConsole.tsx @@ -13,7 +13,7 @@ import React from 'react'; import { localize } from '../../../../../nls.js'; import { PositronButton } from '../../../../../base/browser/ui/positronComponents/button/positronButton.js'; import { usePositronConsoleContext } from '../positronConsoleContext.js'; -import { LANGUAGE_RUNTIME_START_SESSION_ID } from '../../../languageRuntime/browser/languageRuntimeActions.js'; +import { LANGUAGE_RUNTIME_START_NEW_SESSION_ID } from '../../../languageRuntime/browser/languageRuntimeActions.js'; // Load localized copy for control. const noSessionRunning = localize('positron.console.empty.noSessionRunning', "There is no session running."); @@ -30,7 +30,7 @@ export const EmptyConsole = () => { const positronConsoleContext = usePositronConsoleContext(); const handlePressed = () => { - positronConsoleContext.commandService.executeCommand(LANGUAGE_RUNTIME_START_SESSION_ID); + positronConsoleContext.commandService.executeCommand(LANGUAGE_RUNTIME_START_NEW_SESSION_ID); }; // Render. diff --git a/src/vs/workbench/contrib/positronConsole/browser/positronConsoleView.tsx b/src/vs/workbench/contrib/positronConsole/browser/positronConsoleView.tsx index 35d29a32b98..9400d57fe4c 100644 --- a/src/vs/workbench/contrib/positronConsole/browser/positronConsoleView.tsx +++ b/src/vs/workbench/contrib/positronConsole/browser/positronConsoleView.tsx @@ -20,7 +20,7 @@ import { IViewDescriptorService } from '../../../common/views.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; import { ILanguageService } from '../../../../editor/common/languages/language.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; -import { PositronConsoleFocused } from '../../../common/contextkeys.js'; +import { PositronConsoleFocused, PositronConsoleInstancesExistContext } from '../../../common/contextkeys.js'; import { IViewPaneOptions } from '../../../browser/parts/views/viewPane.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; @@ -47,7 +47,7 @@ import { IWorkbenchEnvironmentService } from '../../../services/environment/comm import { IActionViewItem } from '../../../../base/browser/ui/actionbar/actionbar.js'; import { IDropdownMenuActionViewItemOptions } from '../../../../base/browser/ui/dropdown/dropdownActionViewItem.js'; import { Action, IAction } from '../../../../base/common/actions.js'; -import { LANGUAGE_RUNTIME_DUPLICATE_SESSION_ID, LANGUAGE_RUNTIME_START_SESSION_ID } from '../../languageRuntime/browser/languageRuntimeActions.js'; +import { LANGUAGE_RUNTIME_DUPLICATE_ACTIVE_SESSION_ID, LANGUAGE_RUNTIME_START_NEW_SESSION_ID } from '../../languageRuntime/browser/languageRuntimeActions.js'; import { DropdownWithPrimaryActionViewItem } from '../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js'; import { MenuItemAction } from '../../../../platform/actions/common/actions.js'; import { localize } from '../../../../nls.js'; @@ -116,6 +116,12 @@ export class PositronConsoleViewPane extends PositronViewPane implements IReactC */ private readonly _sessionDropdown: MutableDisposable = this._register(new MutableDisposable()); + /** + * Context key used to track if there are any Positron console instances. + * This is used to determine if we show the "+" session dropdown button. + */ + private _positronConsoleInstancesExistContextKey: IContextKey; + //#endregion Private Properties //#region IReactComponentContainer @@ -257,8 +263,9 @@ export class PositronConsoleViewPane extends PositronViewPane implements IReactC themeService, hoverService); - // Bind the PositronConsoleFocused context key. + // Bind the context keys this._positronConsoleFocusedContextKey = PositronConsoleFocused.bindTo(contextKeyService); + this._positronConsoleInstancesExistContextKey = PositronConsoleInstancesExistContext.bindTo(contextKeyService); // Register the onDidChangeBodyVisibility event handler. this._register(this.onDidChangeBodyVisibility(visible => { @@ -268,6 +275,16 @@ export class PositronConsoleViewPane extends PositronViewPane implements IReactC this._register(this.runtimeSessionService.onDidStartRuntime(() => this.updateActions())); this._register(this.runtimeSessionService.onDidDeleteRuntimeSession(() => this.updateActions())); + + // Update the context key used to manage the session dropdown when the console instances change. + this._register(this.positronConsoleService.onDidStartPositronConsoleInstance(() => { + this.updateConsoleInstancesExistContext(); + })); + + // Update the context key used to manage the session dropdown when the console instances change. + this._register(this.positronConsoleService.onDidDeletePositronConsoleInstance(() => { + this.updateConsoleInstancesExistContext(); + })); } /** @@ -333,6 +350,9 @@ export class PositronConsoleViewPane extends PositronViewPane implements IReactC const focusTracker = this._register(DOM.trackFocus(this.element)); this._register(focusTracker.onDidFocus(() => this.focusChanged(true))); this._register(focusTracker.onDidBlur(() => this.focusChanged(false))); + + // Initialize context key state + this.updateConsoleInstancesExistContext(); } /** @@ -371,7 +391,8 @@ export class PositronConsoleViewPane extends PositronViewPane implements IReactC } override createActionViewItem(action: IAction, options?: IDropdownMenuActionViewItemOptions): IActionViewItem | undefined { - if (action.id === LANGUAGE_RUNTIME_DUPLICATE_SESSION_ID) { + // Do not create the session dropdown if there are no Positron console instances. + if (action.id === LANGUAGE_RUNTIME_DUPLICATE_ACTIVE_SESSION_ID && this.positronConsoleService.positronConsoleInstances.length > 0) { if (action instanceof MenuItemAction) { const dropdownAction = new Action('console.session.quickLaunch', localize('console.session.quickLaunch', 'Quick Launch Session...'), 'codicon-chevron-down', true); this._register(dropdownAction); @@ -443,7 +464,7 @@ export class PositronConsoleViewPane extends PositronViewPane implements IReactC undefined, true, () => { - this.commandService.executeCommand(LANGUAGE_RUNTIME_START_SESSION_ID); + this.commandService.executeCommand(LANGUAGE_RUNTIME_START_NEW_SESSION_ID); }) ); @@ -452,5 +473,10 @@ export class PositronConsoleViewPane extends PositronViewPane implements IReactC this._sessionDropdown.value?.update(dropdownAction, dropdownMenuActions, 'codicon-chevron-down'); } + private updateConsoleInstancesExistContext(): void { + const hasInstances = this.positronConsoleService.positronConsoleInstances.length > 0; + this._positronConsoleInstancesExistContextKey.set(hasInstances); + } + //#endregion Public Overrides } diff --git a/src/vs/workbench/contrib/runtimeNotebookKernel/browser/runtimeNotebookKernelService.ts b/src/vs/workbench/contrib/runtimeNotebookKernel/browser/runtimeNotebookKernelService.ts index 490f40e8771..1c84cb22894 100644 --- a/src/vs/workbench/contrib/runtimeNotebookKernel/browser/runtimeNotebookKernelService.ts +++ b/src/vs/workbench/contrib/runtimeNotebookKernel/browser/runtimeNotebookKernelService.ts @@ -23,6 +23,7 @@ import { NotebookExecutionStatus } from './notebookExecutionStatus.js'; import { RuntimeNotebookKernel } from './runtimeNotebookKernel.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { ILanguageRuntimeCodeExecutedEvent } from '../../../services/positronConsole/common/positronConsoleCodeExecution.js'; +import { LANGUAGE_RUNTIME_SELECT_RUNTIME_ID } from '../../languageRuntime/browser/languageRuntimeActions.js'; /** * The affinity of a kernel for a notebook. @@ -134,7 +135,7 @@ export class RuntimeNotebookKernelService extends Disposable implements IRuntime { label: 'Select Environment...', command: { - id: 'workbench.action.languageRuntime.pick', + id: LANGUAGE_RUNTIME_SELECT_RUNTIME_ID, title: 'Select Environment', }, } diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/positronWelcomeConsoleButton.tsx b/src/vs/workbench/contrib/welcomeGettingStarted/browser/positronWelcomeConsoleButton.tsx index 08fe4e0662d..2bb3a5366a3 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/positronWelcomeConsoleButton.tsx +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/positronWelcomeConsoleButton.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { localize } from '../../../../nls.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { ActionButton } from '../../positronNotebook/browser/utilityComponents/ActionButton.js'; -import { LANGUAGE_RUNTIME_START_SESSION_ID } from '../../languageRuntime/browser/languageRuntimeActions.js'; +import { LANGUAGE_RUNTIME_START_NEW_SESSION_ID } from '../../languageRuntime/browser/languageRuntimeActions.js'; interface WelcomeConsoleButtonProps { commandService: ICommandService; @@ -18,7 +18,7 @@ interface WelcomeConsoleButtonProps { export function WelcomeConsoleButton(props: WelcomeConsoleButtonProps) { const handlePressed = () => { - props.commandService.executeCommand(LANGUAGE_RUNTIME_START_SESSION_ID); + props.commandService.executeCommand(LANGUAGE_RUNTIME_START_NEW_SESSION_ID); } // Render. diff --git a/src/vs/workbench/services/positronConsole/browser/positronConsoleService.ts b/src/vs/workbench/services/positronConsole/browser/positronConsoleService.ts index 29fa485cf49..d825027c0df 100644 --- a/src/vs/workbench/services/positronConsole/browser/positronConsoleService.ts +++ b/src/vs/workbench/services/positronConsole/browser/positronConsoleService.ts @@ -756,8 +756,6 @@ export class PositronConsoleService extends Disposable implements IPositronConso return; } - this._onDidDeletePositronConsoleInstanceEmitter.fire(consoleInstance); - // Update the foreground session. // First try to use the last created console session for the runtime. let runtimeSession = this._runtimeSessionService.getConsoleSessionForRuntime( @@ -776,6 +774,8 @@ export class PositronConsoleService extends Disposable implements IPositronConso this._positronConsoleInstancesBySessionId.delete(sessionId); consoleInstance.dispose(); + + this._onDidDeletePositronConsoleInstanceEmitter.fire(consoleInstance); } /** diff --git a/test/e2e/pages/console.ts b/test/e2e/pages/console.ts index 5e836ec939e..f060a6e33da 100644 --- a/test/e2e/pages/console.ts +++ b/test/e2e/pages/console.ts @@ -41,6 +41,7 @@ export class Console { this.restartButton = this.code.driver.page.getByLabel('Restart console'); this.clearButton = this.code.driver.page.getByLabel('Clear console'); this.trashButton = this.code.driver.page.getByTestId('trash-session'); + // TODO @dhruvisompura: This needs to be updated since this now creates or duplicates a session this.newSessionButton = this.code.driver.page.getByRole('toolbar', { name: 'Console actions' }).getByRole('button', { name: 'Start a New Session' }); this.activeConsole = this.code.driver.page.locator(ACTIVE_CONSOLE_INSTANCE); this.suggestionList = this.code.driver.page.locator(SUGGESTION_LIST); diff --git a/test/e2e/pages/sessions.ts b/test/e2e/pages/sessions.ts index 5d46730ee1a..cc949491c4a 100644 --- a/test/e2e/pages/sessions.ts +++ b/test/e2e/pages/sessions.ts @@ -940,8 +940,8 @@ export class Sessions { */ export class SessionQuickPick { private quickInputTitleBar = this.code.driver.page.locator('.quick-input-titlebar'); - private sessionQuickMenu = this.quickInputTitleBar.getByText(/(Select a Session)|(Start a New Session)/); - allSessionsMenu = this.quickInputTitleBar.getByText(/Start a New Session/); + private sessionQuickMenu = this.quickInputTitleBar.getByText(/(Select Interpreter Session)|(Start New Interpreter Session)/); + allSessionsMenu = this.quickInputTitleBar.getByText(/Start New Interpreter Session/); constructor(private code: Code, private sessions: Sessions) { } @@ -960,9 +960,9 @@ export class SessionQuickPick { } if (viewAllRuntimes) { - await this.code.driver.page.getByRole('textbox', { name: /(Select a Session|New Session)/ }).fill('New Session'); + await this.code.driver.page.getByRole('textbox', { name: /(Select Interpreter Session|New Interpreter Session)/ }).fill('New Session'); await this.code.driver.page.keyboard.press('Enter'); - await expect(this.code.driver.page.getByText(/Start a New Session/)).toBeVisible({ timeout: 1000 }); + await expect(this.code.driver.page.getByText(/Start New Interpreter Session/)).toBeVisible({ timeout: 1000 }); } }, 'Open Session QuickPick Menu').toPass({ intervals: [500], timeout: 10000 }); }); @@ -1008,7 +1008,7 @@ export class SessionQuickPick { // Filter out the one with "New Session..." const filteredSessions = activeSessions - .filter(session => !session.name.includes('New Session...')); + .filter(session => !session.name.includes('New Interpreter Session...')); await this.closeSessionQuickPickMenu(); return filteredSessions; diff --git a/test/e2e/tests/new-project-wizard/new-project-python.test.ts b/test/e2e/tests/new-project-wizard/new-project-python.test.ts index d71d0a7de8f..d9368185a94 100644 --- a/test/e2e/tests/new-project-wizard/new-project-python.test.ts +++ b/test/e2e/tests/new-project-wizard/new-project-python.test.ts @@ -89,11 +89,13 @@ test.describe('Python - New Project Wizard', { tag: [tags.MODAL, tags.NEW_PROJEC await expect(notebookEditorTab).toBeVisible(); // Get the Python version from the session selector button - const sessionSelectorButton = app.code.driver.page.getByRole('button', { name: 'Session Selector' }); + const sessionSelectorButton = app.code.driver.page.getByRole('button', { name: 'Select Interpreter Session' }); const sessionSelectorText = await sessionSelectorButton.textContent(); + // Extract the version number (e.g., '3.10.12') from the button text const versionMatch = sessionSelectorText && sessionSelectorText.match(/Python ([0-9]+\.[0-9]+\.[0-9]+)/); const pythonVersion = versionMatch ? versionMatch[1] : undefined; + // Fail the test if we can't extract the version expect(pythonVersion, 'Python version should be present in session selector').toBeTruthy();