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