Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import { PositronConsoleState } from '../../../../services/positronConsole/brows
import { RuntimeExitReason, RuntimeState } from '../../../../services/languageRuntime/common/languageRuntimeService.js';
import { ILanguageRuntimeSession, RuntimeStartMode } from '../../../../services/runtimeSession/common/runtimeSessionService.js';
import { ConsoleInstanceMenuButton } from './consoleInstanceMenuButton.js';
import { multipleConsoleSessionsFeatureEnabled } from '../../../../services/runtimeSession/common/positronMultipleConsoleSessionsFeatureFlag.js';
import { ConsoleInstanceInfoButton } from './consoleInstanceInfoButton.js';

/**
* Constants.
Expand Down Expand Up @@ -101,6 +103,7 @@ export const ActionBar = (props: ActionBarProps) => {

// Constants.
const showDeveloperUI = IsDevelopmentContext.getValue(positronConsoleContext.contextKeyService);
const multiSessionsEnabled = multipleConsoleSessionsFeatureEnabled(positronConsoleContext.configurationService);

// State hooks.
const [activePositronConsoleInstance, setActivePositronConsoleInstance] =
Expand Down Expand Up @@ -382,6 +385,7 @@ export const ActionBar = (props: ActionBarProps) => {
ariaLabel={positronRestartConsole}
onPressed={restartConsoleHandler}
/>
{multiSessionsEnabled && <ConsoleInstanceInfoButton />}
<ActionBarSeparator />
{showDeveloperUI &&
<ActionBarButton
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* Copyright (C) 2025 Posit Software, PBC. All rights reserved.
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/

.positron-modal-popup-children:has(.console-instance-info) {
background: var(--vscode-editorHoverWidget-background);
}

.console-instance-info .top-separator {
border-top: 1px solid var(--vscode-editorHoverWidget-border);
}

.console-instance-info .actions {
background-color: var(--vscode-editorHoverWidget-statusBarBackground);
padding-bottom: 4px;
}

.console-instance-info .content .line {
margin: 8px 0;
overflow-wrap: break-word;
padding: 0 8px;
}

.console-instance-info .actions .link {
color: var(--vscode-textLink-foreground);
cursor: pointer;
font-size: 12px;
line-height: 22px;
padding: 0 8px;
text-decoration: underline;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*---------------------------------------------------------------------------------------------
* Copyright (C) 2025 Posit Software, PBC. All rights reserved.
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/

// CSS.
import './consoleInstanceInfoButton.css';

// React.
import React, { useEffect, useRef, useState } from 'react';

// Other dependencies.
import { localize } from '../../../../../nls.js';
import * as DOM from '../../../../../base/browser/dom.js';
import { PositronModalPopup } from '../../../../browser/positronComponents/positronModalPopup/positronModalPopup.js'
import { ActionBarButton } from '../../../../../platform/positronActionBar/browser/components/actionBarButton.js';
import { PositronModalReactRenderer } from '../../../../browser/positronModalReactRenderer/positronModalReactRenderer.js';
import { usePositronConsoleContext } from '../positronConsoleContext.js';
import { PositronButton } from '../../../../../base/browser/ui/positronComponents/button/positronButton.js';
import { ILanguageRuntimeSession } from '../../../../services/runtimeSession/common/runtimeSessionService.js';
import { DisposableStore } from '../../../../../base/common/lifecycle.js';

const positronConsoleInfo = localize('positron.console.info.label', "Console information");
const showKernelOutputChannel = localize('positron.console.info.showKernelOutputChannel', "Show Kernel Output Channel");

export const ConsoleInstanceInfoButton = () => {
// Hooks.
const positronConsoleContext = usePositronConsoleContext();

// Reference hooks.
const ref = useRef<HTMLButtonElement>(undefined!);

const handlePressed = () => {
if (!positronConsoleContext.activePositronConsoleInstance) {
return;
}

// Create the renderer.
const renderer = new PositronModalReactRenderer({
keybindingService: positronConsoleContext.keybindingService,
layoutService: positronConsoleContext.workbenchLayoutService,
container: positronConsoleContext.workbenchLayoutService.getContainer(DOM.getWindow(ref.current)),
parent: ref.current
});

renderer.render(
<ConsoleInstanceInfoModalPopup
anchorElement={ref.current}
renderer={renderer}
session={positronConsoleContext.activePositronConsoleInstance.session}
/>
);
}

// Render.
return (
<ActionBarButton
iconId='info'
align='right'
tooltip={positronConsoleInfo}
ariaLabel={positronConsoleInfo}
onPressed={handlePressed}
ref={ref}
/>
)
};

interface ConsoleInstanceInfoModalPopupProps {
anchorElement: HTMLElement;
renderer: PositronModalReactRenderer;
session: ILanguageRuntimeSession;
}

const ConsoleInstanceInfoModalPopup = (props: ConsoleInstanceInfoModalPopupProps) => {
const [sessionState, setSessionState] = useState(() => props.session.getRuntimeState());

// Main useEffect hook.
useEffect(() => {
const disposableStore = new DisposableStore();
disposableStore.add(props.session.onDidChangeRuntimeState(state => {
setSessionState(state);
}));
return () => disposableStore.dispose();
}, []);

const showKernelOutputChannelClickHandler = () => {
props.session.showOutput();
}

// Render.
return (
<PositronModalPopup
anchorElement={props.anchorElement}
height='min-content'
keyboardNavigationStyle='menu'
renderer={props.renderer}
popupAlignment='auto'
popupPosition='auto'
width={400}
>
<div className='console-instance-info'>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A idea for the future -- thoughts on making the text copy-able? In particular, it would be great if the interpreter path was copy-able.

image

Copy link
Contributor Author

@dhruvisompura dhruvisompura Jan 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed! This is something I noticed and made a note about in the epic around cleanup/improvements!

<div className='content'>
<p className='line'>{props.session.metadata.sessionName}</p>
<div className='top-separator'>
<p className='line'>
{(() => localize(
'positron.console.info.sessionId', 'Session ID: {0}',
props.session.sessionId
))()}
</p>
<p className='line'>{(() => localize(
'positron.console.info.state', 'State: {0}',
sessionState))()}
</p>
</div>
<div className='top-separator'>
<p className='line'>{(() => localize(
'positron.console.info.runtimePath', 'Path: {0}',
props.session.runtimeMetadata.runtimePath))()}
</p>
<p className='line'>{(() => localize(
'positron.console.info.runtimeSource', 'Source: {0}',
props.session.runtimeMetadata.runtimeSource))()}
</p>
</div>
</div>
<div className='top-separator actions'>
<PositronButton className='link' onPressed={showKernelOutputChannelClickHandler}>
{showKernelOutputChannel}
</PositronButton>
</div>
</div>
</PositronModalPopup>
)
};
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ interface RuntimeSessionProps {
*/
export const RuntimeSession = (props: RuntimeSessionProps) => {

const [sessionState, setSessionState] = useState(props.session.getRuntimeState());
const [sessionState, setSessionState] = useState(() => props.session.getRuntimeState());
const [expanded, setExpanded] = useState(false);

// Main useEffect hook.
Expand All @@ -44,7 +44,7 @@ export const RuntimeSession = (props: RuntimeSessionProps) => {
setSessionState(state);
}));
return () => disposableStore.dispose();
});
}, []);

// Render.
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ interface runtimeSessionCardProps {
*/
export const RuntimeSessionCard = (props: runtimeSessionCardProps) => {

const [sessionState, setSessionState] = useState(props.session.getRuntimeState());
const [sessionState, setSessionState] = useState(() => props.session.getRuntimeState());

const shutdownSession = () => {
props.session.shutdown(RuntimeExitReason.Shutdown);
Expand Down Expand Up @@ -59,7 +59,7 @@ export const RuntimeSessionCard = (props: runtimeSessionCardProps) => {
setSessionState(state);
}));
return () => disposableStore.dispose();
});
}, []);

return (
<tr>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*---------------------------------------------------------------------------------------------
* Copyright (C) 2025 Posit Software, PBC. All rights reserved.
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/

import { localize } from '../../../../nls.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
import { ConfigurationScope, Extensions, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js';
import { Registry } from '../../../../platform/registry/common/platform.js';
import { positronConfigurationNodeBase } from '../../languageRuntime/common/languageRuntime.js';

// Key for the multiple sessions setting
export const USE_POSITRON_MULTIPLE_CONSOLE_SESSIONS_CONFIG_KEY =
'positron.multipleConsoleSessions';

/**
* Retrieves the value of the configuration setting that determines whether to enable
* multiple sessions feature.
* @param configurationService The configuration service
* @returns Whether to enablet the multiple sessions feature
*/
export function multipleConsoleSessionsFeatureEnabled(
configurationService: IConfigurationService
) {
return Boolean(
configurationService.getValue(USE_POSITRON_MULTIPLE_CONSOLE_SESSIONS_CONFIG_KEY)
);
}

// Register the configuration setting
const configurationRegistry = Registry.as<IConfigurationRegistry>(
Extensions.Configuration
);
configurationRegistry.registerConfiguration({
...positronConfigurationNodeBase,
scope: ConfigurationScope.MACHINE_OVERRIDABLE,
properties: {
[USE_POSITRON_MULTIPLE_CONSOLE_SESSIONS_CONFIG_KEY]: {
type: 'boolean',
default: false,
markdownDescription: localize(
'positron.enableMultipleConsoleSessionsFeature',
'**CAUTION**: Enable experimental Positron multiple console sessions features which may result in unexpected behaviour. Please restart Positron if you change this option.'
),
},
},
});