mirror of
https://github.com/storytold/gridcraft.git
synced 2026-11-02 07:06:48 -06:00
formulas.recentlyUsed scanned the session journal for cell.set inputs. It missed formulas entered by range.* commands, lost them when the journal dropped its oldest entries (a large file open or a long script is enough), and started empty in every session. The Function Library's Recently Used menu and the Insert Function dialog list what it returns. - Prefs::recent_functions (a new engine module, recent.rs) holds the ten functions most recently entered in a formula, newest first. Session::execute updates it for every cell.* and range.* command it journals, from any source (the interface, the control channel, MCP or a script). It reads at most 2,000 of the formulas a command enters, starting from the last one; formulas longer than Excel's limit are skipped. formulas.recentlyUsed returns it. It never reads the journal back, so the journal's caps do not affect it. - The list is kept with the other engine preferences (prefs.json, the browser's storage on the web), so it survives a restart as Excel's does. It is read leniently: a malformed value loses only this list. A fresh install starts from common functions. The Insert Function dialog is titled "Insert Function", as in Excel (it said "Formula Builder"), and gains a working category list: Most Recently Used, All, then each category in Excel's order. A category may be given by label or by engine id, so a Function Library menu or the control channel (ui.dialog.set) opens it on one, and a new search switches to All. The dialog keeps its translated labels and its search in the interface language. Each row shows the function's name and its category in two columns; the category column starts after the widest name shown, so a long name in any font or formula language stays clear of it. A function counts as recently used once its formula is entered, not when OK is pressed. The unused "Formula Builder" translations are removed. Tests: engine unit tests for reading functions from formulas (strings, unknown names, over-long and malformed formulas), the formulas a command enters (newest first, within the budget), the ten-name list and the lenient read of prefs.json; tests/it/recently_used.rs for cell.set and range.setValues from the session, a failed command, a cleared journal and a save and reload of the preferences. UI tests for the menu's order and the dialog's title, categories, engine-id category, search and OK, and for the category column with a wide font.
59 lines
2.5 KiB
Rust
59 lines
2.5 KiB
Rust
//! Formulas › Recently Used: the functions most recently entered in a formula, from any command
|
||
//! that enters one, kept in the preferences rather than read back from the journal.
|
||
|
||
use gridcraft_engine::Session;
|
||
use serde_json::{Value as Json, json};
|
||
|
||
fn recent(s: &mut Session) -> Vec<String> {
|
||
let list = s.execute("formulas.recentlyUsed", json!({})).unwrap();
|
||
list.as_array().unwrap().iter().filter_map(Json::as_str).map(String::from).collect()
|
||
}
|
||
|
||
#[test]
|
||
fn formulas_entered_by_any_command_lead_the_list() {
|
||
let mut s = Session::new();
|
||
s.new_workbook();
|
||
s.execute("cell.set", json!({"cell": "A1", "input": "=NPV(0.1,1,2)"})).unwrap();
|
||
s.execute("cell.set", json!({"cell": "A2", "input": "=IRR({-10,6,6})+ROUND(1,0)"})).unwrap();
|
||
s.execute("range.setValues", json!({"range": "B1", "values": [["=FV(0.1,2,-1)", 4, "text"]]})).unwrap();
|
||
s.execute("cell.set", json!({"cell": "A3", "input": "PMT is just text"})).unwrap();
|
||
assert_eq!(&recent(&mut s)[..4], ["FV", "IRR", "ROUND", "NPV"]);
|
||
// A command that fails enters nothing.
|
||
assert!(s.execute("cell.set", json!({"cell": "not a cell", "input": "=DB(1,2,3,4)"})).is_err());
|
||
assert_ne!(recent(&mut s)[0], "DB");
|
||
}
|
||
|
||
#[test]
|
||
fn formulas_typed_with_a_plus_or_minus_count() {
|
||
let mut s = Session::new();
|
||
s.new_workbook();
|
||
s.execute("cell.set", json!({"cell": "A1", "input": "+ROUND(1.5,0)"})).unwrap();
|
||
s.execute("cell.set", json!({"cell": "A2", "input": "-SLN(100,10,5)"})).unwrap();
|
||
s.execute("cell.set", json!({"cell": "A3", "input": "-5"})).unwrap();
|
||
assert_eq!(&recent(&mut s)[..2], ["SLN", "ROUND"]);
|
||
}
|
||
|
||
#[test]
|
||
fn the_list_does_not_depend_on_the_journal() {
|
||
// The journal drops its oldest entries past its caps (and is public); the list stays.
|
||
let mut s = Session::new();
|
||
s.new_workbook();
|
||
s.execute("cell.set", json!({"cell": "A1", "input": "=NPV(0.1,1,2)"})).unwrap();
|
||
s.journal.clear();
|
||
assert_eq!(recent(&mut s)[0], "NPV");
|
||
s.execute("cell.set", json!({"cell": "A2", "input": "=SLN(100,10,5)"})).unwrap();
|
||
assert_eq!(&recent(&mut s)[..2], ["SLN", "NPV"]);
|
||
}
|
||
|
||
#[test]
|
||
fn the_list_is_kept_with_the_preferences() {
|
||
let mut s = Session::new();
|
||
s.new_workbook();
|
||
s.execute("cell.set", json!({"cell": "A1", "input": "=SYD(100,10,5,1)"})).unwrap();
|
||
let saved = serde_json::to_value(&s.prefs).unwrap();
|
||
let mut next = Session::new();
|
||
next.new_workbook();
|
||
next.prefs = serde_json::from_value(saved).unwrap();
|
||
assert_eq!(recent(&mut next)[0], "SYD");
|
||
}
|