summaryrefslogtreecommitdiffstats
path: root/src/features.rs
blob: 151901e9c898adc40a8d3be2ad84be9d74c90a79 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
use ansi_term::Colour::Green;
use app::{Config, LineRange};
use assets::HighlightingAssets;
use diff::get_git_diff;
use errors::*;
use output::OutputType;
use printer::Printer;
use std::fs::File;
use std::io::{self, BufRead, BufReader};
use syntect::easy::HighlightLines;
use syntect::highlighting::Theme;
use syntect::parsing::SyntaxDefinition;

pub fn list_languages(assets: &HighlightingAssets, term_width: usize) {
    let mut languages = assets
        .syntax_set
        .syntaxes()
        .iter()
        .filter(|syntax| !syntax.hidden && !syntax.file_extensions.is_empty())
        .collect::<Vec<_>>();
    languages.sort_by_key(|lang| lang.name.to_uppercase());

    let longest = languages
        .iter()
        .map(|syntax| syntax.name.len())
        .max()
        .unwrap_or(32); // Fallback width if they have no language definitions.

    let comma_separator = ", ";
    let separator = " ";
    // Line-wrapping for the possible file extension overflow.
    let desired_width = term_width - longest - separator.len();

    for lang in languages {
        print!("{:width$}{}", lang.name, separator, width = longest);

        // Number of characters on this line so far, wrap before `desired_width`
        let mut num_chars = 0;

        let mut extension = lang.file_extensions.iter().peekable();
        while let Some(word) = extension.next() {
            // If we can't fit this word in, then create a line break and align it in.
            let new_chars = word.len() + comma_separator.len();
            if num_chars + new_chars >= desired_width {
                num_chars = 0;
                print!("\n{:width$}{}", "", separator, width = longest);
            }

            num_chars += new_chars;
            print!("{}", Green.paint(&word[..]));
            if extension.peek().is_some() {
                print!("{}", comma_separator);
            }
        }
        println!();
    }
}

pub fn print_files(assets: &HighlightingAssets, config: &Config) -> Result<bool> {
    let theme = assets.get_theme(&config.theme);

    let mut output_type = OutputType::from_mode(config.paging_mode);
    let handle = output_type.handle()?;
    let mut printer = Printer::new(handle, &config, &theme);
    let mut no_errors: bool = true;

    for file in &config.files {
        printer.ansi_prefix_sgr.clear();
        printer.line_changes = file.and_then(|filename| get_git_diff(filename));
        let syntax = assets.get_syntax(config.language, *file);

        let result = print_file(theme, &syntax, &mut printer, *file);

        if let Err(error) = result {
            handle_error(&error);
            no_errors = false;
        }
    }

    Ok(no_errors)
}

fn print_file(
    theme: &Theme,
    syntax: &SyntaxDefinition,
    printer: &mut Printer,
    filename: Option<&str>,
) -> Result<()> {
    let stdin = io::stdin(); // TODO: this is not always needed
    {
        let reader: Box<BufRead> = match filename {
            None => Box::new(stdin.lock()),
            Some(filename) => Box::new(BufReader::new(File::open(filename)?)),
        };

        let highlighter = HighlightLines::new(syntax, theme);

        printer.print_header(filename)?;
        print_file_ranges(printer, reader, highlighter, &printer.config.line_range)?;
        printer.print_footer()?;
    }
    Ok(())
}

fn print_file_ranges<'a>(
    printer: &mut Printer,
    mut reader: Box<BufRead + 'a>,
    mut highlighter: HighlightLines,
    line_ranges: &Option<LineRange>,
) -> Result<()> {
    let mut buffer = Vec::new();

    while reader.read_until(b'\n', &mut buffer)? > 0 {
        {
            let line = String::from_utf8_lossy(&buffer);
            let regions = highlighter.highlight(line.as_ref());

            match line_ranges {
                &Some(ref range) => {
                    if printer.line_number + 1 < range.lower {
                        // skip line
                        printer.line_number += 1;
                    } else if printer.line_number >= range.upper {
                        // no more lines in range
                        break;
                    } else {
                        printer.print_line(&regions)?;
                    }
                }
                &None => {
                    printer.print_line(&regions)?;
                }
            }
        }
        buffer.clear();
    }
    Ok(())
}