1use std::fmt::Write as _;
16use std::io::Write as _;
17
18use clap::builder::PossibleValue;
19use clap::builder::StyledStr;
20use clap::error::ContextKind;
21use crossterm::style::Stylize as _;
22use itertools::Itertools as _;
23use tracing::instrument;
24
25use crate::cli_util::CommandHelper;
26use crate::command_error::CommandError;
27use crate::command_error::cli_error;
28use crate::ui::Ui;
29
30#[derive(clap::Args, Clone, Debug)]
32pub(crate) struct HelpArgs {
33 pub(crate) command: Vec<String>,
35
36 #[arg(
38 long,
39 short = 'k',
40 conflicts_with = "command",
41 value_parser = KEYWORDS
42 .iter()
43 .map(|k| PossibleValue::new(k.name).help(k.description))
44 .collect_vec()
45 )]
46 pub(crate) keyword: Option<String>,
47}
48
49#[instrument(skip_all)]
50pub(crate) async fn cmd_help(
51 ui: &mut Ui,
52 command: &CommandHelper,
53 args: &HelpArgs,
54) -> Result<(), CommandError> {
55 if let Some(name) = &args.keyword {
56 let keyword = find_keyword(name).expect("clap should check this with `value_parser`");
57 ui.request_pager();
58 write!(ui.stdout(), "{}", keyword.content)?;
59
60 return Ok(());
61 }
62
63 let bin_name = command
64 .string_args()
65 .first()
66 .map_or(command.app().get_name(), |name| name.as_ref());
67 let mut args_to_get_command = vec![bin_name];
68 args_to_get_command.extend(args.command.iter().map(|s| s.as_str()));
69
70 let mut app = command.app().clone();
71 if let Err(err) = app.try_get_matches_from_mut(args_to_get_command) {
74 if err.get(ContextKind::InvalidSubcommand).is_some() {
75 return Err(err.into());
76 } else {
77 }
79 }
80 let command = args
81 .command
82 .iter()
83 .try_fold(&mut app, |cmd, name| cmd.find_subcommand_mut(name))
84 .ok_or_else(|| cli_error(format!("Unknown command: {}", args.command.join(" "))))?;
85
86 ui.request_pager();
87 let help_text = command.render_long_help();
88 if ui.color() {
89 write!(ui.stdout(), "{}", help_text.ansi())?;
90 } else {
91 write!(ui.stdout(), "{help_text}")?;
92 }
93 Ok(())
94}
95
96#[derive(Clone)]
97struct Keyword {
98 name: &'static str,
99 description: &'static str,
100 content: &'static str,
101}
102
103const KEYWORDS: &[Keyword] = &[
117 Keyword {
118 name: "bookmarks",
119 description: "Named pointers to revisions (similar to Git's branches)",
120 content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "bookmarks.md")),
121 },
122 Keyword {
123 name: "config",
124 description: "How and where to set configuration options",
125 content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "config.md")),
126 },
127 Keyword {
128 name: "filesets",
129 description: "A functional language for selecting a set of files",
130 content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "filesets.md")),
131 },
132 Keyword {
133 name: "glossary",
134 description: "Definitions of various terms",
135 content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "glossary.md")),
136 },
137 Keyword {
138 name: "revsets",
139 description: "A functional language for selecting a set of revision",
140 content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "revsets.md")),
141 },
142 Keyword {
143 name: "templates",
144 description: "A functional language to customize command output",
145 content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "templates.md")),
146 },
147 Keyword {
148 name: "tutorial",
149 description: "Show a tutorial to get started with jj",
150 content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "tutorial.md")),
151 },
152];
153
154fn find_keyword(name: &str) -> Option<&Keyword> {
155 KEYWORDS.iter().find(|keyword| keyword.name == name)
156}
157
158pub fn show_keyword_hint_after_help() -> StyledStr {
159 let mut ret = StyledStr::new();
160 writeln!(
161 ret,
162 "{} lists available keywords. Use {} to show help for one of these keywords.",
163 "'jj help --help'".bold(),
164 "'jj help -k'".bold(),
165 )
166 .unwrap();
167 ret
168}