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