main.rs

   1// Allow binary to be called Zed for a nice application menu when running executable directly
   2#![allow(non_snake_case)]
   3// Disable command line from opening on release mode
   4#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
   5
   6mod reliability;
   7mod zed;
   8
   9use anyhow::{anyhow, Context as _, Result};
  10use clap::{command, Parser};
  11use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
  12use client::{parse_zed_link, Client, DevServerToken, UserStore};
  13use collab_ui::channel_view::ChannelView;
  14use db::kvp::KEY_VALUE_STORE;
  15use editor::Editor;
  16use env_logger::Builder;
  17use fs::{Fs, RealFs};
  18use futures::{future, StreamExt};
  19use git::GitHostingProviderRegistry;
  20use gpui::{
  21    App, AppContext, AsyncAppContext, Context, Global, Task, UpdateGlobal as _, VisualContext,
  22};
  23use image_viewer;
  24use language::LanguageRegistry;
  25use log::LevelFilter;
  26
  27use assets::Assets;
  28use node_runtime::RealNodeRuntime;
  29use parking_lot::Mutex;
  30use release_channel::{AppCommitSha, AppVersion};
  31use session::Session;
  32use settings::{handle_settings_file_changes, watch_config_file, Settings, SettingsStore};
  33use simplelog::ConfigBuilder;
  34use smol::process::Command;
  35use std::{
  36    env,
  37    fs::OpenOptions,
  38    io::{IsTerminal, Write},
  39    path::Path,
  40    process,
  41    sync::Arc,
  42};
  43use theme::{ActiveTheme, SystemAppearance, ThemeRegistry, ThemeSettings};
  44use util::{maybe, parse_env_output, with_clone, ResultExt, TryFutureExt};
  45use uuid::Uuid;
  46use welcome::{show_welcome_view, BaseKeymap, FIRST_OPEN};
  47use workspace::{AppState, WorkspaceSettings, WorkspaceStore};
  48use zed::{
  49    app_menus, build_window_options, handle_cli_connection, handle_keymap_file_changes,
  50    initialize_workspace, open_paths_with_positions, open_ssh_paths, OpenListener, OpenRequest,
  51};
  52
  53use crate::zed::inline_completion_registry;
  54
  55#[cfg(feature = "mimalloc")]
  56#[global_allocator]
  57static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
  58
  59fn fail_to_launch(e: anyhow::Error) {
  60    eprintln!("Zed failed to launch: {e:?}");
  61    App::new().run(move |cx| {
  62        if let Ok(window) = cx.open_window(gpui::WindowOptions::default(), |cx| cx.new_view(|_| gpui::Empty)) {
  63            window.update(cx, |_, cx| {
  64                let response = cx.prompt(gpui::PromptLevel::Critical, "Zed failed to launch", Some(&format!("{e}\n\nFor help resolving this, please open an issue on https://github.com/zed-industries/zed")), &["Exit"]);
  65
  66                cx.spawn(|_, mut cx| async move {
  67                    response.await?;
  68                    cx.update(|cx| {
  69                        cx.quit()
  70                    })
  71                }).detach_and_log_err(cx);
  72            }).log_err();
  73        } else {
  74            fail_to_open_window(e, cx)
  75        }
  76    })
  77}
  78
  79fn fail_to_open_window_async(e: anyhow::Error, cx: &mut AsyncAppContext) {
  80    cx.update(|cx| fail_to_open_window(e, cx)).log_err();
  81}
  82
  83fn fail_to_open_window(e: anyhow::Error, _cx: &mut AppContext) {
  84    eprintln!(
  85        "Zed failed to open a window: {e:?}. See https://zed.dev/docs/linux for troubleshooting steps."
  86    );
  87    #[cfg(not(target_os = "linux"))]
  88    {
  89        process::exit(1);
  90    }
  91
  92    #[cfg(target_os = "linux")]
  93    {
  94        use ashpd::desktop::notification::{Notification, NotificationProxy, Priority};
  95        _cx.spawn(|_cx| async move {
  96            let Ok(proxy) = NotificationProxy::new().await else {
  97                process::exit(1);
  98            };
  99
 100            let notification_id = "dev.zed.Oops";
 101            proxy
 102                .add_notification(
 103                    notification_id,
 104                    Notification::new("Zed failed to launch")
 105                        .body(Some(
 106                            format!(
 107                                "{e:?}. See https://zed.dev/docs/linux for troubleshooting steps."
 108                            )
 109                            .as_str(),
 110                        ))
 111                        .priority(Priority::High)
 112                        .icon(ashpd::desktop::Icon::with_names(&[
 113                            "dialog-question-symbolic",
 114                        ])),
 115                )
 116                .await
 117                .ok();
 118
 119            process::exit(1);
 120        })
 121        .detach();
 122    }
 123}
 124
 125enum AppMode {
 126    Headless(DevServerToken),
 127    Ui,
 128}
 129impl Global for AppMode {}
 130
 131fn init_headless(
 132    dev_server_token: DevServerToken,
 133    app_state: Arc<AppState>,
 134    cx: &mut AppContext,
 135) -> Task<Result<()>> {
 136    match cx.try_global::<AppMode>() {
 137        Some(AppMode::Headless(token)) if token == &dev_server_token => return Task::ready(Ok(())),
 138        Some(_) => {
 139            return Task::ready(Err(anyhow!(
 140                "zed is already running. Use `kill {}` to stop it",
 141                process::id()
 142            )))
 143        }
 144        None => {
 145            cx.set_global(AppMode::Headless(dev_server_token.clone()));
 146        }
 147    };
 148    let client = app_state.client.clone();
 149    client.set_dev_server_token(dev_server_token);
 150    headless::init(
 151        client.clone(),
 152        headless::AppState {
 153            languages: app_state.languages.clone(),
 154            user_store: app_state.user_store.clone(),
 155            fs: app_state.fs.clone(),
 156            node_runtime: app_state.node_runtime.clone(),
 157        },
 158        cx,
 159    )
 160}
 161
 162// init_common is called for both headless and normal mode.
 163fn init_common(app_state: Arc<AppState>, cx: &mut AppContext) {
 164    SystemAppearance::init(cx);
 165    theme::init(theme::LoadThemes::All(Box::new(Assets)), cx);
 166    command_palette::init(cx);
 167    language_model::init(app_state.client.clone(), cx);
 168    snippet_provider::init(cx);
 169    supermaven::init(app_state.client.clone(), cx);
 170    inline_completion_registry::init(app_state.client.telemetry().clone(), cx);
 171    assistant::init(app_state.fs.clone(), app_state.client.clone(), cx);
 172    repl::init(app_state.fs.clone(), cx);
 173    extension::init(
 174        app_state.fs.clone(),
 175        app_state.client.clone(),
 176        app_state.node_runtime.clone(),
 177        app_state.languages.clone(),
 178        ThemeRegistry::global(cx),
 179        cx,
 180    );
 181}
 182
 183fn init_ui(app_state: Arc<AppState>, cx: &mut AppContext) -> Result<()> {
 184    match cx.try_global::<AppMode>() {
 185        Some(AppMode::Headless(_)) => {
 186            return Err(anyhow!(
 187                "zed is already running in headless mode. Use `kill {}` to stop it",
 188                process::id()
 189            ))
 190        }
 191        Some(AppMode::Ui) => return Ok(()),
 192        None => {
 193            cx.set_global(AppMode::Ui);
 194        }
 195    };
 196
 197    load_embedded_fonts(cx);
 198
 199    #[cfg(target_os = "linux")]
 200    crate::zed::linux_prompts::init(cx);
 201
 202    app_state.languages.set_theme(cx.theme().clone());
 203    editor::init(cx);
 204    image_viewer::init(cx);
 205    diagnostics::init(cx);
 206
 207    audio::init(Assets, cx);
 208    workspace::init(app_state.clone(), cx);
 209
 210    recent_projects::init(cx);
 211    go_to_line::init(cx);
 212    file_finder::init(cx);
 213    tab_switcher::init(cx);
 214    dev_server_projects::init(app_state.client.clone(), cx);
 215    outline::init(cx);
 216    project_symbols::init(cx);
 217    project_panel::init(Assets, cx);
 218    outline_panel::init(Assets, cx);
 219    tasks_ui::init(cx);
 220    channel::init(&app_state.client.clone(), app_state.user_store.clone(), cx);
 221    search::init(cx);
 222    vim::init(cx);
 223    terminal_view::init(cx);
 224    journal::init(app_state.clone(), cx);
 225    language_selector::init(cx);
 226    theme_selector::init(cx);
 227    language_tools::init(cx);
 228    call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
 229    notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx);
 230    collab_ui::init(&app_state, cx);
 231    feedback::init(cx);
 232    markdown_preview::init(cx);
 233    welcome::init(cx);
 234    settings_ui::init(cx);
 235    extensions_ui::init(cx);
 236
 237    // Initialize each completion provider. Settings are used for toggling between them.
 238    let copilot_language_server_id = app_state.languages.next_language_server_id();
 239    copilot::init(
 240        copilot_language_server_id,
 241        app_state.client.http_client(),
 242        app_state.node_runtime.clone(),
 243        cx,
 244    );
 245
 246    cx.observe_global::<SettingsStore>({
 247        let languages = app_state.languages.clone();
 248        let http = app_state.client.http_client();
 249        let client = app_state.client.clone();
 250
 251        move |cx| {
 252            for &mut window in cx.windows().iter_mut() {
 253                let background_appearance = cx.theme().window_background_appearance();
 254                window
 255                    .update(cx, |_, cx| {
 256                        cx.set_background_appearance(background_appearance)
 257                    })
 258                    .ok();
 259            }
 260            languages.set_theme(cx.theme().clone());
 261            let new_host = &client::ClientSettings::get_global(cx).server_url;
 262            if &http.base_url() != new_host {
 263                http.set_base_url(new_host);
 264                if client.status().borrow().is_connected() {
 265                    client.reconnect(&cx.to_async());
 266                }
 267            }
 268        }
 269    })
 270    .detach();
 271    let telemetry = app_state.client.telemetry();
 272    telemetry.report_setting_event("theme", cx.theme().name.to_string());
 273    telemetry.report_setting_event("keymap", BaseKeymap::get_global(cx).to_string());
 274    telemetry.flush_events();
 275
 276    let fs = app_state.fs.clone();
 277    load_user_themes_in_background(fs.clone(), cx);
 278    watch_themes(fs.clone(), cx);
 279    watch_languages(fs.clone(), app_state.languages.clone(), cx);
 280    watch_file_types(fs.clone(), cx);
 281
 282    cx.set_menus(app_menus());
 283    initialize_workspace(app_state.clone(), cx);
 284
 285    cx.activate(true);
 286
 287    cx.spawn(|cx| async move { authenticate(app_state.client.clone(), &cx).await })
 288        .detach_and_log_err(cx);
 289
 290    Ok(())
 291}
 292
 293fn main() {
 294    menu::init();
 295    zed_actions::init();
 296
 297    if let Err(e) = init_paths() {
 298        fail_to_launch(e);
 299        return;
 300    }
 301
 302    init_logger();
 303
 304    log::info!("========== starting zed ==========");
 305    let app = App::new().with_assets(Assets);
 306
 307    let (installation_id, existing_installation_id_found) = app
 308        .background_executor()
 309        .block(installation_id())
 310        .ok()
 311        .unzip();
 312
 313    let session = app.background_executor().block(Session::new());
 314
 315    let app_version = AppVersion::init(env!("CARGO_PKG_VERSION"));
 316    reliability::init_panic_hook(
 317        installation_id.clone(),
 318        app_version,
 319        session.id().to_owned(),
 320    );
 321
 322    let (open_listener, mut open_rx) = OpenListener::new();
 323
 324    #[cfg(target_os = "linux")]
 325    {
 326        if env::var("ZED_STATELESS").is_err() {
 327            if crate::zed::listen_for_cli_connections(open_listener.clone()).is_err() {
 328                println!("zed is already running");
 329                return;
 330            }
 331        }
 332    }
 333    #[cfg(not(target_os = "linux"))]
 334    {
 335        use zed::only_instance::*;
 336        if ensure_only_instance() != IsOnlyInstance::Yes {
 337            println!("zed is already running");
 338            return;
 339        }
 340    }
 341
 342    let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new());
 343    let git_binary_path =
 344        if cfg!(target_os = "macos") && option_env!("ZED_BUNDLE").as_deref() == Some("true") {
 345            app.path_for_auxiliary_executable("git")
 346                .context("could not find git binary path")
 347                .log_err()
 348        } else {
 349            None
 350        };
 351    log::info!("Using git binary path: {:?}", git_binary_path);
 352
 353    let fs = Arc::new(RealFs::new(
 354        git_hosting_provider_registry.clone(),
 355        git_binary_path,
 356    ));
 357    let user_settings_file_rx = watch_config_file(
 358        &app.background_executor(),
 359        fs.clone(),
 360        paths::settings_file().clone(),
 361    );
 362    let user_keymap_file_rx = watch_config_file(
 363        &app.background_executor(),
 364        fs.clone(),
 365        paths::keymap_file().clone(),
 366    );
 367
 368    let login_shell_env_loaded = if stdout_is_a_pty() {
 369        Task::ready(())
 370    } else {
 371        app.background_executor().spawn(async {
 372            #[cfg(unix)]
 373            {
 374                load_shell_from_passwd().await.log_err();
 375            }
 376            load_login_shell_environment().await.log_err();
 377        })
 378    };
 379
 380    app.on_open_urls(with_clone!(open_listener, move |urls| open_listener.open_urls(urls)));
 381    app.on_reopen(move |cx| {
 382        if let Some(app_state) = AppState::try_global(cx).and_then(|app_state| app_state.upgrade())
 383        {
 384            cx.spawn({
 385                let app_state = app_state.clone();
 386                |mut cx| async move {
 387                    if let Err(e) = restore_or_create_workspace(app_state, &mut cx).await {
 388                        fail_to_open_window_async(e, &mut cx)
 389                    }
 390                }
 391            })
 392            .detach();
 393        }
 394    });
 395
 396    app.run(move |cx| {
 397        release_channel::init(app_version, cx);
 398        if let Some(build_sha) = option_env!("ZED_COMMIT_SHA") {
 399            AppCommitSha::set_global(AppCommitSha(build_sha.into()), cx);
 400        }
 401
 402        <dyn Fs>::set_global(fs.clone(), cx);
 403
 404        GitHostingProviderRegistry::set_global(git_hosting_provider_registry, cx);
 405        git_hosting_providers::init(cx);
 406
 407        OpenListener::set_global(cx, open_listener.clone());
 408
 409        settings::init(cx);
 410        handle_settings_file_changes(user_settings_file_rx, cx);
 411        handle_keymap_file_changes(user_keymap_file_rx, cx);
 412
 413        client::init_settings(cx);
 414        let client = Client::production(cx);
 415        cx.update_http_client(client.http_client().clone());
 416        let mut languages =
 417            LanguageRegistry::new(login_shell_env_loaded, cx.background_executor().clone());
 418        languages.set_language_server_download_dir(paths::languages_dir().clone());
 419        let languages = Arc::new(languages);
 420        let node_runtime = RealNodeRuntime::new(client.http_client());
 421
 422        language::init(cx);
 423        languages::init(languages.clone(), node_runtime.clone(), cx);
 424        let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
 425        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
 426
 427        Client::set_global(client.clone(), cx);
 428
 429        zed::init(cx);
 430        project::Project::init(&client, cx);
 431        client::init(&client, cx);
 432        language::init(cx);
 433        let telemetry = client.telemetry();
 434        telemetry.start(installation_id.clone(), session.id().to_owned(), cx);
 435        telemetry.report_app_event(
 436            match existing_installation_id_found {
 437                Some(false) => "first open",
 438                _ => "open",
 439            }
 440            .to_string(),
 441        );
 442        let app_state = Arc::new(AppState {
 443            languages: languages.clone(),
 444            client: client.clone(),
 445            user_store: user_store.clone(),
 446            fs: fs.clone(),
 447            build_window_options,
 448            workspace_store,
 449            node_runtime: node_runtime.clone(),
 450            session,
 451        });
 452        AppState::set_global(Arc::downgrade(&app_state), cx);
 453
 454        auto_update::init(client.http_client(), cx);
 455        reliability::init(client.http_client(), installation_id, cx);
 456        init_common(app_state.clone(), cx);
 457
 458        let args = Args::parse();
 459        let urls: Vec<_> = args
 460            .paths_or_urls
 461            .iter()
 462            .filter_map(|arg| parse_url_arg(arg, cx).log_err())
 463            .collect();
 464
 465        if !urls.is_empty() {
 466            open_listener.open_urls(urls)
 467        }
 468
 469        match open_rx
 470            .try_next()
 471            .ok()
 472            .flatten()
 473            .and_then(|urls| OpenRequest::parse(urls, cx).log_err())
 474        {
 475            Some(request) => {
 476                handle_open_request(request, app_state.clone(), cx);
 477            }
 478            None => {
 479                if let Some(dev_server_token) = args.dev_server_token {
 480                    let task =
 481                        init_headless(DevServerToken(dev_server_token), app_state.clone(), cx);
 482                    cx.spawn(|cx| async move {
 483                        if let Err(e) = task.await {
 484                            log::error!("{}", e);
 485                            cx.update(|cx| cx.quit()).log_err();
 486                        } else {
 487                            log::info!("connected!");
 488                        }
 489                    })
 490                    .detach();
 491                } else {
 492                    init_ui(app_state.clone(), cx).unwrap();
 493                    cx.spawn({
 494                        let app_state = app_state.clone();
 495                        |mut cx| async move {
 496                            if let Err(e) = restore_or_create_workspace(app_state, &mut cx).await {
 497                                fail_to_open_window_async(e, &mut cx)
 498                            }
 499                        }
 500                    })
 501                    .detach();
 502                }
 503            }
 504        }
 505
 506        let app_state = app_state.clone();
 507        cx.spawn(move |cx| async move {
 508            while let Some(urls) = open_rx.next().await {
 509                cx.update(|cx| {
 510                    if let Some(request) = OpenRequest::parse(urls, cx).log_err() {
 511                        handle_open_request(request, app_state.clone(), cx);
 512                    }
 513                })
 514                .ok();
 515            }
 516        })
 517        .detach();
 518    });
 519}
 520
 521fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut AppContext) {
 522    if let Some(connection) = request.cli_connection {
 523        let app_state = app_state.clone();
 524        cx.spawn(move |cx| handle_cli_connection(connection, app_state, cx))
 525            .detach();
 526        return;
 527    }
 528
 529    if let Err(e) = init_ui(app_state.clone(), cx) {
 530        fail_to_open_window(e, cx);
 531        return;
 532    };
 533
 534    if let Some(connection_info) = request.ssh_connection {
 535        cx.spawn(|mut cx| async move {
 536            open_ssh_paths(
 537                connection_info,
 538                request.open_paths,
 539                app_state,
 540                workspace::OpenOptions::default(),
 541                &mut cx,
 542            )
 543            .await
 544        })
 545        .detach_and_log_err(cx);
 546        return;
 547    }
 548
 549    let mut task = None;
 550    if !request.open_paths.is_empty() {
 551        let app_state = app_state.clone();
 552        task = Some(cx.spawn(|mut cx| async move {
 553            let (_window, results) = open_paths_with_positions(
 554                &request.open_paths,
 555                app_state,
 556                workspace::OpenOptions::default(),
 557                &mut cx,
 558            )
 559            .await?;
 560            for result in results.into_iter().flatten() {
 561                if let Err(err) = result {
 562                    log::error!("Error opening path: {err}",);
 563                }
 564            }
 565            anyhow::Ok(())
 566        }));
 567    }
 568
 569    if !request.open_channel_notes.is_empty() || request.join_channel.is_some() {
 570        cx.spawn(|mut cx| async move {
 571            let result = maybe!(async {
 572                if let Some(task) = task {
 573                    task.await?;
 574                }
 575                let client = app_state.client.clone();
 576                // we continue even if authentication fails as join_channel/ open channel notes will
 577                // show a visible error message.
 578                authenticate(client, &cx).await.log_err();
 579
 580                if let Some(channel_id) = request.join_channel {
 581                    cx.update(|cx| {
 582                        workspace::join_channel(
 583                            client::ChannelId(channel_id),
 584                            app_state.clone(),
 585                            None,
 586                            cx,
 587                        )
 588                    })?
 589                    .await?;
 590                }
 591
 592                let workspace_window =
 593                    workspace::get_any_active_workspace(app_state, cx.clone()).await?;
 594                let workspace = workspace_window.root_view(&cx)?;
 595
 596                let mut promises = Vec::new();
 597                for (channel_id, heading) in request.open_channel_notes {
 598                    promises.push(cx.update_window(workspace_window.into(), |_, cx| {
 599                        ChannelView::open(
 600                            client::ChannelId(channel_id),
 601                            heading,
 602                            workspace.clone(),
 603                            cx,
 604                        )
 605                        .log_err()
 606                    })?)
 607                }
 608                future::join_all(promises).await;
 609                anyhow::Ok(())
 610            })
 611            .await;
 612            if let Err(err) = result {
 613                fail_to_open_window_async(err, &mut cx);
 614            }
 615        })
 616        .detach()
 617    } else if let Some(task) = task {
 618        cx.spawn(|mut cx| async move {
 619            if let Err(err) = task.await {
 620                fail_to_open_window_async(err, &mut cx);
 621            }
 622        })
 623        .detach();
 624    }
 625}
 626
 627async fn authenticate(client: Arc<Client>, cx: &AsyncAppContext) -> Result<()> {
 628    if stdout_is_a_pty() {
 629        if *client::ZED_DEVELOPMENT_AUTH {
 630            client.authenticate_and_connect(true, &cx).await?;
 631        } else if client::IMPERSONATE_LOGIN.is_some() {
 632            client.authenticate_and_connect(false, &cx).await?;
 633        }
 634    } else if client.has_credentials(&cx).await {
 635        client.authenticate_and_connect(true, &cx).await?;
 636    }
 637    Ok::<_, anyhow::Error>(())
 638}
 639
 640async fn installation_id() -> Result<(String, bool)> {
 641    let legacy_key_name = "device_id".to_string();
 642    let key_name = "installation_id".to_string();
 643
 644    // Migrate legacy key to new key
 645    if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(&legacy_key_name) {
 646        KEY_VALUE_STORE
 647            .write_kvp(key_name, installation_id.clone())
 648            .await?;
 649        KEY_VALUE_STORE.delete_kvp(legacy_key_name).await?;
 650        return Ok((installation_id, true));
 651    }
 652
 653    if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(&key_name) {
 654        return Ok((installation_id, true));
 655    }
 656
 657    let installation_id = Uuid::new_v4().to_string();
 658
 659    KEY_VALUE_STORE
 660        .write_kvp(key_name, installation_id.clone())
 661        .await?;
 662
 663    Ok((installation_id, false))
 664}
 665
 666async fn restore_or_create_workspace(
 667    app_state: Arc<AppState>,
 668    cx: &mut AsyncAppContext,
 669) -> Result<()> {
 670    if let Some(locations) = restorable_workspace_locations(cx, &app_state).await {
 671        for location in locations {
 672            cx.update(|cx| {
 673                workspace::open_paths(
 674                    location.paths().as_ref(),
 675                    app_state.clone(),
 676                    workspace::OpenOptions::default(),
 677                    cx,
 678                )
 679            })?
 680            .await?;
 681        }
 682    } else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
 683        cx.update(|cx| show_welcome_view(app_state, cx))?.await?;
 684    } else {
 685        cx.update(|cx| {
 686            workspace::open_new(app_state, cx, |workspace, cx| {
 687                Editor::new_file(workspace, &Default::default(), cx)
 688            })
 689        })?
 690        .await?;
 691    }
 692
 693    Ok(())
 694}
 695
 696pub(crate) async fn restorable_workspace_locations(
 697    cx: &mut AsyncAppContext,
 698    app_state: &Arc<AppState>,
 699) -> Option<Vec<workspace::LocalPaths>> {
 700    let mut restore_behavior = cx
 701        .update(|cx| WorkspaceSettings::get(None, cx).restore_on_startup)
 702        .ok()?;
 703
 704    let last_session_id = app_state.session.last_session_id();
 705    if last_session_id.is_none()
 706        && matches!(
 707            restore_behavior,
 708            workspace::RestoreOnStartupBehavior::LastSession
 709        )
 710    {
 711        restore_behavior = workspace::RestoreOnStartupBehavior::LastWorkspace;
 712    }
 713
 714    match restore_behavior {
 715        workspace::RestoreOnStartupBehavior::LastWorkspace => {
 716            workspace::last_opened_workspace_paths()
 717                .await
 718                .map(|location| vec![location])
 719        }
 720        workspace::RestoreOnStartupBehavior::LastSession => {
 721            if let Some(last_session_id) = last_session_id {
 722                workspace::last_session_workspace_locations(last_session_id)
 723                    .filter(|locations| !locations.is_empty())
 724            } else {
 725                None
 726            }
 727        }
 728        _ => None,
 729    }
 730}
 731
 732fn init_paths() -> anyhow::Result<()> {
 733    for path in [
 734        paths::config_dir(),
 735        paths::extensions_dir(),
 736        paths::languages_dir(),
 737        paths::database_dir(),
 738        paths::logs_dir(),
 739        paths::temp_dir(),
 740    ]
 741    .iter()
 742    {
 743        std::fs::create_dir_all(path)
 744            .map_err(|e| anyhow!("Could not create directory {:?}: {}", path, e))?;
 745    }
 746    Ok(())
 747}
 748
 749fn init_logger() {
 750    if stdout_is_a_pty() {
 751        init_stdout_logger();
 752    } else {
 753        let level = LevelFilter::Info;
 754
 755        // Prevent log file from becoming too large.
 756        const KIB: u64 = 1024;
 757        const MIB: u64 = 1024 * KIB;
 758        const MAX_LOG_BYTES: u64 = MIB;
 759        if std::fs::metadata(paths::log_file())
 760            .map_or(false, |metadata| metadata.len() > MAX_LOG_BYTES)
 761        {
 762            let _ = std::fs::rename(paths::log_file(), paths::old_log_file());
 763        }
 764
 765        match OpenOptions::new()
 766            .create(true)
 767            .append(true)
 768            .open(paths::log_file())
 769        {
 770            Ok(log_file) => {
 771                let mut config_builder = ConfigBuilder::new();
 772
 773                config_builder.set_time_format_rfc3339();
 774                config_builder.set_time_offset_to_local().log_err();
 775
 776                #[cfg(target_os = "linux")]
 777                {
 778                    config_builder.add_filter_ignore_str("zbus");
 779                    config_builder.add_filter_ignore_str("blade_graphics::hal::resource");
 780                    config_builder.add_filter_ignore_str("naga::back::spv::writer");
 781                }
 782
 783                let config = config_builder.build();
 784                simplelog::WriteLogger::init(level, config, log_file)
 785                    .expect("could not initialize logger");
 786            }
 787            Err(err) => {
 788                init_stdout_logger();
 789                log::error!(
 790                    "could not open log file, defaulting to stdout logging: {}",
 791                    err
 792                );
 793            }
 794        }
 795    }
 796}
 797
 798fn init_stdout_logger() {
 799    Builder::new()
 800        .parse_default_env()
 801        .format(|buf, record| {
 802            use env_logger::fmt::Color;
 803
 804            let subtle = buf
 805                .style()
 806                .set_color(Color::Black)
 807                .set_intense(true)
 808                .clone();
 809            write!(buf, "{}", subtle.value("["))?;
 810            write!(
 811                buf,
 812                "{} ",
 813                chrono::Local::now().format("%Y-%m-%dT%H:%M:%S%:z")
 814            )?;
 815            write!(buf, "{:<5}", buf.default_styled_level(record.level()))?;
 816            if let Some(path) = record.module_path() {
 817                write!(buf, " {path}")?;
 818            }
 819            write!(buf, "{}", subtle.value("]"))?;
 820            writeln!(buf, " {}", record.args())
 821        })
 822        .init();
 823}
 824
 825#[cfg(unix)]
 826async fn load_shell_from_passwd() -> Result<()> {
 827    let buflen = match unsafe { libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) } {
 828        n if n < 0 => 1024,
 829        n => n as usize,
 830    };
 831    let mut buffer = Vec::with_capacity(buflen);
 832
 833    let mut pwd: std::mem::MaybeUninit<libc::passwd> = std::mem::MaybeUninit::uninit();
 834    let mut result: *mut libc::passwd = std::ptr::null_mut();
 835
 836    let uid = unsafe { libc::getuid() };
 837    let status = unsafe {
 838        libc::getpwuid_r(
 839            uid,
 840            pwd.as_mut_ptr(),
 841            buffer.as_mut_ptr() as *mut libc::c_char,
 842            buflen,
 843            &mut result,
 844        )
 845    };
 846    let entry = unsafe { pwd.assume_init() };
 847
 848    anyhow::ensure!(
 849        status == 0,
 850        "call to getpwuid_r failed. uid: {}, status: {}",
 851        uid,
 852        status
 853    );
 854    anyhow::ensure!(!result.is_null(), "passwd entry for uid {} not found", uid);
 855    anyhow::ensure!(
 856        entry.pw_uid == uid,
 857        "passwd entry has different uid ({}) than getuid ({}) returned",
 858        entry.pw_uid,
 859        uid,
 860    );
 861
 862    let shell = unsafe { std::ffi::CStr::from_ptr(entry.pw_shell).to_str().unwrap() };
 863    if env::var("SHELL").map_or(true, |shell_env| shell_env != shell) {
 864        log::info!(
 865            "updating SHELL environment variable to value from passwd entry: {:?}",
 866            shell,
 867        );
 868        env::set_var("SHELL", shell);
 869    }
 870
 871    Ok(())
 872}
 873
 874async fn load_login_shell_environment() -> Result<()> {
 875    let marker = "ZED_LOGIN_SHELL_START";
 876    let shell = env::var("SHELL").context(
 877        "SHELL environment variable is not assigned so we can't source login environment variables",
 878    )?;
 879
 880    // If possible, we want to `cd` in the user's `$HOME` to trigger programs
 881    // such as direnv, asdf, mise, ... to adjust the PATH. These tools often hook
 882    // into shell's `cd` command (and hooks) to manipulate env.
 883    // We do this so that we get the env a user would have when spawning a shell
 884    // in home directory.
 885    let shell_cmd_prefix = std::env::var_os("HOME")
 886        .and_then(|home| home.into_string().ok())
 887        .map(|home| format!("cd '{home}';"));
 888
 889    // The `exit 0` is the result of hours of debugging, trying to find out
 890    // why running this command here, without `exit 0`, would mess
 891    // up signal process for our process so that `ctrl-c` doesn't work
 892    // anymore.
 893    // We still don't know why `$SHELL -l -i -c '/usr/bin/env -0'`  would
 894    // do that, but it does, and `exit 0` helps.
 895    let shell_cmd = format!(
 896        "{}printf '%s' {marker}; /usr/bin/env; exit 0;",
 897        shell_cmd_prefix.as_deref().unwrap_or("")
 898    );
 899
 900    let output = Command::new(&shell)
 901        .args(["-l", "-i", "-c", &shell_cmd])
 902        .output()
 903        .await
 904        .context("failed to spawn login shell to source login environment variables")?;
 905    if !output.status.success() {
 906        Err(anyhow!("login shell exited with error"))?;
 907    }
 908
 909    let stdout = String::from_utf8_lossy(&output.stdout);
 910
 911    if let Some(env_output_start) = stdout.find(marker) {
 912        let env_output = &stdout[env_output_start + marker.len()..];
 913
 914        parse_env_output(env_output, |key, value| env::set_var(key, value));
 915
 916        log::info!(
 917            "set environment variables from shell:{}, path:{}",
 918            shell,
 919            env::var("PATH").unwrap_or_default(),
 920        );
 921    }
 922
 923    Ok(())
 924}
 925
 926fn stdout_is_a_pty() -> bool {
 927    std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_none() && std::io::stdout().is_terminal()
 928}
 929
 930#[derive(Parser, Debug)]
 931#[command(name = "zed", disable_version_flag = true)]
 932struct Args {
 933    /// A sequence of space-separated paths or urls that you want to open.
 934    ///
 935    /// Use `path:line:row` syntax to open a file at a specific location.
 936    /// Non-existing paths and directories will ignore `:line:row` suffix.
 937    ///
 938    /// URLs can either be `file://` or `zed://` scheme, or relative to <https://zed.dev>.
 939    paths_or_urls: Vec<String>,
 940
 941    /// Instructs zed to run as a dev server on this machine. (not implemented)
 942    #[arg(long)]
 943    dev_server_token: Option<String>,
 944}
 945
 946fn parse_url_arg(arg: &str, cx: &AppContext) -> Result<String> {
 947    match std::fs::canonicalize(Path::new(&arg)) {
 948        Ok(path) => Ok(format!("file://{}", path.to_string_lossy())),
 949        Err(error) => {
 950            if arg.starts_with("file://")
 951                || arg.starts_with("zed-cli://")
 952                || arg.starts_with("ssh://")
 953            {
 954                Ok(arg.into())
 955            } else if let Some(_) = parse_zed_link(&arg, cx) {
 956                Ok(arg.into())
 957            } else {
 958                Err(anyhow!("error parsing path argument: {}", error))
 959            }
 960        }
 961    }
 962}
 963
 964fn load_embedded_fonts(cx: &AppContext) {
 965    let asset_source = cx.asset_source();
 966    let font_paths = asset_source.list("fonts").unwrap();
 967    let embedded_fonts = Mutex::new(Vec::new());
 968    let executor = cx.background_executor();
 969
 970    executor.block(executor.scoped(|scope| {
 971        for font_path in &font_paths {
 972            if !font_path.ends_with(".ttf") {
 973                continue;
 974            }
 975
 976            scope.spawn(async {
 977                let font_bytes = asset_source.load(font_path).unwrap().unwrap();
 978                embedded_fonts.lock().push(font_bytes);
 979            });
 980        }
 981    }));
 982
 983    cx.text_system()
 984        .add_fonts(embedded_fonts.into_inner())
 985        .unwrap();
 986}
 987
 988/// Spawns a background task to load the user themes from the themes directory.
 989fn load_user_themes_in_background(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
 990    cx.spawn({
 991        let fs = fs.clone();
 992        |cx| async move {
 993            if let Some(theme_registry) =
 994                cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err()
 995            {
 996                let themes_dir = paths::themes_dir().as_ref();
 997                match fs
 998                    .metadata(themes_dir)
 999                    .await
1000                    .ok()
1001                    .flatten()
1002                    .map(|m| m.is_dir)
1003                {
1004                    Some(is_dir) => {
1005                        anyhow::ensure!(is_dir, "Themes dir path {themes_dir:?} is not a directory")
1006                    }
1007                    None => {
1008                        fs.create_dir(themes_dir).await.with_context(|| {
1009                            format!("Failed to create themes dir at path {themes_dir:?}")
1010                        })?;
1011                    }
1012                }
1013                theme_registry.load_user_themes(themes_dir, fs).await?;
1014                cx.update(|cx| ThemeSettings::reload_current_theme(cx))?;
1015            }
1016            anyhow::Ok(())
1017        }
1018    })
1019    .detach_and_log_err(cx);
1020}
1021
1022/// Spawns a background task to watch the themes directory for changes.
1023fn watch_themes(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
1024    use std::time::Duration;
1025    cx.spawn(|cx| async move {
1026        let (mut events, _) = fs
1027            .watch(paths::themes_dir(), Duration::from_millis(100))
1028            .await;
1029
1030        while let Some(paths) = events.next().await {
1031            for path in paths {
1032                if fs.metadata(&path).await.ok().flatten().is_some() {
1033                    if let Some(theme_registry) =
1034                        cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err()
1035                    {
1036                        if let Some(()) = theme_registry
1037                            .load_user_theme(&path, fs.clone())
1038                            .await
1039                            .log_err()
1040                        {
1041                            cx.update(|cx| ThemeSettings::reload_current_theme(cx))
1042                                .log_err();
1043                        }
1044                    }
1045                }
1046            }
1047        }
1048    })
1049    .detach()
1050}
1051
1052#[cfg(debug_assertions)]
1053fn watch_languages(fs: Arc<dyn fs::Fs>, languages: Arc<LanguageRegistry>, cx: &mut AppContext) {
1054    use std::time::Duration;
1055
1056    let path = {
1057        let p = Path::new("crates/languages/src");
1058        let Ok(full_path) = p.canonicalize() else {
1059            return;
1060        };
1061        full_path
1062    };
1063
1064    cx.spawn(|_| async move {
1065        let (mut events, _) = fs.watch(path.as_path(), Duration::from_millis(100)).await;
1066        while let Some(event) = events.next().await {
1067            let has_language_file = event.iter().any(|path| {
1068                path.extension()
1069                    .map(|ext| ext.to_string_lossy().as_ref() == "scm")
1070                    .unwrap_or(false)
1071            });
1072            if has_language_file {
1073                languages.reload();
1074            }
1075        }
1076    })
1077    .detach()
1078}
1079
1080#[cfg(not(debug_assertions))]
1081fn watch_languages(_fs: Arc<dyn fs::Fs>, _languages: Arc<LanguageRegistry>, _cx: &mut AppContext) {}
1082
1083#[cfg(debug_assertions)]
1084fn watch_file_types(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
1085    use std::time::Duration;
1086
1087    use file_icons::FileIcons;
1088    use gpui::UpdateGlobal;
1089
1090    let path = {
1091        let p = Path::new("assets/icons/file_icons/file_types.json");
1092        let Ok(full_path) = p.canonicalize() else {
1093            return;
1094        };
1095        full_path
1096    };
1097
1098    cx.spawn(|cx| async move {
1099        let (mut events, _) = fs.watch(path.as_path(), Duration::from_millis(100)).await;
1100        while (events.next().await).is_some() {
1101            cx.update(|cx| {
1102                FileIcons::update_global(cx, |file_types, _cx| {
1103                    *file_types = file_icons::FileIcons::new(Assets);
1104                });
1105            })
1106            .ok();
1107        }
1108    })
1109    .detach()
1110}
1111
1112#[cfg(not(debug_assertions))]
1113fn watch_file_types(_fs: Arc<dyn fs::Fs>, _cx: &mut AppContext) {}