Skip to repository content2024 lines · 75.2 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T04:40:24.095Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
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
7// Ensure the binary name stays in sync with the application identity.
8const _: () = assert!(
9 app_identity::BINARY_NAME
10 .as_bytes()
11 .eq_ignore_ascii_case(env!("CARGO_BIN_NAME").as_bytes()),
12 "app_identity::BINARY_NAME must match the binary name.",
13);
14
15use agent_ui::AgentPanel;
16use anyhow::{Context as _, Result};
17use clap::Parser;
18use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
19use client::{Client, ProxySettings, RefreshLlmTokenListener, UserStore, parse_zed_link};
20use collections::HashMap;
21use crashes::InitCrashHandler;
22use db::kvp::{GlobalKeyValueStore, KeyValueStore};
23use extension::ExtensionHostProxy;
24use fs::{Fs, RealFs};
25use futures::{StreamExt, channel::oneshot};
26use git::GitHostingProviderRegistry;
27use git_ui::clone::clone_and_open;
28use gpui::{
29 App, AppContext, Application, AsyncApp, QuitMode, Task, TaskExt, UpdateGlobal as _, block_on,
30};
31use gpui_platform;
32
33use gpui_tokio::Tokio;
34use language::LanguageRegistry;
35use onboarding::await_identity_ready;
36use project_panel::ProjectPanel;
37use prompt_store::PromptBuilder;
38use remote::RemoteConnectionOptions;
39use reqwest_client::ReqwestClient;
40
41use assets::Assets;
42use node_runtime::{NodeBinaryOptions, NodeRuntime};
43use parking_lot::Mutex;
44use project::{project_settings::ProjectSettings, trusted_worktrees};
45use recent_projects::{RemoteSettings, open_remote_project};
46use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
47use session::{AppSession, Session};
48use settings::{BaseKeymap, Settings, SettingsStore, watch_config_file};
49use smol::future::poll_once;
50use std::{
51 cell::RefCell,
52 env,
53 io::{self, IsTerminal},
54 path::{Path, PathBuf},
55 process,
56 rc::Rc,
57 sync::{Arc, LazyLock, OnceLock},
58 time::Instant,
59};
60use theme::{ActiveTheme, GlobalTheme, ThemeRegistry};
61use theme_settings::load_user_theme;
62use util::{ResultExt, maybe};
63use uuid::Uuid;
64use workspace::{
65 AppState, MultiWorkspace, SerializedWorkspaceLocation, SessionWorkspace, Toast,
66 WorkspaceSettings, WorkspaceStore, notifications::NotificationId, restore_multiworkspace,
67};
68use zed::{
69 OpenListener, OpenRequest, RawOpenRequest, app_menus, build_window_options,
70 derive_paths_with_position, edit_prediction_registry, handle_cli_connection,
71 handle_keymap_file_changes, initialize_workspace, open_paths_with_positions,
72};
73
74use crate::zed::{CrashHandler, OpenRequestKind, eager_load_active_theme_and_icon_theme};
75
76#[cfg(feature = "mimalloc")]
77#[global_allocator]
78static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
79
80fn build_application() -> Application {
81 let platform = gpui_platform::current_platform(false);
82 if std::env::var("ZED_EXPERIMENTAL_A11Y").as_deref() == Ok("1") {
83 Application::with_platform(platform)
84 } else {
85 Application::new_inaccessible(platform)
86 }
87}
88
89fn files_not_created_on_launch(errors: HashMap<io::ErrorKind, Vec<&Path>>) {
90 let message = "Omega failed to launch";
91 let error_details = errors
92 .into_iter()
93 .flat_map(|(kind, paths)| {
94 #[allow(unused_mut)] // for non-unix platforms
95 let mut error_kind_details = match paths.len() {
96 0 => return None,
97 1 => format!(
98 "{kind} when creating directory {:?}",
99 paths.first().expect("match arm checks for a single entry")
100 ),
101 _many => format!("{kind} when creating directories {paths:?}"),
102 };
103
104 #[cfg(unix)]
105 {
106 if kind == io::ErrorKind::PermissionDenied {
107 error_kind_details.push_str("\n\nConsider using chown and chmod tools for altering the directories permissions if your user has corresponding rights.\
108 \nFor example, `sudo chown $(whoami):staff ~/.config` and `chmod +uwrx ~/.config`");
109 }
110 }
111
112 Some(error_kind_details)
113 })
114 .collect::<Vec<_>>().join("\n\n");
115
116 eprintln!("{message}: {error_details}");
117 build_application()
118 .with_quit_mode(QuitMode::Explicit)
119 .run(move |cx| {
120 if let Ok(window) = cx.open_window(gpui::WindowOptions::default(), |_, cx| {
121 cx.new(|_| gpui::Empty)
122 }) {
123 window
124 .update(cx, |_, window, cx| {
125 let response = window.prompt(
126 gpui::PromptLevel::Critical,
127 message,
128 Some(&error_details),
129 &["Exit"],
130 cx,
131 );
132
133 cx.spawn_in(window, async move |_, cx| {
134 response.await?;
135 cx.update(|_, cx| cx.quit())
136 })
137 .detach_and_log_err(cx);
138 })
139 .log_err();
140 } else {
141 fail_to_open_window(anyhow::anyhow!("{message}: {error_details}"), cx)
142 }
143 })
144}
145
146fn fail_to_open_window_async(e: anyhow::Error, cx: &mut AsyncApp) {
147 cx.update(|cx| fail_to_open_window(e, cx));
148}
149
150fn fail_to_open_window(e: anyhow::Error, _cx: &mut App) {
151 eprintln!(
152 "Omega failed to open a window: {e:?}. See {} for troubleshooting steps.",
153 app_identity::PRODUCT_DOCS_URL
154 );
155 #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
156 {
157 process::exit(1);
158 }
159
160 // Maybe unify this with gpui::platform::linux::platform::ResultExt::notify_err(..)?
161 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
162 {
163 use ashpd::desktop::notification::{Notification, NotificationProxy, Priority};
164 _cx.spawn(async move |_cx| {
165 let Ok(proxy) = NotificationProxy::new().await else {
166 process::exit(1);
167 };
168
169 let notification_id = "com.openagents.omega.Oops";
170 proxy
171 .add_notification(
172 notification_id,
173 Notification::new("Omega failed to launch")
174 .body(Some(
175 format!(
176 "{e:?}. See {} for troubleshooting steps.",
177 app_identity::PRODUCT_DOCS_URL
178 )
179 .as_str(),
180 ))
181 .priority(Priority::High)
182 .icon(ashpd::desktop::Icon::with_names(&[
183 "dialog-question-symbolic",
184 ])),
185 )
186 .await
187 .ok();
188
189 process::exit(1);
190 })
191 .detach();
192 }
193}
194static STARTUP_TIME: OnceLock<Instant> = OnceLock::new();
195
196fn main() {
197 STARTUP_TIME.get_or_init(|| Instant::now());
198
199 // If this process was re-executed as a Linux sandbox helper, run that mode
200 // without returning. Must run before argument parsing: the wrapped command's
201 // args are appended verbatim and would otherwise be misinterpreted as Zed's
202 // own arguments.
203 sandbox::run_sandbox_launcher_if_invoked();
204
205 #[cfg(unix)]
206 util::prevent_root_execution();
207
208 let args = Args::parse();
209
210 // `zed --askpass` Makes zed operate in nc/netcat mode for use with askpass
211 #[cfg(not(target_os = "windows"))]
212 if let Some(socket) = &args.askpass {
213 askpass::main(socket);
214 return;
215 }
216
217 // `zed --crash-handler` Makes zed operate in minidump crash handler mode
218 if let Some(socket) = &args.crash_handler {
219 crashes::crash_server(socket.as_path(), paths::logs_dir().clone());
220 return;
221 }
222
223 #[cfg(target_os = "windows")]
224 if args.record_etw_trace {
225 let zed_pid = args
226 .etw_zed_pid
227 .and_then(|pid| if pid >= 0 { Some(pid as u32) } else { None });
228 let Some(output_path) = args.etw_output else {
229 eprintln!("--etw-output is required for --record-etw-trace");
230 process::exit(1);
231 };
232
233 let Some(etw_socket) = args.etw_socket else {
234 eprintln!("--etw-socket is required for --record-etw-trace");
235 process::exit(1);
236 };
237
238 if let Err(error) =
239 etw_tracing::record_etw_trace(zed_pid, &output_path, etw_socket.as_str())
240 {
241 eprintln!("ETW trace recording failed: {error:#}");
242 process::exit(1);
243 }
244 return;
245 }
246
247 #[cfg(all(not(debug_assertions), target_os = "windows"))]
248 unsafe {
249 use windows::Win32::System::Console::{ATTACH_PARENT_PROCESS, AttachConsole};
250
251 if args.foreground {
252 let _ = AttachConsole(ATTACH_PARENT_PROCESS);
253 }
254 }
255
256 // `zed --printenv` Outputs environment variables as JSON to stdout
257 if args.printenv {
258 util::shell_env::print_env();
259 return;
260 }
261
262 if args.dump_all_actions {
263 dump_all_gpui_actions();
264 return;
265 }
266
267 if args.demo_workroom {
268 unsafe {
269 std::env::set_var(workroom_ui::PUBLIC_DEMO_ENV, "1");
270 }
271 }
272
273 // Set custom data directory.
274 if let Some(dir) = &args.user_data_dir {
275 paths::set_custom_data_dir(dir);
276 }
277
278 #[cfg(target_os = "windows")]
279 match util::get_zed_cli_path() {
280 Ok(path) => askpass::set_askpass_program(path),
281 Err(err) => {
282 eprintln!("Error: {}", err);
283 if std::option_env!("ZED_BUNDLE").is_some() {
284 process::exit(1);
285 }
286 }
287 }
288
289 let file_errors = init_paths();
290 if !file_errors.is_empty() {
291 files_not_created_on_launch(file_errors);
292 return;
293 }
294
295 zlog::init();
296
297 if stdout_is_a_pty() {
298 zlog::init_output_stdout();
299 } else {
300 let result = zlog::init_output_file(paths::log_file(), Some(paths::old_log_file()));
301 if let Err(err) = result {
302 eprintln!("Could not open log file: {}... Defaulting to stdout", err);
303 zlog::init_output_stdout();
304 };
305 }
306 ztracing::init();
307
308 let version = option_env!("ZED_BUILD_ID");
309 let app_commit_sha =
310 option_env!("ZED_COMMIT_SHA").map(|commit_sha| AppCommitSha::new(commit_sha.to_string()));
311 let app_version = AppVersion::load(env!("CARGO_PKG_VERSION"), version, app_commit_sha.clone());
312
313 if args.system_specs {
314 let system_specs = system_specs::SystemSpecs::new_stateless(
315 app_version,
316 app_commit_sha,
317 *release_channel::RELEASE_CHANNEL,
318 client::telemetry::os_name(),
319 client::telemetry::os_version(),
320 );
321 println!("Omega System Specs (from CLI):\n{}", system_specs);
322 return;
323 }
324
325 rayon::ThreadPoolBuilder::new()
326 .num_threads(std::thread::available_parallelism().map_or(1, |n| n.get().div_ceil(2)))
327 .stack_size(10 * 1024 * 1024)
328 .thread_name(|ix| format!("RayonWorker{}", ix))
329 .build_global()
330 .unwrap();
331
332 log::info!(
333 "========== starting omega version {}, sha {} ==========",
334 app_version,
335 app_commit_sha
336 .as_ref()
337 .map(|sha| sha.short())
338 .as_deref()
339 .unwrap_or("unknown"),
340 );
341
342 #[cfg(windows)]
343 check_for_conpty_dll();
344
345 let app = build_application().with_assets(Assets);
346
347 let app_db = db::AppDatabase::new();
348 let system_id = app.background_executor().spawn(system_id());
349 let installation_id = app
350 .background_executor()
351 .spawn(installation_id(KeyValueStore::from_app_db(&app_db)));
352 let session_id = Uuid::new_v4().to_string();
353 let session = app.background_executor().spawn(Session::new(
354 session_id.clone(),
355 KeyValueStore::from_app_db(&app_db),
356 ));
357 let background_executor = app.background_executor();
358
359 let (open_listener, mut open_rx) = OpenListener::new();
360
361 let failed_single_instance_check = if *zed_env_vars::ZED_STATELESS
362 || *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev
363 {
364 false
365 } else {
366 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
367 {
368 crate::zed::listen_for_cli_connections(open_listener.clone()).is_err()
369 }
370
371 #[cfg(target_os = "windows")]
372 {
373 !crate::zed::windows_only_instance::handle_single_instance(open_listener.clone(), &args)
374 }
375
376 #[cfg(target_os = "macos")]
377 {
378 use zed::mac_only_instance::*;
379 ensure_only_instance() != IsOnlyInstance::Yes
380 }
381 };
382 if failed_single_instance_check {
383 println!("Omega is already running");
384 return;
385 }
386
387 let should_install_crash_handler =
388 client::telemetry::should_install_crash_handler(*release_channel::RELEASE_CHANNEL);
389
390 let crash_handler = if should_install_crash_handler {
391 Some(
392 app.background_executor().spawn(crashes::init(
393 InitCrashHandler {
394 session_id,
395 // strip the build and channel information from the version string, we send them separately
396 zed_version: semver::Version::new(
397 app_version.major,
398 app_version.minor,
399 app_version.patch,
400 )
401 .to_string(),
402 binary: "zed".to_string(),
403 release_channel: release_channel::RELEASE_CHANNEL_NAME.clone(),
404 commit_sha: app_commit_sha
405 .as_ref()
406 .map(|sha| sha.full())
407 .unwrap_or_else(|| "no sha".to_owned()),
408 },
409 {
410 let background_executor1 = app.background_executor();
411 move |task| {
412 background_executor1.spawn(task).detach();
413 }
414 },
415 |pid| paths::temp_dir().join(format!("zed-crash-handler-{pid}")),
416 move |duration| background_executor.timer(duration),
417 )),
418 )
419 } else {
420 crashes::force_backtrace();
421 None
422 };
423
424 let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new());
425 let git_binary_path =
426 if cfg!(target_os = "macos") && option_env!("ZED_BUNDLE").as_deref() == Some("true") {
427 app.path_for_auxiliary_executable("git")
428 .context("could not find git binary path")
429 .log_err()
430 } else {
431 None
432 };
433 if let Some(git_binary_path) = &git_binary_path {
434 log::info!("Using git binary path: {:?}", git_binary_path);
435 }
436
437 let fs = Arc::new(RealFs::new(git_binary_path, app.background_executor()));
438 let (user_keymap_file_rx, user_keymap_watcher) = watch_config_file(
439 &app.background_executor(),
440 fs.clone(),
441 paths::keymap_file().clone(),
442 );
443
444 let (shell_env_loaded_tx, shell_env_loaded_rx) = oneshot::channel();
445 if !stdout_is_a_pty() {
446 app.background_executor()
447 .spawn(async {
448 #[cfg(unix)]
449 util::load_login_shell_environment().await.log_err();
450 shell_env_loaded_tx.send(()).ok();
451 })
452 .detach();
453 } else {
454 drop(shell_env_loaded_tx)
455 }
456
457 app.on_open_urls({
458 let open_listener = open_listener.clone();
459 move |urls| {
460 open_listener.open(RawOpenRequest {
461 urls,
462 diff_paths: Vec::new(),
463 ..Default::default()
464 })
465 }
466 });
467 app.on_reopen(move |cx| {
468 if let Some(app_state) = AppState::try_global(cx) {
469 cx.spawn({
470 async move |cx| {
471 if let Err(e) = restore_or_create_workspace(app_state, cx).await {
472 fail_to_open_window_async(e, cx)
473 }
474 }
475 })
476 .detach();
477 }
478 });
479
480 app.run(move |cx| {
481 cx.set_global(app_db);
482 let db_trusted_paths = match workspace::WorkspaceDb::global(cx).fetch_trusted_worktrees() {
483 Ok(trusted_paths) => trusted_paths,
484 Err(e) => {
485 log::error!("Failed to do initial trusted worktrees fetch: {e:#}");
486 HashMap::default()
487 }
488 };
489 trusted_worktrees::init(db_trusted_paths, cx);
490 menu::init();
491 zed_actions::init();
492
493 release_channel::init(app_version, cx);
494 gpui_tokio::init(cx);
495 if let Some(app_commit_sha) = app_commit_sha {
496 AppCommitSha::set_global(app_commit_sha, cx);
497 }
498 settings::init(cx);
499 zlog_settings::init(cx);
500 zed::watch_settings_files(fs.clone(), cx);
501 handle_keymap_file_changes(user_keymap_file_rx, user_keymap_watcher, cx);
502
503 let user_agent = format!(
504 "Omega/{} ({}; {})",
505 AppVersion::global(cx),
506 std::env::consts::OS,
507 std::env::consts::ARCH
508 );
509 let proxy_url = ProxySettings::get_global(cx).proxy_url();
510 let http = {
511 let _guard = Tokio::handle(cx).enter();
512
513 ReqwestClient::proxy_and_user_agent(proxy_url, &user_agent)
514 .expect("could not start HTTP client")
515 };
516 cx.set_http_client(Arc::new(http));
517
518 <dyn Fs>::set_global(fs.clone(), cx);
519
520 GitHostingProviderRegistry::set_global(git_hosting_provider_registry, cx);
521 git_hosting_providers::init(cx);
522
523 OpenListener::set_global(cx, open_listener.clone());
524
525 extension::init(cx);
526 let extension_host_proxy = ExtensionHostProxy::global(cx);
527
528 let client = Client::production(cx);
529 cx.set_http_client(client.http_client());
530 let mut languages = LanguageRegistry::new(cx.background_executor().clone());
531 languages.set_language_server_download_dir(paths::languages_dir().clone());
532 let languages = Arc::new(languages);
533 let (mut tx, rx) = watch::channel(None);
534 cx.observe_global::<SettingsStore>(move |cx| {
535 let settings = &ProjectSettings::get_global(cx).node;
536 let options = NodeBinaryOptions {
537 allow_path_lookup: !settings.ignore_system_version,
538 // TODO: Expose this setting
539 allow_binary_download: true,
540 use_paths: settings.path.as_ref().map(|node_path| {
541 let node_path = PathBuf::from(shellexpand::tilde(node_path).as_ref());
542 let npm_path = settings
543 .npm_path
544 .as_ref()
545 .map(|path| PathBuf::from(shellexpand::tilde(&path).as_ref()));
546 (
547 node_path.clone(),
548 npm_path.unwrap_or_else(|| {
549 let base_path = PathBuf::new();
550 node_path.parent().unwrap_or(&base_path).join("npm")
551 }),
552 )
553 }),
554 };
555 tx.send(Some(options)).log_err();
556 })
557 .detach();
558 ui::on_new_scrollbars::<SettingsStore>(cx);
559
560 let node_runtime = NodeRuntime::new(client.http_client(), Some(shell_env_loaded_rx), rx);
561
562 debug_adapter_extension::init(extension_host_proxy.clone(), cx);
563 languages::init(languages.clone(), fs.clone(), node_runtime.clone(), cx);
564 let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
565 let workspace_store = cx.new(|cx| WorkspaceStore::new(client.clone(), cx));
566
567 language_extension::init(
568 language_extension::LspAccess::ViaWorkspaces({
569 let workspace_store = workspace_store.clone();
570 Arc::new(move |cx: &mut App| {
571 workspace_store.update(cx, |workspace_store, cx| {
572 Ok(workspace_store
573 .workspaces()
574 .filter_map(|weak| weak.upgrade())
575 .map(|workspace: gpui::Entity<workspace::Workspace>| {
576 workspace.read(cx).project().read(cx).lsp_store()
577 })
578 .collect())
579 })
580 })
581 }),
582 extension_host_proxy.clone(),
583 languages.clone(),
584 );
585
586 Client::set_global(client.clone(), cx);
587
588 zed::init(cx);
589 #[cfg(target_os = "macos")]
590 project::Project::init(&client, cx);
591 debugger_ui::init(cx);
592 omega_effectd::init_openagents_session(cx);
593 omega_effectd::init_openagents_binding(cx);
594 omega_effectd::init_with_host_handler(Some(agent_ui::omega_effectd_host_handler(cx)), cx);
595 agent_computer_ui::init(cx);
596 workroom_ui::init(cx);
597 debugger_tools::init(cx);
598 client::init(&client, cx);
599 feature_flags::FeatureFlagStore::init(cx);
600
601 let system_id = cx.foreground_executor().block_on(system_id).ok();
602 let installation_id = cx.foreground_executor().block_on(installation_id).ok();
603 let session = cx.foreground_executor().block_on(session);
604
605 let telemetry = client.telemetry();
606 telemetry.start(
607 system_id.as_ref().map(|id| id.to_string()),
608 installation_id.as_ref().map(|id| id.to_string()),
609 session.id().to_owned(),
610 cx,
611 );
612 cx.subscribe(&user_store, {
613 let telemetry = telemetry.clone();
614 move |_, evt: &client::user::Event, cx| match evt {
615 client::user::Event::PrivateUserInfoUpdated => {
616 if let Some(crash_client) = cx.try_global::<CrashHandler>() {
617 crashes::set_user_info(
618 &crash_client.0,
619 crashes::UserInfo {
620 metrics_id: telemetry.metrics_id().map(|s| s.to_string()),
621 is_staff: telemetry.is_staff(),
622 },
623 );
624 }
625 }
626 _ => {}
627 }
628 })
629 .detach();
630
631 let is_new_install = matches!(&installation_id, Some(IdType::New(_)));
632
633 // We should rename these in the future to `first app open`, `first app open for release channel`, and `app open`
634 if let (Some(system_id), Some(installation_id)) = (&system_id, &installation_id) {
635 match (&system_id, &installation_id) {
636 (IdType::New(_), IdType::New(_)) => {
637 telemetry::event!("App First Opened");
638 telemetry::event!("App First Opened For Release Channel");
639 }
640 (IdType::Existing(_), IdType::New(_)) => {
641 telemetry::event!("App First Opened For Release Channel");
642 }
643 (_, IdType::Existing(_)) => {
644 telemetry::event!("App Opened");
645 }
646 }
647 }
648 let app_session = cx.new(|cx| AppSession::new(session, cx));
649
650 let app_state = Arc::new(AppState {
651 languages,
652 client: client.clone(),
653 user_store,
654 fs: fs.clone(),
655 build_window_options,
656 workspace_store,
657 node_runtime,
658 session: app_session,
659 });
660 AppState::set_global(app_state.clone(), cx);
661
662 auto_update::init(client.clone(), cx);
663 dap_adapters::init(cx);
664 auto_update_ui::init(cx);
665 reliability::init(client.clone(), app_state.workspace_store.clone(), cx);
666 extension_host::init(
667 extension_host_proxy.clone(),
668 app_state.fs.clone(),
669 app_state.client.clone(),
670 app_state.node_runtime.clone(),
671 cx,
672 );
673
674 theme_settings::init(theme::LoadThemes::All(Box::new(Assets)), cx);
675 eager_load_active_theme_and_icon_theme(fs.clone(), cx);
676 theme_extension::init(
677 extension_host_proxy,
678 ThemeRegistry::global(cx),
679 cx.background_executor().clone(),
680 );
681 command_palette::init(cx);
682 let copilot_chat_configuration = copilot_chat::CopilotChatConfiguration {
683 enterprise_uri: language::language_settings::all_language_settings(None, cx)
684 .edit_predictions
685 .copilot
686 .enterprise_uri
687 .clone(),
688 };
689 copilot_chat::init(
690 app_state.fs.clone(),
691 app_state.client.http_client(),
692 copilot_chat_configuration,
693 cx,
694 );
695
696 copilot_ui::init(&app_state, cx);
697 language_model::init(cx);
698 RefreshLlmTokenListener::register(
699 app_state.client.clone(),
700 app_state.user_store.clone(),
701 cx,
702 );
703 language_models::init(app_state.user_store.clone(), app_state.client.clone(), cx);
704 acp_tools::init(cx);
705 zed::telemetry_log::init(cx);
706 zed::remote_debug::init(cx);
707 edit_prediction_ui::init(cx);
708 web_search::init(cx);
709 web_search_providers::init(app_state.client.clone(), app_state.user_store.clone(), cx);
710 snippet_provider::init(cx);
711 edit_prediction_registry::init(app_state.client.clone(), app_state.user_store.clone(), cx);
712 let prompt_builder = PromptBuilder::load(app_state.fs.clone(), stdout_is_a_pty(), cx);
713 project::AgentRegistryStore::init_global(
714 cx,
715 app_state.fs.clone(),
716 app_state.client.http_client(),
717 );
718 agent_ui::init(
719 app_state.fs.clone(),
720 prompt_builder,
721 app_state.languages.clone(),
722 is_new_install,
723 false,
724 cx,
725 );
726 zed::watch_user_agents_md(app_state.fs.clone(), cx);
727
728 repl::init(app_state.fs.clone(), cx);
729 recent_projects::init(cx);
730 dev_container::init(cx);
731
732 load_embedded_fonts(cx);
733
734 editor::init(cx);
735 image_viewer::init(cx);
736 repl::notebook::init(cx);
737 diagnostics::init(cx);
738
739 audio::init(cx);
740 workspace::init(app_state.clone(), cx);
741 ui_prompt::init(cx);
742
743 go_to_line::init(cx);
744 file_finder::init(cx);
745 tab_switcher::init(cx);
746 outline::init(cx);
747 project_symbols::init(cx);
748 project_panel::init(cx);
749 outline_panel::init(cx);
750 tasks_ui::init(cx);
751 snippets_ui::init(cx);
752 channel::init(&app_state.client.clone(), app_state.user_store.clone(), cx);
753 search::init(cx);
754 lsp_locations::init(cx);
755 cx.set_global(workspace::PaneSearchBarCallbacks {
756 setup_search_bar: |languages, toolbar, window, cx| {
757 let search_bar = cx.new(|cx| search::BufferSearchBar::new(languages, window, cx));
758 toolbar.update(cx, |toolbar, cx| {
759 toolbar.add_item(search_bar, window, cx);
760 });
761 },
762 wrap_div_with_search_actions: search::buffer_search::register_pane_search_actions,
763 });
764 vim::init(cx);
765 terminal_view::init(cx);
766 journal::init(app_state.clone(), cx);
767 encoding_selector::init(cx);
768 language_selector::init(cx);
769 line_ending_selector::init(cx);
770 toolchain_selector::init(cx);
771 theme_selector::init(cx);
772 settings_profile_selector::init(cx);
773 language_tools::init(cx);
774 call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
775 notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx);
776 git_ui::init(cx);
777 feedback::init(cx);
778 markdown_preview::init(cx);
779 csv_preview::init(cx);
780 svg_preview::init(cx);
781 onboarding::init(cx);
782 settings_ui::init(cx);
783 keymap_editor::init(cx);
784 extensions_ui::init(cx);
785 edit_prediction::init(cx);
786 inspector_ui::init(app_state.clone(), cx);
787 json_schema_store::init(cx);
788 miniprofiler_ui::init(*STARTUP_TIME.get().unwrap(), cx);
789 which_key::init(cx);
790 #[cfg(target_os = "windows")]
791 etw_tracing::init(cx);
792
793 cx.observe_global::<SettingsStore>({
794 let http = app_state.client.http_client();
795 let client = app_state.client.clone();
796 move |cx| {
797 for &mut window in cx.windows().iter_mut() {
798 let background_appearance = cx.theme().window_background_appearance();
799 window
800 .update(cx, |_, window, _| {
801 window.set_background_appearance(background_appearance)
802 })
803 .ok();
804 }
805
806 cx.set_text_rendering_mode(
807 match WorkspaceSettings::get_global(cx).text_rendering_mode {
808 settings::TextRenderingMode::PlatformDefault => {
809 gpui::TextRenderingMode::PlatformDefault
810 }
811 settings::TextRenderingMode::Subpixel => gpui::TextRenderingMode::Subpixel,
812 settings::TextRenderingMode::Grayscale => {
813 gpui::TextRenderingMode::Grayscale
814 }
815 },
816 );
817
818 let new_host = &client::ClientSettings::get_global(cx).server_url;
819 if &http.base_url() != new_host {
820 http.set_base_url(new_host);
821 if client.status().borrow().is_connected() {
822 client.reconnect(&cx.to_async());
823 }
824 }
825 }
826 })
827 .detach();
828 app_state.languages.set_theme(cx.theme().clone());
829 cx.observe_global::<GlobalTheme>({
830 let languages = app_state.languages.clone();
831 move |cx| {
832 languages.set_theme(cx.theme().clone());
833 }
834 })
835 .detach();
836 telemetry::event!(
837 "Settings Changed",
838 setting = "theme",
839 value = cx.theme().name.to_string()
840 );
841 telemetry::event!(
842 "Settings Changed",
843 setting = "keymap",
844 value = BaseKeymap::get_global(cx).to_string()
845 );
846 telemetry.flush_events().detach();
847
848 let fs = app_state.fs.clone();
849 load_user_themes_in_background(fs.clone(), cx);
850 watch_themes(fs.clone(), cx);
851 #[cfg(debug_assertions)]
852 watch_languages(fs.clone(), app_state.languages.clone(), cx);
853
854 let menus = app_menus(cx);
855 cx.set_menus(menus);
856
857 if let Some(mut crash_handler) = crash_handler {
858 let crash_handler2 = block_on(poll_once(&mut crash_handler));
859 match crash_handler2 {
860 Some(crash_handler) => {
861 cx.set_global(CrashHandler(crash_handler));
862 }
863 None => {
864 cx.spawn(async move |cx| {
865 let client1 = crash_handler.await;
866 cx.update(|cx| {
867 cx.set_global(CrashHandler(client1));
868 });
869 })
870 .detach();
871 }
872 }
873 }
874
875 initialize_workspace(app_state.clone(), cx);
876
877 cx.activate(true);
878
879 cx.spawn({
880 let client = app_state.client.clone();
881 async move |cx| authenticate(client, cx).await
882 })
883 .detach_and_log_err(cx);
884
885 let urls: Vec<_> = args
886 .paths_or_urls
887 .iter()
888 .map(|arg| parse_url_arg(arg, cx))
889 .collect();
890
891 // Check if any diff paths are directories to determine diff_all mode
892 let diff_all_mode = args
893 .diff
894 .chunks(2)
895 .any(|pair| Path::new(&pair[0]).is_dir() || Path::new(&pair[1]).is_dir());
896
897 let diff_paths: Vec<[String; 2]> = args
898 .diff
899 .chunks(2)
900 .map(|chunk| [chunk[0].clone(), chunk[1].clone()])
901 .collect();
902
903 #[cfg(target_os = "windows")]
904 let wsl = args.wsl;
905 #[cfg(not(target_os = "windows"))]
906 let wsl = None;
907
908 if !urls.is_empty() || !diff_paths.is_empty() {
909 open_listener.open(RawOpenRequest {
910 urls,
911 diff_paths,
912 wsl,
913 diff_all: diff_all_mode,
914 dev_container: args.dev_container,
915 ..Default::default()
916 })
917 }
918
919 let (current_session_id, last_session_id) = {
920 let session = app_state.session.read(cx);
921 (
922 session.id().to_owned(),
923 session.last_session_id().map(|id| id.to_owned()),
924 )
925 };
926
927 let restore_task = match open_rx
928 .try_recv()
929 .ok()
930 .and_then(|request| OpenRequest::parse(request, cx).log_err())
931 {
932 Some(request) if request.is_focus_app_only() => cx.spawn({
933 let app_state = app_state.clone();
934 async move |cx| {
935 if let Err(e) = restore_or_create_workspace(app_state, cx).await {
936 fail_to_open_window_async(e, cx)
937 }
938 }
939 }),
940 Some(request) => {
941 handle_open_request(request, app_state.clone(), cx);
942 Task::ready(())
943 }
944 None => cx.spawn({
945 let app_state = app_state.clone();
946 async move |cx| {
947 if let Err(e) = restore_or_create_workspace(app_state, cx).await {
948 fail_to_open_window_async(e, cx)
949 }
950 }
951 }),
952 };
953
954 cx.spawn({
955 let db = workspace::WorkspaceDb::global(cx);
956 let fs = app_state.fs.clone();
957 async move |_cx| {
958 restore_task.await;
959 db.garbage_collect_workspaces(
960 fs.as_ref(),
961 ¤t_session_id,
962 last_session_id.as_deref(),
963 )
964 .await
965 }
966 })
967 .detach_and_log_err(cx);
968
969 let app_state = app_state.clone();
970
971 component_preview::init(app_state.clone(), cx);
972
973 cx.spawn(async move |cx| {
974 while let Some(urls) = open_rx.next().await {
975 cx.update(|cx| {
976 if let Some(request) = OpenRequest::parse(urls, cx).log_err() {
977 handle_open_request(request, app_state.clone(), cx);
978 }
979 });
980 }
981 })
982 .detach();
983 });
984}
985
986fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut App) {
987 cx.spawn(async move |cx| {
988 if let Err(error) = await_identity_ready(app_state.clone(), cx).await {
989 fail_to_open_window_async(error, cx);
990 return;
991 }
992 cx.update(|cx| dispatch_open_request(request, app_state, cx));
993 })
994 .detach();
995}
996
997fn dispatch_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut App) {
998 if let Some(kind) = request.kind {
999 match kind {
1000 OpenRequestKind::CliConnection(connection) => {
1001 cx.spawn(async move |cx| handle_cli_connection(connection, app_state, cx).await)
1002 .detach();
1003 }
1004 OpenRequestKind::FocusApp => {
1005 cx.spawn(async move |cx| {
1006 if workspace::activate_any_workspace_window(cx).is_some() {
1007 return anyhow::Ok(());
1008 }
1009 restore_or_create_workspace(app_state, cx).await
1010 })
1011 .detach_and_log_err(cx);
1012 }
1013 OpenRequestKind::Extension { extension_id } => {
1014 cx.spawn(async move |cx| {
1015 let workspace =
1016 workspace::get_any_active_multi_workspace(app_state, cx.clone()).await?;
1017 workspace.update(cx, |_, window, cx| {
1018 window.dispatch_action(
1019 Box::new(zed_actions::Extensions {
1020 category_filter: None,
1021 id: Some(extension_id),
1022 }),
1023 cx,
1024 );
1025 })
1026 })
1027 .detach_and_log_err(cx);
1028 }
1029 OpenRequestKind::AgentPanel {
1030 external_source_prompt,
1031 } => {
1032 cx.spawn(async move |cx| {
1033 let multi_workspace =
1034 workspace::get_any_active_multi_workspace(app_state, cx.clone()).await?;
1035
1036 let panels_task = multi_workspace.update(cx, |multi_workspace, _, cx| {
1037 multi_workspace
1038 .workspace()
1039 .update(cx, |workspace, _| workspace.take_panels_task())
1040 })?;
1041 if let Some(task) = panels_task {
1042 task.await.log_err();
1043 }
1044
1045 multi_workspace.update(cx, |multi_workspace, window, cx| {
1046 multi_workspace.workspace().update(cx, |workspace, cx| {
1047 if let Some(panel) = workspace.focus_panel::<AgentPanel>(window, cx) {
1048 panel.update(cx, |panel, cx| {
1049 panel.new_agent_thread_with_external_source_prompt(
1050 external_source_prompt,
1051 window,
1052 cx,
1053 );
1054 });
1055 } else {
1056 log::warn!(
1057 "zed://agent received but the AgentPanel is not registered \
1058 (is `disable_ai` enabled?)"
1059 );
1060 }
1061 });
1062 })
1063 })
1064 .detach_and_log_err(cx);
1065 }
1066 OpenRequestKind::InstallSkill { content } => {
1067 cx.spawn(async move |cx| {
1068 let multi_workspace =
1069 workspace::get_any_active_multi_workspace(app_state, cx.clone()).await?;
1070
1071 multi_workspace.update(cx, |_multi_workspace, _window, cx| {
1072 settings_ui::open_skill_creator(
1073 settings_ui::pages::SkillCreatorOpenMode::Install { content },
1074 Some(multi_workspace),
1075 cx,
1076 );
1077 })
1078 })
1079 .detach_and_log_err(cx);
1080 }
1081 OpenRequestKind::DockMenuAction { index } => {
1082 cx.perform_dock_menu_action(index);
1083 }
1084 OpenRequestKind::BuiltinJsonSchema { schema_path } => {
1085 workspace::with_active_or_new_workspace(cx, |_workspace, window, cx| {
1086 cx.spawn_in(window, async move |workspace, cx| {
1087 let res = async move {
1088 let json = app_state.languages.language_for_name("JSONC").await.ok();
1089 let lsp_store = workspace.update(cx, |workspace, cx| {
1090 workspace
1091 .project()
1092 .update(cx, |project, _| project.lsp_store())
1093 })?;
1094 let uri = format!("zed://schemas/{}", schema_path);
1095 let json_schema_content =
1096 json_schema_store::handle_schema_request(lsp_store, uri, cx)
1097 .await?;
1098 let json_schema_value: serde_json::Value =
1099 serde_json::from_str(&json_schema_content)
1100 .context("Failed to parse JSON Schema")?;
1101 let json_schema_content =
1102 serde_json::to_string_pretty(&json_schema_value)
1103 .context("Failed to serialize JSON Schema as JSON")?;
1104 let buffer_task = workspace.update(cx, |workspace, cx| {
1105 workspace.project().update(cx, |project, cx| {
1106 project.create_buffer(json, false, cx)
1107 })
1108 })?;
1109
1110 let buffer = buffer_task.await?;
1111
1112 workspace.update_in(cx, |workspace, window, cx| {
1113 buffer.update(cx, |buffer, cx| {
1114 buffer.edit([(0..0, json_schema_content)], None, cx);
1115 buffer.edit(
1116 [(0..0, format!("// {} JSON Schema\n", schema_path))],
1117 None,
1118 cx,
1119 );
1120 });
1121
1122 workspace.add_item_to_active_pane(
1123 Box::new(cx.new(|cx| {
1124 let mut editor =
1125 editor::Editor::for_buffer(buffer, None, window, cx);
1126 editor.set_read_only(true);
1127 editor
1128 })),
1129 None,
1130 true,
1131 window,
1132 cx,
1133 );
1134 })
1135 }
1136 .await;
1137 res.context("Failed to open builtin JSON Schema").log_err();
1138 })
1139 .detach();
1140 });
1141 }
1142 OpenRequestKind::Setting { setting_path } => {
1143 // zed://settings/languages/$(language)/tab_size - DONT SUPPORT
1144 // zed://settings/languages/Rust/tab_size - SUPPORT
1145 // languages.$(language).tab_size
1146 // [ languages $(language) tab_size]
1147 cx.spawn(async move |cx| {
1148 let workspace =
1149 workspace::get_any_active_multi_workspace(app_state, cx.clone()).await?;
1150
1151 workspace.update(cx, |_, window, cx| match setting_path {
1152 None => window.dispatch_action(Box::new(zed_actions::OpenSettings), cx),
1153 Some(setting_path) => window.dispatch_action(
1154 Box::new(zed_actions::OpenSettingsAt {
1155 path: setting_path,
1156 target: None,
1157 }),
1158 cx,
1159 ),
1160 })
1161 })
1162 .detach_and_log_err(cx);
1163 }
1164 OpenRequestKind::GitClone { repo_url } => {
1165 workspace::with_active_or_new_workspace(cx, |_workspace, window, cx| {
1166 if window.is_window_active() {
1167 clone_and_open(
1168 repo_url,
1169 cx.weak_entity(),
1170 window,
1171 cx,
1172 Arc::new(|workspace: &mut workspace::Workspace, window, cx| {
1173 workspace.focus_panel::<ProjectPanel>(window, cx);
1174 }),
1175 );
1176 return;
1177 }
1178
1179 let subscription = Rc::new(RefCell::new(None));
1180 subscription.replace(Some(cx.observe_in(&cx.entity(), window, {
1181 let subscription = subscription.clone();
1182 let repo_url = repo_url;
1183 move |_, workspace_entity, window, cx| {
1184 if window.is_window_active() && subscription.take().is_some() {
1185 clone_and_open(
1186 repo_url.clone(),
1187 workspace_entity.downgrade(),
1188 window,
1189 cx,
1190 Arc::new(|workspace: &mut workspace::Workspace, window, cx| {
1191 workspace.focus_panel::<ProjectPanel>(window, cx);
1192 }),
1193 );
1194 }
1195 }
1196 })));
1197 });
1198 }
1199 OpenRequestKind::GitCommit { sha } => {
1200 let base_open_options = zed::open_options_for_request(
1201 request.open_behavior,
1202 &workspace::SerializedWorkspaceLocation::Local,
1203 cx,
1204 );
1205 cx.spawn(async move |cx| {
1206 let paths_with_position =
1207 derive_paths_with_position(app_state.fs.as_ref(), request.open_paths).await;
1208 let (workspace, _results) = open_paths_with_positions(
1209 &paths_with_position,
1210 &[],
1211 false,
1212 app_state,
1213 base_open_options,
1214 cx,
1215 )
1216 .await?;
1217
1218 workspace
1219 .update(cx, |multi_workspace, window, cx| {
1220 multi_workspace
1221 .workspace()
1222 .clone()
1223 .update(cx, |workspace, cx| {
1224 let Some(repo) =
1225 workspace.project().read(cx).active_repository(cx)
1226 else {
1227 log::error!("no active repository found for commit view");
1228 return Err(anyhow::anyhow!("no active repository found"));
1229 };
1230
1231 git_ui::commit_view::CommitView::open(
1232 sha,
1233 repo.downgrade(),
1234 workspace.weak_handle(),
1235 None,
1236 None,
1237 window,
1238 cx,
1239 );
1240 Ok(())
1241 })
1242 })
1243 .log_err();
1244
1245 anyhow::Ok(())
1246 })
1247 .detach_and_log_err(cx);
1248 }
1249 }
1250
1251 return;
1252 }
1253
1254 if let Some(connection_options) = request.remote_connection {
1255 let open_behavior = request.open_behavior;
1256 let location = workspace::SerializedWorkspaceLocation::Remote(connection_options.clone());
1257 let base_open_options = zed::open_options_for_request(open_behavior, &location, cx);
1258 cx.spawn(async move |cx| {
1259 let paths: Vec<PathBuf> = request.open_paths.into_iter().map(PathBuf::from).collect();
1260 open_remote_project(connection_options, paths, app_state, base_open_options, cx).await
1261 })
1262 .detach_and_log_err(cx);
1263 return;
1264 }
1265
1266 let mut task = None;
1267 let dev_container = request.dev_container;
1268 if !request.open_paths.is_empty() || !request.diff_paths.is_empty() {
1269 let app_state = app_state.clone();
1270 let base_open_options = zed::open_options_for_request(
1271 request.open_behavior,
1272 &workspace::SerializedWorkspaceLocation::Local,
1273 cx,
1274 );
1275 task = Some(cx.spawn(async move |cx| {
1276 let paths_with_position =
1277 derive_paths_with_position(app_state.fs.as_ref(), request.open_paths).await;
1278 let (_window, results) = open_paths_with_positions(
1279 &paths_with_position,
1280 &request.diff_paths,
1281 request.diff_all,
1282 app_state,
1283 workspace::OpenOptions {
1284 open_in_dev_container: dev_container,
1285 ..base_open_options
1286 },
1287 cx,
1288 )
1289 .await?;
1290 for result in results.into_iter().flatten() {
1291 if let Err(err) = result {
1292 log::error!("Error opening path: {err:#}");
1293 }
1294 }
1295 anyhow::Ok(())
1296 }));
1297 }
1298
1299 if !request.open_channel_notes.is_empty() || request.join_channel.is_some() {
1300 cx.spawn(async move |cx| {
1301 let result = maybe!(async {
1302 if let Some(task) = task {
1303 task.await?;
1304 }
1305 let client = app_state.client.clone();
1306 // we continue even if connection fails as join_channel/ open channel notes will
1307 // show a visible error message.
1308 client.connect(true, cx).await.into_response().log_err();
1309
1310 if let Some(channel_id) = request.join_channel {
1311 cx.update(|cx| {
1312 workspace::join_channel(
1313 client::ChannelId(channel_id),
1314 app_state.clone(),
1315 None,
1316 None,
1317 cx,
1318 )
1319 })
1320 .await?;
1321 }
1322
1323 // Zed channel notes are retired in Omega. See omega#59.
1324 anyhow::Ok(())
1325 })
1326 .await;
1327 if let Err(err) = result {
1328 fail_to_open_window_async(err, cx);
1329 }
1330 })
1331 .detach()
1332 } else if let Some(task) = task {
1333 cx.spawn(async move |cx| {
1334 if let Err(err) = task.await {
1335 fail_to_open_window_async(err, cx);
1336 }
1337 })
1338 .detach();
1339 }
1340}
1341
1342async fn authenticate(client: Arc<Client>, cx: &AsyncApp) -> Result<()> {
1343 if !app_identity::zed_production_services_enabled() {
1344 log::info!(
1345 "Skipping Zed account authentication; Omega production services isolation is enabled"
1346 );
1347 return Ok(());
1348 }
1349
1350 if stdout_is_a_pty() {
1351 if client::IMPERSONATE_LOGIN.is_some() {
1352 client.sign_in_with_optional_connect(false, cx).await?;
1353 } else if client.has_credentials(cx).await {
1354 client.sign_in_with_optional_connect(true, cx).await?;
1355 }
1356 } else if client.has_credentials(cx).await {
1357 client.sign_in_with_optional_connect(true, cx).await?;
1358 }
1359
1360 Ok(())
1361}
1362
1363async fn system_id() -> Result<IdType> {
1364 let key_name = "system_id".to_string();
1365 let db = GlobalKeyValueStore::global();
1366
1367 if let Ok(Some(system_id)) = db.read_kvp(&key_name) {
1368 return Ok(IdType::Existing(system_id));
1369 }
1370
1371 let system_id = Uuid::new_v4().to_string();
1372
1373 db.write_kvp(key_name, system_id.clone()).await?;
1374
1375 Ok(IdType::New(system_id))
1376}
1377
1378async fn installation_id(db: KeyValueStore) -> Result<IdType> {
1379 let legacy_key_name = "device_id".to_string();
1380 let key_name = "installation_id".to_string();
1381
1382 // Migrate legacy key to new key
1383 if let Ok(Some(installation_id)) = db.read_kvp(&legacy_key_name) {
1384 db.write_kvp(key_name, installation_id.clone()).await?;
1385 db.delete_kvp(legacy_key_name).await?;
1386 return Ok(IdType::Existing(installation_id));
1387 }
1388
1389 if let Ok(Some(installation_id)) = db.read_kvp(&key_name) {
1390 return Ok(IdType::Existing(installation_id));
1391 }
1392
1393 let installation_id = Uuid::new_v4().to_string();
1394
1395 db.write_kvp(key_name, installation_id.clone()).await?;
1396
1397 Ok(IdType::New(installation_id))
1398}
1399
1400pub(crate) async fn restore_or_create_workspace(
1401 app_state: Arc<AppState>,
1402 cx: &mut AsyncApp,
1403) -> Result<()> {
1404 await_identity_ready(app_state.clone(), cx).await?;
1405 if let Some(multi_workspaces) = restorable_workspaces(cx, &app_state).await {
1406 let mut error_count = 0;
1407 for multi_workspace in multi_workspaces {
1408 let result = match &multi_workspace.active_workspace.location {
1409 SerializedWorkspaceLocation::Local => {
1410 restore_multiworkspace(multi_workspace, app_state.clone(), cx)
1411 .await
1412 .map(|_| ())
1413 }
1414 SerializedWorkspaceLocation::Remote(connection_options) => {
1415 let mut connection_options = connection_options.clone();
1416 if let RemoteConnectionOptions::Ssh(options) = &mut connection_options {
1417 cx.update(|cx| {
1418 RemoteSettings::get_global(cx)
1419 .fill_connection_options_from_settings(options)
1420 });
1421 }
1422
1423 let paths = multi_workspace
1424 .active_workspace
1425 .paths
1426 .paths()
1427 .iter()
1428 .map(PathBuf::from)
1429 .collect::<Vec<_>>();
1430 let state = multi_workspace.state.clone();
1431 async {
1432 let window = open_remote_project(
1433 connection_options,
1434 paths,
1435 app_state.clone(),
1436 workspace::OpenOptions::default(),
1437 cx,
1438 )
1439 .await?;
1440 workspace::apply_restored_multiworkspace_state(
1441 window,
1442 &state,
1443 app_state.fs.clone(),
1444 cx,
1445 )
1446 .await;
1447 Ok::<(), anyhow::Error>(())
1448 }
1449 .await
1450 }
1451 };
1452
1453 if let Err(error) = result {
1454 log::error!("Failed to restore workspace: {error:#}");
1455 error_count += 1;
1456 }
1457 }
1458
1459 if error_count > 0 {
1460 let message = if error_count == 1 {
1461 "Failed to restore 1 workspace. Check logs for details.".to_string()
1462 } else {
1463 format!(
1464 "Failed to restore {} workspaces. Check logs for details.",
1465 error_count
1466 )
1467 };
1468
1469 // Try to find an active workspace to show the toast
1470 let toast_shown = cx.update(|cx| {
1471 if let Some(window) = cx.active_window()
1472 && let Some(multi_workspace) = window.downcast::<MultiWorkspace>()
1473 {
1474 multi_workspace
1475 .update(cx, |multi_workspace, _, cx| {
1476 multi_workspace.workspace().update(cx, |workspace, cx| {
1477 workspace.show_toast(
1478 Toast::new(NotificationId::unique::<()>(), message.clone()),
1479 cx,
1480 )
1481 });
1482 })
1483 .ok();
1484 return true;
1485 }
1486 false
1487 });
1488
1489 // If we couldn't show a toast (no windows opened successfully),
1490 // open a fallback empty workspace and show the error there
1491 if !toast_shown {
1492 log::error!("All workspace restorations failed. Opening fallback empty workspace.");
1493 cx.update(|cx| {
1494 workspace::open_new(
1495 Default::default(),
1496 app_state.clone(),
1497 cx,
1498 |workspace, _window, cx| {
1499 workspace.show_toast(
1500 Toast::new(NotificationId::unique::<()>(), message),
1501 cx,
1502 );
1503 },
1504 )
1505 })
1506 .await?;
1507 }
1508 }
1509
1510 // If the user cancelled a failed remote connection at startup,
1511 // open_remote_project returns Ok but removes the window, so error_count
1512 // stays 0 and the toast fallback above does not trigger. Without this
1513 // check, Zed would exit silently.
1514 if cx.update(|cx| cx.windows().is_empty()) {
1515 cx.update(|cx| {
1516 workspace::open_new(
1517 Default::default(),
1518 app_state.clone(),
1519 cx,
1520 |_workspace, window, cx| {
1521 let restore_on_startup =
1522 WorkspaceSettings::get_global(cx).restore_on_startup;
1523 match restore_on_startup {
1524 // OMEGA-DELTA-0019. The launchpad behaviour opens
1525 // no content, and overriding it would be Omega
1526 // ignoring a setting the user set.
1527 workspace::RestoreOnStartupBehavior::Launchpad => {}
1528 // OMEGA-DELTA-0019. Upstream Zed opens an empty
1529 // untitled buffer here. Omega opens its front
1530 // door: the New Agent Thread surface, focused.
1531 _ => {
1532 agent_ui::AgentPanel::open_front_door(window, cx);
1533 }
1534 }
1535 },
1536 )
1537 })
1538 .await?;
1539 }
1540 } else {
1541 cx.update(|cx| {
1542 workspace::open_new(
1543 Default::default(),
1544 app_state,
1545 cx,
1546 |_workspace, window, cx| {
1547 let restore_on_startup = WorkspaceSettings::get_global(cx).restore_on_startup;
1548 match restore_on_startup {
1549 // OMEGA-DELTA-0019. See above: the launchpad choice
1550 // stands, and the empty buffer becomes the front door.
1551 workspace::RestoreOnStartupBehavior::Launchpad => {}
1552 _ => {
1553 agent_ui::AgentPanel::open_front_door(window, cx);
1554 }
1555 }
1556 },
1557 )
1558 })
1559 .await?;
1560 }
1561
1562 Ok(())
1563}
1564
1565async fn restorable_workspaces(
1566 cx: &mut AsyncApp,
1567 app_state: &Arc<AppState>,
1568) -> Option<Vec<workspace::SerializedMultiWorkspace>> {
1569 let locations = restorable_workspace_locations(cx, app_state).await?;
1570 Some(cx.update(|cx| workspace::read_serialized_multi_workspaces(locations, cx)))
1571}
1572
1573pub(crate) async fn restorable_workspace_locations(
1574 cx: &mut AsyncApp,
1575 app_state: &Arc<AppState>,
1576) -> Option<Vec<SessionWorkspace>> {
1577 let (mut restore_behavior, db) = cx.update(|cx| {
1578 (
1579 WorkspaceSettings::get(None, cx).restore_on_startup,
1580 workspace::WorkspaceDb::global(cx),
1581 )
1582 });
1583
1584 let session_handle = app_state.session.clone();
1585 let (last_session_id, last_session_window_stack) = cx.update(|cx| {
1586 let session = session_handle.read(cx);
1587
1588 (
1589 session.last_session_id().map(|id| id.to_string()),
1590 session.last_session_window_stack(),
1591 )
1592 });
1593
1594 if last_session_id.is_none()
1595 && matches!(
1596 restore_behavior,
1597 workspace::RestoreOnStartupBehavior::LastSession
1598 )
1599 {
1600 restore_behavior = workspace::RestoreOnStartupBehavior::LastWorkspace;
1601 }
1602
1603 match restore_behavior {
1604 workspace::RestoreOnStartupBehavior::LastWorkspace => {
1605 workspace::last_opened_workspace_location(&db, app_state.fs.as_ref())
1606 .await
1607 .map(|(workspace_id, location, paths)| {
1608 vec![SessionWorkspace {
1609 workspace_id,
1610 location,
1611 paths,
1612 window_id: None,
1613 }]
1614 })
1615 }
1616 workspace::RestoreOnStartupBehavior::LastSession => {
1617 if let Some(last_session_id) = last_session_id {
1618 let ordered = last_session_window_stack.is_some();
1619
1620 let mut locations = workspace::last_session_workspace_locations(
1621 &db,
1622 &last_session_id,
1623 last_session_window_stack,
1624 app_state.fs.as_ref(),
1625 )
1626 .await
1627 .filter(|locations| !locations.is_empty());
1628
1629 // Since last_session_window_order returns the windows ordered front-to-back
1630 // we need to open the window that was frontmost last.
1631 if ordered && let Some(locations) = locations.as_mut() {
1632 locations.reverse();
1633 }
1634
1635 locations
1636 } else {
1637 None
1638 }
1639 }
1640 _ => None,
1641 }
1642}
1643
1644fn init_paths() -> HashMap<io::ErrorKind, Vec<&'static Path>> {
1645 [
1646 paths::config_dir(),
1647 paths::extensions_dir(),
1648 paths::languages_dir(),
1649 paths::debug_adapters_dir(),
1650 paths::database_dir(),
1651 paths::logs_dir(),
1652 paths::temp_dir(),
1653 paths::hang_traces_dir(),
1654 ]
1655 .into_iter()
1656 .fold(HashMap::default(), |mut errors, path| {
1657 if let Err(e) = std::fs::create_dir_all(path) {
1658 errors.entry(e.kind()).or_insert_with(Vec::new).push(path);
1659 }
1660 errors
1661 })
1662}
1663
1664pub(crate) static FORCE_CLI_MODE: LazyLock<bool> = LazyLock::new(|| {
1665 let env_var = std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_some();
1666 unsafe { std::env::remove_var(FORCE_CLI_MODE_ENV_VAR_NAME) };
1667 env_var
1668});
1669
1670fn stdout_is_a_pty() -> bool {
1671 !*FORCE_CLI_MODE && io::stdout().is_terminal()
1672}
1673
1674#[derive(Parser, Debug)]
1675#[command(name = "omega", disable_version_flag = true, max_term_width = 100)]
1676struct Args {
1677 /// A sequence of space-separated paths or urls that you want to open.
1678 ///
1679 /// Use `path:line:row` syntax to open a file at a specific location.
1680 /// Non-existing paths and directories will ignore `:line:row` suffix.
1681 ///
1682 /// URLs can use `file://`, the current Omega channel scheme, or the legacy `zed://` scheme.
1683 paths_or_urls: Vec<String>,
1684
1685 /// Opens the explicitly fictional, offline-safe public demo workroom.
1686 ///
1687 /// A custom user data directory is required so the demo cannot read or
1688 /// modify the normal Omega profile.
1689 #[arg(long, requires = "user_data_dir")]
1690 demo_workroom: bool,
1691
1692 /// Pairs of file paths to diff. Can be specified multiple times.
1693 /// When directories are provided, recurses into them and shows all changed files in a single multi-diff view.
1694 #[arg(long, action = clap::ArgAction::Append, num_args = 2, value_names = ["OLD_PATH", "NEW_PATH"])]
1695 diff: Vec<String>,
1696
1697 /// Sets a custom directory for all user data (e.g., database, extensions, logs).
1698 ///
1699 /// This overrides the default platform-specific data directory location.
1700 /// On macOS, the default is `~/Library/Application Support/<Omega channel>`.
1701 /// On Linux/FreeBSD, the default is `$XDG_DATA_HOME/<omega-channel>`.
1702 /// On Windows, the default is `%LOCALAPPDATA%\<Omega channel>`.
1703 #[arg(long, value_name = "DIR", verbatim_doc_comment)]
1704 user_data_dir: Option<String>,
1705
1706 /// The username and WSL distribution to use when opening paths. If not specified,
1707 /// Omega will attempt to open the paths directly.
1708 ///
1709 /// The username is optional, and if not specified, the default user for the distribution
1710 /// will be used.
1711 ///
1712 /// Example: `me@Ubuntu` or `Ubuntu`.
1713 ///
1714 /// WARN: You should not fill in this field by hand.
1715 #[cfg(target_os = "windows")]
1716 #[arg(long, value_name = "USER@DISTRO")]
1717 wsl: Option<String>,
1718
1719 /// Open the project in a dev container.
1720 ///
1721 /// Automatically triggers "Reopen in Dev Container" if a `.devcontainer/`
1722 /// configuration is found in the project directory.
1723 #[arg(long)]
1724 dev_container: bool,
1725
1726 /// Instructs Omega to run as a dev server on this machine. (not implemented)
1727 #[arg(long)]
1728 dev_server_token: Option<String>,
1729
1730 /// Prints system specs.
1731 ///
1732 /// Useful for submitting issues on GitHub when encountering a bug that
1733 /// prevents Omega from starting, so you can't run `omega: copy system specs to
1734 /// clipboard`
1735 #[arg(long)]
1736 system_specs: bool,
1737
1738 /// Used for recording minidumps on crashes by having Omega run a separate
1739 /// process communicating over a socket.
1740 #[arg(long, hide = true)]
1741 crash_handler: Option<PathBuf>,
1742
1743 /// Run Omega in the foreground, only used on Windows, to match the behavior on macOS.
1744 #[arg(long)]
1745 #[cfg(target_os = "windows")]
1746 #[arg(hide = true)]
1747 foreground: bool,
1748
1749 /// The dock action to perform. This is used on Windows only.
1750 #[arg(long)]
1751 #[cfg(target_os = "windows")]
1752 #[arg(hide = true)]
1753 dock_action: Option<usize>,
1754
1755 /// Used for SSH/Git password authentication, to remove the need for netcat as a dependency,
1756 /// by having Omega act like netcat communicating over a Unix socket.
1757 #[arg(long)]
1758 #[cfg(not(target_os = "windows"))]
1759 #[arg(hide = true)]
1760 askpass: Option<String>,
1761
1762 #[arg(long, hide = true)]
1763 dump_all_actions: bool,
1764
1765 /// Output current environment variables as JSON to stdout
1766 #[arg(long, hide = true)]
1767 printenv: bool,
1768
1769 /// Record an ETW trace. Must be run as administrator.
1770 #[cfg(target_os = "windows")]
1771 #[arg(long, hide = true)]
1772 record_etw_trace: bool,
1773
1774 /// The PID of the Omega process to trace for heap analysis.
1775 #[cfg(target_os = "windows")]
1776 #[arg(long, hide = true, allow_hyphen_values = true)]
1777 etw_zed_pid: Option<i64>,
1778
1779 /// Output path for the ETW trace file.
1780 #[cfg(target_os = "windows")]
1781 #[arg(long, hide = true)]
1782 etw_output: Option<PathBuf>,
1783
1784 /// Unix socket path for IPC with the parent Omega process.
1785 #[cfg(target_os = "windows")]
1786 #[arg(long, hide = true)]
1787 etw_socket: Option<String>,
1788}
1789
1790#[derive(Clone, Debug)]
1791enum IdType {
1792 New(String),
1793 Existing(String),
1794}
1795
1796impl ToString for IdType {
1797 fn to_string(&self) -> String {
1798 match self {
1799 IdType::New(id) | IdType::Existing(id) => id.clone(),
1800 }
1801 }
1802}
1803
1804fn parse_url_arg(arg: &str, cx: &App) -> String {
1805 match std::fs::canonicalize(Path::new(&arg)) {
1806 Ok(path) => format!("file://{}", path.display()),
1807 Err(_) => {
1808 if arg.starts_with("file://")
1809 || arg.starts_with("zed://")
1810 || arg.starts_with("zed-cli://")
1811 || arg.starts_with("ssh://")
1812 || arg.starts_with(&format!(
1813 "{}://",
1814 ReleaseChannel::try_global(cx)
1815 .unwrap_or(*release_channel::RELEASE_CHANNEL)
1816 .protocol_scheme()
1817 ))
1818 || parse_zed_link(arg, cx).is_some()
1819 {
1820 arg.into()
1821 } else {
1822 format!("file://{arg}")
1823 }
1824 }
1825 }
1826}
1827
1828fn load_embedded_fonts(cx: &App) {
1829 let asset_source = cx.asset_source();
1830 let font_paths = asset_source.list("fonts").unwrap();
1831 let embedded_fonts = Mutex::new(Vec::new());
1832 let executor = cx.background_executor();
1833
1834 cx.foreground_executor().block_on(executor.scoped(|scope| {
1835 for font_path in &font_paths {
1836 if !font_path.ends_with(".ttf") {
1837 continue;
1838 }
1839
1840 scope.spawn(async {
1841 let font_bytes = asset_source.load(font_path).unwrap().unwrap();
1842 embedded_fonts.lock().push(font_bytes);
1843 });
1844 }
1845 }));
1846
1847 cx.text_system()
1848 .add_fonts(embedded_fonts.into_inner())
1849 .unwrap();
1850}
1851
1852/// Spawns a background task to load the user themes from the themes directory.
1853fn load_user_themes_in_background(fs: Arc<dyn fs::Fs>, cx: &mut App) {
1854 cx.spawn({
1855 let fs = fs.clone();
1856 async move |cx| {
1857 let theme_registry = cx.update(|cx| ThemeRegistry::global(cx));
1858 let themes_dir = paths::themes_dir().as_ref();
1859 match fs
1860 .metadata(themes_dir)
1861 .await
1862 .ok()
1863 .flatten()
1864 .map(|m| m.is_dir)
1865 {
1866 Some(is_dir) => {
1867 anyhow::ensure!(is_dir, "Themes dir path {themes_dir:?} is not a directory")
1868 }
1869 None => {
1870 fs.create_dir(themes_dir).await.with_context(|| {
1871 format!("Failed to create themes dir at path {themes_dir:?}")
1872 })?;
1873 }
1874 }
1875
1876 let mut theme_paths = fs
1877 .read_dir(themes_dir)
1878 .await
1879 .with_context(|| format!("reading themes from {themes_dir:?}"))?;
1880
1881 while let Some(theme_path) = theme_paths.next().await {
1882 let Some(theme_path) = theme_path.log_err() else {
1883 continue;
1884 };
1885 let Some(bytes) = fs.load_bytes(&theme_path).await.log_err() else {
1886 continue;
1887 };
1888
1889 load_user_theme(&theme_registry, &bytes).log_err();
1890 }
1891
1892 cx.update(theme_settings::reload_theme);
1893 anyhow::Ok(())
1894 }
1895 })
1896 .detach_and_log_err(cx);
1897}
1898
1899/// Spawns a background task to watch the themes directory for changes.
1900fn watch_themes(fs: Arc<dyn fs::Fs>, cx: &mut App) {
1901 use std::time::Duration;
1902 cx.spawn(async move |cx| {
1903 let (mut events, _) = fs
1904 .watch(paths::themes_dir(), Duration::from_millis(100))
1905 .await;
1906
1907 while let Some(paths) = events.next().await {
1908 for event in paths {
1909 if fs
1910 .metadata(&event.path)
1911 .await
1912 .ok()
1913 .flatten()
1914 .is_some_and(|m| !m.is_dir)
1915 {
1916 let theme_registry = cx.update(|cx| ThemeRegistry::global(cx));
1917 if let Some(bytes) = fs.load_bytes(&event.path).await.log_err()
1918 && load_user_theme(&theme_registry, &bytes).log_err().is_some()
1919 {
1920 cx.update(theme_settings::reload_theme);
1921 }
1922 }
1923 }
1924 }
1925 })
1926 .detach()
1927}
1928
1929#[cfg(debug_assertions)]
1930fn watch_languages(fs: Arc<dyn fs::Fs>, languages: Arc<LanguageRegistry>, cx: &mut App) {
1931 use std::time::Duration;
1932
1933 cx.background_spawn(async move {
1934 let languages_src = Path::new("crates/grammars/src");
1935 let Some(languages_src) = fs.canonicalize(languages_src).await.log_err() else {
1936 return;
1937 };
1938
1939 let (mut events, watcher) = fs.watch(&languages_src, Duration::from_millis(100)).await;
1940
1941 // add subdirectories since fs.watch is not recursive on Linux
1942 if let Some(mut paths) = fs.read_dir(&languages_src).await.log_err() {
1943 while let Some(path) = paths.next().await {
1944 if let Some(path) = path.log_err()
1945 && fs.is_dir(&path).await
1946 {
1947 watcher.add(&path).log_err();
1948 }
1949 }
1950 }
1951
1952 while let Some(event) = events.next().await {
1953 let has_language_file = event
1954 .iter()
1955 .any(|event| event.path.extension().is_some_and(|ext| ext == "scm"));
1956 if has_language_file {
1957 languages.reload();
1958 }
1959 }
1960 })
1961 .detach();
1962}
1963
1964fn dump_all_gpui_actions() {
1965 #[derive(Debug, serde::Serialize)]
1966 struct ActionDef {
1967 name: &'static str,
1968 human_name: String,
1969 schema: Option<serde_json::Value>,
1970 deprecated_aliases: &'static [&'static str],
1971 deprecation_message: Option<&'static str>,
1972 documentation: Option<&'static str>,
1973 }
1974 let mut generator = settings::KeymapFile::action_schema_generator();
1975 let mut actions = gpui::generate_list_of_all_registered_actions()
1976 .map(|action| {
1977 let schema = (action.json_schema)(&mut generator)
1978 .map(|s| serde_json::to_value(s).expect("Failed to serialize action schema"));
1979 ActionDef {
1980 name: action.name,
1981 human_name: command_palette::humanize_action_name(action.name),
1982 schema,
1983 deprecated_aliases: action.deprecated_aliases,
1984 deprecation_message: action.deprecation_message,
1985 documentation: action.documentation,
1986 }
1987 })
1988 .collect::<Vec<ActionDef>>();
1989
1990 actions.sort_by_key(|a| a.name);
1991
1992 let schema_definitions = serde_json::to_value(generator.definitions())
1993 .expect("Failed to serialize schema definitions");
1994
1995 let output = serde_json::json!({
1996 "actions": actions,
1997 "schema_definitions": schema_definitions,
1998 });
1999
2000 io::Write::write(
2001 &mut std::io::stdout(),
2002 serde_json::to_string_pretty(&output).unwrap().as_bytes(),
2003 )
2004 .unwrap();
2005}
2006
2007#[cfg(target_os = "windows")]
2008fn check_for_conpty_dll() {
2009 use windows::{
2010 Win32::{Foundation::FreeLibrary, System::LibraryLoader::LoadLibraryW},
2011 core::w,
2012 };
2013
2014 if let Ok(hmodule) = unsafe { LoadLibraryW(w!("conpty.dll")) } {
2015 unsafe {
2016 FreeLibrary(hmodule)
2017 .context("Failed to free conpty.dll")
2018 .log_err();
2019 }
2020 } else {
2021 log::warn!("Failed to load conpty.dll. Terminal will work with reduced functionality.");
2022 }
2023}
2024