183 lines · 5.8 KB · Rust
RawDownload
use std::collections::HashSet;use std::sync::atomic::{AtomicUsize, Ordering};use std::sync::OnceLock; use db::kvp::KEY_VALUE_STORE;use gpui::{App, EntityId, EventEmitter, Subscription};use ui::{prelude::*, IconButtonShape, Tooltip};use workspace::item::{ItemEvent, ItemHandle};use workspace::{ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView}; pub struct MultibufferHint {    shown_on: HashSet<EntityId>,    active_item: Option<Box<dyn ItemHandle>>,    subscription: Option<Subscription>,} const NUMBER_OF_HINTS: usize = 10; const SHOWN_COUNT_KEY: &str = "MULTIBUFFER_HINT_SHOWN_COUNT"; impl Default for MultibufferHint {    fn default() -> Self {        Self::new()    }} impl MultibufferHint {    pub fn new() -> Self {        Self {            shown_on: Default::default(),            active_item: None,            subscription: None,        }    }} impl MultibufferHint {    fn counter() -> &'static AtomicUsize {        static SHOWN_COUNT: OnceLock<AtomicUsize> = OnceLock::new();        SHOWN_COUNT.get_or_init(|| {            let value: usize = KEY_VALUE_STORE                .read_kvp(SHOWN_COUNT_KEY)                .ok()                .flatten()                .and_then(|v| v.parse().ok())                .unwrap_or(0);             AtomicUsize::new(value)        })    }     fn shown_count() -> usize {        Self::counter().load(Ordering::Relaxed)    }     fn increment_count(cx: &mut App) {        Self::set_count(Self::shown_count() + 1, cx)    }     pub(crate) fn set_count(count: usize, cx: &mut App) {        Self::counter().store(count, Ordering::Relaxed);         db::write_and_log(cx, move || {            KEY_VALUE_STORE.write_kvp(SHOWN_COUNT_KEY.to_string(), format!("{}", count))        });    }     fn dismiss(&mut self, cx: &mut App) {        Self::set_count(NUMBER_OF_HINTS, cx)    }     /// Determines the toolbar location for this [`MultibufferHint`].    fn determine_toolbar_location(&mut self, cx: &mut Context<Self>) -> ToolbarItemLocation {        if Self::shown_count() >= NUMBER_OF_HINTS {            return ToolbarItemLocation::Hidden;        }         let Some(active_pane_item) = self.active_item.as_ref() else {            return ToolbarItemLocation::Hidden;        };         if active_pane_item.is_singleton(cx)            || active_pane_item.breadcrumbs(cx.theme(), cx).is_none()        {            return ToolbarItemLocation::Hidden;        }         if self.shown_on.insert(active_pane_item.item_id()) {            Self::increment_count(cx);        }         ToolbarItemLocation::Secondary    }} impl EventEmitter<ToolbarItemEvent> for MultibufferHint {} impl ToolbarItemView for MultibufferHint {    fn set_active_pane_item(        &mut self,        active_pane_item: Option<&dyn ItemHandle>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> ToolbarItemLocation {        cx.notify();        self.active_item = active_pane_item.map(|item| item.boxed_clone());         let Some(active_pane_item) = active_pane_item else {            return ToolbarItemLocation::Hidden;        };         let this = cx.entity().downgrade();        self.subscription = Some(active_pane_item.subscribe_to_item_events(            window,            cx,            Box::new(move |event, _, cx| {                if let ItemEvent::UpdateBreadcrumbs = event {                    this.update(cx, |this, cx| {                        cx.notify();                        let location = this.determine_toolbar_location(cx);                        cx.emit(ToolbarItemEvent::ChangeLocation(location))                    })                    .ok();                }            }),        ));         self.determine_toolbar_location(cx)    }} impl Render for MultibufferHint {    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {        h_flex()            .px_2()            .py_0p5()            .justify_between()            .bg(cx.theme().status().info_background.opacity(0.5))            .border_1()            .border_color(cx.theme().colors().border_variant)            .rounded_md()            .overflow_hidden()            .child(                h_flex()                    .gap_0p5()                    .child(                        h_flex()                            .gap_2()                            .child(                                Icon::new(IconName::Info)                                    .size(IconSize::XSmall)                                    .color(Color::Muted),                            )                            .child(Label::new(                                "Edit and save files directly in the results multibuffer!",                            )),                    )                    .child(                        Button::new("open_docs", "Learn More")                            .icon(IconName::ArrowUpRight)                            .icon_size(IconSize::XSmall)                            .icon_color(Color::Muted)                            .icon_position(IconPosition::End)                            .on_click(move |_event, _, cx| {                                cx.open_url("https://zed.dev/docs/multibuffers")                            }),                    ),            )            .child(                IconButton::new("dismiss", IconName::Close)                    .shape(IconButtonShape::Square)                    .icon_size(IconSize::Small)                    .on_click(cx.listener(|this, _event, _, cx| {                        this.dismiss(cx);                        cx.emit(ToolbarItemEvent::ChangeLocation(                            ToolbarItemLocation::Hidden,                        ))                    }))                    .tooltip(Tooltip::text("Dismiss Hint")),            )            .into_any_element()    }}