LRC-LIB-KEYWORDS: renaming a set alone keeps its keywords

`keyword.saveSet {name, replace}` without `keywords` took the current
set's keywords, so renaming a set that wasn't current gave it another
set's nine. The keywords default to the renamed set's own now (the
current nine only for a new set), and an empty `replace` is no rename.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nathaniel Ledford
2026-10-09 16:05:44 -04:00
co-authored by Claude Opus 5.5
parent 1f8447ba79
commit 6fd57cdcb1
2 changed files with 32 additions and 9 deletions
+22 -9
View File
@@ -145,7 +145,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Save Keyword Set",
[],
None,
"{name, keywords?: [up to 9, \"\" = an empty slot] (default: the current nine), replace?: the set it renames} — replaces a set of that name (or `replace`, in its place) and makes it current",
"{name, keywords?: [up to 9, \"\" = an empty slot] (default: the renamed set's, else the current nine), replace?: the set it renames} — replaces a set of that name (or `replace`, in its place) and makes it current",
always,
|s, p| {
let name = str_param(p, "name")
@@ -153,17 +153,30 @@ pub fn specs() -> Vec<CommandSpec> {
.filter(|n| !n.is_empty() && !n.eq_ignore_ascii_case(RECENT))
.ok_or_else(|| bad("keyword.saveSet", "missing or reserved `name`"))?
.to_string();
let keywords = if p.get("keywords").is_some() { slots(&strs(p, "keywords")) } else { current_keywords(s) };
let set = KeywordSet { name: name.clone(), keywords };
let same_name = |x: &KeywordSet, n: &str| x.name.to_lowercase() == n.to_lowercase();
match str_param(p, "replace").map(str::trim) {
// rename: in its place, onto a name no other set has
Some(old) => {
let at = s
.keyword_sets
// the set it renames, if any (an empty `replace` is none)
let renames = match str_param(p, "replace").map(str::trim).filter(|o| !o.is_empty()) {
Some(old) => Some(
s.keyword_sets
.iter()
.position(|x| same_name(x, old))
.ok_or_else(|| bad("keyword.saveSet", format!("no keyword set `{old}`")))?;
.ok_or_else(|| bad("keyword.saveSet", format!("no keyword set `{old}`")))?,
),
None => None,
};
// keywords not given: the renamed set's own, else the current nine
let keywords = if p.get("keywords").is_some() {
slots(&strs(p, "keywords"))
} else {
match renames.and_then(|at| s.keyword_sets.get(at)) {
Some(x) => x.keywords.clone(),
None => current_keywords(s),
}
};
let set = KeywordSet { name: name.clone(), keywords };
match renames {
// rename: in its place, onto a name no other set has
Some(at) => {
if s.keyword_sets.iter().enumerate().any(|(i, x)| i != at && same_name(x, &name)) {
return Err(bad("keyword.saveSet", format!("there is a keyword set “{name}” already")));
}
+10
View File
@@ -229,3 +229,13 @@ fn renaming_a_keyword_set_keeps_its_place() {
assert!(err.contains("already"), "{err}");
assert!(s.execute("keyword.saveSet", &json!({"name": "Rings", "replace": "Lisbon", "keywords": []})).is_err(), "no such set");
}
/// Renaming a set without giving its keywords keeps its keywords (not the current set's).
#[test]
fn renaming_a_set_alone_keeps_its_keywords() {
let mut s = Session::with_demo();
s.execute("keyword.saveSet", &json!({"name": "Weddings", "keywords": ["ceremony"]})).unwrap();
s.execute("keyword.saveSet", &json!({"name": "Travel", "keywords": ["harbour"]})).unwrap();
s.execute("keyword.saveSet", &json!({"name": "Ceremonies", "replace": "Weddings"})).unwrap();
assert_eq!(set_named(&mut s, "Ceremonies")["keywords"], json!(["ceremony"]));
}