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}