refactor(navigation): include AppMode in view navigation stack
Introduce `ViewFrame` to store both the view name and the `AppMode` when pushing to the navigation stack. Update `view_back_stack` and `view_forward_stack` to use `ViewFrame` instead of `String` . Update `CmdContext` and `Effect` implementations (SwitchView, ViewBack, ViewForward) to handle the new `ViewFrame` structure. Add `is_editing()` helper to `AppMode` . Co-Authored-By: fiddlerwoaroof/git-smart-commit (gemma-4-26B-A4B-it-UD-Q5_K_XL.gguf)
This commit is contained in:
@ -34,8 +34,8 @@ pub struct CmdContext<'a> {
|
||||
/// Named text buffers
|
||||
pub buffers: &'a HashMap<String, String>,
|
||||
/// View navigation stacks (for drill back/forward)
|
||||
pub view_back_stack: &'a [String],
|
||||
pub view_forward_stack: &'a [String],
|
||||
pub view_back_stack: &'a [crate::ui::app::ViewFrame],
|
||||
pub view_forward_stack: &'a [crate::ui::app::ViewFrame],
|
||||
/// Whether the app currently has an active drill snapshot.
|
||||
pub has_drill_state: bool,
|
||||
/// Display value at the cursor — works uniformly for pivot and records mode.
|
||||
|
||||
@ -67,7 +67,10 @@ mod tests {
|
||||
let m = two_cat_model();
|
||||
let layout = make_layout(&m);
|
||||
let reg = make_registry();
|
||||
let fwd_stack = vec!["View 2".to_string()];
|
||||
let fwd_stack = vec![crate::ui::app::ViewFrame {
|
||||
view_name: "View 2".to_string(),
|
||||
mode: crate::ui::app::AppMode::Normal,
|
||||
}];
|
||||
let mut ctx = make_ctx(&m, &layout, ®);
|
||||
ctx.view_forward_stack = &fwd_stack;
|
||||
let cmd = ViewNavigate { forward: true };
|
||||
@ -84,7 +87,10 @@ mod tests {
|
||||
let m = two_cat_model();
|
||||
let layout = make_layout(&m);
|
||||
let reg = make_registry();
|
||||
let back_stack = vec!["Default".to_string()];
|
||||
let back_stack = vec![crate::ui::app::ViewFrame {
|
||||
view_name: "Default".to_string(),
|
||||
mode: crate::ui::app::AppMode::Normal,
|
||||
}];
|
||||
let mut ctx = make_ctx(&m, &layout, ®);
|
||||
ctx.view_back_stack = &back_stack;
|
||||
let cmd = ViewNavigate { forward: false };
|
||||
|
||||
@ -20,6 +20,13 @@ use crate::ui::grid::{
|
||||
};
|
||||
use crate::view::GridLayout;
|
||||
|
||||
/// A saved view+mode pair for the navigation stack.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ViewFrame {
|
||||
pub view_name: String,
|
||||
pub mode: AppMode,
|
||||
}
|
||||
|
||||
/// Drill-down state: frozen record snapshot + pending edits that have not
|
||||
/// yet been applied to the model.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
@ -88,6 +95,11 @@ impl AppMode {
|
||||
}
|
||||
}
|
||||
|
||||
/// True for any cell-editing mode (normal or records).
|
||||
pub fn is_editing(&self) -> bool {
|
||||
matches!(self, Self::Editing { .. } | Self::RecordsEditing { .. })
|
||||
}
|
||||
|
||||
pub fn editing() -> Self {
|
||||
Self::Editing {
|
||||
minibuf: MinibufferConfig {
|
||||
@ -173,9 +185,9 @@ pub struct App {
|
||||
pub tile_cat_idx: usize,
|
||||
/// View navigation history: views visited before the current one.
|
||||
/// Pushed on SwitchView, popped by `<` (back).
|
||||
pub view_back_stack: Vec<String>,
|
||||
pub view_back_stack: Vec<ViewFrame>,
|
||||
/// Views that were "back-ed" from, available for forward navigation (`>`).
|
||||
pub view_forward_stack: Vec<String>,
|
||||
pub view_forward_stack: Vec<ViewFrame>,
|
||||
/// Frozen records list for the drill view. When present, this is the
|
||||
/// snapshot that records-mode layouts iterate — records don't disappear
|
||||
/// when filters would change. Pending edits are stored alongside and
|
||||
|
||||
@ -4,7 +4,7 @@ use std::path::PathBuf;
|
||||
use crate::model::cell::{CellKey, CellValue};
|
||||
use crate::view::Axis;
|
||||
|
||||
use super::app::{App, AppMode};
|
||||
use super::app::{App, AppMode, ViewFrame};
|
||||
|
||||
pub(crate) const RECORD_COORDS_CANNOT_BE_EMPTY: &str = "Record coordinates cannot be empty";
|
||||
|
||||
@ -193,7 +193,10 @@ impl Effect for SwitchView {
|
||||
fn apply(&self, app: &mut App) {
|
||||
let current = app.model.active_view.clone();
|
||||
if current != self.0 {
|
||||
app.view_back_stack.push(current);
|
||||
app.view_back_stack.push(ViewFrame {
|
||||
view_name: current,
|
||||
mode: app.mode.clone(),
|
||||
});
|
||||
app.view_forward_stack.clear();
|
||||
}
|
||||
let _ = app.model.switch_view(&self.0);
|
||||
@ -205,10 +208,14 @@ impl Effect for SwitchView {
|
||||
pub struct ViewBack;
|
||||
impl Effect for ViewBack {
|
||||
fn apply(&self, app: &mut App) {
|
||||
if let Some(prev) = app.view_back_stack.pop() {
|
||||
if let Some(frame) = app.view_back_stack.pop() {
|
||||
let current = app.model.active_view.clone();
|
||||
app.view_forward_stack.push(current);
|
||||
let _ = app.model.switch_view(&prev);
|
||||
app.view_forward_stack.push(ViewFrame {
|
||||
view_name: current,
|
||||
mode: app.mode.clone(),
|
||||
});
|
||||
let _ = app.model.switch_view(&frame.view_name);
|
||||
app.mode = frame.mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -218,10 +225,14 @@ impl Effect for ViewBack {
|
||||
pub struct ViewForward;
|
||||
impl Effect for ViewForward {
|
||||
fn apply(&self, app: &mut App) {
|
||||
if let Some(next) = app.view_forward_stack.pop() {
|
||||
if let Some(frame) = app.view_forward_stack.pop() {
|
||||
let current = app.model.active_view.clone();
|
||||
app.view_back_stack.push(current);
|
||||
let _ = app.model.switch_view(&next);
|
||||
app.view_back_stack.push(ViewFrame {
|
||||
view_name: current,
|
||||
mode: app.mode.clone(),
|
||||
});
|
||||
let _ = app.model.switch_view(&frame.view_name);
|
||||
app.mode = frame.mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1134,8 +1145,8 @@ mod tests {
|
||||
|
||||
SwitchView("View 2".to_string()).apply(&mut app);
|
||||
assert_eq!(app.model.active_view.as_str(), "View 2");
|
||||
assert_eq!(app.view_back_stack, vec!["Default".to_string()]);
|
||||
// Forward stack should be cleared
|
||||
assert_eq!(app.view_back_stack.len(), 1);
|
||||
assert_eq!(app.view_back_stack[0].view_name, "Default");
|
||||
assert!(app.view_forward_stack.is_empty());
|
||||
}
|
||||
|
||||
@ -1156,13 +1167,15 @@ mod tests {
|
||||
// Go back
|
||||
ViewBack.apply(&mut app);
|
||||
assert_eq!(app.model.active_view.as_str(), "Default");
|
||||
assert_eq!(app.view_forward_stack, vec!["View 2".to_string()]);
|
||||
assert_eq!(app.view_forward_stack.len(), 1);
|
||||
assert_eq!(app.view_forward_stack[0].view_name, "View 2");
|
||||
assert!(app.view_back_stack.is_empty());
|
||||
|
||||
// Go forward
|
||||
ViewForward.apply(&mut app);
|
||||
assert_eq!(app.model.active_view.as_str(), "View 2");
|
||||
assert_eq!(app.view_back_stack, vec!["Default".to_string()]);
|
||||
assert_eq!(app.view_back_stack.len(), 1);
|
||||
assert_eq!(app.view_back_stack[0].view_name, "Default");
|
||||
assert!(app.view_forward_stack.is_empty());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user