diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index e46afd9b5e..a056d84c40 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -179,19 +179,26 @@ pub struct LanguageRegistry { language_server_download_dir: Option>, lsp_binary_statuses_tx: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, lsp_binary_statuses_rx: async_broadcast::Receiver<(Arc, LanguageServerBinaryStatus)>, + login_shell_env_loaded: Shared>, } impl LanguageRegistry { - pub fn new() -> Self { + pub fn new(login_shell_env_loaded: Task<()>) -> Self { let (lsp_binary_statuses_tx, lsp_binary_statuses_rx) = async_broadcast::broadcast(16); Self { language_server_download_dir: None, languages: Default::default(), lsp_binary_statuses_tx, lsp_binary_statuses_rx, + login_shell_env_loaded: login_shell_env_loaded.shared(), } } + #[cfg(any(test, feature = "test-support"))] + pub fn test() -> Self { + Self::new(Task::ready(())) + } + pub fn add(&self, language: Arc) { self.languages.write().push(language.clone()); } @@ -234,7 +241,7 @@ impl LanguageRegistry { pub fn start_language_server( &self, - language: &Arc, + language: Arc, root_path: Arc, http_client: Arc, cx: &mut MutableAppContext, @@ -273,28 +280,28 @@ impl LanguageRegistry { let adapter = language.adapter.clone()?; let background = cx.background().clone(); - let server_binary_path = { - Some( - language - .lsp_binary_path - .lock() - .get_or_insert_with(|| { - get_server_binary_path( - adapter.clone(), - language.clone(), - http_client, - download_dir, - self.lsp_binary_statuses_tx.clone(), - ) - .map_err(Arc::new) - .boxed() - .shared() - }) - .clone() - .map_err(|e| anyhow!(e)), - ) - }?; + let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone(); + let login_shell_env_loaded = self.login_shell_env_loaded.clone(); Some(cx.background().spawn(async move { + login_shell_env_loaded.await; + let server_binary_path = language + .lsp_binary_path + .lock() + .get_or_insert_with(|| { + get_server_binary_path( + adapter.clone(), + language.clone(), + http_client, + download_dir, + lsp_binary_statuses, + ) + .map_err(Arc::new) + .boxed() + .shared() + }) + .clone() + .map_err(|e| anyhow!(e)); + let server_binary_path = server_binary_path.await?; let server_args = adapter.server_args(); let server = lsp::LanguageServer::new( diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index 3783f1e66d..112073aed6 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -25,7 +25,7 @@ fn init_logger() { #[gpui::test] fn test_select_language() { - let registry = LanguageRegistry::new(); + let registry = LanguageRegistry::test(); registry.add(Arc::new(Language::new( LanguageConfig { name: "Rust".into(), diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 945061a3b9..694755274d 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -403,7 +403,7 @@ impl Project { #[cfg(any(test, feature = "test-support"))] pub fn test(fs: Arc, cx: &mut gpui::TestAppContext) -> ModelHandle { - let languages = Arc::new(LanguageRegistry::new()); + let languages = Arc::new(LanguageRegistry::test()); let http_client = client::test::FakeHttpClient::with_404_response(); let client = client::Client::new(http_client.clone()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); @@ -1017,7 +1017,7 @@ impl Project { .entry(key.clone()) .or_insert_with(|| { let language_server = self.languages.start_language_server( - &language, + language.clone(), worktree_path, self.client.http_client(), cx, diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 241217fe63..d05c9e2401 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -1065,7 +1065,7 @@ mod tests { #[gpui::test(iterations = 10)] async fn test_share_project(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { let (window_b, _) = cx_b.add_window(|_| EmptyView); - let lang_registry = Arc::new(LanguageRegistry::new()); + let lang_registry = Arc::new(LanguageRegistry::test()); let fs = FakeFs::new(cx_a.background()); cx_a.foreground().forbid_parking(); @@ -1197,7 +1197,7 @@ mod tests { #[gpui::test(iterations = 10)] async fn test_unshare_project(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { - let lang_registry = Arc::new(LanguageRegistry::new()); + let lang_registry = Arc::new(LanguageRegistry::test()); let fs = FakeFs::new(cx_a.background()); cx_a.foreground().forbid_parking(); @@ -1297,7 +1297,7 @@ mod tests { cx_b: &mut TestAppContext, cx_c: &mut TestAppContext, ) { - let lang_registry = Arc::new(LanguageRegistry::new()); + let lang_registry = Arc::new(LanguageRegistry::test()); let fs = FakeFs::new(cx_a.background()); cx_a.foreground().forbid_parking(); @@ -1471,7 +1471,7 @@ mod tests { #[gpui::test(iterations = 10)] async fn test_buffer_conflict_after_save(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { cx_a.foreground().forbid_parking(); - let lang_registry = Arc::new(LanguageRegistry::new()); + let lang_registry = Arc::new(LanguageRegistry::test()); let fs = FakeFs::new(cx_a.background()); // Connect to a server as 2 clients. @@ -1553,7 +1553,7 @@ mod tests { #[gpui::test(iterations = 10)] async fn test_buffer_reloading(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { cx_a.foreground().forbid_parking(); - let lang_registry = Arc::new(LanguageRegistry::new()); + let lang_registry = Arc::new(LanguageRegistry::test()); let fs = FakeFs::new(cx_a.background()); // Connect to a server as 2 clients. @@ -1635,7 +1635,7 @@ mod tests { cx_b: &mut TestAppContext, ) { cx_a.foreground().forbid_parking(); - let lang_registry = Arc::new(LanguageRegistry::new()); + let lang_registry = Arc::new(LanguageRegistry::test()); let fs = FakeFs::new(cx_a.background()); // Connect to a server as 2 clients. @@ -1714,7 +1714,7 @@ mod tests { cx_b: &mut TestAppContext, ) { cx_a.foreground().forbid_parking(); - let lang_registry = Arc::new(LanguageRegistry::new()); + let lang_registry = Arc::new(LanguageRegistry::test()); let fs = FakeFs::new(cx_a.background()); // Connect to a server as 2 clients. @@ -1786,7 +1786,7 @@ mod tests { #[gpui::test(iterations = 10)] async fn test_leaving_project(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { cx_a.foreground().forbid_parking(); - let lang_registry = Arc::new(LanguageRegistry::new()); + let lang_registry = Arc::new(LanguageRegistry::test()); let fs = FakeFs::new(cx_a.background()); // Connect to a server as 2 clients. @@ -1884,7 +1884,7 @@ mod tests { cx_b: &mut TestAppContext, ) { cx_a.foreground().forbid_parking(); - let mut lang_registry = Arc::new(LanguageRegistry::new()); + let mut lang_registry = Arc::new(LanguageRegistry::test()); let fs = FakeFs::new(cx_a.background()); // Set up a fake language server. @@ -2103,7 +2103,7 @@ mod tests { cx_b: &mut TestAppContext, ) { cx_a.foreground().forbid_parking(); - let mut lang_registry = Arc::new(LanguageRegistry::new()); + let mut lang_registry = Arc::new(LanguageRegistry::test()); let fs = FakeFs::new(cx_a.background()); // Set up a fake language server. @@ -2310,7 +2310,7 @@ mod tests { #[gpui::test(iterations = 10)] async fn test_formatting_buffer(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { cx_a.foreground().forbid_parking(); - let mut lang_registry = Arc::new(LanguageRegistry::new()); + let mut lang_registry = Arc::new(LanguageRegistry::test()); let fs = FakeFs::new(cx_a.background()); // Set up a fake language server. @@ -2409,7 +2409,7 @@ mod tests { #[gpui::test(iterations = 10)] async fn test_definition(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { cx_a.foreground().forbid_parking(); - let mut lang_registry = Arc::new(LanguageRegistry::new()); + let mut lang_registry = Arc::new(LanguageRegistry::test()); let fs = FakeFs::new(cx_a.background()); fs.insert_tree( "/root-1", @@ -2544,7 +2544,7 @@ mod tests { #[gpui::test(iterations = 10)] async fn test_references(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { cx_a.foreground().forbid_parking(); - let mut lang_registry = Arc::new(LanguageRegistry::new()); + let mut lang_registry = Arc::new(LanguageRegistry::test()); let fs = FakeFs::new(cx_a.background()); fs.insert_tree( "/root-1", @@ -2675,7 +2675,7 @@ mod tests { #[gpui::test(iterations = 10)] async fn test_project_search(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { cx_a.foreground().forbid_parking(); - let lang_registry = Arc::new(LanguageRegistry::new()); + let lang_registry = Arc::new(LanguageRegistry::test()); let fs = FakeFs::new(cx_a.background()); fs.insert_tree( "/root-1", @@ -2782,7 +2782,7 @@ mod tests { #[gpui::test(iterations = 10)] async fn test_document_highlights(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { cx_a.foreground().forbid_parking(); - let lang_registry = Arc::new(LanguageRegistry::new()); + let lang_registry = Arc::new(LanguageRegistry::test()); let fs = FakeFs::new(cx_a.background()); fs.insert_tree( "/root-1", @@ -2918,7 +2918,7 @@ mod tests { #[gpui::test(iterations = 10)] async fn test_project_symbols(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { cx_a.foreground().forbid_parking(); - let mut lang_registry = Arc::new(LanguageRegistry::new()); + let mut lang_registry = Arc::new(LanguageRegistry::test()); let fs = FakeFs::new(cx_a.background()); fs.insert_tree( "/code", @@ -3053,7 +3053,7 @@ mod tests { mut rng: StdRng, ) { cx_a.foreground().forbid_parking(); - let mut lang_registry = Arc::new(LanguageRegistry::new()); + let mut lang_registry = Arc::new(LanguageRegistry::test()); let fs = FakeFs::new(cx_a.background()); fs.insert_tree( "/root", @@ -3157,7 +3157,7 @@ mod tests { cx_b: &mut TestAppContext, ) { cx_a.foreground().forbid_parking(); - let mut lang_registry = Arc::new(LanguageRegistry::new()); + let mut lang_registry = Arc::new(LanguageRegistry::test()); let fs = FakeFs::new(cx_a.background()); let mut path_openers_b = Vec::new(); cx_b.update(|cx| editor::init(cx, &mut path_openers_b)); @@ -3393,7 +3393,7 @@ mod tests { #[gpui::test(iterations = 10)] async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { cx_a.foreground().forbid_parking(); - let mut lang_registry = Arc::new(LanguageRegistry::new()); + let mut lang_registry = Arc::new(LanguageRegistry::test()); let fs = FakeFs::new(cx_a.background()); let mut path_openers_b = Vec::new(); cx_b.update(|cx| editor::init(cx, &mut path_openers_b)); @@ -4024,7 +4024,7 @@ mod tests { cx_c: &mut TestAppContext, ) { cx_a.foreground().forbid_parking(); - let lang_registry = Arc::new(LanguageRegistry::new()); + let lang_registry = Arc::new(LanguageRegistry::test()); let fs = FakeFs::new(cx_a.background()); // Connect to a server as 3 clients. @@ -4171,7 +4171,7 @@ mod tests { let rng = Arc::new(Mutex::new(rng)); - let guest_lang_registry = Arc::new(LanguageRegistry::new()); + let guest_lang_registry = Arc::new(LanguageRegistry::test()); let (language_server_config, _fake_language_servers) = LanguageServerConfig::fake(); let fs = FakeFs::new(cx.background()); @@ -4202,7 +4202,7 @@ mod tests { Project::local( host.client.clone(), host.user_store.clone(), - Arc::new(LanguageRegistry::new()), + Arc::new(LanguageRegistry::test()), fs.clone(), cx, ) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 6633242aed..dee138b0ae 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -505,7 +505,7 @@ impl WorkspaceParams { #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &mut MutableAppContext) -> Self { let fs = project::FakeFs::new(cx.background().clone()); - let languages = Arc::new(LanguageRegistry::new()); + let languages = Arc::new(LanguageRegistry::test()); let http_client = client::test::FakeHttpClient::new(|_| async move { Ok(client::http::ServerResponse::new(404)) }); diff --git a/crates/zed/src/language.rs b/crates/zed/src/language.rs index 7b5d0de8b4..ca73b1ddab 100644 --- a/crates/zed/src/language.rs +++ b/crates/zed/src/language.rs @@ -2,6 +2,7 @@ use anyhow::{anyhow, Context, Result}; use async_compression::futures::bufread::GzipDecoder; use client::http::{self, HttpClient, Method}; use futures::{future::BoxFuture, FutureExt, StreamExt}; +use gpui::Task; pub use language::*; use lazy_static::lazy_static; use regex::Regex; @@ -531,8 +532,8 @@ impl LspAdapter for JsonLspAdapter { } } -pub fn build_language_registry() -> LanguageRegistry { - let mut languages = LanguageRegistry::new(); +pub fn build_language_registry(login_shell_env_loaded: Task<()>) -> LanguageRegistry { + let mut languages = LanguageRegistry::new(login_shell_env_loaded); languages.set_language_server_download_dir( dirs::home_dir() .expect("failed to determine home directory") diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index f855f4fb19..0a3ca28133 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -1,14 +1,17 @@ // Allow binary to be called Zed for a nice application menu when running executable direcly #![allow(non_snake_case)] +use anyhow::{anyhow, Context, Result}; use client::{self, http, ChannelList, UserStore}; use fs::OpenOptions; -use gpui::{App, AssetSource}; +use gpui::{App, AssetSource, Task}; use log::LevelFilter; use parking_lot::Mutex; use simplelog::SimpleLogger; -use std::{fs, path::PathBuf, sync::Arc}; +use smol::process::Command; +use std::{env, fs, path::PathBuf, sync::Arc}; use theme::{ThemeRegistry, DEFAULT_THEME_NAME}; +use util::ResultExt; use workspace::{self, settings, AppState, OpenNew, OpenParams, OpenPaths, Settings}; use zed::{ self, assets::Assets, build_window_options, build_workspace, fs::RealFs, language, menus, @@ -39,7 +42,16 @@ fn main() { }, ); let (settings_tx, settings) = postage::watch::channel_with(settings); - let languages = Arc::new(language::build_language_registry()); + + let login_shell_env_loaded = if stdout_is_a_pty() { + Task::ready(()) + } else { + app.background().spawn(async { + load_login_shell_environment().await.log_err(); + }) + }; + + let languages = Arc::new(language::build_language_registry(login_shell_env_loaded)); languages.set_theme(&settings.borrow().theme.editor.syntax); app.run(move |cx| { @@ -127,12 +139,47 @@ fn init_logger() { } } +async fn load_login_shell_environment() -> Result<()> { + let marker = "ZED_LOGIN_SHELL_START"; + let shell = env::var("SHELL").context( + "SHELL environment variable is not assigned so we can't source login environment variables", + )?; + let output = Command::new(&shell) + .args(["-lic", &format!("echo {marker} && /usr/bin/env")]) + .output() + .await + .context("failed to spawn login shell to source login environment variables")?; + if !output.status.success() { + Err(anyhow!("login shell exited with error"))?; + } + + let stdout = String::from_utf8_lossy(&output.stdout); + + if let Some(env_output_start) = stdout.find(marker) { + let env_output = &stdout[env_output_start + marker.len()..]; + for line in env_output.lines() { + if let Some(separator_index) = line.find('=') { + let key = &line[..separator_index]; + let value = &line[separator_index + 1..]; + env::set_var(key, value); + } + } + log::info!( + "set environment variables from shell:{}, path:{}", + shell, + env::var("PATH").unwrap_or_default(), + ); + } + + Ok(()) +} + fn stdout_is_a_pty() -> bool { unsafe { libc::isatty(libc::STDOUT_FILENO as i32) != 0 } } fn collect_path_args() -> Vec { - std::env::args() + env::args() .skip(1) .filter_map(|arg| match fs::canonicalize(arg) { Ok(path) => Some(path), diff --git a/crates/zed/src/test.rs b/crates/zed/src/test.rs index 11205fe25b..7504bc05c1 100644 --- a/crates/zed/src/test.rs +++ b/crates/zed/src/test.rs @@ -25,7 +25,7 @@ pub fn test_app_state(cx: &mut MutableAppContext) -> Arc { let http = FakeHttpClient::with_404_response(); let client = Client::new(http.clone()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx)); - let languages = LanguageRegistry::new(); + let languages = LanguageRegistry::test(); languages.add(Arc::new(language::Language::new( language::LanguageConfig { name: "Rust".into(),