diff --git a/cosmic-conf/src/bind.rs b/cosmic-conf/src/bind.rs index 812112f..d7d82f4 100644 --- a/cosmic-conf/src/bind.rs +++ b/cosmic-conf/src/bind.rs @@ -92,7 +92,12 @@ fn parse_modifiers(raw: &str, span: Span) -> Result, BindError )); } - found.sort_by_key(|m| MODIFIER_ORDER.iter().position(|o| o == m).unwrap_or(usize::MAX)); + found.sort_by_key(|m| { + MODIFIER_ORDER + .iter() + .position(|o| o == m) + .unwrap_or(usize::MAX) + }); Ok(found) } @@ -179,7 +184,10 @@ fn workspace_index(arg: &str, span: Span, dispatcher: &str) -> Result().map_err(|_| { err( span, - format!("`{dispatcher}` needs a workspace number, got `{}`", arg.trim()), + format!( + "`{dispatcher}` needs a workspace number, got `{}`", + arg.trim() + ), Some("COSMIC addresses workspaces 1-255 by index"), ) }) @@ -342,19 +350,33 @@ mod tests { // COSMIC's defaults bind bare Super to the launcher this way. let b = bind("SUPER, , exec, rofi -show drun"); assert_eq!(b.key, None); - assert_eq!(render(&[b]), "{\n (modifiers: [Super]): Spawn(\"rofi -show drun\"),\n}\n"); + assert_eq!( + render(&[b]), + "{\n (modifiers: [Super]): Spawn(\"rofi -show drun\"),\n}\n" + ); } #[test] fn keys_normalise_to_xkb_spelling() { assert_eq!(bind("SUPER, Q, killactive").key.as_deref(), Some("q")); - assert_eq!(bind("SUPER, Return, killactive").key.as_deref(), Some("Return")); - assert_eq!(bind("SUPER, enter, killactive").key.as_deref(), Some("Return")); + assert_eq!( + bind("SUPER, Return, killactive").key.as_deref(), + Some("Return") + ); + assert_eq!( + bind("SUPER, enter, killactive").key.as_deref(), + Some("Return") + ); assert_eq!(bind("SUPER, f5, killactive").key.as_deref(), Some("F5")); - assert_eq!(bind("SUPER, slash, killactive").key.as_deref(), Some("slash")); + assert_eq!( + bind("SUPER, slash, killactive").key.as_deref(), + Some("slash") + ); // Unknown names pass through so exact keysyms stay usable. assert_eq!( - bind("SUPER, XF86AudioRaiseVolume, killactive").key.as_deref(), + bind("SUPER, XF86AudioRaiseVolume, killactive") + .key + .as_deref(), Some("XF86AudioRaiseVolume") ); } @@ -381,7 +403,10 @@ mod tests { assert_eq!(bind("SUPER, Q, killactive").action, "Close"); assert_eq!(bind("SUPER, F, fullscreen").action, "Fullscreen"); assert_eq!(bind("SUPER, left, movefocus, l").action, "Focus(Left)"); - assert_eq!(bind("SUPER SHIFT, left, movewindow, l").action, "Move(Left)"); + assert_eq!( + bind("SUPER SHIFT, left, movewindow, l").action, + "Move(Left)" + ); assert_eq!(bind("SUPER, 1, workspace, 1").action, "Workspace(1)"); assert_eq!( bind("SUPER SHIFT, 1, movetoworkspace, 1").action, diff --git a/cosmic-conf/src/emit.rs b/cosmic-conf/src/emit.rs index c0a8a14..82fdcdf 100644 --- a/cosmic-conf/src/emit.rs +++ b/cosmic-conf/src/emit.rs @@ -72,9 +72,15 @@ impl CornerRadii { pub enum EmitError { Io(io::Error), /// A projected target whose composite shape this emitter cannot rebuild. - UnsupportedComposite { key: String, detail: String }, + UnsupportedComposite { + key: String, + detail: String, + }, /// An existing file could not be parsed, so read-modify-write is unsafe. - Unreadable { path: PathBuf, detail: String }, + Unreadable { + path: PathBuf, + detail: String, + }, NoConfigDirectory, } @@ -293,10 +299,12 @@ fn composite( // default (0, 8) — theme.rs:939. "gaps" => { let (mut outer, mut inner) = match previous { - Some(text) => ron::from_str::<(u32, u32)>(text).map_err(|e| EmitError::Unreadable { - path: path.to_path_buf(), - detail: e.to_string(), - })?, + Some(text) => { + ron::from_str::<(u32, u32)>(text).map_err(|e| EmitError::Unreadable { + path: path.to_path_buf(), + detail: e.to_string(), + })? + } None => (0, 8), }; @@ -398,7 +406,10 @@ mod tests { let e = Emitter::with_root(tmp.path()); e.apply(&planned).unwrap(); - assert_eq!(read(tmp.path(), "com.system76.CosmicComp", "autotile"), "true"); + assert_eq!( + read(tmp.path(), "com.system76.CosmicComp", "autotile"), + "true" + ); } #[test] @@ -411,7 +422,10 @@ mod tests { let root = tmp.path(); assert_eq!(read(root, "com.system76.CosmicComp", "autotile"), "true"); - assert_eq!(read(root, "com.system76.CosmicComp", "edge_snap_threshold"), "12"); + assert_eq!( + read(root, "com.system76.CosmicComp", "edge_snap_threshold"), + "12" + ); assert_eq!( read(root, "com.system76.CosmicTk", "icon_theme"), "\"Tela-circle-dracula\"" @@ -423,7 +437,8 @@ mod tests { #[test] fn both_gaps_reach_disk_in_one_tuple() { let tmp = TempDir::new().unwrap(); - let planned = plan_for("general {\n gaps_in = 3\n gaps_out = 8\n}\n", tmp.path()).unwrap(); + let planned = + plan_for("general {\n gaps_in = 3\n gaps_out = 8\n}\n", tmp.path()).unwrap(); Emitter::with_root(tmp.path()).apply(&planned).unwrap(); // (outer, inner) — theme.rs:895 @@ -480,10 +495,16 @@ mod tests { let b = "com.system76.CosmicTheme.Dark.Builder"; // Option: three components, no alpha. - assert_eq!(read(tmp.path(), b, "accent"), "Some((red: 1.0, green: 0.0, blue: 0.0))"); + assert_eq!( + read(tmp.path(), b, "accent"), + "Some((red: 1.0, green: 0.0, blue: 0.0))" + ); // Option: four. let bg = read(tmp.path(), b, "bg_color"); - assert!(bg.starts_with("Some((red: 0.0, green: 1.0, blue: 0.0, alpha: "), "{bg}"); + assert!( + bg.starts_with("Some((red: 0.0, green: 1.0, blue: 0.0, alpha: "), + "{bg}" + ); } #[test] @@ -492,7 +513,11 @@ mod tests { let planned = plan_for("decoration {\n rounding = 10\n}\n", tmp.path()).unwrap(); Emitter::with_root(tmp.path()).apply(&planned).unwrap(); - let text = read(tmp.path(), "com.system76.CosmicTheme.Dark.Builder", "corner_radii"); + let text = read( + tmp.path(), + "com.system76.CosmicTheme.Dark.Builder", + "corner_radii", + ); let radii: CornerRadii = ron::from_str(&text).expect("round-trips as CornerRadii"); assert_eq!(radii.radius_m, [10.0; 4]); } @@ -504,7 +529,11 @@ mod tests { let planned = plan_for("decoration {\n rounding = 10\n}\n", tmp.path()).unwrap(); Emitter::with_root(tmp.path()).apply(&planned).unwrap(); - let text = read(tmp.path(), "com.system76.CosmicTheme.Dark.Builder", "corner_radii"); + let text = read( + tmp.path(), + "com.system76.CosmicTheme.Dark.Builder", + "corner_radii", + ); let radii: CornerRadii = ron::from_str(&text).unwrap(); let d = CornerRadii::default(); assert_eq!(radii.radius_0, d.radius_0); @@ -544,10 +573,13 @@ mod tests { }; let tmp = TempDir::new().unwrap(); let e = Emitter::with_root(tmp.path()); - let res = e.plan(&Resolved { writes: vec![write] }); - assert!( - matches!(res.unwrap_err()[0], EmitError::UnsupportedComposite { .. }), - ); + let res = e.plan(&Resolved { + writes: vec![write], + }); + assert!(matches!( + res.unwrap_err()[0], + EmitError::UnsupportedComposite { .. } + ),); } #[test] @@ -561,7 +593,11 @@ mod tests { fs::write(dir.join("gaps"), "not ron at all").unwrap(); let errs = plan_for("general {\n gaps_in = 3\n}\n", tmp.path()).unwrap_err(); - assert!(matches!(errs[0], EmitError::Unreadable { .. }), "{:?}", errs[0]); + assert!( + matches!(errs[0], EmitError::Unreadable { .. }), + "{:?}", + errs[0] + ); } /// Planning must not touch disk — that is what makes emission transactional. @@ -613,7 +649,10 @@ mod tests { .map(|e| e.file_name().to_string_lossy().to_string()) .filter(|n| n.starts_with(".atomicwrite")) .collect(); - assert!(leftovers.is_empty(), "temp files left behind: {leftovers:?}"); + assert!( + leftovers.is_empty(), + "temp files left behind: {leftovers:?}" + ); } #[test] diff --git a/cosmic-conf/src/import.rs b/cosmic-conf/src/import.rs index 94bc6b7..89426c9 100644 --- a/cosmic-conf/src/import.rs +++ b/cosmic-conf/src/import.rs @@ -70,7 +70,8 @@ fn strip_hyde_header(src: &str) -> &str { let Some(first) = lines.next() else { return src; }; - let is_destination_header = !first.contains('=') && (first.contains("|>") || first.contains('|')); + let is_destination_header = + !first.contains('=') && (first.contains("|>") || first.contains('|')); if is_destination_header { // Preserve line numbering by keeping the newline count intact: callers // report spans against the stripped text, so re-add a blank line. @@ -105,7 +106,12 @@ fn is_gradient(value: &str) -> bool { /// Flatten to dotted keys, keeping variables separate — HyDE carries /// `$GTK_THEME` / `$ICON_THEME` as variables rather than config keys. -fn walk(items: &[Item], prefix: &str, out: &mut Vec<(String, String, Span)>, vars: &mut Vec<(String, String, Span)>) { +fn walk( + items: &[Item], + prefix: &str, + out: &mut Vec<(String, String, Span)>, + vars: &mut Vec<(String, String, Span)>, +) { for item in items { match item { Item::Section { name, items } => { @@ -150,7 +156,11 @@ pub fn import_hypr_theme(src: &str, theme_name: &str) -> Result theme.push(("icon_theme".into(), value.clone())), "COLOR_SCHEME" => { - let mode = if value.contains("light") { "light" } else { "dark" }; + let mode = if value.contains("light") { + "light" + } else { + "dark" + }; theme.push(("mode".into(), mode.into())); } "GTK_THEME" => notes.push(Note { @@ -208,14 +218,16 @@ pub fn import_hypr_theme(src: &str, theme_name: &str) -> Result notes.push(note(Reason::NoEquivalent( "cosmic-comp uses a BSP tiler; dwindle/master are not available", ))), - "general.resize_on_border" => notes.push(note(Reason::NoEquivalent( - "no equivalent setting", - ))), + "general.resize_on_border" => { + notes.push(note(Reason::NoEquivalent("no equivalent setting"))) + } - k if k.starts_with("decoration.blur") => notes.push(note( - Reason::NeedsCompositorPatch("COSMIC blur is client-requested via \ - ext-background-effect; rule-driven blur is spec Phase 2"), - )), + k if k.starts_with("decoration.blur") => { + notes.push(note(Reason::NeedsCompositorPatch( + "COSMIC blur is client-requested via \ + ext-background-effect; rule-driven blur is spec Phase 2", + ))) + } k if k.starts_with("decoration.shadow") => notes.push(note( Reason::NeedsCompositorPatch("shadow.frag exists but is not configurable yet"), )), @@ -300,7 +312,12 @@ pub fn render_report(import: &Import) -> String { if !lossy.is_empty() { out.push_str("Translated with loss:\n"); for n in &lossy { - out.push_str(&format!(" {} = {}\n {}\n", n.key, n.value, n.reason.describe())); + out.push_str(&format!( + " {} = {}\n {}\n", + n.key, + n.value, + n.reason.describe() + )); } } if !dropped.is_empty() { @@ -309,7 +326,12 @@ pub fn render_report(import: &Import) -> String { } out.push_str("Not translated:\n"); for n in &dropped { - out.push_str(&format!(" {} = {}\n {}\n", n.key, n.value, n.reason.describe())); + out.push_str(&format!( + " {} = {}\n {}\n", + n.key, + n.value, + n.reason.describe() + )); } } out @@ -431,7 +453,10 @@ layerrule = blur,waybar "group.col.border_active", "layerrule", ] { - assert!(keys.contains(&expected), "`{expected}` missing from {keys:?}"); + assert!( + keys.contains(&expected), + "`{expected}` missing from {keys:?}" + ); } } diff --git a/cosmic-conf/src/lib.rs b/cosmic-conf/src/lib.rs index d678edf..3120296 100644 --- a/cosmic-conf/src/lib.rs +++ b/cosmic-conf/src/lib.rs @@ -25,7 +25,10 @@ pub use resolve::{resolve, Diagnostic, Resolved, Value, Write, WriteKind}; /// Render a diagnostic against source text, cargo-style. pub fn render_diagnostic(source: &str, span: Span, message: &str, help: Option<&str>) -> String { - let line = source.lines().nth(span.line.saturating_sub(1)).unwrap_or(""); + let line = source + .lines() + .nth(span.line.saturating_sub(1)) + .unwrap_or(""); let gutter = span.line.to_string().len(); let pad = " ".repeat(gutter); let caret = " ".repeat(span.col.saturating_sub(1)) + &"^".repeat(span.len.max(1)); @@ -72,7 +75,11 @@ theme { // gaps fold per builder; accent fans out to both; autotile is direct. let gaps: Vec<_> = r.writes.iter().filter(|w| w.target.key == "gaps").collect(); assert_eq!(gaps.len(), 2); - let accent: Vec<_> = r.writes.iter().filter(|w| w.target.key == "accent").collect(); + let accent: Vec<_> = r + .writes + .iter() + .filter(|w| w.target.key == "accent") + .collect(); assert_eq!(accent.len(), 2); } @@ -81,7 +88,12 @@ theme { let src = "general {\n gaps_inn = 8\n}\n"; let ast = parse(src).unwrap(); let diags = resolve(&ast).unwrap_err(); - let out = render_diagnostic(src, diags[0].span, &diags[0].message, diags[0].help.as_deref()); + let out = render_diagnostic( + src, + diags[0].span, + &diags[0].message, + diags[0].help.as_deref(), + ); assert!(out.contains("unknown key"), "{out}"); assert!(out.contains("cosmic.conf:2:5"), "{out}"); diff --git a/cosmic-conf/src/parser.rs b/cosmic-conf/src/parser.rs index b23f813..77132eb 100644 --- a/cosmic-conf/src/parser.rs +++ b/cosmic-conf/src/parser.rs @@ -300,7 +300,10 @@ mod tests { _ => None, }) .collect(); - assert_eq!(binds, vec!["SUPER, Return, spawn, kitty", "SUPER, Q, close"]); + assert_eq!( + binds, + vec!["SUPER, Return, spawn, kitty", "SUPER, Q, close"] + ); } #[test] @@ -332,7 +335,11 @@ mod tests { #[test] fn rejects_line_without_equals() { let err = parse("this is not valid\n").unwrap_err(); - assert!(err.message.contains("expected `key = value`"), "{}", err.message); + assert!( + err.message.contains("expected `key = value`"), + "{}", + err.message + ); assert_eq!(err.span.line, 1); } } diff --git a/cosmic-conf/src/schema.rs b/cosmic-conf/src/schema.rs index 1cdcc50..129c46b 100644 --- a/cosmic-conf/src/schema.rs +++ b/cosmic-conf/src/schema.rs @@ -114,8 +114,16 @@ macro_rules! both_themes { macro_rules! both_themes_direct { ($key:literal) => { &[ - Target::Direct { component: DARK_BUILDER, version: 1, key: $key }, - Target::Direct { component: LIGHT_BUILDER, version: 1, key: $key }, + Target::Direct { + component: DARK_BUILDER, + version: 1, + key: $key, + }, + Target::Direct { + component: LIGHT_BUILDER, + version: 1, + key: $key, + }, ] }; } @@ -126,14 +134,20 @@ pub const REGISTRY: &[Entry] = &[ conf: "general.gaps_in", targets: both_themes!("gaps", &[GAPS_INNER_IDX]), ty: Ty::U32, - validate: Some(Range { min: 0.0, max: 128.0 }), + validate: Some(Range { + min: 0.0, + max: 128.0, + }), doc: "Gap between adjacent tiled windows, in px", }, Entry { conf: "general.gaps_out", targets: both_themes!("gaps", &[GAPS_OUTER_IDX]), ty: Ty::U32, - validate: Some(Range { min: 0.0, max: 256.0 }), + validate: Some(Range { + min: 0.0, + max: 256.0, + }), doc: "Gap between tiled windows and the screen edge, in px", }, Entry { @@ -177,7 +191,10 @@ pub const REGISTRY: &[Entry] = &[ key: "focus_follows_cursor_delay", }], ty: Ty::U32, - validate: Some(Range { min: 0.0, max: 5000.0 }), + validate: Some(Range { + min: 0.0, + max: 5000.0, + }), doc: "Delay in ms before focus follows the cursor", }, Entry { @@ -199,7 +216,10 @@ pub const REGISTRY: &[Entry] = &[ key: "edge_snap_threshold", }], ty: Ty::U32, - validate: Some(Range { min: 0.0, max: 256.0 }), + validate: Some(Range { + min: 0.0, + max: 256.0, + }), doc: "Distance in px at which windows snap to output edges", }, // ---- decoration ------------------------------------------------------ @@ -207,7 +227,10 @@ pub const REGISTRY: &[Entry] = &[ conf: "decoration.rounding", targets: both_themes!("corner_radii", &["radius_m"]), ty: Ty::F32, - validate: Some(Range { min: 0.0, max: 64.0 }), + validate: Some(Range { + min: 0.0, + max: 64.0, + }), doc: "Window corner radius in px (maps to the theme's radius_m)", }, // ---- theme ----------------------------------------------------------- diff --git a/cosmic-conf/tests/archive_escape.rs b/cosmic-conf/tests/archive_escape.rs index ba2e613..9a1fd1d 100644 --- a/cosmic-conf/tests/archive_escape.rs +++ b/cosmic-conf/tests/archive_escape.rs @@ -65,13 +65,19 @@ fn write_raw_link(header: &mut tar::Header, target: &str) { } /// A theme directory just complete enough for `plan` to consider the archive. -fn theme_with_archive(entries: &[(&str, tar::EntryType, &[u8], Option<&str>)]) -> (TempDir, PathBuf, PathBuf) { +fn theme_with_archive( + entries: &[(&str, tar::EntryType, &[u8], Option<&str>)], +) -> (TempDir, PathBuf, PathBuf) { let tmp = TempDir::new().unwrap(); let theme_dir = tmp.path().join("Configs/.config/hyde/themes/Evil"); let source_dir = tmp.path().join("Source"); fs::create_dir_all(&theme_dir).unwrap(); fs::create_dir_all(&source_dir).unwrap(); - fs::write(theme_dir.join("hypr.theme"), "general {\n gaps_in = 3\n}\n").unwrap(); + fs::write( + theme_dir.join("hypr.theme"), + "general {\n gaps_in = 3\n}\n", + ) + .unwrap(); hostile_tarball(&source_dir.join("Gtk_Evil.tar.gz"), entries); (tmp, theme_dir, source_dir)