main.rs

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