main.rs

  1// Allow binary to be called Zed for a nice application menu when running executable directly
  2#![allow(non_snake_case)]
  3
  4use anyhow::{anyhow, Context, Result};
  5use backtrace::Backtrace;
  6use cli::{
  7    ipc::{self, IpcSender},
  8    CliRequest, CliResponse, IpcHandshake, FORCE_CLI_MODE_ENV_VAR_NAME,
  9};
 10use client::{self, TelemetrySettings, UserStore, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN};
 11use db::kvp::KEY_VALUE_STORE;
 12use editor::{scroll::autoscroll::Autoscroll, Editor};
 13use futures::{
 14    channel::{mpsc, oneshot},
 15    FutureExt, SinkExt, StreamExt,
 16};
 17use gpui::{Action, App, AppContext, AssetSource, AsyncAppContext, Task, ViewContext};
 18use isahc::{config::Configurable, Request};
 19use language::{LanguageRegistry, Point};
 20use log::LevelFilter;
 21use node_runtime::NodeRuntime;
 22use parking_lot::Mutex;
 23use project::Fs;
 24use serde::{Deserialize, Serialize};
 25use settings::{default_settings, handle_settings_file_changes, watch_config_file, SettingsStore};
 26use simplelog::ConfigBuilder;
 27use smol::process::Command;
 28use std::{
 29    collections::HashMap,
 30    env,
 31    ffi::OsStr,
 32    fs::OpenOptions,
 33    io::Write as _,
 34    os::unix::prelude::OsStrExt,
 35    panic,
 36    path::{Path, PathBuf},
 37    str,
 38    sync::{
 39        atomic::{AtomicBool, AtomicU32, Ordering},
 40        Arc, Weak,
 41    },
 42    thread,
 43    time::{Duration, SystemTime, UNIX_EPOCH},
 44};
 45use sum_tree::Bias;
 46use terminal_view::{get_working_directory, TerminalSettings, TerminalView};
 47use util::{
 48    http::{self, HttpClient},
 49    paths::PathLikeWithPosition,
 50};
 51use uuid::Uuid;
 52use welcome::{show_welcome_experience, FIRST_OPEN};
 53
 54use fs::RealFs;
 55#[cfg(debug_assertions)]
 56use staff_mode::StaffMode;
 57use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt};
 58use workspace::{item::ItemHandle, notifications::NotifyResultExt, AppState, Workspace};
 59use zed::{
 60    assets::Assets,
 61    build_window_options, handle_keymap_file_changes, initialize_workspace, languages, menus,
 62    only_instance::{ensure_only_instance, IsOnlyInstance},
 63};
 64
 65fn main() {
 66    let http = http::client();
 67    init_paths();
 68    init_logger();
 69
 70    if ensure_only_instance() != IsOnlyInstance::Yes {
 71        return;
 72    }
 73
 74    log::info!("========== starting zed ==========");
 75    let mut app = gpui::App::new(Assets).unwrap();
 76
 77    let installation_id = app.background().block(installation_id()).ok();
 78    init_panic_hook(&app, installation_id.clone());
 79
 80    load_embedded_fonts(&app);
 81
 82    let fs = Arc::new(RealFs);
 83    let user_settings_file_rx =
 84        watch_config_file(app.background(), fs.clone(), paths::SETTINGS.clone());
 85    let user_keymap_file_rx =
 86        watch_config_file(app.background(), fs.clone(), paths::KEYMAP.clone());
 87
 88    let login_shell_env_loaded = if stdout_is_a_pty() {
 89        Task::ready(())
 90    } else {
 91        app.background().spawn(async {
 92            load_login_shell_environment().await.log_err();
 93        })
 94    };
 95
 96    let (cli_connections_tx, mut cli_connections_rx) = mpsc::unbounded();
 97    let cli_connections_tx = Arc::new(cli_connections_tx);
 98    let (open_paths_tx, mut open_paths_rx) = mpsc::unbounded();
 99    let open_paths_tx = Arc::new(open_paths_tx);
100    let urls_callback_triggered = Arc::new(AtomicBool::new(false));
101
102    let callback_cli_connections_tx = Arc::clone(&cli_connections_tx);
103    let callback_open_paths_tx = Arc::clone(&open_paths_tx);
104    let callback_urls_callback_triggered = Arc::clone(&urls_callback_triggered);
105    app.on_open_urls(move |urls, _| {
106        callback_urls_callback_triggered.store(true, Ordering::Release);
107        open_urls(urls, &callback_cli_connections_tx, &callback_open_paths_tx);
108    })
109    .on_reopen(move |cx| {
110        if cx.has_global::<Weak<AppState>>() {
111            if let Some(app_state) = cx.global::<Weak<AppState>>().upgrade() {
112                workspace::open_new(&app_state, cx, |workspace, cx| {
113                    Editor::new_file(workspace, &Default::default(), cx)
114                })
115                .detach();
116            }
117        }
118    });
119
120    app.run(move |cx| {
121        cx.set_global(*RELEASE_CHANNEL);
122
123        #[cfg(debug_assertions)]
124        cx.set_global(StaffMode(true));
125
126        let mut store = SettingsStore::default();
127        store
128            .set_default_settings(default_settings().as_ref(), cx)
129            .unwrap();
130        cx.set_global(store);
131        handle_settings_file_changes(user_settings_file_rx, cx);
132        handle_keymap_file_changes(user_keymap_file_rx, cx);
133
134        let client = client::Client::new(http.clone(), cx);
135        let mut languages = LanguageRegistry::new(login_shell_env_loaded);
136        languages.set_executor(cx.background().clone());
137        languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone());
138        let languages = Arc::new(languages);
139        let node_runtime = NodeRuntime::instance(http.clone());
140
141        languages::init(languages.clone(), node_runtime.clone());
142        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx));
143
144        cx.set_global(client.clone());
145
146        theme::init(Assets, cx);
147        context_menu::init(cx);
148        project::Project::init(&client, cx);
149        client::init(&client, cx);
150        command_palette::init(cx);
151        language::init(cx);
152        editor::init(cx);
153        go_to_line::init(cx);
154        file_finder::init(cx);
155        outline::init(cx);
156        project_symbols::init(cx);
157        project_panel::init(Assets, cx);
158        diagnostics::init(cx);
159        search::init(cx);
160        semantic_index::init(fs.clone(), http.clone(), languages.clone(), cx);
161        vim::init(cx);
162        terminal_view::init(cx);
163        copilot::init(http.clone(), node_runtime, cx);
164        ai::init(cx);
165
166        cx.spawn(|cx| watch_themes(fs.clone(), cx)).detach();
167        cx.spawn(|_| watch_languages(fs.clone(), languages.clone()))
168            .detach();
169        watch_file_types(fs.clone(), cx);
170
171        languages.set_theme(theme::current(cx).clone());
172        cx.observe_global::<SettingsStore, _>({
173            let languages = languages.clone();
174            move |cx| languages.set_theme(theme::current(cx).clone())
175        })
176        .detach();
177
178        client.telemetry().start(installation_id);
179
180        let app_state = Arc::new(AppState {
181            languages,
182            client: client.clone(),
183            user_store,
184            fs,
185            build_window_options,
186            initialize_workspace,
187            background_actions,
188        });
189        cx.set_global(Arc::downgrade(&app_state));
190
191        audio::init(Assets, cx);
192        auto_update::init(http.clone(), client::ZED_SERVER_URL.clone(), cx);
193
194        workspace::init(app_state.clone(), cx);
195        recent_projects::init(cx);
196
197        journal::init(app_state.clone(), cx);
198        language_selector::init(cx);
199        theme_selector::init(cx);
200        activity_indicator::init(cx);
201        language_tools::init(cx);
202        call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
203        collab_ui::init(&app_state, cx);
204        feedback::init(cx);
205        welcome::init(cx);
206        zed::init(&app_state, cx);
207
208        cx.set_menus(menus::menus());
209
210        if stdout_is_a_pty() {
211            cx.platform().activate(true);
212            let paths = collect_path_args();
213            if paths.is_empty() {
214                cx.spawn(|cx| async move { restore_or_create_workspace(&app_state, cx).await })
215                    .detach()
216            } else {
217                workspace::open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx);
218            }
219        } else {
220            upload_previous_panics(http.clone(), cx);
221
222            // TODO Development mode that forces the CLI mode usually runs Zed binary as is instead
223            // of an *app, hence gets no specific callbacks run. Emulate them here, if needed.
224            if std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_some()
225                && !urls_callback_triggered.load(Ordering::Acquire)
226            {
227                open_urls(collect_url_args(), &cli_connections_tx, &open_paths_tx)
228            }
229
230            if let Ok(Some(connection)) = cli_connections_rx.try_next() {
231                cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx))
232                    .detach();
233            } else if let Ok(Some(paths)) = open_paths_rx.try_next() {
234                cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx))
235                    .detach();
236            } else {
237                cx.spawn({
238                    let app_state = app_state.clone();
239                    |cx| async move { restore_or_create_workspace(&app_state, cx).await }
240                })
241                .detach()
242            }
243
244            cx.spawn(|cx| {
245                let app_state = app_state.clone();
246                async move {
247                    while let Some(connection) = cli_connections_rx.next().await {
248                        handle_cli_connection(connection, app_state.clone(), cx.clone()).await;
249                    }
250                }
251            })
252            .detach();
253
254            cx.spawn(|mut cx| {
255                let app_state = app_state.clone();
256                async move {
257                    while let Some(paths) = open_paths_rx.next().await {
258                        cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx))
259                            .detach();
260                    }
261                }
262            })
263            .detach();
264        }
265
266        cx.spawn(|cx| async move {
267            if stdout_is_a_pty() {
268                if client::IMPERSONATE_LOGIN.is_some() {
269                    client.authenticate_and_connect(false, &cx).await?;
270                }
271            } else if client.has_keychain_credentials(&cx) {
272                client.authenticate_and_connect(true, &cx).await?;
273            }
274            Ok::<_, anyhow::Error>(())
275        })
276        .detach_and_log_err(cx);
277    });
278}
279
280async fn installation_id() -> Result<String> {
281    let legacy_key_name = "device_id";
282
283    if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(legacy_key_name) {
284        Ok(installation_id)
285    } else {
286        let installation_id = Uuid::new_v4().to_string();
287
288        KEY_VALUE_STORE
289            .write_kvp(legacy_key_name.to_string(), installation_id.clone())
290            .await?;
291
292        Ok(installation_id)
293    }
294}
295
296fn open_urls(
297    urls: Vec<String>,
298    cli_connections_tx: &mpsc::UnboundedSender<(
299        mpsc::Receiver<CliRequest>,
300        IpcSender<CliResponse>,
301    )>,
302    open_paths_tx: &mpsc::UnboundedSender<Vec<PathBuf>>,
303) {
304    if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://")) {
305        if let Some(cli_connection) = connect_to_cli(server_name).log_err() {
306            cli_connections_tx
307                .unbounded_send(cli_connection)
308                .map_err(|_| anyhow!("no listener for cli connections"))
309                .log_err();
310        };
311    } else {
312        let paths: Vec<_> = urls
313            .iter()
314            .flat_map(|url| url.strip_prefix("file://"))
315            .map(|url| {
316                let decoded = urlencoding::decode_binary(url.as_bytes());
317                PathBuf::from(OsStr::from_bytes(decoded.as_ref()))
318            })
319            .collect();
320        open_paths_tx
321            .unbounded_send(paths)
322            .map_err(|_| anyhow!("no listener for open urls requests"))
323            .log_err();
324    }
325}
326
327async fn restore_or_create_workspace(app_state: &Arc<AppState>, mut cx: AsyncAppContext) {
328    if let Some(location) = workspace::last_opened_workspace_paths().await {
329        cx.update(|cx| workspace::open_paths(location.paths().as_ref(), app_state, None, cx))
330            .await
331            .log_err();
332    } else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
333        cx.update(|cx| show_welcome_experience(app_state, cx));
334    } else {
335        cx.update(|cx| {
336            workspace::open_new(app_state, cx, |workspace, cx| {
337                Editor::new_file(workspace, &Default::default(), cx)
338            })
339            .detach();
340        });
341    }
342}
343
344fn init_paths() {
345    std::fs::create_dir_all(&*util::paths::CONFIG_DIR).expect("could not create config path");
346    std::fs::create_dir_all(&*util::paths::LANGUAGES_DIR).expect("could not create languages path");
347    std::fs::create_dir_all(&*util::paths::DB_DIR).expect("could not create database path");
348    std::fs::create_dir_all(&*util::paths::LOGS_DIR).expect("could not create logs path");
349}
350
351fn init_logger() {
352    if stdout_is_a_pty() {
353        env_logger::init();
354    } else {
355        let level = LevelFilter::Info;
356
357        // Prevent log file from becoming too large.
358        const KIB: u64 = 1024;
359        const MIB: u64 = 1024 * KIB;
360        const MAX_LOG_BYTES: u64 = MIB;
361        if std::fs::metadata(&*paths::LOG).map_or(false, |metadata| metadata.len() > MAX_LOG_BYTES)
362        {
363            let _ = std::fs::rename(&*paths::LOG, &*paths::OLD_LOG);
364        }
365
366        let log_file = OpenOptions::new()
367            .create(true)
368            .append(true)
369            .open(&*paths::LOG)
370            .expect("could not open logfile");
371
372        let config = ConfigBuilder::new()
373            .set_time_format_str("%Y-%m-%dT%T") //All timestamps are UTC
374            .build();
375
376        simplelog::WriteLogger::init(level, config, log_file).expect("could not initialize logger");
377    }
378}
379
380#[derive(Serialize, Deserialize)]
381struct LocationData {
382    file: String,
383    line: u32,
384}
385
386#[derive(Serialize, Deserialize)]
387struct Panic {
388    thread: String,
389    payload: String,
390    #[serde(skip_serializing_if = "Option::is_none")]
391    location_data: Option<LocationData>,
392    backtrace: Vec<String>,
393    app_version: String,
394    release_channel: String,
395    os_name: String,
396    os_version: Option<String>,
397    architecture: String,
398    panicked_on: u128,
399    #[serde(skip_serializing_if = "Option::is_none")]
400    installation_id: Option<String>,
401}
402
403#[derive(Serialize)]
404struct PanicRequest {
405    panic: Panic,
406    token: String,
407}
408
409static PANIC_COUNT: AtomicU32 = AtomicU32::new(0);
410
411fn init_panic_hook(app: &App, installation_id: Option<String>) {
412    let is_pty = stdout_is_a_pty();
413    let platform = app.platform();
414
415    panic::set_hook(Box::new(move |info| {
416        let prior_panic_count = PANIC_COUNT.fetch_add(1, Ordering::SeqCst);
417        if prior_panic_count > 0 {
418            std::panic::resume_unwind(Box::new(()));
419        }
420
421        let app_version = ZED_APP_VERSION
422            .or_else(|| platform.app_version().ok())
423            .map_or("dev".to_string(), |v| v.to_string());
424
425        let thread = thread::current();
426        let thread = thread.name().unwrap_or("<unnamed>");
427
428        let payload = info.payload();
429        let payload = None
430            .or_else(|| payload.downcast_ref::<&str>().map(|s| s.to_string()))
431            .or_else(|| payload.downcast_ref::<String>().map(|s| s.clone()))
432            .unwrap_or_else(|| "Box<Any>".to_string());
433
434        let backtrace = Backtrace::new();
435        let mut backtrace = backtrace
436            .frames()
437            .iter()
438            .filter_map(|frame| Some(format!("{:#}", frame.symbols().first()?.name()?)))
439            .collect::<Vec<_>>();
440
441        // Strip out leading stack frames for rust panic-handling.
442        if let Some(ix) = backtrace
443            .iter()
444            .position(|name| name == "rust_begin_unwind")
445        {
446            backtrace.drain(0..=ix);
447        }
448
449        let panic_data = Panic {
450            thread: thread.into(),
451            payload: payload.into(),
452            location_data: info.location().map(|location| LocationData {
453                file: location.file().into(),
454                line: location.line(),
455            }),
456            app_version: app_version.clone(),
457            release_channel: RELEASE_CHANNEL.dev_name().into(),
458            os_name: platform.os_name().into(),
459            os_version: platform
460                .os_version()
461                .ok()
462                .map(|os_version| os_version.to_string()),
463            architecture: env::consts::ARCH.into(),
464            panicked_on: SystemTime::now()
465                .duration_since(UNIX_EPOCH)
466                .unwrap()
467                .as_millis(),
468            backtrace,
469            installation_id: installation_id.clone(),
470        };
471
472        if is_pty {
473            if let Some(panic_data_json) = serde_json::to_string_pretty(&panic_data).log_err() {
474                eprintln!("{}", panic_data_json);
475            }
476        } else {
477            if let Some(panic_data_json) = serde_json::to_string(&panic_data).log_err() {
478                let timestamp = chrono::Utc::now().format("%Y_%m_%d %H_%M_%S").to_string();
479                let panic_file_path = paths::LOGS_DIR.join(format!("zed-{}.panic", timestamp));
480                let panic_file = std::fs::OpenOptions::new()
481                    .append(true)
482                    .create(true)
483                    .open(&panic_file_path)
484                    .log_err();
485                if let Some(mut panic_file) = panic_file {
486                    writeln!(&mut panic_file, "{}", panic_data_json).log_err();
487                    panic_file.flush().log_err();
488                }
489            }
490        }
491
492        std::process::abort();
493    }));
494}
495
496fn upload_previous_panics(http: Arc<dyn HttpClient>, cx: &mut AppContext) {
497    let telemetry_settings = *settings::get::<TelemetrySettings>(cx);
498
499    cx.background()
500        .spawn({
501            async move {
502                let panic_report_url = format!("{}/api/panic", &*client::ZED_SERVER_URL);
503                let mut children = smol::fs::read_dir(&*paths::LOGS_DIR).await?;
504                while let Some(child) = children.next().await {
505                    let child = child?;
506                    let child_path = child.path();
507
508                    if child_path.extension() != Some(OsStr::new("panic")) {
509                        continue;
510                    }
511                    let filename = if let Some(filename) = child_path.file_name() {
512                        filename.to_string_lossy()
513                    } else {
514                        continue;
515                    };
516
517                    if !filename.starts_with("zed") {
518                        continue;
519                    }
520
521                    if telemetry_settings.diagnostics {
522                        let panic_file_content = smol::fs::read_to_string(&child_path)
523                            .await
524                            .context("error reading panic file")?;
525
526                        let panic = serde_json::from_str(&panic_file_content)
527                            .ok()
528                            .or_else(|| {
529                                panic_file_content
530                                    .lines()
531                                    .next()
532                                    .and_then(|line| serde_json::from_str(line).ok())
533                            })
534                            .unwrap_or_else(|| {
535                                log::error!(
536                                    "failed to deserialize panic file {:?}",
537                                    panic_file_content
538                                );
539                                None
540                            });
541
542                        if let Some(panic) = panic {
543                            let body = serde_json::to_string(&PanicRequest {
544                                panic,
545                                token: ZED_SECRET_CLIENT_TOKEN.into(),
546                            })
547                            .unwrap();
548
549                            let request = Request::post(&panic_report_url)
550                                .redirect_policy(isahc::config::RedirectPolicy::Follow)
551                                .header("Content-Type", "application/json")
552                                .body(body.into())?;
553                            let response =
554                                http.send(request).await.context("error sending panic")?;
555                            if !response.status().is_success() {
556                                log::error!(
557                                    "Error uploading panic to server: {}",
558                                    response.status()
559                                );
560                            }
561                        }
562                    }
563
564                    // We've done what we can, delete the file
565                    std::fs::remove_file(child_path)
566                        .context("error removing panic")
567                        .log_err();
568                }
569                Ok::<_, anyhow::Error>(())
570            }
571            .log_err()
572        })
573        .detach();
574}
575
576async fn load_login_shell_environment() -> Result<()> {
577    let marker = "ZED_LOGIN_SHELL_START";
578    let shell = env::var("SHELL").context(
579        "SHELL environment variable is not assigned so we can't source login environment variables",
580    )?;
581    let output = Command::new(&shell)
582        .args(["-lic", &format!("echo {marker} && /usr/bin/env -0")])
583        .output()
584        .await
585        .context("failed to spawn login shell to source login environment variables")?;
586    if !output.status.success() {
587        Err(anyhow!("login shell exited with error"))?;
588    }
589
590    let stdout = String::from_utf8_lossy(&output.stdout);
591
592    if let Some(env_output_start) = stdout.find(marker) {
593        let env_output = &stdout[env_output_start + marker.len()..];
594        for line in env_output.split_terminator('\0') {
595            if let Some(separator_index) = line.find('=') {
596                let key = &line[..separator_index];
597                let value = &line[separator_index + 1..];
598                env::set_var(key, value);
599            }
600        }
601        log::info!(
602            "set environment variables from shell:{}, path:{}",
603            shell,
604            env::var("PATH").unwrap_or_default(),
605        );
606    }
607
608    Ok(())
609}
610
611fn stdout_is_a_pty() -> bool {
612    std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_none()
613        && unsafe { libc::isatty(libc::STDOUT_FILENO as i32) != 0 }
614}
615
616fn collect_path_args() -> Vec<PathBuf> {
617    env::args()
618        .skip(1)
619        .filter_map(|arg| match std::fs::canonicalize(arg) {
620            Ok(path) => Some(path),
621            Err(error) => {
622                log::error!("error parsing path argument: {}", error);
623                None
624            }
625        })
626        .collect()
627}
628
629fn collect_url_args() -> Vec<String> {
630    env::args().skip(1).collect()
631}
632
633fn load_embedded_fonts(app: &App) {
634    let font_paths = Assets.list("fonts");
635    let embedded_fonts = Mutex::new(Vec::new());
636    smol::block_on(app.background().scoped(|scope| {
637        for font_path in &font_paths {
638            scope.spawn(async {
639                let font_path = &*font_path;
640                let font_bytes = Assets.load(font_path).unwrap().to_vec();
641                embedded_fonts.lock().push(Arc::from(font_bytes));
642            });
643        }
644    }));
645    app.platform()
646        .fonts()
647        .add_fonts(&embedded_fonts.into_inner())
648        .unwrap();
649}
650
651#[cfg(debug_assertions)]
652async fn watch_themes(fs: Arc<dyn Fs>, mut cx: AsyncAppContext) -> Option<()> {
653    let mut events = fs
654        .watch("styles/src".as_ref(), Duration::from_millis(100))
655        .await;
656    while (events.next().await).is_some() {
657        let output = Command::new("npm")
658            .current_dir("styles")
659            .args(["run", "build"])
660            .output()
661            .await
662            .log_err()?;
663        if output.status.success() {
664            cx.update(|cx| theme_selector::reload(cx))
665        } else {
666            eprintln!(
667                "build script failed {}",
668                String::from_utf8_lossy(&output.stderr)
669            );
670        }
671    }
672    Some(())
673}
674
675#[cfg(debug_assertions)]
676async fn watch_languages(fs: Arc<dyn Fs>, languages: Arc<LanguageRegistry>) -> Option<()> {
677    let mut events = fs
678        .watch(
679            "crates/zed/src/languages".as_ref(),
680            Duration::from_millis(100),
681        )
682        .await;
683    while (events.next().await).is_some() {
684        languages.reload();
685    }
686    Some(())
687}
688
689#[cfg(debug_assertions)]
690fn watch_file_types(fs: Arc<dyn Fs>, cx: &mut AppContext) {
691    cx.spawn(|mut cx| async move {
692        let mut events = fs
693            .watch(
694                "assets/icons/file_icons/file_types.json".as_ref(),
695                Duration::from_millis(100),
696            )
697            .await;
698        while (events.next().await).is_some() {
699            cx.update(|cx| {
700                cx.update_global(|file_types, _| {
701                    *file_types = project_panel::file_associations::FileAssociations::new(Assets);
702                });
703            })
704        }
705    })
706    .detach()
707}
708
709#[cfg(not(debug_assertions))]
710async fn watch_themes(_fs: Arc<dyn Fs>, _cx: AsyncAppContext) -> Option<()> {
711    None
712}
713
714#[cfg(not(debug_assertions))]
715async fn watch_languages(_: Arc<dyn Fs>, _: Arc<LanguageRegistry>) -> Option<()> {
716    None
717}
718
719#[cfg(not(debug_assertions))]
720fn watch_file_types(_fs: Arc<dyn Fs>, _cx: &mut AppContext) {}
721
722fn connect_to_cli(
723    server_name: &str,
724) -> Result<(mpsc::Receiver<CliRequest>, IpcSender<CliResponse>)> {
725    let handshake_tx = cli::ipc::IpcSender::<IpcHandshake>::connect(server_name.to_string())
726        .context("error connecting to cli")?;
727    let (request_tx, request_rx) = ipc::channel::<CliRequest>()?;
728    let (response_tx, response_rx) = ipc::channel::<CliResponse>()?;
729
730    handshake_tx
731        .send(IpcHandshake {
732            requests: request_tx,
733            responses: response_rx,
734        })
735        .context("error sending ipc handshake")?;
736
737    let (mut async_request_tx, async_request_rx) =
738        futures::channel::mpsc::channel::<CliRequest>(16);
739    thread::spawn(move || {
740        while let Ok(cli_request) = request_rx.recv() {
741            if smol::block_on(async_request_tx.send(cli_request)).is_err() {
742                break;
743            }
744        }
745        Ok::<_, anyhow::Error>(())
746    });
747
748    Ok((async_request_rx, response_tx))
749}
750
751async fn handle_cli_connection(
752    (mut requests, responses): (mpsc::Receiver<CliRequest>, IpcSender<CliResponse>),
753    app_state: Arc<AppState>,
754    mut cx: AsyncAppContext,
755) {
756    if let Some(request) = requests.next().await {
757        match request {
758            CliRequest::Open { paths, wait } => {
759                let mut caret_positions = HashMap::new();
760
761                let paths = if paths.is_empty() {
762                    workspace::last_opened_workspace_paths()
763                        .await
764                        .map(|location| location.paths().to_vec())
765                        .unwrap_or_default()
766                } else {
767                    paths
768                        .into_iter()
769                        .filter_map(|path_with_position_string| {
770                            let path_with_position = PathLikeWithPosition::parse_str(
771                                &path_with_position_string,
772                                |path_str| {
773                                    Ok::<_, std::convert::Infallible>(
774                                        Path::new(path_str).to_path_buf(),
775                                    )
776                                },
777                            )
778                            .expect("Infallible");
779                            let path = path_with_position.path_like;
780                            if let Some(row) = path_with_position.row {
781                                if path.is_file() {
782                                    let row = row.saturating_sub(1);
783                                    let col =
784                                        path_with_position.column.unwrap_or(0).saturating_sub(1);
785                                    caret_positions.insert(path.clone(), Point::new(row, col));
786                                }
787                            }
788                            Some(path)
789                        })
790                        .collect()
791                };
792
793                let mut errored = false;
794                match cx
795                    .update(|cx| workspace::open_paths(&paths, &app_state, None, cx))
796                    .await
797                {
798                    Ok((workspace, items)) => {
799                        let mut item_release_futures = Vec::new();
800
801                        for (item, path) in items.into_iter().zip(&paths) {
802                            match item {
803                                Some(Ok(item)) => {
804                                    if let Some(point) = caret_positions.remove(path) {
805                                        if let Some(active_editor) = item.downcast::<Editor>() {
806                                            active_editor
807                                                .downgrade()
808                                                .update(&mut cx, |editor, cx| {
809                                                    let snapshot =
810                                                        editor.snapshot(cx).display_snapshot;
811                                                    let point = snapshot
812                                                        .buffer_snapshot
813                                                        .clip_point(point, Bias::Left);
814                                                    editor.change_selections(
815                                                        Some(Autoscroll::center()),
816                                                        cx,
817                                                        |s| s.select_ranges([point..point]),
818                                                    );
819                                                })
820                                                .log_err();
821                                        }
822                                    }
823
824                                    let released = oneshot::channel();
825                                    cx.update(|cx| {
826                                        item.on_release(
827                                            cx,
828                                            Box::new(move |_| {
829                                                let _ = released.0.send(());
830                                            }),
831                                        )
832                                        .detach();
833                                    });
834                                    item_release_futures.push(released.1);
835                                }
836                                Some(Err(err)) => {
837                                    responses
838                                        .send(CliResponse::Stderr {
839                                            message: format!("error opening {:?}: {}", path, err),
840                                        })
841                                        .log_err();
842                                    errored = true;
843                                }
844                                None => {}
845                            }
846                        }
847
848                        if wait {
849                            let background = cx.background();
850                            let wait = async move {
851                                if paths.is_empty() {
852                                    let (done_tx, done_rx) = oneshot::channel();
853                                    if let Some(workspace) = workspace.upgrade(&cx) {
854                                        let _subscription = cx.update(|cx| {
855                                            cx.observe_release(&workspace, move |_, _| {
856                                                let _ = done_tx.send(());
857                                            })
858                                        });
859                                        drop(workspace);
860                                        let _ = done_rx.await;
861                                    }
862                                } else {
863                                    let _ =
864                                        futures::future::try_join_all(item_release_futures).await;
865                                };
866                            }
867                            .fuse();
868                            futures::pin_mut!(wait);
869
870                            loop {
871                                // Repeatedly check if CLI is still open to avoid wasting resources
872                                // waiting for files or workspaces to close.
873                                let mut timer = background.timer(Duration::from_secs(1)).fuse();
874                                futures::select_biased! {
875                                    _ = wait => break,
876                                    _ = timer => {
877                                        if responses.send(CliResponse::Ping).is_err() {
878                                            break;
879                                        }
880                                    }
881                                }
882                            }
883                        }
884                    }
885                    Err(error) => {
886                        errored = true;
887                        responses
888                            .send(CliResponse::Stderr {
889                                message: format!("error opening {:?}: {}", paths, error),
890                            })
891                            .log_err();
892                    }
893                }
894
895                responses
896                    .send(CliResponse::Exit {
897                        status: i32::from(errored),
898                    })
899                    .log_err();
900            }
901        }
902    }
903}
904
905pub fn dock_default_item_factory(
906    workspace: &mut Workspace,
907    cx: &mut ViewContext<Workspace>,
908) -> Option<Box<dyn ItemHandle>> {
909    let strategy = settings::get::<TerminalSettings>(cx)
910        .working_directory
911        .clone();
912    let working_directory = get_working_directory(workspace, cx, strategy);
913
914    let window_id = cx.window_id();
915    let terminal = workspace
916        .project()
917        .update(cx, |project, cx| {
918            project.create_terminal(working_directory, window_id, cx)
919        })
920        .notify_err(workspace, cx)?;
921
922    let terminal_view = cx.add_view(|cx| {
923        TerminalView::new(
924            terminal,
925            workspace.weak_handle(),
926            workspace.database_id(),
927            cx,
928        )
929    });
930
931    Some(Box::new(terminal_view))
932}
933
934pub fn background_actions() -> &'static [(&'static str, &'static dyn Action)] {
935    &[
936        ("Go to file", &file_finder::Toggle),
937        ("Open command palette", &command_palette::Toggle),
938        ("Open recent projects", &recent_projects::OpenRecent),
939        ("Change your settings", &zed_actions::OpenSettings),
940    ]
941}