refactor(command): improve formula commitment and buffer management

Refactor command execution and buffer management.

- `CommitFormula` now defaults to targeting `_Measure` .
- `CommitFormula` no longer automatically clears the buffer; buffer
  clearing is now handled by keymap sequences.
- Added `ClearBufferCmd` to the command registry.
- Updated `AddFormulaCmd` to support optional target category.
- Added `SetBuffer` effect to allow clearing buffers.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (unsloth/gemma-4-26B-A4B-it-GGUF:UD-Q5_K_XL)
This commit is contained in:
Edward Langley
2026-04-09 14:24:39 -07:00
parent c3fb8669c2
commit 7fea5f67ed
4 changed files with 62 additions and 29 deletions

View File

@ -34,8 +34,10 @@ mod tests {
);
}
/// Formulas always target _Measure by default, even when no regular
/// categories exist. _Measure is a virtual category that always exists.
#[test]
fn commit_formula_without_regular_categories_shows_status() {
fn commit_formula_without_regular_categories_targets_measure() {
let m = Model::new("Empty");
let layout = make_layout(&m);
let reg = make_registry();
@ -46,12 +48,12 @@ mod tests {
let effects = CommitFormula.execute(&ctx);
let dbg = effects_debug(&effects);
assert!(
!dbg.contains("AddFormula"),
"Should not add formula when only virtual categories exist, got: {dbg}"
dbg.contains("AddFormula"),
"Should add formula targeting _Measure, got: {dbg}"
);
assert!(
dbg.contains("Add at least one category first"),
"Expected status message, got: {dbg}"
dbg.contains("_Measure"),
"target_category should be _Measure, got: {dbg}"
);
}
@ -220,30 +222,23 @@ impl Cmd for CommitFormula {
}
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
let buf = ctx.buffers.get("formula").cloned().unwrap_or_default();
let first_cat = ctx
.model
.regular_category_names()
.into_iter()
.next()
.map(String::from);
let mut effects: Vec<Box<dyn Effect>> = Vec::new();
if let Some(cat) = first_cat {
effects.push(Box::new(effect::AddFormula {
raw: buf,
target_category: cat,
}));
effects.push(effect::mark_dirty());
effects.push(effect::set_status("Formula added"));
} else {
effects.push(effect::set_status("Add at least one category first."));
}
// Default formula target to _Measure (the virtual measure category).
// _Measure dynamically includes all formula targets.
effects.push(Box::new(effect::AddFormula {
raw: buf,
target_category: "_Measure".to_string(),
}));
effects.push(effect::mark_dirty());
effects.push(effect::set_status("Formula added"));
effects.push(effect::change_mode(AppMode::FormulaPanel));
effects
}
}
/// Shared helper: read a buffer, trim it, and if non-empty, produce add + dirty
/// + status + clear-buffer effects. If empty, return to CategoryPanel.
/// + status effects. If empty, return to CategoryPanel.
/// Buffer clearing is handled by the keymap (Enter → [commit, clear-buffer]).
fn commit_add_from_buffer(
ctx: &CmdContext,
buffer_name: &str,
@ -262,10 +257,6 @@ fn commit_add_from_buffer(
add,
effect::mark_dirty(),
effect::set_status(status_msg(&trimmed)),
Box::new(effect::SetBuffer {
name: buffer_name.to_string(),
value: String::new(),
}),
]
}

View File

@ -200,11 +200,35 @@ effect_cmd!(
effect_cmd!(
AddFormulaCmd,
"add-formula",
|args: &[String]| require_args("add-formula", args, 2),
|args: &[String]| {
if args.is_empty() || args.len() > 2 {
return Err(format!("add-formula requires 1-2 argument(s), got {}", args.len()));
}
Ok(())
},
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
// 1 arg: formula text (target_category defaults to _Measure)
// 2 args: target_category, formula text
let (cat, raw) = if args.len() == 2 {
(args[0].clone(), args[1].clone())
} else {
("_Measure".to_string(), args[0].clone())
};
vec![Box::new(effect::AddFormula {
target_category: args[0].clone(),
raw: args[1].clone(),
target_category: cat,
raw,
})]
}
);
effect_cmd!(
ClearBufferCmd,
"clear-buffer",
|args: &[String]| require_args("clear-buffer", args, 1),
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
vec![Box::new(effect::SetBuffer {
name: args[0].clone(),
value: String::new(),
})]
}
);

View File

@ -25,6 +25,7 @@ pub fn default_registry() -> CmdRegistry {
r.register_pure(&AddItemsCmd(vec![]), AddItemsCmd::parse);
r.register_pure(&AddItemInGroupCmd(vec![]), AddItemInGroupCmd::parse);
r.register_pure(&SetCellCmd(vec![]), SetCellCmd::parse);
r.register_pure(&ClearBufferCmd(vec![]), ClearBufferCmd::parse);
r.register(
&ClearCellCommand {
key: CellKey::new(vec![]),

View File

@ -1252,6 +1252,23 @@ mod tests {
assert_eq!(app.mode, AppMode::Help);
}
/// SetBuffer with empty value clears the buffer (used by clear-buffer command
/// in keymap sequences after commit).
#[test]
fn set_buffer_empty_clears() {
let mut app = test_app();
app.buffers.insert("formula".to_string(), "old text".to_string());
SetBuffer {
name: "formula".to_string(),
value: String::new(),
}
.apply(&mut app);
assert_eq!(
app.buffers.get("formula").map(|s| s.as_str()),
Some(""),
);
}
#[test]
fn set_status_effect() {
let mut app = test_app();