zed2.rs

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