gardesk/tarmac / 44b908f

Browse files

persist settings changes to init.lua via regex write-back

- update_lua_setting: regex-replace gar.set("key", ...) lines in config
- preserves all comments, bindings, rules, and custom Lua code
- all General tab controls write to config on change: gaps, bar height,
border width/radius/colors, FFM, mouse-follows-focus, mod key
- lua_number/lua_string helpers for formatting Lua literals
- 3 unit tests for write-back (number, string, preservation)
Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
44b908fbdf27dfbdfa46142ee221401e11f1bf1b
Parents
3a47b86
Tree
e9a7f9c

2 changed files

StatusFile+-
M tarmac/src/config/lua.rs 88 0
M tarmac/src/main.rs 44 15
tarmac/src/config/lua.rsmodified
@@ -740,6 +740,52 @@ pub fn default_keybinds(settings: &Settings) -> Vec<LuaKeybind> {
740740
     binds
741741
 }
742742
 
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
+
743789
 #[cfg(test)]
744790
 mod tests {
745791
     use super::*;
@@ -790,4 +836,46 @@ mod tests {
790836
         // 4 basic + 12 focus + 12 swap + 4 resize + 20 workspaces = 52
791837
         assert!(binds.len() >= 40);
792838
     }
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
+    }
793881
 }
tarmac/src/main.rsmodified
@@ -1102,6 +1102,9 @@ fn build_settings_snapshot(
11021102
 }
11031103
 
11041104
 fn poll_settings_actions() {
1105
+    use tarmac::config::lua::{lua_number, lua_string};
1106
+    use tarmac::ui::settings::SettingsAction;
1107
+
11051108
     SETTINGS_WIN.with(|sw| {
11061109
         let borrow = sw.borrow();
11071110
         let Some(win) = borrow.as_ref() else { return };
@@ -1112,44 +1115,62 @@ fn poll_settings_actions() {
11121115
         win.refresh_labels();
11131116
         drop(borrow);
11141117
 
1118
+        let config_path = CONFIG_PATH.with(|p| p.borrow().clone());
1119
+
11151120
         WM_STATE.with(|s| {
11161121
             if let Some(state) = s.borrow_mut().as_mut() {
11171122
                 for action in actions {
11181123
                     match action {
1119
-                        tarmac::ui::settings::SettingsAction::GapInner(v) => {
1124
+                        SettingsAction::GapInner(v) => {
11201125
                             state.gap_inner = v;
1126
+                            write_setting(&config_path, "gap_inner", &lua_number(v));
11211127
                         }
1122
-                        tarmac::ui::settings::SettingsAction::GapOuter(v) => {
1128
+                        SettingsAction::GapOuter(v) => {
11231129
                             state.gap_outer = v;
1130
+                            write_setting(&config_path, "gap_outer", &lua_number(v));
11241131
                         }
1125
-                        tarmac::ui::settings::SettingsAction::BarHeight(v) => {
1132
+                        SettingsAction::BarHeight(v) => {
11261133
                             state.bar_height = v;
1134
+                            write_setting(&config_path, "bar_height", &lua_number(v));
11271135
                         }
1128
-                        tarmac::ui::settings::SettingsAction::BorderWidth(v) => {
1136
+                        SettingsAction::BorderWidth(v) => {
11291137
                             state.borders.border_width = v;
1138
+                            write_setting(&config_path, "border_width", &lua_string(&lua_number(v)));
11301139
                         }
1131
-                        tarmac::ui::settings::SettingsAction::BorderRadius(v) => {
1140
+                        SettingsAction::BorderRadius(v) => {
11321141
                             state.borders.radius = v;
1142
+                            write_setting(&config_path, "border_radius", &lua_string(&lua_number(v)));
11331143
                         }
1134
-                        tarmac::ui::settings::SettingsAction::BorderColorFocused(hex) => {
1144
+                        SettingsAction::BorderColorFocused(ref hex) => {
11351145
                             state.borders.focused_color =
1136
-                                tarmac::platform::border::BorderColor::from_hex(&hex);
1146
+                                tarmac::platform::border::BorderColor::from_hex(hex);
1147
+                            write_setting(&config_path, "border_color_focused", &lua_string(hex));
11371148
                         }
1138
-                        tarmac::ui::settings::SettingsAction::BorderColorUnfocused(hex) => {
1149
+                        SettingsAction::BorderColorUnfocused(ref hex) => {
11391150
                             state.borders.unfocused_color =
1140
-                                tarmac::platform::border::BorderColor::from_hex(&hex);
1151
+                                tarmac::platform::border::BorderColor::from_hex(hex);
1152
+                            write_setting(&config_path, "border_color_unfocused", &lua_string(hex));
11411153
                         }
1142
-                        tarmac::ui::settings::SettingsAction::FocusFollowsMouse(v) => {
1154
+                        SettingsAction::FocusFollowsMouse(v) => {
11431155
                             state.focus_follows_mouse = v;
1156
+                            write_setting(
1157
+                                &config_path,
1158
+                                "focus_follows_mouse",
1159
+                                &lua_string(if v { "true" } else { "false" }),
1160
+                            );
11441161
                         }
1145
-                        tarmac::ui::settings::SettingsAction::MouseFollowsFocus(v) => {
1162
+                        SettingsAction::MouseFollowsFocus(v) => {
11461163
                             state.mouse_follows_focus = v;
1164
+                            write_setting(
1165
+                                &config_path,
1166
+                                "mouse_follows_focus",
1167
+                                &lua_string(if v { "true" } else { "false" }),
1168
+                            );
11471169
                         }
1148
-                        tarmac::ui::settings::SettingsAction::ModKey(_) => {
1149
-                            // Mod key changes require re-registering hotkeys,
1150
-                            // which needs a full config reload. Skip for now.
1170
+                        SettingsAction::ModKey(ref key) => {
1171
+                            write_setting(&config_path, "mod_key", &lua_string(key));
11511172
                         }
1152
-                        tarmac::ui::settings::SettingsAction::Open => {}
1173
+                        SettingsAction::Open => {}
11531174
                     }
11541175
                 }
11551176
                 state.apply_layout();
@@ -1159,6 +1180,14 @@ fn poll_settings_actions() {
11591180
     });
11601181
 }
11611182
 
1183
+fn write_setting(config_path: &Option<std::path::PathBuf>, key: &str, value: &str) {
1184
+    if let Some(path) = config_path
1185
+        && let Err(e) = tarmac::config::lua::update_lua_setting(path, key, value)
1186
+    {
1187
+        tracing::warn!(key, err = %e, "failed to write setting to config");
1188
+    }
1189
+}
1190
+
11621191
 fn run_app() {
11631192
     use objc2::MainThreadMarker;
11641193
     use objc2_app_kit::NSApplication;