From 476ac471aa9366574c89871fa70e094516a6be6f Mon Sep 17 00:00:00 2001 From: Kayvan Zahiri <123409429+Kayvan-Zahiri@users.noreply.github.com> Date: Wed, 21 Oct 2026 21:30:38 -0700 Subject: [PATCH] fix: preserve Unicode whitespace in variable edit documents --- src/controllers/variable_edit.rs | 45 +++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/src/controllers/variable_edit.rs b/src/controllers/variable_edit.rs index bc42e857d..87c9a4d6a 100644 --- a/src/controllers/variable_edit.rs +++ b/src/controllers/variable_edit.rs @@ -258,7 +258,8 @@ pub fn write_edit_document( fn escape_dotenv_value(value: &str) -> String { let needs_quotes = value.is_empty() - || value.contains([' ', '\t', '\n', '\r', '#', '"', '\'', '\\']) + || value.chars().any(char::is_whitespace) + || value.contains(['#', '"', '\'', '\\']) || value.starts_with(['=', '#']); if !needs_quotes { @@ -635,6 +636,48 @@ mod tests { ); } + #[test] + fn dotenv_round_trip_preserves_unicode_whitespace() { + // Rust's trim recognizes more whitespace than the dotenv writer used to quote. + let whitespace = [ + '\u{00a0}', '\u{2003}', '\u{2009}', '\u{202f}', '\u{3000}', '\u{0085}', '\u{000b}', + '\u{000c}', + ]; + let mut failures = Vec::new(); + for ch in whitespace { + for value in [ + format!("{ch}value"), + format!("value{ch}"), + ch.to_string(), + format!("left{ch}right"), + ] { + let snapshot = EditSnapshot { + editable: BTreeMap::from([( + "VALUE".into(), + EditVariableEntry { + value: value.clone(), + is_sealed: false, + }, + )]), + read_only: BTreeMap::new(), + }; + let path = temp_edit_path("whitespace"); + write_edit_document(&path, &snapshot, &[]).unwrap(); + let parsed = parse_edit_document(&fs::read_to_string(&path).unwrap()).unwrap(); + let _ = fs::remove_file(&path); + if parsed["VALUE"].value != value { + failures.push(format!("{value:?} became {:?}", parsed["VALUE"].value)); + } + if !diff_edit_snapshot(&snapshot, &parsed).is_empty() + && parsed["VALUE"].value == value + { + failures.push(format!("unchanged {value:?} produced a diff")); + } + } + } + assert!(failures.is_empty(), "{}", failures.join("\n")); + } + #[test] fn read_only_railway_vars_are_comments_only() { let snapshot = demo_snapshot();