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
|
/// Named text buffers
|
||||||
pub buffers: &'a HashMap<String, String>,
|
pub buffers: &'a HashMap<String, String>,
|
||||||
/// View navigation stacks (for drill back/forward)
|
/// View navigation stacks (for drill back/forward)
|
||||||
pub view_back_stack: &'a [String],
|
pub view_back_stack: &'a [crate::ui::app::ViewFrame],
|
||||||
pub view_forward_stack: &'a [String],
|
pub view_forward_stack: &'a [crate::ui::app::ViewFrame],
|
||||||
/// Whether the app currently has an active drill snapshot.
|
/// Whether the app currently has an active drill snapshot.
|
||||||
pub has_drill_state: bool,
|
pub has_drill_state: bool,
|
||||||
/// Display value at the cursor — works uniformly for pivot and records mode.
|
/// Display value at the cursor — works uniformly for pivot and records mode.
|
||||||
|
|||||||
@ -67,7 +67,10 @@ mod tests {
|
|||||||
let m = two_cat_model();
|
let m = two_cat_model();
|
||||||
let layout = make_layout(&m);
|
let layout = make_layout(&m);
|
||||||
let reg = make_registry();
|
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, ®);
|
let mut ctx = make_ctx(&m, &layout, ®);
|
||||||
ctx.view_forward_stack = &fwd_stack;
|
ctx.view_forward_stack = &fwd_stack;
|
||||||
let cmd = ViewNavigate { forward: true };
|
let cmd = ViewNavigate { forward: true };
|
||||||
@ -84,7 +87,10 @@ mod tests {
|
|||||||
let m = two_cat_model();
|
let m = two_cat_model();
|
||||||
let layout = make_layout(&m);
|
let layout = make_layout(&m);
|
||||||
let reg = make_registry();
|
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, ®);
|
let mut ctx = make_ctx(&m, &layout, ®);
|
||||||
ctx.view_back_stack = &back_stack;
|
ctx.view_back_stack = &back_stack;
|
||||||
let cmd = ViewNavigate { forward: false };
|
let cmd = ViewNavigate { forward: false };
|
||||||
|
|||||||
@ -20,6 +20,13 @@ use crate::ui::grid::{
|
|||||||
};
|
};
|
||||||
use crate::view::GridLayout;
|
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
|
/// Drill-down state: frozen record snapshot + pending edits that have not
|
||||||
/// yet been applied to the model.
|
/// yet been applied to the model.
|
||||||
#[derive(Debug, Clone, Default)]
|
#[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 {
|
pub fn editing() -> Self {
|
||||||
Self::Editing {
|
Self::Editing {
|
||||||
minibuf: MinibufferConfig {
|
minibuf: MinibufferConfig {
|
||||||
@ -173,9 +185,9 @@ pub struct App {
|
|||||||
pub tile_cat_idx: usize,
|
pub tile_cat_idx: usize,
|
||||||
/// View navigation history: views visited before the current one.
|
/// View navigation history: views visited before the current one.
|
||||||
/// Pushed on SwitchView, popped by `<` (back).
|
/// 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 (`>`).
|
/// 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
|
/// Frozen records list for the drill view. When present, this is the
|
||||||
/// snapshot that records-mode layouts iterate — records don't disappear
|
/// snapshot that records-mode layouts iterate — records don't disappear
|
||||||
/// when filters would change. Pending edits are stored alongside and
|
/// 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::model::cell::{CellKey, CellValue};
|
||||||
use crate::view::Axis;
|
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";
|
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) {
|
fn apply(&self, app: &mut App) {
|
||||||
let current = app.model.active_view.clone();
|
let current = app.model.active_view.clone();
|
||||||
if current != self.0 {
|
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();
|
app.view_forward_stack.clear();
|
||||||
}
|
}
|
||||||
let _ = app.model.switch_view(&self.0);
|
let _ = app.model.switch_view(&self.0);
|
||||||
@ -205,10 +208,14 @@ impl Effect for SwitchView {
|
|||||||
pub struct ViewBack;
|
pub struct ViewBack;
|
||||||
impl Effect for ViewBack {
|
impl Effect for ViewBack {
|
||||||
fn apply(&self, app: &mut App) {
|
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();
|
let current = app.model.active_view.clone();
|
||||||
app.view_forward_stack.push(current);
|
app.view_forward_stack.push(ViewFrame {
|
||||||
let _ = app.model.switch_view(&prev);
|
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;
|
pub struct ViewForward;
|
||||||
impl Effect for ViewForward {
|
impl Effect for ViewForward {
|
||||||
fn apply(&self, app: &mut App) {
|
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();
|
let current = app.model.active_view.clone();
|
||||||
app.view_back_stack.push(current);
|
app.view_back_stack.push(ViewFrame {
|
||||||
let _ = app.model.switch_view(&next);
|
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);
|
SwitchView("View 2".to_string()).apply(&mut app);
|
||||||
assert_eq!(app.model.active_view.as_str(), "View 2");
|
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);
|
||||||
// Forward stack should be cleared
|
assert_eq!(app.view_back_stack[0].view_name, "Default");
|
||||||
assert!(app.view_forward_stack.is_empty());
|
assert!(app.view_forward_stack.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1156,13 +1167,15 @@ mod tests {
|
|||||||
// Go back
|
// Go back
|
||||||
ViewBack.apply(&mut app);
|
ViewBack.apply(&mut app);
|
||||||
assert_eq!(app.model.active_view.as_str(), "Default");
|
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());
|
assert!(app.view_back_stack.is_empty());
|
||||||
|
|
||||||
// Go forward
|
// Go forward
|
||||||
ViewForward.apply(&mut app);
|
ViewForward.apply(&mut app);
|
||||||
assert_eq!(app.model.active_view.as_str(), "View 2");
|
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());
|
assert!(app.view_forward_stack.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user