//! Key bindings.

use std::collections::HashMap;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;

use indexmap::IndexMap;
use thiserror::Error;

use crate::action::Action;
use crate::file::FileIndex;

/// Key codes for key bindings.
///
pub use termwiz::input::KeyCode;

/// Keyboard modifiers for key bindings.
///
pub use termwiz::input::Modifiers;

/// Errors specific to bindings.
#[derive(Debug, Error)]
pub enum BindingError {
    /// Error when a binding is invalid.
    #[error("invalid keybinding: {0}")]
    Invalid(String),

    /// Binding is missing a parameter.
    #[error("{0} missing parameter {1}")]
    MissingParameter(String, usize),

    /// Integer parsing error.
    #[error("invalid integer")]
    InvalidInt(#[from] std::num::ParseIntError),

    /// Wrapped error within the context of a binding parameter.
    #[error("invalid {binding} parameter {index}")]
    ForParameter {
        /// Wrapped error.
        #[source]
        error: Box<BindingError>,

        /// Binding.
        binding: String,

        /// Parameter index.
        index: usize,
    },
}

impl BindingError {
    fn for_parameter(self, binding: String, index: usize) -> Self {
        Self::ForParameter {
            error: Box::new(self),
            binding,
            index,
        }
    }
}

type Result<T> = std::result::Result<T, BindingError>;

/// A key binding category.
///
/// Key bindings are listed by category in the help screen.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Category {
    /// Uncategorized actions.
    None,

    /// Actions for controlling the pager.
    General,

    /// Actions for moving around the file.
    Navigation,

    /// Actions that affect the presentation of the file.
    Presentation,

    /// Actions that initiate or modify searches.
    Searching,

    /// Actions that are hidden in help view (for example, too verbose).
    Hidden,
}

impl Category {
    /// Non-hidden categories.
    pub(crate) fn categories() -> impl Iterator<Item = Category> {
        [
            Category::General,
            Category::Navigation,
            Category::Presentation,
            Category::Searching,
            Category::None,
        ]
        .iter()
        .cloned()
    }
}

impl std::fmt::Display for Category {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match *self {
            Category::None => f.write_str("Other"),
            Category::General => f.write_str("General"),
            Category::Navigation => f.write_str("Navigation"),
            Category::Presentation => f.write_str("Presentation"),
            Category::Searching => f.write_str("Searching"),
            Category::Hidden => f.write_str("Hidden"),
        }
    }
}

/// An action that may be bound to a key.
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum Binding {
    /// An action.
    Action(Action),

    /// A custom binding.
    Custom(CustomBinding),

    /// An unrecognised binding.
    Unrecognized(String),
}

impl Binding {
    /// Create new custom binding.
    ///
    /// When this binding is invoked, the callback is called.  The callback is provided with the
    /// file index of the file that is currently being displayed.  Note that this may differ from
    /// any of the file indexes returned by the `add` methods on the `Pager`, as additional file
    /// indexes can be allocated, e.g. for the help screen.
    pub fn custom(
        category: Category,
        description: impl Into<String>,
        callback: impl Fn(FileIndex) + Send + Sync + 'static,
    ) -> Self {
        Binding::Custom(CustomBinding::new(category, description, callback))
    }

    pub(crate) fn category(&self) -> Category {
        match self {
            Binding::Action(action) => {
                use Action::*;
                match action {
                    Quit | Refresh | Help | Cancel => Category::General,
                    PreviousFile
                    | NextFile
                    | ScrollUpLines(_)
                    | ScrollDownLines(_)
                    | ScrollUpScreenFraction(_)
                    | ScrollDownScreenFraction(_)
                    | ScrollToTop
                    | ScrollToBottom
                    | ScrollLeftColumns(_)
                    | ScrollRightColumns(_)
                    | ScrollLeftScreenFraction(_)
                    | ScrollRightScreenFraction(_)
                    | PromptGoToLine => Category::Navigation,
                    ToggleRuler | ToggleLineNumbers | ToggleLineWrapping => Category::Presentation,
                    PromptSearchFromStart
                    | PromptSearchForwards
                    | PromptSearchBackwards
                    | NextMatch
                    | PreviousMatch
                    | NextMatchLine
                    | PreviousMatchLine
                    | PreviousMatchScreen
                    | NextMatchScreen
                    | FirstMatch
                    | LastMatch => Category::Searching,
                    AppendDigitToRepeatCount(_) => Category::Hidden,
                }
            }
            Binding::Custom(binding) => binding.category,
            Binding::Unrecognized(_) => Category::None,
        }
    }

    /// Parse a keybinding identifier and list of parameters into a key binding.
    pub fn parse(ident: String, params: Vec<String>) -> Result<Self> {
        use Action::*;

        let param_usize = |index| -> Result<usize> {
            let value: &String = params
                .get(index)
                .ok_or_else(|| BindingError::MissingParameter(ident.clone(), index))?;
            let value = value
                .parse::<usize>()
                .map_err(|err| BindingError::from(err).for_parameter(ident.clone(), index))?;
            Ok(value)
        };

        let action = match ident.as_str() {
            "Quit" => Quit,
            "Refresh" => Refresh,
            "Help" => Help,
            "Cancel" => Cancel,
            "PreviousFile" => PreviousFile,
            "NextFile" => NextFile,
            "ToggleRuler" => ToggleRuler,
            "ScrollUpLines" => ScrollUpLines(param_usize(0)?),
            "ScrollDownLines" => ScrollDownLines(param_usize(0)?),
            "ScrollUpScreenFraction" => ScrollUpScreenFraction(param_usize(0)?),
            "ScrollDownScreenFraction" => ScrollDownScreenFraction(param_usize(0)?),
            "ScrollToTop" => ScrollToTop,
            "ScrollToBottom" => ScrollToBottom,
            "ScrollLeftColumns" => ScrollLeftColumns(param_usize(0)?),
            "ScrollRightColumns" => ScrollRightColumns(param_usize(0)?),
            "ScrollLeftScreenFraction" => ScrollLeftScreenFraction(param_usize(0)?),
            "ScrollRightScreenFraction" => ScrollRightScreenFraction(param_usize(0)?),
            "ToggleLineNumbers" => ToggleLineNumbers,
            "ToggleLineWrapping" => ToggleLineWrapping,
            "PromptGoToLine" => PromptGoToLine,
            "PromptSearchFromStart" => PromptSearchFromStart,
            "PromptSearchForwards" => PromptSearchForwards,
            "PromptSearchBackwards" => PromptSearchBackwards,
            "PreviousMatch" => PreviousMatch,
            "NextMatch" => NextMatch,
            "PreviousMatchLine" => PreviousMatchLine,
            "NextMatchLine" => NextMatchLine,
            "FirstMatch" => FirstMatch,
            "LastMatch" => LastMatch,
            _ => return Ok(Binding::Unrecognized(ident)),
        };

        Ok(Binding::Action(action))
    }
}

impl From<Action> for Binding {
    fn from(action: Action) -> Binding {
        Binding::Action(action)
    }
}

impl From<Action> for Option<Binding> {
    fn from(action: Action) -> Option<Binding> {
        Some(Binding::Action(action))
    }
}

impl std::fmt::Display for Binding {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match *self {
            Binding::Action(ref a) => write!(f, "{}", a),
            Binding::Custom(ref b) => write!(f, "{}", b.description),
            Binding::Unrecognized(ref s) => write!(f, "Unrecognized binding ({})", s),
        }
    }
}

static CUSTOM_BINDING_ID: AtomicUsize = AtomicUsize::new(0);

/// A custom binding.  This can be used by applications using streampager
/// to add custom actions on keys.
#[derive(Clone)]
pub struct CustomBinding {
    /// The id of this binding.  This is unique for each binding.
    id: usize,

    /// The category of this binding.
    category: Category,

    /// The description of this binding.
    description: String,

    /// Called when the action is triggered.
    callback: Arc<dyn Fn(FileIndex) + Sync + Send>,
}

impl CustomBinding {
    /// Create a new custom binding.
    ///
    /// The category and description are used in the help screen.  The
    /// callback is executed whenever the binding is triggered.
    pub fn new(
        category: Category,
        description: impl Into<String>,
        callback: impl Fn(FileIndex) + Sync + Send + 'static,
    ) -> CustomBinding {
        CustomBinding {
            id: CUSTOM_BINDING_ID.fetch_add(1, Ordering::SeqCst),
            category,
            description: description.into(),
            callback: Arc::new(callback),
        }
    }

    /// Trigger the binding and run its callback.
    pub fn run(&self, file_index: FileIndex) {
        (self.callback)(file_index)
    }
}

impl PartialEq for CustomBinding {
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id
    }
}

impl Eq for CustomBinding {}

impl std::hash::Hash for CustomBinding {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.id.hash(state);
    }
}

impl std::fmt::Debug for CustomBinding {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_tuple("CustomBinding")
            .field(&self.id)
            .field(&self.description)
            .finish()
    }
}

/// A binding to a key and its associated help visibility.  Used by
/// the keymaps macro to provide binding configuration.
#[derive(Clone, Debug)]
#[doc(hidden)]
pub struct BindingConfig {
    /// The binding.
    pub binding: Binding,

    /// Whether this binding is visible in the help screen.
    pub visible: bool,
}

/// A collection of key bindings.
#[derive(PartialEq, Eq)]
pub struct Keymap {
    /// Map of bindings from keys.
    bindings: HashMap<(Modifiers, KeyCode), Binding>,

    /// Map of visible keys from bindings.
    keys: IndexMap<Binding, Vec<(Modifiers, KeyCode)>>,
}

impl<'a, I: IntoIterator<Item = &'a ((Modifiers, KeyCode), BindingConfig)>> From<I> for Keymap {
    fn from(iter: I) -> Keymap {
        let iter = iter.into_iter();
        let size_hint = iter.size_hint();
        let mut bindings = HashMap::with_capacity(size_hint.0);
        let mut keys = IndexMap::with_capacity(size_hint.0);
        for &((modifiers, keycode), ref binding_config) in iter {
            bindings.insert((modifiers, keycode), binding_config.binding.clone());
            if binding_config.visible {
                keys.entry(binding_config.binding.clone())
                    .or_insert_with(Vec::new)
                    .push((modifiers, keycode));
            }
        }
        Keymap { bindings, keys }
    }
}

impl Keymap {
    /// Create a new, empty, keymap.
    pub fn new() -> Self {
        Keymap {
            bindings: HashMap::new(),
            keys: IndexMap::new(),
        }
    }

    /// Get the binding associated with a key combination.
    pub fn get(&self, modifiers: Modifiers, keycode: KeyCode) -> Option<&Binding> {
        self.bindings.get(&(modifiers, keycode))
    }

    /// Bind (or unbind) a key combination.
    pub fn bind(
        &mut self,
        modifiers: Modifiers,
        keycode: KeyCode,
        binding: impl Into<Option<Binding>>,
    ) -> &mut Self {
        self.bind_impl(modifiers, keycode, binding.into(), true)
    }

    /// Bind (or unbind) a key combination, but exclude it from the help screen.
    pub fn bind_hidden(
        &mut self,
        modifiers: Modifiers,
        keycode: KeyCode,
        binding: impl Into<Option<Binding>>,
    ) -> &mut Self {
        self.bind_impl(modifiers, keycode, binding.into(), false)
    }

    fn bind_impl(
        &mut self,
        modifiers: Modifiers,
        keycode: KeyCode,
        binding: Option<Binding>,
        visible: bool,
    ) -> &mut Self {
        if let Some(old_binding) = self.bindings.remove(&(modifiers, keycode)) {
            if let Some(keys) = self.keys.get_mut(&old_binding) {
                keys.retain(|&item| item != (modifiers, keycode));
            }
        }
        if let Some(binding) = binding {
            self.bindings.insert((modifiers, keycode), binding.clone());
            if visible {
                self.keys
                    .entry(binding)
                    .or_insert_with(Vec::new)
                    .push((modifiers, keycode));
            }
        }
        self
    }

    pub(crate) fn iter_keys(&self) -> impl Iterator<Item = (&Binding, &Vec<(Modifiers, KeyCode)>)> {
        self.keys.iter()
    }
}

impl Default for Keymap {
    fn default() -> Self {
        Keymap::from(crate::keymaps::default::KEYMAP.iter())
    }
}

impl std::fmt::Debug for Keymap {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_tuple("Keymap")
            .field(&format!("<{} keys bound>", self.bindings.len()))
            .finish()
    }
}


//! Fillable buffer
//!
//! Buffers used for loading streams.

use std::cell::UnsafeCell;
use std::ops::{Deref, DerefMut};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Mutex, MutexGuard};

use memmap2::MmapMut;

/// Fillable buffer
///
/// This struct enapsulates a memory-mapped buffer that can be simulataneously written to and read
/// from.  Writes can only be appends, and reads can only happen to the portion that has already
/// been written.
pub(crate) struct Buffer {
    /// The underlying memory map.  This can be accessed for both reading and writing, and so is
    /// stored in an UnsafeCell.
    mmap: UnsafeCell<MmapMut>,

    /// How much of the buffer has been filled.  Reads are permitted in the range `0..filled`.
    /// Writes are permitted in the range `filled..`.
    filled: AtomicUsize,

    /// Lock for write access, to ensure only one writer can write at a time.
    lock: Mutex<()>,
}

unsafe impl Send for Buffer {}
unsafe impl Sync for Buffer {}

pub(crate) struct BufferWrite<'buffer> {
    /// The buffer this write is for.
    buffer: &'buffer Buffer,

    /// Lock guard for write access.
    _guard: MutexGuard<'buffer, ()>,
}

impl Buffer {
    pub(crate) fn new(capacity: usize) -> Buffer {
        Buffer {
            mmap: UnsafeCell::new(MmapMut::map_anon(capacity).unwrap()),
            filled: AtomicUsize::new(0usize),
            lock: Mutex::new(()),
        }
    }

    /// Returns the writable portion of the buffer
    pub(crate) fn write(&self) -> BufferWrite<'_> {
        BufferWrite {
            buffer: self,
            _guard: self.lock.lock().unwrap(),
        }
    }

    /// Returns the readable portion of the buffer
    pub(crate) fn read(&self) -> &[u8] {
        let end = self.filled.load(Ordering::SeqCst);
        let data = unsafe { &*self.mmap.get() };
        &data[..end]
    }

    #[cfg(feature = "load_file")]
    /// Returns the size of the readable portion of the buffer
    pub(crate) fn available(&self) -> usize {
        self.filled.load(Ordering::SeqCst)
    }
}

impl<'buffer> BufferWrite<'buffer> {
    /// Completes the write operation for `len` bytes to the buffer.  After calling `written`, the
    /// data is made available to callers to `read`.
    pub(crate) fn written(self, len: usize) {
        self.buffer.filled.fetch_add(len, Ordering::SeqCst);
    }
}

impl<'buffer> Deref for BufferWrite<'buffer> {
    type Target = [u8];
    fn deref(&self) -> &[u8] {
        let start = self.buffer.filled.load(Ordering::SeqCst);
        let data = unsafe { &*self.buffer.mmap.get() };
        &data[start..]
    }
}

impl<'buffer> DerefMut for BufferWrite<'buffer> {
    fn deref_mut(&mut self) -> &mut [u8] {
        let start = self.buffer.filled.load(Ordering::SeqCst);
        let data = unsafe { &mut *self.buffer.mmap.get() };
        &mut data[start..]
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::sync::Arc;
    use std::thread;

    #[test]
    fn test_buffer_write() {
        let b = Arc::new(Buffer::new(20));
        let b2 = b.clone();
        let b3 = b.clone();
        let mut w = b.write();
        // do a write
        w[0] = 42;
        w.written(1);
        // do some writes on other threads
        let t1 = thread::spawn(move || {
            let mut w = b2.write();
            w[0] = 64;
            w.written(1);
        });
        let t2 = thread::spawn(move || {
            let mut w = b3.write();
            w[0] = 81;
            w.written(1);
        });
        t1.join().unwrap();
        t2.join().unwrap();
        let mut w = b.write();
        // do another write
        w[0] = 101;
        w[1] = 99;
        w.written(2);
        assert_eq!(b.read().len(), 5);
        assert_eq!(b.read()[0], 42);
        // these two writes could have happened in any order
        assert_eq!(b.read()[1] + b.read()[2], 64 + 81);
        assert_eq!(b.read()[3], 101);
        assert_eq!(b.read()[4], 99);
    }
}


//! Commands
//!
//! Commands the user can invoke.

use crate::display::DisplayAction;
use crate::error::Error;
use crate::event::EventSender;
use crate::file::FileInfo;
use crate::prompt::Prompt;
use crate::screen::Screen;
use crate::search::{MatchMotion, Search, SearchKind};

/// Go to a line (Shortcut: ':')
///
/// Prompts the user for a line number or percentage within the file and jumps
/// to that position.  Negative numbers can be used to refer to locations
/// relative to the end of the file.
pub(crate) fn goto() -> Prompt {
    Prompt::new(
        "goto",
        "Go to line:",
        Box::new(
            |screen: &mut Screen, value: &str| -> Result<DisplayAction, Error> {
                match value {
                    // Let vi users quit with `:q` muscle memory.
                    "q" => return Ok(DisplayAction::Quit),
                    "" => return Ok(DisplayAction::Render),
                    _ => {}
                }
                let lines = screen.file.lines() as isize;
                if let Some(value_percent) = value.strip_suffix('%') {
                    // Percentage
                    match str::parse::<isize>(value_percent) {
                        Ok(mut value_percent) => {
                            value_percent = value_percent.max(-100).min(100);
                            if value_percent < 0 {
                                value_percent += 100;
                            }
                            let value = value_percent * (lines - 1) / 100;
                            screen.scroll_to(value as usize);
                        }
                        Err(e) => {
                            screen.error = Some(e.to_string());
                        }
                    }
                } else {
                    // Absolute
                    match str::parse::<isize>(value) {
                        Ok(value) => {
                            let value = if value < -lines || value == 0 {
                                0
                            } else if value > lines {
                                lines - 1
                            } else if value < 0 {
                                lines + value - 1
                            } else {
                                value - 1
                            };
                            screen.scroll_to(value as usize);
                        }
                        Err(e) => {
                            screen.error = Some(e.to_string());
                        }
                    }
                }
                Ok(DisplayAction::Render)
            },
        ),
    )
}

/// Search for text (Shortcuts: '/', '<', '>')
///
/// Prompts the user for text to search.
pub(crate) fn search(kind: SearchKind, event_sender: EventSender) -> Prompt {
    Prompt::new(
        "search",
        "Search:",
        Box::new(
            move |screen: &mut Screen, value: &str| -> Result<DisplayAction, Error> {
                screen.refresh_matched_lines();
                if value.is_empty() {
                    match kind {
                        SearchKind::First | SearchKind::FirstAfter(_) => {
                            screen.move_match(MatchMotion::NextLine)
                        }
                        SearchKind::FirstBefore(_) => screen.move_match(MatchMotion::PreviousLine),
                    }
                } else {
                    screen.set_search(
                        Search::new(&screen.file, value, kind, event_sender.clone()).ok(),
                    );
                }
                Ok(DisplayAction::Render)
            },
        ),
    )
}


//! Configuration that affects Pager behaviors.

use std::sync::Arc;
use std::time::Duration;

use serde::Deserialize;

use crate::bindings::Keymap;
use crate::error::Result;

/// Specify what interface to use.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
#[serde(from = "&str")]
pub enum InterfaceMode {
    /// The full screen terminal interface.
    ///
    /// Support text search and other operations.
    ///
    /// Use the alternate screen. The pager UI will disappear completely at
    /// exit (except for terminals without alternate screen support).
    ///
    /// Similar to external command `less` without flags. This is the default.
    FullScreen,

    /// The minimal interface. Output goes to the terminal directly.
    ///
    /// Does not support text search or other fancy operations.
    ///
    /// Does not use the alternate screen. Content will be kept in the terminal
    /// at exit.
    ///
    /// Error messages and progress messages are printed after
    /// outputs.
    ///
    /// Similar to shell command `cat` without buffering.
    Direct,

    /// Hybrid: `Direct` first, `FullScreen` next.
    ///
    /// `Direct` is used initially. When content exceeds one screen, switch to the
    /// `FullScreen` interface.
    ///
    /// Unlike `FullScreen` or `Delayed`, skip initializing the alternate
    /// screen. This is because the initial `Direct` might have "polluted"
    /// the terminal.
    ///
    /// Similar to external command `less -F -X`.
    Hybrid,

    /// Wait to decide.
    ///
    /// If output completes in the delayed time, and is within one screen, print
    /// the output and exit. Otherwise, enter the `FullScreen` interface.
    ///
    /// Unlike `Hybrid`, output is buffered in memory. So the terminal is not
    /// "polluted" and the alternate screen is used for the `FullScreen`
    /// interface.
    ///
    /// If duration is set to infinite, similar to external command `less -F`.
    /// If duration is set to 0, similar to `FullScreen`.
    Delayed(Duration),
}

impl Default for InterfaceMode {
    fn default() -> Self {
        Self::FullScreen
    }
}

impl From<&str> for InterfaceMode {
    fn from(value: &str) -> InterfaceMode {
        match value.to_lowercase().as_ref() {
            "full" | "fullscreen" | "" => InterfaceMode::FullScreen,
            "direct" => InterfaceMode::Direct,
            "hybrid" => InterfaceMode::Hybrid,
            s if s.starts_with("delayed") => {
                let duration = s.rsplit(':').next().unwrap_or("inf");
                let duration = if duration.ends_with("ms") {
                    // ex. delayed:100ms
                    Duration::from_millis(duration.trim_end_matches("ms").parse().unwrap_or(0))
                } else {
                    // ex. delayed:1s, delayed:1, delayed
                    Duration::from_secs(duration.trim_end_matches('s').parse().unwrap_or(1 << 30))
                };
                InterfaceMode::Delayed(duration)
            }
            _ => InterfaceMode::default(),
        }
    }
}

/// Specify the default line wrapping mode.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize)]
pub enum WrappingMode {
    /// Lines are not wrapped.
    #[serde(rename = "none")]
    Unwrapped,
    /// Lines are wrapped on grapheme boundaries.
    #[serde(rename = "line")]
    GraphemeBoundary,
    /// Lines are wrapped on word boundaries.
    #[serde(rename = "word")]
    WordBoundary,
}

impl WrappingMode {
    pub(crate) fn next_mode(self) -> WrappingMode {
        match self {
            WrappingMode::Unwrapped => WrappingMode::GraphemeBoundary,
            WrappingMode::GraphemeBoundary => WrappingMode::WordBoundary,
            WrappingMode::WordBoundary => WrappingMode::Unwrapped,
        }
    }
}

impl Default for WrappingMode {
    fn default() -> Self {
        Self::Unwrapped
    }
}

/// Keymap Configuration
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(from = "&str")]
pub enum KeymapConfig {
    /// A keymap name to be loaded.
    Name(String),

    /// An already-loaded keymap.
    Keymap(Arc<Keymap>),
}

impl KeymapConfig {
    pub(crate) fn load(&self) -> Result<Arc<Keymap>> {
        match self {
            Self::Name(name) => Ok(Arc::new(crate::keymaps::load(name)?)),
            Self::Keymap(keymap) => Ok(keymap.clone()),
        }
    }
}

impl Default for KeymapConfig {
    fn default() -> Self {
        Self::Name(String::from("default"))
    }
}

impl From<&str> for KeymapConfig {
    fn from(value: &str) -> Self {
        Self::Name(String::from(value))
    }
}

/// A group of configurations.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(default)]
pub struct Config {
    /// Specify when to use fullscreen.
    pub interface_mode: InterfaceMode,

    /// Specify whether scrolling down can past end of file.
    pub scroll_past_eof: bool,

    /// Specify how many lines to read ahead.
    pub read_ahead_lines: usize,

    /// Specify whether to poll input during start-up (delayed or direct mode).
    pub startup_poll_input: bool,

    /// Specify whether to show the ruler by default.
    pub show_ruler: bool,

    /// Specify whether to show the cursor by default.
    pub show_cursor: bool,

    /// Specify default wrapping move.
    pub wrapping_mode: WrappingMode,

    /// Specify the name of the default key map.
    pub keymap: KeymapConfig,
}

impl Default for Config {
    fn default() -> Self {
        Self {
            interface_mode: Default::default(),
            scroll_past_eof: true,
            read_ahead_lines: crate::file::DEFAULT_NEEDED_LINES,
            startup_poll_input: false,
            show_ruler: true,
            // See issue #52. With cursor hidden, scrolling is flaky in VSCode terminal.
            show_cursor: std::env::var("TERM_PROGRAM").ok().as_deref() == Some("vscode"),
            wrapping_mode: Default::default(),
            keymap: Default::default(),
        }
    }
}

impl Config {
    /// Create [`Config`] from the user's default config file.
    pub fn from_config_file() -> Self {
        if let Some(mut path) = dirs::config_dir() {
            path.push("streampager");
            path.push("streampager.toml");
            if let Ok(config) = std::fs::read_to_string(&path) {
                match toml::from_str(&config) {
                    Ok(config) => return config,
                    Err(e) => eprintln!(
                        "streampager: failed to parse config at {:?}, using defaults: {}",
                        path, e
                    ),
                }
            }
        }
        Self::default()
    }

    /// Modify [`Config`] using environment variables.
    pub fn with_env(mut self) -> Self {
        use std::env::var;
        if let Ok(s) = var("SP_INTERFACE_MODE") {
            self.interface_mode = InterfaceMode::from(s.as_ref());
        }
        if let Ok(s) = var("SP_SCROLL_PAST_EOF") {
            if let Some(b) = parse_bool(&s) {
                self.scroll_past_eof = b;
            }
        }
        if let Ok(s) = var("SP_READ_AHEAD_LINES") {
            if let Ok(n) = s.parse::<usize>() {
                self.read_ahead_lines = n;
            }
        }
        self
    }
}

fn parse_bool(value: &str) -> Option<bool> {
    match value.to_ascii_lowercase().as_ref() {
        "1" | "yes" | "true" | "on" | "always" => Some(true),
        "0" | "no" | "false" | "off" | "never" => Some(false),
        _ => None,
    }
}


//! Controlled files.
//!
//! Files where data is provided by a controller.

use std::borrow::Cow;
use std::ops::Range;
use std::sync::{Arc, Mutex, RwLock};

use thiserror::Error;

use crate::event::{Event, EventSender};
use crate::file::{FileIndex, FileInfo};

/// Errors that may occur during controlled file operations.
#[derive(Debug, Error)]
pub enum ControlledFileError {
    /// Line number out of range.
    #[error("line number {index} out of range (0..{length})")]
    LineOutOfRange {
        /// The index of the line number that is out of range.
        index: usize,
        /// The length of the file (and so the limit for the line number).
        length: usize,
    },

    /// Other error type.
    #[error(transparent)]
    Error(#[from] crate::error::Error),
}

/// Result alias for controlled file operations that may fail.
pub type Result<T> = std::result::Result<T, ControlledFileError>;

/// A controller for a controlled file.
///
/// This contains a logical file which can be mutated by a controlling
/// program.  It can be added to the pager using
/// `Pager::add_controlled_file`.
#[derive(Clone)]
pub struct Controller {
    data: Arc<RwLock<FileData>>,
    notify: Arc<Mutex<Vec<(EventSender, FileIndex)>>>,
}

impl Controller {
    /// Create a new controller.  The controlled file is initially empty.
    pub fn new(title: impl Into<String>) -> Controller {
        Controller {
            data: Arc::new(RwLock::new(FileData::new(title))),
            notify: Arc::new(Mutex::new(Vec::new())),
        }
    }

    /// Returns a copy of the current title.
    pub fn title(&self) -> String {
        let data = self.data.read().unwrap();
        data.title.clone()
    }

    /// Returns a copy of the current file info.
    pub fn info(&self) -> String {
        let data = self.data.read().unwrap();
        data.info.clone()
    }

    /// Apply a sequence of changes to the controlled file.
    pub fn apply_changes(&self, changes: impl IntoIterator<Item = Change>) -> Result<()> {
        let mut data = self.data.write().unwrap();
        for change in changes {
            data.apply_change(change)?;
        }
        // TODO(markbt): more fine-grained notifications.
        // For now, just reload the file.
        let notify = self.notify.lock().unwrap();
        for (event_sender, index) in notify.iter() {
            event_sender.send(Event::Reloading(*index))?;
        }
        Ok(())
    }
}

/// A change to apply to a controlled file.
pub enum Change {
    /// Set the title for the file.
    SetTitle {
        /// The new title.
        title: String,
    },

    /// Set the file information for the file.
    SetInfo {
        /// The text of the new file info.
        info: String,
    },

    /// Append a single line to the file.
    AppendLine {
        /// The content of the new line.
        content: Vec<u8>,
    },

    /// Insert a single line into the file.
    InsertLine {
        /// Index of the line in the file to insert before.
        before_index: usize,
        /// The content of the new line.
        content: Vec<u8>,
    },

    /// Replace a single line in the file.
    ReplaceLine {
        /// Index of the line in fhe file to replace.
        index: usize,
        /// The content of the new line.
        content: Vec<u8>,
    },

    /// Delete a single line from the file.
    DeleteLine {
        /// Index of the line in the file to delete.
        index: usize,
    },

    /// Append multiple lines to the file
    AppendLines {
        /// The contents of the new lines.
        contents: Vec<Vec<u8>>,
    },

    /// Insert some lines before another line in the file.
    InsertLines {
        /// Index of the line in the file to insert before.
        before_index: usize,
        /// The contents of the new lines.
        contents: Vec<Vec<u8>>,
    },

    /// Replace a range of lines with another set of lines.
    /// The range and the new lines do not need to be the same size.
    ReplaceLines {
        /// The range of lines in the file to replace.
        range: Range<usize>,
        /// The contents of the new lines.
        contents: Vec<Vec<u8>>,
    },

    /// Delete a range of lines in the file.
    DeleteLines {
        /// The range of lines in the file to delete.
        range: Range<usize>,
    },

    /// Replace all lines with another set of lines.
    ReplaceAll {
        /// The new contents of the file.
        contents: Vec<Vec<u8>>,
    },
}

/// A file whose contents is controlled by a `Controller`.
#[derive(Clone)]
pub struct ControlledFile {
    index: FileIndex,
    data: Arc<RwLock<FileData>>,
}

impl ControlledFile {
    pub(crate) fn new(
        controller: &Controller,
        index: FileIndex,
        event_sender: EventSender,
    ) -> ControlledFile {
        let mut notify = controller.notify.lock().unwrap();
        notify.push((event_sender, index));
        ControlledFile {
            index,
            data: controller.data.clone(),
        }
    }
}

impl FileInfo for ControlledFile {
    /// The file's index.
    fn index(&self) -> FileIndex {
        self.index
    }

    /// The file's title.
    fn title(&self) -> Cow<'_, str> {
        let data = self.data.read().unwrap();
        Cow::Owned(data.title.clone())
    }

    /// The file's info.
    fn info(&self) -> Cow<'_, str> {
        let data = self.data.read().unwrap();
        Cow::Owned(data.info.clone())
    }

    /// True once the file is loaded and all newlines have been parsed.
    fn loaded(&self) -> bool {
        true
    }

    /// Returns the number of lines in the file.
    fn lines(&self) -> usize {
        self.data.read().unwrap().lines.len()
    }

    /// Runs the `call` function, passing it the contents of line `index`.
    /// Tries to avoid copying the data if possible, however the borrowed
    /// line only lasts as long as the function call.
    fn with_line<T, F>(&self, index: usize, mut call: F) -> Option<T>
    where
        F: FnMut(Cow<'_, [u8]>) -> T,
    {
        let data = self.data.read().unwrap();
        if let Some(line) = data.lines.get(index) {
            Some(call(Cow::Borrowed(line.content.as_slice())))
        } else {
            None
        }
    }

    /// Set how many lines are needed.
    ///
    /// If `self.lines()` exceeds that number, pause loading until
    /// `set_needed_lines` is called with a larger number.
    /// This is only effective for "streamed" input.
    fn set_needed_lines(&self, _lines: usize) {}

    /// True if the loading thread has been paused.
    fn paused(&self) -> bool {
        false
    }
}

struct FileData {
    title: String,
    info: String,
    lines: Vec<LineData>,
}

impl FileData {
    fn new(title: impl Into<String>) -> FileData {
        FileData {
            title: title.into(),
            info: String::new(),
            lines: Vec::new(),
        }
    }

    fn line_mut(&mut self, index: usize) -> Result<&mut LineData> {
        let length = self.lines.len();
        if let Some(line) = self.lines.get_mut(index) {
            return Ok(line);
        }
        Err(ControlledFileError::LineOutOfRange { index, length })
    }

    fn apply_change(&mut self, change: Change) -> Result<()> {
        match change {
            Change::SetTitle { title } => {
                self.title = title;
            }
            Change::SetInfo { info } => {
                self.info = info;
            }
            Change::AppendLine { content } => {
                self.lines.push(LineData::with_content(content));
            }
            Change::InsertLine {
                before_index,
                content,
            } => {
                self.lines
                    .insert(before_index, LineData::with_content(content));
            }
            Change::ReplaceLine { index, content } => {
                self.line_mut(index)?.content = content;
            }
            Change::DeleteLine { index } => {
                self.lines.remove(index);
            }
            Change::AppendLines { contents } => {
                let new_lines = contents.into_iter().map(LineData::with_content);
                self.lines.extend(new_lines);
            }
            Change::InsertLines {
                before_index,
                contents,
            } => {
                let new_lines = contents.into_iter().map(LineData::with_content);
                self.lines.splice(before_index..before_index, new_lines);
            }
            Change::ReplaceLines { range, contents } => {
                let new_lines = contents.into_iter().map(LineData::with_content);
                self.lines.splice(range, new_lines);
            }
            Change::DeleteLines { range } => {
                self.lines.splice(range, std::iter::empty());
            }
            Change::ReplaceAll { contents } => {
                let new_lines = contents.into_iter().map(LineData::with_content);
                self.lines = new_lines.collect();
            }
        }
        Ok(())
    }
}

struct LineData {
    content: Vec<u8>,
}

impl LineData {
    fn with_content(content: Vec<u8>) -> LineData {
        LineData { content }
    }
}


//! Support for `InterfaceMode::Direct` and other modes using `Direct`.

use std::time::Duration;
use std::time::Instant;

use bit_set::BitSet;
use termwiz::input::InputEvent;
use termwiz::surface::change::Change;
use termwiz::surface::CursorVisibility;
use termwiz::surface::Position;
use termwiz::terminal::Terminal;
use vec_map::VecMap;

use crate::action::Action;
use crate::config::InterfaceMode;
use crate::config::WrappingMode;
use crate::error::Error;
use crate::error::Result;
use crate::event::Event;
use crate::event::EventStream;
use crate::file::File;
use crate::file::FileInfo;
use crate::line::Line;
use crate::progress::Progress;

/// Return value of `direct`.
#[derive(Debug)]
pub(crate) enum Outcome {
    /// Content is not completely rendered.  A hint to enter full-screen.
    /// The number of rows that have been rendered is included.
    RenderIncomplete(usize),

    /// Content is not rendered at all.  A hint to enter full-screen.
    RenderNothing,

    /// Content is completely rendered.
    RenderComplete,

    /// The user pressed a key to exit.
    Interrupted,
}

/// Streaming content to the terminal without entering full screen.
///
/// Similar to `tail -f`, but with dynamic progress support.
/// Useful for rendering content before entering the full-screen mode.
///
/// Lines are rendered in this order:
/// - Output (append-only)
/// - Error (append-only)
/// - Progress (mutable)
///
/// Return `Outcome::Interrupted` if `q` or `Ctrl+C` is pressed.
/// Otherwise, return values and conditions are as follows:
///
/// | Interface  | Fits Screen | Streams Ended | Return           |
/// |------------|-------------|---------------|------------------|
/// | FullScreen | (any)       | (any)         | RenderNothing    |
/// | Direct     | (any)       | no            | -                |
/// | Direct     | (any)       | yes           | RenderComplete   |
/// | Hybrid     | yes         | no            | -                |
/// | Hybrid     | yes         | yes           | RenderComplete   |
/// | Hybrid     | no          | (any)         | RenderIncomplete |
/// | Delayed    | (any)       | no (time out) | RenderNothing    |
/// | Delayed    | yes         | yes           | RenderComplete   |
/// | Delayed    | no          | yes           | RenderNothing    |
pub(crate) fn direct<T: Terminal>(
    term: &mut T,
    output_files: &[File],
    error_files: &[File],
    progress: Option<&Progress>,
    events: &mut EventStream,
    mode: InterfaceMode,
    poll_input: bool,
) -> Result<Outcome> {
    if mode == InterfaceMode::FullScreen {
        return Ok(Outcome::RenderNothing);
    }
    let delayed_deadline = match mode {
        InterfaceMode::Delayed(duration) => Some(Instant::now() + duration),
        _ => None,
    };
    let mut loading = BitSet::with_capacity(output_files.len() + error_files.len());
    for file in output_files.iter().chain(error_files.iter()) {
        loading.insert(file.index());
    }

    let mut last_read = VecMap::new(); // file index -> line number last read
    let mut collect_unread = |files: &[File], max_lines: usize| -> Vec<Vec<u8>> {
        let mut result = Vec::new();
        for file in files.iter() {
            let index = file.index();
            let mut lines = file.lines();
            let last = last_read.get(index).cloned().unwrap_or(0);
            file.set_needed_lines(last + max_lines);
            // Ignore the incomplete last line if the file is loading.
            if lines > 0
                && !file.loaded()
                && file
                    .with_line(lines - 1, |l| !l.ends_with(b"\n"))
                    .unwrap_or(true)
            {
                lines -= 1;
            }
            if lines >= last {
                let lines = (last + max_lines).min(lines);
                result.reserve(lines - last);
                for i in last..lines {
                    file.with_line(i, |l| result.push(l.to_vec()));
                }
                last_read.insert(index, lines);
            }
        }
        result
    };

    let read_progress_lines = || -> Vec<Vec<u8>> {
        let line_count = progress.map(|p| p.lines()).unwrap_or(0);
        (0..line_count)
            .filter_map(|i| progress.and_then(|p| p.with_line(i, |l| l.to_vec())))
            .collect::<Vec<_>>()
    };

    let mut state = StreamingLines::default();
    let delayed = delayed_deadline.is_some();
    let has_one_screen_limit = !matches!(mode, InterfaceMode::Direct);
    let mut render = |term: &mut T, h: usize, w: usize| -> Result<Option<Outcome>> {
        let append_output_lines = collect_unread(output_files, h + 2);
        let append_error_lines = collect_unread(error_files, h + 2);
        let progress_lines = read_progress_lines();
        state.add_lines(append_output_lines, append_error_lines, progress_lines);
        if delayed {
            if has_one_screen_limit && state.height(w) >= h {
                return Ok(Some(Outcome::RenderNothing));
            }
        } else {
            if has_one_screen_limit && state.height(w) >= h {
                return Ok(Some(Outcome::RenderIncomplete(state.rendered_row_count())));
            }
            let changes = state.render_pending_lines(w)?;
            term.render(&changes).map_err(Error::Termwiz)?;
        }
        Ok(None)
    };

    let mut size = term.get_screen_size().map_err(Error::Termwiz)?;
    let mut loaded = BitSet::with_capacity(loading.capacity());
    let mut remaining = output_files.len() + error_files.len();
    let interval = Duration::from_millis(10);
    while remaining > 0 {
        let maybe_event = if poll_input {
            events.get(term, Some(interval))?
        } else {
            events.try_recv(Some(interval))?
        };
        match maybe_event {
            Some(Event::Loaded(i)) => {
                if loading.contains(i) && loaded.insert(i) {
                    remaining -= 1;
                }
            }
            Some(Event::Input(InputEvent::Resized { .. })) => {
                size = term.get_screen_size().map_err(Error::Termwiz)?;
            }
            Some(Event::Input(InputEvent::Key(key))) => {
                use termwiz::input::KeyCode::Char;
                use termwiz::input::Modifiers;
                match (key.modifiers, key.key) {
                    (Modifiers::NONE, Char('q')) | (Modifiers::CTRL, Char('C')) => {
                        term.render(&state.abort()).map_err(Error::Termwiz)?;
                        return Ok(Outcome::Interrupted);
                    }
                    (Modifiers::NONE, Char('f')) | (Modifiers::NONE, Char(' ')) => {
                        let outcome = if delayed {
                            Outcome::RenderNothing
                        } else {
                            Outcome::RenderIncomplete(state.rendered_row_count())
                        };
                        return Ok(outcome);
                    }
                    _ => (),
                }
            }
            Some(Event::Action(Action::Quit)) => {
                term.render(&state.abort()).map_err(Error::Termwiz)?;
                return Ok(Outcome::Interrupted);
            }
            _ => (),
        }
        if let Some(deadline) = delayed_deadline {
            if deadline <= Instant::now() {
                return Ok(Outcome::RenderNothing);
            }
        }
        if let Some(outcome) = render(term, size.rows, size.cols)? {
            return Ok(outcome);
        }
    }

    if delayed {
        term.render(&state.render_pending_lines(size.cols)?)
            .map_err(Error::Termwiz)?;
    }

    Ok(Outcome::RenderComplete)
}

/// State for calculating how to incrementally render streaming changes.
///
/// +----------------------------+
/// | past output (never redraw) |
/// +----------------------------+
/// | new output (just received) |
/// +----------------------------+
/// | error (always redraw)      |
/// +----------------------------+
/// | progress (always redraw)   |
/// +----------------------------+
#[derive(Default)]
struct StreamingLines {
    past_output_row_count: usize,
    new_output_lines: Vec<Vec<u8>>,
    error_lines: Vec<Vec<u8>>,
    progress_lines: Vec<Vec<u8>>,
    erase_row_count: usize,
    pending_changes: bool,
    cursor_hidden: bool,
}

impl StreamingLines {
    fn add_lines(
        &mut self,
        mut append_output_lines: Vec<Vec<u8>>,
        mut append_error_lines: Vec<Vec<u8>>,
        replace_progress_lines: Vec<Vec<u8>>,
    ) {
        if append_output_lines.is_empty()
            && append_error_lines.is_empty()
            && replace_progress_lines == self.progress_lines
        {
            return;
        }
        self.new_output_lines.append(&mut append_output_lines);
        self.error_lines.append(&mut append_error_lines);
        self.progress_lines = replace_progress_lines;
        self.pending_changes = true;
    }

    fn render_pending_lines(&mut self, terminal_width: usize) -> Result<Vec<Change>> {
        // Fast path: nothing changed?
        if !self.pending_changes {
            return Ok(Vec::new());
        }

        // Every line needs at least 2 `Change`s: Text, and CursorPosition,
        // plus 2 Changes for erasing existing lines.
        let line_count =
            self.new_output_lines.len() + self.error_lines.len() + self.progress_lines.len();
        let mut changes = Vec::with_capacity(line_count * 2 + 2);

        // Step 1: Erase progress, and error.
        if self.erase_row_count > 0 {
            let dy = -(self.erase_row_count as isize);
            changes.push(Change::CursorPosition {
                x: Position::Relative(0),
                y: Position::Relative(dy),
            });
            changes.push(Change::ClearToEndOfScreen(Default::default()));
        }

        // Step 2: Render new output + error + progress
        let mut render = |lines| -> Result<_> {
            let mut row_count = 0;
            for line in lines {
                let line = Line::new(0, line);
                let height = line.height(terminal_width, WrappingMode::GraphemeBoundary);
                line.render(&mut changes, 0, terminal_width * height, None);
                changes.push(Change::CursorPosition {
                    x: Position::Absolute(0),
                    y: Position::Relative(1),
                });
                row_count += height;
            }
            Ok(row_count)
        };

        let new_output_row_count = render(self.new_output_lines.iter())?;
        let error_row_count = render(self.error_lines.iter())?;
        let mut progress_row_count = render(self.progress_lines.iter())?;

        // Don't render the last newline after progress, and hide the
        // cursor while progress is being shown.
        if progress_row_count > 0 {
            changes.pop();
            changes.push(Change::CursorPosition {
                x: Position::Absolute(0),
                y: Position::Relative(0),
            });
            if !self.cursor_hidden {
                changes.push(Change::CursorVisibility(CursorVisibility::Hidden));
                self.cursor_hidden = true;
            }
            progress_row_count -= 1;
        } else if self.cursor_hidden {
            changes.push(Change::CursorVisibility(CursorVisibility::Visible));
            self.cursor_hidden = false;
        }

        // Step 3: Update internal state.
        self.past_output_row_count += new_output_row_count;
        self.new_output_lines.clear();
        self.erase_row_count = error_row_count + progress_row_count;
        self.pending_changes = false;

        Ok(changes)
    }

    fn abort(&mut self) -> Vec<Change> {
        let mut changes = Vec::new();
        if self.cursor_hidden {
            changes.push(Change::CursorPosition {
                x: Position::Absolute(0),
                y: Position::Relative(1),
            });
            changes.push(Change::CursorVisibility(CursorVisibility::Visible));
            self.cursor_hidden = false;
        }
        changes
    }

    fn height(&self, terminal_width: usize) -> usize {
        let mut row_count = self.past_output_row_count;
        for line in self
            .new_output_lines
            .iter()
            .chain(self.error_lines.iter())
            .chain(self.progress_lines.iter())
        {
            let line = Line::new(0, line);
            row_count += line.height(terminal_width, WrappingMode::GraphemeBoundary);
        }
        row_count
    }

    fn rendered_row_count(&self) -> usize {
        self.past_output_row_count + self.erase_row_count
    }
}


//! Manage the Display.

use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Duration;

use scopeguard::guard;
use termwiz::caps::Capabilities as TermCapabilities;
use termwiz::cell::CellAttributes;
use termwiz::color::ColorAttribute;
use termwiz::input::InputEvent;
use termwiz::surface::change::Change;
use termwiz::surface::{CursorVisibility, Position};
use termwiz::terminal::Terminal;
use vec_map::VecMap;

use crate::command;
use crate::config::Config;
use crate::direct;
use crate::error::Error;
use crate::event::{Event, EventStream, UniqueInstance};
use crate::file::{File, FileIndex, FileInfo, LoadedFile};
use crate::help::help_text;
use crate::progress::Progress;
use crate::screen::Screen;
use crate::search::SearchKind;

/// Capabilities of the terminal that we care about.
#[derive(Default)]
pub(crate) struct Capabilities {
    pub(crate) scroll_up: bool,
    pub(crate) scroll_down: bool,
}

impl Capabilities {
    fn new(term_caps: TermCapabilities) -> Capabilities {
        use terminfo::capability as cap;
        let mut caps = Capabilities::default();
        if let Some(db) = term_caps.terminfo_db() {
            if db.get::<cap::ChangeScrollRegion>().is_some() {
                caps.scroll_up = db.get::<cap::ParmIndex>().is_some()
                    || (db.get::<cap::CursorAddress>().is_some()
                        && db.get::<cap::ScrollForward>().is_some());
                caps.scroll_down = db.get::<cap::ParmRindex>().is_some()
                    || (db.get::<cap::CursorAddress>().is_some()
                        && db.get::<cap::ScrollReverse>().is_some());
            }
        }
        caps
    }
}

/// An action that affects the display.
pub(crate) enum DisplayAction {
    /// Do nothing.
    None,

    /// Run a function.  The function may return a new action to run next.
    Run(Box<dyn FnMut(&mut Screen) -> Result<DisplayAction, Error>>),

    /// Change the terminal.
    Change(Change),

    /// Render the parts of the screen that have changed.
    Render,

    /// Render the whole screen.
    Refresh,

    /// Render the prompt.
    RefreshPrompt,

    /// Move to the next file.
    NextFile,

    /// Move to the previous file.
    PreviousFile,

    /// Show the help screen.
    ShowHelp,

    /// Clear the overlay.
    ClearOverlay,

    /// Close the program.
    Quit,
}

/// Container for all screens.
struct Screens {
    /// The loaded files.
    screens: Vec<Screen>,

    /// An overlaid screen (e.g. the help screen).
    overlay: Option<Screen>,

    /// The currently active screen.
    current_index: FileIndex,

    /// The file index of the overlay.  While overlays aren't part of the
    /// screens vector, we still need a file index so that the file loader can
    /// report loading completion and the search thread can report search
    /// matches.  Use an index starting after the loaded files for this purpose.
    /// Each time a new overlay is added, this index is incremented, so that
    /// each overlay gets a unique index.
    overlay_index: FileIndex,
}

impl Screens {
    /// Create a new screens container for the given files.
    fn new(
        files: Vec<File>,
        mut error_files: VecMap<File>,
        progress: Option<Progress>,
        config: Arc<Config>,
    ) -> Result<Screens, Error> {
        let count = files.len();
        let mut screens = Vec::new();
        for file in files.into_iter() {
            let index = file.index();
            let mut screen = Screen::new(file, config.clone())?;
            screen.set_progress(progress.clone());
            screen.set_error_file(error_files.remove(index));
            screens.push(screen);
        }
        Ok(Screens {
            screens,
            overlay: None,
            current_index: 0,
            overlay_index: count,
        })
    }

    /// Get the current screen.
    fn current(&mut self) -> &mut Screen {
        if let Some(ref mut screen) = self.overlay {
            screen
        } else {
            &mut self.screens[self.current_index]
        }
    }

    /// True if the given index is the index of the currently visible screen.
    fn is_current_index(&self, index: FileIndex) -> bool {
        match self.overlay {
            Some(_) => index == self.overlay_index,
            None => index == self.current_index,
        }
    }

    /// Get the screen with the given index.
    fn get(&mut self, index: usize) -> Option<&mut Screen> {
        if index == self.overlay_index {
            self.overlay.as_mut()
        } else if index < self.screens.len() {
            Some(&mut self.screens[index])
        } else {
            None
        }
    }
}

/// Start displaying files.
pub(crate) fn start(
    mut term: impl Terminal,
    term_caps: TermCapabilities,
    mut events: EventStream,
    files: Vec<File>,
    error_files: VecMap<File>,
    progress: Option<Progress>,
    config: Config,
) -> Result<(), Error> {
    // Defer enabling raw mode until we need it. It has some undesirable side
    // effects for direct mode such as disabling terminal echo and consuming all
    // pending terminal input (e.g. user types next terminal command before the
    // current command has finished).
    let mut in_raw_mode = false;
    if config.startup_poll_input {
        // We need raw mode to poll for user input during direct mode.
        term.set_raw_mode().map_err(Error::Termwiz)?;
        in_raw_mode = true;
    }

    let outcome = {
        // Only take the first output and error. This emulates the behavior that
        // the main pager can only display one stream at a time.
        let output_files = &files[0..1.min(files.len())];
        let error_files = match error_files.iter().next() {
            None => Vec::new(),
            Some((_i, file)) => vec![file.clone()],
        };
        direct::direct(
            &mut term,
            output_files,
            &error_files[..],
            progress.as_ref(),
            &mut events,
            config.interface_mode,
            config.startup_poll_input,
        )?
    };
    match outcome {
        direct::Outcome::RenderComplete | direct::Outcome::Interrupted => return Ok(()),
        direct::Outcome::RenderIncomplete(rows) => {
            // Push the rendered output up to the top of the screen, so that
            // when we start rendering full screen we don't overwrite output
            // from earlier commands.  In direct mode the bottom line held the
            // cursor, so we must subtract that line, too, otherwise we will
            // scroll up too far.
            let size = term.get_screen_size().map_err(Error::Termwiz)?;
            let scroll_count = size.rows.saturating_sub(rows).saturating_sub(1);
            if scroll_count > 0 {
                term.render(&[Change::Text("\n".repeat(scroll_count))])
                    .map_err(Error::Termwiz)?;
            }
        }
        direct::Outcome::RenderNothing => term.enter_alternate_screen().map_err(Error::Termwiz)?,
    };

    // We certainly need raw mode for fullscreen.
    if !in_raw_mode {
        term.set_raw_mode().map_err(Error::Termwiz)?;
    }

    let overlay_height = AtomicUsize::new(0);
    let mut term = guard(term, |mut term| {
        // Clean up when exiting.  Most of this should be achieved by exiting
        // the alternate screen, but just in case it isn't, move to the
        // bottom of the screen and reset all attributes.
        let size = term.get_screen_size().unwrap();
        let overlay_height = overlay_height.load(Ordering::SeqCst);
        let scroll_count = 1usize.saturating_sub(overlay_height);
        term.render(&[
            Change::CursorVisibility(CursorVisibility::Visible),
            Change::AllAttributes(CellAttributes::default()),
            Change::ScrollRegionUp {
                first_row: 0,
                region_size: size.rows,
                scroll_count,
            },
            Change::CursorPosition {
                x: Position::Absolute(0),
                y: Position::Absolute(size.rows.saturating_sub(overlay_height + scroll_count)),
            },
            Change::ClearToEndOfScreen(ColorAttribute::default()),
        ])
        .unwrap();
    });
    let config = Arc::new(config);
    let caps = Capabilities::new(term_caps);
    let mut screens = Screens::new(files, error_files, progress, config.clone())?;
    let event_sender = events.sender();
    let render_unique = UniqueInstance::new();
    let refresh_unique = UniqueInstance::new();
    {
        let screen = screens.current();
        let size = term.get_screen_size().map_err(Error::Termwiz)?;
        screen.resize(size.cols, size.rows);
        screen.maybe_load_more();
        term.render(&screen.render(&caps)).map_err(Error::Termwiz)?;
    }
    loop {
        // Listen for an event or input.  If we are animating, put a timeout on the wait.
        let timeout = if screens.current().animate() {
            Some(Duration::from_millis(100))
        } else {
            None
        };
        let event = events.get(&mut *term, timeout)?;

        // Dispatch the event and receive an action to take.
        let mut action = {
            let screen = screens.current();
            screen.maybe_load_more();

            match event {
                None => screen.dispatch_animation(),
                Some(Event::Render) => {
                    term.render(&screen.render(&caps)).map_err(Error::Termwiz)?;
                    DisplayAction::None
                }
                Some(Event::Input(InputEvent::Resized { .. })) => {
                    let size = term.get_screen_size().map_err(Error::Termwiz)?;
                    screen.resize(size.cols, size.rows);
                    term.render(&screen.render(&caps)).map_err(Error::Termwiz)?;
                    DisplayAction::None
                }
                Some(Event::Refresh) => {
                    let size = term.get_screen_size().map_err(Error::Termwiz)?;
                    screen.resize(size.cols, size.rows);
                    screen.refresh();
                    term.render(&screen.render(&caps)).map_err(Error::Termwiz)?;
                    DisplayAction::None
                }
                Some(Event::Progress) => {
                    screen.refresh_progress();
                    term.render(&screen.render(&caps)).map_err(Error::Termwiz)?;
                    DisplayAction::None
                }
                Some(Event::Action(action)) => screen.dispatch_action(action, &event_sender),
                Some(Event::Input(InputEvent::Key(key))) => {
                    let width = screen.width();
                    if let Some(prompt) = screen.prompt() {
                        prompt.dispatch_key(key, width)
                    } else {
                        screen.dispatch_key(key, &event_sender)
                    }
                }
                Some(Event::Input(InputEvent::Paste(ref text))) => {
                    let width = screen.width();
                    screen
                        .prompt()
                        .get_or_insert_with(|| {
                            // Assume the user wanted to search for what they're pasting.
                            command::search(SearchKind::First, event_sender.clone())
                        })
                        .paste(text, width)
                }
                Some(Event::Loaded(index)) if screens.is_current_index(index) => {
                    DisplayAction::Refresh
                }
                #[cfg(feature = "load_file")]
                Some(Event::Appending(index)) if screens.is_current_index(index) => {
                    DisplayAction::Refresh
                }
                Some(Event::Reloading(index)) => {
                    if let Some(screen) = screens.get(index) {
                        screen.flush_line_caches();
                    }
                    if screens.is_current_index(index) {
                        DisplayAction::Refresh
                    } else {
                        DisplayAction::None
                    }
                }
                Some(Event::SearchFirstMatch(index)) => {
                    if let Some(screen) = screens.get(index) {
                        screen.search_first_match()
                    } else {
                        DisplayAction::None
                    }
                }
                Some(Event::SearchFinished(index)) => {
                    if let Some(screen) = screens.get(index) {
                        screen.search_finished()
                    } else {
                        DisplayAction::None
                    }
                }
                _ => DisplayAction::None,
            }
        };

        // Process the action.  We may get new actions in return from the action.
        loop {
            match std::mem::replace(&mut action, DisplayAction::None) {
                DisplayAction::None => break,
                DisplayAction::Run(mut f) => action = f(screens.current())?,
                DisplayAction::Change(c) => {
                    term.render(&[c]).map_err(Error::Termwiz)?;
                }
                DisplayAction::Render => event_sender.send_unique(Event::Render, &render_unique)?,
                DisplayAction::Refresh => {
                    event_sender.send_unique(Event::Refresh, &refresh_unique)?
                }
                DisplayAction::RefreshPrompt => {
                    screens.current().refresh_prompt();
                    event_sender.send_unique(Event::Render, &render_unique)?;
                }
                DisplayAction::NextFile => {
                    screens.overlay = None;
                    if screens.current_index < screens.screens.len() - 1 {
                        screens.current_index += 1;
                        let screen = screens.current();
                        let size = term.get_screen_size().map_err(Error::Termwiz)?;
                        screen.resize(size.cols, size.rows);
                        screen.refresh();
                        term.render(&screen.render(&caps)).map_err(Error::Termwiz)?;
                    }
                }
                DisplayAction::PreviousFile => {
                    screens.overlay = None;
                    if screens.current_index > 0 {
                        screens.current_index -= 1;
                        let screen = screens.current();
                        let size = term.get_screen_size().map_err(Error::Termwiz)?;
                        screen.resize(size.cols, size.rows);
                        screen.refresh();
                        term.render(&screen.render(&caps)).map_err(Error::Termwiz)?;
                    }
                }
                DisplayAction::ShowHelp => {
                    let overlay_index = screens.overlay_index + 1;
                    let screen = screens.current();
                    let mut screen = Screen::new(
                        LoadedFile::new_static(
                            overlay_index,
                            "HELP",
                            help_text(screen.keymap())?.into_bytes(),
                            event_sender.clone(),
                        )
                        .into(),
                        config.clone(),
                    )?;
                    let size = term.get_screen_size().map_err(Error::Termwiz)?;
                    screen.resize(size.cols, size.rows);
                    screen.refresh();
                    term.render(&screen.render(&caps)).map_err(Error::Termwiz)?;
                    screens.overlay = Some(screen);
                    screens.overlay_index = overlay_index;
                }
                DisplayAction::ClearOverlay => {
                    screens.overlay = None;
                    let screen = screens.current();
                    let size = term.get_screen_size().map_err(Error::Termwiz)?;
                    screen.resize(size.cols, size.rows);
                    screen.refresh();
                    term.render(&screen.render(&caps)).map_err(Error::Termwiz)?;
                }
                DisplayAction::Quit => {
                    let screen = screens.current();
                    overlay_height.store(screen.overlay_height(), Ordering::SeqCst);
                    return Ok(());
                }
            }
        }
    }
}


//! Error types.

use std::ffi::OsStr;
use std::result::Result as StdResult;
use std::sync::mpsc::RecvError;
use std::sync::mpsc::RecvTimeoutError;
use std::sync::mpsc::SendError;
use std::sync::mpsc::TryRecvError;

use thiserror::Error;

/// Convenient return type for functions.
pub type Result<T> = StdResult<T, Error>;

/// Main error type.
#[derive(Debug, Error)]
pub enum Error {
    /// Comes from [Termwiz](https://crates.io/crates/termwiz).
    #[error("terminal error")]
    Termwiz(#[source] termwiz::Error),

    /// Comes from [Regex](https://github.com/rust-lang/regex).
    #[error("regex error")]
    Regex(#[from] regex::Error),

    /// Generic I/O error.
    #[error("i/o error")]
    Io(#[from] std::io::Error),

    /// Returned when persisting a temporary file fails.
    #[error(transparent)]
    TempfilePersist(#[from] tempfile::PersistError),

    /// Keymap-related error.
    #[error("keymap error")]
    Keymap(#[from] crate::keymap_error::KeymapError),

    /// Binding-related error.
    #[error("keybinding error")]
    Binding(#[from] crate::bindings::BindingError),

    /// Generic formatting error.
    #[error(transparent)]
    Fmt(#[from] std::fmt::Error),

    /// Receive error on a channel.
    #[error("channel error")]
    ChannelRecv(#[from] RecvError),

    /// Try-receive error on a channel.
    #[error("channel error")]
    ChannelTryRecv(#[from] TryRecvError),

    /// Receive-timeout error on a channel.
    #[error("channel error")]
    ChannelRecvTimeout(#[from] RecvTimeoutError),

    /// Send error on a channel.
    #[error("channel error")]
    ChannelSend,

    /// Error returned if the terminfo database is missing.
    #[error("terminfo database not found (is $TERM correct?)")]
    TerminfoDatabaseMissing,

    /// Wrapped error within the context of a command.
    #[error("error running command '{command}'")]
    WithCommand {
        /// Wrapped error.
        #[source]
        error: Box<Self>,

        /// Command the error is about.
        command: String,
    },

    /// Wrapped error within the context of a file.
    #[error("error loading file '{file}'")]
    WithFile {
        /// Wrapped error.
        #[source]
        error: Box<Self>,

        /// File the error is about.
        file: String,
    },
}

impl Error {
    #[cfg(feature = "load_file")]
    pub(crate) fn with_file(self, file: impl AsRef<str>) -> Self {
        Self::WithFile {
            error: Box::new(self),
            file: file.as_ref().to_owned(),
        }
    }

    pub(crate) fn with_command(self, command: impl AsRef<OsStr>) -> Self {
        Self::WithCommand {
            error: Box::new(self),
            command: command.as_ref().to_string_lossy().to_string(),
        }
    }
}

impl<T> From<SendError<T>> for Error {
    fn from(_send_error: SendError<T>) -> Error {
        Error::ChannelSend
    }
}


//! Events.

use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::mpsc;
use std::sync::Arc;
use std::time::Duration;

use termwiz::input::InputEvent;
use termwiz::terminal::Terminal;
use termwiz::terminal::TerminalWaker;

use crate::action::Action;
use crate::action::ActionSender;
use crate::error::Error;
use crate::file::FileIndex;

/// An event.
///
/// Events drive most of the main processing of `sp`.  This includes user
/// input, state changes, and display refresh requests.
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Event {
    /// An action.
    Action(Action),
    /// An input event.
    Input(InputEvent),
    /// A file has finished loading.
    Loaded(FileIndex),
    #[cfg(feature = "load_file")]
    /// A file has started loading more data.
    Appending(FileIndex),
    /// A file has started reloading.
    Reloading(FileIndex),
    /// Render an update to the screen.
    Render,
    /// Refresh the whole screen.
    Refresh,
    /// Refresh the overlay.
    RefreshOverlay,
    /// A new progress display is available.
    Progress,
    /// Search has found the first match.
    SearchFirstMatch(FileIndex),
    /// Search has finished.
    SearchFinished(FileIndex),
}

#[derive(Debug, Clone)]
pub(crate) struct UniqueInstance(Arc<AtomicBool>);

impl UniqueInstance {
    pub(crate) fn new() -> UniqueInstance {
        UniqueInstance(Arc::new(AtomicBool::new(false)))
    }
}

pub(crate) enum Envelope {
    Normal(Event),
    Unique(Event, UniqueInstance),
}

/// An event sender endpoint.
#[derive(Clone)]
pub(crate) struct EventSender(mpsc::Sender<Envelope>, TerminalWaker);

impl EventSender {
    pub(crate) fn send(&self, event: Event) -> Result<(), Error> {
        self.0.send(Envelope::Normal(event))?;
        self.1.wake()?;
        Ok(())
    }
    pub(crate) fn send_unique(&self, event: Event, unique: &UniqueInstance) -> Result<(), Error> {
        if unique
            .0
            .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
            .is_ok()
        {
            self.0.send(Envelope::Unique(event, unique.clone()))?;
            self.1.wake()?;
        }
        Ok(())
    }
}

/// An event stream.  This is a wrapper multi-producer, single-consumer
/// stream of `Event`s.
pub(crate) struct EventStream {
    send: mpsc::Sender<Envelope>,
    recv: mpsc::Receiver<Envelope>,
    waker: TerminalWaker,
}

impl EventStream {
    /// Create a new event stream.
    pub(crate) fn new(waker: TerminalWaker) -> EventStream {
        let (send, recv) = mpsc::channel();
        EventStream { send, recv, waker }
    }

    /// Create a sender for the event stream.
    pub(crate) fn sender(&self) -> EventSender {
        EventSender(self.send.clone(), self.waker.clone())
    }

    /// Create an action sender for the event stream.
    pub(crate) fn action_sender(&self) -> ActionSender {
        ActionSender::new(self.sender())
    }

    /// Attempt to receive an event. If timeout is specified, wait up to timeout
    /// for an event, returning None if there is no event. With no timeout,
    /// return None immediately if there is no event.
    pub(crate) fn try_recv(&self, timeout: Option<Duration>) -> Result<Option<Event>, Error> {
        let envelope = match timeout {
            Some(timeout) => match self.recv.recv_timeout(timeout) {
                Ok(envelope) => envelope,
                Err(mpsc::RecvTimeoutError::Timeout) => return Ok(None),
                Err(e) => return Err(e.into()),
            },
            None => match self.recv.try_recv() {
                Ok(envelope) => envelope,
                Err(mpsc::TryRecvError::Empty) => return Ok(None),
                Err(e) => return Err(e.into()),
            },
        };

        match envelope {
            Envelope::Normal(event) => Ok(Some(event)),
            Envelope::Unique(event, unique) => {
                unique.0.store(false, Ordering::SeqCst);
                Ok(Some(event))
            }
        }
    }

    /// Get an event, either from the event stream or from the terminal.
    pub(crate) fn get(
        &self,
        term: &mut dyn Terminal,
        wait: Option<Duration>,
    ) -> Result<Option<Event>, Error> {
        loop {
            if let Some(event) = self.try_recv(None)? {
                return Ok(Some(event));
            }

            // The queue is empty.  Try to get an input event from the terminal.
            match term.poll_input(wait).map_err(Error::Termwiz)? {
                Some(InputEvent::Wake) => {}
                Some(input_event) => return Ok(Some(Event::Input(input_event))),
                None => return Ok(None),
            }
        }
    }
}


//! Files.

use std::borrow::Cow;

use enum_dispatch::enum_dispatch;

pub(crate) use crate::control::ControlledFile;
pub(crate) use crate::loaded_file::LoadedFile;

/// An identifier for a file streampager is paging.
pub type FileIndex = usize;

/// Default value for `needed_lines`.
pub(crate) const DEFAULT_NEEDED_LINES: usize = 5000;

/// Trait for getting information from a file.
#[enum_dispatch]
pub(crate) trait FileInfo {
    /// The file's index.
    fn index(&self) -> FileIndex;

    /// The file's title.
    fn title(&self) -> Cow<'_, str>;

    /// The file's info.
    fn info(&self) -> Cow<'_, str>;

    /// True once the file is loaded and all newlines have been parsed.
    fn loaded(&self) -> bool;

    /// Returns the number of lines in the file.
    fn lines(&self) -> usize;

    /// Runs the `call` function, passing it the contents of line `index`.
    /// Tries to avoid copying the data if possible, however the borrowed
    /// line only lasts as long as the function call.
    fn with_line<T, F>(&self, index: usize, call: F) -> Option<T>
    where
        F: FnMut(Cow<'_, [u8]>) -> T;

    /// Set how many lines are needed.
    ///
    /// If `self.lines()` exceeds that number, pause loading until
    /// `set_needed_lines` is called with a larger number.
    /// This is only effective for "streamed" input.
    fn set_needed_lines(&self, lines: usize);

    /// True if the loading thread has been paused.
    fn paused(&self) -> bool;
}

/// A file.
#[enum_dispatch(FileInfo)]
#[derive(Clone)]
pub(crate) enum File {
    LoadedFile,
    ControlledFile,
}


//! Help screen

use std::fmt::Write;

use termwiz::input::{KeyCode, Modifiers};
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};

use crate::bindings::{Category, Keymap};
use crate::error::Result;

fn write_key_names(text: &mut String, keys: &[(Modifiers, KeyCode)]) -> Result<usize> {
    let mut w = 0;
    for (index, (modifiers, keycode)) in keys.iter().enumerate() {
        if index > 0 {
            if index == keys.len() - 1 {
                text.push_str("\x1B[0;2m or ");
                w += 4;
            } else {
                text.push_str("\x1B[0;2m, ");
                w += 2;
            }
        }
        text.push_str("\x1B[1m");
        for (modifier, desc) in [
            (Modifiers::CTRL, "Ctrl-"),
            (Modifiers::ALT, "Alt-"),
            (Modifiers::SUPER, "Super-"),
            (Modifiers::SHIFT, "Shift-"),
        ]
        .iter()
        {
            if modifiers.contains(*modifier) {
                text.push_str(desc);
                w += desc.width();
            }
        }
        match keycode {
            KeyCode::Char(' ') => {
                text.push_str("Space");
                w += 5;
            }
            KeyCode::Char(c) => {
                text.push(*c);
                w += c.width().unwrap_or(0);
            }
            KeyCode::Function(n) => {
                let n_string = n.to_string();
                text.push('F');
                text.push_str(&n_string);
                w += n_string.width() + 1;
            }
            KeyCode::UpArrow => {
                text.push_str("Up");
                w += 2;
            }
            KeyCode::DownArrow => {
                text.push_str("Down");
                w += 4;
            }
            KeyCode::LeftArrow => {
                text.push_str("Left");
                w += 4;
            }
            KeyCode::RightArrow => {
                text.push_str("Right");
                w += 5;
            }
            keycode => {
                let mut key_string = String::new();
                write!(key_string, "{:?}", keycode)?;
                text.push_str(&key_string);
                w += key_string.width();
            }
        }
    }
    text.push_str("\x1B[m");
    Ok(w)
}

pub(crate) fn help_text(keymap: &Keymap) -> Result<String> {
    let mut text = String::from(
        "\n  \x1B[1;3;36;38;5;39mStream Pager\x1B[m \x1B[35;38;57m(\x1B[1msp\x1B[22m)\n",
    );
    let prefix = "                                  ";

    for category in Category::categories() {
        let mut title = false;

        for (binding, keys) in keymap.iter_keys() {
            if binding.category() == category {
                if !title {
                    write!(text, "\n  \x1B[1;4;33;38;5;130m{}\x1B[m\n\n", category)?;
                    title = true;
                }
                text.push_str("    ");
                let w = write_key_names(&mut text, keys)?;
                if w < 34 {
                    text.push_str(&prefix[w..]);
                } else {
                    text.push_str("\n    ");
                    text.push_str(prefix);
                }
                writeln!(text, "{}", binding)?;
            }
        }
    }

    Ok(text)
}


//! Keymaps

use termwiz::input::{KeyCode, Modifiers};

use crate::bindings::{BindingConfig, Keymap};
use crate::keymap_error::{KeymapError, Result};

// Static data to generate a keymap.
type KeymapData = &'static [((Modifiers, KeyCode), BindingConfig)];

macro_rules! keymaps {
    ( $( $visibility:vis mod $name:ident ; )* ) => {
        $( $visibility mod $name ; )*

        pub(crate) static KEYMAPS: &'static [(&'static str, $crate::keymaps::KeymapData)] = &[
            $( ( stringify!( $name ), $crate::keymaps::$name::KEYMAP ), )*
        ];
    }
}

keymaps! {
    pub(crate) mod default;
}

pub(crate) fn load(name: &str) -> Result<Keymap> {
    for (keymap_name, keymap_data) in KEYMAPS {
        if &name == keymap_name {
            return Ok(Keymap::from(keymap_data.iter()));
        }
    }

    #[cfg(feature = "keymap-file")]
    {
        if let Some(mut path) = dirs::config_dir() {
            path.push("streampager");
            path.push("keymaps");
            path.push(name);
            if let Ok(keymap_data) = std::fs::read_to_string(&path) {
                let keymap_file = crate::keymap_file::KeymapFile::parse(&keymap_data)
                    .map_err(|err| err.with_file(path))?;
                return Ok(Keymap::from(keymap_file.iter()));
            }
        }
    }

    Err(KeymapError::MissingKeymap(name.to_string()))
}


//! Errors specific to keymaps.

use std::path::{Path, PathBuf};

use thiserror::Error;

/// Errors specific to keymaps.
#[derive(Debug, Error)]
pub enum KeymapError {
    /// Error when encountering an unknown modifier.
    #[error("unknown modifier: {0}")]
    UnknownModifier(String),

    /// Error when a key definition is missing.
    #[error("key definition missing")]
    MissingDefinition,

    /// Error when a keymap is missing.
    #[error("keymap not found: {0}")]
    MissingKeymap(String),

    /// Error when a key is unrecognised.
    #[error("unrecognised key: {0}")]
    UnknownKey(String),

    /// Parsing error.
    #[cfg(feature = "keymap-file")]
    #[error("parse error")]
    Parse(#[from] pest::error::Error<crate::keymap_file::Rule>),

    /// Error related to parsing a binding within a keymap.
    #[error("keybinding error")]
    Binding(#[from] crate::bindings::BindingError),

    /// Wrapped error within the context of a file.
    #[error("error loading file '{file}'")]
    WithFile {
        /// Wrapped error.
        #[source]
        error: Box<KeymapError>,

        /// File the error is about.
        file: PathBuf,
    },
}

impl KeymapError {
    #[allow(unused)]
    pub(crate) fn with_file(self, file: impl AsRef<Path>) -> Self {
        Self::WithFile {
            error: Box::new(self),
            file: file.as_ref().to_owned(),
        }
    }
}

pub(crate) type Result<T> = std::result::Result<T, KeymapError>;


//! Keymap macro

// Keymap macro implementation.
//
// Token-tree muncher: { rest } ( visible ) ( modifiers ) ( keys ) [ data ]
//
// Consumes definition from 'rest'.  Modifiers are accumulated in 'modifiers'.  Key definitions are
// accumulated in 'keys'.  Bindings are accumulated in 'data'.
macro_rules! keymap_impl {
    // Base case: generate keymap data.
    ( {} ( $visible:literal ) () () $data:tt ) => {
        pub(crate) static KEYMAP: $crate::keymaps::KeymapData = &$data;
    };

    // , (consume comma between keys)
    (
        { , $( $rest:tt )* }
        ( $visible:literal )
        ( )
        ( $( $keys:tt )* )
        [ $( $data:tt )* ]
    ) => {
        keymap_impl! {
            { $( $rest )* }
            ( $visible )
            ( )
            ( $( $keys )* )
            [ $( $data )* ]
        }
    };

    // => Binding (termination)
    (
        {
            => $action:ident $( ( $( $action_params:tt )* ) )? ;
            $( $rest:tt )*
        }
        ( $visible:literal )
        ( )
        ( )
        [ $( $data:tt )* ]
    ) => {
        keymap_impl! {
            { $( $rest )* }
            ( $visible )
            ( )
            ( )
            [ $( $data )* ]
        }
    };

    // => Binding (assign key)
    (
        {
            => $action:ident $( ( $( $action_params:tt )* ) )? ;
            $( $rest:tt )*
        }
        ( $visible:literal )
        ( )
        ( $key:tt $key_visible:literal $( $keys:tt )* )
        [ $( $data:tt )* ]
    ) => {
        keymap_impl! {
            {
                => $action $( ( $( $action_params )* ) )? ;
                $( $rest )*
            }
            ( $visible )
            ( )
            ( $( $keys )* )
            [
                $( $data )*
                (
                    $key,
                    $crate::bindings::BindingConfig {
                        binding: $crate::bindings::Binding::Action(
                            $crate::action::Action::$action $( ( $( $action_params )* ) )?
                        ),
                        visible: $key_visible,
                    },
                ),
            ]
        }
    };

    // CTRL
    (
        { CTRL $( $rest:tt )* }
        ( $visible:literal )
        ( $( $modifier:ident )* )
        ( $( $keys:tt )* )
        [ $( $data:tt )* ]
    ) => {
        keymap_impl! {
            { $( $rest )* }
            ( $visible )
            ( $( $modifier )* CTRL )
            ( $( $keys )* )
            [ $( $data )* ]
        }
    };

    // SHIFT
    (
        { SHIFT $( $rest:tt )* }
        ( $visible:literal )
        ( $( $modifier:ident )* )
        ( $( $keys:tt )* )
        [ $( $data:tt )* ]
    ) => {
        keymap_impl! {
            { $( $rest )* }
            ( $visible )
            ( $( $modifier )* SHIFT )
            ( $( $keys )* )
            [ $( $data )* ]
        }
    };

    // ALT
    (
        { ALT $( $rest:tt )* }
        ( $visible:literal )
        ( $( $modifier:ident )* )
        ( $( $keys:tt )* )
        [ $( $data:tt )* ]
    ) => {
        keymap_impl! {
            { $( $rest )* }
            ( $visible )
            ( $( $modifier )* ALT )
            ( $( $keys )* )
            [ $( $data )* ]
        }
    };

    // SUPER
    (
        { SUPER $( $rest:tt )* }
        ( $visible:literal )
        ( $( $modifier:ident )* )
        ( $( $keys:tt )* )
        [ $( $data:tt )* ]
    ) => {
        keymap_impl! {
            { $( $rest )* }
            ( $visible )
            ( $( $modifier )* SUPER )
            ( $( $keys )* )
            [ $( $data )* ]
        }
    };

    // Character key (e.g. 'c')
    (
        { $key:literal $( $rest:tt )* }
        ( $visible:literal )
        ( $( $modifier:ident )* )
        ( $( $keys:tt )* )
        [ $( $data:tt )* ]
    ) => {
        keymap_impl! {
            { $( $rest )* }
            ( true )
            ( )
            (
                $( $keys )*
                (
                    termwiz::input::Modifiers::from_bits_truncate(
                        $( termwiz::input::Modifiers::$modifier.bits() | )*
                        termwiz::input::Modifiers::NONE.bits()
                    ),
                    termwiz::input::KeyCode::Char($key),
                )
                $visible
            )
            [ $( $data )* ]
        }
    };

    // F <number>
    (
        { F $num:literal $( $rest:tt )* }
        ( $visible:literal )
        ( $( $modifier:ident )* )
        ( $( $keys:tt )* )
        [ $( $data:tt )* ]
    ) => {
        keymap_impl! {
            { $( $rest )* }
            ( true )
            ( )
            (
                $( $keys )*
                (
                    termwiz::input::Modifiers::from_bits_truncate(
                        $( termwiz::input::Modifiers::$modifier.bits() | )*
                        termwiz::input::Modifiers::NONE.bits()
                    ),
                    termwiz::input::KeyCode::Function($num),
                )
                $visible
            )
            [ $( $data )* ]
        }
    };

    // KeyCode
    (
        { $key:ident $( $rest:tt )* }
        ( $visible:literal )
        ( $( $modifier:ident )* )
        ( $( $keys:tt )* )
        [ $( $data:tt )* ]
    ) => {
        keymap_impl! {
            { $( $rest )* }
            ( true )
            ( )
            (
                $( $keys )*
                (
                    termwiz::input::Modifiers::from_bits_truncate(
                        $( termwiz::input::Modifiers::$modifier.bits() | )*
                        termwiz::input::Modifiers::NONE.bits()
                    ),
                    termwiz::input::KeyCode::$key,
                )
                $visible
            )
            [ $( $data )* ]
        }
    };

    // ( hidden binding )
    (
        { ( $( $bind:tt )* ) $( $rest:tt )* }
        ( $visible:literal )
        ( $( $modifier:ident )* )
        ( $( $keys:tt )* )
        [ $( $data:tt )* ]
    ) => {
        keymap_impl! {
            { $( $bind )* $( $rest )* }
            ( false )
            ( $( $modifier )* )
            ( $( $keys )* )
            [ $( $data )* ]
        }
    };
}

macro_rules! keymap {
    ( $( $all:tt )* ) => {
        keymap_impl! { { $( $all )* } (true) () () [] }
    };
}


//! Stream Pager
//!
//! A pager for streams.
#![warn(missing_docs)]
#![recursion_limit = "1024"]
#![allow(clippy::comparison_chain)]

pub mod action;
mod bar;
pub mod bindings;
mod buffer;
#[cfg(feature = "load_file")]
mod buffer_cache;
mod command;
pub mod config;
pub mod control;
mod direct;
mod display;
pub mod error;
mod event;
pub mod file;
mod help;
mod keymap_error;
#[cfg(feature = "keymap-file")]
mod keymap_file;
#[macro_use]
mod keymap_macro;
mod keymaps;
mod line;
mod line_cache;
mod line_drawing;
mod loaded_file;
mod overstrike;
pub mod pager;
mod progress;
mod prompt;
mod prompt_history;
mod refresh;
mod ruler;
mod screen;
mod search;
mod util;

pub use error::{Error, Result};
pub use file::FileIndex;
pub use pager::Pager;