+ use clap::parser::ValueSource;
+ use clap::{value_parser, Arg};
+ use xilem_html::{elements as el, Adapt, MessageResult, View, ViewMarker, ViewSequence};
+
+ use crate::AppState;
+
+ #[non_exhaustive]
+ enum ArgType {
+ String,
+ Other,
+ }
+
+ impl From<&Arg> for ArgType {
+ fn from(value: &Arg) -> Self {
+ let type_id = value.get_value_parser().type_id();
+ if type_id == value_parser!(String).type_id() {
+ Self::String
+ } else {
+ Self::Other
+ }
+ }
+ }
+
+ fn type_description(arg: &Arg) -> impl View<Arg> + ViewMarker {
+ let supported_type_description = ArgType::from(arg);
+ el::div((
+ match supported_type_description {
+ ArgType::String => "string",
+ ArgType::Other => "other",
+ },
+ el::div("placeholder content").attr("class", "hover_content"),
+ ))
+ .attr("class", "hoverable")
+ }
+
+ fn env(arg: &Arg) -> impl ViewSequence<Arg> {
+ arg.get_env()
+ .map(|variable| variable.to_string_lossy().to_string())
+ }
+
+ fn aliases(arg: &Arg) -> impl ViewSequence<Arg> {
+ let aliases = if let Some(aliases) = arg.get_all_aliases() {
+ aliases
+ .iter()
+ .map(|alias| alias.to_string())
+ .filter(|alias| alias != arg.get_id())
+ .collect::<Vec<String>>()
+ } else {
+ // Ignore if there's no aliases
+ return None;
+ };
+
+ // Ignore if all aliases have been filtered out.
+ // This will usually happen when there is 1 alias returned by get_all_aliases,
+ // which is the long argument version, which is equivalent to the argument id
+ // and is therefore redundant
+ if aliases.is_empty() {
+ return None;
+ }
+
+ Some(el::div((
+ el::h4("Also known as:"),
+ el::ul(aliases.into_iter().map(el::li).collect::<Vec<el::Li<_>>>()),
+ )))
+ }
+
+ fn source(state: &AppState, arg: &Arg) -> impl View<Arg> + ViewMarker {
+ String::from(if let Ok(arg_matches) = state.arg_matches.as_ref() {
+ if let Some(source) = arg_matches.value_source(arg.get_id().as_str()) {
+ match source {
+ ValueSource::DefaultValue => "default",
+ ValueSource::EnvVariable => "environment",
+ ValueSource::CommandLine => "cmdline",
+ _ => "todo",
+ }
+ } else {
+ "none"
+ }
+ } else {
+ "error"
+ })
+ }
+
+ fn matched_values(state: &AppState, arg: &Arg) -> impl View<Arg> + ViewMarker {
+ el::ul(
+ state
+ .arg_matches
+ .as_ref()
+ .iter()
+ .map(|arg_matches| {
+ arg_matches
+ .get_raw_occurrences(&arg.get_id().as_str())
+ .unwrap_or_default()
+ .map(|os_strings| {
+ el::li(
+ os_strings
+ .map(|s| s.to_string_lossy().to_string())
+ .collect::<Vec<String>>()
+ .join(" "),
+ )
+ })
+ })
+ .flatten()
+ .collect::<Vec<el::Li<_>>>(),
+ )
+ }
+
+ fn item(state: &AppState, arg: &Arg) -> impl View<Arg> + ViewMarker {
+ el::div((
+ // Name of the argument
+ el::h3(arg.get_id().to_string().to_uppercase()),
+ // The argument's type info
+ type_description(arg),
+ el::br(()),
+ // Alternate sources
+ el::div((env(arg), aliases(arg))),
+ // Hand-written documentation
+ el::div((
+ // One-line short help
+ arg.get_help()
+ .map(|short_help| el::i(short_help.to_string())),
+ el::br(()),
+ // Long-form help
+ arg.get_long_help()
+ .map(|long_help| el::q(long_help.to_string())),
+ )),
+ // Source location (defaults, command line etc)
+ source(state, arg),
+ // Number of allowed arguments
+ arg.get_num_args().map(|arg_range| arg_range.to_string()),
+ // Matched values
+ matched_values(state, arg),
+ ))
+ }
+
+ pub fn args_view(state: &mut AppState) -> impl View<AppState> + ViewMarker {
+ el::div(if state.arg_matches.is_ok() {
+ state
+ .command
+ .get_opts()
+ .map(|arg| {
+ Adapt::new(
+ |_state, _thunk| {
+ // TODO: actually re-compute this value
+ MessageResult::Nop
+ },
+ item(state, arg),
+ )
+ })
+ .collect::<Vec<_>>()
+ } else {
+ Vec::new()
+ })
+ .attr("id", "sidebar")
+ }