@@ -740,6 +740,52 @@ pub fn default_keybinds(settings: &Settings) -> Vec<LuaKeybind> { |
| 740 | 740 | binds |
| 741 | 741 | } |
| 742 | 742 | |
| 743 | +/// Update a single `gar.set("key", ...)` line in a Lua config file. |
| 744 | +/// Preserves all other content (comments, bindings, rules, etc.). |
| 745 | +/// `value` should be the Lua literal: a number like `8` or a quoted string like `"#5294e2"`. |
| 746 | +pub fn update_lua_setting( |
| 747 | + path: &std::path::Path, |
| 748 | + key: &str, |
| 749 | + value: &str, |
| 750 | +) -> Result<(), String> { |
| 751 | + let content = std::fs::read_to_string(path).map_err(|e| e.to_string())?; |
| 752 | + |
| 753 | + // Match: gar.set("key", <anything>) or gar.set("key", <anything>) |
| 754 | + // The value can be a number, a quoted string, or a boolean string |
| 755 | + let pattern = format!( |
| 756 | + r#"(gar\.set\(\s*"{key}"\s*,\s*).+?(\s*\))"#, |
| 757 | + key = regex::escape(key), |
| 758 | + ); |
| 759 | + let re = regex::Regex::new(&pattern).map_err(|e| e.to_string())?; |
| 760 | + |
| 761 | + if !re.is_match(&content) { |
| 762 | + tracing::debug!(key, "gar.set line not found in config, skipping write-back"); |
| 763 | + return Ok(()); |
| 764 | + } |
| 765 | + |
| 766 | + let replacement = format!("${{1}}{value}${{2}}"); |
| 767 | + let updated = re.replace(&content, replacement.as_str()); |
| 768 | + |
| 769 | + std::fs::write(path, updated.as_bytes()).map_err(|e| e.to_string())?; |
| 770 | + tracing::debug!(key, value, "config write-back"); |
| 771 | + Ok(()) |
| 772 | +} |
| 773 | + |
| 774 | +/// Format a numeric value for Lua config write-back. |
| 775 | +pub fn lua_number(v: f64) -> String { |
| 776 | + let i = v as i64; |
| 777 | + if (v - i as f64).abs() < 0.01 { |
| 778 | + i.to_string() |
| 779 | + } else { |
| 780 | + format!("{v:.1}") |
| 781 | + } |
| 782 | +} |
| 783 | + |
| 784 | +/// Format a string value for Lua config write-back (quoted). |
| 785 | +pub fn lua_string(s: &str) -> String { |
| 786 | + format!("\"{s}\"") |
| 787 | +} |
| 788 | + |
| 743 | 789 | #[cfg(test)] |
| 744 | 790 | mod tests { |
| 745 | 791 | use super::*; |
@@ -790,4 +836,46 @@ mod tests { |
| 790 | 836 | // 4 basic + 12 focus + 12 swap + 4 resize + 20 workspaces = 52 |
| 791 | 837 | assert!(binds.len() >= 40); |
| 792 | 838 | } |
| 839 | + |
| 840 | + #[test] |
| 841 | + fn write_back_number() { |
| 842 | + let dir = std::env::temp_dir().join("tarmac_test_wb"); |
| 843 | + let _ = std::fs::create_dir_all(&dir); |
| 844 | + let path = dir.join("test.lua"); |
| 845 | + std::fs::write(&path, "gar.set(\"gap_inner\", 8)\n").unwrap(); |
| 846 | + update_lua_setting(&path, "gap_inner", "20").unwrap(); |
| 847 | + let content = std::fs::read_to_string(&path).unwrap(); |
| 848 | + assert!(content.contains("gar.set(\"gap_inner\", 20)"), "got: {content}"); |
| 849 | + let _ = std::fs::remove_dir_all(&dir); |
| 850 | + } |
| 851 | + |
| 852 | + #[test] |
| 853 | + fn write_back_string() { |
| 854 | + let dir = std::env::temp_dir().join("tarmac_test_wb2"); |
| 855 | + let _ = std::fs::create_dir_all(&dir); |
| 856 | + let path = dir.join("test.lua"); |
| 857 | + std::fs::write(&path, "gar.set(\"border_color_focused\", \"#5294e2\")\n").unwrap(); |
| 858 | + update_lua_setting(&path, "border_color_focused", "\"#ff0000\"").unwrap(); |
| 859 | + let content = std::fs::read_to_string(&path).unwrap(); |
| 860 | + assert!(content.contains("\"#ff0000\""), "got: {content}"); |
| 861 | + let _ = std::fs::remove_dir_all(&dir); |
| 862 | + } |
| 863 | + |
| 864 | + #[test] |
| 865 | + fn write_back_preserves_other_lines() { |
| 866 | + let dir = std::env::temp_dir().join("tarmac_test_wb3"); |
| 867 | + let _ = std::fs::create_dir_all(&dir); |
| 868 | + let path = dir.join("test.lua"); |
| 869 | + std::fs::write( |
| 870 | + &path, |
| 871 | + "-- comment\ngar.set(\"gap_inner\", 8)\ngar.bind(\"mod+h\", \"focus left\")\n", |
| 872 | + ) |
| 873 | + .unwrap(); |
| 874 | + update_lua_setting(&path, "gap_inner", "12").unwrap(); |
| 875 | + let content = std::fs::read_to_string(&path).unwrap(); |
| 876 | + assert!(content.contains("-- comment")); |
| 877 | + assert!(content.contains("gar.set(\"gap_inner\", 12)")); |
| 878 | + assert!(content.contains("gar.bind(\"mod+h\", \"focus left\")")); |
| 879 | + let _ = std::fs::remove_dir_all(&dir); |
| 880 | + } |
| 793 | 881 | } |