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

feat(help): Allow customizing terminal styling (unstable) #4843

Merged
merged 13 commits into from Apr 18, 2023
1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -94,6 +94,7 @@ string = ["clap_builder/string"] # Allow runtime generated strings

# In-work features
unstable-v5 = ["clap_builder/unstable-v5", "clap_derive?/unstable-v5", "deprecated"]
unstable-styles = ["clap_builder/unstable-styles"]

[lib]
bench = false
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Expand Up @@ -16,7 +16,7 @@ _FEATURES = minimal default wasm full debug release
_FEATURES_minimal = --no-default-features --features "std"
_FEATURES_default =
_FEATURES_wasm = --no-default-features --features "std help usage error-context suggestions" --features "deprecated derive cargo env unicode string"
_FEATURES_full = --features "deprecated derive cargo env unicode string wrap_help"
_FEATURES_full = --features "deprecated derive cargo env unicode string wrap_help unstable-styles"
_FEATURES_next = ${_FEATURES_full} --features unstable-v5
_FEATURES_debug = ${_FEATURES_full} --features debug --features clap_complete/debug
_FEATURES_release = ${_FEATURES_full} --release
Expand Down
9 changes: 5 additions & 4 deletions clap_builder/Cargo.toml
Expand Up @@ -32,11 +32,11 @@ tag-name = "v{{version}}"
[features]
default = ["std", "color", "help", "usage", "error-context", "suggestions"]
debug = ["dep:backtrace"] # Enables debug messages
unstable-doc = ["cargo", "wrap_help", "env", "unicode", "string"] # for docs.rs
unstable-doc = ["cargo", "wrap_help", "env", "unicode", "string", "unstable-styles"] # for docs.rs

# Used in default
std = [] # support for no_std in a backwards-compatible way
color = ["dep:anstyle", "dep:anstream"]
std = ["anstyle/std"] # support for no_std in a backwards-compatible way
color = ["dep:anstream"]
help = []
usage = []
error-context = []
Expand All @@ -52,6 +52,7 @@ string = [] # Allow runtime generated strings

# In-work features
unstable-v5 = ["deprecated"]
unstable-styles = ["color"]

[lib]
bench = false
Expand All @@ -62,7 +63,7 @@ bitflags = "1.2.0"
unicase = { version = "2.6.0", optional = true }
strsim = { version = "0.10.0", optional = true }
anstream = { version = "0.3.0", optional = true }
anstyle = { version = "1.0.0", features = ["std"], optional = true }
anstyle = "1.0.0"
terminal_size = { version = "0.2.1", optional = true }
backtrace = { version = "0.3.67", optional = true }
unicode-width = { version = "0.1.9", optional = true }
Expand Down
61 changes: 44 additions & 17 deletions clap_builder/src/builder/arg.rs
Expand Up @@ -17,6 +17,7 @@ use crate::builder::OsStr;
use crate::builder::PossibleValue;
use crate::builder::Str;
use crate::builder::StyledStr;
use crate::builder::Styles;
use crate::builder::ValueRange;
use crate::util::AnyValueId;
use crate::ArgAction;
Expand Down Expand Up @@ -4271,49 +4272,74 @@ impl Arg {
}
}

pub(crate) fn stylized(&self, required: Option<bool>) -> StyledStr {
pub(crate) fn stylized(&self, styles: &Styles, required: Option<bool>) -> StyledStr {
use std::fmt::Write as _;
let literal = styles.get_literal();

let mut styled = StyledStr::new();
// Write the name such --long or -l
if let Some(l) = self.get_long() {
styled.literal("--");
styled.literal(l);
let _ = write!(
styled,
"{}--{l}{}",
literal.render(),
literal.render_reset()
);
} else if let Some(s) = self.get_short() {
styled.literal("-");
styled.literal(s);
let _ = write!(styled, "{}-{s}{}", literal.render(), literal.render_reset());
}
styled.push_styled(&self.stylize_arg_suffix(required));
styled.push_styled(&self.stylize_arg_suffix(styles, required));
styled
}

pub(crate) fn stylize_arg_suffix(&self, required: Option<bool>) -> StyledStr {
pub(crate) fn stylize_arg_suffix(&self, styles: &Styles, required: Option<bool>) -> StyledStr {
use std::fmt::Write as _;
let literal = styles.get_literal();
let placeholder = styles.get_placeholder();
let mut styled = StyledStr::new();

let mut need_closing_bracket = false;
if self.is_takes_value_set() && !self.is_positional() {
let is_optional_val = self.get_min_vals() == 0;
if self.is_require_equals_set() {
let (style, start) = if self.is_require_equals_set() {
if is_optional_val {
need_closing_bracket = true;
styled.placeholder("[=");
(placeholder, "[=")
} else {
styled.literal("=");
(literal, "=")
}
} else if is_optional_val {
need_closing_bracket = true;
styled.placeholder(" [");
(placeholder, " [")
} else {
styled.placeholder(" ");
}
(placeholder, " ")
};
let _ = write!(styled, "{}{start}{}", style.render(), style.render_reset());
}
if self.is_takes_value_set() || self.is_positional() {
let required = required.unwrap_or_else(|| self.is_required_set());
let arg_val = self.render_arg_val(required);
styled.placeholder(arg_val);
let _ = write!(
styled,
"{}{arg_val}{}",
placeholder.render(),
placeholder.render_reset()
);
} else if matches!(*self.get_action(), ArgAction::Count) {
styled.placeholder("...");
let _ = write!(
styled,
"{}...{}",
placeholder.render(),
placeholder.render_reset()
);
}
if need_closing_bracket {
styled.placeholder("]");
let _ = write!(
styled,
"{}]{}",
placeholder.render(),
placeholder.render_reset()
);
}

styled
Expand Down Expand Up @@ -4401,7 +4427,8 @@ impl Eq for Arg {}

impl Display for Arg {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
self.stylized(None).fmt(f)
let plain = Styles::plain();
self.stylized(&plain, None).fmt(f)
}
}

Expand Down
38 changes: 34 additions & 4 deletions clap_builder/src/builder/command.rs
Expand Up @@ -17,6 +17,7 @@ use crate::builder::IntoResettable;
use crate::builder::PossibleValue;
use crate::builder::Str;
use crate::builder::StyledStr;
use crate::builder::Styles;
use crate::builder::{Arg, ArgGroup, ArgPredicate};
use crate::error::ErrorKind;
use crate::error::Result as ClapResult;
Expand Down Expand Up @@ -1080,6 +1081,31 @@ impl Command {
}
}

/// Sets when to color output.
///
/// **NOTE:** This choice is propagated to all child subcommands.
///
/// **NOTE:** Default behaviour is [`ColorChoice::Auto`].
///
/// # Examples
///
/// ```no_run
/// # use clap_builder as clap;
/// # use clap::{Command, ColorChoice};
/// Command::new("myprog")
/// .color(ColorChoice::Never)
/// .get_matches();
/// ```
/// [`ColorChoice::Auto`]: crate::ColorChoice::Auto
#[cfg(feature = "color")]
#[inline]
#[must_use]
#[cfg(feature = "unstable-styles")]
pub fn styles(mut self, styles: Styles) -> Self {
self.app_ext.set(styles);
self
}

/// Sets the terminal width at which to wrap help messages.
///
/// Using `0` will ignore terminal widths and use source formatting.
Expand Down Expand Up @@ -3338,6 +3364,10 @@ impl Command {
}
}

pub(crate) fn get_styles(&self) -> &Styles {
self.app_ext.get().unwrap_or_default()
}

/// Iterate through the set of subcommands, getting a reference to each.
#[inline]
pub fn get_subcommands(&self) -> impl Iterator<Item = &Command> {
Expand Down Expand Up @@ -4321,9 +4351,9 @@ impl Command {
.collect::<Vec<_>>()
.join("|");
let mut styled = StyledStr::new();
styled.none("<");
styled.none(g_string);
styled.none(">");
styled.push_str("<");
styled.push_string(g_string);
styled.push_str(">");
styled
}
}
Expand Down Expand Up @@ -4649,7 +4679,7 @@ impl fmt::Display for Command {
}
}

trait AppTag: crate::builder::ext::Extension {}
pub(crate) trait AppTag: crate::builder::ext::Extension {}

#[derive(Default, Copy, Clone, Debug)]
struct TermWidth(usize);
Expand Down
5 changes: 5 additions & 0 deletions clap_builder/src/builder/mod.rs
Expand Up @@ -35,6 +35,8 @@ pub use range::ValueRange;
pub use resettable::IntoResettable;
pub use resettable::Resettable;
pub use styled_str::StyledStr;
#[cfg(feature = "unstable-styles")]
pub use styled_str::Styles;
pub use value_hint::ValueHint;
pub use value_parser::_AutoValueParser;
pub use value_parser::via_prelude;
Expand All @@ -59,3 +61,6 @@ pub use value_parser::_AnonymousValueParser;
pub(crate) use self::str::Inner as StrInner;
pub(crate) use action::CountType;
pub(crate) use arg_settings::{ArgFlags, ArgSettings};
pub(crate) use command::AppTag;
#[cfg(not(feature = "unstable-styles"))]
pub(crate) use styled_str::Styles;