Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display active runtime metadata in console pane #6156

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());

dhruvisompura marked this conversation as resolved.
Show resolved Hide resolved
// 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
dhruvisompura marked this conversation as resolved.
Show resolved Hide resolved
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.'
),
},
},
});