dua-cli 2.34.0

A tool to conveniently learn about the disk usage of directories, fast!
Documentation
use crate::interactive::CursorDirection;
use crate::interactive::widgets::tui_ext::{
    draw_text_nowrap_fn,
    util::{block_width, rect},
};
pub use crossterm::event::KeyCode::*;
use crossterm::event::{KeyEvent, KeyEventKind, KeyModifiers};
use std::{borrow::Borrow, cell::RefCell};
use tui::{
    buffer::Buffer,
    layout::Rect,
    style::{Color, Modifier, Style},
    text::{Line, Span, Text},
    widgets::{Block, Borders, Paragraph, Widget},
};

#[derive(Default, Clone)]
pub struct HelpPane {
    pub scroll: u16,
}

pub struct HelpPaneProps {
    pub border_style: Style,
    pub has_focus: bool,
    pub esc_navigates_back: bool,
}

fn margin(r: Rect, margin: u16) -> Rect {
    Rect {
        x: r.x + margin,
        y: r.y + margin,
        width: r.width - 2 * margin,
        height: r.height - 2 * margin,
    }
}

impl HelpPane {
    pub fn process_events(&mut self, key: KeyEvent) {
        if key.kind == KeyEventKind::Release {
            return;
        }
        match key.code {
            Char('H') => self.scroll_help(CursorDirection::ToTop),
            Char('G') => self.scroll_help(CursorDirection::ToBottom),
            Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                self.scroll_help(CursorDirection::PageUp)
            }
            Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                self.scroll_help(CursorDirection::PageDown)
            }
            PageUp => self.scroll_help(CursorDirection::PageUp),
            PageDown => self.scroll_help(CursorDirection::PageDown),
            Char('k') | Up => self.scroll_help(CursorDirection::Up),
            Char('j') | Down => self.scroll_help(CursorDirection::Down),
            _ => {}
        };
    }
    fn scroll_help(&mut self, direction: CursorDirection) {
        self.scroll = direction.move_cursor(self.scroll as usize) as u16;
    }

    pub fn render(&mut self, props: impl Borrow<HelpPaneProps>, area: Rect, buf: &mut Buffer) {
        let esc_navigates_back = props.borrow().esc_navigates_back;
        let lines = {
            let lines = RefCell::new(Vec::<Line<'_>>::with_capacity(30));
            let add_newlines = |n| {
                for _ in 0..n {
                    lines.borrow_mut().push(Line::from(Span::raw("")))
                }
            };

            let spacer = || add_newlines(2);
            let title = |name: &str| {
                lines.borrow_mut().push(Line::from(Span::styled(
                    name.to_string(),
                    Style {
                        add_modifier: Modifier::BOLD | Modifier::UNDERLINED,
                        ..Default::default()
                    },
                )));
                add_newlines(1);
            };
            let hotkey = |keys, description, other_line: Option<&str>| {
                let separator_size = 3;
                let column_size = 11 + separator_size;
                lines.borrow_mut().push(Line::from(vec![
                    Span::styled(
                        format!(
                            "{:>column_size$}",
                            keys,
                            column_size = column_size - separator_size
                        ),
                        Style {
                            fg: Color::Green.into(),
                            ..Default::default()
                        },
                    ),
                    Span::from(format!(" => {description}")),
                ]));
                if let Some(second_line) = other_line {
                    lines.borrow_mut().push(Line::from(Span::from(format!(
                        "{:>column_size$}{}",
                        "",
                        second_line,
                        column_size = column_size + 1
                    ))));
                }
            };

            title("Pane control");
            {
                if esc_navigates_back {
                    hotkey(
                        "q",
                        "Close the current pane. In main view, quit (may require confirmation).",
                        None,
                    );
                    hotkey(
                        "<Esc>",
                        "Close the current pane.",
                        Some("In main view, ascend to the parent directory."),
                    );
                } else {
                    hotkey(
                        "q/<Esc>",
                        "Close the current pane.",
                        Some("Closes the program if no pane is open."),
                    );
                }
                hotkey(
                    "<Tab>",
                    "Cycle between all open panes.",
                    Some("Activate 'Marked Items' pane to delete selected files."),
                );
                hotkey("?", "Show or hide this help pane.", None);
                spacer();
            }
            title("Navigation");
            {
                hotkey("j/<Down>", "Move down 1 entry.", None);
                hotkey("k/<Up>", "Move up 1 entry.", None);
                hotkey("o/l/<Enter>", "Descent into the selected directory.", None);
                hotkey("<Right>", "^", None);
                hotkey(
                    "u/h/<Left>",
                    "Ascent one level into the parent directory.",
                    None,
                );
                hotkey("<Backspace>", "^", None);
                hotkey("Ctrl + d", "Move down 10 entries.", None);
                hotkey("<Page Down>", "^", None);
                hotkey("Ctrl + u", "Move up 10 entries.", None);
                hotkey("<Page Up>", "^", None);
                hotkey("H/<Home>", "Move to the top of the list.", None);
                hotkey("G/<End>", "Move to the bottom of the list.", None);
                spacer();
            }
            title("Display");
            {
                hotkey("s", "Toggle sort by size descending/ascending.", None);
                hotkey(
                    "m",
                    "Toggle sort by modified time descending/ascending.",
                    None,
                );
                hotkey("M", "Show/hide modified time.", None);
                hotkey("c", "Toggle sort by entries descending/ascending.", None);
                hotkey("C", "Show/hide entry count.", None);
                hotkey("n", "Toggle sort by name ascending/descending.", None);
                hotkey(
                    "g/S",
                    "Cycle through percentage display and bar options.",
                    None,
                );
                spacer();
            }
            title("Open/Mark/Search");
            {
                hotkey(
                    "Shift + o",
                    "Open the selected entry with the associated program.",
                    None,
                );
                hotkey(
                    "d",
                    "Toggle the currently selected entry and move down.",
                    None,
                );
                hotkey(
                    "x",
                    "Mark the currently selected entry for deletion and move down.",
                    None,
                );
                hotkey("<Space>", "Toggle the currently selected entry.", None);
                hotkey("a", "Toggle all entries.", None);
                hotkey(
                    "/",
                    "Git-style glob search. Toggle case with 'I'.",
                    Some("Search starts from the current directory."),
                );
                hotkey("r", "Refresh only the selected entry.", None);
                hotkey("R", "Refresh all entries in the current view.", None);
                spacer();
            }
            title("Mark entries pane");
            {
                hotkey(
                    "x/d/<Space>",
                    "Remove the selected entry from the list.",
                    None,
                );
                hotkey("a", "Remove all entries from the list.", None);
                hotkey(
                    "Ctrl + r",
                    "Permanently delete all marked entries without prompt.",
                    Some("This operation cannot be undone!"),
                );
                #[cfg(feature = "trash-move")]
                hotkey(
                    "Ctrl + t",
                    "Move all marked entries to the trash bin.",
                    Some("The entries can be restored from the trash bin."),
                );
                spacer();
            }
            title("Application control");
            {
                hotkey(
                    "Ctrl + c",
                    "Close the application. No questions asked!",
                    None,
                );
                spacer();
            }
            lines.into_inner()
        };

        let HelpPaneProps {
            border_style,
            has_focus,
            ..
        } = props.borrow();

        let title = "Help";
        let block = Block::default()
            .title(title)
            .border_style(*border_style)
            .borders(Borders::ALL);
        let inner_block_area = block.inner(area);
        block.render(area, buf);

        if *has_focus {
            let help_text = " . = o|.. = u ── ⇊ = Ctrl+d|↓ = j|⇈ = Ctrl+u|↑ = k ";
            let help_text_block_width = block_width(help_text);
            let bound = Rect {
                width: area.width.saturating_sub(1),
                ..area
            };
            if block_width(title) + help_text_block_width <= bound.width {
                draw_text_nowrap_fn(
                    rect::snap_to_right(bound, help_text_block_width),
                    buf,
                    help_text,
                    |_, _, _| Style::default(),
                );
            }
        }

        let area = margin(inner_block_area, 1);
        self.scroll = self
            .scroll
            .min(lines.len().saturating_sub(area.height as usize) as u16);
        Paragraph::new(Text::from(lines))
            .scroll((self.scroll, 0))
            .render(area, buf);
    }
}