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
use std::{
    env::var,
    ffi::OsStr,
    fmt::Display,
    io::{self, ErrorKind, Write},
    process::Child,
    sync::atomic::{AtomicI32, Ordering},
};

use crate::writer::Writer;

pub static SUBPROCESS: AtomicI32 = AtomicI32::new(-1);

pub enum Pager {
    Plain,
    External((String, Child)),
}

impl Pager {
    pub fn plain() -> Self {
        Self::Plain
    }

    pub fn external<D: Display + AsRef<OsStr>>(tips: D) -> io::Result<Self> {
        let pager_cmd = var("PAGER").unwrap_or_else(|_| "less".to_owned());

        let term = var("TERM").unwrap_or_default();
        if term == "dumb" || term == "dialup" {
            return Ok(Pager::Plain);
        }

        let pager_cmd_segments: Vec<&str> = pager_cmd.split_ascii_whitespace().collect();
        let pager_name = pager_cmd_segments.first().unwrap_or(&"less");
        let mut p = std::process::Command::new(pager_name);

        if pager_name == &"less" {
            p.arg("-R"); // Show ANSI escape sequences correctly
            p.arg("-c"); // Start from the top of the screen
            p.arg("-S"); // 打开横向滚动
            p.arg("-~"); // 让 less 不显示空行的波浪线
            p.arg("-P"); // 打开滚动提示
            p.arg(tips);
            p.env("LESSCHARSET", "UTF-8"); // Rust uses UTF-8
        } else if pager_cmd_segments.len() > 1 {
            p.args(&pager_cmd_segments[1..]);
        }
        let pager_process = p.stdin(std::process::Stdio::piped()).spawn()?;

        // Record PID
        SUBPROCESS.store(pager_process.id() as i32, Ordering::SeqCst);

        let res = Pager::External((pager_name.to_string(), pager_process));

        Ok(res)
    }

    /// Get pager name (like less)
    pub fn pager_name(&self) -> Option<&str> {
        match self {
            Pager::Plain => None,
            Pager::External((name, _)) => Some(name.as_str()),
        }
    }

    /// Get writer to writer something to pager
    pub fn get_writer(&self) -> io::Result<Box<dyn Write + '_>> {
        let res = match self {
            Pager::Plain => Writer::default().get_writer(),
            Pager::External((_, child)) => {
                let stdin = child
                    .stdin
                    .as_ref()
                    .ok_or_else(|| io::Error::new(ErrorKind::BrokenPipe, "stdin does not exist"))?;
                let res: Box<dyn Write> = Box::new(stdin);
                res
            }
        };

        Ok(res)
    }

    /// Wait pager to exit
    pub fn wait_for_exit(&mut self) -> io::Result<bool> {
        let success = if let Pager::External((_, child)) = self {
            child.wait()?.success()
        } else {
            true
        };

        Ok(success)
    }
}

impl Drop for Pager {
    fn drop(&mut self) {
        // Un-set subprocess pid
        SUBPROCESS.store(-1, Ordering::SeqCst);
    }
}