diff --git a/CHANGELOG.md b/CHANGELOG.md index b5a2dc8b5c..b67636a3df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). * `jj squash` now supports `-f/-t` shorthands for `--from/--[in]to`. +* `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..1400472275 100644 --- a/cli/src/commands/help.rs +++ b/cli/src/commands/help.rs @@ -12,6 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::HashMap; +use std::fmt::Write; + +use clap::builder::StyledStr; +use itertools::Itertools; use tracing::instrument; use crate::cli_util::CommandHelper; @@ -26,21 +31,99 @@ pub(crate) struct HelpArgs { pub(crate) command: Vec, } +struct Category { + description: &'static str, + content: &'static str, +} + #[instrument(skip_all)] pub(crate) fn cmd_help( _ui: &mut Ui, command: &CommandHelper, args: &HelpArgs, ) -> Result<(), CommandError> { + // TODO: Add all documentation to categories + // + // Maybe adding some code to build.rs to find all the docs files and build the + // HashMap 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. + let categories = HashMap::from([ + ( + "revsets", + Category { + description: "Documentation about revsets", + content: include_str!("../../../docs/revsets.md"), + }, + ), + ( + "tutorial", + Category { + description: "Show a tutorial to get started with jj", + content: include_str!("../../../docs/tutorial.md"), + }, + ), + ]); + + if let Some(category) = categories.get(args.command.join(" ").as_str()) { + let mut help_str = StyledStr::new(); + let _ = help_str.write_str(category.content); + + let help_err = clap::Command::new("stub") + .override_help(help_str) + .try_get_matches_from(vec!["stub", "--help"]) + .unwrap_err(); + + return Err(command_error::cli_error(help_err)); + } + 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"); + let help_template_before_categories = "\ + {about-with-newline}\n{usage-heading} {usage}\n\n\x1b[1m\x1b[4mCommands:\x1b[0m\n{subcommands} + "; + + let subcommand_max_len = command + .app() + .get_subcommands() + .map(|cmd| cmd.get_name().len()) + .max() + .unwrap(); + + let help_template_categories = format!( + "\x1b[1m\x1b[4mCategories:\x1b[0m\n{}", + &categories + .iter() + .map(|(&name, category)| format!( + "{{tab}}\x1b[1m{: