1use std::io::{self, BufRead, Write};
2
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};
16
17use clircle::{Clircle, Identifier};
18
19pub struct Controller<'a> {
20 config: &'a Config<'a>,
21 assets: &'a HighlightingAssets,
22}
23
24impl<'b> Controller<'b> {
25 pub fn new<'a>(config: &'a Config, assets: &'a HighlightingAssets) -> Controller<'a> {
26 Controller { config, assets }
27 }
28
29 pub fn run(&self, inputs: Vec<Input>) -> Result<bool> {
30 self.run_with_error_handler(inputs, default_error_handler)
31 }
32
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;
39
40 #[cfg(feature = "paging")]
41 {
42 use crate::input::InputKind;
43 use std::path::Path;
44
45 let mut paging_mode = self.config.paging_mode;
47 if self.config.paging_mode != PagingMode::Never {
48 let call_pager = inputs.iter().any(|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 }
59
60 let wrapping_mode = self.config.wrapping_mode;
61
62 output_type = OutputType::from_mode(paging_mode, wrapping_mode, self.config.pager)?;
63 }
64
65 #[cfg(not(feature = "paging"))]
66 {
67 output_type = OutputType::stdout();
68 }
69
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 };
76
77 let writer = output_type.handle()?;
78 let mut no_errors: bool = true;
79 let stderr = io::stderr();
80
81 for (index, input) in inputs.into_iter().enumerate() {
82 let identifier = stdout_identifier.as_ref();
83 let is_first = index == 0;
84 let result = if input.is_stdin() {
85 self.print_input(input, writer, io::stdin().lock(), identifier, is_first)
86 } else {
87 self.print_input(input, writer, io::empty(), identifier, is_first)
89 };
90 if let Err(error) = result {
91 if attached_to_pager {
92 handle_error(&error, writer);
93 } else {
94 handle_error(&error, &mut stderr.lock());
95 }
96 no_errors = false;
97 }
98 }
99
100 Ok(no_errors)
101 }
102
103 fn print_input<R: BufRead>(
104 &self,
105 input: Input,
106 writer: &mut dyn Write,
107 stdin: R,
108 stdout_identifier: Option<&Identifier>,
109 is_first: bool,
110 ) -> Result<()> {
111 let mut opened_input = input.open(stdin, stdout_identifier)?;
112 #[cfg(feature = "git")]
113 let line_changes = if self.config.visible_lines.diff_mode()
114 || (!self.config.loop_through && self.config.style_components.changes())
115 {
116 match opened_input.kind {
117 crate::input::OpenedInputKind::OrdinaryFile(ref path) => {
118 let diff = get_git_diff(path);
119
120 if self.config.visible_lines.diff_mode()
122 && diff
123 .as_ref()
124 .map(|changes| changes.is_empty())
125 .unwrap_or(false)
126 {
127 return Ok(());
128 }
129
130 diff
131 }
132 _ if self.config.visible_lines.diff_mode() => {
133 return Ok(());
135 }
136 _ => None,
137 }
138 } else {
139 None
140 };
141
142 let mut printer: Box<dyn Printer> = if self.config.loop_through {
143 Box::new(SimplePrinter::new(self.config))
144 } else {
145 Box::new(InteractivePrinter::new(
146 self.config,
147 self.assets,
148 &mut opened_input,
149 #[cfg(feature = "git")]
150 &line_changes,
151 )?)
152 };
153
154 self.print_file(
155 &mut *printer,
156 writer,
157 &mut opened_input,
158 !is_first,
159 #[cfg(feature = "git")]
160 &line_changes,
161 )
162 }
163
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 }
175
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![];
182
183 if let Some(line_changes) = line_changes {
184 for &line in line_changes.keys() {
185 let line = line as usize;
186 line_ranges
187 .push(LineRange::new(line.saturating_sub(context), line + context));
188 }
189 }
190
191 LineRanges::from(line_ranges)
192 }
193 };
194
195 self.print_file_ranges(printer, writer, &mut input.reader, &line_ranges)?;
196 }
197 printer.print_footer(writer, input)?;
198
199 Ok(())
200 }
201
202 fn print_file_ranges(
203 &self,
204 printer: &mut dyn Printer,
205 writer: &mut dyn Write,
206 reader: &mut InputReader,
207 line_ranges: &LineRanges,
208 ) -> Result<()> {
209 let mut line_buffer = Vec::new();
210 let mut line_number: usize = 1;
211
212 let mut first_range: bool = true;
213 let mut mid_range: bool = false;
214
215 let style_snip = self.config.style_components.snip();
216
217 while reader.read_line(&mut line_buffer)? {
218 match line_ranges.check(line_number) {
219 RangeCheckResult::BeforeOrBetweenRanges => {
220 printer.print_line(true, writer, line_number, &line_buffer)?;
223 mid_range = false;
224 }
225
226 RangeCheckResult::InRange => {
227 if style_snip {
228 if first_range {
229 first_range = false;
230 mid_range = true;
231 } else if !mid_range {
232 mid_range = true;
233 printer.print_snip(writer)?;
234 }
235 }
236
237 printer.print_line(false, writer, line_number, &line_buffer)?;
238 }
239 RangeCheckResult::AfterLastRange => {
240 break;
241 }
242 }
243
244 line_number += 1;
245 line_buffer.clear();
246 }
247 Ok(())
248 }
249}