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