Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion extensions/positron-python/python_files/posit/positron/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
import inspect
import logging
import os
import re
import sys
import webbrowser
from pathlib import Path
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Union
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union
from urllib.parse import urlparse

from comm.base_comm import BaseComm
Expand Down Expand Up @@ -126,11 +127,28 @@ def _get_packages_installed(_kernel: "PositronIPyKernel", _params: List[JsonData
canonical = canonicalize_name(name)
# Dedupe by canonical name - keeps first occurrence (the one that would be imported)
if canonical not in packages_dict:
# PackageMetadata (the 3.14 protocol) doesn't expose .get(), but the
# runtime object (email.message.Message) always has it.
metadata: Any = dist.metadata
summary = metadata.get("Summary")
# Fall back through Author → Author-email → Maintainer → Maintainer-email so
# packages that set only a maintainer (e.g. click's `Pallets`) still show one.
author = ""
for field in ("Author", "Author-email", "Maintainer", "Maintainer-email"):
value = metadata.get(field)
if value and value != "UNKNOWN":
# Strip trailing email addresses in angle brackets to match the R side.
stripped = re.sub(r"\s*<[^>]+>", "", value).strip()
if stripped:
author = stripped
break
packages_dict[canonical] = {
"id": f"{canonical}-{dist.version}",
"name": name,
"displayName": canonical,
"version": dist.version,
"description": summary if summary and summary != "UNKNOWN" else "",
"author": author,
}
return sorted(packages_dict.values(), key=lambda p: p["displayName"])

Expand Down
5 changes: 5 additions & 0 deletions src/positron-dts/positron.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1156,6 +1156,11 @@ declare module 'positron' {

/** Publication/release date */
publishedDate?: string;

/** Optional short description or summary shown in the Packages pane card view. */
description?: string;
/** Optional author shown in the Packages pane card view. */
author?: string;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,52 @@
}

.packages-list-item {
margin-top: 5px;
padding-left: 8px;
padding-right: 8px;
cursor: pointer;
overflow: hidden;
display: flex;
box-sizing: border-box;
}

.packages-list-item.item-size-row {
flex-direction: row;
align-items: center;
cursor: pointer;
margin-top: 5px;
}

.packages-list-item.item-size-card {
flex-direction: column;
justify-content: center;
/* Align with the search input's outer-left edge (filter-container 8px + input border 1px), plus the 0.5px nudge from .packages-list-container. */
padding-left: 10px;
padding-right: 11px;
padding-top: 6px;
padding-bottom: 6px;
}

.packages-list-item-header {
display: flex;
flex-direction: row;
align-items: baseline;
overflow: hidden;
min-width: 0;
}

.packages-list-item.item-size-row .packages-list-item-header {
flex: 1 1 auto;
}

.packages-list-item-name {
flex: 0 0 auto;
flex: 0 1 auto;
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

.packages-list-item.item-size-card .packages-list-item-name {
font-weight: bold;
}

.packages-list-item-version {
Expand All @@ -67,6 +101,24 @@
font-size: 0.9em;
}

.packages-list-item-description {
color: var(--vscode-descriptionForeground);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 0;
}

.packages-list-item-author {
font-size: 90%;
color: var(--vscode-descriptionForeground);
opacity: 0.85;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 0;
}

.packages-list-item:hover {
background: var(--vscode-list-hoverBackground);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ const positronUpdatePackage = localize(
// Height of the filter container in pixels
const FILTER_HEIGHT = 34;

// Row heights for each item size mode.
const ROW_ITEM_HEIGHT = 26;
const CARD_ITEM_HEIGHT = 72;

export const ListPackages = (props: React.PropsWithChildren<ViewsProps>) => {
const {
activeInstance,
Expand All @@ -57,6 +61,15 @@ export const ListPackages = (props: React.PropsWithChildren<ViewsProps>) => {

const [packages, setPackages] = useState<ILanguageRuntimePackage[]>([]);

// Item size mode ('card' or 'row'), driven by the packages service.
const [itemSize, setItemSize] = useState(() => services.positronPackagesService.itemSize);
useEffect(() => {
const disposable = services.positronPackagesService.onDidChangeItemSize((size) => {
setItemSize(size);
});
return () => disposable.dispose();
}, [services.positronPackagesService]);

// Progress Bar
const progressRef = useRef<HTMLDivElement>(null);

Expand Down Expand Up @@ -251,15 +264,15 @@ export const ListPackages = (props: React.PropsWithChildren<ViewsProps>) => {
// Item renderer
const ItemEntry = (props: { index: number; style: CSSProperties }) => {
const itemProps = filteredPackages[props.index];
const { id, name, displayName, version, latestVersion } = itemProps;
const { id, name, displayName, version, latestVersion, description, author } = itemProps;

// Check if package has an update available
const hasUpdate = latestVersion && latestVersion !== version;

return (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<div
className={positronClassNames('packages-list-item', {
className={positronClassNames('packages-list-item', `item-size-${itemSize}`, {
selected: id === selectedItem,
})}
style={props.style}
Expand Down Expand Up @@ -311,15 +324,27 @@ export const ListPackages = (props: React.PropsWithChildren<ViewsProps>) => {
}
}}
>
<div className='packages-list-item-name'>{displayName}</div>
<div className='packages-list-item-version'>{version}</div>
{hasUpdate && (
<div
className='packages-list-item-update'
title={localize('positronPackages.updateAvailable', "Update available: {0}", latestVersion)}
>
&#x2191;
</div>
<div className='packages-list-item-header'>
<div className='packages-list-item-name'>{displayName}</div>
<div className='packages-list-item-version'>{version}</div>
{hasUpdate && (
<div
className='packages-list-item-update'
title={localize('positronPackages.updateAvailable', "Update available: {0}", latestVersion)}
>
&#x2191;
</div>
)}
</div>
{itemSize === 'card' && (
<>
<div className='packages-list-item-description' title={description ?? ''}>
{description ?? ''}
</div>
<div className='packages-list-item-author' title={author ?? ''}>
{author ?? ''}
</div>
</>
)}
</div >
);
Expand Down Expand Up @@ -410,7 +435,8 @@ export const ListPackages = (props: React.PropsWithChildren<ViewsProps>) => {
innerRef={innerRef}
itemCount={filteredPackages.length}
itemKey={(index) => filteredPackages[index].id}
itemSize={26}
itemSize={itemSize === 'card' ? CARD_ITEM_HEIGHT : ROW_ITEM_HEIGHT}
style={{ overflowX: 'hidden' }}
width={'calc(100% - 2px)'}
>
{ItemEntry}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Event } from '../../../../../base/common/event.js';
import { createDecorator } from '../../../../../platform/instantiation/common/instantiation.js';
import { ILanguageRuntimePackage, ILanguageRuntimeSession, IPackageSpec } from '../../../../services/runtimeSession/common/runtimeSessionService.js';
import { IPositronPackagesInstance } from '../positronPackagesInstance.js';
import { PackagesItemSize } from '../positronPackagesContextKeys.js';

// Create the decorator for the Positron packages service (used in dependency injection).
export const IPositronPackagesService = createDecorator<IPositronPackagesService>('positronPackagesService');
Expand Down Expand Up @@ -36,6 +37,21 @@ export interface IPositronPackagesService {
*/
setSelectedPackage(packageName: string | undefined): void;

/**
* The current item size mode for the packages list.
*/
readonly itemSize: PackagesItemSize;

/**
* Sets the item size mode for the packages list.
*/
setItemSize(itemSize: PackagesItemSize): void;

/**
* Fired when the item size mode changes.
*/
readonly onDidChangeItemSize: Event<PackagesItemSize>;

/**
* The onDidRefreshPackagesInstance event.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { IViewContainersRegistry, IViewsRegistry, Extensions as ViewContainerExt
import { ILanguageRuntimePackage, IRuntimeSessionService } from '../../../services/runtimeSession/common/runtimeSessionService.js';
import { positronSessionViewIcon } from '../../positronSession/browser/positronSessionContainer.js';
import { IPositronPackagesService } from './interfaces/positronPackagesService.js';
import { PACKAGES_CAN_RUN_ACTION, PACKAGES_HAS_SELECTION, PACKAGES_VIEW_VISIBLE, POSITRON_PACKAGES_VIEW_ID } from './positronPackagesContextKeys.js';
import { PACKAGES_CAN_RUN_ACTION, PACKAGES_HAS_SELECTION, PACKAGES_VIEW_VISIBLE, POSITRON_PACKAGES_ITEM_SIZE, POSITRON_PACKAGES_VIEW_ID } from './positronPackagesContextKeys.js';
import { installPackage, uninstallPackage, updatePackage } from './positronPackagesQuickPick.js';
import { PositronPackagesService } from './positronPackagesService.js';
import { PositronPackagesView } from './positronPackagesView.js';
Expand Down Expand Up @@ -580,6 +580,75 @@ class RefreshMetadataAction extends Action2 {
}
}

/**
* Switches the Packages view to the expanded card layout.
* Only visible in the view title when the view is currently showing compact rows.
*/
class SetPackagesCardViewAction extends Action2 {
constructor() {
super({
id: 'positronPackages.setCardView',
title: nls.localize2('positronPackages.showAsCards', 'Show as Cards'),
category: PACKAGES_CATEGORY,
icon: Codicon.listSelection,
precondition: POSITRON_PACKAGES_ENABLED,
menu: {
id: MenuId.ViewTitle,
when: ContextKeyExpr.and(PACKAGES_VIEW_VISIBLE, POSITRON_PACKAGES_ITEM_SIZE.isEqualTo('row')),
group: 'navigation',
order: 2,
},
});
}
override async run(accessor: ServicesAccessor): Promise<void> {
accessor.get<IPositronPackagesService>(IPositronPackagesService).setItemSize('card');
}
}

/**
* Switches the Packages view to the compact row layout.
* Only visible in the view title when the view is currently showing cards.
*/
class SetPackagesRowViewAction extends Action2 {
constructor() {
super({
id: 'positronPackages.setRowView',
title: nls.localize2('positronPackages.showAsRows', 'Show as Rows'),
category: PACKAGES_CATEGORY,
icon: Codicon.listFlat,
precondition: POSITRON_PACKAGES_ENABLED,
menu: {
id: MenuId.ViewTitle,
when: ContextKeyExpr.and(PACKAGES_VIEW_VISIBLE, POSITRON_PACKAGES_ITEM_SIZE.isEqualTo('card')),
group: 'navigation',
order: 2,
},
});
}
override async run(accessor: ServicesAccessor): Promise<void> {
accessor.get<IPositronPackagesService>(IPositronPackagesService).setItemSize('row');
}
}

/**
* Toggles between card and row layouts. Exposed via the command palette.
*/
class TogglePackagesItemSizeAction extends Action2 {
constructor() {
super({
id: 'positronPackages.toggleItemSize',
title: nls.localize2('positronPackages.toggleItemSize', 'Toggle Packages List Layout'),
category: PACKAGES_CATEGORY,
f1: true,
precondition: POSITRON_PACKAGES_ENABLED,
});
}
override async run(accessor: ServicesAccessor): Promise<void> {
const service = accessor.get<IPositronPackagesService>(IPositronPackagesService);
service.setItemSize(service.itemSize === 'card' ? 'row' : 'card');
}
}

registerAction2(InstallPackageAction);
registerAction2(RefreshPackagesAction);
registerAction2(RefreshMetadataAction);
Expand All @@ -588,4 +657,7 @@ registerAction2(UpdatePackageAction);
registerAction2(UpdateAllPackagesAction);
registerAction2(UpdateSelectedPackageAction);
registerAction2(UninstallSelectedPackageAction);
registerAction2(SetPackagesCardViewAction);
registerAction2(SetPackagesRowViewAction);
registerAction2(TogglePackagesItemSizeAction);
registerSingleton(IPositronPackagesService, PositronPackagesService, InstantiationType.Delayed);
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ export const POSITRON_PACKAGES_HAS_ACTIVE_SESSION = new RawContextKey<boolean>('
export const POSITRON_PACKAGES_IS_BUSY = new RawContextKey<boolean>('positronPackages.isBusy', false);
export const POSITRON_PACKAGES_SELECTED_PACKAGE = new RawContextKey<string>('positronPackages.selectedPackage', '');

// Item size mode for the packages list: 'card' or 'row'.
export type PackagesItemSize = 'card' | 'row';
export const POSITRON_PACKAGES_ITEM_SIZE = new RawContextKey<PackagesItemSize>('positronPackages.itemSize', 'row');

// Context key expressions for menu enablement
export const PACKAGES_VIEW_VISIBLE = ContextKeyExpr.equals('view', POSITRON_PACKAGES_VIEW_ID);
export const PACKAGES_CAN_RUN_ACTION = ContextKeyExpr.and(
Expand Down
Loading
Loading