6335 lines · 231.3 KB · Rust
RawDownload
use crate::{    CloseWindow, NewFile, NewTerminal, OpenInTerminal, OpenOptions, OpenTerminal, OpenVisible,    SplitDirection, ToggleFileFinder, ToggleProjectSymbols, ToggleZoom, Workspace,    WorkspaceItemBuilder,    item::{        ActivateOnClose, ClosePosition, Item, ItemHandle, ItemSettings, PreviewTabsSettings,        ProjectItemKind, SaveOptions, ShowCloseButton, ShowDiagnostics, TabContentParams,        TabTooltipContent, WeakItemHandle,    },    move_item,    notifications::NotifyResultExt,    toolbar::Toolbar,    workspace_settings::{AutosaveSetting, TabBarSettings, WorkspaceSettings},};use anyhow::Result;use collections::{BTreeSet, HashMap, HashSet, VecDeque};use futures::{StreamExt, stream::FuturesUnordered};use gpui::{    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,    actions, anchored, deferred, prelude::*,};use itertools::Itertools;use language::DiagnosticSeverity;use parking_lot::Mutex;use project::{DirectoryLister, Project, ProjectEntryId, ProjectPath, WorktreeId};use schemars::JsonSchema;use serde::Deserialize;use settings::{Settings, SettingsStore};use std::{    any::Any,    cmp, fmt, mem,    num::NonZeroUsize,    ops::ControlFlow,    path::PathBuf,    rc::Rc,    sync::{        Arc,        atomic::{AtomicUsize, Ordering},    },};use theme::ThemeSettings;use ui::{    ButtonSize, Color, ContextMenu, ContextMenuEntry, ContextMenuItem, DecoratedIcon, IconButton,    IconButtonShape, IconDecoration, IconDecorationKind, IconName, IconSize, Indicator, Label,    PopoverMenu, PopoverMenuHandle, ScrollableHandle, Tab, TabBar, TabPosition, Tooltip,    prelude::*, right_click_menu,};use util::{ResultExt, debug_panic, maybe, truncate_and_remove_front}; /// 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, Action)]#[action(namespace = pane)]pub struct ActivateItem(pub usize); #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default, Action)]#[action(namespace = pane)]#[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, Action)]#[action(namespace = pane)]#[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, Action)]#[action(namespace = pane)]#[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, Action)]#[action(namespace = pane)]#[serde(deny_unknown_fields)]pub struct CloseCleanItems {    #[serde(default)]    pub close_pinned: bool,} #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default, Action)]#[action(namespace = pane)]#[serde(deny_unknown_fields)]pub struct CloseItemsToTheRight {    #[serde(default)]    pub close_pinned: bool,} #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default, Action)]#[action(namespace = pane)]#[serde(deny_unknown_fields)]pub struct CloseItemsToTheLeft {    #[serde(default)]    pub close_pinned: bool,} #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default, Action)]#[action(namespace = pane)]#[serde(deny_unknown_fields)]pub struct RevealInProjectPanel {    #[serde(skip)]    pub entry_id: Option<u64>,} #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default, Action)]#[action(namespace = pane)]#[serde(deny_unknown_fields)]pub struct DeploySearch {    #[serde(default)]    pub replace_enabled: bool,    #[serde(default)]    pub included_files: Option<String>,    #[serde(default)]    pub excluded_files: Option<String>,} actions!(    pane,    [        ActivatePreviousItem,        ActivateNextItem,        ActivateLastItem,        AlternateFile,        GoBack,        GoForward,        JoinIntoNext,        JoinAll,        ReopenClosedItem,        SplitLeft,        SplitUp,        SplitRight,        SplitDown,        SplitHorizontal,        SplitVertical,        SwapItemLeft,        SwapItemRight,        TogglePreviewTab,        TogglePinTab,        UnpinAllTabs,    ]); impl DeploySearch {    pub fn find() -> Self {        Self {            replace_enabled: false,            included_files: None,            excluded_files: None,        }    }} 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: Box<dyn ItemHandle>,    },    Split(SplitDirection),    ItemPinned,    ItemUnpinned,    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 } => f                .debug_struct("RemovedItem")                .field("item", &item.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(),            Event::ItemPinned => f.write_str("ItemPinned"),            Event::ItemUnpinned => f.write_str("ItemUnpinned"),        }    }} /// 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>,    pub 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>>,    can_toggle_zoom: 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>),    >,    render_tab_bar: Rc<dyn Fn(&mut Pane, &mut Window, &mut Context<Pane>) -> AnyElement>,    show_tab_bar_buttons: bool,    max_tabs: Option<NonZeroUsize>,    _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>,    close_pane_if_empty: bool,    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,    /// If a certain project item wants to get recreated with specific data, it can persist its data before the recreation here.    pub project_item_restoration_data: HashMap<ProjectItemKind, Box<dyn Any + Send>>,} 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 {} pub enum Side {    Left,    Right,} #[derive(Copy, Clone)]enum PinOperation {    Pin,    Unpin,} 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_in::<SettingsStore>(window, 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,            max_tabs: WorkspaceSettings::get_global(cx).max_tabs,            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,            can_toggle_zoom: true,            should_display_tab_bar: Rc::new(|_, cx| TabBarSettings::get_global(cx).show),            render_tab_bar_buttons: Rc::new(default_render_tab_bar_buttons),            render_tab_bar: Rc::new(Self::render_tab_bar),            show_tab_bar_buttons: TabBarSettings::get_global(cx).show_tab_bar_buttons,            display_nav_history_buttons: Some(                TabBarSettings::get_global(cx).show_nav_history_buttons,            ),            _subscriptions: subscriptions,            double_click_dispatch_action,            save_modals_spawned: HashSet::default(),            close_pane_if_empty: true,            split_item_context_menu_handle: Default::default(),            new_item_context_menu_handle: Default::default(),            pinned_tab_count: 0,            diagnostics: Default::default(),            zoom_out_on_close: true,            project_item_restoration_data: HashMap::default(),        }    }     fn alternate_file(&mut self, window: &mut Window, cx: &mut Context<Pane>) {        let (_, alternative) = &self.alternate_file_items;        if let Some(alternative) = alternative {            let existing = self                .items()                .find_position(|item| item.item_id() == alternative.id());            if let Some((ix, _)) = existing {                self.activate_item(ix, true, true, window, cx);            } else if let Some(upgraded) = alternative.upgrade() {                self.add_item(upgraded, true, true, None, window, cx);            }        }    }     pub fn track_alternate_file_items(&mut self) {        if let Some(item) = self.active_item().map(|item| item.downgrade_item()) {            let (current, _) = &self.alternate_file_items;            match current {                Some(current) => {                    if current.id() != item.id() {                        self.alternate_file_items =

Lines 1–500 of 6335.