269 lines · 8.4 KB · Rust
RawDownload
use std::sync::Arc; use gpui::{App, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity};use picker::{Picker, PickerDelegate};use project::Project;use ui::utils::{format_distance_from_now, DateTimeType};use ui::{prelude::*, Avatar, ListItem, ListItemSpacing};use workspace::{Item, Workspace}; use crate::{    AssistantPanelDelegate, ContextStore, RemoteContextMetadata, SavedContextMetadata,    DEFAULT_TAB_TITLE,}; #[derive(Clone)]pub enum ContextMetadata {    Remote(RemoteContextMetadata),    Saved(SavedContextMetadata),} enum SavedContextPickerEvent {    Confirmed(ContextMetadata),} pub struct ContextHistory {    picker: Entity<Picker<SavedContextPickerDelegate>>,    _subscriptions: Vec<Subscription>,    workspace: WeakEntity<Workspace>,} impl ContextHistory {    pub fn new(        project: Entity<Project>,        context_store: Entity<ContextStore>,        workspace: WeakEntity<Workspace>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Self {        let picker = cx.new(|cx| {            Picker::uniform_list(                SavedContextPickerDelegate::new(project, context_store.clone()),                window,                cx,            )            .modal(false)            .max_height(None)        });         let subscriptions = vec![            cx.observe_in(&context_store, window, |this, _, window, cx| {                this.picker                    .update(cx, |picker, cx| picker.refresh(window, cx));            }),            cx.subscribe_in(&picker, window, Self::handle_picker_event),        ];         Self {            picker,            _subscriptions: subscriptions,            workspace,        }    }     fn handle_picker_event(        &mut self,        _: &Entity<Picker<SavedContextPickerDelegate>>,        event: &SavedContextPickerEvent,        window: &mut Window,        cx: &mut Context<Self>,    ) {        let SavedContextPickerEvent::Confirmed(context) = event;         let Some(assistant_panel_delegate) = <dyn AssistantPanelDelegate>::try_global(cx) else {            return;        };         self.workspace            .update(cx, |workspace, cx| match context {                ContextMetadata::Remote(metadata) => {                    assistant_panel_delegate                        .open_remote_context(workspace, metadata.id.clone(), window, cx)                        .detach_and_log_err(cx);                }                ContextMetadata::Saved(metadata) => {                    assistant_panel_delegate                        .open_saved_context(workspace, metadata.path.clone(), window, cx)                        .detach_and_log_err(cx);                }            })            .ok();    }} impl Render for ContextHistory {    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {        div().size_full().child(self.picker.clone())    }} impl Focusable for ContextHistory {    fn focus_handle(&self, cx: &App) -> FocusHandle {        self.picker.focus_handle(cx)    }} impl EventEmitter<()> for ContextHistory {} impl Item for ContextHistory {    type Event = ();     fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {        Some("History".into())    }} struct SavedContextPickerDelegate {    store: Entity<ContextStore>,    project: Entity<Project>,    matches: Vec<ContextMetadata>,    selected_index: usize,} impl EventEmitter<SavedContextPickerEvent> for Picker<SavedContextPickerDelegate> {} impl SavedContextPickerDelegate {    fn new(project: Entity<Project>, store: Entity<ContextStore>) -> Self {        Self {            project,            store,            matches: Vec::new(),            selected_index: 0,        }    }} impl PickerDelegate for SavedContextPickerDelegate {    type ListItem = ListItem;     fn match_count(&self) -> usize {        self.matches.len()    }     fn selected_index(&self) -> usize {        self.selected_index    }     fn set_selected_index(        &mut self,        ix: usize,        _window: &mut Window,        _cx: &mut Context<Picker<Self>>,    ) {        self.selected_index = ix;    }     fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {        "Search...".into()    }     fn update_matches(        &mut self,        query: String,        _window: &mut Window,        cx: &mut Context<Picker<Self>>,    ) -> Task<()> {        let search = self.store.read(cx).search(query, cx);        cx.spawn(|this, mut cx| async move {            let matches = search.await;            this.update(&mut cx, |this, cx| {                let host_contexts = this.delegate.store.read(cx).host_contexts();                this.delegate.matches = host_contexts                    .iter()                    .cloned()                    .map(ContextMetadata::Remote)                    .chain(matches.into_iter().map(ContextMetadata::Saved))                    .collect();                this.delegate.selected_index = 0;                cx.notify();            })            .ok();        })    }     fn confirm(&mut self, _secondary: bool, _window: &mut Window, cx: &mut Context<Picker<Self>>) {        if let Some(metadata) = self.matches.get(self.selected_index) {            cx.emit(SavedContextPickerEvent::Confirmed(metadata.clone()));        }    }     fn dismissed(&mut self, _window: &mut Window, _cx: &mut Context<Picker<Self>>) {}     fn render_match(        &self,        ix: usize,        selected: bool,        _window: &mut Window,        cx: &mut Context<Picker<Self>>,    ) -> Option<Self::ListItem> {        let context = self.matches.get(ix)?;        let item = match context {            ContextMetadata::Remote(context) => {                let host_user = self.project.read(cx).host().and_then(|collaborator| {                    self.project                        .read(cx)                        .user_store()                        .read(cx)                        .get_cached_user(collaborator.user_id)                });                div()                    .flex()                    .w_full()                    .justify_between()                    .gap_2()                    .child(                        h_flex().flex_1().overflow_x_hidden().child(                            Label::new(context.summary.clone().unwrap_or(DEFAULT_TAB_TITLE.into()))                                .size(LabelSize::Small),                        ),                    )                    .child(                        h_flex()                            .gap_2()                            .children(if let Some(host_user) = host_user {                                vec![                                    Avatar::new(host_user.avatar_uri.clone()).into_any_element(),                                    Label::new(format!("Shared by @{}", host_user.github_login))                                        .color(Color::Muted)                                        .size(LabelSize::Small)                                        .into_any_element(),                                ]                            } else {                                vec![Label::new("Shared by host")                                    .color(Color::Muted)                                    .size(LabelSize::Small)                                    .into_any_element()]                            }),                    )            }            ContextMetadata::Saved(context) => div()                .flex()                .w_full()                .justify_between()                .gap_2()                .child(                    h_flex()                        .flex_1()                        .child(Label::new(context.title.clone()).size(LabelSize::Small))                        .overflow_x_hidden(),                )                .child(                    Label::new(format_distance_from_now(                        DateTimeType::Local(context.mtime),                        false,                        true,                        true,                    ))                    .color(Color::Muted)                    .size(LabelSize::Small),                ),        };        Some(            ListItem::new(ix)                .inset(true)                .spacing(ListItemSpacing::Sparse)                .toggle_state(selected)                .child(item),        )    }}