multibuffer_hint.rs

  1use std::collections::HashSet;
  2use std::sync::atomic::{AtomicUsize, Ordering};
  3use std::sync::OnceLock;
  4
  5use db::kvp::KEY_VALUE_STORE;
  6use gpui::{AppContext, Empty, EntityId, EventEmitter};
  7use ui::{prelude::*, ButtonLike, IconButtonShape, Tooltip};
  8use workspace::item::ItemHandle;
  9use workspace::{ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
 10
 11pub struct MultibufferHint {
 12    shown_on: HashSet<EntityId>,
 13    active_item: Option<Box<dyn ItemHandle>>,
 14}
 15
 16const NUMBER_OF_HINTS: usize = 10;
 17
 18const SHOWN_COUNT_KEY: &str = "MULTIBUFFER_HINT_SHOWN_COUNT";
 19
 20impl MultibufferHint {
 21    pub fn new() -> Self {
 22        Self {
 23            shown_on: Default::default(),
 24            active_item: None,
 25        }
 26    }
 27}
 28
 29impl MultibufferHint {
 30    fn counter() -> &'static AtomicUsize {
 31        static SHOWN_COUNT: OnceLock<AtomicUsize> = OnceLock::new();
 32        SHOWN_COUNT.get_or_init(|| {
 33            let value: usize = KEY_VALUE_STORE
 34                .read_kvp(SHOWN_COUNT_KEY)
 35                .ok()
 36                .flatten()
 37                .and_then(|v| v.parse().ok())
 38                .unwrap_or(0);
 39
 40            AtomicUsize::new(value)
 41        })
 42    }
 43
 44    fn shown_count() -> usize {
 45        Self::counter().load(Ordering::Relaxed)
 46    }
 47
 48    fn increment_count(cx: &mut AppContext) {
 49        Self::set_count(Self::shown_count() + 1, cx)
 50    }
 51
 52    pub(crate) fn set_count(count: usize, cx: &mut AppContext) {
 53        Self::counter().store(count, Ordering::Relaxed);
 54
 55        db::write_and_log(cx, move || {
 56            KEY_VALUE_STORE.write_kvp(SHOWN_COUNT_KEY.to_string(), format!("{}", count))
 57        });
 58    }
 59
 60    fn dismiss(&mut self, cx: &mut AppContext) {
 61        Self::set_count(NUMBER_OF_HINTS, cx)
 62    }
 63}
 64
 65impl EventEmitter<ToolbarItemEvent> for MultibufferHint {}
 66
 67impl ToolbarItemView for MultibufferHint {
 68    fn set_active_pane_item(
 69        &mut self,
 70        active_pane_item: Option<&dyn ItemHandle>,
 71        cx: &mut ViewContext<Self>,
 72    ) -> ToolbarItemLocation {
 73        if Self::shown_count() > NUMBER_OF_HINTS {
 74            return ToolbarItemLocation::Hidden;
 75        }
 76
 77        let Some(active_pane_item) = active_pane_item else {
 78            return ToolbarItemLocation::Hidden;
 79        };
 80
 81        if active_pane_item.is_singleton(cx) {
 82            return ToolbarItemLocation::Hidden;
 83        }
 84
 85        if self.shown_on.insert(active_pane_item.item_id()) {
 86            Self::increment_count(cx)
 87        }
 88
 89        self.active_item = Some(active_pane_item.boxed_clone());
 90        ToolbarItemLocation::Secondary
 91    }
 92}
 93
 94impl Render for MultibufferHint {
 95    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
 96        let Some(active_item) = self.active_item.as_ref() else {
 97            return Empty.into_any_element();
 98        };
 99
100        if active_item.breadcrumbs(cx.theme(), cx).is_none() {
101            return Empty.into_any_element();
102        }
103
104        h_flex()
105            .px_2()
106            .justify_between()
107            .bg(cx.theme().status().info_background)
108            .rounded_md()
109            .child(
110                h_flex()
111                    .gap_2()
112                    .child(Label::new(
113                        "Edit and save files directly in the results multibuffer!",
114                    ))
115                    .child(
116                        ButtonLike::new("open_docs")
117                            .style(ButtonStyle::Transparent)
118                            .child(
119                                h_flex()
120                                    .gap_1()
121                                    .child(Label::new("Read more…"))
122                                    .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
123                            )
124                            .on_click(move |_event, cx| {
125                                cx.open_url("https://zed.dev/docs/multibuffers")
126                            }),
127                    ),
128            )
129            .child(
130                IconButton::new("dismiss", IconName::Close)
131                    .style(ButtonStyle::Transparent)
132                    .shape(IconButtonShape::Square)
133                    .icon_size(IconSize::Small)
134                    .on_click(cx.listener(|this, _event, cx| {
135                        this.dismiss(cx);
136                        cx.emit(ToolbarItemEvent::ChangeLocation(
137                            ToolbarItemLocation::Hidden,
138                        ))
139                    }))
140                    .tooltip(move |cx| Tooltip::text("Dismiss this hint", cx)),
141            )
142            .into_any_element()
143    }
144}