Adjust env parsing to account for multiline env values (#10216)

Mikayla Maki created

fixes https://github.com/zed-industries/zed/issues/6012

Release Notes:

- N/A

Change summary

crates/project/src/project.rs | 13 +++++--------
crates/util/src/util.rs       | 26 ++++++++++++++++++++++++++
crates/zed/src/main.rs        | 12 ++++--------
3 files changed, 35 insertions(+), 16 deletions(-)

Detailed changes

crates/project/src/project.rs 🔗

@@ -99,7 +99,7 @@ use text::{Anchor, BufferId, RopeFingerprint};
 use util::{
     debug_panic, defer,
     http::{HttpClient, Url},
-    maybe, merge_json_value_into,
+    maybe, merge_json_value_into, parse_env_output,
     paths::{
         LOCAL_SETTINGS_RELATIVE_PATH, LOCAL_TASKS_RELATIVE_PATH, LOCAL_VSCODE_TASKS_RELATIVE_PATH,
     },
@@ -10569,13 +10569,10 @@ async fn load_shell_environment(dir: &Path) -> Result<HashMap<String, String>> {
 
     let mut parsed_env = HashMap::default();
     let env_output = &stdout[env_output_start + marker.len()..];
-    for line in env_output.split_terminator('\n') {
-        if let Some(separator_index) = line.find('=') {
-            let key = line[..separator_index].to_string();
-            let value = line[separator_index + 1..].to_string();
-            parsed_env.insert(key, value);
-        }
-    }
+
+    parse_env_output(env_output, |key, value| {
+        parsed_env.insert(key, value);
+    });
 
     Ok(parsed_env)
 }

crates/util/src/util.rs 🔗

@@ -130,6 +130,32 @@ where
     }
 }
 
+/// Parse the result of calling `usr/bin/env` with no arguments
+pub fn parse_env_output(env: &str, mut f: impl FnMut(String, String)) {
+    let mut current_key: Option<String> = None;
+    let mut current_value: Option<String> = None;
+
+    for line in env.split_terminator('\n') {
+        if let Some(separator_index) = line.find('=') {
+            if &line[..separator_index] != "" {
+                if let Some((key, value)) = Option::zip(current_key.take(), current_value.take()) {
+                    f(key, value)
+                }
+                current_key = Some(line[..separator_index].to_string());
+                current_value = Some(line[separator_index + 1..].to_string());
+                continue;
+            };
+        }
+        if let Some(value) = current_value.as_mut() {
+            value.push('\n');
+            value.push_str(line);
+        }
+    }
+    if let Some((key, value)) = Option::zip(current_key.take(), current_value.take()) {
+        f(key, value)
+    }
+}
+
 pub fn merge_json_value_into(source: serde_json::Value, target: &mut serde_json::Value) {
     use serde_json::Value;
 

crates/zed/src/main.rs 🔗

@@ -54,7 +54,7 @@ use std::{
 use theme::{ActiveTheme, SystemAppearance, ThemeRegistry, ThemeSettings};
 use util::{
     http::{HttpClient, HttpClientWithUrl},
-    maybe,
+    maybe, parse_env_output,
     paths::{self, CRASHES_DIR, CRASHES_RETIRED_DIR},
     ResultExt, TryFutureExt,
 };
@@ -923,13 +923,9 @@ async fn load_login_shell_environment() -> Result<()> {
 
     if let Some(env_output_start) = stdout.find(marker) {
         let env_output = &stdout[env_output_start + marker.len()..];
-        for line in env_output.split_terminator('\n') {
-            if let Some(separator_index) = line.find('=') {
-                let key = &line[..separator_index];
-                let value = &line[separator_index + 1..];
-                env::set_var(key, value);
-            }
-        }
+
+        parse_env_output(env_output, |key, value| env::set_var(key, value));
+
         log::info!(
             "set environment variables from shell:{}, path:{}",
             shell,