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