main.rs

  1// Allow binary to be called Zed for a nice application menu when running executable direcly
  2#![allow(non_snake_case)]
  3
  4use anyhow::{anyhow, Context, Result};
  5use assets::Assets;
  6use client::{self, http, ChannelList, UserStore};
  7use fs::OpenOptions;
  8use futures::{channel::oneshot, StreamExt};
  9use gpui::{App, AssetSource, Task};
 10use log::LevelFilter;
 11use parking_lot::Mutex;
 12use project::Fs;
 13use settings::{self, KeymapFile, Settings, SettingsFileContent};
 14use smol::process::Command;
 15use std::{env, fs, path::PathBuf, sync::Arc};
 16use theme::{ThemeRegistry, DEFAULT_THEME_NAME};
 17use util::ResultExt;
 18use workspace::{self, AppState, OpenNew, OpenPaths};
 19use zed::{
 20    self, build_window_options, build_workspace,
 21    fs::RealFs,
 22    languages, menus,
 23    settings_file::{settings_from_files, watch_keymap_file, WatchedJsonFile},
 24};
 25
 26fn main() {
 27    init_logger();
 28
 29    let app = gpui::App::new(Assets).unwrap();
 30    load_embedded_fonts(&app);
 31
 32    let fs = Arc::new(RealFs);
 33    let themes = ThemeRegistry::new(Assets, app.font_cache());
 34    let theme = themes.get(DEFAULT_THEME_NAME).unwrap();
 35    let default_settings = Settings::new("Zed Mono", &app.font_cache(), theme)
 36        .unwrap()
 37        .with_overrides(
 38            languages::PLAIN_TEXT.name(),
 39            settings::LanguageOverride {
 40                soft_wrap: Some(settings::SoftWrap::PreferredLineLength),
 41                ..Default::default()
 42            },
 43        )
 44        .with_overrides(
 45            "Markdown",
 46            settings::LanguageOverride {
 47                soft_wrap: Some(settings::SoftWrap::PreferredLineLength),
 48                ..Default::default()
 49            },
 50        )
 51        .with_overrides(
 52            "Rust",
 53            settings::LanguageOverride {
 54                tab_size: Some(4),
 55                ..Default::default()
 56            },
 57        )
 58        .with_overrides(
 59            "TypeScript",
 60            settings::LanguageOverride {
 61                tab_size: Some(2),
 62                ..Default::default()
 63            },
 64        );
 65
 66    let config_files = load_config_files(&app, fs.clone());
 67
 68    let login_shell_env_loaded = if stdout_is_a_pty() {
 69        Task::ready(())
 70    } else {
 71        app.background().spawn(async {
 72            load_login_shell_environment().await.log_err();
 73        })
 74    };
 75
 76    app.run(move |cx| {
 77        let http = http::client();
 78        let client = client::Client::new(http.clone());
 79        let mut languages = languages::build_language_registry(login_shell_env_loaded);
 80        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx));
 81        let channel_list =
 82            cx.add_model(|cx| ChannelList::new(user_store.clone(), client.clone(), cx));
 83
 84        project::Project::init(&client);
 85        client::Channel::init(&client);
 86        client::init(client.clone(), cx);
 87        workspace::init(&client, cx);
 88        editor::init(cx);
 89        go_to_line::init(cx);
 90        file_finder::init(cx);
 91        chat_panel::init(cx);
 92        outline::init(cx);
 93        project_symbols::init(cx);
 94        project_panel::init(cx);
 95        diagnostics::init(cx);
 96        search::init(cx);
 97        vim::init(cx);
 98        cx.spawn({
 99            let client = client.clone();
100            |cx| async move {
101                if stdout_is_a_pty() {
102                    if client::IMPERSONATE_LOGIN.is_some() {
103                        client.authenticate_and_connect(false, &cx).await?;
104                    }
105                } else {
106                    if client.has_keychain_credentials(&cx) {
107                        client.authenticate_and_connect(true, &cx).await?;
108                    }
109                }
110                Ok::<_, anyhow::Error>(())
111            }
112        })
113        .detach_and_log_err(cx);
114
115        let (settings_file, keymap_file) = cx.background().block(config_files).unwrap();
116        let mut settings_rx = settings_from_files(
117            default_settings,
118            vec![settings_file],
119            themes.clone(),
120            cx.font_cache().clone(),
121        );
122
123        cx.spawn(|cx| watch_keymap_file(keymap_file, cx)).detach();
124
125        let settings = cx.background().block(settings_rx.next()).unwrap();
126        cx.spawn(|mut cx| async move {
127            while let Some(settings) = settings_rx.next().await {
128                cx.update(|cx| {
129                    cx.update_global(|s, _| *s = settings);
130                    cx.refresh_windows();
131                });
132            }
133        })
134        .detach();
135
136        languages.set_language_server_download_dir(zed::ROOT_PATH.clone());
137        languages.set_theme(&settings.theme.editor.syntax);
138        cx.set_global(settings);
139
140        let app_state = Arc::new(AppState {
141            languages: Arc::new(languages),
142            themes,
143            channel_list,
144            client,
145            user_store,
146            fs,
147            build_window_options: &build_window_options,
148            build_workspace: &build_workspace,
149        });
150        journal::init(app_state.clone(), cx);
151        theme_selector::init(cx);
152        zed::init(&app_state, cx);
153
154        cx.set_menus(menus::menus(&app_state.clone()));
155
156        if stdout_is_a_pty() {
157            cx.platform().activate(true);
158        }
159
160        let paths = collect_path_args();
161        if paths.is_empty() {
162            cx.dispatch_global_action(OpenNew(app_state.clone()));
163        } else {
164            cx.dispatch_global_action(OpenPaths { paths, app_state });
165        }
166    });
167}
168
169fn init_logger() {
170    if stdout_is_a_pty() {
171        env_logger::init();
172    } else {
173        let level = LevelFilter::Info;
174        let log_dir_path = dirs::home_dir()
175            .expect("could not locate home directory for logging")
176            .join("Library/Logs/");
177        let log_file_path = log_dir_path.join("Zed.log");
178        fs::create_dir_all(&log_dir_path).expect("could not create log directory");
179        let log_file = OpenOptions::new()
180            .create(true)
181            .append(true)
182            .open(log_file_path)
183            .expect("could not open logfile");
184        simplelog::WriteLogger::init(level, simplelog::Config::default(), log_file)
185            .expect("could not initialize logger");
186        log_panics::init();
187    }
188}
189
190async fn load_login_shell_environment() -> Result<()> {
191    let marker = "ZED_LOGIN_SHELL_START";
192    let shell = env::var("SHELL").context(
193        "SHELL environment variable is not assigned so we can't source login environment variables",
194    )?;
195    let output = Command::new(&shell)
196        .args(["-lic", &format!("echo {marker} && /usr/bin/env")])
197        .output()
198        .await
199        .context("failed to spawn login shell to source login environment variables")?;
200    if !output.status.success() {
201        Err(anyhow!("login shell exited with error"))?;
202    }
203
204    let stdout = String::from_utf8_lossy(&output.stdout);
205
206    if let Some(env_output_start) = stdout.find(marker) {
207        let env_output = &stdout[env_output_start + marker.len()..];
208        for line in env_output.lines() {
209            if let Some(separator_index) = line.find('=') {
210                let key = &line[..separator_index];
211                let value = &line[separator_index + 1..];
212                env::set_var(key, value);
213            }
214        }
215        log::info!(
216            "set environment variables from shell:{}, path:{}",
217            shell,
218            env::var("PATH").unwrap_or_default(),
219        );
220    }
221
222    Ok(())
223}
224
225fn stdout_is_a_pty() -> bool {
226    unsafe { libc::isatty(libc::STDOUT_FILENO as i32) != 0 }
227}
228
229fn collect_path_args() -> Vec<PathBuf> {
230    env::args()
231        .skip(1)
232        .filter_map(|arg| match fs::canonicalize(arg) {
233            Ok(path) => Some(path),
234            Err(error) => {
235                log::error!("error parsing path argument: {}", error);
236                None
237            }
238        })
239        .collect::<Vec<_>>()
240}
241
242fn load_embedded_fonts(app: &App) {
243    let font_paths = Assets.list("fonts");
244    let embedded_fonts = Mutex::new(Vec::new());
245    smol::block_on(app.background().scoped(|scope| {
246        for font_path in &font_paths {
247            scope.spawn(async {
248                let font_path = &*font_path;
249                let font_bytes = Assets.load(&font_path).unwrap().to_vec();
250                embedded_fonts.lock().push(Arc::from(font_bytes));
251            });
252        }
253    }));
254    app.platform()
255        .fonts()
256        .add_fonts(&embedded_fonts.into_inner())
257        .unwrap();
258}
259
260fn load_config_files(
261    app: &App,
262    fs: Arc<dyn Fs>,
263) -> oneshot::Receiver<(
264    WatchedJsonFile<SettingsFileContent>,
265    WatchedJsonFile<KeymapFile>,
266)> {
267    let executor = app.background();
268    let (tx, rx) = oneshot::channel();
269    executor
270        .clone()
271        .spawn(async move {
272            let settings_file =
273                WatchedJsonFile::new(fs.clone(), &executor, zed::SETTINGS_PATH.clone()).await;
274            let keymap_file = WatchedJsonFile::new(fs, &executor, zed::KEYMAP_PATH.clone()).await;
275            tx.send((settings_file, keymap_file)).ok()
276        })
277        .detach();
278    rx
279}