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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
//! The pager.

use std::ffi::OsStr;
use std::io::Read;
use std::sync::Arc;

use termwiz::caps::ColorLevel;
use termwiz::caps::{Capabilities, ProbeHints};
use termwiz::terminal::{SystemTerminal, Terminal};
use vec_map::VecMap;

use crate::action::ActionSender;
use crate::bindings::Keymap;
use crate::config::{Config, InterfaceMode, KeymapConfig, WrappingMode};
use crate::control::Controller;
use crate::error::{Error, Result};
use crate::event::EventStream;
use crate::file::{ControlledFile, File, FileIndex, FileInfo, LoadedFile};
use crate::progress::Progress;

/// The main pager state.
pub struct Pager {
    /// The Terminal.
    term: SystemTerminal,

    /// The Terminal's capabilites.
    caps: Capabilities,

    /// Event Stream to process.
    events: EventStream,

    /// Files to load.
    files: Vec<File>,

    /// Error file mapping.  Maps file indices to the associated error files.
    error_files: VecMap<File>,

    /// Progress indicators to display.
    progress: Option<Progress>,

    /// Configuration.
    config: Config,
}

/// Determine terminal capabilities.
fn termcaps() -> Result<Capabilities> {
    // Get terminal capabilities from the environment, but disable mouse
    // reporting, as we don't want to change the terminal's mouse handling.
    // Enable TrueColor support, which is backwards compatible with 16
    // or 256 colors. Applications can still limit themselves to 16 or
    // 256 colors if they want.
    let hints = ProbeHints::new_from_env()
        .color_level(Some(ColorLevel::TrueColor))
        .mouse_reporting(Some(false));
    let caps = Capabilities::new_with_hints(hints).map_err(Error::Termwiz)?;
    if cfg!(unix) && caps.terminfo_db().is_none() {
        Err(Error::TerminfoDatabaseMissing)
    } else {
        Ok(caps)
    }
}

impl Pager {
    /// Build a `Pager` using the system terminal.
    pub fn new_using_system_terminal() -> Result<Self> {
        Self::new_with_terminal_func(move |caps| SystemTerminal::new(caps).map_err(Error::Termwiz))
    }

    /// Build a `Pager` using the system stdio.
    pub fn new_using_stdio() -> Result<Self> {
        Self::new_with_terminal_func(move |caps| {
            SystemTerminal::new_from_stdio(caps).map_err(Error::Termwiz)
        })
    }

    #[cfg(unix)]
    /// Build a `Pager` using the specified terminal input and output.
    pub fn new_with_input_output(
        input: &impl std::os::unix::io::AsRawFd,
        output: &impl std::os::unix::io::AsRawFd,
    ) -> Result<Self> {
        Self::new_with_terminal_func(move |caps| {
            SystemTerminal::new_with(caps, input, output).map_err(Error::Termwiz)
        })
    }

    #[cfg(windows)]
    /// Build a `Pager` using the specified terminal input and output.
    pub fn new_with_input_output(
        input: impl std::io::Read + termwiz::istty::IsTty + std::os::windows::io::AsRawHandle,
        output: impl std::io::Write + termwiz::istty::IsTty + std::os::windows::io::AsRawHandle,
    ) -> Result<Self> {
        Self::new_with_terminal_func(move |caps| {
            SystemTerminal::new_with(caps, input, output).map_err(Error::Termwiz)
        })
    }

    fn new_with_terminal_func(
        create_term: impl FnOnce(Capabilities) -> Result<SystemTerminal>,
    ) -> Result<Self> {
        let caps = termcaps()?;
        let mut term = create_term(caps.clone())?;
        term.set_raw_mode().map_err(Error::Termwiz)?;

        let events = EventStream::new(term.waker());
        let files = Vec::new();
        let error_files = VecMap::new();
        let progress = None;
        let config = Config::from_config_file().with_env();

        Ok(Self {
            term,
            caps,
            events,
            files,
            error_files,
            progress,
            config,
        })
    }

    /// Add a stream to be paged.
    pub fn add_stream(
        &mut self,
        stream: impl Read + Send + 'static,
        title: &str,
    ) -> Result<FileIndex> {
        let index = self.files.len();
        let event_sender = self.events.sender();
        let file = LoadedFile::new_streamed(index, stream, title, event_sender);
        self.files.push(file.into());
        Ok(index)
    }

    /// Attach an error stream to the previously added output stream.
    pub fn add_error_stream(
        &mut self,
        stream: impl Read + Send + 'static,
        title: &str,
    ) -> Result<FileIndex> {
        let index = self.files.len();
        let event_sender = self.events.sender();
        let file = LoadedFile::new_streamed(index, stream, title, event_sender);
        if let Some(out_file) = self.files.last() {
            self.error_files
                .insert(out_file.index(), file.clone().into());
        }
        self.files.push(file.into());
        Ok(index)
    }

    /// Attach a file from disk.
    pub fn add_file(&mut self, filename: &OsStr) -> Result<FileIndex> {
        let index = self.files.len();
        let event_sender = self.events.sender();
        let file = LoadedFile::new_file(index, filename, event_sender)?;
        self.files.push(file.into());
        Ok(index)
    }

    /// Attach a controlled file.
    pub fn add_controlled_file(&mut self, controller: &Controller) -> Result<FileIndex> {
        let index = self.files.len();
        let event_sender = self.events.sender();
        let file = ControlledFile::new(controller, index, event_sender);
        self.files.push(file.into());
        Ok(index)
    }

    /// Attach the output and error streams from a subprocess.
    ///
    /// Returns the file index for each stream.
    pub fn add_subprocess<I, S>(
        &mut self,
        command: &OsStr,
        args: I,
        title: &str,
    ) -> Result<(FileIndex, FileIndex)>
    where
        I: IntoIterator<Item = S>,
        S: AsRef<OsStr>,
    {
        let index = self.files.len();
        let event_sender = self.events.sender();
        let (out_file, err_file) =
            LoadedFile::new_command(index, command, args, title, event_sender)?;
        self.error_files.insert(index, err_file.clone().into());
        self.files.push(out_file.into());
        self.files.push(err_file.into());
        Ok((index, index + 1))
    }

    /// Set the progress stream.
    pub fn set_progress_stream(&mut self, stream: impl Read + Send + 'static) {
        let event_sender = self.events.sender();
        self.progress = Some(Progress::new(stream, event_sender));
    }

    /// Set when to use full screen mode. See [`InterfaceMode`] for details.
    pub fn set_interface_mode(&mut self, value: impl Into<InterfaceMode>) {
        self.config.interface_mode = value.into();
    }

    /// Set whether scrolling can past end of file.
    pub fn set_scroll_past_eof(&mut self, value: bool) {
        self.config.scroll_past_eof = value;
    }

    /// Set how many lines to read ahead.
    pub fn set_read_ahead_lines(&mut self, lines: usize) {
        self.config.read_ahead_lines = lines;
    }

    /// Set default wrapping mode. See [`WrappingMode`] for details.
    pub fn set_wrapping_mode(&mut self, value: impl Into<WrappingMode>) {
        self.config.wrapping_mode = value.into();
    }

    /// Set keymap name.
    pub fn set_keymap_name(&mut self, keymap: impl Into<String>) {
        self.config.keymap = KeymapConfig::Name(keymap.into());
    }

    /// Set keymap.
    pub fn set_keymap(&mut self, keymap: Keymap) {
        self.config.keymap = KeymapConfig::Keymap(Arc::new(keymap));
    }

    /// Create an action sender which can be used to send `Action`s to this pager.
    pub fn action_sender(&self) -> ActionSender {
        self.events.action_sender()
    }

    /// Run Stream Pager.
    pub fn run(self) -> Result<()> {
        crate::display::start(
            self.term,
            self.caps,
            self.events,
            self.files,
            self.error_files,
            self.progress,
            self.config,
        )
    }
}