summaryrefslogtreecommitdiffstats
path: root/src/pager.rs
blob: 5b70777925baf8cd4dc7d9612039b9bfdab80d35 (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
use shell_words::ParseError;
use std::env;

/// If we use a pager, this enum tells us from where we were told to use it.
#[derive(Debug, PartialEq)]
pub(crate) enum PagerSource {
    /// From --config
    Config,

    /// From the env var BAT_PAGER
    EnvVarBatPager,

    /// From the env var PAGER
    EnvVarPager,

    /// No pager was specified, default is used
    Default,
}

/// We know about some pagers, for example 'less'. This is a list of all pagers we know about
#[derive(Debug, PartialEq)]
pub(crate) enum PagerKind {
    /// bat
    Bat,

    /// less
    Less,

    /// more
    More,

    /// most
    Most,

    /// A pager we don't know about
    Unknown,
}

impl PagerKind {
    fn from_bin(bin: &str) -> PagerKind {
        use std::path::Path;

        // Set to `less` by default on most Linux distros.
        let pager_bin = Path::new(bin).file_stem();

        // The name of the current running binary. Normally `bat` but sometimes
        // `batcat` for compatibility reasons.
        let current_bin = env::args_os().next();

        // Check if the current running binary is set to be our pager.
        let is_current_bin_pager = current_bin
            .map(|s| Path::new(&s).file_stem() == pager_bin)
            .unwrap_or(false);

        match pager_bin.map(|s| s.to_string_lossy()).as_deref() {
            Some("less") => PagerKind::Less,
            Some("more") => PagerKind::More,
            Some("most") => PagerKind::Most,
            _ if is_current_bin_pager => PagerKind::Bat,
            _ => PagerKind::Unknown,
        }
    }
}

/// A pager such as 'less', and from where we got it.
#[derive(Debug)]
pub(crate) struct Pager {
    /// The pager binary
    pub bin: String,

    /// The pager binary arguments (that we might tweak)
    pub args: Vec<String>,

    /// What pager this is
    pub kind: PagerKind,

    /// From where this pager comes
    pub source: PagerSource,
}

impl Pager {
    fn new(bin: &str, args: &[String], kind: PagerKind, source: PagerSource) -> Pager {
        Pager {
            bin: String::from(bin),
            args: args.to_vec(),
            kind,
            source,
        }
    }
}

/// Returns what pager to use, after looking at both config and environment variables.
pub(crate) fn get_pager(config_pager: Option<&str>) -> Result<Option<Pager>, ParseError> {
    let bat_pager = env::var("BAT_PAGER");
    let pager = env::var("PAGER");

    let (cmd, source) = match (config_pager, &bat_pager, &pager) {
        (Some(config_pager), _, _) => (config_pager, PagerSource::Config),
        (_, Ok(bat_pager), _) => (bat_pager.as_str(), PagerSource::EnvVarBatPager),
        (_, _, Ok(pager)) => (pager.as_str(), PagerSource::EnvVarPager),
        _ => ("less", PagerSource::Default),
    };

    let parts = shell_words::split(cmd)?;
    match parts.split_first() {
        Some((bin, args)) => {
            let kind = PagerKind::from_bin(bin);

            let use_less_instead = if source == PagerSource::EnvVarPager {
                // 'more' and 'most' do not supports colors; automatically use
                // 'less' instead if the problematic pager came from the
                // generic PAGER env var.
                // If PAGER=bat, silently use 'less' instead to prevent
                // recursion.
                // Never silently use 'less' if BAT_PAGER or --pager has been
                // specified.
                matches!(kind, PagerKind::More | PagerKind::Most | PagerKind::Bat)
            } else {
                false
            };

            Ok(Some(if use_less_instead {
                let no_args = vec![];
                Pager::new("less", &no_args, PagerKind::Less, PagerSource::EnvVarPager)
            } else {
                Pager::new(bin, args, kind, source)
            }))
        }
        None => Ok(None),
    }
}