1// `error_chain!` can recurse deeply
2#![recursion_limit = "1024"]
5extern crate error_chain;
11extern crate lazy_static;
13extern crate ansi_term;
16extern crate directories;
28use std::fs::{self, File};
29use std::io::{self, BufRead, BufReader, Write};
30use std::process::{self, Child, Command, Stdio};
33use std::os::unix::fs::FileTypeExt;
35use ansi_term::Colour::{Fixed, Green, Red, White, Yellow};
38use syntect::easy::HighlightLines;
39use syntect::highlighting::Theme;
40use syntect::parsing::SyntaxSet;
42use app::{App, Config};
43use assets::{config_dir, syntax_set_path, theme_set_path, HighlightingAssets};
44use diff::get_git_diff;
64 fn pager() -> Result<Self> {
65 Ok(OutputType::Pager(Command::new("less")
66 .args(&["--quit-if-one-screen", "--RAW-CONTROL-CHARS", "--no-init"])
67 .stdin(Stdio::piped())
69 .chain_err(|| "Could not spawn pager")?))
73 OutputType::Stdout(io::stdout())
76 fn handle(&mut self) -> Result<&mut Write> {
78 OutputType::Pager(ref mut command) => command
81 .chain_err(|| "Could not open stdin for pager")?,
82 OutputType::Stdout(ref mut handle) => handle,
87impl Drop for OutputType {
89 if let OutputType::Pager(ref mut command) = *self {
90 let _ = command.wait();
95const GRID_COLOR: u8 = 238;
96const LINE_NUMBER_COLOR: u8 = 244;
102 pub git_added: Style,
103 pub git_removed: Style,
104 pub git_modified: Style,
105 pub line_number: Style,
113 fn colored() -> Self {
115 grid: Fixed(GRID_COLOR).normal(),
116 filename: White.bold(),
117 git_added: Green.normal(),
118 git_removed: Red.normal(),
119 git_modified: Yellow.normal(),
120 line_number: Fixed(LINE_NUMBER_COLOR).normal(),
128 syntax_set: &SyntaxSet,
129 printer: &mut Printer,
130 filename: Option<&str>,
132 let stdin = io::stdin(); // TODO: this is not always needed
134 let mut reader: Box<BufRead> = match filename {
135 None => Box::new(stdin.lock()),
136 Some(filename) => Box::new(BufReader::new(File::open(filename)?)),
139 let syntax = match (config.language, filename) {
140 (Some(language), _) => syntax_set.find_syntax_by_token(language),
141 (None, Some(filename)) => {
143 let may_read_from_file = true;
145 // Do not peek at the file (to determine the syntax) if it is a FIFO because they can
146 // only be read once.
148 let may_read_from_file = !fs::metadata(filename)?.file_type().is_fifo();
150 if may_read_from_file {
151 syntax_set.find_syntax_for_file(filename)?
156 (None, None) => None,
159 let syntax = syntax.unwrap_or_else(|| syntax_set.find_syntax_plain_text());
160 let mut highlighter = HighlightLines::new(syntax, theme);
162 printer.print_header(filename)?;
165 let mut line_buffer = String::new();
168 let num_bytes = reader.read_line(&mut line_buffer);
170 let line = match num_bytes {
175 if !line_buffer.ends_with('\n') {
176 line_buffer.push('\n');
180 Err(_) => "<bat: INVALID UTF-8>\n",
183 let regions = highlighter.highlight(line);
185 printer.print_line(line_nr, ®ions)?;
190 printer.print_footer()?;
195fn get_output_type(paging: bool) -> OutputType {
197 OutputType::pager().unwrap_or_else(|_| OutputType::stdout())
203fn run() -> Result<()> {
204 let app = App::new();
206 match app.matches.subcommand() {
207 ("cache", Some(cache_matches)) => {
208 if cache_matches.is_present("init") {
209 let assets = HighlightingAssets::from_files()?;
211 } else if cache_matches.is_present("clear") {
212 print!("Clearing theme set cache ... ");
213 fs::remove_file(theme_set_path()).ok();
216 print!("Clearing syntax set cache ... ");
217 fs::remove_file(syntax_set_path()).ok();
219 } else if cache_matches.is_present("config-dir") {
220 println!("{}", config_dir());
224 let config = app.config()?;
226 let assets = HighlightingAssets::new();
227 let theme = assets.get_theme(config.theme.unwrap_or("Default"))?;
229 if app.matches.is_present("list-languages") {
230 let mut languages = assets.syntax_set.syntaxes().to_owned();
231 languages.sort_by_key(|lang| lang.name.to_uppercase());
233 let longest = languages
235 .filter(|s| !s.hidden && !s.file_extensions.is_empty())
236 .map(|s| s.name.len())
238 .unwrap_or(32); // Fallback width if they have no language definitions.
241 for lang in languages {
242 if lang.hidden || lang.file_extensions.is_empty() {
245 print!("{:width$}{}", lang.name, separator, width = longest);
247 // Line-wrapping for the possible file extension overflow.
248 let desired_width = config.term_width - longest - separator.len();
249 // Number of characters on this line so far, wrap before `desired_width`
250 let mut num_chars = 0;
252 let comma_separator = ", ";
253 let mut extension = lang.file_extensions.iter().peekable();
254 while let Some(word) = extension.next() {
255 // If we can't fit this word in, then create a line break and align it in.
256 let new_chars = word.len() + comma_separator.len();
257 if num_chars + new_chars >= desired_width {
259 print!("\n{:width$}{}", "", separator, width = longest);
262 num_chars += new_chars;
263 print!("{}", Green.paint(word as &str));
264 if extension.peek().is_some() {
265 print!("{}", comma_separator);
274 if app.matches.is_present("list-themes") {
275 let themes = &assets.theme_set.themes;
276 for (theme, _) in themes.iter() {
277 println!("{}", theme);
282 let mut output_type = get_output_type(config.paging);
283 let handle = output_type.handle()?;
284 let mut printer = Printer::new(handle, &config);
286 for file in &config.files {
287 printer.line_changes = file.and_then(|filename| get_git_diff(filename));
289 print_file(&config, theme, &assets.syntax_set, &mut printer, *file)?;
300 if let Err(error) = result {
302 Error(ErrorKind::Io(ref io_error), _)
303 if io_error.kind() == io::ErrorKind::BrokenPipe => {}
305 eprintln!("{}: {}", Red.paint("[bat error]"), error);
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