W26: Wire viewer F9 menubar into viewer display
Press F9 in the viewer to toggle the menubar at the top of the viewer. The menubar occupies the top row, and the viewer content shifts down by 1 row when the menubar is open. - Viewer::menubar: Option<ViewerMenuBar> field - Viewer::render: calls mb.render() at the top when menubar is open - Viewer::handle_key: F9 toggles, other keys route to menubar when open - Viewer::execute_menubar_cmd: dispatches ViewerCmd to existing viewer methods (ToggleHex, ToggleMagic, ToggleWrap, ToggleGrowing, Search, SearchNext, SearchPrev, Goto) Bookmark / file navigation / close commands are documented as a planned follow-up — still reachable via their keyboard shortcuts. Tests: 1393 pass, zero warnings.
This commit is contained in:
@@ -0,0 +1,472 @@
|
||||
//! Menu bar for the viewer (F9).
|
||||
//!
|
||||
//! Mirrors `filemanager::menubar::MenuBar` and
|
||||
//! `editor::menubar::EditorMenuBar` — top-level menus with arrow-key
|
||||
//! navigation, dropdown rendering, and item dispatch.
|
||||
//!
|
||||
//! Surface the existing viewer toggles (hex mode, magic mode, line
|
||||
//! wrap, growing buffer, bookmarks) plus search/goto. Closing the
|
||||
//! viewer maps to Esc/F10.
|
||||
|
||||
use ratatui::layout::{Constraint, Direction, Layout, Rect};
|
||||
use ratatui::style::{Modifier, Style};
|
||||
use ratatui::text::{Line, Span};
|
||||
use ratatui::widgets::{Clear, Paragraph};
|
||||
use ratatui::Frame;
|
||||
|
||||
use crate::key::Key;
|
||||
use crate::terminal::color::Theme;
|
||||
use crate::terminal::mc_skin;
|
||||
use crate::terminal::popup::render_popup;
|
||||
|
||||
/// A viewer command surfaced by the menu bar.
|
||||
///
|
||||
/// Mirrors Midnight Commander's viewer menu items.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ViewerCmd {
|
||||
/// No-op (used for separators).
|
||||
Nop,
|
||||
/// Toggle hex view.
|
||||
ToggleHex,
|
||||
/// Toggle magic (binary detection) on open.
|
||||
ToggleMagic,
|
||||
/// Toggle line wrap.
|
||||
ToggleWrap,
|
||||
/// Toggle growing buffer (tail -f style).
|
||||
ToggleGrowing,
|
||||
/// Search forward.
|
||||
Search,
|
||||
/// Search next.
|
||||
SearchNext,
|
||||
/// Search previous.
|
||||
SearchPrev,
|
||||
/// Goto line.
|
||||
Goto,
|
||||
/// Toggle bookmark on current line.
|
||||
BookmarkToggle,
|
||||
/// Jump to next bookmark.
|
||||
BookmarkNext,
|
||||
/// Jump to previous bookmark.
|
||||
BookmarkPrev,
|
||||
/// Open the next file in the directory.
|
||||
NextFile,
|
||||
/// Open the previous file in the directory.
|
||||
PrevFile,
|
||||
/// Close the viewer.
|
||||
Close,
|
||||
}
|
||||
|
||||
/// A single item in a menu — either a labelled command or a separator.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MenuItem {
|
||||
/// A selectable command item.
|
||||
Item {
|
||||
/// Display label.
|
||||
label: String,
|
||||
/// Hotkey character (highlighted in the label).
|
||||
hotkey: char,
|
||||
/// Command to dispatch when selected.
|
||||
cmd: ViewerCmd,
|
||||
},
|
||||
/// Visual separator (a blank line in the dropdown).
|
||||
Separator,
|
||||
}
|
||||
|
||||
impl MenuItem {
|
||||
/// Build a labelled item with a hotkey.
|
||||
#[must_use]
|
||||
pub fn item(label: &str, hotkey: char, cmd: ViewerCmd) -> Self {
|
||||
Self::Item {
|
||||
label: label.to_string(),
|
||||
hotkey,
|
||||
cmd,
|
||||
}
|
||||
}
|
||||
/// Build a separator.
|
||||
#[must_use]
|
||||
pub fn separator() -> Self {
|
||||
Self::Separator
|
||||
}
|
||||
}
|
||||
|
||||
/// A single top-level menu: a title and a list of items.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Menu {
|
||||
/// Title shown in the top bar.
|
||||
pub title: String,
|
||||
/// Dropdown items, in render order.
|
||||
pub items: Vec<MenuItem>,
|
||||
}
|
||||
|
||||
impl Menu {
|
||||
/// Construct a menu with a title and items.
|
||||
#[must_use]
|
||||
pub fn new(title: &str, items: Vec<MenuItem>) -> Self {
|
||||
Self {
|
||||
title: title.to_string(),
|
||||
items,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The viewer menu bar state.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ViewerMenuBar {
|
||||
/// All top-level menus.
|
||||
menus: Vec<Menu>,
|
||||
/// Index of the currently highlighted menu (0 = first).
|
||||
active_menu: usize,
|
||||
/// Index of the highlighted item within the active menu's
|
||||
/// dropdown.
|
||||
selected_item: usize,
|
||||
/// Whether the dropdown is currently open. When false, only the
|
||||
/// top bar is visible.
|
||||
dropdown_open: bool,
|
||||
}
|
||||
|
||||
impl Default for ViewerMenuBar {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ViewerMenuBar {
|
||||
/// Construct the default viewer menu set: File, View, Search,
|
||||
/// Bookmark, Goto.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
let menus = vec![
|
||||
Menu::new(
|
||||
"File",
|
||||
vec![
|
||||
MenuItem::item("Next file", 'n', ViewerCmd::NextFile),
|
||||
MenuItem::item("Prev file", 'p', ViewerCmd::PrevFile),
|
||||
MenuItem::separator(),
|
||||
MenuItem::item("Close", 'c', ViewerCmd::Close),
|
||||
],
|
||||
),
|
||||
Menu::new(
|
||||
"View",
|
||||
vec![
|
||||
MenuItem::item("Hex mode", 'h', ViewerCmd::ToggleHex),
|
||||
MenuItem::item("Magic", 'm', ViewerCmd::ToggleMagic),
|
||||
MenuItem::item("Wrap", 'w', ViewerCmd::ToggleWrap),
|
||||
MenuItem::item("Growing buffer", 'g', ViewerCmd::ToggleGrowing),
|
||||
],
|
||||
),
|
||||
Menu::new(
|
||||
"Search",
|
||||
vec![
|
||||
MenuItem::item("Find...", 'f', ViewerCmd::Search),
|
||||
MenuItem::item("Find next", 'n', ViewerCmd::SearchNext),
|
||||
MenuItem::item("Find prev", 'p', ViewerCmd::SearchPrev),
|
||||
],
|
||||
),
|
||||
Menu::new(
|
||||
"Bookmark",
|
||||
vec![
|
||||
MenuItem::item("Toggle", 't', ViewerCmd::BookmarkToggle),
|
||||
MenuItem::item("Next", 'n', ViewerCmd::BookmarkNext),
|
||||
MenuItem::item("Prev", 'p', ViewerCmd::BookmarkPrev),
|
||||
],
|
||||
),
|
||||
Menu::new(
|
||||
"Goto",
|
||||
vec![MenuItem::item("Line...", 'l', ViewerCmd::Goto)],
|
||||
),
|
||||
];
|
||||
Self {
|
||||
menus,
|
||||
active_menu: 0,
|
||||
selected_item: 0,
|
||||
dropdown_open: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Open the menu bar (show the top row with the first menu
|
||||
/// highlighted).
|
||||
pub fn open(&mut self) {
|
||||
self.dropdown_open = true;
|
||||
self.active_menu = 0;
|
||||
self.selected_item = 0;
|
||||
}
|
||||
|
||||
/// Close the menu bar without dispatching.
|
||||
pub fn close(&mut self) {
|
||||
self.dropdown_open = false;
|
||||
}
|
||||
|
||||
/// `true` if the menu bar is currently visible (either as a
|
||||
/// collapsed top row or with the dropdown open).
|
||||
#[must_use]
|
||||
pub fn is_open(&self) -> bool {
|
||||
self.dropdown_open
|
||||
}
|
||||
|
||||
/// Currently-highlighted item in the open dropdown, or `None`
|
||||
/// if the dropdown is not open or the active menu has no
|
||||
/// selectable items.
|
||||
#[must_use]
|
||||
pub fn selected_cmd(&self) -> Option<ViewerCmd> {
|
||||
if !self.dropdown_open {
|
||||
return None;
|
||||
}
|
||||
let menu = self.menus.get(self.active_menu)?;
|
||||
match menu.items.get(self.selected_item)? {
|
||||
MenuItem::Item { cmd, .. } => Some(*cmd),
|
||||
MenuItem::Separator => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle a key while the menu bar is open. Returns `true` if
|
||||
/// the key was consumed (including dispatching a command and
|
||||
/// closing the bar).
|
||||
pub fn handle_key(&mut self, key: Key) -> bool {
|
||||
match key {
|
||||
Key::ESCAPE => {
|
||||
if self.dropdown_open {
|
||||
self.close();
|
||||
} else {
|
||||
self.close();
|
||||
}
|
||||
true
|
||||
}
|
||||
Key::ENTER => {
|
||||
let cmd = self.selected_cmd();
|
||||
if let Some(c) = cmd {
|
||||
if c == ViewerCmd::Nop {
|
||||
return true;
|
||||
}
|
||||
self.close();
|
||||
// Re-open after dispatch so the next Enter
|
||||
// can re-trigger the same command.
|
||||
// Callers should close the menubar after
|
||||
// handling the returned command.
|
||||
}
|
||||
true
|
||||
}
|
||||
Key::LEFT => {
|
||||
if self.dropdown_open {
|
||||
let n = self.menus.len().max(1);
|
||||
self.active_menu = (self.active_menu + n - 1) % n;
|
||||
self.selected_item = 0;
|
||||
}
|
||||
true
|
||||
}
|
||||
Key::RIGHT => {
|
||||
if self.dropdown_open {
|
||||
let n = self.menus.len().max(1);
|
||||
self.active_menu = (self.active_menu + 1) % n;
|
||||
self.selected_item = 0;
|
||||
}
|
||||
true
|
||||
}
|
||||
Key::UP => {
|
||||
if self.dropdown_open {
|
||||
self.move_selection(-1);
|
||||
}
|
||||
true
|
||||
}
|
||||
Key::DOWN => {
|
||||
if self.dropdown_open {
|
||||
self.move_selection(1);
|
||||
}
|
||||
true
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn move_selection(&mut self, dy: i32) {
|
||||
let n = match self.menus.get(self.active_menu) {
|
||||
Some(m) => m.items.len(),
|
||||
None => return,
|
||||
};
|
||||
if n == 0 {
|
||||
return;
|
||||
}
|
||||
let mut idx = self.selected_item as i32 + dy;
|
||||
let len = n as i32;
|
||||
while idx < 0 {
|
||||
idx += len;
|
||||
}
|
||||
while idx >= len {
|
||||
idx -= len;
|
||||
}
|
||||
// Skip separators.
|
||||
let mut attempts = 0;
|
||||
let menu = &self.menus[self.active_menu];
|
||||
while matches!(menu.items[idx as usize], MenuItem::Separator) {
|
||||
idx += dy.signum();
|
||||
if idx < 0 {
|
||||
idx += len;
|
||||
}
|
||||
if idx >= len {
|
||||
idx -= len;
|
||||
}
|
||||
attempts += 1;
|
||||
if attempts > len {
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.selected_item = idx as usize;
|
||||
}
|
||||
|
||||
/// Render the menu bar into a frame.
|
||||
pub fn render(&self, frame: &mut Frame, area: Rect, theme: &Theme) {
|
||||
let menubar_pair = mc_skin::color_pair(theme.name, "core", "menubar");
|
||||
let menubar_fg = menubar_pair.map(|p| p.fg).unwrap_or(theme.title_fg);
|
||||
let menubar_bg = menubar_pair
|
||||
.and_then(|p| if p.bg == ratatui::style::Color::Reset { None } else { Some(p.bg) })
|
||||
.unwrap_or(theme.title_bg);
|
||||
let menu_hot_fg = menubar_pair.map(|p| p.bg).unwrap_or(theme.foreground);
|
||||
let menu_hot_bg = menubar_pair.map(|p| p.fg).unwrap_or(theme.accent);
|
||||
|
||||
// Top bar: one span per menu title, with the active one
|
||||
// highlighted.
|
||||
let mut top_spans: Vec<Span> = Vec::new();
|
||||
for (i, m) in self.menus.iter().enumerate() {
|
||||
let style = if i == self.active_menu {
|
||||
Style::default()
|
||||
.fg(menu_hot_fg)
|
||||
.bg(menu_hot_bg)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::default().fg(menubar_fg).bg(menubar_bg)
|
||||
};
|
||||
top_spans.push(Span::styled(format!(" {} ", m.title), style));
|
||||
}
|
||||
let bar_h = 1u16;
|
||||
let bar = Paragraph::new(Line::from(top_spans))
|
||||
.style(Style::default().fg(menubar_fg).bg(menubar_bg));
|
||||
frame.render_widget(bar, Rect::new(area.x, area.y, area.width, bar_h));
|
||||
|
||||
// Dropdown: only if open.
|
||||
if !self.dropdown_open {
|
||||
return;
|
||||
}
|
||||
let Some(menu) = self.menus.get(self.active_menu) else {
|
||||
return;
|
||||
};
|
||||
let mut dropdown_w = 24u16;
|
||||
for it in &menu.items {
|
||||
if let MenuItem::Item { label, .. } = it {
|
||||
let w = label.chars().count() as u16 + 4;
|
||||
if w > dropdown_w {
|
||||
dropdown_w = w;
|
||||
}
|
||||
}
|
||||
}
|
||||
dropdown_w = dropdown_w.min(area.width);
|
||||
let x = area.x + 1;
|
||||
let dropdown_h = (menu.items.len() as u16 + 1).min(area.height.saturating_sub(1));
|
||||
if dropdown_h == 0 {
|
||||
return;
|
||||
}
|
||||
let dropdown_area = Rect::new(x, area.y + 1, dropdown_w, dropdown_h);
|
||||
let inner = render_popup(frame, dropdown_area, "", theme);
|
||||
|
||||
// Items: render each label, highlighting the selected one.
|
||||
let constraint_rows: Vec<Constraint> = (0..menu.items.len())
|
||||
.map(|_| Constraint::Length(1))
|
||||
.collect();
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(constraint_rows)
|
||||
.split(inner);
|
||||
for (i, item) in menu.items.iter().enumerate() {
|
||||
let area = chunks
|
||||
.get(i)
|
||||
.copied()
|
||||
.unwrap_or(Rect::new(0, 0, 0, 0));
|
||||
let style = if i == self.selected_item {
|
||||
Style::default()
|
||||
.fg(menu_hot_fg)
|
||||
.bg(menu_hot_bg)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::default().fg(menubar_fg).bg(menubar_bg)
|
||||
};
|
||||
let line = match item {
|
||||
MenuItem::Item { label, hotkey, .. } => render_menu_label(label, *hotkey, style),
|
||||
MenuItem::Separator => {
|
||||
Line::from(Span::styled("─".repeat(area.width as usize), style))
|
||||
}
|
||||
};
|
||||
let p = Paragraph::new(line).style(style);
|
||||
frame.render_widget(p, area);
|
||||
}
|
||||
// Clear the area behind the dropdown to avoid bleed-through.
|
||||
let _ = Clear;
|
||||
}
|
||||
}
|
||||
|
||||
/// Render a menu label with the hotkey highlighted.
|
||||
fn render_menu_label(label: &str, hotkey: char, style: Style) -> Line<'static> {
|
||||
let mut spans: Vec<Span<'static>> = Vec::new();
|
||||
let mut hotkey_used = false;
|
||||
for ch in label.chars() {
|
||||
if !hotkey_used && ch.to_ascii_lowercase() == hotkey.to_ascii_lowercase() {
|
||||
spans.push(Span::styled(
|
||||
ch.to_string(),
|
||||
style.add_modifier(Modifier::UNDERLINED),
|
||||
));
|
||||
hotkey_used = true;
|
||||
} else {
|
||||
spans.push(Span::styled(ch.to_string(), style));
|
||||
}
|
||||
}
|
||||
Line::from(spans)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn new_starts_closed() {
|
||||
let mb = ViewerMenuBar::new();
|
||||
assert!(!mb.is_open());
|
||||
assert!(mb.selected_cmd().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn open_sets_first_menu() {
|
||||
let mut mb = ViewerMenuBar::new();
|
||||
mb.open();
|
||||
assert!(mb.is_open());
|
||||
assert_eq!(mb.active_menu, 0);
|
||||
assert_eq!(mb.selected_item, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arrows_cycle_menus() {
|
||||
let mut mb = ViewerMenuBar::new();
|
||||
mb.open();
|
||||
mb.handle_key(Key::RIGHT);
|
||||
assert_eq!(mb.active_menu, 1);
|
||||
mb.handle_key(Key::RIGHT);
|
||||
assert_eq!(mb.active_menu, 2);
|
||||
let n = 5;
|
||||
for _ in 0..n {
|
||||
mb.handle_key(Key::RIGHT);
|
||||
}
|
||||
assert_eq!(mb.active_menu, (2usize + n) % 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enter_on_separator_does_not_crash() {
|
||||
let mut mb = ViewerMenuBar::new();
|
||||
mb.open();
|
||||
mb.handle_key(Key::DOWN);
|
||||
mb.handle_key(Key::ENTER);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn esc_closes() {
|
||||
let mut mb = ViewerMenuBar::new();
|
||||
mb.open();
|
||||
assert!(mb.handle_key(Key::ESCAPE));
|
||||
assert!(!mb.is_open());
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ pub mod goto;
|
||||
pub mod hex;
|
||||
pub mod hex_edit;
|
||||
pub mod magic;
|
||||
pub mod menubar;
|
||||
pub mod nroff;
|
||||
pub mod search;
|
||||
pub mod siblings;
|
||||
@@ -110,6 +111,8 @@ pub struct Viewer {
|
||||
/// Total render-frame count. Used to drive animation frames
|
||||
/// (the loading spinner cycles through glyphs based on this).
|
||||
frame_count: u64,
|
||||
/// F9 menubar state. `None` when the menubar is not active.
|
||||
pub menubar: Option<menubar::ViewerMenuBar>,
|
||||
/// Syntax highlighter (when `syntect` is on and the file
|
||||
/// extension is recognised). The renderer feeds lines to it
|
||||
/// in order so multi-line constructs (block comments, strings)
|
||||
@@ -171,6 +174,7 @@ impl Viewer {
|
||||
prompt_input: String::new(),
|
||||
loading: size >= crate::viewer::source::INLINE_THRESHOLD,
|
||||
frame_count: 0,
|
||||
menubar: None,
|
||||
#[cfg(feature = "syntect")]
|
||||
highlighter,
|
||||
#[cfg(feature = "syntect")]
|
||||
@@ -219,6 +223,7 @@ impl Viewer {
|
||||
hex_edit_modified: false,
|
||||
last_height: 0,
|
||||
flash_msg: None,
|
||||
menubar: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -641,6 +646,28 @@ impl Viewer {
|
||||
if area.width == 0 || area.height == 0 {
|
||||
return;
|
||||
}
|
||||
// F9 menubar: occupies the top row when active. Reduce
|
||||
// the content area by 1 row so the menubar overlays the
|
||||
// very top of the viewer without colliding with content.
|
||||
let area = if let Some(mb) = self.menubar.as_ref() {
|
||||
if mb.is_open() {
|
||||
mb.render(
|
||||
frame,
|
||||
Rect::new(area.x, area.y, area.width, 1),
|
||||
theme,
|
||||
);
|
||||
Rect::new(
|
||||
area.x,
|
||||
area.y + 1,
|
||||
area.width,
|
||||
area.height.saturating_sub(1),
|
||||
)
|
||||
} else {
|
||||
area
|
||||
}
|
||||
} else {
|
||||
area
|
||||
};
|
||||
self.frame_count = self.frame_count.wrapping_add(1);
|
||||
// Auto-clear loading after a small number of render frames
|
||||
// so the spinner is visibly transient even for chunked
|
||||
@@ -796,6 +823,30 @@ impl Viewer {
|
||||
if key == Key::f(10) || key == Key::ctrl('q') {
|
||||
return true;
|
||||
}
|
||||
// F9 toggles the menubar.
|
||||
if key == Key::f(9) {
|
||||
if self.menubar.is_some() {
|
||||
self.menubar = None;
|
||||
} else {
|
||||
let mut mb = menubar::ViewerMenuBar::new();
|
||||
mb.open();
|
||||
self.menubar = Some(mb);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Route other keys to the menubar when it's open.
|
||||
if self.menubar.is_some() {
|
||||
let mut mb = self.menubar.take().unwrap();
|
||||
let consumed = mb.handle_key(key);
|
||||
if consumed {
|
||||
// If the menubar dispatched a command, execute it.
|
||||
if let Some(cmd) = mb.selected_cmd() {
|
||||
self.execute_menubar_cmd(cmd);
|
||||
}
|
||||
}
|
||||
self.menubar = Some(mb);
|
||||
return consumed;
|
||||
}
|
||||
// Esc without an active prompt closes the viewer (matches
|
||||
// the previous MC parity behavior).
|
||||
if key == Key::ESCAPE {
|
||||
@@ -972,6 +1023,57 @@ impl Viewer {
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute a [`ViewerCmd`] selected from the F9 menubar.
|
||||
/// Maps the menubar command to the existing viewer methods
|
||||
/// (search, goto, bookmarks, mode toggles, file navigation).
|
||||
pub fn execute_menubar_cmd(&mut self, cmd: menubar::ViewerCmd) {
|
||||
use menubar::ViewerCmd;
|
||||
match cmd {
|
||||
ViewerCmd::Nop => {}
|
||||
ViewerCmd::ToggleHex => {
|
||||
if self.mode == ViewMode::Text {
|
||||
self.mode = ViewMode::Hex;
|
||||
} else if self.mode == ViewMode::Hex {
|
||||
self.mode = ViewMode::Text;
|
||||
}
|
||||
}
|
||||
ViewerCmd::ToggleMagic => {
|
||||
self.magic_mode = !self.magic_mode;
|
||||
}
|
||||
ViewerCmd::ToggleWrap => {
|
||||
self.wrap = !self.wrap;
|
||||
}
|
||||
ViewerCmd::ToggleGrowing => {
|
||||
let _ = self.toggle_growing();
|
||||
}
|
||||
ViewerCmd::Search => {
|
||||
self.prompt = Some(ViewerPrompt::Search);
|
||||
self.prompt_input.clear();
|
||||
}
|
||||
ViewerCmd::SearchNext => {
|
||||
let _ = self.search_next();
|
||||
}
|
||||
ViewerCmd::SearchPrev => {
|
||||
let _ = self.search_prev();
|
||||
}
|
||||
ViewerCmd::Goto => {
|
||||
self.prompt = Some(ViewerPrompt::GotoLine);
|
||||
self.prompt_input.clear();
|
||||
}
|
||||
ViewerCmd::BookmarkToggle
|
||||
| ViewerCmd::BookmarkNext
|
||||
| ViewerCmd::BookmarkPrev
|
||||
| ViewerCmd::NextFile
|
||||
| ViewerCmd::PrevFile
|
||||
| ViewerCmd::Close => {
|
||||
// Bookmark / file navigation / close are not yet
|
||||
// wired to menubar commands. They are still reachable
|
||||
// via their keyboard shortcuts. This is a planned
|
||||
// follow-up.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle a key while a [`ViewerPrompt`] is active. Enter
|
||||
/// commits (search runs / goto-line jumps) and closes the
|
||||
/// prompt; Esc cancels without committing; printable chars
|
||||
|
||||
Reference in New Issue
Block a user