From 7a527fce64822c3ba45e5d729dcf6ce576010e9a Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 31 Oct 2026 23:35:31 -0700 Subject: [PATCH] Character Styles: New Character Style keeps a language tag the selected text holds charStyle.new now validates attrs against the target text's attributes, so a language tag that is not in the list but that the selected run already holds (a native file can store any tag) is kept. Any other unlisted tag is still an error. --- crates/engine/src/cmd/textstyles.rs | 10 ++++++---- crates/engine/src/cmd/textstyles/tests.rs | 12 ++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/crates/engine/src/cmd/textstyles.rs b/crates/engine/src/cmd/textstyles.rs index 4987c479f..a44dd1773 100644 --- a/crates/engine/src/cmd/textstyles.rs +++ b/crates/engine/src/cmd/textstyles.rs @@ -475,8 +475,9 @@ fn name_param<'a>(p: &'a Value, kind: Kind, verb: &str) -> Result<&'a str> { str_param(p, "name").ok_or_else(|| bad(&kind.cmd(verb), "missing `name`")) } -/// The `attrs` of `p`, validated. `kept` holds the style's current attributes (`charStyle.edit`): -/// a language tag that is not in the list, which a file can give a style, stays when `attrs` keeps it. +/// The `attrs` of `p`, validated. `kept` holds the style's current attributes (`charStyle.edit`) or +/// the target text's attributes (`charStyle.new`): a language tag that is not in the list, which a +/// file can give a style or a run, stays when `attrs` keeps it. fn attrs_param(p: &Value, kind: Kind, verb: &str, kept: Option<&Map>) -> Result>> { match p.get("attrs") { None | Some(Value::Null) => Ok(None), @@ -536,9 +537,10 @@ fn list(s: &mut Session, kind: Kind) -> Result { fn new(s: &mut Session, p: &Value, kind: Kind) -> Result { let c = kind.cmd("new"); - let attrs = match attrs_param(p, kind, "new", None)? { + let target = selection_attrs(s, p, kind)?; + let attrs = match attrs_param(p, kind, "new", target.as_ref())? { Some(a) => a, - None => match selection_attrs(s, p, kind)? { + None => match target { Some(a) => a, None => { let a = new_style_attrs(s, kind); diff --git a/crates/engine/src/cmd/textstyles/tests.rs b/crates/engine/src/cmd/textstyles/tests.rs index bb37dd877..d7021efda 100644 --- a/crates/engine/src/cmd/textstyles/tests.rs +++ b/crates/engine/src/cmd/textstyles/tests.rs @@ -245,6 +245,18 @@ fn character_style_options_edit_keeps_a_language_tag_from_a_file() { assert!(error.contains("unknown language"), "{error}"); } +#[test] +fn new_character_style_keeps_a_language_tag_from_the_text() { + let (mut s, id) = session_with_text("Text"); + if let Some(NodeKind::Text(t)) = std::sync::Arc::make_mut(&mut s.doc_mut().unwrap().doc).node_mut(NodeId(id)).map(|n| &mut n.kind) { + t.runs[0].style.language = Some("x-file".into()); + } + s.execute("charStyle.new", &json!({"name":"Body","id":id,"attrs":{"size":18.0,"language":"x-file"}})).unwrap(); + assert_eq!(s.doc().unwrap().doc.char_styles.iter().find(|d| d.name == "Body").unwrap().attrs["language"], json!("x-file")); + let error = s.execute("charStyle.new", &json!({"name":"Other","id":id,"attrs":{"language":"x-other"}})).unwrap_err().to_string(); + assert!(error.contains("unknown language"), "{error}"); +} + #[test] fn editing_character_style_clears_removed_size_without_losing_local_override() { let (mut s, id) = session_with_text("One two");