refactor(ui): move UI session fields into ViewState (improvise-ew0)
Step 3 of vb4. Populates ViewState with the 20 UI session fields (mode, status_msg, wizard, search_query, search_mode, three panel-open flags, three panel cursors, formula_cursor, yanked, tile_cat_idx, two view nav stacks, drill_state, help_page, expanded_cats, buffers, transient_keymap) and routes every read/write site through app.view_state.X. App now contains only model_state, view_state, and the runtime/derived residue (term dims, layout, last_autosave, abort_effects, keymap_set). ViewState gets a manual Default impl mirroring the previous App::new field initialisers; AppMode has no Default of its own so AppMode::Normal is the explicit baseline. Effect::apply still takes &mut App; narrowing remains step 5 (improvise-drg). A structural test (app_view_state_owns_ui_session_fields) locks in the 20-field layout. 624 tests pass workspace-wide (+1 new). cargo clippy --workspace --tests clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+35
-34
@@ -61,7 +61,7 @@ pub fn run_tui(
|
||||
if let Some(json) = import_value {
|
||||
app.start_import_wizard(json);
|
||||
} else if app.is_empty_model() {
|
||||
app.mode = AppMode::Help;
|
||||
app.view_state.mode = AppMode::Help;
|
||||
}
|
||||
|
||||
loop {
|
||||
@@ -82,7 +82,7 @@ pub fn run_tui(
|
||||
|
||||
app.autosave_if_needed();
|
||||
|
||||
if matches!(app.mode, AppMode::Quit) {
|
||||
if matches!(app.view_state.mode, AppMode::Quit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -167,21 +167,21 @@ fn draw(f: &mut Frame, app: &App) {
|
||||
draw_bottom_bar(f, main_chunks[3], app);
|
||||
|
||||
// Overlays (rendered last so they appear on top)
|
||||
if matches!(app.mode, AppMode::Help) {
|
||||
f.render_widget(HelpWidget::new(app.help_page), size);
|
||||
if matches!(app.view_state.mode, AppMode::Help) {
|
||||
f.render_widget(HelpWidget::new(app.view_state.help_page), size);
|
||||
}
|
||||
if matches!(app.mode, AppMode::ImportWizard)
|
||||
&& let Some(wizard) = &app.wizard
|
||||
if matches!(app.view_state.mode, AppMode::ImportWizard)
|
||||
&& let Some(wizard) = &app.view_state.wizard
|
||||
{
|
||||
f.render_widget(ImportWizardWidget::new(wizard), size);
|
||||
}
|
||||
// ExportPrompt now uses the minibuffer at the bottom bar.
|
||||
if app.is_empty_model() && matches!(app.mode, AppMode::Normal | AppMode::CommandMode { .. }) {
|
||||
if app.is_empty_model() && matches!(app.view_state.mode, AppMode::Normal | AppMode::CommandMode { .. }) {
|
||||
draw_welcome(f, main_chunks[1]);
|
||||
}
|
||||
|
||||
// Which-key popup: show available completions after a prefix key
|
||||
if let Some(ref km) = app.transient_keymap {
|
||||
if let Some(ref km) = app.view_state.transient_keymap {
|
||||
let hints = km.binding_hints();
|
||||
f.render_widget(WhichKeyWidget::new(&hints), size);
|
||||
}
|
||||
@@ -215,7 +215,7 @@ fn draw_title(f: &mut Frame, area: Rect, app: &App) {
|
||||
}
|
||||
|
||||
fn draw_content(f: &mut Frame, area: Rect, app: &App) {
|
||||
let side_open = app.formula_panel_open || app.category_panel_open || app.view_panel_open;
|
||||
let side_open = app.view_state.formula_panel_open || app.view_state.category_panel_open || app.view_state.view_panel_open;
|
||||
|
||||
let grid_area;
|
||||
if side_open {
|
||||
@@ -229,9 +229,9 @@ fn draw_content(f: &mut Frame, area: Rect, app: &App) {
|
||||
|
||||
let side = chunks[1];
|
||||
let panel_count = [
|
||||
app.formula_panel_open,
|
||||
app.category_panel_open,
|
||||
app.view_panel_open,
|
||||
app.view_state.formula_panel_open,
|
||||
app.view_state.category_panel_open,
|
||||
app.view_state.view_panel_open,
|
||||
]
|
||||
.iter()
|
||||
.filter(|&&b| b)
|
||||
@@ -239,26 +239,26 @@ fn draw_content(f: &mut Frame, area: Rect, app: &App) {
|
||||
let ph = side.height / panel_count.max(1);
|
||||
let mut y = side.y;
|
||||
|
||||
if app.formula_panel_open {
|
||||
if app.view_state.formula_panel_open {
|
||||
let a = Rect::new(side.x, y, side.width, ph);
|
||||
let content = FormulaContent::new(&app.model_state.workbook.model, &app.mode);
|
||||
f.render_widget(Panel::new(content, &app.mode, app.formula_cursor), a);
|
||||
let content = FormulaContent::new(&app.model_state.workbook.model, &app.view_state.mode);
|
||||
f.render_widget(Panel::new(content, &app.view_state.mode, app.view_state.formula_cursor), a);
|
||||
y += ph;
|
||||
}
|
||||
if app.category_panel_open {
|
||||
if app.view_state.category_panel_open {
|
||||
let a = Rect::new(side.x, y, side.width, ph);
|
||||
let content = CategoryContent::new(
|
||||
&app.model_state.workbook.model,
|
||||
app.model_state.workbook.active_view(),
|
||||
&app.expanded_cats,
|
||||
&app.view_state.expanded_cats,
|
||||
);
|
||||
f.render_widget(Panel::new(content, &app.mode, app.cat_panel_cursor), a);
|
||||
f.render_widget(Panel::new(content, &app.view_state.mode, app.view_state.cat_panel_cursor), a);
|
||||
y += ph;
|
||||
}
|
||||
if app.view_panel_open {
|
||||
if app.view_state.view_panel_open {
|
||||
let a = Rect::new(side.x, y, side.width, ph);
|
||||
let content = ViewContent::new(&app.model_state.workbook);
|
||||
f.render_widget(Panel::new(content, &app.mode, app.view_panel_cursor), a);
|
||||
f.render_widget(Panel::new(content, &app.view_state.mode, app.view_state.view_panel_cursor), a);
|
||||
}
|
||||
} else {
|
||||
grid_area = area;
|
||||
@@ -270,10 +270,10 @@ fn draw_content(f: &mut Frame, area: Rect, app: &App) {
|
||||
app.model_state.workbook.active_view(),
|
||||
&app.model_state.workbook.active_view,
|
||||
&app.layout,
|
||||
&app.mode,
|
||||
&app.search_query,
|
||||
&app.buffers,
|
||||
app.drill_state.as_ref(),
|
||||
&app.view_state.mode,
|
||||
&app.view_state.search_query,
|
||||
&app.view_state.buffers,
|
||||
app.view_state.drill_state.as_ref(),
|
||||
),
|
||||
grid_area,
|
||||
);
|
||||
@@ -284,16 +284,17 @@ fn draw_tile_bar(f: &mut Frame, area: Rect, app: &App) {
|
||||
TileBar::new(
|
||||
&app.model_state.workbook.model,
|
||||
app.model_state.workbook.active_view(),
|
||||
&app.mode,
|
||||
app.tile_cat_idx,
|
||||
&app.view_state.mode,
|
||||
app.view_state.tile_cat_idx,
|
||||
),
|
||||
area,
|
||||
);
|
||||
}
|
||||
|
||||
fn draw_bottom_bar(f: &mut Frame, area: Rect, app: &App) {
|
||||
if let Some(mb) = app.mode.minibuffer() {
|
||||
if let Some(mb) = app.view_state.mode.minibuffer() {
|
||||
let buf = app
|
||||
.view_state
|
||||
.buffers
|
||||
.get(mb.buffer_key)
|
||||
.map(|s| s.as_str())
|
||||
@@ -314,25 +315,25 @@ fn draw_bottom_bar(f: &mut Frame, area: Rect, app: &App) {
|
||||
}
|
||||
|
||||
fn draw_status(f: &mut Frame, area: Rect, app: &App) {
|
||||
let search_part = if app.search_mode {
|
||||
format!(" /{}▌", app.search_query)
|
||||
let search_part = if app.view_state.search_mode {
|
||||
format!(" /{}▌", app.view_state.search_query)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let msg = if !app.status_msg.is_empty() {
|
||||
app.status_msg.as_str()
|
||||
let msg = if !app.view_state.status_msg.is_empty() {
|
||||
app.view_state.status_msg.as_str()
|
||||
} else {
|
||||
app.hint_text()
|
||||
};
|
||||
|
||||
let yank_indicator = if app.yanked.is_some() { " [yank]" } else { "" };
|
||||
let yank_indicator = if app.view_state.yanked.is_some() { " [yank]" } else { "" };
|
||||
let view_badge = format!(" {}{} ", app.model_state.workbook.active_view, yank_indicator);
|
||||
|
||||
let left = format!(" {}{search_part} {msg}", mode_name(&app.mode));
|
||||
let left = format!(" {}{search_part} {msg}", mode_name(&app.view_state.mode));
|
||||
let line = fill_line(left, &view_badge, area.width);
|
||||
|
||||
f.render_widget(Paragraph::new(line).style(mode_style(&app.mode)), area);
|
||||
f.render_widget(Paragraph::new(line).style(mode_style(&app.view_state.mode)), area);
|
||||
}
|
||||
|
||||
fn draw_welcome(f: &mut Frame, area: Rect) {
|
||||
|
||||
Reference in New Issue
Block a user