diff --git a/CHANGELOG.md b/CHANGELOG.md index 541c51ab6d..ea523db0ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). * Timestamp objects in templates now have `after(date) -> Boolean` and `before(date) -> Boolean` methods for comparing timestamps to other dates. +* `jj help` now can give help for some categories, like `jj help revsets`. + ### Fixed bugs * Error on `trunk()` revset resolution is now handled gracefully. diff --git a/cli/src/commands/help.rs b/cli/src/commands/help.rs index 364cbe7fca..b1a852f824 100644 --- a/cli/src/commands/help.rs +++ b/cli/src/commands/help.rs @@ -12,6 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::fmt::Write as _; +use std::io::Write; + +use crossterm::style::Stylize; use tracing::instrument; use crate::cli_util::CommandHelper; @@ -28,10 +32,19 @@ pub(crate) struct HelpArgs { #[instrument(skip_all)] pub(crate) fn cmd_help( - _ui: &mut Ui, + ui: &mut Ui, command: &CommandHelper, args: &HelpArgs, ) -> Result<(), CommandError> { + if let [name] = &*args.command { + if let Some(category) = find_category(name.as_str()) { + ui.request_pager(); + write!(ui.stdout(), "{}", category.content)?; + + return Ok(()); + } + } + let mut args_to_show_help = vec![command.app().get_name()]; args_to_show_help.extend(args.command.iter().map(|s| s.as_str())); args_to_show_help.push("--help"); @@ -42,8 +55,70 @@ pub(crate) fn cmd_help( .app() .clone() .subcommand_required(true) + .after_help(format_categories(command.app())) .try_get_matches_from(args_to_show_help) .expect_err("Clap library should return a DisplayHelp error in this context"); Err(command_error::cli_error(help_err)) } + +#[derive(Clone)] +struct Category { + description: &'static str, + content: &'static str, +} + +// TODO: Add all documentation to categories +// +// Maybe adding some code to build.rs to find all the docs files and build the +// `CATEGORIES` 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/martinvonz/jj/pull/3130 +const CATEGORIES: &[(&str, Category)] = &[ + ( + "revsets", + Category { + description: "A functional language for selecting a set of revision", + content: include_str!("../../../docs/revsets.md"), + }, + ), + ( + "tutorial", + Category { + description: "Show a tutorial to get started with jj", + content: include_str!("../../../docs/tutorial.md"), + }, + ), +]; + +fn find_category(name: &str) -> Option<&Category> { + CATEGORIES + .iter() + .find(|(cat_name, _)| *cat_name == name) + .map(|tuple| &tuple.1) +} + +fn format_categories(command: &clap::Command) -> String { + let subcommand_max_len = command + .get_subcommands() + .map(|cmd| cmd.get_name().len()) + .max() + .unwrap(); + + let mut ret = String::new(); + + writeln!(ret, "{}", "Help Categories:".bold().underlined()).unwrap(); + for (name, category) in CATEGORIES { + write!(ret, " {} ", format!("{name: vec![ + (r"(?s).+(?Help Categories:.+)", "$h"), + ]}, { + insta::assert_snapshot!(help_cmd_stdout, @r#" + Help Categories: + revsets A functional language for selecting a set of revision + tutorial Show a tutorial to get started with jj + "#); + }); + + // Help command should work with category commands + let help_cmd_stdout = test_env.jj_cmd_success(test_env.env_root(), &["help", "revsets"]); + // It should be equal to the docs + assert_eq!(help_cmd_stdout, include_str!("../../docs/revsets.md")); +}