@@ -68,8 +68,13 @@ impl App {
6868 if let Some ( rest) = trimmed. strip_prefix ( "w " ) . or_else ( || trimmed. strip_prefix ( "W " ) ) {
6969 let name = rest. trim ( ) . to_string ( ) ;
7070 if !name. is_empty ( ) {
71- self . snapshot_view_state_to_active_sheet ( ) ;
72- let result = if name. to_lowercase ( ) . ends_with ( ".xlsx" ) {
71+ let is_xlsx = name. to_lowercase ( ) . ends_with ( ".xlsx" ) ;
72+ // See save_in_place_or_prompt: xlsx doesn't round-trip
73+ // App-only view state, so skip the snapshot for that target.
74+ if !is_xlsx {
75+ self . snapshot_view_state_to_active_sheet ( ) ;
76+ }
77+ let result = if is_xlsx {
7378 crate :: infrastructure:: xlsx:: save_xlsx ( & self . workbook , & name)
7479 . map ( |_| name. clone ( ) )
7580 } else {
@@ -183,11 +188,15 @@ impl App {
183188 let max_c = sheet. cols . saturating_sub ( 1 ) ;
184189 self . frozen_rows = self . selected_row . min ( max_r) ;
185190 self . frozen_cols = self . selected_col . min ( max_c) ;
191+ // Freeze is persistent view state — mark dirty so it
192+ // survives :q after a freeze with no other edits.
193+ self . dirty = true ;
186194 self . status_message = Some ( format ! ( "Frozen {} rows, {} cols" , self . frozen_rows, self . frozen_cols) ) ;
187195 }
188196 [ "unfreeze" ] => {
189197 self . frozen_rows = 0 ;
190198 self . frozen_cols = 0 ;
199+ self . dirty = true ;
191200 self . status_message = Some ( "Unfrozen all panes" . to_string ( ) ) ;
192201 }
193202 [ "format" , "general" ] => self . set_selection_format ( NumberFormat :: General ) ,
@@ -231,33 +240,33 @@ impl App {
231240 }
232241 [ "sheet" , "new" ] | [ "new" , "sheet" ] | [ "addsheet" ] => {
233242 let name = format ! ( "Sheet{}" , self . workbook. sheets. len( ) + 1 ) ;
234- self . workbook . add_sheet ( name. clone ( ) ) ;
235- self . workbook . active_sheet = self . workbook . sheets . len ( ) - 1 ;
236- self . selected_row = 0 ;
237- self . selected_col = 0 ;
238- self . scroll_row = 0 ;
239- self . scroll_col = 0 ;
240- self . dirty = true ;
241- crate :: infrastructure:: autosave:: mark_dirty ( ) ;
242- self . status_message = Some ( format ! ( "Added sheet '{}'" , name) ) ;
243+ let label = format ! ( "Add sheet '{}'" , name) ;
244+ self . with_snapshot_undo ( & label, |app| {
245+ app. workbook . add_sheet ( name. clone ( ) ) ;
246+ app. workbook . active_sheet = app. workbook . sheets . len ( ) - 1 ;
247+ app. selected_row = 0 ;
248+ app. selected_col = 0 ;
249+ app. scroll_row = 0 ;
250+ app. scroll_col = 0 ;
251+ } ) ;
252+ self . status_message = Some ( format ! ( "Added sheet (undo with u)" ) ) ;
243253 }
244254 [ "sheet" , "delete" ] | [ "delsheet" ] => {
245255 let name = self . workbook . sheet_names [ self . workbook . active_sheet ] . clone ( ) ;
246- if self . workbook . remove_sheet ( self . workbook . active_sheet ) {
247- self . selected_row = 0 ;
248- self . selected_col = 0 ;
249- self . scroll_row = 0 ;
250- self . scroll_col = 0 ;
251- self . dirty = true ;
252- crate :: infrastructure:: autosave:: mark_dirty ( ) ;
253- // remove_sheet may have shifted the active sheet (the
254- // workbook re-anchors it). Drop search/find-replace state
255- // since their (row, col) results belonged to the old
256- // active sheet.
257- self . invalidate_cross_sheet_state ( ) ;
258- self . status_message = Some ( format ! ( "Deleted sheet '{}'" , name) ) ;
259- } else {
256+ if self . workbook . sheets . len ( ) <= 1 {
260257 self . status_message = Some ( "Cannot delete the last sheet" . to_string ( ) ) ;
258+ } else {
259+ let label = format ! ( "Delete sheet '{}'" , name) ;
260+ let active = self . workbook . active_sheet ;
261+ self . with_snapshot_undo ( & label, |app| {
262+ app. workbook . remove_sheet ( active) ;
263+ app. selected_row = 0 ;
264+ app. selected_col = 0 ;
265+ app. scroll_row = 0 ;
266+ app. scroll_col = 0 ;
267+ app. invalidate_cross_sheet_state ( ) ;
268+ } ) ;
269+ self . status_message = Some ( format ! ( "Deleted sheet '{}' (undo with u)" , name) ) ;
261270 }
262271 }
263272 [ "sheet" , "next" ] | [ "sn" ] => {
@@ -539,6 +548,11 @@ impl App {
539548 for s in & mut self . workbook . sheets {
540549 s. iterative_calc = true ;
541550 }
551+ // Existing circular formulas need to resolve under the new
552+ // mode; without this they keep showing the previous #CYCLE!
553+ // value until the next user edit.
554+ self . recalc_all ( ) ;
555+ self . dirty = true ;
542556 self . status_message = Some ( format ! (
543557 "Iterative calc: on (max {} iters, eps {})" ,
544558 self . workbook. current_sheet( ) . iter_max,
@@ -550,13 +564,19 @@ impl App {
550564 for s in & mut self . workbook . sheets {
551565 s. iterative_calc = false ;
552566 }
567+ self . recalc_all ( ) ;
568+ self . dirty = true ;
553569 self . status_message = Some ( "Iterative calc: off" . to_string ( ) ) ;
554570 }
555571 [ "iterative" , "max" , n] => {
556572 if let Ok ( v) = n. parse :: < usize > ( ) {
557573 for s in & mut self . workbook . sheets {
558574 s. iter_max = v;
559575 }
576+ if self . iterative_calc {
577+ self . recalc_all ( ) ;
578+ }
579+ self . dirty = true ;
560580 self . status_message = Some ( format ! ( "Iterative max = {}" , v) ) ;
561581 } else {
562582 self . status_message = Some ( "iterative max: bad number" . to_string ( ) ) ;
@@ -567,6 +587,10 @@ impl App {
567587 for s in & mut self . workbook . sheets {
568588 s. iter_epsilon = v;
569589 }
590+ if self . iterative_calc {
591+ self . recalc_all ( ) ;
592+ }
593+ self . dirty = true ;
570594 self . status_message = Some ( format ! ( "Iterative epsilon = {}" , v) ) ;
571595 } else {
572596 self . status_message = Some ( "iterative epsilon: bad number" . to_string ( ) ) ;
@@ -948,16 +972,26 @@ impl App {
948972 // Join the remaining tokens so values with spaces (e.g.
949973 // `LAMBDA(x, x*2)`) survive intact.
950974 let value = rest. join ( " " ) ;
951- self . workbook . set_name ( name, & value) ;
952- self . dirty = true ;
975+ let label = format ! ( "Name '{}' = {}" , name, value) ;
976+ let name_owned = ( * name) . to_string ( ) ;
977+ let value_owned = value. clone ( ) ;
978+ self . with_snapshot_undo ( & label, |app| {
979+ app. workbook . set_name ( & name_owned, & value_owned) ;
980+ } ) ;
953981 self . status_message = Some ( format ! ( "Named '{}' = {}" , name, value) ) ;
954982 }
955983 [ "unname" , name] => {
956- if self . workbook . remove_name ( name) {
957- self . dirty = true ;
958- self . status_message = Some ( format ! ( "Removed name '{}'" , name) ) ;
959- } else {
984+ if !self . workbook . named_ranges . contains_key ( * name)
985+ && !self . workbook . named_ranges . keys ( ) . any ( |k| k. eq_ignore_ascii_case ( name) )
986+ {
960987 self . status_message = Some ( format ! ( "No such name: {}" , name) ) ;
988+ } else {
989+ let label = format ! ( "Remove name '{}'" , name) ;
990+ let name_owned = ( * name) . to_string ( ) ;
991+ self . with_snapshot_undo ( & label, |app| {
992+ app. workbook . remove_name ( & name_owned) ;
993+ } ) ;
994+ self . status_message = Some ( format ! ( "Removed name '{}'" , name) ) ;
961995 }
962996 }
963997 [ "names" ] => {
0 commit comments