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