1use std::collections::HashSet;
4use atty::{self, Stream};
6use clap::{App as ClapApp, AppSettings, Arg, ArgGroup, ArgMatches, SubCommand};
13use assets::BAT_THEME_DEFAULT;
15use line_range::LineRange;
16use style::{OutputComponent, OutputComponents, OutputWrap};
18#[derive(Debug, Clone, Copy)]
25#[derive(Debug, Clone, Copy, PartialEq)]
26pub enum InputFile<'a> {
33pub struct Config<'a> {
34 /// List of files to print
35 pub files: Vec<InputFile<'a>>,
37 /// The explicitly configured language, if any
38 pub language: Option<&'a str>,
40 /// The character width of the terminal
41 pub term_width: usize,
43 /// Whether or not to simply loop through all input (`cat` mode)
44 pub loop_through: bool,
46 /// Whether or not the output should be colorized
47 pub colored_output: bool,
49 /// Whether or not the output terminal supports true color
52 /// Style elements (grid, line numbers, ...)
53 pub output_components: OutputComponents,
55 /// Text wrapping mode
56 pub output_wrap: OutputWrap,
59 pub paging_mode: PagingMode,
61 /// The range lines that should be printed, if specified
62 pub line_range: Option<LineRange>,
64 /// The syntax highlighting theme
68fn is_truecolor_terminal() -> bool {
70 .map(|colorterm| colorterm == "truecolor" || colorterm == "24bit")
74/// Helper function that should might appear in Rust stable at some point
75/// (https://doc.rust-lang.org/stable/std/option/enum.Option.html#method.transpose)
76fn transpose<T>(opt: Option<Result<T>>) -> Result<Option<T>> {
77 opt.map_or(Ok(None), |res| res.map(Some))
81 pub matches: ArgMatches<'static>,
82 interactive_output: bool,
86 pub fn new() -> Self {
87 let interactive_output = atty::is(Stream::Stdout);
90 let interactive_output = interactive_output && ansi_term::enable_ansi_support().is_ok();
93 matches: Self::matches(interactive_output),
98 fn matches(interactive_output: bool) -> ArgMatches<'static> {
99 let clap_color_setting = if interactive_output {
100 AppSettings::ColoredHelp
102 AppSettings::ColorNever
105 ClapApp::new(crate_name!())
106 .version(crate_version!())
107 .global_setting(clap_color_setting)
108 .global_setting(AppSettings::DeriveDisplayOrder)
109 .global_setting(AppSettings::UnifiedHelpMessage)
110 .global_setting(AppSettings::NextLineHelp)
111 .setting(AppSettings::InferSubcommands)
112 .setting(AppSettings::ArgsNegateSubcommands)
113 .setting(AppSettings::DisableHelpSubcommand)
114 .setting(AppSettings::VersionlessSubcommands)
117 "A cat(1) clone with wings.\n\n\
118 Use '--help' instead of '-h' to see a more detailed version of the help text.",
119 ).long_about("A cat(1) clone with syntax highlighting and Git integration.")
121 Arg::with_name("FILE")
122 .help("File(s) to print / concatenate. Use '-' for standard input.")
124 "File(s) to print / concatenate. Use a dash ('-') or no argument at all \
125 to read from standard input.",
127 .empty_values(false),
130 Arg::with_name("language")
133 .overrides_with("language")
134 .help("Set the language for syntax highlighting.")
136 "Explicitly set the language for syntax highlighting. The language can be \
137 specified as a name (like 'C++' or 'LaTeX') or possible file extension \
138 (like 'cpp', 'hpp' or 'md'). Use '--list-languages' to show all supported \
139 language names and file extensions."
142 Arg::with_name("list-languages")
143 .long("list-languages")
144 .conflicts_with("list-themes")
145 .help("Display all supported languages.")
146 .long_help("Display a list of supported languages for syntax highlighting."),
148 Arg::with_name("theme")
150 .overrides_with("theme")
152 .help("Set the color theme for syntax highlighting.")
154 "Set the theme for syntax highlighting. Use '--list-themes' to \
155 see all available themes. To set a default theme, export the \
156 BAT_THEME environment variable (e.g.: export \
157 BAT_THEME=\"TwoDark\").",
160 Arg::with_name("list-themes")
162 .help("Display all supported highlighting themes.")
163 .long_help("Display a list of supported themes for syntax highlighting."),
165 Arg::with_name("style")
167 .value_name("style-components")
171 "auto", "full", "plain", "changes", "header", "grid", "numbers",
172 ]).default_value("auto")
173 .help("Comma-separated list of style elements to display.")
175 "Configure which elements (line numbers, file headers, grid \
176 borders, Git modifications, ..) to display in addition to the \
177 file contents. The argument is a comma-separated list of \
178 components to display (e.g. 'numbers,changes,grid') or a \
179 pre-defined style ('full')",
182 Arg::with_name("plain")
183 .overrides_with("plain")
186 .conflicts_with("style")
187 .conflicts_with("number")
188 .help("Show plain style (alias for '--style=plain').")
190 "Only show plain style, no decorations. This is an alias for \
194 Arg::with_name("number")
196 .overrides_with("number")
198 .conflicts_with("style")
199 .help("Show line numbers (alias for '--style=numbers').")
201 "Only show line numbers, no other decorations. This is an alias for \
205 Arg::with_name("line-range")
207 .overrides_with("line-range")
210 .help("Only print the lines from N to M.")
212 "Only print the specified range of lines for each file. \
214 '--line-range 30:40' prints lines 30 to 40\n \
215 '--line-range :40' prints lines 1 to 40\n \
216 '--line-range 40:' prints lines 40 to the end of the file",
219 Arg::with_name("color")
221 .overrides_with("color")
224 .possible_values(&["auto", "never", "always"])
225 .default_value("auto")
226 .help("When to use colors.")
227 .long_help("Specify when to use colored output. The automatic mode \
228 only enables colors if an interactive terminal is detected."),
230 Arg::with_name("decorations")
232 .overrides_with("decorations")
235 .possible_values(&["auto", "never", "always"])
236 .default_value("auto")
237 .help("When to show the decorations specified by '--style'.")
238 .long_help("Specify when to use the decorations that have been specified \
239 via '--style'. The automatic mode only enables decorations if \
240 an interactive terminal is detected."),
242 Arg::with_name("paging")
244 .overrides_with("paging")
247 .possible_values(&["auto", "never", "always"])
248 .default_value("auto")
249 .help("Specify when to use the pager.")
250 .long_help("Specify when to use the pager. To control which pager \
251 is used, set the PAGER or BAT_PAGER environment \
252 variables (the latter takes precedence). The default \
253 pager is 'less'. To disable the pager permanently, set \
254 BAT_PAGER to an empty string."),
256 Arg::with_name("wrap")
258 .overrides_with("wrap")
261 .possible_values(&["character", "never"])
262 .default_value("character")
263 .help("Specify the text-wrapping mode.")
264 .long_help("Specify the text-wrapping mode."),
266 Arg::with_name("unbuffered")
268 .hidden_short_help(true)
270 "This option exists for POSIX-compliance reasons ('u' is for \
271 'unbuffered'). The output is always unbuffered - this option \
275 SubCommand::with_name("cache")
276 .about("Modify the syntax-definition and theme cache")
278 Arg::with_name("init")
281 .help("Initialize the syntax/theme cache.")
283 "Initialize the syntax/theme cache by loading from the \
284 source directory (default: the configuration directory).",
287 Arg::with_name("clear")
290 .help("Remove the cached syntax definitions and themes."),
292 Arg::with_name("config-dir")
295 .help("Show bat's configuration directory."),
297 ArgGroup::with_name("cache-actions")
298 .args(&["init", "clear", "config-dir"])
301 Arg::with_name("source")
306 .help("Use a different directory to load syntaxes and themes from."),
308 Arg::with_name("target")
314 "Use a different directory to store the cached syntax and theme set.",
317 Arg::with_name("blank")
320 .help("Create completely new syntax and theme sets \
321 (instead of appending to the default sets).")
323 ).help_message("Print this help message.")
324 .version_message("Show version information.")
328 pub fn config(&self) -> Result<Config> {
329 let files = self.files();
332 true_color: is_truecolor_terminal(),
333 output_components: self.output_components()?,
334 language: self.matches.value_of("language"),
335 output_wrap: if !self.interactive_output {
336 // We don't have the tty width when piping to another program.
337 // There's no point in wrapping when this is the case.
340 match self.matches.value_of("wrap") {
341 Some("character") => OutputWrap::Character,
342 Some("never") | _ => OutputWrap::None,
345 colored_output: match self.matches.value_of("color") {
346 Some("always") => true,
347 Some("never") => false,
348 Some("auto") | _ => self.interactive_output,
350 paging_mode: match self.matches.value_of("paging") {
351 Some("always") => PagingMode::Always,
352 Some("never") => PagingMode::Never,
353 Some("auto") | _ => if files.contains(&InputFile::StdIn) {
354 // If we are reading from stdin, only enable paging if we write to an
355 // interactive terminal and if we do not *read* from an interactive
357 if self.interactive_output && !atty::is(Stream::Stdin) {
358 PagingMode::QuitIfOneScreen
363 if self.interactive_output {
364 PagingMode::QuitIfOneScreen
370 term_width: Term::stdout().size().1 as usize,
371 loop_through: !(self.interactive_output
372 || self.matches.value_of("color") == Some("always")
373 || self.matches.value_of("decorations") == Some("always")),
379 .or_else(|| env::var("BAT_THEME").ok())
380 .unwrap_or(String::from(BAT_THEME_DEFAULT)),
381 line_range: transpose(self.matches.value_of("line-range").map(LineRange::from))?,
385 fn files(&self) -> Vec<InputFile> {
394 InputFile::Ordinary(filename)
397 }).unwrap_or_else(|| vec![InputFile::StdIn])
400 fn output_components(&self) -> Result<OutputComponents> {
401 let matches = &self.matches;
403 if matches.value_of("decorations") == Some("never") {
405 } else if matches.is_present("number") {
406 [OutputComponent::Numbers].iter().cloned().collect()
407 } else if matches.is_present("plain") {
408 [OutputComponent::Plain].iter().cloned().collect()
410 values_t!(matches.values_of("style"), OutputComponent)?
412 .map(|style| style.components(self.interactive_output))
413 .fold(HashSet::new(), |mut acc, components| {
414 acc.extend(components.iter().cloned());
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list