1use std::collections::HashSet;
5use atty::{self, Stream};
16use assets::BAT_THEME_DEFAULT;
18use line_range::LineRange;
19use style::{OutputComponent, OutputComponents, OutputWrap};
21#[derive(Debug, Clone, Copy, PartialEq)]
28#[derive(Debug, Clone, Copy, PartialEq)]
29pub enum InputFile<'a> {
36pub struct Config<'a> {
37 /// List of files to print
38 pub files: Vec<InputFile<'a>>,
40 /// The explicitly configured language, if any
41 pub language: Option<&'a str>,
43 /// The character width of the terminal
44 pub term_width: usize,
46 /// The width of tab characters.
47 /// Currently, a value of 0 will cause tabs to be passed through without expanding them.
50 /// Whether or not to simply loop through all input (`cat` mode)
51 pub loop_through: bool,
53 /// Whether or not the output should be colorized
54 pub colored_output: bool,
56 /// Whether or not the output terminal supports true color
59 /// Style elements (grid, line numbers, ...)
60 pub output_components: OutputComponents,
62 /// Text wrapping mode
63 pub output_wrap: OutputWrap,
66 pub paging_mode: PagingMode,
68 /// The range lines that should be printed, if specified
69 pub line_range: Option<LineRange>,
71 /// The syntax highlighting theme
75fn is_truecolor_terminal() -> bool {
77 .map(|colorterm| colorterm == "truecolor" || colorterm == "24bit")
81/// Helper function that might appear in Rust stable at some point
82/// (https://doc.rust-lang.org/stable/std/option/enum.Option.html#method.transpose)
83fn transpose<T>(opt: Option<Result<T>>) -> Result<Option<T>> {
84 opt.map_or(Ok(None), |res| res.map(Some))
88 pub matches: ArgMatches<'static>,
89 interactive_output: bool,
93 pub fn new() -> Self {
95 let _ = ansi_term::enable_ansi_support();
97 let interactive_output = atty::is(Stream::Stdout);
100 matches: Self::matches(interactive_output),
105 fn matches(interactive_output: bool) -> ArgMatches<'static> {
106 clap_app::build_app(interactive_output).get_matches_from(wild::args())
109 pub fn config(&self) -> Result<Config> {
110 let files = self.files();
111 let output_components = self.output_components()?;
113 let paging_mode = match self.matches.value_of("paging") {
114 Some("always") => PagingMode::Always,
115 Some("never") => PagingMode::Never,
116 Some("auto") | _ => {
117 if files.contains(&InputFile::StdIn) {
118 // If we are reading from stdin, only enable paging if we write to an
119 // interactive terminal and if we do not *read* from an interactive
121 if self.interactive_output && !atty::is(Stream::Stdin) {
122 PagingMode::QuitIfOneScreen
127 if self.interactive_output {
128 PagingMode::QuitIfOneScreen
137 true_color: is_truecolor_terminal(),
138 language: self.matches.value_of("language"),
139 output_wrap: if !self.interactive_output {
140 // We don't have the tty width when piping to another program.
141 // There's no point in wrapping when this is the case.
144 match self.matches.value_of("wrap") {
145 Some("character") => OutputWrap::Character,
146 Some("never") => OutputWrap::None,
147 Some("auto") | _ => {
148 if output_components.plain() {
151 OutputWrap::Character
156 colored_output: match self.matches.value_of("color") {
157 Some("always") => true,
158 Some("never") => false,
159 Some("auto") | _ => self.interactive_output,
164 .value_of("terminal-width")
165 .and_then(|w| w.parse().ok())
166 .unwrap_or(Term::stdout().size().1 as usize),
167 loop_through: !(self.interactive_output
168 || self.matches.value_of("color") == Some("always")
169 || self.matches.value_of("decorations") == Some("always")),
175 .or_else(|| env::var("BAT_TABS").ok())
176 .and_then(|t| t.parse().ok())
178 if output_components.plain() && paging_mode == PagingMode::Never {
188 .or_else(|| env::var("BAT_THEME").ok())
189 .unwrap_or(String::from(BAT_THEME_DEFAULT)),
190 line_range: transpose(self.matches.value_of("line-range").map(LineRange::from))?,
195 fn files(&self) -> Vec<InputFile> {
204 InputFile::Ordinary(filename)
209 .unwrap_or_else(|| vec![InputFile::StdIn])
212 fn output_components(&self) -> Result<OutputComponents> {
213 let matches = &self.matches;
215 if matches.value_of("decorations") == Some("never") {
217 } else if matches.is_present("number") {
218 [OutputComponent::Numbers].iter().cloned().collect()
219 } else if matches.is_present("plain") {
220 [OutputComponent::Plain].iter().cloned().collect()
222 let env_style_components: Option<Vec<OutputComponent>> =
223 transpose(env::var("BAT_STYLE").ok().map(|style_str| {
226 .map(|x| OutputComponent::from_str(&x))
227 .collect::<Result<Vec<OutputComponent>>>()
230 values_t!(matches.values_of("style"), OutputComponent)
232 .or(env_style_components)
233 .unwrap_or(vec![OutputComponent::Full])
235 .map(|style| style.components(self.interactive_output))
236 .fold(HashSet::new(), |mut acc, components| {
237 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