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