1use std::io::{self, Write};
3use crate::assets::HighlightingAssets;
4use crate::config::{Config, VisibleLines};
6use crate::diff::{get_git_diff, LineChanges};
8use crate::input::{Input, InputReader, OpenedInput};
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 }
27 pub fn run(&self, inputs: Vec<Input>) -> Result<bool> {
28 self.run_with_error_handler(inputs, default_error_handler)
31 pub fn run_with_error_handler(
34 handle_error: impl Fn(&Error, &mut dyn Write),
38 #[cfg(feature = "paging")]
40 use crate::input::InputKind;
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()
54 paging_mode = PagingMode::Never;
57 output_type = OutputType::from_mode(paging_mode, self.config.pager)?;
60 #[cfg(not(feature = "paging"))]
62 output_type = OutputType::stdout();
65 let attached_to_pager = output_type.is_pager();
66 let writer = output_type.handle()?;
67 let mut no_errors: bool = true;
69 let stderr = io::stderr();
70 let print_error = |error: &Error, write: &mut dyn Write| {
71 if attached_to_pager {
72 handle_error(error, write);
74 handle_error(error, &mut stderr.lock());
78 for input in inputs.into_iter() {
79 match input.open(io::stdin().lock()) {
81 print_error(&error, writer);
84 Ok(mut opened_input) => {
85 #[cfg(feature = "git")]
86 let line_changes = if self.config.visible_lines.diff_mode()
87 || (!self.config.loop_through && self.config.style_components.changes())
89 match opened_input.kind {
90 crate::input::OpenedInputKind::OrdinaryFile(ref path) => {
91 let diff = get_git_diff(path);
93 // Skip files without Git modifications
94 if self.config.visible_lines.diff_mode()
97 .map(|changes| changes.is_empty())
105 _ if self.config.visible_lines.diff_mode() => {
106 // Skip non-file inputs in diff mode
115 let mut printer: Box<dyn Printer> = if self.config.loop_through {
116 Box::new(SimplePrinter::new())
118 Box::new(InteractivePrinter::new(
122 #[cfg(feature = "git")]
127 let result = self.print_file(
131 #[cfg(feature = "git")]
135 if let Err(error) = result {
136 print_error(&error, writer);
148 printer: &mut dyn Printer,
149 writer: &mut dyn Write,
150 input: &mut OpenedInput,
151 #[cfg(feature = "git")] line_changes: &Option<LineChanges>,
153 if !input.reader.first_line.is_empty() || self.config.style_components.header() {
154 printer.print_header(writer, input)?;
157 if !input.reader.first_line.is_empty() {
158 let line_ranges = match self.config.visible_lines {
159 VisibleLines::Ranges(ref line_ranges) => line_ranges.clone(),
160 #[cfg(feature = "git")]
161 VisibleLines::DiffContext(context) => {
162 let mut line_ranges: Vec<LineRange> = vec![];
164 if let Some(line_changes) = line_changes {
165 for line in line_changes.keys() {
166 let line = *line as usize;
167 line_ranges.push(LineRange::new(line - context, line + context));
171 LineRanges::from(line_ranges)
175 self.print_file_ranges(printer, writer, &mut input.reader, &line_ranges)?;
177 printer.print_footer(writer, input)?;
182 fn print_file_ranges(
184 printer: &mut dyn Printer,
185 writer: &mut dyn Write,
186 reader: &mut InputReader,
187 line_ranges: &LineRanges,
189 let mut line_buffer = Vec::new();
190 let mut line_number: usize = 1;
192 let mut first_range: bool = true;
193 let mut mid_range: bool = false;
195 while reader.read_line(&mut line_buffer)? {
196 match line_ranges.check(line_number) {
197 RangeCheckResult::BeforeOrBetweenRanges => {
198 // Call the printer in case we need to call the syntax highlighter
199 // for this line. However, set `out_of_range` to `true`.
200 printer.print_line(true, writer, line_number, &line_buffer)?;
204 RangeCheckResult::InRange => {
205 if self.config.style_components.snip() {
209 } else if !mid_range {
211 printer.print_snip(writer)?;
215 printer.print_line(false, writer, line_number, &line_buffer)?;
217 RangeCheckResult::AfterLastRange => {
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