167 lines · 5.3 KB · Rust
RawDownload
// Copyright 2024 The Jujutsu Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License. use std::fmt::Write as _;use std::io::Write as _; use clap::builder::PossibleValue;use clap::builder::StyledStr;use clap::error::ContextKind;use crossterm::style::Stylize as _;use itertools::Itertools as _;use tracing::instrument; use crate::cli_util::CommandHelper;use crate::command_error::CommandError;use crate::command_error::cli_error;use crate::ui::Ui; /// Print this message or the help of the given subcommand(s)#[derive(clap::Args, Clone, Debug)]pub(crate) struct HelpArgs {    /// Print help for the subcommand(s)    pub(crate) command: Vec<String>,    /// Show help for keywords instead of commands    #[arg(        long,        short = 'k',        conflicts_with = "command",        value_parser = KEYWORDS            .iter()            .map(|k| PossibleValue::new(k.name).help(k.description))            .collect_vec()    )]    pub(crate) keyword: Option<String>,} #[instrument(skip_all)]pub(crate) fn cmd_help(    ui: &mut Ui,    command: &CommandHelper,    args: &HelpArgs,) -> Result<(), CommandError> {    if let Some(name) = &args.keyword {        let keyword = find_keyword(name).expect("clap should check this with `value_parser`");        ui.request_pager();        write!(ui.stdout(), "{}", keyword.content)?;         return Ok(());    }     let bin_name = command        .string_args()        .first()        .map_or(command.app().get_name(), |name| name.as_ref());    let mut args_to_get_command = vec![bin_name];    args_to_get_command.extend(args.command.iter().map(|s| s.as_str()));     let mut app = command.app().clone();    // This propagates global arguments to subcommand, and generates error if    // the subcommand doesn't exist.    if let Err(err) = app.try_get_matches_from_mut(args_to_get_command) {        if err.get(ContextKind::InvalidSubcommand).is_some() {            return Err(err.into());        } else {            // `help log -- -r`, etc. shouldn't generate an argument error.        }    }    let command = args        .command        .iter()        .try_fold(&mut app, |cmd, name| cmd.find_subcommand_mut(name))        .ok_or_else(|| cli_error(format!("Unknown command: {}", args.command.join(" "))))?;     ui.request_pager();    let help_text = command.render_long_help();    if ui.color() {        write!(ui.stdout(), "{}", help_text.ansi())?;    } else {        write!(ui.stdout(), "{help_text}")?;    }    Ok(())} #[derive(Clone)]struct Keyword {    name: &'static str,    description: &'static str,    content: &'static str,} // TODO: Add all documentation to keywords//// Maybe adding some code to build.rs to find all the docs files and build the// `KEYWORDS` at compile time.//// It would be cool to follow the docs hierarchy somehow.//// One of the problems would be `config.md`, as it has the same name as a// subcommand.//// TODO: Find a way to render markdown using ANSI escape codes.//// Maybe we can steal some ideas from https://github.com/jj-vcs/jj/pull/3130const KEYWORDS: &[Keyword] = &[    Keyword {        name: "bookmarks",        description: "Named pointers to revisions (similar to Git's branches)",        content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "bookmarks.md")),    },    Keyword {        name: "config",        description: "How and where to set configuration options",        content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "config.md")),    },    Keyword {        name: "filesets",        description: "A functional language for selecting a set of files",        content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "filesets.md")),    },    Keyword {        name: "glossary",        description: "Definitions of various terms",        content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "glossary.md")),    },    Keyword {        name: "revsets",        description: "A functional language for selecting a set of revision",        content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "revsets.md")),    },    Keyword {        name: "templates",        description: "A functional language to customize command output",        content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "templates.md")),    },    Keyword {        name: "tutorial",        description: "Show a tutorial to get started with jj",        content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "tutorial.md")),    },]; fn find_keyword(name: &str) -> Option<&Keyword> {    KEYWORDS.iter().find(|keyword| keyword.name == name)} pub fn show_keyword_hint_after_help() -> StyledStr {    let mut ret = StyledStr::new();    writeln!(        ret,        "{} lists available keywords. Use {} to show help for one of these keywords.",        "'jj help --help'".bold(),        "'jj help -k'".bold(),    )    .unwrap();    ret}