feat(cmd): unify jump-to-edge and fix viewport scrolling

Replaces separate jump-to-edge commands with a unified `JumpToEdge`
command. Simplifies the command registry using a macro and updates
`ScrollRows` to use a shared `viewport_effects` helper for consistent
scrolling behavior.

This fixes a bug where viewport scrolling was based on a hardcoded
constant (20) instead of the actual visible row count.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (unsloth/gemma-4-31B-it-GGUF:UD-Q5_K_XL)
This commit is contained in:
Edward Langley
2026-04-06 21:56:47 -07:00
parent b7e5115a8e
commit 2767d83665

View File

@ -313,75 +313,32 @@ impl Cmd for MoveSelection {
}
}
/// Unified jump-to-edge: jump to first/last row or column.
/// `is_row` selects the axis; `end` selects first (false) or last (true).
#[derive(Debug)]
pub struct JumpToFirstRow {
pub col: usize,
pub struct JumpToEdge {
pub cursor: CursorState,
pub is_row: bool,
pub end: bool,
pub cmd_name: &'static str,
}
impl Cmd for JumpToFirstRow {
impl Cmd for JumpToEdge {
fn name(&self) -> &'static str {
"jump-first-row"
self.cmd_name
}
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
vec![
Box::new(effect::SetSelected(0, self.col)),
Box::new(effect::SetRowOffset(0)),
]
}
}
#[derive(Debug)]
pub struct JumpToLastRow {
pub col: usize,
pub row_count: usize,
pub row_offset: usize,
}
impl Cmd for JumpToLastRow {
fn name(&self) -> &'static str {
"jump-last-row"
}
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
let last = self.row_count.saturating_sub(1);
let mut effects: Vec<Box<dyn Effect>> = vec![Box::new(effect::SetSelected(last, self.col))];
if last >= self.row_offset + 20 {
effects.push(Box::new(effect::SetRowOffset(last.saturating_sub(19))));
}
effects
}
}
#[derive(Debug)]
pub struct JumpToFirstCol {
pub row: usize,
}
impl Cmd for JumpToFirstCol {
fn name(&self) -> &'static str {
"jump-first-col"
}
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
vec![
Box::new(effect::SetSelected(self.row, 0)),
Box::new(effect::SetColOffset(0)),
]
}
}
#[derive(Debug)]
pub struct JumpToLastCol {
pub row: usize,
pub col_count: usize,
pub col_offset: usize,
}
impl Cmd for JumpToLastCol {
fn name(&self) -> &'static str {
"jump-last-col"
}
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
let last = self.col_count.saturating_sub(1);
let mut effects: Vec<Box<dyn Effect>> = vec![Box::new(effect::SetSelected(self.row, last))];
if last >= self.col_offset + 8 {
effects.push(Box::new(effect::SetColOffset(last.saturating_sub(7))));
}
effects
let (nr, nc) = if self.is_row {
let r = if self.end { self.cursor.row_count.saturating_sub(1) } else { 0 };
(r, self.cursor.col)
} else {
let c = if self.end { self.cursor.col_count.saturating_sub(1) } else { 0 };
(self.cursor.row, c)
};
viewport_effects(
nr, nc,
self.cursor.row_offset, self.cursor.col_offset,
self.cursor.visible_rows, self.cursor.visible_cols,
)
}
}
@ -397,19 +354,14 @@ impl Cmd for ScrollRows {
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
let row_max = self.cursor.row_count.saturating_sub(1) as i32;
let nr = (self.cursor.row as i32 + self.delta).clamp(0, row_max) as usize;
let mut effects: Vec<Box<dyn Effect>> =
vec![Box::new(effect::SetSelected(nr, self.cursor.col))];
let mut row_offset = self.cursor.row_offset;
if nr < row_offset {
row_offset = nr;
}
if nr >= row_offset + 20 {
row_offset = nr.saturating_sub(19);
}
if row_offset != self.cursor.row_offset {
effects.push(Box::new(effect::SetRowOffset(row_offset)));
}
effects
viewport_effects(
nr,
self.cursor.col,
self.cursor.row_offset,
self.cursor.col_offset,
self.cursor.visible_rows,
self.cursor.visible_cols,
)
}
}
@ -2549,58 +2501,20 @@ pub fn default_registry() -> CmdRegistry {
}))
},
);
r.register(
&JumpToFirstRow { col: 0 },
|_| Ok(Box::new(JumpToFirstRow { col: 0 })),
|_, ctx| {
Ok(Box::new(JumpToFirstRow {
col: ctx.selected.1,
}))
},
);
r.register(
&JumpToLastRow { col: 0, row_count: 0, row_offset: 0 },
|_| {
Ok(Box::new(JumpToLastRow {
col: 0,
row_count: 0,
row_offset: 0,
}))
},
|_, ctx| {
Ok(Box::new(JumpToLastRow {
col: ctx.selected.1,
row_count: ctx.row_count,
row_offset: ctx.row_offset,
}))
},
);
r.register(
&JumpToFirstCol { row: 0 },
|_| Ok(Box::new(JumpToFirstCol { row: 0 })),
|_, ctx| {
Ok(Box::new(JumpToFirstCol {
row: ctx.selected.0,
}))
},
);
r.register(
&JumpToLastCol { row: 0, col_count: 0, col_offset: 0 },
|_| {
Ok(Box::new(JumpToLastCol {
row: 0,
col_count: 0,
col_offset: 0,
}))
},
|_, ctx| {
Ok(Box::new(JumpToLastCol {
row: ctx.selected.0,
col_count: ctx.col_count,
col_offset: ctx.col_offset,
}))
},
);
// Jump-to-edge commands: first/last row/col
macro_rules! reg_jump {
($r:expr, $is_row:expr, $end:expr, $name:expr) => {
$r.register(
&JumpToEdge { cursor: CursorState::default(), is_row: $is_row, end: $end, cmd_name: $name },
|_| Ok(Box::new(JumpToEdge { cursor: CursorState::default(), is_row: $is_row, end: $end, cmd_name: $name })),
|_, ctx| Ok(Box::new(JumpToEdge { cursor: CursorState::from_ctx(ctx), is_row: $is_row, end: $end, cmd_name: $name })),
);
};
}
reg_jump!(r, true, false, "jump-first-row");
reg_jump!(r, true, true, "jump-last-row");
reg_jump!(r, false, false, "jump-first-col");
reg_jump!(r, false, true, "jump-last-col");
r.register(
&ScrollRows { delta: 0, cursor: CursorState::default() },
|args| {