Merge branch 'main' into worktree-improvise-ewi-formula-crate

# Conflicts:
#	TAGS
This commit is contained in:
Edward Langley
2026-04-15 22:47:51 -07:00
9 changed files with 9970 additions and 433 deletions

2
.gitignore vendored
View File

@ -24,3 +24,5 @@ bench/*.txt
proptest-regressions/ proptest-regressions/
.cursor .cursor
.claude/worktrees/ .claude/worktrees/
*~
/roadmap.html

662
TAGS
View File

@ -748,7 +748,7 @@ impl<'a> Widget for ImportWizardWidget<'a> {ImportWizardWidget21,444
fn render(self, area: Rect, buf: &mut Buffer) {render22,489 fn render(self, area: Rect, buf: &mut Buffer) {render22,489
fn truncate(s: &str, max: usize) -> String {truncate339,13390 fn truncate(s: &str, max: usize) -> String {truncate339,13390
src/ui/effect.rs,14177 src/ui/effect.rs,14356
pub(crate) const RECORD_COORDS_CANNOT_BE_EMPTY: &str = "Record coordinates cannot be empty";RECORD_COORDS_CANNOT_BE_EMPTY9,160 pub(crate) const RECORD_COORDS_CANNOT_BE_EMPTY: &str = "Record coordinates cannot be empty";RECORD_COORDS_CANNOT_BE_EMPTY9,160
pub trait Effect: Debug {Effect13,358 pub trait Effect: Debug {Effect13,358
fn apply(&self, app: &mut App);apply14,384 fn apply(&self, app: &mut App);apply14,384
@ -786,223 +786,225 @@ pub struct RemoveFormula {RemoveFormula113,3206
pub target_category: String,target_category115,3257 pub target_category: String,target_category115,3257
impl Effect for RemoveFormula {RemoveFormula117,3292 impl Effect for RemoveFormula {RemoveFormula117,3292
fn apply(&self, app: &mut App) {apply118,3324 fn apply(&self, app: &mut App) {apply118,3324
pub struct EnterEditAtCursor;EnterEditAtCursor128,3619 pub struct EnterEditAtCursor {EnterEditAtCursor133,3880
impl Effect for EnterEditAtCursor {EnterEditAtCursor129,3649 pub target_mode: AppMode,target_mode134,3911
fn apply(&self, app: &mut App) {apply130,3685 impl Effect for EnterEditAtCursor {EnterEditAtCursor136,3943
pub struct TogglePruneEmpty;TogglePruneEmpty148,4184 fn apply(&self, app: &mut App) {apply137,3979
impl Effect for TogglePruneEmpty {TogglePruneEmpty149,4213 pub struct TogglePruneEmpty;TogglePruneEmpty153,4532
fn apply(&self, app: &mut App) {apply150,4248 impl Effect for TogglePruneEmpty {TogglePruneEmpty154,4561
pub struct ToggleCatExpand(pub String);ToggleCatExpand157,4399 fn apply(&self, app: &mut App) {apply155,4596
impl Effect for ToggleCatExpand {ToggleCatExpand158,4439 pub struct ToggleCatExpand(pub String);ToggleCatExpand162,4747
fn apply(&self, app: &mut App) {apply159,4473 impl Effect for ToggleCatExpand {ToggleCatExpand163,4787
pub struct RemoveItem {RemoveItem167,4648 fn apply(&self, app: &mut App) {apply164,4821
pub category: String,category168,4672 pub struct RemoveItem {RemoveItem172,4996
pub item: String,item169,4698 pub category: String,category173,5020
impl Effect for RemoveItem {RemoveItem171,4722 pub item: String,item174,5046
fn apply(&self, app: &mut App) {apply172,4751 impl Effect for RemoveItem {RemoveItem176,5070
pub struct RemoveCategory(pub String);RemoveCategory178,4882 fn apply(&self, app: &mut App) {apply177,5099
impl Effect for RemoveCategory {RemoveCategory179,4921 pub struct RemoveCategory(pub String);RemoveCategory183,5230
fn apply(&self, app: &mut App) {apply180,4954 impl Effect for RemoveCategory {RemoveCategory184,5269
pub struct CreateView(pub String);CreateView188,5268 fn apply(&self, app: &mut App) {apply185,5302
impl Effect for CreateView {CreateView189,5303 pub struct CreateView(pub String);CreateView193,5616
fn apply(&self, app: &mut App) {apply190,5332 impl Effect for CreateView {CreateView194,5651
pub struct DeleteView(pub String);DeleteView196,5438 fn apply(&self, app: &mut App) {apply195,5680
impl Effect for DeleteView {DeleteView197,5473 pub struct DeleteView(pub String);DeleteView201,5786
fn apply(&self, app: &mut App) {apply198,5502 impl Effect for DeleteView {DeleteView202,5821
pub struct SwitchView(pub String);SwitchView204,5616 fn apply(&self, app: &mut App) {apply203,5850
impl Effect for SwitchView {SwitchView205,5651 pub struct SwitchView(pub String);SwitchView209,5964
fn apply(&self, app: &mut App) {apply206,5680 impl Effect for SwitchView {SwitchView210,5999
pub struct ViewBack;ViewBack221,6153 fn apply(&self, app: &mut App) {apply211,6028
impl Effect for ViewBack {ViewBack222,6174 pub struct ViewBack;ViewBack226,6501
fn apply(&self, app: &mut App) {apply223,6201 impl Effect for ViewBack {ViewBack227,6522
pub struct ViewForward;ViewForward238,6714 fn apply(&self, app: &mut App) {apply228,6549
impl Effect for ViewForward {ViewForward239,6738 pub struct ViewForward;ViewForward243,7062
fn apply(&self, app: &mut App) {apply240,6768 impl Effect for ViewForward {ViewForward244,7086
pub struct SetAxis {SetAxis254,7201 fn apply(&self, app: &mut App) {apply245,7116
pub category: String,category255,7222 pub struct SetAxis {SetAxis259,7549
pub axis: Axis,axis256,7248 pub category: String,category260,7570
impl Effect for SetAxis {SetAxis258,7270 pub axis: Axis,axis261,7596
fn apply(&self, app: &mut App) {apply259,7296 impl Effect for SetAxis {SetAxis263,7618
pub struct SetPageSelection {SetPageSelection267,7461 fn apply(&self, app: &mut App) {apply264,7644
pub category: String,category268,7491 pub struct SetPageSelection {SetPageSelection272,7809
pub item: String,item269,7517 pub category: String,category273,7839
impl Effect for SetPageSelection {SetPageSelection271,7541 pub item: String,item274,7865
fn apply(&self, app: &mut App) {apply272,7576 impl Effect for SetPageSelection {SetPageSelection276,7889
pub struct ToggleGroup {ToggleGroup280,7752 fn apply(&self, app: &mut App) {apply277,7924
pub category: String,category281,7777 pub struct ToggleGroup {ToggleGroup285,8100
pub group: String,group282,7803 pub category: String,category286,8125
impl Effect for ToggleGroup {ToggleGroup284,7828 pub group: String,group287,8151
fn apply(&self, app: &mut App) {apply285,7858 impl Effect for ToggleGroup {ToggleGroup289,8176
pub struct HideItem {HideItem293,8038 fn apply(&self, app: &mut App) {apply290,8206
pub category: String,category294,8060 pub struct HideItem {HideItem298,8386
pub item: String,item295,8086 pub category: String,category299,8408
impl Effect for HideItem {HideItem297,8110 pub item: String,item300,8434
fn apply(&self, app: &mut App) {apply298,8137 impl Effect for HideItem {HideItem302,8458
pub struct ShowItem {ShowItem306,8304 fn apply(&self, app: &mut App) {apply303,8485
pub category: String,category307,8326 pub struct ShowItem {ShowItem311,8652
pub item: String,item308,8352 pub category: String,category312,8674
impl Effect for ShowItem {ShowItem310,8376 pub item: String,item313,8700
fn apply(&self, app: &mut App) {apply311,8403 impl Effect for ShowItem {ShowItem315,8724
pub struct TransposeAxes;TransposeAxes319,8570 fn apply(&self, app: &mut App) {apply316,8751
impl Effect for TransposeAxes {TransposeAxes320,8596 pub struct TransposeAxes;TransposeAxes324,8918
fn apply(&self, app: &mut App) {apply321,8628 impl Effect for TransposeAxes {TransposeAxes325,8944
pub struct CycleAxis(pub String);CycleAxis327,8748 fn apply(&self, app: &mut App) {apply326,8976
impl Effect for CycleAxis {CycleAxis328,8782 pub struct CycleAxis(pub String);CycleAxis332,9096
fn apply(&self, app: &mut App) {apply329,8810 impl Effect for CycleAxis {CycleAxis333,9130
pub struct SetNumberFormat(pub String);SetNumberFormat335,8933 fn apply(&self, app: &mut App) {apply334,9158
impl Effect for SetNumberFormat {SetNumberFormat336,8973 pub struct SetNumberFormat(pub String);SetNumberFormat340,9281
fn apply(&self, app: &mut App) {apply337,9007 impl Effect for SetNumberFormat {SetNumberFormat341,9321
pub struct SetSelected(pub usize, pub usize);SetSelected345,9353 fn apply(&self, app: &mut App) {apply342,9355
impl Effect for SetSelected {SetSelected346,9399 pub struct SetSelected(pub usize, pub usize);SetSelected350,9701
fn apply(&self, app: &mut App) {apply347,9429 impl Effect for SetSelected {SetSelected351,9747
pub struct SetRowOffset(pub usize);SetRowOffset353,9560 fn apply(&self, app: &mut App) {apply352,9777
impl Effect for SetRowOffset {SetRowOffset354,9596 pub struct SetRowOffset(pub usize);SetRowOffset358,9908
fn apply(&self, app: &mut App) {apply355,9627 impl Effect for SetRowOffset {SetRowOffset359,9944
pub struct SetColOffset(pub usize);SetColOffset361,9750 fn apply(&self, app: &mut App) {apply360,9975
impl Effect for SetColOffset {SetColOffset362,9786 pub struct SetColOffset(pub usize);SetColOffset366,10098
fn apply(&self, app: &mut App) {apply363,9817 impl Effect for SetColOffset {SetColOffset367,10134
pub struct ChangeMode(pub AppMode);ChangeMode371,10154 fn apply(&self, app: &mut App) {apply368,10165
impl Effect for ChangeMode {ChangeMode372,10190 pub struct ChangeMode(pub AppMode);ChangeMode376,10502
fn apply(&self, app: &mut App) {apply373,10219 impl Effect for ChangeMode {ChangeMode377,10538
fn changes_mode(&self) -> bool {changes_mode376,10297 fn apply(&self, app: &mut App) {apply378,10567
pub struct SetStatus(pub String);SetStatus382,10373 fn changes_mode(&self) -> bool {changes_mode381,10645
impl Effect for SetStatus {SetStatus383,10407 pub struct SetStatus(pub String);SetStatus387,10721
fn apply(&self, app: &mut App) {apply384,10435 impl Effect for SetStatus {SetStatus388,10755
pub struct MarkDirty;MarkDirty390,10539 fn apply(&self, app: &mut App) {apply389,10783
impl Effect for MarkDirty {MarkDirty391,10561 pub struct MarkDirty;MarkDirty395,10887
fn apply(&self, app: &mut App) {apply392,10589 impl Effect for MarkDirty {MarkDirty396,10909
pub struct SetYanked(pub Option<CellValue>);SetYanked398,10678 fn apply(&self, app: &mut App) {apply397,10937
impl Effect for SetYanked {SetYanked399,10723 pub struct SetYanked(pub Option<CellValue>);SetYanked403,11026
fn apply(&self, app: &mut App) {apply400,10751 impl Effect for SetYanked {SetYanked404,11071
pub struct SetSearchQuery(pub String);SetSearchQuery406,10851 fn apply(&self, app: &mut App) {apply405,11099
impl Effect for SetSearchQuery {SetSearchQuery407,10890 pub struct SetSearchQuery(pub String);SetSearchQuery411,11199
fn apply(&self, app: &mut App) {apply408,10923 impl Effect for SetSearchQuery {SetSearchQuery412,11238
pub struct SetSearchMode(pub bool);SetSearchMode414,11029 fn apply(&self, app: &mut App) {apply413,11271
impl Effect for SetSearchMode {SetSearchMode415,11065 pub struct SetSearchMode(pub bool);SetSearchMode419,11377
fn apply(&self, app: &mut App) {apply416,11097 impl Effect for SetSearchMode {SetSearchMode420,11413
pub struct SetBuffer {SetBuffer423,11229 fn apply(&self, app: &mut App) {apply421,11445
pub name: String,name424,11252 pub struct SetBuffer {SetBuffer428,11577
pub value: String,value425,11274 pub name: String,name429,11600
impl Effect for SetBuffer {SetBuffer427,11299 pub value: String,value430,11622
fn apply(&self, app: &mut App) {apply428,11327 impl Effect for SetBuffer {SetBuffer432,11647
pub struct SetTileCatIdx(pub usize);SetTileCatIdx439,11655 fn apply(&self, app: &mut App) {apply433,11675
impl Effect for SetTileCatIdx {SetTileCatIdx440,11692 pub struct SetTileCatIdx(pub usize);SetTileCatIdx444,12003
fn apply(&self, app: &mut App) {apply441,11724 impl Effect for SetTileCatIdx {SetTileCatIdx445,12040
pub struct StartDrill(pub Vec<(CellKey, CellValue)>);StartDrill449,11923 fn apply(&self, app: &mut App) {apply446,12072
impl Effect for StartDrill {StartDrill450,11977 pub struct StartDrill(pub Vec<(CellKey, CellValue)>);StartDrill454,12271
fn apply(&self, app: &mut App) {apply451,12006 impl Effect for StartDrill {StartDrill455,12325
pub struct ApplyAndClearDrill;ApplyAndClearDrill461,12321 fn apply(&self, app: &mut App) {apply456,12354
impl Effect for ApplyAndClearDrill {ApplyAndClearDrill462,12352 pub struct ApplyAndClearDrill;ApplyAndClearDrill466,12669
fn apply(&self, app: &mut App) {apply463,12389 impl Effect for ApplyAndClearDrill {ApplyAndClearDrill467,12700
pub struct SetDrillPendingEdit {SetDrillPendingEdit523,14762 fn apply(&self, app: &mut App) {apply468,12737
pub record_idx: usize,record_idx524,14795 pub struct SetDrillPendingEdit {SetDrillPendingEdit528,15110
pub col_name: String,col_name525,14822 pub record_idx: usize,record_idx529,15143
pub new_value: String,new_value526,14848 pub col_name: String,col_name530,15170
impl Effect for SetDrillPendingEdit {SetDrillPendingEdit528,14877 pub new_value: String,new_value531,15196
fn apply(&self, app: &mut App) {apply529,14915 impl Effect for SetDrillPendingEdit {SetDrillPendingEdit533,15225
pub struct Save;Save542,15401 fn apply(&self, app: &mut App) {apply534,15263
impl Effect for Save {Save543,15418 pub struct Save;Save547,15749
fn apply(&self, app: &mut App) {apply544,15441 impl Effect for Save {Save548,15766
pub struct SaveAs(pub PathBuf);SaveAs562,16006 fn apply(&self, app: &mut App) {apply549,15789
impl Effect for SaveAs {SaveAs563,16038 pub struct SaveAs(pub PathBuf);SaveAs567,16354
fn apply(&self, app: &mut App) {apply564,16063 impl Effect for SaveAs {SaveAs568,16386
pub struct WizardKey {WizardKey582,16679 fn apply(&self, app: &mut App) {apply569,16411
pub key_code: crossterm::event::KeyCode,key_code583,16702 pub struct WizardKey {WizardKey587,17027
impl Effect for WizardKey {WizardKey585,16749 pub key_code: crossterm::event::KeyCode,key_code588,17050
fn apply(&self, app: &mut App) {apply586,16777 impl Effect for WizardKey {WizardKey590,17097
pub struct StartImportWizard(pub String);StartImportWizard712,22429 fn apply(&self, app: &mut App) {apply591,17125
impl Effect for StartImportWizard {StartImportWizard713,22471 pub struct StartImportWizard(pub String);StartImportWizard717,22777
fn apply(&self, app: &mut App) {apply714,22507 impl Effect for StartImportWizard {StartImportWizard718,22819
pub struct ExportCsv(pub PathBuf);ExportCsv733,23142 fn apply(&self, app: &mut App) {apply719,22855
impl Effect for ExportCsv {ExportCsv734,23177 pub struct ExportCsv(pub PathBuf);ExportCsv738,23490
fn apply(&self, app: &mut App) {apply735,23205 impl Effect for ExportCsv {ExportCsv739,23525
pub struct LoadModel(pub PathBuf);LoadModel750,23693 fn apply(&self, app: &mut App) {apply740,23553
impl Effect for LoadModel {LoadModel751,23728 pub struct LoadModel(pub PathBuf);LoadModel755,24041
fn apply(&self, app: &mut App) {apply752,23756 impl Effect for LoadModel {LoadModel756,24076
pub struct ImportJsonHeadless {ImportJsonHeadless768,24268 fn apply(&self, app: &mut App) {apply757,24104
pub path: PathBuf,path769,24300 pub struct ImportJsonHeadless {ImportJsonHeadless773,24616
pub model_name: Option<String>,model_name770,24323 pub path: PathBuf,path774,24648
pub array_path: Option<String>,array_path771,24359 pub model_name: Option<String>,model_name775,24671
impl Effect for ImportJsonHeadless {ImportJsonHeadless773,24397 pub array_path: Option<String>,array_path776,24707
fn apply(&self, app: &mut App) {apply774,24434 impl Effect for ImportJsonHeadless {ImportJsonHeadless778,24745
pub struct SetPanelOpen {SetPanelOpen878,28005 fn apply(&self, app: &mut App) {apply779,24782
pub panel: Panel,panel879,28031 pub struct SetPanelOpen {SetPanelOpen883,28353
pub open: bool,open880,28053 pub panel: Panel,panel884,28379
pub enum Panel {Panel884,28106 pub open: bool,open885,28401
Formula,Formula885,28123 pub enum Panel {Panel889,28454
Category,Category886,28136 Formula,Formula890,28471
View,View887,28150 Category,Category891,28484
impl Panel {Panel890,28163 View,View892,28498
pub fn mode(self) -> AppMode {mode891,28176 impl Panel {Panel895,28511
impl Effect for SetPanelOpen {SetPanelOpen900,28406 pub fn mode(self) -> AppMode {mode896,28524
fn apply(&self, app: &mut App) {apply901,28437 impl Effect for SetPanelOpen {SetPanelOpen905,28754
pub struct SetPanelCursor {SetPanelCursor911,28731 fn apply(&self, app: &mut App) {apply906,28785
pub panel: Panel,panel912,28759 pub struct SetPanelCursor {SetPanelCursor916,29079
pub cursor: usize,cursor913,28781 pub panel: Panel,panel917,29107
impl Effect for SetPanelCursor {SetPanelCursor915,28806 pub cursor: usize,cursor918,29129
fn apply(&self, app: &mut App) {apply916,28839 impl Effect for SetPanelCursor {SetPanelCursor920,29154
pub fn mark_dirty() -> Box<dyn Effect> {mark_dirty927,29301 fn apply(&self, app: &mut App) {apply921,29187
pub fn set_status(msg: impl Into<String>) -> Box<dyn Effect> {set_status931,29369 pub fn mark_dirty() -> Box<dyn Effect> {mark_dirty932,29649
pub fn change_mode(mode: AppMode) -> Box<dyn Effect> {change_mode935,29471 pub fn set_status(msg: impl Into<String>) -> Box<dyn Effect> {set_status936,29717
pub fn set_selected(row: usize, col: usize) -> Box<dyn Effect> {set_selected939,29560 pub fn change_mode(mode: AppMode) -> Box<dyn Effect> {change_mode940,29819
pub struct HelpPageNext;HelpPageNext946,29870 pub fn set_selected(row: usize, col: usize) -> Box<dyn Effect> {set_selected944,29908
impl Effect for HelpPageNext {HelpPageNext947,29895 pub struct HelpPageNext;HelpPageNext951,30218
fn apply(&self, app: &mut App) {apply948,29926 impl Effect for HelpPageNext {HelpPageNext952,30243
pub struct HelpPagePrev;HelpPagePrev955,30125 fn apply(&self, app: &mut App) {apply953,30274
impl Effect for HelpPagePrev {HelpPagePrev956,30150 pub struct HelpPagePrev;HelpPagePrev960,30473
fn apply(&self, app: &mut App) {apply957,30181 impl Effect for HelpPagePrev {HelpPagePrev961,30498
pub struct HelpPageSet(pub usize);HelpPageSet963,30301 fn apply(&self, app: &mut App) {apply962,30529
impl Effect for HelpPageSet {HelpPageSet964,30336 pub struct HelpPageSet(pub usize);HelpPageSet968,30649
fn apply(&self, app: &mut App) {apply965,30366 impl Effect for HelpPageSet {HelpPageSet969,30684
pub fn help_page_next() -> Box<dyn Effect> {help_page_next970,30444 fn apply(&self, app: &mut App) {apply970,30714
pub fn help_page_prev() -> Box<dyn Effect> {help_page_prev974,30519 pub fn help_page_next() -> Box<dyn Effect> {help_page_next975,30792
pub fn help_page_set(page: usize) -> Box<dyn Effect> {help_page_set978,30594 pub fn help_page_prev() -> Box<dyn Effect> {help_page_prev979,30867
mod tests {tests983,30697 pub fn help_page_set(page: usize) -> Box<dyn Effect> {help_page_set983,30942
fn test_app() -> App {test_app988,30813 mod tests {tests988,31045
fn add_category_effect() {add_category_effect1002,31444 fn test_app() -> App {test_app993,31161
fn add_item_to_existing_category() {add_item_to_existing_category1009,31653 fn add_category_effect() {add_category_effect1007,31792
fn add_item_to_nonexistent_category_sets_status() {add_item_to_nonexistent_category_sets_status1028,32148 fn add_item_to_existing_category() {add_item_to_existing_category1014,32001
fn set_cell_and_clear_cell() {set_cell_and_clear_cell1039,32457 fn add_item_to_nonexistent_category_sets_status() {add_item_to_nonexistent_category_sets_status1033,32496
fn add_formula_valid() {add_formula_valid1053,32950 fn set_cell_and_clear_cell() {set_cell_and_clear_cell1044,32805
fn add_formula_adds_target_item_to_category() {add_formula_adds_target_item_to_category1067,33491 fn add_formula_valid() {add_formula_valid1058,33298
fn add_formula_to_measure_shows_in_effective_items() {add_formula_to_measure_shows_in_effective_items1100,34562 fn add_formula_adds_target_item_to_category() {add_formula_adds_target_item_to_category1072,33839
fn add_formula_invalid_sets_error_status() {add_formula_invalid_sets_error_status1126,35512 fn add_formula_to_measure_shows_in_effective_items() {add_formula_to_measure_shows_in_effective_items1105,34910
fn remove_formula_effect() {remove_formula_effect1137,35829 fn add_formula_invalid_sets_error_status() {add_formula_invalid_sets_error_status1131,35860
fn switch_view_pushes_to_back_stack() {switch_view_pushes_to_back_stack1156,36530 fn remove_formula_effect() {remove_formula_effect1142,36177
fn switch_view_to_same_does_not_push_stack() {switch_view_to_same_does_not_push_stack1170,37054 fn switch_view_pushes_to_back_stack() {switch_view_pushes_to_back_stack1161,36878
fn view_back_and_forward() {view_back_and_forward1177,37266 fn switch_view_to_same_does_not_push_stack() {switch_view_to_same_does_not_push_stack1175,37402
fn view_back_with_empty_stack_is_noop() {view_back_with_empty_stack_is_noop1199,38100 fn view_back_and_forward() {view_back_and_forward1182,37614
fn create_and_delete_view() {create_and_delete_view1207,38342 fn view_back_with_empty_stack_is_noop() {view_back_with_empty_stack_is_noop1204,38448
fn set_axis_effect() {set_axis_effect1217,38667 fn create_and_delete_view() {create_and_delete_view1212,38690
fn transpose_axes_effect() {transpose_axes_effect1228,38949 fn set_axis_effect() {set_axis_effect1222,39015
fn set_selected_effect() {set_selected_effect1266,40173 fn transpose_axes_effect() {transpose_axes_effect1233,39297
fn set_row_and_col_offset() {set_row_and_col_offset1273,40365 fn set_selected_effect() {set_selected_effect1271,40521
fn change_mode_effect() {change_mode_effect1284,40835 fn set_row_and_col_offset() {set_row_and_col_offset1278,40713
fn set_buffer_empty_clears() {set_buffer_empty_clears1294,41199 fn change_mode_effect() {change_mode_effect1289,41183
fn set_status_effect() {set_status_effect1307,41585 fn enter_edit_at_cursor_uses_target_mode_not_app_mode() {enter_edit_at_cursor_uses_target_mode_not_app_mode1301,41700
fn mark_dirty_effect() {mark_dirty_effect1314,41768 fn set_buffer_empty_clears() {set_buffer_empty_clears1334,42863
fn set_yanked_effect() {set_yanked_effect1322,41942 fn set_status_effect() {set_status_effect1347,43249
fn set_search_query_and_mode() {set_search_query_and_mode1329,42153 fn mark_dirty_effect() {mark_dirty_effect1354,43432
fn set_buffer_normal_key() {set_buffer_normal_key1342,42664 fn set_yanked_effect() {set_yanked_effect1362,43606
fn set_buffer_search_writes_to_search_query() {set_buffer_search_writes_to_search_query1353,42947 fn set_search_query_and_mode() {set_search_query_and_mode1369,43817
fn set_panel_open_and_cursor() {set_panel_open_and_cursor1367,43487 fn set_buffer_normal_key() {set_buffer_normal_key1382,44328
fn set_tile_cat_idx_effect() {set_tile_cat_idx_effect1399,44215 fn set_buffer_search_writes_to_search_query() {set_buffer_search_writes_to_search_query1393,44611
fn help_page_navigation() {help_page_navigation1408,44561 fn set_panel_open_and_cursor() {set_panel_open_and_cursor1407,45151
fn help_page_prev_clamps_at_zero() {help_page_prev_clamps_at_zero1422,44990 fn set_tile_cat_idx_effect() {set_tile_cat_idx_effect1439,45879
fn start_drill_and_apply_clear_drill_with_no_edits() {start_drill_and_apply_clear_drill_with_no_edits1431,45343 fn help_page_navigation() {help_page_navigation1448,46225
fn apply_and_clear_drill_with_value_edit() {apply_and_clear_drill_with_value_edit1448,45952 fn help_page_prev_clamps_at_zero() {help_page_prev_clamps_at_zero1462,46654
fn apply_and_clear_drill_with_coord_rename() {apply_and_clear_drill_with_coord_rename1475,46849 fn start_drill_and_apply_clear_drill_with_no_edits() {start_drill_and_apply_clear_drill_with_no_edits1471,47007
fn apply_and_clear_drill_empty_value_clears_cell() {apply_and_clear_drill_empty_value_clears_cell1517,48255 fn apply_and_clear_drill_with_value_edit() {apply_and_clear_drill_with_value_edit1488,47616
fn toggle_prune_empty_effect() {toggle_prune_empty_effect1543,49214 fn apply_and_clear_drill_with_coord_rename() {apply_and_clear_drill_with_coord_rename1515,48513
fn toggle_cat_expand_effect() {toggle_cat_expand_effect1553,49585 fn apply_and_clear_drill_empty_value_clears_cell() {apply_and_clear_drill_empty_value_clears_cell1557,49919
fn remove_item_and_category() {remove_item_and_category1563,49957 fn toggle_prune_empty_effect() {toggle_prune_empty_effect1583,50878
fn set_number_format_effect() {set_number_format_effect1587,50747 fn toggle_cat_expand_effect() {toggle_cat_expand_effect1593,51249
fn set_page_selection_effect() {set_page_selection_effect1596,51148 fn remove_item_and_category() {remove_item_and_category1603,51621
fn hide_and_show_item_effects() {hide_and_show_item_effects1609,51645 fn set_number_format_effect() {set_number_format_effect1627,52411
fn toggle_group_effect() {toggle_group_effect1629,52335 fn set_page_selection_effect() {set_page_selection_effect1636,52812
fn cycle_axis_effect() {cycle_axis_effect1656,53171 fn hide_and_show_item_effects() {hide_and_show_item_effects1649,53309
fn save_without_file_path_shows_status() {save_without_file_path_shows_status1667,53637 fn toggle_group_effect() {toggle_group_effect1669,53999
fn panel_mode_mapping() {panel_mode_mapping1676,54000 fn cycle_axis_effect() {cycle_axis_effect1696,54835
fn save_without_file_path_shows_status() {save_without_file_path_shows_status1707,55301
fn panel_mode_mapping() {panel_mode_mapping1716,55664
src/ui/mod.rs,400 src/ui/mod.rs,400
pub mod app;app1,0 pub mod app;app1,0
@ -1434,7 +1436,7 @@ mod tests {tests302,10085
fn parse_axis_recognizes_all_variants() {parse_axis_recognizes_all_variants306,10128 fn parse_axis_recognizes_all_variants() {parse_axis_recognizes_all_variants306,10128
fn parse_axis_rejects_unknown() {parse_axis_rejects_unknown316,10462 fn parse_axis_rejects_unknown() {parse_axis_rejects_unknown316,10462
src/command/cmd/commit.rs,2750 src/command/cmd/commit.rs,2939
mod tests {tests10,266 mod tests {tests10,266
fn commit_formula_with_categories_adds_formula() {commit_formula_with_categories_adds_formula18,426 fn commit_formula_with_categories_adds_formula() {commit_formula_with_categories_adds_formula18,426
fn commit_formula_without_regular_categories_targets_measure() {commit_formula_without_regular_categories_targets_measure41,1294 fn commit_formula_without_regular_categories_targets_measure() {commit_formula_without_regular_categories_targets_measure41,1294
@ -1442,39 +1444,41 @@ mod tests {tests10,266
fn commit_category_add_with_empty_buffer_returns_to_panel() {commit_category_add_with_empty_buffer_returns_to_panel79,2622 fn commit_category_add_with_empty_buffer_returns_to_panel() {commit_category_add_with_empty_buffer_returns_to_panel79,2622
fn commit_item_add_with_name_produces_add_item() {commit_item_add_with_name_produces_add_item96,3221 fn commit_item_add_with_name_produces_add_item() {commit_item_add_with_name_produces_add_item96,3221
fn commit_item_add_outside_item_add_mode_returns_empty() {commit_item_add_outside_item_add_mode_returns_empty112,3853 fn commit_item_add_outside_item_add_mode_returns_empty() {commit_item_add_outside_item_add_mode_returns_empty112,3853
fn commit_export_produces_export_and_normal_mode() {commit_export_produces_export_and_normal_mode122,4176 fn commit_and_advance_threads_edit_mode_to_enter_edit_at_cursor() {commit_and_advance_threads_edit_mode_to_enter_edit_at_cursor125,4399
fn commit_regular_cell_value(key: &CellKey, value: &str, effects: &mut Vec<Box<dyn Effect>>) {commit_regular_cell_value143,5139 fn commit_export_produces_export_and_normal_mode() {commit_export_produces_export_and_normal_mode151,5424
fn stage_drill_edit(record_idx: usize, col_name: String, value: &str) -> Box<dyn Effect> {stage_drill_edit158,5735 fn commit_regular_cell_value(key: &CellKey, value: &str, effects: &mut Vec<Box<dyn Effect>>) {commit_regular_cell_value172,6387
fn commit_plain_records_edit(commit_plain_records_edit167,6034 fn stage_drill_edit(record_idx: usize, col_name: String, value: &str) -> Box<dyn Effect> {stage_drill_edit187,6983
fn commit_cell_value(commit_cell_value208,7063 fn commit_plain_records_edit(commit_plain_records_edit196,7282
pub enum AdvanceDir {AdvanceDir228,7649 fn commit_cell_value(commit_cell_value237,8311
Down,Down230,7741 pub enum AdvanceDir {AdvanceDir257,8897
Right,Right232,7800 Down,Down259,8989
pub struct CommitAndAdvance {CommitAndAdvance238,7981 Right,Right261,9048
pub key: CellKey,key239,8011 pub struct CommitAndAdvance {CommitAndAdvance272,9491
pub value: String,value240,8033 pub key: CellKey,key273,9521
pub advance: AdvanceDir,advance241,8056 pub value: String,value274,9543
pub cursor: CursorState,cursor242,8085 pub advance: AdvanceDir,advance275,9566
impl Cmd for CommitAndAdvance {CommitAndAdvance244,8116 pub cursor: CursorState,cursor276,9595
fn name(&self) -> &'static str {name245,8148 pub edit_mode: AppMode,edit_mode277,9624
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute251,8343 impl Cmd for CommitAndAdvance {CommitAndAdvance279,9654
pub struct CommitFormula;CommitFormula297,10180 fn name(&self) -> &'static str {name280,9686
impl Cmd for CommitFormula {CommitFormula298,10206 fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute286,9881
fn name(&self) -> &'static str {name299,10235 pub struct CommitFormula;CommitFormula334,11779
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute302,10303 impl Cmd for CommitFormula {CommitFormula335,11805
fn commit_add_from_buffer(commit_add_from_buffer322,11120 fn name(&self) -> &'static str {name336,11834
pub struct CommitCategoryAdd;CommitCategoryAdd345,11827 fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute339,11902
impl Cmd for CommitCategoryAdd {CommitCategoryAdd346,11857 fn commit_add_from_buffer(commit_add_from_buffer359,12719
fn name(&self) -> &'static str {name347,11890 pub struct CommitCategoryAdd;CommitCategoryAdd382,13426
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute350,11963 impl Cmd for CommitCategoryAdd {CommitCategoryAdd383,13456
pub struct CommitItemAdd;CommitItemAdd362,12340 fn name(&self) -> &'static str {name384,13489
impl Cmd for CommitItemAdd {CommitItemAdd363,12366 fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute387,13562
fn name(&self) -> &'static str {name364,12395 pub struct CommitItemAdd;CommitItemAdd399,13939
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute367,12464 impl Cmd for CommitItemAdd {CommitItemAdd400,13965
pub struct CommitExport;CommitExport389,13085 fn name(&self) -> &'static str {name401,13994
impl Cmd for CommitExport {CommitExport390,13110 fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute404,14063
fn name(&self) -> &'static str {name391,13138 pub struct CommitExport;CommitExport426,14684
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute394,13205 impl Cmd for CommitExport {CommitExport427,14709
fn name(&self) -> &'static str {name428,14737
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute431,14804
src/command/cmd/cell.rs,1886 src/command/cmd/cell.rs,1886
mod tests {tests6,90 mod tests {tests6,90
@ -1508,9 +1512,10 @@ impl Cmd for SaveCmd {SaveCmd191,5956
fn name(&self) -> &'static str {name192,5979 fn name(&self) -> &'static str {name192,5979
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute195,6037 fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute195,6037
src/command/cmd/registry.rs,113 src/command/cmd/registry.rs,193
pub fn default_registry() -> CmdRegistry {default_registry19,459 fn parse_mode_name(s: &str) -> Result<AppMode, String> {parse_mode_name7,208
macro_rules! reg_jump {reg_jump105,4345 pub fn default_registry() -> CmdRegistry {default_registry40,1375
macro_rules! reg_jump {reg_jump126,5261
src/command/cmd/effect_cmds.rs,518 src/command/cmd/effect_cmds.rs,518
mod tests {tests8,162 mod tests {tests8,162
@ -1791,59 +1796,58 @@ impl Cmd for ExitSearchMode {ExitSearchMode195,6137
fn name(&self) -> &'static str {name196,6167 fn name(&self) -> &'static str {name196,6167
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute199,6237 fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute199,6237
src/command/cmd/mode.rs,3175 src/command/cmd/mode.rs,3285
mod tests {tests8,151 mod tests {tests8,151
fn enter_edit_mode_produces_editing_mode() {enter_edit_mode_produces_editing_mode14,275 fn enter_tile_select_with_categories() {enter_tile_select_with_categories14,275
fn enter_tile_select_with_categories() {enter_tile_select_with_categories29,788 fn enter_tile_select_no_categories() {enter_tile_select_no_categories30,772
fn enter_tile_select_no_categories() {enter_tile_select_no_categories45,1285 fn enter_export_prompt_sets_mode() {enter_export_prompt_sets_mode41,1108
fn enter_export_prompt_sets_mode() {enter_export_prompt_sets_mode56,1621 fn force_quit_always_produces_quit_mode() {force_quit_always_produces_quit_mode55,1542
fn force_quit_always_produces_quit_mode() {force_quit_always_produces_quit_mode70,2055 fn save_and_quit_produces_save_then_quit() {save_and_quit_produces_save_then_quit68,1993
fn save_and_quit_produces_save_then_quit() {save_and_quit_produces_save_then_quit83,2506 fn edit_or_drill_without_aggregation_enters_edit() {edit_or_drill_without_aggregation_enters_edit81,2480
fn edit_or_drill_without_aggregation_enters_edit() {edit_or_drill_without_aggregation_enters_edit96,2993 fn edit_or_drill_passes_records_editing_mode_through() {edit_or_drill_passes_records_editing_mode_through101,3324
fn enter_search_mode_sets_flag_and_clears_query() {enter_search_mode_sets_flag_and_clears_query107,3393 fn enter_edit_at_cursor_cmd_passes_target_mode_to_effect() {enter_edit_at_cursor_cmd_passes_target_mode_to_effect123,4197
pub struct EnterMode(pub AppMode);EnterMode129,4200 fn edit_or_drill_pre_fills_edit_buffer_with_display_value() {edit_or_drill_pre_fills_edit_buffer_with_display_value143,4926
impl Cmd for EnterMode {EnterMode130,4235 fn enter_search_mode_sets_flag_and_clears_query() {enter_search_mode_sets_flag_and_clears_query161,5554
fn name(&self) -> &'static str {name131,4260 pub struct EnterMode(pub AppMode);EnterMode183,6361
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute134,4324 impl Cmd for EnterMode {EnterMode184,6396
pub struct ForceQuit;ForceQuit149,4842 fn name(&self) -> &'static str {name185,6421
impl Cmd for ForceQuit {ForceQuit150,4864 fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute188,6485
fn name(&self) -> &'static str {name151,4889 pub struct ForceQuit;ForceQuit203,7003
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute154,4953 impl Cmd for ForceQuit {ForceQuit204,7025
pub struct Quit;Quit161,5159 fn name(&self) -> &'static str {name205,7050
impl Cmd for Quit {Quit162,5176 fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute208,7114
fn name(&self) -> &'static str {name163,5196 pub struct Quit;Quit215,7320
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute166,5251 impl Cmd for Quit {Quit216,7337
pub struct SaveAndQuit;SaveAndQuit179,5597 fn name(&self) -> &'static str {name217,7357
impl Cmd for SaveAndQuit {SaveAndQuit180,5621 fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute220,7412
fn name(&self) -> &'static str {name181,5648 pub struct SaveAndQuit;SaveAndQuit233,7758
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute184,5704 impl Cmd for SaveAndQuit {SaveAndQuit234,7782
pub struct EnterEditMode {EnterEditMode193,6114 fn name(&self) -> &'static str {name235,7809
pub initial_value: String,initial_value194,6141 fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute238,7865
impl Cmd for EnterEditMode {EnterEditMode196,6174 pub struct EditOrDrill {EditOrDrill254,8707
fn name(&self) -> &'static str {name197,6203 pub edit_mode: AppMode,edit_mode255,8732
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute200,6272 impl Cmd for EditOrDrill {EditOrDrill257,8762
pub struct EditOrDrill;EditOrDrill220,6953 fn name(&self) -> &'static str {name258,8789
impl Cmd for EditOrDrill {EditOrDrill221,6977 fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute261,8856
fn name(&self) -> &'static str {name222,7004 pub struct EnterEditAtCursorCmd {EnterEditAtCursorCmd295,10299
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute225,7071 pub target_mode: AppMode,target_mode296,10333
pub struct EnterEditAtCursorCmd;EnterEditAtCursorCmd254,8262 impl Cmd for EnterEditAtCursorCmd {EnterEditAtCursorCmd298,10365
impl Cmd for EnterEditAtCursorCmd {EnterEditAtCursorCmd255,8295 fn name(&self) -> &'static str {name299,10401
fn name(&self) -> &'static str {name256,8331 fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute302,10475
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute259,8405 pub struct EnterExportPrompt;EnterExportPrompt311,10711
pub struct EnterExportPrompt;EnterExportPrompt266,8578 impl Cmd for EnterExportPrompt {EnterExportPrompt312,10741
impl Cmd for EnterExportPrompt {EnterExportPrompt267,8608 fn name(&self) -> &'static str {name313,10774
fn name(&self) -> &'static str {name268,8641 fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute316,10847
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute271,8714 pub struct EnterSearchMode;EnterSearchMode323,11023
pub struct EnterSearchMode;EnterSearchMode278,8890 impl Cmd for EnterSearchMode {EnterSearchMode324,11051
impl Cmd for EnterSearchMode {EnterSearchMode279,8918 fn name(&self) -> &'static str {name325,11082
fn name(&self) -> &'static str {name280,8949 fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute328,11142
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute283,9009 pub struct EnterTileSelect;EnterTileSelect338,11399
pub struct EnterTileSelect;EnterTileSelect293,9266 impl Cmd for EnterTileSelect {EnterTileSelect339,11427
impl Cmd for EnterTileSelect {EnterTileSelect294,9294 fn name(&self) -> &'static str {name340,11458
fn name(&self) -> &'static str {name295,9325 fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute343,11529
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute298,9396
src/command/keymap.rs,5088 src/command/keymap.rs,5093
fn format_key_label(code: &KeyCode) -> String {format_key_label13,301 fn format_key_label(code: &KeyCode) -> String {format_key_label13,301
pub enum KeyPattern {KeyPattern38,1303 pub enum KeyPattern {KeyPattern38,1303
Key(KeyCode, KeyModifiers),Key40,1359 Key(KeyCode, KeyModifiers),Key40,1359
@ -1904,29 +1908,29 @@ impl KeymapSet {KeymapSet289,9804
pub fn dispatch(dispatch306,10224 pub fn dispatch(dispatch306,10224
pub fn dispatch_transient(dispatch_transient318,10642 pub fn dispatch_transient(dispatch_transient318,10642
pub fn default_keymaps() -> Self {default_keymaps329,10948 pub fn default_keymaps() -> Self {default_keymaps329,10948
mod tests {tests930,33396 mod tests {tests978,34976
fn lookup_exact_match() {lookup_exact_match936,33587 fn lookup_exact_match() {lookup_exact_match984,35167
fn lookup_exact_with_ctrl() {lookup_exact_with_ctrl944,33877 fn lookup_exact_with_ctrl() {lookup_exact_with_ctrl992,35457
fn lookup_char_falls_back_to_none_mods() {lookup_char_falls_back_to_none_mods952,34175 fn lookup_char_falls_back_to_none_mods() {lookup_char_falls_back_to_none_mods1000,35755
fn lookup_non_char_does_not_fall_back_to_none_mods() {lookup_non_char_does_not_fall_back_to_none_mods962,34633 fn lookup_non_char_does_not_fall_back_to_none_mods() {lookup_non_char_does_not_fall_back_to_none_mods1010,36213
fn lookup_char_falls_to_any_char() {lookup_char_falls_to_any_char972,35046 fn lookup_char_falls_to_any_char() {lookup_char_falls_to_any_char1020,36626
fn lookup_non_char_skips_any_char() {lookup_non_char_skips_any_char986,35414 fn lookup_non_char_skips_any_char() {lookup_non_char_skips_any_char1034,36994
fn lookup_any_matches_everything() {lookup_any_matches_everything995,35722 fn lookup_any_matches_everything() {lookup_any_matches_everything1043,37302
fn lookup_exact_takes_priority_over_any_char() {lookup_exact_takes_priority_over_any_char1009,36067 fn lookup_exact_takes_priority_over_any_char() {lookup_exact_takes_priority_over_any_char1057,37647
fn lookup_any_char_takes_priority_over_any() {lookup_any_char_takes_priority_over_any1024,36509 fn lookup_any_char_takes_priority_over_any() {lookup_any_char_takes_priority_over_any1072,38089
fn lookup_non_char_falls_to_any_not_any_char() {lookup_non_char_falls_to_any_not_any_char1039,36921 fn lookup_non_char_falls_to_any_not_any_char() {lookup_non_char_falls_to_any_not_any_char1087,38501
fn lookup_ctrl_char_with_only_none_binding_falls_through() {lookup_ctrl_char_with_only_none_binding_falls_through1054,37330 fn lookup_ctrl_char_with_only_none_binding_falls_through() {lookup_ctrl_char_with_only_none_binding_falls_through1102,38910
fn mode_key_normal_no_search() {mode_key_normal_no_search1065,37836 fn mode_key_normal_no_search() {mode_key_normal_no_search1113,39416
fn mode_key_normal_with_search_overrides() {mode_key_normal_with_search_overrides1071,38005 fn mode_key_normal_with_search_overrides() {mode_key_normal_with_search_overrides1119,39585
fn mode_key_help() {mode_key_help1077,38189 fn mode_key_help() {mode_key_help1125,39769
fn mode_key_quit_returns_none() {mode_key_quit_returns_none1083,38342 fn mode_key_quit_returns_none() {mode_key_quit_returns_none1131,39922
fn lookup_returns_prefix_binding() {lookup_returns_prefix_binding1091,38669 fn lookup_returns_prefix_binding() {lookup_returns_prefix_binding1139,40249
fn lookup_returns_sequence_binding() {lookup_returns_sequence_binding1100,39000 fn lookup_returns_sequence_binding() {lookup_returns_sequence_binding1148,40580
fn default_keymaps_has_all_modes() {default_keymaps_has_all_modes1114,39551 fn default_keymaps_has_all_modes() {default_keymaps_has_all_modes1162,41131
fn normal_mode_has_basic_movement() {normal_mode_has_basic_movement1144,40436 fn normal_mode_has_basic_movement() {normal_mode_has_basic_movement1192,42016
fn editing_mode_has_any_char_and_esc() {editing_mode_has_any_char_and_esc1168,41220 fn editing_mode_has_any_char_and_esc() {editing_mode_has_any_char_and_esc1216,42800
fn search_mode_has_any_char_and_esc() {search_mode_has_any_char_and_esc1182,41696 fn search_mode_has_any_char_and_esc() {search_mode_has_any_char_and_esc1230,43276
fn import_wizard_has_any_catchall() {import_wizard_has_any_catchall1194,42090 fn import_wizard_has_any_catchall() {import_wizard_has_any_catchall1242,43670
src/command/mod.rs,80 src/command/mod.rs,80
pub mod cmd;cmd8,323 pub mod cmd;cmd8,323

9170
roadmap.org Normal file

File diff suppressed because it is too large Load Diff

179
scripts/gen_roadmap.py Executable file
View File

@ -0,0 +1,179 @@
#!/usr/bin/env python3
"""Generate roadmap.org from bd.
Inverted-tree layout: each tree is rooted at an issue that nothing else
(among the open set) depends on — the "epic" or standalone goal. Its
children are the things blocking it, recursively. Diamonds in the DAG
are broken into a tree by duplicating shared deps under each parent so
emacs `[/]` cookies count whole subtrees.
Usage: python3 scripts/gen_roadmap.py [output-path]
(default output: roadmap.org at the repo root)
"""
import json
import re
import subprocess
import sys
from collections import defaultdict
from pathlib import Path
def bd_json(*args):
"""Run `bd <args>` and parse JSON from stdout."""
cmd = ['bd', *args, '--json']
res = subprocess.run(cmd, check=True, capture_output=True, text=True)
return json.loads(res.stdout)
def main():
repo = Path(__file__).resolve().parent.parent
out_path = Path(sys.argv[1]) if len(sys.argv) > 1 else repo / 'roadmap.org'
issues = bd_json('list')
by_id = {i['id']: i for i in issues}
# `bd list --all` includes closed issues — used only to resolve titles
# of already-done dep targets so we can label them in :DONE_DEPS:.
all_issues = bd_json('list', '--all')
all_by_id = {i['id']: i for i in all_issues}
# --- Build dep graph (open-issue subgraph only) ----------------------
# child depends_on parent; in our inverted tree, parent (the goal/epic)
# sits ABOVE its deps (children).
tree_children = defaultdict(set)
tree_parents = defaultdict(set)
for i in issues:
iid = i['id']
for d in (i.get('dependencies') or []):
dep = d['depends_on_id']
if dep in by_id and iid in by_id:
tree_children[iid].add(dep)
tree_parents[dep].add(iid)
def order_key(iid):
i = by_id[iid]
return (
0 if i['status'] == 'in_progress' else 1,
i['priority'],
iid,
)
roots = sorted(
[iid for iid in by_id if not tree_parents.get(iid)],
key=order_key,
)
# --- Helpers ---------------------------------------------------------
status_kw = {
'open': 'TODO',
'in_progress': 'DOING',
'closed': 'DONE',
'blocked': 'WAIT',
'deferred': 'WAIT',
}
def tag(s):
return re.sub(r'[^A-Za-z0-9_@#%]', '_', s)
def headline_tags(i, kind):
tags = [kind, f"P{i['priority']}", tag(i['issue_type'])]
if i.get('assignee'):
tags.append('@' + tag(i['assignee']))
return ':' + ':'.join(tags) + ':'
def fmt_date(s):
return s.replace('T', ' ').rstrip('Z') if s else ''
def kind_of(iid):
return 'epic' if tree_children.get(iid) else 'standalone'
out = []
out.append('#+TITLE: Improvise Roadmap')
out.append('#+AUTHOR: Edward Langley')
out.append('#+TODO: TODO DOING WAIT | DONE')
out.append('#+STARTUP: overview')
out.append('#+TAGS: epic standalone P0 P1 P2 P3 P4 task feature bug')
out.append('#+PROPERTY: COOKIE_DATA todo recursive')
out.append('')
out.append(
f'Generated from ~bd list --json~. {len(issues)} open issues '
f'organised as {len(roots)} inverted dep-trees: each root is a goal '
'that nothing else depends on; its children are the deps blocking '
'it. Diamonds in the DAG are duplicated so each tree stands alone.'
)
out.append('')
def emit(iid, depth, path):
i = by_id[iid]
kind = kind_of(iid)
kw = status_kw.get(i['status'], 'TODO')
title = i['title'].replace('[', '(').replace(']', ')')
stars = '*' * depth
cookie = ' [/]' if tree_children.get(iid) else ''
head = f'{stars} {kw} {title} ({kind}){cookie}'
pad = max(1, 95 - len(head))
out.append(f'{head}{" " * pad}{headline_tags(i, kind)}')
indent = ' ' * (depth - 1) + ' '
out.append(f'{indent}:PROPERTIES:')
out.append(f'{indent}:ID: {iid}')
out.append(f'{indent}:TYPE: {i["issue_type"]}')
out.append(f'{indent}:PRIORITY: P{i["priority"]}')
out.append(f'{indent}:STATUS: {i["status"]}')
if i.get('assignee'):
out.append(f'{indent}:ASSIGNEE: {i["assignee"]}')
if i.get('owner'):
out.append(f'{indent}:OWNER: {i["owner"]}')
if i.get('created_by'):
out.append(f'{indent}:CREATED_BY: {i["created_by"]}')
out.append(f'{indent}:CREATED: {fmt_date(i["created_at"])}')
out.append(f'{indent}:UPDATED: {fmt_date(i["updated_at"])}')
if i.get('comment_count'):
out.append(f'{indent}:COMMENTS: {i["comment_count"]}')
out.append(f'{indent}:KIND: {kind}')
closed_deps = sorted(
d['depends_on_id']
for d in (i.get('dependencies') or [])
if (ref := all_by_id.get(d['depends_on_id'])) and ref['status'] == 'closed'
)
if closed_deps:
out.append(f'{indent}:DONE_DEPS: {", ".join(closed_deps)}')
if iid in path:
out.append(f'{indent}:CYCLE: yes — descendants pruned at this node')
out.append(f'{indent}:END:')
if iid in path:
return # cycle guard
details_stars = '*' * (depth + 1)
section_stars = '*' * (depth + 2)
section_indent = ' ' * (depth + 1) + ' '
if any(i.get(k) for k in ('description', 'design', 'acceptance_criteria', 'notes')):
out.append(f'{details_stars} Details')
for label, key in [
('Description', 'description'),
('Design', 'design'),
('Acceptance Criteria', 'acceptance_criteria'),
('Notes', 'notes'),
]:
v = i.get(key)
if not v:
continue
out.append(f'{section_stars} {label}')
for line in v.rstrip('\n').splitlines():
out.append(f'{section_indent}{line}' if line else '')
new_path = path | {iid}
for c in sorted(tree_children.get(iid, set()), key=order_key):
out.append('')
emit(c, depth + 1, new_path)
for r in roots:
emit(r, 1, frozenset())
out.append('')
out_path.write_text('\n'.join(out))
print(f'Wrote {out_path} ({len(out)} lines, {len(roots)} roots, {len(by_id)} issues)')
if __name__ == '__main__':
main()

View File

@ -118,6 +118,35 @@ mod tests {
assert!(effects.is_empty()); assert!(effects.is_empty());
} }
/// `CommitAndAdvance` must thread its `edit_mode` through to the
/// trailing `EnterEditAtCursor` effect so the post-commit re-edit lands
/// in the mode the keymap requested. The command never reads ctx.mode.
#[test]
fn commit_and_advance_threads_edit_mode_to_enter_edit_at_cursor() {
let m = two_cat_model();
let layout = make_layout(&m);
let reg = make_registry();
let mut bufs = HashMap::new();
bufs.insert("edit".to_string(), "42".to_string());
let mut ctx = make_ctx(&m, &layout, &reg);
ctx.buffers = &bufs;
// ctx.mode stays Normal — the command must not look at it.
let key = ctx.cell_key().unwrap();
let cmd = CommitAndAdvance {
key,
value: "42".to_string(),
advance: super::AdvanceDir::Down,
cursor: super::CursorState::from_ctx(&ctx),
edit_mode: AppMode::records_editing(),
};
let effects = cmd.execute(&ctx);
let dbg = effects_debug(&effects);
assert!(
dbg.contains("EnterEditAtCursor") && dbg.contains("RecordsEditing"),
"Expected trailing EnterEditAtCursor with RecordsEditing target, got: {dbg}"
);
}
#[test] #[test]
fn commit_export_produces_export_and_normal_mode() { fn commit_export_produces_export_and_normal_mode() {
let m = two_cat_model(); let m = two_cat_model();
@ -234,12 +263,18 @@ pub enum AdvanceDir {
/// Commit a cell edit, advance the cursor, and re-enter edit mode. /// Commit a cell edit, advance the cursor, and re-enter edit mode.
/// Subsumes the old `CommitCellEdit` (Down) and `CommitAndAdvanceRight` (Right). /// Subsumes the old `CommitCellEdit` (Down) and `CommitAndAdvanceRight` (Right).
///
/// `edit_mode` is the editing mode to re-enter after advancing. The keymap
/// binding supplies this — the editing-mode keymap passes `editing` and the
/// records-editing keymap passes `records-editing`. The command itself
/// never inspects `ctx.mode`.
#[derive(Debug)] #[derive(Debug)]
pub struct CommitAndAdvance { pub struct CommitAndAdvance {
pub key: CellKey, pub key: CellKey,
pub value: String, pub value: String,
pub advance: AdvanceDir, pub advance: AdvanceDir,
pub cursor: CursorState, pub cursor: CursorState,
pub edit_mode: AppMode,
} }
impl Cmd for CommitAndAdvance { impl Cmd for CommitAndAdvance {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
@ -287,7 +322,9 @@ impl Cmd for CommitAndAdvance {
} }
} }
} }
effects.push(Box::new(effect::EnterEditAtCursor)); effects.push(Box::new(effect::EnterEditAtCursor {
target_mode: self.edit_mode.clone(),
}));
effects effects
} }
} }

View File

@ -10,21 +10,6 @@ mod tests {
use crate::command::cmd::test_helpers::*; use crate::command::cmd::test_helpers::*;
use crate::workbook::Workbook; use crate::workbook::Workbook;
#[test]
fn enter_edit_mode_produces_editing_mode() {
let m = two_cat_model();
let layout = make_layout(&m);
let reg = make_registry();
let ctx = make_ctx(&m, &layout, &reg);
let cmd = EnterEditMode {
initial_value: String::new(),
};
let effects = cmd.execute(&ctx);
assert_eq!(effects.len(), 2);
let dbg = format!("{:?}", effects[1]);
assert!(dbg.contains("Editing"), "Expected Editing mode, got: {dbg}");
}
#[test] #[test]
fn enter_tile_select_with_categories() { fn enter_tile_select_with_categories() {
let m = two_cat_model(); let m = two_cat_model();
@ -98,11 +83,80 @@ mod tests {
let layout = make_layout(&m); let layout = make_layout(&m);
let reg = make_registry(); let reg = make_registry();
let ctx = make_ctx(&m, &layout, &reg); let ctx = make_ctx(&m, &layout, &reg);
let effects = EditOrDrill.execute(&ctx); let effects = EditOrDrill {
edit_mode: AppMode::editing(),
}
.execute(&ctx);
assert_eq!(effects.len(), 2);
let dbg = effects_debug(&effects); let dbg = effects_debug(&effects);
assert!(dbg.contains("Editing"), "Expected Editing mode, got: {dbg}"); assert!(dbg.contains("Editing"), "Expected Editing mode, got: {dbg}");
} }
/// EditOrDrill must trust its `edit_mode` parameter rather than checking
/// `ctx.mode` — the records-normal keymap supplies `records-editing`,
/// but the command itself never inspects the runtime mode. This is the
/// parallel of the (deleted) `enter_edit_mode_produces_editing_mode`
/// test for the records branch.
#[test]
fn edit_or_drill_passes_records_editing_mode_through() {
let m = two_cat_model();
let layout = make_layout(&m);
let reg = make_registry();
// Note: ctx.mode is still Normal here — the command must not look at it.
let ctx = make_ctx(&m, &layout, &reg);
let effects = EditOrDrill {
edit_mode: AppMode::records_editing(),
}
.execute(&ctx);
assert_eq!(effects.len(), 2);
let dbg = effects_debug(&effects);
assert!(
dbg.contains("RecordsEditing"),
"Expected RecordsEditing mode, got: {dbg}"
);
}
/// `EnterEditAtCursorCmd` must hand its `target_mode` straight through
/// to the `EnterEditAtCursor` effect — the keymap (records `o` sequence
/// or commit-and-advance) decides; the command never inspects ctx.
#[test]
fn enter_edit_at_cursor_cmd_passes_target_mode_to_effect() {
let m = two_cat_model();
let layout = make_layout(&m);
let reg = make_registry();
let ctx = make_ctx(&m, &layout, &reg);
let effects = EnterEditAtCursorCmd {
target_mode: AppMode::records_editing(),
}
.execute(&ctx);
assert_eq!(effects.len(), 1);
let dbg = format!("{:?}", effects[0]);
assert!(
dbg.contains("RecordsEditing"),
"Expected RecordsEditing target_mode, got: {dbg}"
);
}
/// The edit branch pre-fills the `edit` buffer with the cell's current
/// display value so the user can modify rather than retype.
#[test]
fn edit_or_drill_pre_fills_edit_buffer_with_display_value() {
let m = two_cat_model();
let layout = make_layout(&m);
let reg = make_registry();
let mut ctx = make_ctx(&m, &layout, &reg);
ctx.display_value = "42".to_string();
let effects = EditOrDrill {
edit_mode: AppMode::editing(),
}
.execute(&ctx);
let dbg = effects_debug(&effects);
assert!(
dbg.contains("SetBuffer") && dbg.contains("\"edit\"") && dbg.contains("\"42\""),
"Expected SetBuffer(\"edit\", \"42\"), got: {dbg}"
);
}
#[test] #[test]
fn enter_search_mode_sets_flag_and_clears_query() { fn enter_search_mode_sets_flag_and_clears_query() {
let m = two_cat_model(); let m = two_cat_model();
@ -188,36 +242,18 @@ impl Cmd for SaveAndQuit {
// ── Editing entry ─────────────────────────────────────────────────────── // ── Editing entry ───────────────────────────────────────────────────────
/// Enter editing mode with an initial buffer value.
#[derive(Debug)]
pub struct EnterEditMode {
pub initial_value: String,
}
impl Cmd for EnterEditMode {
fn name(&self) -> &'static str {
"enter-edit-mode"
}
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
let edit_mode = if ctx.mode.is_records() {
AppMode::records_editing()
} else {
AppMode::editing()
};
vec![
Box::new(effect::SetBuffer {
name: "edit".to_string(),
value: self.initial_value.clone(),
}),
effect::change_mode(edit_mode),
]
}
}
/// Smart dispatch for i/a: if the cursor is on an aggregated pivot cell /// Smart dispatch for i/a: if the cursor is on an aggregated pivot cell
/// (categories on `Axis::None`, no records mode), drill into it instead of /// (categories on `Axis::None` and the cell is not a synthetic records-mode
/// editing. Otherwise enter edit mode with the current displayed value. /// row), drill into it instead of editing. Otherwise pre-fill the edit
/// buffer with the displayed cell value and enter `edit_mode`.
///
/// `edit_mode` is supplied by the keymap binding — the command itself is
/// mode-agnostic, so the records-normal keymap passes `records-editing`
/// while the normal keymap passes `editing`.
#[derive(Debug)] #[derive(Debug)]
pub struct EditOrDrill; pub struct EditOrDrill {
pub edit_mode: AppMode,
}
impl Cmd for EditOrDrill { impl Cmd for EditOrDrill {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"edit-or-drill" "edit-or-drill"
@ -232,7 +268,8 @@ impl Cmd for EditOrDrill {
.map(|cat| cat.kind.is_regular()) .map(|cat| cat.kind.is_regular())
.unwrap_or(false) .unwrap_or(false)
}); });
// In records mode (synthetic key), always edit directly — no drilling. // Synthetic records-mode cells are never aggregated — edit directly.
// (This is a layout property, not a mode flag.)
let is_synthetic = ctx.synthetic_record_at_cursor().is_some(); let is_synthetic = ctx.synthetic_record_at_cursor().is_some();
let is_aggregated = !is_synthetic && regular_none; let is_aggregated = !is_synthetic && regular_none;
if is_aggregated { if is_aggregated {
@ -241,23 +278,31 @@ impl Cmd for EditOrDrill {
}; };
return DrillIntoCell { key }.execute(ctx); return DrillIntoCell { key }.execute(ctx);
} }
EnterEditMode { vec![
initial_value: ctx.display_value.clone(), Box::new(effect::SetBuffer {
} name: "edit".to_string(),
.execute(ctx) value: ctx.display_value.clone(),
}),
effect::change_mode(self.edit_mode.clone()),
]
} }
} }
/// Thin command wrapper around the `EnterEditAtCursor` effect so it can /// Thin command wrapper around the `EnterEditAtCursor` effect so it can
/// participate in `Binding::Sequence`. /// participate in `Binding::Sequence`. `target_mode` is supplied as the
/// command argument by the keymap binding.
#[derive(Debug)] #[derive(Debug)]
pub struct EnterEditAtCursorCmd; pub struct EnterEditAtCursorCmd {
pub target_mode: AppMode,
}
impl Cmd for EnterEditAtCursorCmd { impl Cmd for EnterEditAtCursorCmd {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"enter-edit-at-cursor" "enter-edit-at-cursor"
} }
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
vec![Box::new(effect::EnterEditAtCursor)] vec![Box::new(effect::EnterEditAtCursor {
target_mode: self.target_mode.clone(),
})]
} }
} }

View File

@ -2,6 +2,27 @@ use crate::model::cell::CellKey;
use crate::ui::app::AppMode; use crate::ui::app::AppMode;
use crate::ui::effect::Panel; use crate::ui::effect::Panel;
/// Decode a mode-name string (as supplied by `enter-mode`/`edit-or-drill`
/// keymap bindings) into an `AppMode`.
fn parse_mode_name(s: &str) -> Result<AppMode, String> {
match s {
"normal" => Ok(AppMode::Normal),
"help" => Ok(AppMode::Help),
"formula-panel" => Ok(AppMode::FormulaPanel),
"category-panel" => Ok(AppMode::CategoryPanel),
"view-panel" => Ok(AppMode::ViewPanel),
"tile-select" => Ok(AppMode::TileSelect),
"command" => Ok(AppMode::command_mode()),
"category-add" => Ok(AppMode::category_add()),
"editing" => Ok(AppMode::editing()),
"records-normal" => Ok(AppMode::RecordsNormal),
"records-editing" => Ok(AppMode::records_editing()),
"formula-edit" => Ok(AppMode::formula_edit()),
"export-prompt" => Ok(AppMode::export_prompt()),
other => Err(format!("Unknown mode: {other}")),
}
}
use super::cell::*; use super::cell::*;
use super::commit::*; use super::commit::*;
use super::core::*; use super::core::*;
@ -266,22 +287,16 @@ pub fn default_registry() -> CmdRegistry {
r.register_nullary(|| Box::new(SaveAndQuit)); r.register_nullary(|| Box::new(SaveAndQuit));
r.register_nullary(|| Box::new(SaveCmd)); r.register_nullary(|| Box::new(SaveCmd));
r.register_nullary(|| Box::new(EnterSearchMode)); r.register_nullary(|| Box::new(EnterSearchMode));
r.register( r.register_pure(&NamedCmd("edit-or-drill"), |args| {
&EnterEditMode { require_args("edit-or-drill", args, 1)?;
initial_value: String::new(), let edit_mode = parse_mode_name(&args[0])?;
}, Ok(Box::new(EditOrDrill { edit_mode }))
|args| { });
let val = args.first().cloned().unwrap_or_default(); r.register_pure(&NamedCmd("enter-edit-at-cursor"), |args| {
Ok(Box::new(EnterEditMode { initial_value: val })) require_args("enter-edit-at-cursor", args, 1)?;
}, let target_mode = parse_mode_name(&args[0])?;
|_args, ctx| { Ok(Box::new(EnterEditAtCursorCmd { target_mode }))
Ok(Box::new(EnterEditMode { });
initial_value: ctx.display_value.clone(),
}))
},
);
r.register_nullary(|| Box::new(EditOrDrill));
r.register_nullary(|| Box::new(EnterEditAtCursorCmd));
r.register_nullary(|| Box::new(EnterExportPrompt)); r.register_nullary(|| Box::new(EnterExportPrompt));
r.register_nullary(|| Box::new(EnterFormulaEdit)); r.register_nullary(|| Box::new(EnterFormulaEdit));
r.register_nullary(|| Box::new(EnterTileSelect)); r.register_nullary(|| Box::new(EnterTileSelect));
@ -310,23 +325,7 @@ pub fn default_registry() -> CmdRegistry {
); );
r.register_pure(&NamedCmd("enter-mode"), |args| { r.register_pure(&NamedCmd("enter-mode"), |args| {
require_args("enter-mode", args, 1)?; require_args("enter-mode", args, 1)?;
let mode = match args[0].as_str() { Ok(Box::new(EnterMode(parse_mode_name(&args[0])?)))
"normal" => AppMode::Normal,
"help" => AppMode::Help,
"formula-panel" => AppMode::FormulaPanel,
"category-panel" => AppMode::CategoryPanel,
"view-panel" => AppMode::ViewPanel,
"tile-select" => AppMode::TileSelect,
"command" => AppMode::command_mode(),
"category-add" => AppMode::category_add(),
"editing" => AppMode::editing(),
"records-normal" => AppMode::RecordsNormal,
"records-editing" => AppMode::records_editing(),
"formula-edit" => AppMode::formula_edit(),
"export-prompt" => AppMode::export_prompt(),
other => return Err(format!("Unknown mode: {other}")),
};
Ok(Box::new(EnterMode(mode)))
}); });
// ── Search ─────────────────────────────────────────────────────────── // ── Search ───────────────────────────────────────────────────────────
@ -522,25 +521,33 @@ pub fn default_registry() -> CmdRegistry {
r.register_nullary(|| Box::new(CommandModeBackspace)); r.register_nullary(|| Box::new(CommandModeBackspace));
// ── Commit ─────────────────────────────────────────────────────────── // ── Commit ───────────────────────────────────────────────────────────
// commit-cell-edit / commit-and-advance-right take a mode-name arg
// (e.g. "editing" or "records-editing") as args[0]. The keymap supplies
// it; the command never inspects ctx.mode.
r.register( r.register(
&CommitAndAdvance { &CommitAndAdvance {
key: CellKey::new(vec![]), key: CellKey::new(vec![]),
value: String::new(), value: String::new(),
advance: AdvanceDir::Down, advance: AdvanceDir::Down,
cursor: CursorState::default(), cursor: CursorState::default(),
edit_mode: AppMode::editing(),
}, },
|args| { |args| {
if args.len() < 2 { if args.len() < 3 {
return Err("commit-cell-edit requires a value and coords".into()); return Err("commit-cell-edit requires a mode, value, and coords".into());
} }
let edit_mode = parse_mode_name(&args[0])?;
Ok(Box::new(CommitAndAdvance { Ok(Box::new(CommitAndAdvance {
key: parse_cell_key_from_args(&args[1..]), key: parse_cell_key_from_args(&args[2..]),
value: args[0].clone(), value: args[1].clone(),
advance: AdvanceDir::Down, advance: AdvanceDir::Down,
cursor: CursorState::default(), cursor: CursorState::default(),
edit_mode,
})) }))
}, },
|_args, ctx| { |args, ctx| {
require_args("commit-cell-edit", args, 1)?;
let edit_mode = parse_mode_name(&args[0])?;
let value = read_buffer(ctx, "edit"); let value = read_buffer(ctx, "edit");
let key = ctx.cell_key().clone().ok_or("no cell at cursor")?; let key = ctx.cell_key().clone().ok_or("no cell at cursor")?;
Ok(Box::new(CommitAndAdvance { Ok(Box::new(CommitAndAdvance {
@ -548,6 +555,7 @@ pub fn default_registry() -> CmdRegistry {
value, value,
advance: AdvanceDir::Down, advance: AdvanceDir::Down,
cursor: CursorState::from_ctx(ctx), cursor: CursorState::from_ctx(ctx),
edit_mode,
})) }))
}, },
); );
@ -557,9 +565,12 @@ pub fn default_registry() -> CmdRegistry {
value: String::new(), value: String::new(),
advance: AdvanceDir::Right, advance: AdvanceDir::Right,
cursor: CursorState::default(), cursor: CursorState::default(),
edit_mode: AppMode::editing(),
}, },
|_| Err("commit-and-advance-right requires context".into()), |_| Err("commit-and-advance-right requires context".into()),
|_args, ctx| { |args, ctx| {
require_args("commit-and-advance-right", args, 1)?;
let edit_mode = parse_mode_name(&args[0])?;
let value = read_buffer(ctx, "edit"); let value = read_buffer(ctx, "edit");
let key = ctx.cell_key().clone().ok_or("no cell at cursor")?; let key = ctx.cell_key().clone().ok_or("no cell at cursor")?;
Ok(Box::new(CommitAndAdvance { Ok(Box::new(CommitAndAdvance {
@ -567,6 +578,7 @@ pub fn default_registry() -> CmdRegistry {
value, value,
advance: AdvanceDir::Right, advance: AdvanceDir::Right,
cursor: CursorState::from_ctx(ctx), cursor: CursorState::from_ctx(ctx),
edit_mode,
})) }))
}, },
); );

View File

@ -431,9 +431,21 @@ impl KeymapSet {
); );
normal.bind(KeyCode::Tab, none, "cycle-panel-focus"); normal.bind(KeyCode::Tab, none, "cycle-panel-focus");
// Editing entry — i/a drill into aggregated cells, else edit // Editing entry — i/a drill into aggregated cells, else edit.
normal.bind(KeyCode::Char('i'), none, "edit-or-drill"); // The mode arg controls which editing mode is entered; records-normal
normal.bind(KeyCode::Char('a'), none, "edit-or-drill"); // overrides these to "records-editing" via its own bindings.
normal.bind_args(
KeyCode::Char('i'),
none,
"edit-or-drill",
vec!["editing".into()],
);
normal.bind_args(
KeyCode::Char('a'),
none,
"edit-or-drill",
vec!["editing".into()],
);
normal.bind(KeyCode::Enter, none, "enter-advance"); normal.bind(KeyCode::Enter, none, "enter-advance");
normal.bind(KeyCode::Char('e'), ctrl, "enter-export-prompt"); normal.bind(KeyCode::Char('e'), ctrl, "enter-export-prompt");
@ -488,10 +500,27 @@ impl KeymapSet {
// ── Records normal mode (inherits from normal) ──────────────────── // ── Records normal mode (inherits from normal) ────────────────────
let mut rn = Keymap::with_parent(normal); let mut rn = Keymap::with_parent(normal);
// Override i/a so the edit branch produces records-editing mode
// instead of inheriting the normal-mode "editing" arg.
rn.bind_args(
KeyCode::Char('i'),
none,
"edit-or-drill",
vec!["records-editing".into()],
);
rn.bind_args(
KeyCode::Char('a'),
none,
"edit-or-drill",
vec!["records-editing".into()],
);
rn.bind_seq( rn.bind_seq(
KeyCode::Char('o'), KeyCode::Char('o'),
none, none,
vec![("add-record-row", vec![]), ("enter-edit-at-cursor", vec![])], vec![
("add-record-row", vec![]),
("enter-edit-at-cursor", vec!["records-editing".into()]),
],
); );
set.insert(ModeKey::RecordsNormal, Arc::new(rn)); set.insert(ModeKey::RecordsNormal, Arc::new(rn));
@ -736,6 +765,8 @@ impl KeymapSet {
set.insert(ModeKey::TileSelect, Arc::new(ts)); set.insert(ModeKey::TileSelect, Arc::new(ts));
// ── Editing mode ───────────────────────────────────────────────── // ── Editing mode ─────────────────────────────────────────────────
// commit-* takes the target edit-mode arg so the command stays
// mode-agnostic; records-editing overrides Enter/Tab below.
let mut ed = Keymap::new(); let mut ed = Keymap::new();
ed.bind_seq( ed.bind_seq(
KeyCode::Esc, KeyCode::Esc,
@ -749,7 +780,7 @@ impl KeymapSet {
KeyCode::Enter, KeyCode::Enter,
none, none,
vec![ vec![
("commit-cell-edit", vec![]), ("commit-cell-edit", vec!["editing".into()]),
("clear-buffer", vec!["edit".into()]), ("clear-buffer", vec!["edit".into()]),
], ],
); );
@ -757,7 +788,7 @@ impl KeymapSet {
KeyCode::Tab, KeyCode::Tab,
none, none,
vec![ vec![
("commit-and-advance-right", vec![]), ("commit-and-advance-right", vec!["editing".into()]),
("clear-buffer", vec!["edit".into()]), ("clear-buffer", vec!["edit".into()]),
], ],
); );
@ -767,6 +798,7 @@ impl KeymapSet {
set.insert(ModeKey::Editing, ed.clone()); set.insert(ModeKey::Editing, ed.clone());
// ── Records editing mode (inherits from editing) ────────────────── // ── Records editing mode (inherits from editing) ──────────────────
// Override Enter/Tab so the post-commit re-enter targets records-editing.
let mut re = Keymap::with_parent(ed); let mut re = Keymap::with_parent(ed);
re.bind_seq( re.bind_seq(
KeyCode::Esc, KeyCode::Esc,
@ -776,6 +808,22 @@ impl KeymapSet {
("enter-mode", vec!["records-normal".into()]), ("enter-mode", vec!["records-normal".into()]),
], ],
); );
re.bind_seq(
KeyCode::Enter,
none,
vec![
("commit-cell-edit", vec!["records-editing".into()]),
("clear-buffer", vec!["edit".into()]),
],
);
re.bind_seq(
KeyCode::Tab,
none,
vec![
("commit-and-advance-right", vec!["records-editing".into()]),
("clear-buffer", vec!["edit".into()]),
],
);
set.insert(ModeKey::RecordsEditing, Arc::new(re)); set.insert(ModeKey::RecordsEditing, Arc::new(re));
// ── Formula edit ───────────────────────────────────────────────── // ── Formula edit ─────────────────────────────────────────────────

View File

@ -124,10 +124,19 @@ impl Effect for RemoveFormula {
/// Re-enter edit mode by reading the cell value at the current cursor. /// Re-enter edit mode by reading the cell value at the current cursor.
/// Used after commit+advance to continue data entry. /// Used after commit+advance to continue data entry.
///
/// `target_mode` is supplied by the caller (keymap binding via
/// `EnterEditAtCursorCmd`, or `CommitAndAdvance` from its own `edit_mode`
/// field). The effect itself never inspects `app.mode` — the mode is decided
/// statically by whoever invoked us.
#[derive(Debug)] #[derive(Debug)]
pub struct EnterEditAtCursor; pub struct EnterEditAtCursor {
pub target_mode: AppMode,
}
impl Effect for EnterEditAtCursor { impl Effect for EnterEditAtCursor {
fn apply(&self, app: &mut App) { fn apply(&self, app: &mut App) {
// Layout may be stale relative to prior effects in this batch (e.g.
// AddRecordRow added a row); rebuild before reading display_value.
app.rebuild_layout(); app.rebuild_layout();
let ctx = app.cmd_context( let ctx = app.cmd_context(
crossterm::event::KeyCode::Null, crossterm::event::KeyCode::Null,
@ -136,11 +145,7 @@ impl Effect for EnterEditAtCursor {
let value = ctx.display_value.clone(); let value = ctx.display_value.clone();
drop(ctx); drop(ctx);
app.buffers.insert("edit".to_string(), value); app.buffers.insert("edit".to_string(), value);
app.mode = if app.mode.is_records() { app.mode = self.target_mode.clone();
AppMode::records_editing()
} else {
AppMode::editing()
};
} }
} }
@ -1288,6 +1293,41 @@ mod tests {
assert_eq!(app.mode, AppMode::Help); assert_eq!(app.mode, AppMode::Help);
} }
/// `EnterEditAtCursor` must use its `target_mode` field, *not* whatever
/// `app.mode` happens to be when applied. Previous implementation
/// branched on `app.mode.is_records()` — the parameterized version
/// trusts the caller (keymap or composing command).
#[test]
fn enter_edit_at_cursor_uses_target_mode_not_app_mode() {
let mut app = test_app();
// App starts in Normal mode — but caller has decided we want
// RecordsEditing (e.g. records-mode `o` sequence).
assert_eq!(app.mode, AppMode::Normal);
EnterEditAtCursor {
target_mode: AppMode::records_editing(),
}
.apply(&mut app);
assert!(
matches!(app.mode, AppMode::RecordsEditing { .. }),
"Expected RecordsEditing, got {:?}",
app.mode
);
// Same effect with editing target — should land in plain Editing
// even if app.mode was something else.
let mut app2 = test_app();
app2.mode = AppMode::RecordsNormal;
EnterEditAtCursor {
target_mode: AppMode::editing(),
}
.apply(&mut app2);
assert!(
matches!(app2.mode, AppMode::Editing { .. }),
"Expected Editing, got {:?}",
app2.mode
);
}
/// SetBuffer with empty value clears the buffer (used by clear-buffer command /// SetBuffer with empty value clears the buffer (used by clear-buffer command
/// in keymap sequences after commit). /// in keymap sequences after commit).
#[test] #[test]