main.rs

  1#![allow(unused_variables, dead_code, unused_mut)]
  2// todo!() this is to make transition easier.
  3
  4// Allow binary to be called Zed for a nice application menu when running executable directly
  5#![allow(non_snake_case)]
  6
  7use anyhow::{anyhow, Context as _, Result};
  8use backtrace::Backtrace;
  9use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
 10use client::UserStore;
 11use db::kvp::KEY_VALUE_STORE;
 12use editor::Editor;
 13use fs::RealFs;
 14use futures::StreamExt;
 15use gpui::{Action, App, AppContext, AsyncAppContext, Context, SemanticVersion, Task};
 16use isahc::{prelude::Configurable, Request};
 17use language::LanguageRegistry;
 18use log::LevelFilter;
 19
 20use node_runtime::RealNodeRuntime;
 21use parking_lot::Mutex;
 22use serde::{Deserialize, Serialize};
 23use settings::{
 24    default_settings, handle_keymap_file_changes, handle_settings_file_changes, watch_config_file,
 25    Settings, SettingsStore,
 26};
 27use simplelog::ConfigBuilder;
 28use smol::process::Command;
 29use std::{
 30    env,
 31    ffi::OsStr,
 32    fs::OpenOptions,
 33    io::{IsTerminal, Write},
 34    panic,
 35    path::{Path, PathBuf},
 36    sync::{
 37        atomic::{AtomicU32, Ordering},
 38        Arc,
 39    },
 40    thread,
 41    time::{SystemTime, UNIX_EPOCH},
 42};
 43use theme::ActiveTheme;
 44use util::{
 45    async_maybe,
 46    channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
 47    http::{self, HttpClient},
 48    paths, ResultExt,
 49};
 50use uuid::Uuid;
 51use workspace::{AppState, WorkspaceStore};
 52use zed2::{
 53    build_window_options, ensure_only_instance, handle_cli_connection, initialize_workspace,
 54    languages, Assets, IsOnlyInstance, OpenListener, OpenRequest,
 55};
 56
 57mod open_listener;
 58
 59fn main() {
 60    menu::init();
 61    zed_actions::init();
 62
 63    let http = http::client();
 64    init_paths();
 65    init_logger();
 66
 67    if ensure_only_instance() != IsOnlyInstance::Yes {
 68        return;
 69    }
 70
 71    log::info!("========== starting zed ==========");
 72    let app = App::production(Arc::new(Assets));
 73
 74    let installation_id = app.background_executor().block(installation_id()).ok();
 75    let session_id = Uuid::new_v4().to_string();
 76    init_panic_hook(&app, installation_id.clone(), session_id.clone());
 77
 78    let fs = Arc::new(RealFs);
 79    let user_settings_file_rx = watch_config_file(
 80        &app.background_executor(),
 81        fs.clone(),
 82        paths::SETTINGS.clone(),
 83    );
 84    let user_keymap_file_rx = watch_config_file(
 85        &app.background_executor(),
 86        fs.clone(),
 87        paths::KEYMAP.clone(),
 88    );
 89
 90    let login_shell_env_loaded = if stdout_is_a_pty() {
 91        Task::ready(())
 92    } else {
 93        app.background_executor().spawn(async {
 94            load_login_shell_environment().await.log_err();
 95        })
 96    };
 97
 98    let (listener, mut open_rx) = OpenListener::new();
 99    let listener = Arc::new(listener);
100    let open_listener = listener.clone();
101    app.on_open_urls(move |urls, _| open_listener.open_urls(&urls));
102    app.on_reopen(move |_cx| {
103        // todo!("workspace")
104        // if cx.has_global::<Weak<AppState>>() {
105        // if let Some(app_state) = cx.global::<Weak<AppState>>().upgrade() {
106        // workspace::open_new(&app_state, cx, |workspace, cx| {
107        //     Editor::new_file(workspace, &Default::default(), cx)
108        // })
109        // .detach();
110        // }
111        // }
112    });
113
114    app.run(move |cx| {
115        cx.set_global(*RELEASE_CHANNEL);
116        cx.set_global(listener.clone());
117
118        load_embedded_fonts(cx);
119
120        let mut store = SettingsStore::default();
121        store
122            .set_default_settings(default_settings().as_ref(), cx)
123            .unwrap();
124        cx.set_global(store);
125        handle_settings_file_changes(user_settings_file_rx, cx);
126        handle_keymap_file_changes(user_keymap_file_rx, cx);
127
128        let client = client::Client::new(http.clone(), cx);
129        let mut languages = LanguageRegistry::new(login_shell_env_loaded);
130        let copilot_language_server_id = languages.next_language_server_id();
131        languages.set_executor(cx.background_executor().clone());
132        languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone());
133        let languages = Arc::new(languages);
134        let node_runtime = RealNodeRuntime::new(http.clone());
135
136        language::init(cx);
137        languages::init(languages.clone(), node_runtime.clone(), cx);
138        let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http.clone(), cx));
139        let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
140
141        cx.set_global(client.clone());
142
143        theme::init(theme::LoadThemes::All, cx);
144        project::Project::init(&client, cx);
145        client::init(&client, cx);
146        command_palette::init(cx);
147        language::init(cx);
148        editor::init(cx);
149        diagnostics::init(cx);
150        copilot::init(
151            copilot_language_server_id,
152            http.clone(),
153            node_runtime.clone(),
154            cx,
155        );
156        // assistant::init(cx);
157        // component_test::init(cx);
158
159        // cx.spawn(|cx| watch_themes(fs.clone(), cx)).detach();
160        // cx.spawn(|_| watch_languages(fs.clone(), languages.clone()))
161        //     .detach();
162        // watch_file_types(fs.clone(), cx);
163
164        languages.set_theme(cx.theme().clone());
165        // cx.observe_global::<SettingsStore, _>({
166        //     let languages = languages.clone();
167        //     move |cx| languages.set_theme(theme::current(cx).clone())
168        // })
169        // .detach();
170
171        // client.telemetry().start(installation_id, session_id, cx);
172
173        let app_state = Arc::new(AppState {
174            languages,
175            client: client.clone(),
176            user_store,
177            fs,
178            build_window_options,
179            // background_actions: todo!("ask Mikayla"),
180            workspace_store,
181            node_runtime,
182        });
183        cx.set_global(Arc::downgrade(&app_state));
184
185        // audio::init(Assets, cx);
186        // auto_update::init(http.clone(), client::ZED_SERVER_URL.clone(), cx);
187
188        workspace::init(app_state.clone(), cx);
189        // recent_projects::init(cx);
190
191        go_to_line::init(cx);
192        file_finder::init(cx);
193        // outline::init(cx);
194        // project_symbols::init(cx);
195        project_panel::init(Assets, cx);
196        // channel::init(&client, user_store.clone(), cx);
197        // diagnostics::init(cx);
198        // search::init(cx);
199        // semantic_index::init(fs.clone(), http.clone(), languages.clone(), cx);
200        // vim::init(cx);
201        terminal_view::init(cx);
202
203        // journal2::init(app_state.clone(), cx);
204        // language_selector::init(cx);
205        // theme_selector::init(cx);
206        // activity_indicator::init(cx);
207        // language_tools::init(cx);
208        call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
209        collab_ui::init(&app_state, cx);
210        // feedback::init(cx);
211        // welcome::init(cx);
212        // zed::init(&app_state, cx);
213
214        // cx.set_menus(menus::menus());
215        initialize_workspace(app_state.clone(), cx);
216
217        if stdout_is_a_pty() {
218            cx.activate(true);
219            let urls = collect_url_args();
220            if !urls.is_empty() {
221                listener.open_urls(&urls)
222            }
223        } else {
224            upload_previous_panics(http.clone(), cx);
225
226            // TODO Development mode that forces the CLI mode usually runs Zed binary as is instead
227            // of an *app, hence gets no specific callbacks run. Emulate them here, if needed.
228            if std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_some()
229                && !listener.triggered.load(Ordering::Acquire)
230            {
231                listener.open_urls(&collect_url_args())
232            }
233        }
234
235        let mut _triggered_authentication = false;
236
237        fn open_paths_and_log_errs(
238            paths: &[PathBuf],
239            app_state: &Arc<AppState>,
240            cx: &mut AppContext,
241        ) {
242            let task = workspace::open_paths(&paths, &app_state, None, cx);
243            cx.spawn(|cx| async move {
244                if let Some((_window, results)) = task.await.log_err() {
245                    for result in results {
246                        if let Some(Err(e)) = result {
247                            log::error!("Error opening path: {}", e);
248                        }
249                    }
250                }
251            })
252            .detach();
253        }
254
255        match open_rx.try_next() {
256            Ok(Some(OpenRequest::Paths { paths })) => {
257                open_paths_and_log_errs(&paths, &app_state, cx)
258            }
259            Ok(Some(OpenRequest::CliConnection { connection })) => {
260                let app_state = app_state.clone();
261                cx.spawn(move |cx| handle_cli_connection(connection, app_state, cx))
262                    .detach();
263            }
264            Ok(Some(OpenRequest::JoinChannel { channel_id: _ })) => {
265                todo!()
266                // triggered_authentication = true;
267                // let app_state = app_state.clone();
268                // let client = client.clone();
269                // cx.spawn(|mut cx| async move {
270                //     // ignore errors here, we'll show a generic "not signed in"
271                //     let _ = authenticate(client, &cx).await;
272                //     cx.update(|cx| workspace::join_channel(channel_id, app_state, None, cx))
273                //         .await
274                // })
275                // .detach_and_log_err(cx)
276            }
277            Ok(Some(OpenRequest::OpenChannelNotes { channel_id: _ })) => {
278                todo!()
279            }
280            Ok(None) | Err(_) => cx
281                .spawn({
282                    let app_state = app_state.clone();
283                    |cx| async move { restore_or_create_workspace(&app_state, cx).await }
284                })
285                .detach(),
286        }
287
288        let app_state = app_state.clone();
289        cx.spawn(|cx| async move {
290            while let Some(request) = open_rx.next().await {
291                match request {
292                    OpenRequest::Paths { paths } => {
293                        cx.update(|cx| open_paths_and_log_errs(&paths, &app_state, cx))
294                            .ok();
295                    }
296                    OpenRequest::CliConnection { connection } => {
297                        let app_state = app_state.clone();
298                        cx.spawn(move |cx| {
299                            handle_cli_connection(connection, app_state.clone(), cx)
300                        })
301                        .detach();
302                    }
303                    OpenRequest::JoinChannel { channel_id: _ } => {
304                        todo!()
305                    }
306                    OpenRequest::OpenChannelNotes { channel_id: _ } => {
307                        todo!()
308                    }
309                }
310            }
311        })
312        .detach();
313
314        // if !triggered_authentication {
315        //     cx.spawn(|cx| async move { authenticate(client, &cx).await })
316        //         .detach_and_log_err(cx);
317        // }
318    });
319}
320
321// async fn authenticate(client: Arc<Client>, cx: &AsyncAppContext) -> Result<()> {
322//     if stdout_is_a_pty() {
323//         if client::IMPERSONATE_LOGIN.is_some() {
324//             client.authenticate_and_connect(false, &cx).await?;
325//         }
326//     } else if client.has_keychain_credentials(&cx) {
327//         client.authenticate_and_connect(true, &cx).await?;
328//     }
329//     Ok::<_, anyhow::Error>(())
330// }
331
332async fn installation_id() -> Result<String> {
333    let legacy_key_name = "device_id";
334
335    if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(legacy_key_name) {
336        Ok(installation_id)
337    } else {
338        let installation_id = Uuid::new_v4().to_string();
339
340        KEY_VALUE_STORE
341            .write_kvp(legacy_key_name.to_string(), installation_id.clone())
342            .await?;
343
344        Ok(installation_id)
345    }
346}
347
348async fn restore_or_create_workspace(app_state: &Arc<AppState>, mut cx: AsyncAppContext) {
349    async_maybe!({
350        if let Some(location) = workspace::last_opened_workspace_paths().await {
351            cx.update(|cx| workspace::open_paths(location.paths().as_ref(), app_state, None, cx))?
352                .await
353                .log_err();
354        } else if matches!(KEY_VALUE_STORE.read_kvp("******* THIS IS A BAD KEY PLEASE UNCOMMENT BELOW TO FIX THIS VERY LONG LINE *******"), Ok(None)) {
355            // todo!(welcome)
356            //} else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
357            //todo!()
358            // cx.update(|cx| show_welcome_experience(app_state, cx));
359        } else {
360            cx.update(|cx| {
361                workspace::open_new(app_state, cx, |workspace, cx| {
362                    Editor::new_file(workspace, &Default::default(), cx)
363                })
364                .detach();
365            })?;
366        }
367        anyhow::Ok(())
368    })
369    .await
370    .log_err();
371}
372
373fn init_paths() {
374    std::fs::create_dir_all(&*util::paths::CONFIG_DIR).expect("could not create config path");
375    std::fs::create_dir_all(&*util::paths::LANGUAGES_DIR).expect("could not create languages path");
376    std::fs::create_dir_all(&*util::paths::DB_DIR).expect("could not create database path");
377    std::fs::create_dir_all(&*util::paths::LOGS_DIR).expect("could not create logs path");
378}
379
380fn init_logger() {
381    if stdout_is_a_pty() {
382        env_logger::init();
383    } else {
384        let level = LevelFilter::Info;
385
386        // Prevent log file from becoming too large.
387        const KIB: u64 = 1024;
388        const MIB: u64 = 1024 * KIB;
389        const MAX_LOG_BYTES: u64 = MIB;
390        if std::fs::metadata(&*paths::LOG).map_or(false, |metadata| metadata.len() > MAX_LOG_BYTES)
391        {
392            let _ = std::fs::rename(&*paths::LOG, &*paths::OLD_LOG);
393        }
394
395        let log_file = OpenOptions::new()
396            .create(true)
397            .append(true)
398            .open(&*paths::LOG)
399            .expect("could not open logfile");
400
401        let config = ConfigBuilder::new()
402            .set_time_format_str("%Y-%m-%dT%T") //All timestamps are UTC
403            .build();
404
405        simplelog::WriteLogger::init(level, config, log_file).expect("could not initialize logger");
406    }
407}
408
409#[derive(Serialize, Deserialize)]
410struct LocationData {
411    file: String,
412    line: u32,
413}
414
415#[derive(Serialize, Deserialize)]
416struct Panic {
417    thread: String,
418    payload: String,
419    #[serde(skip_serializing_if = "Option::is_none")]
420    location_data: Option<LocationData>,
421    backtrace: Vec<String>,
422    app_version: String,
423    release_channel: String,
424    os_name: String,
425    os_version: Option<String>,
426    architecture: String,
427    panicked_on: u128,
428    #[serde(skip_serializing_if = "Option::is_none")]
429    installation_id: Option<String>,
430    session_id: String,
431}
432
433#[derive(Serialize)]
434struct PanicRequest {
435    panic: Panic,
436    token: String,
437}
438
439static PANIC_COUNT: AtomicU32 = AtomicU32::new(0);
440
441fn init_panic_hook(app: &App, installation_id: Option<String>, session_id: String) {
442    let is_pty = stdout_is_a_pty();
443    let app_metadata = app.metadata();
444
445    panic::set_hook(Box::new(move |info| {
446        let prior_panic_count = PANIC_COUNT.fetch_add(1, Ordering::SeqCst);
447        if prior_panic_count > 0 {
448            // Give the panic-ing thread time to write the panic file
449            loop {
450                std::thread::yield_now();
451            }
452        }
453
454        let thread = thread::current();
455        let thread_name = thread.name().unwrap_or("<unnamed>");
456
457        let payload = info
458            .payload()
459            .downcast_ref::<&str>()
460            .map(|s| s.to_string())
461            .or_else(|| info.payload().downcast_ref::<String>().map(|s| s.clone()))
462            .unwrap_or_else(|| "Box<Any>".to_string());
463
464        if *util::channel::RELEASE_CHANNEL == ReleaseChannel::Dev {
465            let location = info.location().unwrap();
466            let backtrace = Backtrace::new();
467            eprintln!(
468                "Thread {:?} panicked with {:?} at {}:{}:{}\n{:?}",
469                thread_name,
470                payload,
471                location.file(),
472                location.line(),
473                location.column(),
474                backtrace,
475            );
476            std::process::exit(-1);
477        }
478
479        let app_version = client::ZED_APP_VERSION
480            .or(app_metadata.app_version)
481            .map_or("dev".to_string(), |v| v.to_string());
482
483        let backtrace = Backtrace::new();
484        let mut backtrace = backtrace
485            .frames()
486            .iter()
487            .filter_map(|frame| Some(format!("{:#}", frame.symbols().first()?.name()?)))
488            .collect::<Vec<_>>();
489
490        // Strip out leading stack frames for rust panic-handling.
491        if let Some(ix) = backtrace
492            .iter()
493            .position(|name| name == "rust_begin_unwind")
494        {
495            backtrace.drain(0..=ix);
496        }
497
498        let panic_data = Panic {
499            thread: thread_name.into(),
500            payload: payload.into(),
501            location_data: info.location().map(|location| LocationData {
502                file: location.file().into(),
503                line: location.line(),
504            }),
505            app_version: app_version.clone(),
506            release_channel: RELEASE_CHANNEL.display_name().into(),
507            os_name: app_metadata.os_name.into(),
508            os_version: app_metadata
509                .os_version
510                .as_ref()
511                .map(SemanticVersion::to_string),
512            architecture: env::consts::ARCH.into(),
513            panicked_on: SystemTime::now()
514                .duration_since(UNIX_EPOCH)
515                .unwrap()
516                .as_millis(),
517            backtrace,
518            installation_id: installation_id.clone(),
519            session_id: session_id.clone(),
520        };
521
522        if let Some(panic_data_json) = serde_json::to_string_pretty(&panic_data).log_err() {
523            log::error!("{}", panic_data_json);
524        }
525
526        if !is_pty {
527            if let Some(panic_data_json) = serde_json::to_string(&panic_data).log_err() {
528                let timestamp = chrono::Utc::now().format("%Y_%m_%d %H_%M_%S").to_string();
529                let panic_file_path = paths::LOGS_DIR.join(format!("zed-{}.panic", timestamp));
530                let panic_file = std::fs::OpenOptions::new()
531                    .append(true)
532                    .create(true)
533                    .open(&panic_file_path)
534                    .log_err();
535                if let Some(mut panic_file) = panic_file {
536                    writeln!(&mut panic_file, "{}", panic_data_json).log_err();
537                    panic_file.flush().log_err();
538                }
539            }
540        }
541
542        std::process::abort();
543    }));
544}
545
546fn upload_previous_panics(http: Arc<dyn HttpClient>, cx: &mut AppContext) {
547    let telemetry_settings = *client::TelemetrySettings::get_global(cx);
548
549    cx.background_executor()
550        .spawn(async move {
551            let panic_report_url = format!("{}/api/panic", &*client::ZED_SERVER_URL);
552            let mut children = smol::fs::read_dir(&*paths::LOGS_DIR).await?;
553            while let Some(child) = children.next().await {
554                let child = child?;
555                let child_path = child.path();
556
557                if child_path.extension() != Some(OsStr::new("panic")) {
558                    continue;
559                }
560                let filename = if let Some(filename) = child_path.file_name() {
561                    filename.to_string_lossy()
562                } else {
563                    continue;
564                };
565
566                if !filename.starts_with("zed") {
567                    continue;
568                }
569
570                if telemetry_settings.diagnostics {
571                    let panic_file_content = smol::fs::read_to_string(&child_path)
572                        .await
573                        .context("error reading panic file")?;
574
575                    let panic = serde_json::from_str(&panic_file_content)
576                        .ok()
577                        .or_else(|| {
578                            panic_file_content
579                                .lines()
580                                .next()
581                                .and_then(|line| serde_json::from_str(line).ok())
582                        })
583                        .unwrap_or_else(|| {
584                            log::error!(
585                                "failed to deserialize panic file {:?}",
586                                panic_file_content
587                            );
588                            None
589                        });
590
591                    if let Some(panic) = panic {
592                        let body = serde_json::to_string(&PanicRequest {
593                            panic,
594                            token: client::ZED_SECRET_CLIENT_TOKEN.into(),
595                        })
596                        .unwrap();
597
598                        let request = Request::post(&panic_report_url)
599                            .redirect_policy(isahc::config::RedirectPolicy::Follow)
600                            .header("Content-Type", "application/json")
601                            .body(body.into())?;
602                        let response = http.send(request).await.context("error sending panic")?;
603                        if !response.status().is_success() {
604                            log::error!("Error uploading panic to server: {}", response.status());
605                        }
606                    }
607                }
608
609                // We've done what we can, delete the file
610                std::fs::remove_file(child_path)
611                    .context("error removing panic")
612                    .log_err();
613            }
614            Ok::<_, anyhow::Error>(())
615        })
616        .detach_and_log_err(cx);
617}
618
619async fn load_login_shell_environment() -> Result<()> {
620    let marker = "ZED_LOGIN_SHELL_START";
621    let shell = env::var("SHELL").context(
622        "SHELL environment variable is not assigned so we can't source login environment variables",
623    )?;
624    let output = Command::new(&shell)
625        .args(["-lic", &format!("echo {marker} && /usr/bin/env -0")])
626        .output()
627        .await
628        .context("failed to spawn login shell to source login environment variables")?;
629    if !output.status.success() {
630        Err(anyhow!("login shell exited with error"))?;
631    }
632
633    let stdout = String::from_utf8_lossy(&output.stdout);
634
635    if let Some(env_output_start) = stdout.find(marker) {
636        let env_output = &stdout[env_output_start + marker.len()..];
637        for line in env_output.split_terminator('\0') {
638            if let Some(separator_index) = line.find('=') {
639                let key = &line[..separator_index];
640                let value = &line[separator_index + 1..];
641                env::set_var(key, value);
642            }
643        }
644        log::info!(
645            "set environment variables from shell:{}, path:{}",
646            shell,
647            env::var("PATH").unwrap_or_default(),
648        );
649    }
650
651    Ok(())
652}
653
654fn stdout_is_a_pty() -> bool {
655    std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_none() && std::io::stdout().is_terminal()
656}
657
658fn collect_url_args() -> Vec<String> {
659    env::args()
660        .skip(1)
661        .filter_map(|arg| match std::fs::canonicalize(Path::new(&arg)) {
662            Ok(path) => Some(format!("file://{}", path.to_string_lossy())),
663            Err(error) => {
664                if let Some(_) = parse_zed_link(&arg) {
665                    Some(arg)
666                } else {
667                    log::error!("error parsing path argument: {}", error);
668                    None
669                }
670            }
671        })
672        .collect()
673}
674
675fn load_embedded_fonts(cx: &AppContext) {
676    let asset_source = cx.asset_source();
677    let font_paths = asset_source.list("fonts").unwrap();
678    let embedded_fonts = Mutex::new(Vec::new());
679    let executor = cx.background_executor();
680
681    executor.block(executor.scoped(|scope| {
682        for font_path in &font_paths {
683            if !font_path.ends_with(".ttf") {
684                continue;
685            }
686
687            scope.spawn(async {
688                let font_bytes = asset_source.load(font_path).unwrap().to_vec();
689                embedded_fonts.lock().push(Arc::from(font_bytes));
690            });
691        }
692    }));
693
694    cx.text_system()
695        .add_fonts(&embedded_fonts.into_inner())
696        .unwrap();
697}
698
699// #[cfg(debug_assertions)]
700// async fn watch_themes(fs: Arc<dyn Fs>, mut cx: AsyncAppContext) -> Option<()> {
701//     let mut events = fs
702//         .watch("styles/src".as_ref(), Duration::from_millis(100))
703//         .await;
704//     while (events.next().await).is_some() {
705//         let output = Command::new("npm")
706//             .current_dir("styles")
707//             .args(["run", "build"])
708//             .output()
709//             .await
710//             .log_err()?;
711//         if output.status.success() {
712//             cx.update(|cx| theme_selector::reload(cx))
713//         } else {
714//             eprintln!(
715//                 "build script failed {}",
716//                 String::from_utf8_lossy(&output.stderr)
717//             );
718//         }
719//     }
720//     Some(())
721// }
722
723// #[cfg(debug_assertions)]
724// async fn watch_languages(fs: Arc<dyn Fs>, languages: Arc<LanguageRegistry>) -> Option<()> {
725//     let mut events = fs
726//         .watch(
727//             "crates/zed/src/languages".as_ref(),
728//             Duration::from_millis(100),
729//         )
730//         .await;
731//     while (events.next().await).is_some() {
732//         languages.reload();
733//     }
734//     Some(())
735// }
736
737// #[cfg(debug_assertions)]
738// fn watch_file_types(fs: Arc<dyn Fs>, cx: &mut AppContext) {
739//     cx.spawn(|mut cx| async move {
740//         let mut events = fs
741//             .watch(
742//                 "assets/icons/file_icons/file_types.json".as_ref(),
743//                 Duration::from_millis(100),
744//             )
745//             .await;
746//         while (events.next().await).is_some() {
747//             cx.update(|cx| {
748//                 cx.update_global(|file_types, _| {
749//                     *file_types = project_panel::file_associations::FileAssociations::new(Assets);
750//                 });
751//             })
752//         }
753//     })
754//     .detach()
755// }
756
757// #[cfg(not(debug_assertions))]
758// async fn watch_themes(_fs: Arc<dyn Fs>, _cx: AsyncAppContext) -> Option<()> {
759//     None
760// }
761
762// #[cfg(not(debug_assertions))]
763// async fn watch_languages(_: Arc<dyn Fs>, _: Arc<LanguageRegistry>) -> Option<()> {
764//     None
765//
766
767// #[cfg(not(debug_assertions))]
768// fn watch_file_types(_fs: Arc<dyn Fs>, _cx: &mut AppContext) {}
769
770pub fn background_actions() -> &'static [(&'static str, &'static dyn Action)] {
771    // &[
772    //     ("Go to file", &file_finder::Toggle),
773    //     ("Open command palette", &command_palette::Toggle),
774    //     ("Open recent projects", &recent_projects::OpenRecent),
775    //     ("Change your settings", &zed_actions::OpenSettings),
776    // ]
777    // todo!()
778    &[]
779}