From 152b14bb2e1b877047563a6c67edb15b5648d7d2 Mon Sep 17 00:00:00 2001 From: jdx <216188+jdx@users.noreply.github.com> Date: Sat, 14 Dec 2024 17:41:48 -0600 Subject: [PATCH] feat: added selector for `mise use` with no args (#3570) --- build.rs | 8 +++++++- docs/cli/use.md | 6 ++++++ mise.usage.kdl | 3 +++ src/cli/run.rs | 14 ++++++++++---- src/cli/upgrade.rs | 11 +++++++++-- src/cli/use.rs | 43 +++++++++++++++++++++++++++++++++++-------- src/registry.rs | 8 ++++++++ 7 files changed, 78 insertions(+), 15 deletions(-) diff --git a/build.rs b/build.rs index d43ed03aa8..9484e293e1 100644 --- a/build.rs +++ b/build.rs @@ -78,6 +78,9 @@ fn codegen_registry() { os }) .unwrap_or_default(); + let description = info + .get("description") + .map(|d| d.as_str().unwrap().to_string()); let depends = info .get("depends") .map(|depends| { @@ -102,7 +105,10 @@ fn codegen_registry() { }) .unwrap_or_default(); let rt = format!( - r#"RegistryTool{{short: "{short}", backends: vec!["{backends}"], aliases: &[{aliases}], test: &{test}, os: &[{os}], depends: &[{depends}], idiomatic_files: &[{idiomatic_files}]}}"#, + r#"RegistryTool{{short: "{short}", description: {description}, backends: vec!["{backends}"], aliases: &[{aliases}], test: &{test}, os: &[{os}], depends: &[{depends}], idiomatic_files: &[{idiomatic_files}]}}"#, + description = description + .map(|d| format!("Some(\"{}\")", d)) + .unwrap_or("None".to_string()), backends = fulls.join("\", \""), aliases = aliases .iter() diff --git a/docs/cli/use.md b/docs/cli/use.md index 00aafa660e..64c7d21520 100644 --- a/docs/cli/use.md +++ b/docs/cli/use.md @@ -84,6 +84,12 @@ Consider using mise.lock as a better alternative to pinning in mise.toml: Examples: +``` + +# run with no arguments to use the interactive selector +$ mise use +``` + ``` # set the current version of node to 20.x in mise.toml of current directory # will write the fuzzy version (e.g.: 20) diff --git a/mise.usage.kdl b/mise.usage.kdl index e45ff0288b..556a241b08 100644 --- a/mise.usage.kdl +++ b/mise.usage.kdl @@ -1573,6 +1573,9 @@ In the following order: Use the `--global` flag to use the global config file instead."# after_long_help r"Examples: + + # run with no arguments to use the interactive selector + $ mise use # set the current version of node to 20.x in mise.toml of current directory # will write the fuzzy version (e.g.: 20) diff --git a/src/cli/run.rs b/src/cli/run.rs index 15bf73a707..4160fb574e 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -18,6 +18,7 @@ use crate::toolset::{InstallOptions, ToolsetBuilder}; use crate::ui::{ctrlc, prompt, style, time}; use crate::{dirs, env, exit, file, ui}; use clap::{CommandFactory, ValueHint}; +use console::Term; use crossbeam_channel::{select, unbounded}; use demand::{DemandOption, Select}; use duct::IntoExecutablePath; @@ -865,10 +866,15 @@ fn prompt_for_task() -> Result { s = s.option(DemandOption::new(&t.name).description(&t.description)); } ctrlc::show_cursor_after_ctrl_c(); - let name = s.run()?; - match tasks.get(name) { - Some(task) => Ok((*task).clone()), - None => bail!("no tasks {} found", style::ered(name)), + match s.run() { + Ok(name) => match tasks.get(name) { + Some(task) => Ok(task.clone()), + None => bail!("no tasks {} found", style::ered(name)), + }, + Err(err) => { + Term::stderr().show_cursor()?; + Err(eyre!(err)) + } } } diff --git a/src/cli/upgrade.rs b/src/cli/upgrade.rs index e3884c5201..b3a2723b2b 100644 --- a/src/cli/upgrade.rs +++ b/src/cli/upgrade.rs @@ -7,8 +7,9 @@ use crate::toolset::{InstallOptions, ResolveOptions, ToolVersion, ToolsetBuilder use crate::ui::multi_progress_report::MultiProgressReport; use crate::ui::progress_report::SingleReport; use crate::{config, ui}; +use console::Term; use demand::DemandOption; -use eyre::{Context, Result}; +use eyre::{eyre, Context, Result}; /// Upgrades outdated tools /// @@ -186,7 +187,13 @@ impl Upgrade { for out in outdated { ms = ms.option(DemandOption::new(out.clone())); } - Ok(ms.run()?.into_iter().collect()) + match ms.run() { + Ok(selected) => Ok(selected.into_iter().collect()), + Err(e) => { + Term::stderr().show_cursor()?; + Err(eyre!(e)) + } + } } } diff --git a/src/cli/use.rs b/src/cli/use.rs index 4055ba8bc6..84de5ab93e 100644 --- a/src/cli/use.rs +++ b/src/cli/use.rs @@ -1,7 +1,7 @@ use std::path::{Path, PathBuf}; -use console::style; -use eyre::Result; +use console::{style, Term}; +use eyre::{eyre, Result}; use itertools::Itertools; use path_absolutize::Absolutize; @@ -12,9 +12,11 @@ use crate::env::{ MISE_DEFAULT_CONFIG_FILENAME, MISE_DEFAULT_TOOL_VERSIONS_FILENAME, MISE_GLOBAL_CONFIG_FILE, }; use crate::file::display_path; +use crate::registry::REGISTRY; use crate::toolset::{ InstallOptions, ResolveOptions, ToolRequest, ToolSource, ToolVersion, ToolsetBuilder, }; +use crate::ui::ctrlc; use crate::ui::multi_progress_report::MultiProgressReport; use crate::{config, env, file}; @@ -41,11 +43,7 @@ pub struct Use { /// Tool options can be set with this syntax: /// /// mise use ubi:BurntSushi/ripgrep[exe=rg] - #[clap( - value_name = "TOOL@VERSION", - verbatim_doc_comment, - required_unless_present = "remove" - )] + #[clap(value_name = "TOOL@VERSION", verbatim_doc_comment)] tool: Vec, /// Force reinstall even if already installed @@ -99,7 +97,10 @@ pub struct Use { } impl Use { - pub fn run(self) -> Result<()> { + pub fn run(mut self) -> Result<()> { + if self.tool.is_empty() && self.remove.is_empty() { + self.tool = vec![self.tool_selector()?]; + } let config = Config::try_get()?; let mut ts = ToolsetBuilder::new() .with_global_only(self.global) @@ -249,6 +250,29 @@ impl Use { ); Ok(()) } + + fn tool_selector(&self) -> Result { + let mut s = demand::Select::new("Select a tool to install") + .description("Select a tasks to run") + .filtering(true) + .filterable(true); + for rt in REGISTRY.values() { + if let Some(backend) = rt.backends().first() { + // TODO: populate registry with descriptions from aqua and other sources + // TODO: use the backend from the lockfile if available + let description = rt.description.unwrap_or(backend); + s = s.option(demand::DemandOption::new(rt).description(description)); + } + } + ctrlc::show_cursor_after_ctrl_c(); + match s.run() { + Ok(rt) => rt.short.parse(), + Err(err) => { + Term::stderr().show_cursor()?; + Err(eyre!(err)) + } + } + } } fn config_file_from_dir(p: &Path) -> PathBuf { @@ -270,6 +294,9 @@ fn config_file_from_dir(p: &Path) -> PathBuf { static AFTER_LONG_HELP: &str = color_print::cstr!( r#"Examples: + + # run with no arguments to use the interactive selector + $ mise use # set the current version of node to 20.x in mise.toml of current directory # will write the fuzzy version (e.g.: 20) diff --git a/src/registry.rs b/src/registry.rs index 135811d632..88ab7c87d0 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -4,6 +4,7 @@ use crate::config::SETTINGS; use once_cell::sync::Lazy; use std::collections::{BTreeMap, HashMap, HashSet}; use std::env::consts::OS; +use std::fmt::Display; use std::iter::Iterator; use strum::IntoEnumIterator; use url::Url; @@ -15,6 +16,7 @@ pub static REGISTRY: Lazy> = #[derive(Debug, Clone)] pub struct RegistryTool { pub short: &'static str, + pub description: Option<&'static str>, pub backends: Vec<&'static str>, #[allow(unused)] pub aliases: &'static [&'static str], @@ -114,3 +116,9 @@ fn url_like(s: &str) -> bool { || s.starts_with("ssh://") || s.starts_with("git://") } + +impl Display for RegistryTool { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.short) + } +}