zed2.rs

   1#![allow(unused_variables, unused_mut)]
   2//todo!()
   3
   4mod app_menus;
   5mod assets;
   6pub mod languages;
   7mod only_instance;
   8mod open_listener;
   9
  10pub use app_menus::*;
  11pub use assets::*;
  12use assistant::AssistantPanel;
  13use breadcrumbs::Breadcrumbs;
  14use collections::VecDeque;
  15use editor::{Editor, MultiBuffer};
  16use gpui::{
  17    actions, point, px, AppContext, Context, FocusableView, PromptLevel, TitlebarOptions, View,
  18    ViewContext, VisualContext, WindowBounds, WindowKind, WindowOptions,
  19};
  20pub use only_instance::*;
  21pub use open_listener::*;
  22
  23use anyhow::{anyhow, Context as _};
  24use futures::{channel::mpsc, StreamExt};
  25use project_panel::ProjectPanel;
  26use quick_action_bar::QuickActionBar;
  27use settings::{initial_local_settings_content, load_default_keymap, KeymapFile, Settings};
  28use std::{borrow::Cow, ops::Deref, sync::Arc};
  29use terminal_view::terminal_panel::TerminalPanel;
  30use util::{
  31    asset_str,
  32    channel::{AppCommitSha, ReleaseChannel},
  33    paths::{self, LOCAL_SETTINGS_RELATIVE_PATH},
  34    ResultExt,
  35};
  36use uuid::Uuid;
  37use workspace::Pane;
  38use workspace::{
  39    create_and_open_local_file, dock::PanelHandle,
  40    notifications::simple_message_notification::MessageNotification, open_new, AppState, NewFile,
  41    NewWindow, Workspace, WorkspaceSettings,
  42};
  43use zed_actions::{OpenBrowser, OpenZedURL};
  44
  45actions!(
  46    About,
  47    DebugElements,
  48    DecreaseBufferFontSize,
  49    Hide,
  50    HideOthers,
  51    IncreaseBufferFontSize,
  52    Minimize,
  53    OpenDefaultKeymap,
  54    OpenDefaultSettings,
  55    OpenKeymap,
  56    OpenLicenses,
  57    OpenLocalSettings,
  58    OpenLog,
  59    OpenSettings,
  60    OpenTelemetryLog,
  61    Quit,
  62    ResetBufferFontSize,
  63    ResetDatabase,
  64    ShowAll,
  65    ToggleFullScreen,
  66    Zoom,
  67);
  68
  69pub fn build_window_options(
  70    bounds: Option<WindowBounds>,
  71    display_uuid: Option<Uuid>,
  72    cx: &mut AppContext,
  73) -> WindowOptions {
  74    let bounds = bounds.unwrap_or(WindowBounds::Maximized);
  75    let display = display_uuid.and_then(|uuid| {
  76        cx.displays()
  77            .into_iter()
  78            .find(|display| display.uuid().ok() == Some(uuid))
  79    });
  80
  81    WindowOptions {
  82        bounds,
  83        titlebar: Some(TitlebarOptions {
  84            title: None,
  85            appears_transparent: true,
  86            traffic_light_position: Some(point(px(8.), px(8.))),
  87        }),
  88        center: false,
  89        focus: false,
  90        show: false,
  91        kind: WindowKind::Normal,
  92        is_movable: true,
  93        display_id: display.map(|display| display.id()),
  94    }
  95}
  96
  97pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
  98    cx.observe_new_views(move |workspace: &mut Workspace, cx| {
  99        let workspace_handle = cx.view().clone();
 100        let center_pane = workspace.active_pane().clone();
 101        initialize_pane(workspace, &center_pane, cx);
 102        cx.subscribe(&workspace_handle, {
 103            move |workspace, _, event, cx| {
 104                if let workspace::Event::PaneAdded(pane) = event {
 105                    initialize_pane(workspace, pane, cx);
 106                }
 107            }
 108        })
 109        .detach();
 110
 111        //     cx.emit(workspace2::Event::PaneAdded(
 112        //         workspace.active_pane().clone(),
 113        //     ));
 114
 115        //     let collab_titlebar_item =
 116        //         cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx));
 117        //     workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx);
 118
 119        let copilot =
 120            cx.build_view(|cx| copilot_button::CopilotButton::new(app_state.fs.clone(), cx));
 121        let diagnostic_summary =
 122            cx.build_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx));
 123        let activity_indicator =
 124            activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx);
 125        let active_buffer_language =
 126            cx.build_view(|_| language_selector::ActiveBufferLanguage::new(workspace));
 127        //     let vim_mode_indicator = cx.add_view(|cx| vim::ModeIndicator::new(cx));
 128        //     let feedback_button = cx.add_view(|_| {
 129        //         feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace)
 130        //     });
 131        let cursor_position = cx.build_view(|_| editor::items::CursorPosition::new());
 132        workspace.status_bar().update(cx, |status_bar, cx| {
 133            status_bar.add_left_item(diagnostic_summary, cx);
 134            status_bar.add_left_item(activity_indicator, cx);
 135
 136            // status_bar.add_right_item(feedback_button, cx);
 137            status_bar.add_right_item(copilot, cx);
 138            status_bar.add_right_item(active_buffer_language, cx);
 139            // status_bar.add_right_item(vim_mode_indicator, cx);
 140            status_bar.add_right_item(cursor_position, cx);
 141        });
 142
 143        auto_update::notify_of_any_new_update(cx);
 144
 145        //     vim::observe_keystrokes(cx);
 146
 147        let handle = cx.view().downgrade();
 148        cx.on_window_should_close(move |cx| {
 149            handle
 150                .update(cx, |workspace, cx| {
 151                    workspace.close_window(&Default::default(), cx);
 152                    false
 153                })
 154                .unwrap_or(true)
 155        });
 156
 157        cx.spawn(|workspace_handle, mut cx| async move {
 158            let project_panel = ProjectPanel::load(workspace_handle.clone(), cx.clone());
 159            let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone());
 160            let assistant_panel = AssistantPanel::load(workspace_handle.clone(), cx.clone());
 161            let channels_panel =
 162                collab_ui::collab_panel::CollabPanel::load(workspace_handle.clone(), cx.clone());
 163            // let chat_panel =
 164            //     collab_ui::chat_panel::ChatPanel::load(workspace_handle.clone(), cx.clone());
 165            // let notification_panel = collab_ui::notification_panel::NotificationPanel::load(
 166            //     workspace_handle.clone(),
 167            //     cx.clone(),
 168            // );
 169            let (
 170                project_panel,
 171                terminal_panel,
 172                assistant_panel,
 173                channels_panel,
 174                //     chat_panel,
 175                //     notification_panel,
 176            ) = futures::try_join!(
 177                project_panel,
 178                terminal_panel,
 179                assistant_panel,
 180                channels_panel,
 181                //     chat_panel,
 182                //     notification_panel,
 183            )?;
 184
 185            workspace_handle.update(&mut cx, |workspace, cx| {
 186                let project_panel_position = project_panel.position(cx);
 187                workspace.add_panel(project_panel, cx);
 188                workspace.add_panel(terminal_panel, cx);
 189                workspace.add_panel(assistant_panel, cx);
 190                workspace.add_panel(channels_panel, cx);
 191                //     workspace.add_panel(chat_panel, cx);
 192                //     workspace.add_panel(notification_panel, cx);
 193
 194                // if !was_deserialized
 195                //     && workspace
 196                //         .project()
 197                //         .read(cx)
 198                //         .visible_worktrees(cx)
 199                //         .any(|tree| {
 200                //             tree.read(cx)
 201                //                 .root_entry()
 202                //                 .map_or(false, |entry| entry.is_dir())
 203                //         })
 204                // {
 205                //     workspace.toggle_dock(project_panel_position, cx);
 206                // }
 207                cx.focus_self();
 208            })
 209        })
 210        .detach();
 211
 212        workspace
 213            .register_action(about)
 214            .register_action(|_, _: &Hide, cx| {
 215                cx.hide();
 216            })
 217            .register_action(|_, _: &HideOthers, cx| {
 218                cx.hide_other_apps();
 219            })
 220            .register_action(|_, _: &ShowAll, cx| {
 221                cx.unhide_other_apps();
 222            })
 223            .register_action(|_, _: &Minimize, cx| {
 224                cx.minimize_window();
 225            })
 226            .register_action(|_, _: &Zoom, cx| {
 227                cx.zoom_window();
 228            })
 229            .register_action(|_, _: &ToggleFullScreen, cx| {
 230                cx.toggle_full_screen();
 231            })
 232            .register_action(quit)
 233            .register_action(|_, action: &OpenZedURL, cx| {
 234                cx.global::<Arc<OpenListener>>()
 235                    .open_urls(&[action.url.clone()])
 236            })
 237            .register_action(|_, action: &OpenBrowser, cx| cx.open_url(&action.url))
 238            //todo!(buffer font size)
 239            // cx.add_global_action(move |_: &IncreaseBufferFontSize, cx| {
 240            //     theme::adjust_font_size(cx, |size| *size += 1.0)
 241            // });
 242            // cx.add_global_action(move |_: &DecreaseBufferFontSize, cx| {
 243            //     theme::adjust_font_size(cx, |size| *size -= 1.0)
 244            // });
 245            // cx.add_global_action(move |_: &ResetBufferFontSize, cx| theme::reset_font_size(cx));
 246            .register_action(|_, _: &install_cli::Install, cx| {
 247                cx.spawn(|_, cx| async move {
 248                    install_cli::install_cli(cx.deref())
 249                        .await
 250                        .context("error creating CLI symlink")
 251                })
 252                .detach_and_log_err(cx);
 253            })
 254            .register_action(|workspace, _: &OpenLog, cx| {
 255                open_log_file(workspace, cx);
 256            })
 257            .register_action(|workspace, _: &OpenLicenses, cx| {
 258                open_bundled_file(
 259                    workspace,
 260                    asset_str::<Assets>("licenses.md"),
 261                    "Open Source License Attribution",
 262                    "Markdown",
 263                    cx,
 264                );
 265            })
 266            .register_action(
 267                move |workspace: &mut Workspace,
 268                      _: &OpenTelemetryLog,
 269                      cx: &mut ViewContext<Workspace>| {
 270                    open_telemetry_log_file(workspace, cx);
 271                },
 272            )
 273            .register_action(
 274                move |_: &mut Workspace, _: &OpenKeymap, cx: &mut ViewContext<Workspace>| {
 275                    create_and_open_local_file(&paths::KEYMAP, cx, Default::default)
 276                        .detach_and_log_err(cx);
 277                },
 278            )
 279            .register_action(
 280                move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext<Workspace>| {
 281                    create_and_open_local_file(&paths::SETTINGS, cx, || {
 282                        settings::initial_user_settings_content().as_ref().into()
 283                    })
 284                    .detach_and_log_err(cx);
 285                },
 286            )
 287            .register_action(open_local_settings_file)
 288            .register_action(
 289                move |workspace: &mut Workspace,
 290                      _: &OpenDefaultKeymap,
 291                      cx: &mut ViewContext<Workspace>| {
 292                    open_bundled_file(
 293                        workspace,
 294                        settings::default_keymap(),
 295                        "Default Key Bindings",
 296                        "JSON",
 297                        cx,
 298                    );
 299                },
 300            )
 301            .register_action(
 302                move |workspace: &mut Workspace,
 303                      _: &OpenDefaultSettings,
 304                      cx: &mut ViewContext<Workspace>| {
 305                    open_bundled_file(
 306                        workspace,
 307                        settings::default_settings(),
 308                        "Default Settings",
 309                        "JSON",
 310                        cx,
 311                    );
 312                },
 313            )
 314            //todo!()
 315            // cx.add_action({
 316            //     move |workspace: &mut Workspace, _: &DebugElements, cx: &mut ViewContext<Workspace>| {
 317            //         let app_state = workspace.app_state().clone();
 318            //         let markdown = app_state.languages.language_for_name("JSON");
 319            //         let window = cx.window();
 320            //         cx.spawn(|workspace, mut cx| async move {
 321            //             let markdown = markdown.await.log_err();
 322            //             let content = to_string_pretty(&window.debug_elements(&cx).ok_or_else(|| {
 323            //                 anyhow!("could not debug elements for window {}", window.id())
 324            //             })?)
 325            //             .unwrap();
 326            //             workspace
 327            //                 .update(&mut cx, |workspace, cx| {
 328            //                     workspace.with_local_workspace(cx, move |workspace, cx| {
 329            //                         let project = workspace.project().clone();
 330            //                         let buffer = project
 331            //                             .update(cx, |project, cx| {
 332            //                                 project.create_buffer(&content, markdown, cx)
 333            //                             })
 334            //                             .expect("creating buffers on a local workspace always succeeds");
 335            //                         let buffer = cx.add_model(|cx| {
 336            //                             MultiBuffer::singleton(buffer, cx)
 337            //                                 .with_title("Debug Elements".into())
 338            //                         });
 339            //                         workspace.add_item(
 340            //                             Box::new(cx.add_view(|cx| {
 341            //                                 Editor::for_multibuffer(buffer, Some(project.clone()), cx)
 342            //                             })),
 343            //                             cx,
 344            //                         );
 345            //                     })
 346            //                 })?
 347            //                 .await
 348            //         })
 349            //         .detach_and_log_err(cx);
 350            //     }
 351            // });
 352            // .register_action(
 353            //     |workspace: &mut Workspace,
 354            //      _: &project_panel::ToggleFocus,
 355            //      cx: &mut ViewContext<Workspace>| {
 356            //         workspace.toggle_panel_focus::<ProjectPanel>(cx);
 357            //     },
 358            // );
 359            // cx.add_action(
 360            //     |workspace: &mut Workspace,
 361            //      _: &collab_ui::collab_panel::ToggleFocus,
 362            //      cx: &mut ViewContext<Workspace>| {
 363            //         workspace.toggle_panel_focus::<collab_ui::collab_panel::CollabPanel>(cx);
 364            //     },
 365            // );
 366            // cx.add_action(
 367            //     |workspace: &mut Workspace,
 368            //      _: &collab_ui::chat_panel::ToggleFocus,
 369            //      cx: &mut ViewContext<Workspace>| {
 370            //         workspace.toggle_panel_focus::<collab_ui::chat_panel::ChatPanel>(cx);
 371            //     },
 372            // );
 373            // cx.add_action(
 374            //     |workspace: &mut Workspace,
 375            //      _: &collab_ui::notification_panel::ToggleFocus,
 376            //      cx: &mut ViewContext<Workspace>| {
 377            //         workspace.toggle_panel_focus::<collab_ui::notification_panel::NotificationPanel>(cx);
 378            //     },
 379            // );
 380            // cx.add_action(
 381            //     |workspace: &mut Workspace,
 382            //      _: &terminal_panel::ToggleFocus,
 383            //      cx: &mut ViewContext<Workspace>| {
 384            //         workspace.toggle_panel_focus::<TerminalPanel>(cx);
 385            //     },
 386            // );
 387            .register_action({
 388                let app_state = Arc::downgrade(&app_state);
 389                move |_, _: &NewWindow, cx| {
 390                    if let Some(app_state) = app_state.upgrade() {
 391                        open_new(&app_state, cx, |workspace, cx| {
 392                            Editor::new_file(workspace, &Default::default(), cx)
 393                        })
 394                        .detach();
 395                    }
 396                }
 397            })
 398            .register_action({
 399                let app_state = Arc::downgrade(&app_state);
 400                move |_, _: &NewFile, cx| {
 401                    if let Some(app_state) = app_state.upgrade() {
 402                        open_new(&app_state, cx, |workspace, cx| {
 403                            Editor::new_file(workspace, &Default::default(), cx)
 404                        })
 405                        .detach();
 406                    }
 407                }
 408            });
 409
 410        workspace.focus_handle(cx).focus(cx);
 411        //todo!()
 412        // load_default_keymap(cx);
 413    })
 414    .detach();
 415}
 416
 417fn initialize_pane(workspace: &mut Workspace, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
 418    pane.update(cx, |pane, cx| {
 419        pane.toolbar().update(cx, |toolbar, cx| {
 420            let breadcrumbs = cx.build_view(|_| Breadcrumbs::new(workspace));
 421            toolbar.add_item(breadcrumbs, cx);
 422            let buffer_search_bar = cx.build_view(search::BufferSearchBar::new);
 423            toolbar.add_item(buffer_search_bar.clone(), cx);
 424
 425            let quick_action_bar =
 426                cx.build_view(|_| QuickActionBar::new(buffer_search_bar, workspace));
 427            toolbar.add_item(quick_action_bar, cx);
 428            let diagnostic_editor_controls = cx.build_view(|_| diagnostics::ToolbarControls::new());
 429            //     toolbar.add_item(diagnostic_editor_controls, cx);
 430            //     let project_search_bar = cx.add_view(|_| ProjectSearchBar::new());
 431            //     toolbar.add_item(project_search_bar, cx);
 432            //     let submit_feedback_button =
 433            //         cx.add_view(|_| SubmitFeedbackButton::new());
 434            //     toolbar.add_item(submit_feedback_button, cx);
 435            //     let feedback_info_text = cx.add_view(|_| FeedbackInfoText::new());
 436            //     toolbar.add_item(feedback_info_text, cx);
 437            //     let lsp_log_item =
 438            //         cx.add_view(|_| language_tools::LspLogToolbarItemView::new());
 439            //     toolbar.add_item(lsp_log_item, cx);
 440            //     let syntax_tree_item = cx
 441            //         .add_view(|_| language_tools::SyntaxTreeToolbarItemView::new());
 442            //     toolbar.add_item(syntax_tree_item, cx);
 443        })
 444    });
 445}
 446
 447fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext<Workspace>) {
 448    use std::fmt::Write as _;
 449
 450    let app_name = cx.global::<ReleaseChannel>().display_name();
 451    let version = env!("CARGO_PKG_VERSION");
 452    let mut message = format!("{app_name} {version}");
 453    if let Some(sha) = cx.try_global::<AppCommitSha>() {
 454        write!(&mut message, "\n\n{}", sha.0).unwrap();
 455    }
 456
 457    let prompt = cx.prompt(PromptLevel::Info, &message, &["OK"]);
 458    cx.foreground_executor()
 459        .spawn(async {
 460            prompt.await.ok();
 461        })
 462        .detach();
 463}
 464
 465fn quit(_: &mut Workspace, _: &Quit, cx: &mut gpui::ViewContext<Workspace>) {
 466    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
 467    cx.spawn(|_, mut cx| async move {
 468        let mut workspace_windows = cx.update(|_, cx| {
 469            cx.windows()
 470                .into_iter()
 471                .filter_map(|window| window.downcast::<Workspace>())
 472                .collect::<Vec<_>>()
 473        })?;
 474
 475        // If multiple windows have unsaved changes, and need a save prompt,
 476        // prompt in the active window before switching to a different window.
 477        cx.update(|_, cx| {
 478            workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
 479        })
 480        .log_err();
 481
 482        if let (true, Some(window)) = (should_confirm, workspace_windows.first().copied()) {
 483            let answer = cx
 484                .update(|_, cx| {
 485                    cx.prompt(
 486                        PromptLevel::Info,
 487                        "Are you sure you want to quit?",
 488                        &["Quit", "Cancel"],
 489                    )
 490                })
 491                .log_err();
 492
 493            if let Some(mut answer) = answer {
 494                let answer = answer.await.ok();
 495                if answer != Some(0) {
 496                    return Ok(());
 497                }
 498            }
 499        }
 500
 501        // If the user cancels any save prompt, then keep the app open.
 502        for window in workspace_windows {
 503            if let Some(should_close) = window
 504                .update(&mut cx, |workspace, cx| {
 505                    workspace.prepare_to_close(true, cx)
 506                })
 507                .log_err()
 508            {
 509                if !should_close.await? {
 510                    return Ok(());
 511                }
 512            }
 513        }
 514        cx.update(|_, cx| {
 515            cx.quit();
 516        })?;
 517        anyhow::Ok(())
 518    })
 519    .detach_and_log_err(cx);
 520}
 521
 522fn open_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
 523    const MAX_LINES: usize = 1000;
 524    workspace
 525        .with_local_workspace(cx, move |workspace, cx| {
 526            let fs = workspace.app_state().fs.clone();
 527            cx.spawn(|workspace, mut cx| async move {
 528                let (old_log, new_log) =
 529                    futures::join!(fs.load(&paths::OLD_LOG), fs.load(&paths::LOG));
 530
 531                let mut lines = VecDeque::with_capacity(MAX_LINES);
 532                for line in old_log
 533                    .iter()
 534                    .flat_map(|log| log.lines())
 535                    .chain(new_log.iter().flat_map(|log| log.lines()))
 536                {
 537                    if lines.len() == MAX_LINES {
 538                        lines.pop_front();
 539                    }
 540                    lines.push_back(line);
 541                }
 542                let log = lines
 543                    .into_iter()
 544                    .flat_map(|line| [line, "\n"])
 545                    .collect::<String>();
 546
 547                workspace
 548                    .update(&mut cx, |workspace, cx| {
 549                        let project = workspace.project().clone();
 550                        let buffer = project
 551                            .update(cx, |project, cx| project.create_buffer("", None, cx))
 552                            .expect("creating buffers on a local workspace always succeeds");
 553                        buffer.update(cx, |buffer, cx| buffer.edit([(0..0, log)], None, cx));
 554
 555                        let buffer = cx.build_model(|cx| {
 556                            MultiBuffer::singleton(buffer, cx).with_title("Log".into())
 557                        });
 558                        workspace.add_item(
 559                            Box::new(cx.build_view(|cx| {
 560                                Editor::for_multibuffer(buffer, Some(project), cx)
 561                            })),
 562                            cx,
 563                        );
 564                    })
 565                    .log_err();
 566            })
 567            .detach();
 568        })
 569        .detach();
 570}
 571
 572pub fn handle_keymap_file_changes(
 573    mut user_keymap_file_rx: mpsc::UnboundedReceiver<String>,
 574    cx: &mut AppContext,
 575) {
 576    cx.spawn(move |cx| async move {
 577        //  let mut settings_subscription = None;
 578        while let Some(user_keymap_content) = user_keymap_file_rx.next().await {
 579            if let Some(keymap_content) = KeymapFile::parse(&user_keymap_content).log_err() {
 580                cx.update(|cx| reload_keymaps(cx, &keymap_content)).ok();
 581
 582                // todo!()
 583                // let mut old_base_keymap = cx.read(|cx| *settings::get::<BaseKeymap>(cx));
 584                // drop(settings_subscription);
 585                // settings_subscription = Some(cx.update(|cx| {
 586                //     cx.observe_global::<SettingsStore, _>(move |cx| {
 587                //         let new_base_keymap = *settings::get::<BaseKeymap>(cx);
 588                //         if new_base_keymap != old_base_keymap {
 589                //             old_base_keymap = new_base_keymap.clone();
 590                //             reload_keymaps(cx, &keymap_content);
 591                //         }
 592                //     })
 593                // }));
 594            }
 595        }
 596    })
 597    .detach();
 598}
 599
 600fn reload_keymaps(cx: &mut AppContext, keymap_content: &KeymapFile) {
 601    // todo!()
 602    // cx.clear_bindings();
 603    load_default_keymap(cx);
 604    keymap_content.clone().add_to_cx(cx).log_err();
 605    cx.set_menus(app_menus());
 606}
 607
 608fn open_local_settings_file(
 609    workspace: &mut Workspace,
 610    _: &OpenLocalSettings,
 611    cx: &mut ViewContext<Workspace>,
 612) {
 613    let project = workspace.project().clone();
 614    let worktree = project
 615        .read(cx)
 616        .visible_worktrees(cx)
 617        .find_map(|tree| tree.read(cx).root_entry()?.is_dir().then_some(tree));
 618    if let Some(worktree) = worktree {
 619        let tree_id = worktree.read(cx).id();
 620        cx.spawn(|workspace, mut cx| async move {
 621            let file_path = &*LOCAL_SETTINGS_RELATIVE_PATH;
 622
 623            if let Some(dir_path) = file_path.parent() {
 624                if worktree.update(&mut cx, |tree, _| tree.entry_for_path(dir_path).is_none())? {
 625                    project
 626                        .update(&mut cx, |project, cx| {
 627                            project.create_entry((tree_id, dir_path), true, cx)
 628                        })?
 629                        .await
 630                        .context("worktree was removed")?;
 631                }
 632            }
 633
 634            if worktree.update(&mut cx, |tree, _| tree.entry_for_path(file_path).is_none())? {
 635                project
 636                    .update(&mut cx, |project, cx| {
 637                        project.create_entry((tree_id, file_path), false, cx)
 638                    })?
 639                    .await
 640                    .context("worktree was removed")?;
 641            }
 642
 643            let editor = workspace
 644                .update(&mut cx, |workspace, cx| {
 645                    workspace.open_path((tree_id, file_path), None, true, cx)
 646                })?
 647                .await?
 648                .downcast::<Editor>()
 649                .ok_or_else(|| anyhow!("unexpected item type"))?;
 650
 651            editor
 652                .downgrade()
 653                .update(&mut cx, |editor, cx| {
 654                    if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
 655                        if buffer.read(cx).is_empty() {
 656                            buffer.update(cx, |buffer, cx| {
 657                                buffer.edit([(0..0, initial_local_settings_content())], None, cx)
 658                            });
 659                        }
 660                    }
 661                })
 662                .ok();
 663
 664            anyhow::Ok(())
 665        })
 666        .detach();
 667    } else {
 668        workspace.show_notification(0, cx, |cx| {
 669            cx.build_view(|_| MessageNotification::new("This project has no folders open."))
 670        })
 671    }
 672}
 673
 674fn open_telemetry_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
 675    workspace.with_local_workspace(cx, move |workspace, cx| {
 676        let app_state = workspace.app_state().clone();
 677        cx.spawn(|workspace, mut cx| async move {
 678            async fn fetch_log_string(app_state: &Arc<AppState>) -> Option<String> {
 679                let path = app_state.client.telemetry().log_file_path()?;
 680                app_state.fs.load(&path).await.log_err()
 681            }
 682
 683            let log = fetch_log_string(&app_state).await.unwrap_or_else(|| "// No data has been collected yet".to_string());
 684
 685            const MAX_TELEMETRY_LOG_LEN: usize = 5 * 1024 * 1024;
 686            let mut start_offset = log.len().saturating_sub(MAX_TELEMETRY_LOG_LEN);
 687            if let Some(newline_offset) = log[start_offset..].find('\n') {
 688                start_offset += newline_offset + 1;
 689            }
 690            let log_suffix = &log[start_offset..];
 691            let json = app_state.languages.language_for_name("JSON").await.log_err();
 692
 693            workspace.update(&mut cx, |workspace, cx| {
 694                let project = workspace.project().clone();
 695                let buffer = project
 696                    .update(cx, |project, cx| project.create_buffer("", None, cx))
 697                    .expect("creating buffers on a local workspace always succeeds");
 698                buffer.update(cx, |buffer, cx| {
 699                    buffer.set_language(json, cx);
 700                    buffer.edit(
 701                        [(
 702                            0..0,
 703                            concat!(
 704                                "// Zed collects anonymous usage data to help us understand how people are using the app.\n",
 705                                "// Telemetry can be disabled via the `settings.json` file.\n",
 706                                "// Here is the data that has been reported for the current session:\n",
 707                                "\n"
 708                            ),
 709                        )],
 710                        None,
 711                        cx,
 712                    );
 713                    buffer.edit([(buffer.len()..buffer.len(), log_suffix)], None, cx);
 714                });
 715
 716                let buffer = cx.build_model(|cx| {
 717                    MultiBuffer::singleton(buffer, cx).with_title("Telemetry Log".into())
 718                });
 719                workspace.add_item(
 720                    Box::new(cx.build_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx))),
 721                    cx,
 722                );
 723            }).log_err()?;
 724
 725            Some(())
 726        })
 727        .detach();
 728    }).detach();
 729}
 730
 731fn open_bundled_file(
 732    workspace: &mut Workspace,
 733    text: Cow<'static, str>,
 734    title: &'static str,
 735    language: &'static str,
 736    cx: &mut ViewContext<Workspace>,
 737) {
 738    let language = workspace.app_state().languages.language_for_name(language);
 739    cx.spawn(|workspace, mut cx| async move {
 740        let language = language.await.log_err();
 741        workspace
 742            .update(&mut cx, |workspace, cx| {
 743                workspace.with_local_workspace(cx, |workspace, cx| {
 744                    let project = workspace.project();
 745                    let buffer = project.update(cx, move |project, cx| {
 746                        project
 747                            .create_buffer(text.as_ref(), language, cx)
 748                            .expect("creating buffers on a local workspace always succeeds")
 749                    });
 750                    let buffer = cx.build_model(|cx| {
 751                        MultiBuffer::singleton(buffer, cx).with_title(title.into())
 752                    });
 753                    workspace.add_item(
 754                        Box::new(cx.build_view(|cx| {
 755                            Editor::for_multibuffer(buffer, Some(project.clone()), cx)
 756                        })),
 757                        cx,
 758                    );
 759                })
 760            })?
 761            .await
 762    })
 763    .detach_and_log_err(cx);
 764}
 765
 766// todo!()
 767// #[cfg(test)]
 768// mod tests {
 769//     use super::*;
 770//     use assets::Assets;
 771//     use editor::{scroll::autoscroll::Autoscroll, DisplayPoint, Editor};
 772//     use fs::{FakeFs, Fs};
 773//     use gpui::{
 774//         actions, elements::Empty, executor::Deterministic, Action, AnyElement, AnyWindowHandle,
 775//         AppContext, AssetSource, Element, Entity, TestAppContext, View, ViewHandle,
 776//     };
 777//     use language::LanguageRegistry;
 778//     use project::{project_settings::ProjectSettings, Project, ProjectPath};
 779//     use serde_json::json;
 780//     use settings::{handle_settings_file_changes, watch_config_file, SettingsStore};
 781//     use std::{
 782//         collections::HashSet,
 783//         path::{Path, PathBuf},
 784//     };
 785//     use theme::{ThemeRegistry, ThemeSettings};
 786//     use workspace::{
 787//         item::{Item, ItemHandle},
 788//         open_new, open_paths, pane, NewFile, SaveIntent, SplitDirection, WorkspaceHandle,
 789//     };
 790
 791//     #[gpui::test]
 792//     async fn test_open_paths_action(cx: &mut TestAppContext) {
 793//         let app_state = init_test(cx);
 794//         app_state
 795//             .fs
 796//             .as_fake()
 797//             .insert_tree(
 798//                 "/root",
 799//                 json!({
 800//                     "a": {
 801//                         "aa": null,
 802//                         "ab": null,
 803//                     },
 804//                     "b": {
 805//                         "ba": null,
 806//                         "bb": null,
 807//                     },
 808//                     "c": {
 809//                         "ca": null,
 810//                         "cb": null,
 811//                     },
 812//                     "d": {
 813//                         "da": null,
 814//                         "db": null,
 815//                     },
 816//                 }),
 817//             )
 818//             .await;
 819
 820//         cx.update(|cx| {
 821//             open_paths(
 822//                 &[PathBuf::from("/root/a"), PathBuf::from("/root/b")],
 823//                 &app_state,
 824//                 None,
 825//                 cx,
 826//             )
 827//         })
 828//         .await
 829//         .unwrap();
 830//         assert_eq!(cx.windows().len(), 1);
 831
 832//         cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
 833//             .await
 834//             .unwrap();
 835//         assert_eq!(cx.windows().len(), 1);
 836//         let workspace_1 = cx.windows()[0].downcast::<Workspace>().unwrap().root(cx);
 837//         workspace_1.update(cx, |workspace, cx| {
 838//             assert_eq!(workspace.worktrees(cx).count(), 2);
 839//             assert!(workspace.left_dock().read(cx).is_open());
 840//             assert!(workspace.active_pane().is_focused(cx));
 841//         });
 842
 843//         cx.update(|cx| {
 844//             open_paths(
 845//                 &[PathBuf::from("/root/b"), PathBuf::from("/root/c")],
 846//                 &app_state,
 847//                 None,
 848//                 cx,
 849//             )
 850//         })
 851//         .await
 852//         .unwrap();
 853//         assert_eq!(cx.windows().len(), 2);
 854
 855//         // Replace existing windows
 856//         let window = cx.windows()[0].downcast::<Workspace>().unwrap();
 857//         cx.update(|cx| {
 858//             open_paths(
 859//                 &[PathBuf::from("/root/c"), PathBuf::from("/root/d")],
 860//                 &app_state,
 861//                 Some(window),
 862//                 cx,
 863//             )
 864//         })
 865//         .await
 866//         .unwrap();
 867//         assert_eq!(cx.windows().len(), 2);
 868//         let workspace_1 = cx.windows()[0].downcast::<Workspace>().unwrap().root(cx);
 869//         workspace_1.update(cx, |workspace, cx| {
 870//             assert_eq!(
 871//                 workspace
 872//                     .worktrees(cx)
 873//                     .map(|w| w.read(cx).abs_path())
 874//                     .collect::<Vec<_>>(),
 875//                 &[Path::new("/root/c").into(), Path::new("/root/d").into()]
 876//             );
 877//             assert!(workspace.left_dock().read(cx).is_open());
 878//             assert!(workspace.active_pane().is_focused(cx));
 879//         });
 880//     }
 881
 882//     #[gpui::test]
 883//     async fn test_window_edit_state(executor: Arc<Deterministic>, cx: &mut TestAppContext) {
 884//         let app_state = init_test(cx);
 885//         app_state
 886//             .fs
 887//             .as_fake()
 888//             .insert_tree("/root", json!({"a": "hey"}))
 889//             .await;
 890
 891//         cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
 892//             .await
 893//             .unwrap();
 894//         assert_eq!(cx.windows().len(), 1);
 895
 896//         // When opening the workspace, the window is not in a edited state.
 897//         let window = cx.windows()[0].downcast::<Workspace>().unwrap();
 898//         let workspace = window.root(cx);
 899//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 900//         let editor = workspace.read_with(cx, |workspace, cx| {
 901//             workspace
 902//                 .active_item(cx)
 903//                 .unwrap()
 904//                 .downcast::<Editor>()
 905//                 .unwrap()
 906//         });
 907//         assert!(!window.is_edited(cx));
 908
 909//         // Editing a buffer marks the window as edited.
 910//         editor.update(cx, |editor, cx| editor.insert("EDIT", cx));
 911//         assert!(window.is_edited(cx));
 912
 913//         // Undoing the edit restores the window's edited state.
 914//         editor.update(cx, |editor, cx| editor.undo(&Default::default(), cx));
 915//         assert!(!window.is_edited(cx));
 916
 917//         // Redoing the edit marks the window as edited again.
 918//         editor.update(cx, |editor, cx| editor.redo(&Default::default(), cx));
 919//         assert!(window.is_edited(cx));
 920
 921//         // Closing the item restores the window's edited state.
 922//         let close = pane.update(cx, |pane, cx| {
 923//             drop(editor);
 924//             pane.close_active_item(&Default::default(), cx).unwrap()
 925//         });
 926//         executor.run_until_parked();
 927
 928//         window.simulate_prompt_answer(1, cx);
 929//         close.await.unwrap();
 930//         assert!(!window.is_edited(cx));
 931
 932//         // Opening the buffer again doesn't impact the window's edited state.
 933//         cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
 934//             .await
 935//             .unwrap();
 936//         let editor = workspace.read_with(cx, |workspace, cx| {
 937//             workspace
 938//                 .active_item(cx)
 939//                 .unwrap()
 940//                 .downcast::<Editor>()
 941//                 .unwrap()
 942//         });
 943//         assert!(!window.is_edited(cx));
 944
 945//         // Editing the buffer marks the window as edited.
 946//         editor.update(cx, |editor, cx| editor.insert("EDIT", cx));
 947//         assert!(window.is_edited(cx));
 948
 949//         // Ensure closing the window via the mouse gets preempted due to the
 950//         // buffer having unsaved changes.
 951//         assert!(!window.simulate_close(cx));
 952//         executor.run_until_parked();
 953//         assert_eq!(cx.windows().len(), 1);
 954
 955//         // The window is successfully closed after the user dismisses the prompt.
 956//         window.simulate_prompt_answer(1, cx);
 957//         executor.run_until_parked();
 958//         assert_eq!(cx.windows().len(), 0);
 959//     }
 960
 961//     #[gpui::test]
 962//     async fn test_new_empty_workspace(cx: &mut TestAppContext) {
 963//         let app_state = init_test(cx);
 964//         cx.update(|cx| {
 965//             open_new(&app_state, cx, |workspace, cx| {
 966//                 Editor::new_file(workspace, &Default::default(), cx)
 967//             })
 968//         })
 969//         .await;
 970
 971//         let window = cx
 972//             .windows()
 973//             .first()
 974//             .unwrap()
 975//             .downcast::<Workspace>()
 976//             .unwrap();
 977//         let workspace = window.root(cx);
 978
 979//         let editor = workspace.update(cx, |workspace, cx| {
 980//             workspace
 981//                 .active_item(cx)
 982//                 .unwrap()
 983//                 .downcast::<editor::Editor>()
 984//                 .unwrap()
 985//         });
 986
 987//         editor.update(cx, |editor, cx| {
 988//             assert!(editor.text(cx).is_empty());
 989//             assert!(!editor.is_dirty(cx));
 990//         });
 991
 992//         let save_task = workspace.update(cx, |workspace, cx| {
 993//             workspace.save_active_item(SaveIntent::Save, cx)
 994//         });
 995//         app_state.fs.create_dir(Path::new("/root")).await.unwrap();
 996//         cx.foreground().run_until_parked();
 997//         cx.simulate_new_path_selection(|_| Some(PathBuf::from("/root/the-new-name")));
 998//         save_task.await.unwrap();
 999//         editor.read_with(cx, |editor, cx| {
1000//             assert!(!editor.is_dirty(cx));
1001//             assert_eq!(editor.title(cx), "the-new-name");
1002//         });
1003//     }
1004
1005//     #[gpui::test]
1006//     async fn test_open_entry(cx: &mut TestAppContext) {
1007//         let app_state = init_test(cx);
1008//         app_state
1009//             .fs
1010//             .as_fake()
1011//             .insert_tree(
1012//                 "/root",
1013//                 json!({
1014//                     "a": {
1015//                         "file1": "contents 1",
1016//                         "file2": "contents 2",
1017//                         "file3": "contents 3",
1018//                     },
1019//                 }),
1020//             )
1021//             .await;
1022
1023//         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
1024//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
1025//         let workspace = window.root(cx);
1026
1027//         let entries = cx.read(|cx| workspace.file_project_paths(cx));
1028//         let file1 = entries[0].clone();
1029//         let file2 = entries[1].clone();
1030//         let file3 = entries[2].clone();
1031
1032//         // Open the first entry
1033//         let entry_1 = workspace
1034//             .update(cx, |w, cx| w.open_path(file1.clone(), None, true, cx))
1035//             .await
1036//             .unwrap();
1037//         cx.read(|cx| {
1038//             let pane = workspace.read(cx).active_pane().read(cx);
1039//             assert_eq!(
1040//                 pane.active_item().unwrap().project_path(cx),
1041//                 Some(file1.clone())
1042//             );
1043//             assert_eq!(pane.items_len(), 1);
1044//         });
1045
1046//         // Open the second entry
1047//         workspace
1048//             .update(cx, |w, cx| w.open_path(file2.clone(), None, true, cx))
1049//             .await
1050//             .unwrap();
1051//         cx.read(|cx| {
1052//             let pane = workspace.read(cx).active_pane().read(cx);
1053//             assert_eq!(
1054//                 pane.active_item().unwrap().project_path(cx),
1055//                 Some(file2.clone())
1056//             );
1057//             assert_eq!(pane.items_len(), 2);
1058//         });
1059
1060//         // Open the first entry again. The existing pane item is activated.
1061//         let entry_1b = workspace
1062//             .update(cx, |w, cx| w.open_path(file1.clone(), None, true, cx))
1063//             .await
1064//             .unwrap();
1065//         assert_eq!(entry_1.id(), entry_1b.id());
1066
1067//         cx.read(|cx| {
1068//             let pane = workspace.read(cx).active_pane().read(cx);
1069//             assert_eq!(
1070//                 pane.active_item().unwrap().project_path(cx),
1071//                 Some(file1.clone())
1072//             );
1073//             assert_eq!(pane.items_len(), 2);
1074//         });
1075
1076//         // Split the pane with the first entry, then open the second entry again.
1077//         workspace
1078//             .update(cx, |w, cx| {
1079//                 w.split_and_clone(w.active_pane().clone(), SplitDirection::Right, cx);
1080//                 w.open_path(file2.clone(), None, true, cx)
1081//             })
1082//             .await
1083//             .unwrap();
1084
1085//         workspace.read_with(cx, |w, cx| {
1086//             assert_eq!(
1087//                 w.active_pane()
1088//                     .read(cx)
1089//                     .active_item()
1090//                     .unwrap()
1091//                     .project_path(cx),
1092//                 Some(file2.clone())
1093//             );
1094//         });
1095
1096//         // Open the third entry twice concurrently. Only one pane item is added.
1097//         let (t1, t2) = workspace.update(cx, |w, cx| {
1098//             (
1099//                 w.open_path(file3.clone(), None, true, cx),
1100//                 w.open_path(file3.clone(), None, true, cx),
1101//             )
1102//         });
1103//         t1.await.unwrap();
1104//         t2.await.unwrap();
1105//         cx.read(|cx| {
1106//             let pane = workspace.read(cx).active_pane().read(cx);
1107//             assert_eq!(
1108//                 pane.active_item().unwrap().project_path(cx),
1109//                 Some(file3.clone())
1110//             );
1111//             let pane_entries = pane
1112//                 .items()
1113//                 .map(|i| i.project_path(cx).unwrap())
1114//                 .collect::<Vec<_>>();
1115//             assert_eq!(pane_entries, &[file1, file2, file3]);
1116//         });
1117//     }
1118
1119//     #[gpui::test]
1120//     async fn test_open_paths(cx: &mut TestAppContext) {
1121//         let app_state = init_test(cx);
1122
1123//         app_state
1124//             .fs
1125//             .as_fake()
1126//             .insert_tree(
1127//                 "/",
1128//                 json!({
1129//                     "dir1": {
1130//                         "a.txt": ""
1131//                     },
1132//                     "dir2": {
1133//                         "b.txt": ""
1134//                     },
1135//                     "dir3": {
1136//                         "c.txt": ""
1137//                     },
1138//                     "d.txt": ""
1139//                 }),
1140//             )
1141//             .await;
1142
1143//         cx.update(|cx| open_paths(&[PathBuf::from("/dir1/")], &app_state, None, cx))
1144//             .await
1145//             .unwrap();
1146//         assert_eq!(cx.windows().len(), 1);
1147//         let workspace = cx.windows()[0].downcast::<Workspace>().unwrap().root(cx);
1148
1149//         #[track_caller]
1150//         fn assert_project_panel_selection(
1151//             workspace: &Workspace,
1152//             expected_worktree_path: &Path,
1153//             expected_entry_path: &Path,
1154//             cx: &AppContext,
1155//         ) {
1156//             let project_panel = [
1157//                 workspace.left_dock().read(cx).panel::<ProjectPanel>(),
1158//                 workspace.right_dock().read(cx).panel::<ProjectPanel>(),
1159//                 workspace.bottom_dock().read(cx).panel::<ProjectPanel>(),
1160//             ]
1161//             .into_iter()
1162//             .find_map(std::convert::identity)
1163//             .expect("found no project panels")
1164//             .read(cx);
1165//             let (selected_worktree, selected_entry) = project_panel
1166//                 .selected_entry(cx)
1167//                 .expect("project panel should have a selected entry");
1168//             assert_eq!(
1169//                 selected_worktree.abs_path().as_ref(),
1170//                 expected_worktree_path,
1171//                 "Unexpected project panel selected worktree path"
1172//             );
1173//             assert_eq!(
1174//                 selected_entry.path.as_ref(),
1175//                 expected_entry_path,
1176//                 "Unexpected project panel selected entry path"
1177//             );
1178//         }
1179
1180//         // Open a file within an existing worktree.
1181//         workspace
1182//             .update(cx, |view, cx| {
1183//                 view.open_paths(vec!["/dir1/a.txt".into()], true, cx)
1184//             })
1185//             .await;
1186//         cx.read(|cx| {
1187//             let workspace = workspace.read(cx);
1188//             assert_project_panel_selection(workspace, Path::new("/dir1"), Path::new("a.txt"), cx);
1189//             assert_eq!(
1190//                 workspace
1191//                     .active_pane()
1192//                     .read(cx)
1193//                     .active_item()
1194//                     .unwrap()
1195//                     .as_any()
1196//                     .downcast_ref::<Editor>()
1197//                     .unwrap()
1198//                     .read(cx)
1199//                     .title(cx),
1200//                 "a.txt"
1201//             );
1202//         });
1203
1204//         // Open a file outside of any existing worktree.
1205//         workspace
1206//             .update(cx, |view, cx| {
1207//                 view.open_paths(vec!["/dir2/b.txt".into()], true, cx)
1208//             })
1209//             .await;
1210//         cx.read(|cx| {
1211//             let workspace = workspace.read(cx);
1212//             assert_project_panel_selection(workspace, Path::new("/dir2/b.txt"), Path::new(""), cx);
1213//             let worktree_roots = workspace
1214//                 .worktrees(cx)
1215//                 .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
1216//                 .collect::<HashSet<_>>();
1217//             assert_eq!(
1218//                 worktree_roots,
1219//                 vec!["/dir1", "/dir2/b.txt"]
1220//                     .into_iter()
1221//                     .map(Path::new)
1222//                     .collect(),
1223//             );
1224//             assert_eq!(
1225//                 workspace
1226//                     .active_pane()
1227//                     .read(cx)
1228//                     .active_item()
1229//                     .unwrap()
1230//                     .as_any()
1231//                     .downcast_ref::<Editor>()
1232//                     .unwrap()
1233//                     .read(cx)
1234//                     .title(cx),
1235//                 "b.txt"
1236//             );
1237//         });
1238
1239//         // Ensure opening a directory and one of its children only adds one worktree.
1240//         workspace
1241//             .update(cx, |view, cx| {
1242//                 view.open_paths(vec!["/dir3".into(), "/dir3/c.txt".into()], true, cx)
1243//             })
1244//             .await;
1245//         cx.read(|cx| {
1246//             let workspace = workspace.read(cx);
1247//             assert_project_panel_selection(workspace, Path::new("/dir3"), Path::new("c.txt"), cx);
1248//             let worktree_roots = workspace
1249//                 .worktrees(cx)
1250//                 .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
1251//                 .collect::<HashSet<_>>();
1252//             assert_eq!(
1253//                 worktree_roots,
1254//                 vec!["/dir1", "/dir2/b.txt", "/dir3"]
1255//                     .into_iter()
1256//                     .map(Path::new)
1257//                     .collect(),
1258//             );
1259//             assert_eq!(
1260//                 workspace
1261//                     .active_pane()
1262//                     .read(cx)
1263//                     .active_item()
1264//                     .unwrap()
1265//                     .as_any()
1266//                     .downcast_ref::<Editor>()
1267//                     .unwrap()
1268//                     .read(cx)
1269//                     .title(cx),
1270//                 "c.txt"
1271//             );
1272//         });
1273
1274//         // Ensure opening invisibly a file outside an existing worktree adds a new, invisible worktree.
1275//         workspace
1276//             .update(cx, |view, cx| {
1277//                 view.open_paths(vec!["/d.txt".into()], false, cx)
1278//             })
1279//             .await;
1280//         cx.read(|cx| {
1281//             let workspace = workspace.read(cx);
1282//             assert_project_panel_selection(workspace, Path::new("/d.txt"), Path::new(""), cx);
1283//             let worktree_roots = workspace
1284//                 .worktrees(cx)
1285//                 .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
1286//                 .collect::<HashSet<_>>();
1287//             assert_eq!(
1288//                 worktree_roots,
1289//                 vec!["/dir1", "/dir2/b.txt", "/dir3", "/d.txt"]
1290//                     .into_iter()
1291//                     .map(Path::new)
1292//                     .collect(),
1293//             );
1294
1295//             let visible_worktree_roots = workspace
1296//                 .visible_worktrees(cx)
1297//                 .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
1298//                 .collect::<HashSet<_>>();
1299//             assert_eq!(
1300//                 visible_worktree_roots,
1301//                 vec!["/dir1", "/dir2/b.txt", "/dir3"]
1302//                     .into_iter()
1303//                     .map(Path::new)
1304//                     .collect(),
1305//             );
1306
1307//             assert_eq!(
1308//                 workspace
1309//                     .active_pane()
1310//                     .read(cx)
1311//                     .active_item()
1312//                     .unwrap()
1313//                     .as_any()
1314//                     .downcast_ref::<Editor>()
1315//                     .unwrap()
1316//                     .read(cx)
1317//                     .title(cx),
1318//                 "d.txt"
1319//             );
1320//         });
1321//     }
1322
1323//     #[gpui::test]
1324//     async fn test_opening_excluded_paths(cx: &mut TestAppContext) {
1325//         let app_state = init_test(cx);
1326//         cx.update(|cx| {
1327//             cx.update_global::<SettingsStore, _, _>(|store, cx| {
1328//                 store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
1329//                     project_settings.file_scan_exclusions =
1330//                         Some(vec!["excluded_dir".to_string(), "**/.git".to_string()]);
1331//                 });
1332//             });
1333//         });
1334//         app_state
1335//             .fs
1336//             .as_fake()
1337//             .insert_tree(
1338//                 "/root",
1339//                 json!({
1340//                     ".gitignore": "ignored_dir\n",
1341//                     ".git": {
1342//                         "HEAD": "ref: refs/heads/main",
1343//                     },
1344//                     "regular_dir": {
1345//                         "file": "regular file contents",
1346//                     },
1347//                     "ignored_dir": {
1348//                         "ignored_subdir": {
1349//                             "file": "ignored subfile contents",
1350//                         },
1351//                         "file": "ignored file contents",
1352//                     },
1353//                     "excluded_dir": {
1354//                         "file": "excluded file contents",
1355//                     },
1356//                 }),
1357//             )
1358//             .await;
1359
1360//         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
1361//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
1362//         let workspace = window.root(cx);
1363
1364//         let initial_entries = cx.read(|cx| workspace.file_project_paths(cx));
1365//         let paths_to_open = [
1366//             Path::new("/root/excluded_dir/file").to_path_buf(),
1367//             Path::new("/root/.git/HEAD").to_path_buf(),
1368//             Path::new("/root/excluded_dir/ignored_subdir").to_path_buf(),
1369//         ];
1370//         let (opened_workspace, new_items) = cx
1371//             .update(|cx| workspace::open_paths(&paths_to_open, &app_state, None, cx))
1372//             .await
1373//             .unwrap();
1374
1375//         assert_eq!(
1376//             opened_workspace.id(),
1377//             workspace.id(),
1378//             "Excluded files in subfolders of a workspace root should be opened in the workspace"
1379//         );
1380//         let mut opened_paths = cx.read(|cx| {
1381//             assert_eq!(
1382//                 new_items.len(),
1383//                 paths_to_open.len(),
1384//                 "Expect to get the same number of opened items as submitted paths to open"
1385//             );
1386//             new_items
1387//                 .iter()
1388//                 .zip(paths_to_open.iter())
1389//                 .map(|(i, path)| {
1390//                     match i {
1391//                         Some(Ok(i)) => {
1392//                             Some(i.project_path(cx).map(|p| p.path.display().to_string()))
1393//                         }
1394//                         Some(Err(e)) => panic!("Excluded file {path:?} failed to open: {e:?}"),
1395//                         None => None,
1396//                     }
1397//                     .flatten()
1398//                 })
1399//                 .collect::<Vec<_>>()
1400//         });
1401//         opened_paths.sort();
1402//         assert_eq!(
1403//             opened_paths,
1404//             vec![
1405//                 None,
1406//                 Some(".git/HEAD".to_string()),
1407//                 Some("excluded_dir/file".to_string()),
1408//             ],
1409//             "Excluded files should get opened, excluded dir should not get opened"
1410//         );
1411
1412//         let entries = cx.read(|cx| workspace.file_project_paths(cx));
1413//         assert_eq!(
1414//             initial_entries, entries,
1415//             "Workspace entries should not change after opening excluded files and directories paths"
1416//         );
1417
1418//         cx.read(|cx| {
1419//             let pane = workspace.read(cx).active_pane().read(cx);
1420//             let mut opened_buffer_paths = pane
1421//                 .items()
1422//                 .map(|i| {
1423//                     i.project_path(cx)
1424//                         .expect("all excluded files that got open should have a path")
1425//                         .path
1426//                         .display()
1427//                         .to_string()
1428//                 })
1429//                 .collect::<Vec<_>>();
1430//             opened_buffer_paths.sort();
1431//             assert_eq!(
1432//                 opened_buffer_paths,
1433//                 vec![".git/HEAD".to_string(), "excluded_dir/file".to_string()],
1434//                 "Despite not being present in the worktrees, buffers for excluded files are opened and added to the pane"
1435//             );
1436//         });
1437//     }
1438
1439//     #[gpui::test]
1440//     async fn test_save_conflicting_item(cx: &mut TestAppContext) {
1441//         let app_state = init_test(cx);
1442//         app_state
1443//             .fs
1444//             .as_fake()
1445//             .insert_tree("/root", json!({ "a.txt": "" }))
1446//             .await;
1447
1448//         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
1449//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
1450//         let workspace = window.root(cx);
1451
1452//         // Open a file within an existing worktree.
1453//         workspace
1454//             .update(cx, |view, cx| {
1455//                 view.open_paths(vec![PathBuf::from("/root/a.txt")], true, cx)
1456//             })
1457//             .await;
1458//         let editor = cx.read(|cx| {
1459//             let pane = workspace.read(cx).active_pane().read(cx);
1460//             let item = pane.active_item().unwrap();
1461//             item.downcast::<Editor>().unwrap()
1462//         });
1463
1464//         editor.update(cx, |editor, cx| editor.handle_input("x", cx));
1465//         app_state
1466//             .fs
1467//             .as_fake()
1468//             .insert_file("/root/a.txt", "changed".to_string())
1469//             .await;
1470//         editor
1471//             .condition(cx, |editor, cx| editor.has_conflict(cx))
1472//             .await;
1473//         cx.read(|cx| assert!(editor.is_dirty(cx)));
1474
1475//         let save_task = workspace.update(cx, |workspace, cx| {
1476//             workspace.save_active_item(SaveIntent::Save, cx)
1477//         });
1478//         cx.foreground().run_until_parked();
1479//         window.simulate_prompt_answer(0, cx);
1480//         save_task.await.unwrap();
1481//         editor.read_with(cx, |editor, cx| {
1482//             assert!(!editor.is_dirty(cx));
1483//             assert!(!editor.has_conflict(cx));
1484//         });
1485//     }
1486
1487//     #[gpui::test]
1488//     async fn test_open_and_save_new_file(cx: &mut TestAppContext) {
1489//         let app_state = init_test(cx);
1490//         app_state.fs.create_dir(Path::new("/root")).await.unwrap();
1491
1492//         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
1493//         project.update(cx, |project, _| project.languages().add(rust_lang()));
1494//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
1495//         let workspace = window.root(cx);
1496//         let worktree = cx.read(|cx| workspace.read(cx).worktrees(cx).next().unwrap());
1497
1498//         // Create a new untitled buffer
1499//         cx.dispatch_action(window.into(), NewFile);
1500//         let editor = workspace.read_with(cx, |workspace, cx| {
1501//             workspace
1502//                 .active_item(cx)
1503//                 .unwrap()
1504//                 .downcast::<Editor>()
1505//                 .unwrap()
1506//         });
1507
1508//         editor.update(cx, |editor, cx| {
1509//             assert!(!editor.is_dirty(cx));
1510//             assert_eq!(editor.title(cx), "untitled");
1511//             assert!(Arc::ptr_eq(
1512//                 &editor.language_at(0, cx).unwrap(),
1513//                 &languages::PLAIN_TEXT
1514//             ));
1515//             editor.handle_input("hi", cx);
1516//             assert!(editor.is_dirty(cx));
1517//         });
1518
1519//         // Save the buffer. This prompts for a filename.
1520//         let save_task = workspace.update(cx, |workspace, cx| {
1521//             workspace.save_active_item(SaveIntent::Save, cx)
1522//         });
1523//         cx.foreground().run_until_parked();
1524//         cx.simulate_new_path_selection(|parent_dir| {
1525//             assert_eq!(parent_dir, Path::new("/root"));
1526//             Some(parent_dir.join("the-new-name.rs"))
1527//         });
1528//         cx.read(|cx| {
1529//             assert!(editor.is_dirty(cx));
1530//             assert_eq!(editor.read(cx).title(cx), "untitled");
1531//         });
1532
1533//         // When the save completes, the buffer's title is updated and the language is assigned based
1534//         // on the path.
1535//         save_task.await.unwrap();
1536//         editor.read_with(cx, |editor, cx| {
1537//             assert!(!editor.is_dirty(cx));
1538//             assert_eq!(editor.title(cx), "the-new-name.rs");
1539//             assert_eq!(editor.language_at(0, cx).unwrap().name().as_ref(), "Rust");
1540//         });
1541
1542//         // Edit the file and save it again. This time, there is no filename prompt.
1543//         editor.update(cx, |editor, cx| {
1544//             editor.handle_input(" there", cx);
1545//             assert!(editor.is_dirty(cx));
1546//         });
1547//         let save_task = workspace.update(cx, |workspace, cx| {
1548//             workspace.save_active_item(SaveIntent::Save, cx)
1549//         });
1550//         save_task.await.unwrap();
1551//         assert!(!cx.did_prompt_for_new_path());
1552//         editor.read_with(cx, |editor, cx| {
1553//             assert!(!editor.is_dirty(cx));
1554//             assert_eq!(editor.title(cx), "the-new-name.rs")
1555//         });
1556
1557//         // Open the same newly-created file in another pane item. The new editor should reuse
1558//         // the same buffer.
1559//         cx.dispatch_action(window.into(), NewFile);
1560//         workspace
1561//             .update(cx, |workspace, cx| {
1562//                 workspace.split_and_clone(
1563//                     workspace.active_pane().clone(),
1564//                     SplitDirection::Right,
1565//                     cx,
1566//                 );
1567//                 workspace.open_path((worktree.read(cx).id(), "the-new-name.rs"), None, true, cx)
1568//             })
1569//             .await
1570//             .unwrap();
1571//         let editor2 = workspace.update(cx, |workspace, cx| {
1572//             workspace
1573//                 .active_item(cx)
1574//                 .unwrap()
1575//                 .downcast::<Editor>()
1576//                 .unwrap()
1577//         });
1578//         cx.read(|cx| {
1579//             assert_eq!(
1580//                 editor2.read(cx).buffer().read(cx).as_singleton().unwrap(),
1581//                 editor.read(cx).buffer().read(cx).as_singleton().unwrap()
1582//             );
1583//         })
1584//     }
1585
1586//     #[gpui::test]
1587//     async fn test_setting_language_when_saving_as_single_file_worktree(cx: &mut TestAppContext) {
1588//         let app_state = init_test(cx);
1589//         app_state.fs.create_dir(Path::new("/root")).await.unwrap();
1590
1591//         let project = Project::test(app_state.fs.clone(), [], cx).await;
1592//         project.update(cx, |project, _| project.languages().add(rust_lang()));
1593//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
1594//         let workspace = window.root(cx);
1595
1596//         // Create a new untitled buffer
1597//         cx.dispatch_action(window.into(), NewFile);
1598//         let editor = workspace.read_with(cx, |workspace, cx| {
1599//             workspace
1600//                 .active_item(cx)
1601//                 .unwrap()
1602//                 .downcast::<Editor>()
1603//                 .unwrap()
1604//         });
1605
1606//         editor.update(cx, |editor, cx| {
1607//             assert!(Arc::ptr_eq(
1608//                 &editor.language_at(0, cx).unwrap(),
1609//                 &languages::PLAIN_TEXT
1610//             ));
1611//             editor.handle_input("hi", cx);
1612//             assert!(editor.is_dirty(cx));
1613//         });
1614
1615//         // Save the buffer. This prompts for a filename.
1616//         let save_task = workspace.update(cx, |workspace, cx| {
1617//             workspace.save_active_item(SaveIntent::Save, cx)
1618//         });
1619//         cx.foreground().run_until_parked();
1620//         cx.simulate_new_path_selection(|_| Some(PathBuf::from("/root/the-new-name.rs")));
1621//         save_task.await.unwrap();
1622//         // The buffer is not dirty anymore and the language is assigned based on the path.
1623//         editor.read_with(cx, |editor, cx| {
1624//             assert!(!editor.is_dirty(cx));
1625//             assert_eq!(editor.language_at(0, cx).unwrap().name().as_ref(), "Rust")
1626//         });
1627//     }
1628
1629//     #[gpui::test]
1630//     async fn test_pane_actions(cx: &mut TestAppContext) {
1631//         let app_state = init_test(cx);
1632//         app_state
1633//             .fs
1634//             .as_fake()
1635//             .insert_tree(
1636//                 "/root",
1637//                 json!({
1638//                     "a": {
1639//                         "file1": "contents 1",
1640//                         "file2": "contents 2",
1641//                         "file3": "contents 3",
1642//                     },
1643//                 }),
1644//             )
1645//             .await;
1646
1647//         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
1648//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
1649//         let workspace = window.root(cx);
1650
1651//         let entries = cx.read(|cx| workspace.file_project_paths(cx));
1652//         let file1 = entries[0].clone();
1653
1654//         let pane_1 = cx.read(|cx| workspace.read(cx).active_pane().clone());
1655
1656//         workspace
1657//             .update(cx, |w, cx| w.open_path(file1.clone(), None, true, cx))
1658//             .await
1659//             .unwrap();
1660
1661//         let (editor_1, buffer) = pane_1.update(cx, |pane_1, cx| {
1662//             let editor = pane_1.active_item().unwrap().downcast::<Editor>().unwrap();
1663//             assert_eq!(editor.project_path(cx), Some(file1.clone()));
1664//             let buffer = editor.update(cx, |editor, cx| {
1665//                 editor.insert("dirt", cx);
1666//                 editor.buffer().downgrade()
1667//             });
1668//             (editor.downgrade(), buffer)
1669//         });
1670
1671//         cx.dispatch_action(window.into(), pane::SplitRight);
1672//         let editor_2 = cx.update(|cx| {
1673//             let pane_2 = workspace.read(cx).active_pane().clone();
1674//             assert_ne!(pane_1, pane_2);
1675
1676//             let pane2_item = pane_2.read(cx).active_item().unwrap();
1677//             assert_eq!(pane2_item.project_path(cx), Some(file1.clone()));
1678
1679//             pane2_item.downcast::<Editor>().unwrap().downgrade()
1680//         });
1681//         cx.dispatch_action(
1682//             window.into(),
1683//             workspace::CloseActiveItem { save_intent: None },
1684//         );
1685
1686//         cx.foreground().run_until_parked();
1687//         workspace.read_with(cx, |workspace, _| {
1688//             assert_eq!(workspace.panes().len(), 1);
1689//             assert_eq!(workspace.active_pane(), &pane_1);
1690//         });
1691
1692//         cx.dispatch_action(
1693//             window.into(),
1694//             workspace::CloseActiveItem { save_intent: None },
1695//         );
1696//         cx.foreground().run_until_parked();
1697//         window.simulate_prompt_answer(1, cx);
1698//         cx.foreground().run_until_parked();
1699
1700//         workspace.read_with(cx, |workspace, cx| {
1701//             assert_eq!(workspace.panes().len(), 1);
1702//             assert!(workspace.active_item(cx).is_none());
1703//         });
1704
1705//         cx.assert_dropped(editor_1);
1706//         cx.assert_dropped(editor_2);
1707//         cx.assert_dropped(buffer);
1708//     }
1709
1710//     #[gpui::test]
1711//     async fn test_navigation(cx: &mut TestAppContext) {
1712//         let app_state = init_test(cx);
1713//         app_state
1714//             .fs
1715//             .as_fake()
1716//             .insert_tree(
1717//                 "/root",
1718//                 json!({
1719//                     "a": {
1720//                         "file1": "contents 1\n".repeat(20),
1721//                         "file2": "contents 2\n".repeat(20),
1722//                         "file3": "contents 3\n".repeat(20),
1723//                     },
1724//                 }),
1725//             )
1726//             .await;
1727
1728//         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
1729//         let workspace = cx
1730//             .add_window(|cx| Workspace::test_new(project.clone(), cx))
1731//             .root(cx);
1732//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
1733
1734//         let entries = cx.read(|cx| workspace.file_project_paths(cx));
1735//         let file1 = entries[0].clone();
1736//         let file2 = entries[1].clone();
1737//         let file3 = entries[2].clone();
1738
1739//         let editor1 = workspace
1740//             .update(cx, |w, cx| w.open_path(file1.clone(), None, true, cx))
1741//             .await
1742//             .unwrap()
1743//             .downcast::<Editor>()
1744//             .unwrap();
1745//         editor1.update(cx, |editor, cx| {
1746//             editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
1747//                 s.select_display_ranges([DisplayPoint::new(10, 0)..DisplayPoint::new(10, 0)])
1748//             });
1749//         });
1750//         let editor2 = workspace
1751//             .update(cx, |w, cx| w.open_path(file2.clone(), None, true, cx))
1752//             .await
1753//             .unwrap()
1754//             .downcast::<Editor>()
1755//             .unwrap();
1756//         let editor3 = workspace
1757//             .update(cx, |w, cx| w.open_path(file3.clone(), None, true, cx))
1758//             .await
1759//             .unwrap()
1760//             .downcast::<Editor>()
1761//             .unwrap();
1762
1763//         editor3
1764//             .update(cx, |editor, cx| {
1765//                 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
1766//                     s.select_display_ranges([DisplayPoint::new(12, 0)..DisplayPoint::new(12, 0)])
1767//                 });
1768//                 editor.newline(&Default::default(), cx);
1769//                 editor.newline(&Default::default(), cx);
1770//                 editor.move_down(&Default::default(), cx);
1771//                 editor.move_down(&Default::default(), cx);
1772//                 editor.save(project.clone(), cx)
1773//             })
1774//             .await
1775//             .unwrap();
1776//         editor3.update(cx, |editor, cx| {
1777//             editor.set_scroll_position(vec2f(0., 12.5), cx)
1778//         });
1779//         assert_eq!(
1780//             active_location(&workspace, cx),
1781//             (file3.clone(), DisplayPoint::new(16, 0), 12.5)
1782//         );
1783
1784//         workspace
1785//             .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1786//             .await
1787//             .unwrap();
1788//         assert_eq!(
1789//             active_location(&workspace, cx),
1790//             (file3.clone(), DisplayPoint::new(0, 0), 0.)
1791//         );
1792
1793//         workspace
1794//             .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1795//             .await
1796//             .unwrap();
1797//         assert_eq!(
1798//             active_location(&workspace, cx),
1799//             (file2.clone(), DisplayPoint::new(0, 0), 0.)
1800//         );
1801
1802//         workspace
1803//             .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1804//             .await
1805//             .unwrap();
1806//         assert_eq!(
1807//             active_location(&workspace, cx),
1808//             (file1.clone(), DisplayPoint::new(10, 0), 0.)
1809//         );
1810
1811//         workspace
1812//             .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1813//             .await
1814//             .unwrap();
1815//         assert_eq!(
1816//             active_location(&workspace, cx),
1817//             (file1.clone(), DisplayPoint::new(0, 0), 0.)
1818//         );
1819
1820//         // Go back one more time and ensure we don't navigate past the first item in the history.
1821//         workspace
1822//             .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1823//             .await
1824//             .unwrap();
1825//         assert_eq!(
1826//             active_location(&workspace, cx),
1827//             (file1.clone(), DisplayPoint::new(0, 0), 0.)
1828//         );
1829
1830//         workspace
1831//             .update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
1832//             .await
1833//             .unwrap();
1834//         assert_eq!(
1835//             active_location(&workspace, cx),
1836//             (file1.clone(), DisplayPoint::new(10, 0), 0.)
1837//         );
1838
1839//         workspace
1840//             .update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
1841//             .await
1842//             .unwrap();
1843//         assert_eq!(
1844//             active_location(&workspace, cx),
1845//             (file2.clone(), DisplayPoint::new(0, 0), 0.)
1846//         );
1847
1848//         // Go forward to an item that has been closed, ensuring it gets re-opened at the same
1849//         // location.
1850//         pane.update(cx, |pane, cx| {
1851//             let editor3_id = editor3.id();
1852//             drop(editor3);
1853//             pane.close_item_by_id(editor3_id, SaveIntent::Close, cx)
1854//         })
1855//         .await
1856//         .unwrap();
1857//         workspace
1858//             .update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
1859//             .await
1860//             .unwrap();
1861//         assert_eq!(
1862//             active_location(&workspace, cx),
1863//             (file3.clone(), DisplayPoint::new(0, 0), 0.)
1864//         );
1865
1866//         workspace
1867//             .update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
1868//             .await
1869//             .unwrap();
1870//         assert_eq!(
1871//             active_location(&workspace, cx),
1872//             (file3.clone(), DisplayPoint::new(16, 0), 12.5)
1873//         );
1874
1875//         workspace
1876//             .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1877//             .await
1878//             .unwrap();
1879//         assert_eq!(
1880//             active_location(&workspace, cx),
1881//             (file3.clone(), DisplayPoint::new(0, 0), 0.)
1882//         );
1883
1884//         // Go back to an item that has been closed and removed from disk, ensuring it gets skipped.
1885//         pane.update(cx, |pane, cx| {
1886//             let editor2_id = editor2.id();
1887//             drop(editor2);
1888//             pane.close_item_by_id(editor2_id, SaveIntent::Close, cx)
1889//         })
1890//         .await
1891//         .unwrap();
1892//         app_state
1893//             .fs
1894//             .remove_file(Path::new("/root/a/file2"), Default::default())
1895//             .await
1896//             .unwrap();
1897//         cx.foreground().run_until_parked();
1898
1899//         workspace
1900//             .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1901//             .await
1902//             .unwrap();
1903//         assert_eq!(
1904//             active_location(&workspace, cx),
1905//             (file1.clone(), DisplayPoint::new(10, 0), 0.)
1906//         );
1907//         workspace
1908//             .update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
1909//             .await
1910//             .unwrap();
1911//         assert_eq!(
1912//             active_location(&workspace, cx),
1913//             (file3.clone(), DisplayPoint::new(0, 0), 0.)
1914//         );
1915
1916//         // Modify file to collapse multiple nav history entries into the same location.
1917//         // Ensure we don't visit the same location twice when navigating.
1918//         editor1.update(cx, |editor, cx| {
1919//             editor.change_selections(None, cx, |s| {
1920//                 s.select_display_ranges([DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)])
1921//             })
1922//         });
1923
1924//         for _ in 0..5 {
1925//             editor1.update(cx, |editor, cx| {
1926//                 editor.change_selections(None, cx, |s| {
1927//                     s.select_display_ranges([DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)])
1928//                 });
1929//             });
1930//             editor1.update(cx, |editor, cx| {
1931//                 editor.change_selections(None, cx, |s| {
1932//                     s.select_display_ranges([DisplayPoint::new(13, 0)..DisplayPoint::new(13, 0)])
1933//                 })
1934//             });
1935//         }
1936
1937//         editor1.update(cx, |editor, cx| {
1938//             editor.transact(cx, |editor, cx| {
1939//                 editor.change_selections(None, cx, |s| {
1940//                     s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(14, 0)])
1941//                 });
1942//                 editor.insert("", cx);
1943//             })
1944//         });
1945
1946//         editor1.update(cx, |editor, cx| {
1947//             editor.change_selections(None, cx, |s| {
1948//                 s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
1949//             })
1950//         });
1951//         workspace
1952//             .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1953//             .await
1954//             .unwrap();
1955//         assert_eq!(
1956//             active_location(&workspace, cx),
1957//             (file1.clone(), DisplayPoint::new(2, 0), 0.)
1958//         );
1959//         workspace
1960//             .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1961//             .await
1962//             .unwrap();
1963//         assert_eq!(
1964//             active_location(&workspace, cx),
1965//             (file1.clone(), DisplayPoint::new(3, 0), 0.)
1966//         );
1967
1968//         fn active_location(
1969//             workspace: &ViewHandle<Workspace>,
1970//             cx: &mut TestAppContext,
1971//         ) -> (ProjectPath, DisplayPoint, f32) {
1972//             workspace.update(cx, |workspace, cx| {
1973//                 let item = workspace.active_item(cx).unwrap();
1974//                 let editor = item.downcast::<Editor>().unwrap();
1975//                 let (selections, scroll_position) = editor.update(cx, |editor, cx| {
1976//                     (
1977//                         editor.selections.display_ranges(cx),
1978//                         editor.scroll_position(cx),
1979//                     )
1980//                 });
1981//                 (
1982//                     item.project_path(cx).unwrap(),
1983//                     selections[0].start,
1984//                     scroll_position.y(),
1985//                 )
1986//             })
1987//         }
1988//     }
1989
1990//     #[gpui::test]
1991//     async fn test_reopening_closed_items(cx: &mut TestAppContext) {
1992//         let app_state = init_test(cx);
1993//         app_state
1994//             .fs
1995//             .as_fake()
1996//             .insert_tree(
1997//                 "/root",
1998//                 json!({
1999//                     "a": {
2000//                         "file1": "",
2001//                         "file2": "",
2002//                         "file3": "",
2003//                         "file4": "",
2004//                     },
2005//                 }),
2006//             )
2007//             .await;
2008
2009//         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
2010//         let workspace = cx
2011//             .add_window(|cx| Workspace::test_new(project, cx))
2012//             .root(cx);
2013//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2014
2015//         let entries = cx.read(|cx| workspace.file_project_paths(cx));
2016//         let file1 = entries[0].clone();
2017//         let file2 = entries[1].clone();
2018//         let file3 = entries[2].clone();
2019//         let file4 = entries[3].clone();
2020
2021//         let file1_item_id = workspace
2022//             .update(cx, |w, cx| w.open_path(file1.clone(), None, true, cx))
2023//             .await
2024//             .unwrap()
2025//             .id();
2026//         let file2_item_id = workspace
2027//             .update(cx, |w, cx| w.open_path(file2.clone(), None, true, cx))
2028//             .await
2029//             .unwrap()
2030//             .id();
2031//         let file3_item_id = workspace
2032//             .update(cx, |w, cx| w.open_path(file3.clone(), None, true, cx))
2033//             .await
2034//             .unwrap()
2035//             .id();
2036//         let file4_item_id = workspace
2037//             .update(cx, |w, cx| w.open_path(file4.clone(), None, true, cx))
2038//             .await
2039//             .unwrap()
2040//             .id();
2041//         assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
2042
2043//         // Close all the pane items in some arbitrary order.
2044//         pane.update(cx, |pane, cx| {
2045//             pane.close_item_by_id(file1_item_id, SaveIntent::Close, cx)
2046//         })
2047//         .await
2048//         .unwrap();
2049//         assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
2050
2051//         pane.update(cx, |pane, cx| {
2052//             pane.close_item_by_id(file4_item_id, SaveIntent::Close, cx)
2053//         })
2054//         .await
2055//         .unwrap();
2056//         assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
2057
2058//         pane.update(cx, |pane, cx| {
2059//             pane.close_item_by_id(file2_item_id, SaveIntent::Close, cx)
2060//         })
2061//         .await
2062//         .unwrap();
2063//         assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
2064
2065//         pane.update(cx, |pane, cx| {
2066//             pane.close_item_by_id(file3_item_id, SaveIntent::Close, cx)
2067//         })
2068//         .await
2069//         .unwrap();
2070//         assert_eq!(active_path(&workspace, cx), None);
2071
2072//         // Reopen all the closed items, ensuring they are reopened in the same order
2073//         // in which they were closed.
2074//         workspace
2075//             .update(cx, Workspace::reopen_closed_item)
2076//             .await
2077//             .unwrap();
2078//         assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
2079
2080//         workspace
2081//             .update(cx, Workspace::reopen_closed_item)
2082//             .await
2083//             .unwrap();
2084//         assert_eq!(active_path(&workspace, cx), Some(file2.clone()));
2085
2086//         workspace
2087//             .update(cx, Workspace::reopen_closed_item)
2088//             .await
2089//             .unwrap();
2090//         assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
2091
2092//         workspace
2093//             .update(cx, Workspace::reopen_closed_item)
2094//             .await
2095//             .unwrap();
2096//         assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
2097
2098//         // Reopening past the last closed item is a no-op.
2099//         workspace
2100//             .update(cx, Workspace::reopen_closed_item)
2101//             .await
2102//             .unwrap();
2103//         assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
2104
2105//         // Reopening closed items doesn't interfere with navigation history.
2106//         workspace
2107//             .update(cx, |workspace, cx| {
2108//                 workspace.go_back(workspace.active_pane().downgrade(), cx)
2109//             })
2110//             .await
2111//             .unwrap();
2112//         assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
2113
2114//         workspace
2115//             .update(cx, |workspace, cx| {
2116//                 workspace.go_back(workspace.active_pane().downgrade(), cx)
2117//             })
2118//             .await
2119//             .unwrap();
2120//         assert_eq!(active_path(&workspace, cx), Some(file2.clone()));
2121
2122//         workspace
2123//             .update(cx, |workspace, cx| {
2124//                 workspace.go_back(workspace.active_pane().downgrade(), cx)
2125//             })
2126//             .await
2127//             .unwrap();
2128//         assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
2129
2130//         workspace
2131//             .update(cx, |workspace, cx| {
2132//                 workspace.go_back(workspace.active_pane().downgrade(), cx)
2133//             })
2134//             .await
2135//             .unwrap();
2136//         assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
2137
2138//         workspace
2139//             .update(cx, |workspace, cx| {
2140//                 workspace.go_back(workspace.active_pane().downgrade(), cx)
2141//             })
2142//             .await
2143//             .unwrap();
2144//         assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
2145
2146//         workspace
2147//             .update(cx, |workspace, cx| {
2148//                 workspace.go_back(workspace.active_pane().downgrade(), cx)
2149//             })
2150//             .await
2151//             .unwrap();
2152//         assert_eq!(active_path(&workspace, cx), Some(file2.clone()));
2153
2154//         workspace
2155//             .update(cx, |workspace, cx| {
2156//                 workspace.go_back(workspace.active_pane().downgrade(), cx)
2157//             })
2158//             .await
2159//             .unwrap();
2160//         assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
2161
2162//         workspace
2163//             .update(cx, |workspace, cx| {
2164//                 workspace.go_back(workspace.active_pane().downgrade(), cx)
2165//             })
2166//             .await
2167//             .unwrap();
2168//         assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
2169
2170//         fn active_path(
2171//             workspace: &ViewHandle<Workspace>,
2172//             cx: &TestAppContext,
2173//         ) -> Option<ProjectPath> {
2174//             workspace.read_with(cx, |workspace, cx| {
2175//                 let item = workspace.active_item(cx)?;
2176//                 item.project_path(cx)
2177//             })
2178//         }
2179//     }
2180
2181//     #[gpui::test]
2182//     async fn test_base_keymap(cx: &mut gpui::TestAppContext) {
2183//         struct TestView;
2184
2185//         impl Entity for TestView {
2186//             type Event = ();
2187//         }
2188
2189//         impl View for TestView {
2190//             fn ui_name() -> &'static str {
2191//                 "TestView"
2192//             }
2193
2194//             fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
2195//                 Empty::new().into_any()
2196//             }
2197//         }
2198
2199//         let executor = cx.background();
2200//         let fs = FakeFs::new(executor.clone());
2201
2202//         actions!(test, [A, B]);
2203//         // From the Atom keymap
2204//         actions!(workspace, [ActivatePreviousPane]);
2205//         // From the JetBrains keymap
2206//         actions!(pane, [ActivatePrevItem]);
2207
2208//         fs.save(
2209//             "/settings.json".as_ref(),
2210//             &r#"
2211//             {
2212//                 "base_keymap": "Atom"
2213//             }
2214//             "#
2215//             .into(),
2216//             Default::default(),
2217//         )
2218//         .await
2219//         .unwrap();
2220
2221//         fs.save(
2222//             "/keymap.json".as_ref(),
2223//             &r#"
2224//             [
2225//                 {
2226//                     "bindings": {
2227//                         "backspace": "test::A"
2228//                     }
2229//                 }
2230//             ]
2231//             "#
2232//             .into(),
2233//             Default::default(),
2234//         )
2235//         .await
2236//         .unwrap();
2237
2238//         cx.update(|cx| {
2239//             cx.set_global(SettingsStore::test(cx));
2240//             theme::init(Assets, cx);
2241//             welcome::init(cx);
2242
2243//             cx.add_global_action(|_: &A, _cx| {});
2244//             cx.add_global_action(|_: &B, _cx| {});
2245//             cx.add_global_action(|_: &ActivatePreviousPane, _cx| {});
2246//             cx.add_global_action(|_: &ActivatePrevItem, _cx| {});
2247
2248//             let settings_rx = watch_config_file(
2249//                 executor.clone(),
2250//                 fs.clone(),
2251//                 PathBuf::from("/settings.json"),
2252//             );
2253//             let keymap_rx =
2254//                 watch_config_file(executor.clone(), fs.clone(), PathBuf::from("/keymap.json"));
2255
2256//             handle_keymap_file_changes(keymap_rx, cx);
2257//             handle_settings_file_changes(settings_rx, cx);
2258//         });
2259
2260//         cx.foreground().run_until_parked();
2261
2262//         let window = cx.add_window(|_| TestView);
2263
2264//         // Test loading the keymap base at all
2265//         assert_key_bindings_for(
2266//             window.into(),
2267//             cx,
2268//             vec![("backspace", &A), ("k", &ActivatePreviousPane)],
2269//             line!(),
2270//         );
2271
2272//         // Test modifying the users keymap, while retaining the base keymap
2273//         fs.save(
2274//             "/keymap.json".as_ref(),
2275//             &r#"
2276//             [
2277//                 {
2278//                     "bindings": {
2279//                         "backspace": "test::B"
2280//                     }
2281//                 }
2282//             ]
2283//             "#
2284//             .into(),
2285//             Default::default(),
2286//         )
2287//         .await
2288//         .unwrap();
2289
2290//         cx.foreground().run_until_parked();
2291
2292//         assert_key_bindings_for(
2293//             window.into(),
2294//             cx,
2295//             vec![("backspace", &B), ("k", &ActivatePreviousPane)],
2296//             line!(),
2297//         );
2298
2299//         // Test modifying the base, while retaining the users keymap
2300//         fs.save(
2301//             "/settings.json".as_ref(),
2302//             &r#"
2303//             {
2304//                 "base_keymap": "JetBrains"
2305//             }
2306//             "#
2307//             .into(),
2308//             Default::default(),
2309//         )
2310//         .await
2311//         .unwrap();
2312
2313//         cx.foreground().run_until_parked();
2314
2315//         assert_key_bindings_for(
2316//             window.into(),
2317//             cx,
2318//             vec![("backspace", &B), ("[", &ActivatePrevItem)],
2319//             line!(),
2320//         );
2321
2322//         #[track_caller]
2323//         fn assert_key_bindings_for<'a>(
2324//             window: AnyWindowHandle,
2325//             cx: &TestAppContext,
2326//             actions: Vec<(&'static str, &'a dyn Action)>,
2327//             line: u32,
2328//         ) {
2329//             for (key, action) in actions {
2330//                 // assert that...
2331//                 assert!(
2332//                     cx.available_actions(window, 0)
2333//                         .into_iter()
2334//                         .any(|(_, bound_action, b)| {
2335//                             // action names match...
2336//                             bound_action.name() == action.name()
2337//                         && bound_action.namespace() == action.namespace()
2338//                         // and key strokes contain the given key
2339//                         && b.iter()
2340//                             .any(|binding| binding.keystrokes().iter().any(|k| k.key == key))
2341//                         }),
2342//                     "On {} Failed to find {} with key binding {}",
2343//                     line,
2344//                     action.name(),
2345//                     key
2346//                 );
2347//             }
2348//         }
2349//     }
2350
2351//     #[gpui::test]
2352//     async fn test_disabled_keymap_binding(cx: &mut gpui::TestAppContext) {
2353//         struct TestView;
2354
2355//         impl Entity for TestView {
2356//             type Event = ();
2357//         }
2358
2359//         impl View for TestView {
2360//             fn ui_name() -> &'static str {
2361//                 "TestView"
2362//             }
2363
2364//             fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
2365//                 Empty::new().into_any()
2366//             }
2367//         }
2368
2369//         let executor = cx.background();
2370//         let fs = FakeFs::new(executor.clone());
2371
2372//         actions!(test, [A, B]);
2373//         // From the Atom keymap
2374//         actions!(workspace, [ActivatePreviousPane]);
2375//         // From the JetBrains keymap
2376//         actions!(pane, [ActivatePrevItem]);
2377
2378//         fs.save(
2379//             "/settings.json".as_ref(),
2380//             &r#"
2381//             {
2382//                 "base_keymap": "Atom"
2383//             }
2384//             "#
2385//             .into(),
2386//             Default::default(),
2387//         )
2388//         .await
2389//         .unwrap();
2390
2391//         fs.save(
2392//             "/keymap.json".as_ref(),
2393//             &r#"
2394//             [
2395//                 {
2396//                     "bindings": {
2397//                         "backspace": "test::A"
2398//                     }
2399//                 }
2400//             ]
2401//             "#
2402//             .into(),
2403//             Default::default(),
2404//         )
2405//         .await
2406//         .unwrap();
2407
2408//         cx.update(|cx| {
2409//             cx.set_global(SettingsStore::test(cx));
2410//             theme::init(Assets, cx);
2411//             welcome::init(cx);
2412
2413//             cx.add_global_action(|_: &A, _cx| {});
2414//             cx.add_global_action(|_: &B, _cx| {});
2415//             cx.add_global_action(|_: &ActivatePreviousPane, _cx| {});
2416//             cx.add_global_action(|_: &ActivatePrevItem, _cx| {});
2417
2418//             let settings_rx = watch_config_file(
2419//                 executor.clone(),
2420//                 fs.clone(),
2421//                 PathBuf::from("/settings.json"),
2422//             );
2423//             let keymap_rx =
2424//                 watch_config_file(executor.clone(), fs.clone(), PathBuf::from("/keymap.json"));
2425
2426//             handle_keymap_file_changes(keymap_rx, cx);
2427//             handle_settings_file_changes(settings_rx, cx);
2428//         });
2429
2430//         cx.foreground().run_until_parked();
2431
2432//         let window = cx.add_window(|_| TestView);
2433
2434//         // Test loading the keymap base at all
2435//         assert_key_bindings_for(
2436//             window.into(),
2437//             cx,
2438//             vec![("backspace", &A), ("k", &ActivatePreviousPane)],
2439//             line!(),
2440//         );
2441
2442//         // Test disabling the key binding for the base keymap
2443//         fs.save(
2444//             "/keymap.json".as_ref(),
2445//             &r#"
2446//             [
2447//                 {
2448//                     "bindings": {
2449//                         "backspace": null
2450//                     }
2451//                 }
2452//             ]
2453//             "#
2454//             .into(),
2455//             Default::default(),
2456//         )
2457//         .await
2458//         .unwrap();
2459
2460//         cx.foreground().run_until_parked();
2461
2462//         assert_key_bindings_for(
2463//             window.into(),
2464//             cx,
2465//             vec![("k", &ActivatePreviousPane)],
2466//             line!(),
2467//         );
2468
2469//         // Test modifying the base, while retaining the users keymap
2470//         fs.save(
2471//             "/settings.json".as_ref(),
2472//             &r#"
2473//             {
2474//                 "base_keymap": "JetBrains"
2475//             }
2476//             "#
2477//             .into(),
2478//             Default::default(),
2479//         )
2480//         .await
2481//         .unwrap();
2482
2483//         cx.foreground().run_until_parked();
2484
2485//         assert_key_bindings_for(window.into(), cx, vec![("[", &ActivatePrevItem)], line!());
2486
2487//         #[track_caller]
2488//         fn assert_key_bindings_for<'a>(
2489//             window: AnyWindowHandle,
2490//             cx: &TestAppContext,
2491//             actions: Vec<(&'static str, &'a dyn Action)>,
2492//             line: u32,
2493//         ) {
2494//             for (key, action) in actions {
2495//                 // assert that...
2496//                 assert!(
2497//                     cx.available_actions(window, 0)
2498//                         .into_iter()
2499//                         .any(|(_, bound_action, b)| {
2500//                             // action names match...
2501//                             bound_action.name() == action.name()
2502//                         && bound_action.namespace() == action.namespace()
2503//                         // and key strokes contain the given key
2504//                         && b.iter()
2505//                             .any(|binding| binding.keystrokes().iter().any(|k| k.key == key))
2506//                         }),
2507//                     "On {} Failed to find {} with key binding {}",
2508//                     line,
2509//                     action.name(),
2510//                     key
2511//                 );
2512//             }
2513//         }
2514//     }
2515
2516//     #[gpui::test]
2517//     fn test_bundled_settings_and_themes(cx: &mut AppContext) {
2518//         cx.platform()
2519//             .fonts()
2520//             .add_fonts(&[
2521//                 Assets
2522//                     .load("fonts/zed-sans/zed-sans-extended.ttf")
2523//                     .unwrap()
2524//                     .to_vec()
2525//                     .into(),
2526//                 Assets
2527//                     .load("fonts/zed-mono/zed-mono-extended.ttf")
2528//                     .unwrap()
2529//                     .to_vec()
2530//                     .into(),
2531//                 Assets
2532//                     .load("fonts/plex/IBMPlexSans-Regular.ttf")
2533//                     .unwrap()
2534//                     .to_vec()
2535//                     .into(),
2536//             ])
2537//             .unwrap();
2538//         let themes = ThemeRegistry::new(Assets, cx.font_cache().clone());
2539//         let mut settings = SettingsStore::default();
2540//         settings
2541//             .set_default_settings(&settings::default_settings(), cx)
2542//             .unwrap();
2543//         cx.set_global(settings);
2544//         theme::init(Assets, cx);
2545
2546//         let mut has_default_theme = false;
2547//         for theme_name in themes.list(false).map(|meta| meta.name) {
2548//             let theme = themes.get(&theme_name).unwrap();
2549//             assert_eq!(theme.meta.name, theme_name);
2550//             if theme.meta.name == settings::get::<ThemeSettings>(cx).theme.meta.name {
2551//                 has_default_theme = true;
2552//             }
2553//         }
2554//         assert!(has_default_theme);
2555//     }
2556
2557//     #[gpui::test]
2558//     fn test_bundled_languages(cx: &mut AppContext) {
2559//         cx.set_global(SettingsStore::test(cx));
2560//         let mut languages = LanguageRegistry::test();
2561//         languages.set_executor(cx.background().clone());
2562//         let languages = Arc::new(languages);
2563//         let node_runtime = node_runtime::FakeNodeRuntime::new();
2564//         languages::init(languages.clone(), node_runtime, cx);
2565//         for name in languages.language_names() {
2566//             languages.language_for_name(&name);
2567//         }
2568//         cx.foreground().run_until_parked();
2569//     }
2570
2571//     fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
2572//         cx.foreground().forbid_parking();
2573//         cx.update(|cx| {
2574//             let mut app_state = AppState::test(cx);
2575//             let state = Arc::get_mut(&mut app_state).unwrap();
2576//             state.initialize_workspace = initialize_workspace;
2577//             state.build_window_options = build_window_options;
2578//             theme::init((), cx);
2579//             audio::init((), cx);
2580//             channel::init(&app_state.client, app_state.user_store.clone(), cx);
2581//             call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
2582//             notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx);
2583//             workspace::init(app_state.clone(), cx);
2584//             Project::init_settings(cx);
2585//             language::init(cx);
2586//             editor::init(cx);
2587//             project_panel::init_settings(cx);
2588//             collab_ui::init(&app_state, cx);
2589//             pane::init(cx);
2590//             project_panel::init((), cx);
2591//             terminal_view::init(cx);
2592//             assistant::init(cx);
2593//             app_state
2594//         })
2595//     }
2596
2597//     fn rust_lang() -> Arc<language::Language> {
2598//         Arc::new(language::Language::new(
2599//             language::LanguageConfig {
2600//                 name: "Rust".into(),
2601//                 path_suffixes: vec!["rs".to_string()],
2602//                 ..Default::default()
2603//             },
2604//             Some(tree_sitter_rust::language()),
2605//         ))
2606//     }
2607// }