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 cli::{
  7    ipc::{self, IpcSender},
  8    CliRequest, CliResponse, IpcHandshake,
  9};
 10use client::{self, http, ChannelList, UserStore};
 11use fs::OpenOptions;
 12use futures::{
 13    channel::{mpsc, oneshot},
 14    FutureExt, SinkExt, StreamExt,
 15};
 16use gpui::{App, AssetSource, AsyncAppContext, Task};
 17use log::LevelFilter;
 18use parking_lot::Mutex;
 19use project::Fs;
 20use settings::{self, KeymapFileContent, Settings, SettingsFileContent};
 21use smol::process::Command;
 22use std::{env, fs, path::PathBuf, sync::Arc, thread, time::Duration};
 23use theme::{ThemeRegistry, DEFAULT_THEME_NAME};
 24use util::ResultExt;
 25use workspace::{self, AppState, OpenNew, OpenPaths};
 26use zed::{
 27    self, build_window_options, build_workspace,
 28    fs::RealFs,
 29    languages, menus,
 30    settings_file::{settings_from_files, watch_keymap_file, WatchedJsonFile},
 31};
 32
 33fn main() {
 34    init_logger();
 35
 36    let mut app = gpui::App::new(Assets).unwrap();
 37    load_embedded_fonts(&app);
 38
 39    let fs = Arc::new(RealFs);
 40    let themes = ThemeRegistry::new(Assets, app.font_cache());
 41    let theme = themes.get(DEFAULT_THEME_NAME).unwrap();
 42    let default_settings = Settings::new("Zed Mono", &app.font_cache(), theme)
 43        .unwrap()
 44        .with_overrides(
 45            languages::PLAIN_TEXT.name(),
 46            settings::LanguageOverride {
 47                soft_wrap: Some(settings::SoftWrap::PreferredLineLength),
 48                ..Default::default()
 49            },
 50        )
 51        .with_overrides(
 52            "Markdown",
 53            settings::LanguageOverride {
 54                soft_wrap: Some(settings::SoftWrap::PreferredLineLength),
 55                ..Default::default()
 56            },
 57        )
 58        .with_overrides(
 59            "Rust",
 60            settings::LanguageOverride {
 61                tab_size: Some(4),
 62                ..Default::default()
 63            },
 64        )
 65        .with_overrides(
 66            "JavaScript",
 67            settings::LanguageOverride {
 68                tab_size: Some(2),
 69                ..Default::default()
 70            },
 71        )
 72        .with_overrides(
 73            "TypeScript",
 74            settings::LanguageOverride {
 75                tab_size: Some(2),
 76                ..Default::default()
 77            },
 78        )
 79        .with_overrides(
 80            "TSX",
 81            settings::LanguageOverride {
 82                tab_size: Some(2),
 83                ..Default::default()
 84            },
 85        );
 86
 87    let config_files = load_config_files(&app, fs.clone());
 88
 89    let login_shell_env_loaded = if stdout_is_a_pty() {
 90        Task::ready(())
 91    } else {
 92        app.background().spawn(async {
 93            load_login_shell_environment().await.log_err();
 94        })
 95    };
 96
 97    let (cli_connections_tx, mut cli_connections_rx) = mpsc::unbounded();
 98    app.on_open_urls(move |urls, _| {
 99        if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://")) {
100            if let Some(cli_connection) = connect_to_cli(server_name).log_err() {
101                cli_connections_tx
102                    .unbounded_send(cli_connection)
103                    .map_err(|_| anyhow!("no listener for cli connections"))
104                    .log_err();
105            };
106        }
107    });
108
109    app.run(move |cx| {
110        let http = http::client();
111        let client = client::Client::new(http.clone());
112        let mut languages = languages::build_language_registry(login_shell_env_loaded);
113        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx));
114        let channel_list =
115            cx.add_model(|cx| ChannelList::new(user_store.clone(), client.clone(), cx));
116
117        auto_update::init(http, client::ZED_SERVER_URL.clone(), cx);
118        project::Project::init(&client);
119        client::Channel::init(&client);
120        client::init(client.clone(), cx);
121        command_palette::init(cx);
122        workspace::init(&client, cx);
123        editor::init(cx);
124        go_to_line::init(cx);
125        file_finder::init(cx);
126        chat_panel::init(cx);
127        outline::init(cx);
128        project_symbols::init(cx);
129        project_panel::init(cx);
130        diagnostics::init(cx);
131        search::init(cx);
132        vim::init(cx);
133        cx.spawn({
134            let client = client.clone();
135            |cx| async move {
136                if stdout_is_a_pty() {
137                    if client::IMPERSONATE_LOGIN.is_some() {
138                        client.authenticate_and_connect(false, &cx).await?;
139                    }
140                } else {
141                    if client.has_keychain_credentials(&cx) {
142                        client.authenticate_and_connect(true, &cx).await?;
143                    }
144                }
145                Ok::<_, anyhow::Error>(())
146            }
147        })
148        .detach_and_log_err(cx);
149
150        let (settings_file, keymap_file) = cx.background().block(config_files).unwrap();
151        let mut settings_rx = settings_from_files(
152            default_settings,
153            vec![settings_file],
154            themes.clone(),
155            cx.font_cache().clone(),
156        );
157
158        cx.spawn(|cx| watch_keymap_file(keymap_file, cx)).detach();
159
160        let settings = cx.background().block(settings_rx.next()).unwrap();
161        cx.spawn(|mut cx| async move {
162            while let Some(settings) = settings_rx.next().await {
163                cx.update(|cx| {
164                    cx.update_global(|s, _| *s = settings);
165                    cx.refresh_windows();
166                });
167            }
168        })
169        .detach();
170
171        languages.set_language_server_download_dir(zed::ROOT_PATH.clone());
172        languages.set_theme(&settings.theme.editor.syntax);
173        cx.set_global(settings);
174
175        let app_state = Arc::new(AppState {
176            languages: Arc::new(languages),
177            themes,
178            channel_list,
179            client,
180            user_store,
181            fs,
182            build_window_options,
183            build_workspace,
184        });
185        journal::init(app_state.clone(), cx);
186        theme_selector::init(cx);
187        zed::init(&app_state, cx);
188
189        cx.set_menus(menus::menus(&app_state.clone()));
190
191        if stdout_is_a_pty() {
192            cx.platform().activate(true);
193            let paths = collect_path_args();
194            if paths.is_empty() {
195                cx.dispatch_global_action(OpenNew(app_state.clone()));
196            } else {
197                cx.dispatch_global_action(OpenPaths { paths, app_state });
198            }
199        } else {
200            if let Ok(Some(connection)) = cli_connections_rx.try_next() {
201                cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx))
202                    .detach();
203            } else {
204                cx.dispatch_global_action(OpenNew(app_state.clone()));
205            }
206            cx.spawn(|cx| async move {
207                while let Some(connection) = cli_connections_rx.next().await {
208                    handle_cli_connection(connection, app_state.clone(), cx.clone()).await;
209                }
210            })
211            .detach();
212        }
213    });
214}
215
216fn init_logger() {
217    if stdout_is_a_pty() {
218        env_logger::init();
219    } else {
220        let level = LevelFilter::Info;
221        let log_dir_path = dirs::home_dir()
222            .expect("could not locate home directory for logging")
223            .join("Library/Logs/");
224        let log_file_path = log_dir_path.join("Zed.log");
225        fs::create_dir_all(&log_dir_path).expect("could not create log directory");
226        let log_file = OpenOptions::new()
227            .create(true)
228            .append(true)
229            .open(log_file_path)
230            .expect("could not open logfile");
231        simplelog::WriteLogger::init(level, simplelog::Config::default(), log_file)
232            .expect("could not initialize logger");
233        log_panics::init();
234    }
235}
236
237async fn load_login_shell_environment() -> Result<()> {
238    let marker = "ZED_LOGIN_SHELL_START";
239    let shell = env::var("SHELL").context(
240        "SHELL environment variable is not assigned so we can't source login environment variables",
241    )?;
242    let output = Command::new(&shell)
243        .args(["-lic", &format!("echo {marker} && /usr/bin/env")])
244        .output()
245        .await
246        .context("failed to spawn login shell to source login environment variables")?;
247    if !output.status.success() {
248        Err(anyhow!("login shell exited with error"))?;
249    }
250
251    let stdout = String::from_utf8_lossy(&output.stdout);
252
253    if let Some(env_output_start) = stdout.find(marker) {
254        let env_output = &stdout[env_output_start + marker.len()..];
255        for line in env_output.lines() {
256            if let Some(separator_index) = line.find('=') {
257                let key = &line[..separator_index];
258                let value = &line[separator_index + 1..];
259                env::set_var(key, value);
260            }
261        }
262        log::info!(
263            "set environment variables from shell:{}, path:{}",
264            shell,
265            env::var("PATH").unwrap_or_default(),
266        );
267    }
268
269    Ok(())
270}
271
272fn stdout_is_a_pty() -> bool {
273    unsafe { libc::isatty(libc::STDOUT_FILENO as i32) != 0 }
274}
275
276fn collect_path_args() -> Vec<PathBuf> {
277    env::args()
278        .skip(1)
279        .filter_map(|arg| match fs::canonicalize(arg) {
280            Ok(path) => Some(path),
281            Err(error) => {
282                log::error!("error parsing path argument: {}", error);
283                None
284            }
285        })
286        .collect::<Vec<_>>()
287}
288
289fn load_embedded_fonts(app: &App) {
290    let font_paths = Assets.list("fonts");
291    let embedded_fonts = Mutex::new(Vec::new());
292    smol::block_on(app.background().scoped(|scope| {
293        for font_path in &font_paths {
294            scope.spawn(async {
295                let font_path = &*font_path;
296                let font_bytes = Assets.load(&font_path).unwrap().to_vec();
297                embedded_fonts.lock().push(Arc::from(font_bytes));
298            });
299        }
300    }));
301    app.platform()
302        .fonts()
303        .add_fonts(&embedded_fonts.into_inner())
304        .unwrap();
305}
306
307fn load_config_files(
308    app: &App,
309    fs: Arc<dyn Fs>,
310) -> oneshot::Receiver<(
311    WatchedJsonFile<SettingsFileContent>,
312    WatchedJsonFile<KeymapFileContent>,
313)> {
314    let executor = app.background();
315    let (tx, rx) = oneshot::channel();
316    executor
317        .clone()
318        .spawn(async move {
319            let settings_file =
320                WatchedJsonFile::new(fs.clone(), &executor, zed::SETTINGS_PATH.clone()).await;
321            let keymap_file = WatchedJsonFile::new(fs, &executor, zed::KEYMAP_PATH.clone()).await;
322            tx.send((settings_file, keymap_file)).ok()
323        })
324        .detach();
325    rx
326}
327
328fn connect_to_cli(
329    server_name: &str,
330) -> Result<(mpsc::Receiver<CliRequest>, IpcSender<CliResponse>)> {
331    let handshake_tx = cli::ipc::IpcSender::<IpcHandshake>::connect(server_name.to_string())
332        .context("error connecting to cli")?;
333    let (request_tx, request_rx) = ipc::channel::<CliRequest>()?;
334    let (response_tx, response_rx) = ipc::channel::<CliResponse>()?;
335
336    handshake_tx
337        .send(IpcHandshake {
338            requests: request_tx,
339            responses: response_rx,
340        })
341        .context("error sending ipc handshake")?;
342
343    let (mut async_request_tx, async_request_rx) =
344        futures::channel::mpsc::channel::<CliRequest>(16);
345    thread::spawn(move || {
346        while let Ok(cli_request) = request_rx.recv() {
347            if smol::block_on(async_request_tx.send(cli_request)).is_err() {
348                break;
349            }
350        }
351        Ok::<_, anyhow::Error>(())
352    });
353
354    Ok((async_request_rx, response_tx))
355}
356
357async fn handle_cli_connection(
358    (mut requests, responses): (mpsc::Receiver<CliRequest>, IpcSender<CliResponse>),
359    app_state: Arc<AppState>,
360    mut cx: AsyncAppContext,
361) {
362    if let Some(request) = requests.next().await {
363        match request {
364            CliRequest::Open { paths, wait } => {
365                let (workspace, items) = cx
366                    .update(|cx| workspace::open_paths(&paths, &app_state, cx))
367                    .await;
368
369                let mut errored = false;
370                let mut futures = Vec::new();
371                cx.update(|cx| {
372                    for (item, path) in items.into_iter().zip(&paths) {
373                        match item {
374                            Some(Ok(item)) => {
375                                let released = oneshot::channel();
376                                item.on_release(
377                                    cx,
378                                    Box::new(move |_| {
379                                        let _ = released.0.send(());
380                                    }),
381                                )
382                                .detach();
383                                futures.push(released.1);
384                            }
385                            Some(Err(err)) => {
386                                responses
387                                    .send(CliResponse::Stderr {
388                                        message: format!("error opening {:?}: {}", path, err),
389                                    })
390                                    .log_err();
391                                errored = true;
392                            }
393                            None => {}
394                        }
395                    }
396                });
397
398                if wait {
399                    let background = cx.background();
400                    let wait = async move {
401                        if paths.is_empty() {
402                            let (done_tx, done_rx) = oneshot::channel();
403                            let _subscription = cx.update(|cx| {
404                                cx.observe_release(&workspace, move |_, _| {
405                                    let _ = done_tx.send(());
406                                })
407                            });
408                            drop(workspace);
409                            let _ = done_rx.await;
410                        } else {
411                            let _ = futures::future::try_join_all(futures).await;
412                        };
413                    }
414                    .fuse();
415                    futures::pin_mut!(wait);
416
417                    loop {
418                        // Repeatedly check if CLI is still open to avoid wasting resources
419                        // waiting for files or workspaces to close.
420                        let mut timer = background.timer(Duration::from_secs(1)).fuse();
421                        futures::select_biased! {
422                            _ = wait => break,
423                            _ = timer => {
424                                if responses.send(CliResponse::Ping).is_err() {
425                                    break;
426                                }
427                            }
428                        }
429                    }
430                }
431
432                responses
433                    .send(CliResponse::Exit {
434                        status: if errored { 1 } else { 0 },
435                    })
436                    .log_err();
437            }
438        }
439    }
440}