on v0.18.2 help sign in
bat/src/controller.rs
242 lines 8.4 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};
17use clircle::Clircle;
19pub struct Controller<'a> {
20 config: &'a Config<'a>,
21 assets: &'a HighlightingAssets,
24impl<'b> Controller<'b> {
25 pub fn new<'a>(config: &'a Config, assets: &'a HighlightingAssets) -> Controller<'a> {
26 Controller { config, assets }
27 }
29 pub fn run(&self, inputs: Vec<Input>) -> Result<bool> {
30 self.run_with_error_handler(inputs, default_error_handler)
31 }
33 pub fn run_with_error_handler(
34 &self,
35 inputs: Vec<Input>,
36 handle_error: impl Fn(&Error, &mut dyn Write),
37 ) -> Result<bool> {
38 let mut output_type;
40 #[cfg(feature = "paging")]
41 {
42 use crate::input::InputKind;
43 use std::path::Path;
45 // Do not launch the pager if NONE of the input files exist
46 let mut paging_mode = self.config.paging_mode;
47 if self.config.paging_mode != PagingMode::Never {
48 let call_pager = inputs.iter().any(|ref input| {
49 if let InputKind::OrdinaryFile(ref path) = input.kind {
50 Path::new(path).exists()
51 } else {
52 true
53 }
54 });
55 if !call_pager {
56 paging_mode = PagingMode::Never;
57 }
58 }
60 let wrapping_mode = self.config.wrapping_mode;
62 output_type = OutputType::from_mode(paging_mode, wrapping_mode, self.config.pager)?;
63 }
65 #[cfg(not(feature = "paging"))]
66 {
67 output_type = OutputType::stdout();
68 }
70 let attached_to_pager = output_type.is_pager();
71 let stdout_identifier = if cfg!(windows) || attached_to_pager {
72 None
73 } else {
74 clircle::Identifier::stdout()
75 };
77 let writer = output_type.handle()?;
78 let mut no_errors: bool = true;
80 let stderr = io::stderr();
81 let print_error = |error: &Error, write: &mut dyn Write| {
82 if attached_to_pager {
83 handle_error(error, write);
84 } else {
85 handle_error(error, &mut stderr.lock());
86 }
87 };
89 for (index, input) in inputs.into_iter().enumerate() {
90 match input.open(io::stdin().lock(), stdout_identifier.as_ref()) {
91 Err(error) => {
92 print_error(&error, writer);
93 no_errors = false;
94 }
95 Ok(mut opened_input) => {
96 #[cfg(feature = "git")]
97 let line_changes = if self.config.visible_lines.diff_mode()
98 || (!self.config.loop_through && self.config.style_components.changes())
99 {
100 match opened_input.kind {
101 crate::input::OpenedInputKind::OrdinaryFile(ref path) => {
102 let diff = get_git_diff(path);
104 // Skip files without Git modifications
105 if self.config.visible_lines.diff_mode()
106 && diff
107 .as_ref()
108 .map(|changes| changes.is_empty())
109 .unwrap_or(false)
110 {
111 continue;
112 }
114 diff
115 }
116 _ if self.config.visible_lines.diff_mode() => {
117 // Skip non-file inputs in diff mode
118 continue;
119 }
120 _ => None,
121 }
122 } else {
123 None
124 };
126 let mut printer: Box<dyn Printer> = if self.config.loop_through {
127 Box::new(SimplePrinter::new(&self.config))
128 } else {
129 Box::new(InteractivePrinter::new(
130 &self.config,
131 &self.assets,
132 &mut opened_input,
133 #[cfg(feature = "git")]
134 &line_changes,
135 )?)
136 };
138 let result = self.print_file(
139 &mut *printer,
140 writer,
141 &mut opened_input,
142 index != 0,
143 #[cfg(feature = "git")]
144 &line_changes,
145 );
147 if let Err(error) = result {
148 print_error(&error, writer);
149 no_errors = false;
150 }
151 }
152 }
153 }
155 Ok(no_errors)
156 }
158 fn print_file(
159 &self,
160 printer: &mut dyn Printer,
161 writer: &mut dyn Write,
162 input: &mut OpenedInput,
163 add_header_padding: bool,
164 #[cfg(feature = "git")] line_changes: &Option<LineChanges>,
165 ) -> Result<()> {
166 if !input.reader.first_line.is_empty() || self.config.style_components.header() {
167 printer.print_header(writer, input, add_header_padding)?;
168 }
170 if !input.reader.first_line.is_empty() {
171 let line_ranges = match self.config.visible_lines {
172 VisibleLines::Ranges(ref line_ranges) => line_ranges.clone(),
173 #[cfg(feature = "git")]
174 VisibleLines::DiffContext(context) => {
175 let mut line_ranges: Vec<LineRange> = vec![];
177 if let Some(line_changes) = line_changes {
178 for line in line_changes.keys() {
179 let line = *line as usize;
180 line_ranges.push(LineRange::new(line - context, line + context));
181 }
182 }
184 LineRanges::from(line_ranges)
185 }
186 };
188 self.print_file_ranges(printer, writer, &mut input.reader, &line_ranges)?;
189 }
190 printer.print_footer(writer, input)?;
192 Ok(())
193 }
195 fn print_file_ranges(
196 &self,
197 printer: &mut dyn Printer,
198 writer: &mut dyn Write,
199 reader: &mut InputReader,
200 line_ranges: &LineRanges,
201 ) -> Result<()> {
202 let mut line_buffer = Vec::new();
203 let mut line_number: usize = 1;
205 let mut first_range: bool = true;
206 let mut mid_range: bool = false;
208 let style_snip = self.config.style_components.snip();
210 while reader.read_line(&mut line_buffer)? {
211 match line_ranges.check(line_number) {
212 RangeCheckResult::BeforeOrBetweenRanges => {
213 // Call the printer in case we need to call the syntax highlighter
214 // for this line. However, set `out_of_range` to `true`.
215 printer.print_line(true, writer, line_number, &line_buffer)?;
216 mid_range = false;
217 }
219 RangeCheckResult::InRange => {
220 if style_snip {
221 if first_range {
222 first_range = false;
223 mid_range = true;
224 } else if !mid_range {
225 mid_range = true;
226 printer.print_snip(writer)?;
227 }
228 }
230 printer.print_line(false, writer, line_number, &line_buffer)?;
231 }
232 RangeCheckResult::AfterLastRange => {
233 break;
234 }
235 }
237 line_number += 1;
238 line_buffer.clear();
239 }
240 Ok(())
241 }