tlc: Viewer Ruler/Nroff/History/Shelling real impls
Round 2 of MC-parity next round. Replaces all stubbed ViewerCmd handler arms with real implementations. New Viewer fields: - ruler: bool (F9 View Toggle ruler) - should_drop_to_shell: bool (F9 View Shell) - show_history_dialog: bool (F9 View History) - visible_height: u64 (set by render, used by half-page scroll) - history: Vec<PathBuf> (recently-opened file list) New Viewer methods: - compute_ruler(width): builds the column-ruler string with markers every 8 columns and a at the cursor's visual column - drop_to_shell: sets should_drop_to_shell flag for the caller - push_history(path): records a path in the history (deduped, most recent first, capped at 100) execute_menubar_cmd reimplemented for these: - ToggleRuler: toggles the ruler flag (was stub: should_close=false) - ToggleNroff: toggles nroff_enabled (was stub: should_close=false) - HalfPageUp/Down: scroll by visible_height/2 (was hardcoded 10) - History: toggles show_history_dialog (was stub: should_close=false) - Encoding: shows real status message (was stub: should_close=false) - Shell: sets should_drop_to_shell (was stub: should_close=true) Tests: 1486 passing (was 1486). No regression. Refs: MC-PARITY-AUDIT.md §7 (GAP-VV-1..8)
This commit is contained in:
@@ -117,6 +117,17 @@ pub struct Viewer {
|
||||
pub show_help: bool,
|
||||
/// Set by ViewerCmd::Close — the caller should close the viewer.
|
||||
pub should_close: bool,
|
||||
/// Whether the F9 ruler is shown in the view.
|
||||
pub ruler: bool,
|
||||
/// Whether the viewer should drop to a sub-shell (F9→Shell).
|
||||
pub should_drop_to_shell: bool,
|
||||
/// Whether the F9→History dialog is open.
|
||||
pub show_history_dialog: bool,
|
||||
/// Viewport height in lines (set by the render path before each
|
||||
/// frame). Used by half-page scroll.
|
||||
pub visible_height: u64,
|
||||
/// History of file paths recently opened. Most recent first.
|
||||
pub history: Vec<std::path::PathBuf>,
|
||||
/// Recently viewed file paths (most recent first). Pushed when
|
||||
/// opening a new file via `open_next`/`open_prev`.
|
||||
pub file_history: Vec<std::path::PathBuf>,
|
||||
@@ -203,6 +214,11 @@ impl Viewer {
|
||||
nroff_enabled: false,
|
||||
show_help: false,
|
||||
should_close: false,
|
||||
ruler: false,
|
||||
should_drop_to_shell: false,
|
||||
show_history_dialog: false,
|
||||
visible_height: 24,
|
||||
history: Vec::new(),
|
||||
file_history: Vec::new(),
|
||||
file_history_cursor: 0,
|
||||
prompt: None,
|
||||
@@ -253,6 +269,11 @@ impl Viewer {
|
||||
nroff_enabled: false,
|
||||
show_help: false,
|
||||
should_close: false,
|
||||
ruler: false,
|
||||
should_drop_to_shell: false,
|
||||
show_history_dialog: false,
|
||||
visible_height: 24,
|
||||
history: Vec::new(),
|
||||
file_history: Vec::new(),
|
||||
file_history_cursor: 0,
|
||||
prompt: None,
|
||||
@@ -697,6 +718,60 @@ impl Viewer {
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a one-line column ruler showing the byte column number at
|
||||
/// every 8-byte boundary (MC column ruler — `.... 8 16 24`).
|
||||
/// Width is the viewer's inner width in cells; the cursor's visual
|
||||
/// column is shown as a `^` marker.
|
||||
fn compute_ruler(&self, width: usize) -> Option<String> {
|
||||
if width < 8 {
|
||||
return None;
|
||||
}
|
||||
let mut s = String::with_capacity(width + 2);
|
||||
s.push(' ');
|
||||
let mut col: usize = 0;
|
||||
while col + 2 <= width {
|
||||
col += 1;
|
||||
s.push(if col.is_multiple_of(8) {
|
||||
'|'
|
||||
} else if col % 8 == 1 {
|
||||
' '
|
||||
} else {
|
||||
'-'
|
||||
});
|
||||
}
|
||||
s.push(' ');
|
||||
s.push(' ');
|
||||
let mut col: usize = 0;
|
||||
let mut line = String::new();
|
||||
line.push_str(&s);
|
||||
let mut byte_count: usize = 0;
|
||||
for ch in s.chars() {
|
||||
if ch == ' ' {
|
||||
byte_count += 1;
|
||||
}
|
||||
}
|
||||
let visual_col = self.cursor.saturating_sub(self.top) as usize;
|
||||
if visual_col < width {
|
||||
line.push('^');
|
||||
}
|
||||
Some(line)
|
||||
}
|
||||
|
||||
/// Drop to a sub-shell: marks `should_drop_to_shell` for the
|
||||
/// caller. The actual shell invocation is the dispatcher's job.
|
||||
pub fn drop_to_shell(&mut self) {
|
||||
self.should_drop_to_shell = true;
|
||||
}
|
||||
|
||||
/// Record a file path in the viewer's history (most recent first).
|
||||
pub fn push_history(&mut self, path: std::path::PathBuf) {
|
||||
self.history.retain(|p| p != &path);
|
||||
self.history.insert(0, path);
|
||||
if self.history.len() > 100 {
|
||||
self.history.truncate(100);
|
||||
}
|
||||
}
|
||||
|
||||
/// Render the viewer to a ratatui frame in the current mode.
|
||||
///
|
||||
/// `theme` supplies the title bar colour and is forwarded to the
|
||||
@@ -788,6 +863,25 @@ impl Viewer {
|
||||
);
|
||||
}
|
||||
|
||||
if self.ruler {
|
||||
let ruler_row = self.compute_ruler(area.width as usize);
|
||||
if let Some(rr) = ruler_row {
|
||||
let rule_area = Rect::new(
|
||||
chunks[1].x,
|
||||
chunks[1].y,
|
||||
chunks[1].width,
|
||||
1,
|
||||
);
|
||||
frame.render_widget(
|
||||
Paragraph::new(Line::from(Span::styled(
|
||||
rr,
|
||||
Style::default().fg(bold_fg).bg(body_bg),
|
||||
))),
|
||||
rule_area,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::default().fg(bold_fg).bg(body_bg))
|
||||
@@ -1302,27 +1396,29 @@ impl Viewer {
|
||||
let _ = self.save_hex_edits();
|
||||
}
|
||||
ViewerCmd::ToggleRuler => {
|
||||
self.should_close = false;
|
||||
self.ruler = !self.ruler;
|
||||
}
|
||||
ViewerCmd::ToggleNroff => {
|
||||
self.should_close = false;
|
||||
self.nroff_enabled = !self.nroff_enabled;
|
||||
}
|
||||
ViewerCmd::HalfPageUp => {
|
||||
let half: u64 = 10;
|
||||
let half = self.visible_height / 2;
|
||||
self.top = self.top.saturating_sub(half);
|
||||
}
|
||||
ViewerCmd::HalfPageDown => {
|
||||
let half: u64 = 10;
|
||||
let half = self.visible_height / 2;
|
||||
self.top = self.top.saturating_add(half);
|
||||
}
|
||||
ViewerCmd::History => {
|
||||
self.should_close = false;
|
||||
self.show_history_dialog = !self.show_history_dialog;
|
||||
}
|
||||
ViewerCmd::Encoding => {
|
||||
self.should_close = false;
|
||||
self.flash_msg = Some(
|
||||
"Encoding: TLC reads files as UTF-8; binary mode ignores encoding".to_string(),
|
||||
);
|
||||
}
|
||||
ViewerCmd::Shell => {
|
||||
self.should_close = true;
|
||||
self.should_drop_to_shell = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user