on v0.17.1 help sign in
bat/src/controller.rs
232 lines 8.2 kB history raw
1use std::io::{self, Write};
3use crate::assets::HighlightingAssets;
4use crate::config::{Config, VisibleLines};
5#[cfg(feature = "git")]
6use crate::diff::{get_git_diff, LineChanges};
7use crate::error::*;
8use crate::input::{Input, InputReader, OpenedInput};
9#[cfg(feature = "git")]
10use crate::line_range::LineRange;
11use crate::line_range::{LineRanges, RangeCheckResult};
12use crate::output::OutputType;
13#[cfg(feature = "paging")]
14use crate::paging::PagingMode;
15use crate::printer::{InteractivePrinter, Printer, SimplePrinter};
17pub struct Controller<'a> {
18 config: &'a Config<'a>,
19 assets: &'a HighlightingAssets,
22impl<'b> Controller<'b> {
23 pub fn new<'a>(config: &'a Config, assets: &'a HighlightingAssets) -> Controller<'a> {
24 Controller { config, assets }
25 }
27 pub fn run(&self, inputs: Vec<Input>) -> Result<bool> {
28 self.run_with_error_handler(inputs, default_error_handler)
29 }
31 pub fn run_with_error_handler(
32 &self,
33 inputs: Vec<Input>,
34 handle_error: impl Fn(&Error, &mut dyn Write),
35 ) -> Result<bool> {
36 let mut output_type;
38 #[cfg(feature = "paging")]
39 {
40 use crate::input::InputKind;
41 use std::path::Path;
43 // Do not launch the pager if NONE of the input files exist
44 let mut paging_mode = self.config.paging_mode;
45 if self.config.paging_mode != PagingMode::Never {
46 let call_pager = inputs.iter().any(|ref input| {
47 if let InputKind::OrdinaryFile(ref path) = input.kind {
48 Path::new(path).exists()
49 } else {
50 true
51 }
52 });
53 if !call_pager {
54 paging_mode = PagingMode::Never;
55 }
56 }
58 let wrapping_mode = self.config.wrapping_mode;
60 output_type = OutputType::from_mode(paging_mode, wrapping_mode, self.config.pager)?;
61 }
63 #[cfg(not(feature = "paging"))]
64 {
65 output_type = OutputType::stdout();
66 }
68 let attached_to_pager = output_type.is_pager();
69 let writer = output_type.handle()?;
70 let mut no_errors: bool = true;
72 let stderr = io::stderr();
73 let print_error = |error: &Error, write: &mut dyn Write| {
74 if attached_to_pager {
75 handle_error(error, write);
76 } else {
77 handle_error(error, &mut stderr.lock());
78 }
79 };
81 for (index, input) in inputs.into_iter().enumerate() {
82 match input.open(io::stdin().lock()) {
83 Err(error) => {
84 print_error(&error, writer);
85 no_errors = false;
86 }
87 Ok(mut opened_input) => {
88 #[cfg(feature = "git")]
89 let line_changes = if self.config.visible_lines.diff_mode()
90 || (!self.config.loop_through && self.config.style_components.changes())
91 {
92 match opened_input.kind {
93 crate::input::OpenedInputKind::OrdinaryFile(ref path) => {
94 let diff = get_git_diff(path);
96 // Skip files without Git modifications
97 if self.config.visible_lines.diff_mode()
98 && diff
99 .as_ref()
100 .map(|changes| changes.is_empty())
101 .unwrap_or(false)
102 {
103 continue;
104 }
106 diff
107 }
108 _ if self.config.visible_lines.diff_mode() => {
109 // Skip non-file inputs in diff mode
110 continue;
111 }
112 _ => None,
113 }
114 } else {
115 None
116 };
118 let mut printer: Box<dyn Printer> = if self.config.loop_through {
119 Box::new(SimplePrinter::new(&self.config))
120 } else {
121 Box::new(InteractivePrinter::new(
122 &self.config,
123 &self.assets,
124 &mut opened_input,
125 #[cfg(feature = "git")]
126 &line_changes,
127 )?)
128 };
130 let result = self.print_file(
131 &mut *printer,
132 writer,
133 &mut opened_input,
134 index != 0,
135 #[cfg(feature = "git")]
136 &line_changes,
137 );
139 if let Err(error) = result {
140 print_error(&error, writer);
141 no_errors = false;
142 }
143 }
144 }
145 }
147 Ok(no_errors)
148 }
150 fn print_file(
151 &self,
152 printer: &mut dyn Printer,
153 writer: &mut dyn Write,
154 input: &mut OpenedInput,
155 add_header_padding: bool,
156 #[cfg(feature = "git")] line_changes: &Option<LineChanges>,
157 ) -> Result<()> {
158 if !input.reader.first_line.is_empty() || self.config.style_components.header() {
159 printer.print_header(writer, input, add_header_padding)?;
160 }
162 if !input.reader.first_line.is_empty() {
163 let line_ranges = match self.config.visible_lines {
164 VisibleLines::Ranges(ref line_ranges) => line_ranges.clone(),
165 #[cfg(feature = "git")]
166 VisibleLines::DiffContext(context) => {
167 let mut line_ranges: Vec<LineRange> = vec![];
169 if let Some(line_changes) = line_changes {
170 for line in line_changes.keys() {
171 let line = *line as usize;
172 line_ranges.push(LineRange::new(line - context, line + context));
173 }
174 }
176 LineRanges::from(line_ranges)
177 }
178 };
180 self.print_file_ranges(printer, writer, &mut input.reader, &line_ranges)?;
181 }
182 printer.print_footer(writer, input)?;
184 Ok(())
185 }
187 fn print_file_ranges(
188 &self,
189 printer: &mut dyn Printer,
190 writer: &mut dyn Write,
191 reader: &mut InputReader,
192 line_ranges: &LineRanges,
193 ) -> Result<()> {
194 let mut line_buffer = Vec::new();
195 let mut line_number: usize = 1;
197 let mut first_range: bool = true;
198 let mut mid_range: bool = false;
200 while reader.read_line(&mut line_buffer)? {
201 match line_ranges.check(line_number) {
202 RangeCheckResult::BeforeOrBetweenRanges => {
203 // Call the printer in case we need to call the syntax highlighter
204 // for this line. However, set `out_of_range` to `true`.
205 printer.print_line(true, writer, line_number, &line_buffer)?;
206 mid_range = false;
207 }
209 RangeCheckResult::InRange => {
210 if self.config.style_components.snip() {
211 if first_range {
212 first_range = false;
213 mid_range = true;
214 } else if !mid_range {
215 mid_range = true;
216 printer.print_snip(writer)?;
217 }
218 }
220 printer.print_line(false, writer, line_number, &line_buffer)?;
221 }
222 RangeCheckResult::AfterLastRange => {
223 break;
224 }
225 }
227 line_number += 1;
228 line_buffer.clear();
229 }
230 Ok(())
231 }