4721 lines · 170.2 KB · Rust
RawDownload
use crate::{    item::{        ActivateOnClose, ClosePosition, Item, ItemHandle, ItemSettings, PreviewTabsSettings,        ShowDiagnostics, TabContentParams, TabTooltipContent, WeakItemHandle,    },    move_item,    notifications::NotifyResultExt,    toolbar::Toolbar,    workspace_settings::{AutosaveSetting, TabBarSettings, WorkspaceSettings},    CloseWindow, NewFile, NewTerminal, OpenInTerminal, OpenTerminal, OpenVisible, SplitDirection,    ToggleFileFinder, ToggleProjectSymbols, ToggleZoom, Workspace,};use anyhow::Result;use collections::{BTreeSet, HashMap, HashSet, VecDeque};use futures::{stream::FuturesUnordered, StreamExt};use gpui::{    actions, anchored, deferred, impl_actions, prelude::*, Action, AnyElement, App,    AsyncWindowContext, ClickEvent, ClipboardItem, Context, Corner, Div, DragMoveEvent, Entity,    EntityId, EventEmitter, ExternalPaths, FocusHandle, FocusOutEvent, Focusable, KeyContext,    MouseButton, MouseDownEvent, NavigationDirection, Pixels, Point, PromptLevel, Render,    ScrollHandle, Subscription, Task, WeakEntity, WeakFocusHandle, Window,};use itertools::Itertools;use language::DiagnosticSeverity;use parking_lot::Mutex;use project::{Project, ProjectEntryId, ProjectPath, WorktreeId};use schemars::JsonSchema;use serde::Deserialize;use settings::{Settings, SettingsStore};use std::{    any::Any,    cmp, fmt, mem,    ops::ControlFlow,    path::PathBuf,    rc::Rc,    sync::{        atomic::{AtomicUsize, Ordering},        Arc,    },};use theme::ThemeSettings;use ui::{    prelude::*, right_click_menu, ButtonSize, Color, ContextMenu, ContextMenuEntry,    ContextMenuItem, DecoratedIcon, IconButton, IconButtonShape, IconDecoration,    IconDecorationKind, IconName, IconSize, Indicator, Label, PopoverMenu, PopoverMenuHandle, Tab,    TabBar, TabPosition, Tooltip,};use util::{debug_panic, maybe, truncate_and_remove_front, ResultExt}; /// A selected entry in e.g. project panel.#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]pub struct SelectedEntry {    pub worktree_id: WorktreeId,    pub entry_id: ProjectEntryId,} /// A group of selected entries from project panel.#[derive(Debug)]pub struct DraggedSelection {    pub active_selection: SelectedEntry,    pub marked_selections: Arc<BTreeSet<SelectedEntry>>,} impl DraggedSelection {    pub fn items<'a>(&'a self) -> Box<dyn Iterator<Item = &'a SelectedEntry> + 'a> {        if self.marked_selections.contains(&self.active_selection) {            Box::new(self.marked_selections.iter())        } else {            Box::new(std::iter::once(&self.active_selection))        }    }} #[derive(Clone, Copy, PartialEq, Debug, Deserialize, JsonSchema)]#[serde(rename_all = "snake_case")]pub enum SaveIntent {    /// write all files (even if unchanged)    /// prompt before overwriting on-disk changes    Save,    /// same as Save, but without auto formatting    SaveWithoutFormat,    /// write any files that have local changes    /// prompt before overwriting on-disk changes    SaveAll,    /// always prompt for a new path    SaveAs,    /// prompt "you have unsaved changes" before writing    Close,    /// write all dirty files, don't prompt on conflict    Overwrite,    /// skip all save-related behavior    Skip,} #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]pub struct ActivateItem(pub usize); #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]#[serde(deny_unknown_fields)]pub struct CloseActiveItem {    pub save_intent: Option<SaveIntent>,    #[serde(default)]    pub close_pinned: bool,} #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]#[serde(deny_unknown_fields)]pub struct CloseInactiveItems {    pub save_intent: Option<SaveIntent>,    #[serde(default)]    pub close_pinned: bool,} #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]#[serde(deny_unknown_fields)]pub struct CloseAllItems {    pub save_intent: Option<SaveIntent>,    #[serde(default)]    pub close_pinned: bool,} #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]#[serde(deny_unknown_fields)]pub struct CloseCleanItems {    #[serde(default)]    pub close_pinned: bool,} #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]#[serde(deny_unknown_fields)]pub struct CloseItemsToTheRight {    #[serde(default)]    pub close_pinned: bool,} #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]#[serde(deny_unknown_fields)]pub struct CloseItemsToTheLeft {    #[serde(default)]    pub close_pinned: bool,} #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]#[serde(deny_unknown_fields)]pub struct RevealInProjectPanel {    #[serde(skip)]    pub entry_id: Option<u64>,} #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]#[serde(deny_unknown_fields)]pub struct DeploySearch {    #[serde(default)]    pub replace_enabled: bool,} impl_actions!(    pane,    [        CloseAllItems,        CloseActiveItem,        CloseCleanItems,        CloseItemsToTheLeft,        CloseItemsToTheRight,        CloseInactiveItems,        ActivateItem,        RevealInProjectPanel,        DeploySearch,    ]); actions!(    pane,    [        ActivatePrevItem,        ActivateNextItem,        ActivateLastItem,        AlternateFile,        GoBack,        GoForward,        JoinIntoNext,        JoinAll,        ReopenClosedItem,        SplitLeft,        SplitUp,        SplitRight,        SplitDown,        SplitHorizontal,        SplitVertical,        SwapItemLeft,        SwapItemRight,        TogglePreviewTab,        TogglePinTab,    ]); impl DeploySearch {    pub fn find() -> Self {        Self {            replace_enabled: false,        }    }} const MAX_NAVIGATION_HISTORY_LEN: usize = 1024; pub enum Event {    AddItem {        item: Box<dyn ItemHandle>,    },    ActivateItem {        local: bool,        focus_changed: bool,    },    Remove {        focus_on_pane: Option<Entity<Pane>>,    },    RemoveItem {        idx: usize,    },    RemovedItem {        item_id: EntityId,    },    Split(SplitDirection),    JoinAll,    JoinIntoNext,    ChangeItemTitle,    Focus,    ZoomIn,    ZoomOut,    UserSavedItem {        item: Box<dyn WeakItemHandle>,        save_intent: SaveIntent,    },} impl fmt::Debug for Event {    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {        match self {            Event::AddItem { item } => f                .debug_struct("AddItem")                .field("item", &item.item_id())                .finish(),            Event::ActivateItem { local, .. } => f                .debug_struct("ActivateItem")                .field("local", local)                .finish(),            Event::Remove { .. } => f.write_str("Remove"),            Event::RemoveItem { idx } => f.debug_struct("RemoveItem").field("idx", idx).finish(),            Event::RemovedItem { item_id } => f                .debug_struct("RemovedItem")                .field("item_id", item_id)                .finish(),            Event::Split(direction) => f                .debug_struct("Split")                .field("direction", direction)                .finish(),            Event::JoinAll => f.write_str("JoinAll"),            Event::JoinIntoNext => f.write_str("JoinIntoNext"),            Event::ChangeItemTitle => f.write_str("ChangeItemTitle"),            Event::Focus => f.write_str("Focus"),            Event::ZoomIn => f.write_str("ZoomIn"),            Event::ZoomOut => f.write_str("ZoomOut"),            Event::UserSavedItem { item, save_intent } => f                .debug_struct("UserSavedItem")                .field("item", &item.id())                .field("save_intent", save_intent)                .finish(),        }    }} /// A container for 0 to many items that are open in the workspace./// Treats all items uniformly via the [`ItemHandle`] trait, whether it's an editor, search results multibuffer, terminal or something else,/// responsible for managing item tabs, focus and zoom states and drag and drop features./// Can be split, see `PaneGroup` for more details.pub struct Pane {    alternate_file_items: (        Option<Box<dyn WeakItemHandle>>,        Option<Box<dyn WeakItemHandle>>,    ),    focus_handle: FocusHandle,    items: Vec<Box<dyn ItemHandle>>,    activation_history: Vec<ActivationHistoryEntry>,    next_activation_timestamp: Arc<AtomicUsize>,    zoomed: bool,    was_focused: bool,    active_item_index: usize,    preview_item_id: Option<EntityId>,    last_focus_handle_by_item: HashMap<EntityId, WeakFocusHandle>,    nav_history: NavHistory,    toolbar: Entity<Toolbar>,    pub(crate) workspace: WeakEntity<Workspace>,    project: WeakEntity<Project>,    drag_split_direction: Option<SplitDirection>,    can_drop_predicate: Option<Arc<dyn Fn(&dyn Any, &mut Window, &mut App) -> bool>>,    custom_drop_handle: Option<        Arc<dyn Fn(&mut Pane, &dyn Any, &mut Window, &mut Context<Pane>) -> ControlFlow<(), ()>>,    >,    can_split_predicate:        Option<Arc<dyn Fn(&mut Self, &dyn Any, &mut Window, &mut Context<Self>) -> bool>>,    should_display_tab_bar: Rc<dyn Fn(&Window, &mut Context<Pane>) -> bool>,    render_tab_bar_buttons: Rc<        dyn Fn(            &mut Pane,            &mut Window,            &mut Context<Pane>,        ) -> (Option<AnyElement>, Option<AnyElement>),    >,    show_tab_bar_buttons: bool,    _subscriptions: Vec<Subscription>,    tab_bar_scroll_handle: ScrollHandle,    /// Is None if navigation buttons are permanently turned off (and should not react to setting changes).    /// Otherwise, when `display_nav_history_buttons` is Some, it determines whether nav buttons should be displayed.    display_nav_history_buttons: Option<bool>,    double_click_dispatch_action: Box<dyn Action>,    save_modals_spawned: HashSet<EntityId>,    pub new_item_context_menu_handle: PopoverMenuHandle<ContextMenu>,    pub split_item_context_menu_handle: PopoverMenuHandle<ContextMenu>,    pinned_tab_count: usize,    diagnostics: HashMap<ProjectPath, DiagnosticSeverity>,    zoom_out_on_close: bool,} pub struct ActivationHistoryEntry {    pub entity_id: EntityId,    pub timestamp: usize,} pub struct ItemNavHistory {    history: NavHistory,    item: Arc<dyn WeakItemHandle>,    is_preview: bool,} #[derive(Clone)]pub struct NavHistory(Arc<Mutex<NavHistoryState>>); struct NavHistoryState {    mode: NavigationMode,    backward_stack: VecDeque<NavigationEntry>,    forward_stack: VecDeque<NavigationEntry>,    closed_stack: VecDeque<NavigationEntry>,    paths_by_item: HashMap<EntityId, (ProjectPath, Option<PathBuf>)>,    pane: WeakEntity<Pane>,    next_timestamp: Arc<AtomicUsize>,} #[derive(Debug, Copy, Clone)]pub enum NavigationMode {    Normal,    GoingBack,    GoingForward,    ClosingItem,    ReopeningClosedItem,    Disabled,} impl Default for NavigationMode {    fn default() -> Self {        Self::Normal    }} pub struct NavigationEntry {    pub item: Arc<dyn WeakItemHandle>,    pub data: Option<Box<dyn Any + Send>>,    pub timestamp: usize,    pub is_preview: bool,} #[derive(Clone)]pub struct DraggedTab {    pub pane: Entity<Pane>,    pub item: Box<dyn ItemHandle>,    pub ix: usize,    pub detail: usize,    pub is_active: bool,} impl EventEmitter<Event> for Pane {} impl Pane {    pub fn new(        workspace: WeakEntity<Workspace>,        project: Entity<Project>,        next_timestamp: Arc<AtomicUsize>,        can_drop_predicate: Option<Arc<dyn Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static>>,        double_click_dispatch_action: Box<dyn Action>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Self {        let focus_handle = cx.focus_handle();         let subscriptions = vec![            cx.on_focus(&focus_handle, window, Pane::focus_in),            cx.on_focus_in(&focus_handle, window, Pane::focus_in),            cx.on_focus_out(&focus_handle, window, Pane::focus_out),            cx.observe_global::<SettingsStore>(Self::settings_changed),            cx.subscribe(&project, Self::project_events),        ];         let handle = cx.entity().downgrade();        Self {            alternate_file_items: (None, None),            focus_handle,            items: Vec::new(),            activation_history: Vec::new(),            next_activation_timestamp: next_timestamp.clone(),            was_focused: false,            zoomed: false,            active_item_index: 0,            preview_item_id: None,            last_focus_handle_by_item: Default::default(),            nav_history: NavHistory(Arc::new(Mutex::new(NavHistoryState {                mode: NavigationMode::Normal,                backward_stack: Default::default(),                forward_stack: Default::default(),                closed_stack: Default::default(),                paths_by_item: Default::default(),                pane: handle.clone(),                next_timestamp,            }))),            toolbar: cx.new(|_| Toolbar::new()),            tab_bar_scroll_handle: ScrollHandle::new(),            drag_split_direction: None,            workspace,            project: project.downgrade(),            can_drop_predicate,            custom_drop_handle: None,            can_split_predicate: None,            should_display_tab_bar: Rc::new(|_, cx| TabBarSettings::get_global(cx).show),            render_tab_bar_buttons: Rc::new(move |pane, window, cx| {                if !pane.has_focus(window, cx) && !pane.context_menu_focused(window, cx) {                    return (None, None);                }                // Ideally we would return a vec of elements here to pass directly to the [TabBar]'s                // `end_slot`, but due to needing a view here that isn't possible.                let right_children = h_flex()                    // Instead we need to replicate the spacing from the [TabBar]'s `end_slot` here.                    .gap(DynamicSpacing::Base04.rems(cx))                    .child(                        PopoverMenu::new("pane-tab-bar-popover-menu")                            .trigger_with_tooltip(                                IconButton::new("plus", IconName::Plus).icon_size(IconSize::Small),                                Tooltip::text("New..."),                            )                            .anchor(Corner::TopRight)                            .with_handle(pane.new_item_context_menu_handle.clone())                            .menu(move |window, cx| {                                Some(ContextMenu::build(window, cx, |menu, _, _| {                                    menu.action("New File", NewFile.boxed_clone())                                        .action(                                            "Open File",                                            ToggleFileFinder::default().boxed_clone(),                                        )                                        .separator()                                        .action(                                            "Search Project",                                            DeploySearch {                                                replace_enabled: false,                                            }                                            .boxed_clone(),                                        )                                        .action(                                            "Search Symbols",                                            ToggleProjectSymbols.boxed_clone(),                                        )                                        .separator()                                        .action("New Terminal", NewTerminal.boxed_clone())                                }))                            }),                    )                    .child(                        PopoverMenu::new("pane-tab-bar-split")                            .trigger_with_tooltip(                                IconButton::new("split", IconName::Split)                                    .icon_size(IconSize::Small),                                Tooltip::text("Split Pane"),                            )                            .anchor(Corner::TopRight)                            .with_handle(pane.split_item_context_menu_handle.clone())                            .menu(move |window, cx| {                                ContextMenu::build(window, cx, |menu, _, _| {                                    menu.action("Split Right", SplitRight.boxed_clone())                                        .action("Split Left", SplitLeft.boxed_clone())                                        .action("Split Up", SplitUp.boxed_clone())                                        .action("Split Down", SplitDown.boxed_clone())                                })                                .into()                            }),                    )                    .child({                        let zoomed = pane.is_zoomed();                        IconButton::new("toggle_zoom", IconName::Maximize)                            .icon_size(IconSize::Small)                            .toggle_state(zoomed)                            .selected_icon(IconName::Minimize)                            .on_click(cx.listener(|pane, _, window, cx| {                                pane.toggle_zoom(&crate::ToggleZoom, window, cx);

Lines 1–500 of 4721.