Skip to content
Draft
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
1 change: 1 addition & 0 deletions crates/pixi/tests/integration_rust/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,7 @@ impl PixiControl {
dry_run: false,
specs: Default::default(),
json: false,
interactive: false,
},
}
}
Expand Down
57 changes: 56 additions & 1 deletion crates/pixi_cli/src/update.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{cmp::Ordering, collections::HashSet};

use clap::Parser;
use dialoguer::{MultiSelect, theme::ColorfulTheme};
use fancy_display::FancyDisplay;
use itertools::Itertools;
use miette::{Context, IntoDiagnostic, MietteDiagnostic};
Expand Down Expand Up @@ -44,6 +45,10 @@ pub struct Args {
/// Output the changes in JSON format.
#[clap(long)]
pub json: bool,

/// Run in interactive mode
#[clap(short = 'i', long)]
pub interactive: bool,
}

#[derive(Parser, Debug, Default)]
Expand Down Expand Up @@ -130,7 +135,7 @@ pub async fn execute(args: Args) -> miette::Result<()> {
.locate()?
.with_cli_config(config);

let specs = UpdateSpecs::from(args.specs);
let mut specs = UpdateSpecs::from(args.specs);

// If the user specified an environment name, check to see if it exists.
if let Some(env) = &specs.environments {
Expand All @@ -151,6 +156,56 @@ pub async fn execute(args: Args) -> miette::Result<()> {
.await?
.into_lock_file_or_empty_with_warning();

// If interactive mode is requested and no packages were explicitly
// specified, prompt the user to choose which packages to update.
if args.interactive && specs.packages.is_none() {
// Collect unique package names and current versions from the lock-file.
let packages: Vec<(String, String)> = loaded_lock_file
.environments()
.flat_map(|(_, env)| {
env.packages_by_platform()
.flat_map(|(_, packages)| {
packages.into_iter().map(|p| match p {
LockedPackageRef::Conda(ver) => {
(p.name().to_string(), ver.record().version.to_string())
}
LockedPackageRef::Pypi(ver, _) => {
(p.name().to_string(), ver.version.to_string())
}
})
})
.collect::<Vec<_>>()
})
.unique()
.sorted()
.collect();
let package_items: Vec<String> = packages
.iter()
.map(|(name, version)| format!("{name} ({version})"))
.collect();
if !packages.is_empty() {
let theme = ColorfulTheme {
active_item_style: console::Style::new().for_stderr().magenta(),
..ColorfulTheme::default()
};

let prompt = "Select packages to update (space to select, enter to confirm):";
let selections = MultiSelect::with_theme(&theme)
.with_prompt(prompt)
.items(&package_items)
.interact()
.expect("Failed to load packages.");

if !selections.is_empty() {
let selected: HashSet<String> = selections
.into_iter()
.map(|i| packages[i].0.clone())
.collect();
specs.packages = Some(selected);
}
}
}

// If the user specified a package name, check to see if it is even locked.
if let Some(packages) = &specs.packages {
for package in packages {
Expand Down
Loading