342 lines · 11.9 KB · Rust
RawDownload
use std::{rc::Rc, time::Duration}; use file_icons::FileIcons;use gpui::ClickEvent;use gpui::{Animation, AnimationExt as _, pulsating_between};use ui::{IconButtonShape, Tooltip, prelude::*}; use crate::context::{AssistantContext, ContextId, ContextKind}; #[derive(IntoElement)]pub enum ContextPill {    Added {        context: AddedContext,        dupe_name: bool,        focused: bool,        on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,        on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,    },    Suggested {        name: SharedString,        icon_path: Option<SharedString>,        kind: ContextKind,        focused: bool,        on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,    },} impl ContextPill {    pub fn added(        context: AddedContext,        dupe_name: bool,        focused: bool,        on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,    ) -> Self {        Self::Added {            context,            dupe_name,            on_remove,            focused,            on_click: None,        }    }     pub fn suggested(        name: SharedString,        icon_path: Option<SharedString>,        kind: ContextKind,        focused: bool,    ) -> Self {        Self::Suggested {            name,            icon_path,            kind,            focused,            on_click: None,        }    }     pub fn on_click(mut self, listener: Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>) -> Self {        match &mut self {            ContextPill::Added { on_click, .. } => {                *on_click = Some(listener);            }            ContextPill::Suggested { on_click, .. } => {                *on_click = Some(listener);            }        }        self    }     pub fn id(&self) -> ElementId {        match self {            Self::Added { context, .. } => {                ElementId::NamedInteger("context-pill".into(), context.id.0)            }            Self::Suggested { .. } => "suggested-context-pill".into(),        }    }     pub fn icon(&self) -> Icon {        match self {            Self::Suggested {                icon_path: Some(icon_path),                ..            }            | Self::Added {                context:                    AddedContext {                        icon_path: Some(icon_path),                        ..                    },                ..            } => Icon::from_path(icon_path),            Self::Suggested { kind, .. }            | Self::Added {                context: AddedContext { kind, .. },                ..            } => Icon::new(kind.icon()),        }    }} impl RenderOnce for ContextPill {    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {        let color = cx.theme().colors();         let base_pill = h_flex()            .id(self.id())            .pl_1()            .pb(px(1.))            .border_1()            .rounded_sm()            .gap_1()            .child(self.icon().size(IconSize::XSmall).color(Color::Muted));         match &self {            ContextPill::Added {                context,                dupe_name,                on_remove,                focused,                on_click,            } => base_pill                .bg(color.element_background)                .border_color(if *focused {                    color.border_focused                } else {                    color.border.opacity(0.5)                })                .pr(if on_remove.is_some() { px(2.) } else { px(4.) })                .child(                    h_flex()                        .id("context-data")                        .gap_1()                        .child(                            div().max_w_64().child(                                Label::new(context.name.clone())                                    .size(LabelSize::Small)                                    .truncate(),                            ),                        )                        .when_some(context.parent.as_ref(), |element, parent_name| {                            if *dupe_name {                                element.child(                                    Label::new(parent_name.clone())                                        .size(LabelSize::XSmall)                                        .color(Color::Muted),                                )                            } else {                                element                            }                        })                        .when_some(context.tooltip.as_ref(), |element, tooltip| {                            element.tooltip(Tooltip::text(tooltip.clone()))                        }),                )                .when_some(on_remove.as_ref(), |element, on_remove| {                    element.child(                        IconButton::new(("remove", context.id.0), IconName::Close)                            .shape(IconButtonShape::Square)                            .icon_size(IconSize::XSmall)                            .tooltip(Tooltip::text("Remove Context"))                            .on_click({                                let on_remove = on_remove.clone();                                move |event, window, cx| on_remove(event, window, cx)                            }),                    )                })                .when_some(on_click.as_ref(), |element, on_click| {                    let on_click = on_click.clone();                    element                        .cursor_pointer()                        .on_click(move |event, window, cx| on_click(event, window, cx))                })                .map(|element| {                    if context.summarizing {                        element                            .tooltip(ui::Tooltip::text("Summarizing..."))                            .with_animation(                                "pulsating-ctx-pill",                                Animation::new(Duration::from_secs(2))                                    .repeat()                                    .with_easing(pulsating_between(0.4, 0.8)),                                |label, delta| label.opacity(delta),                            )                            .into_any_element()                    } else {                        element.into_any()                    }                }),            ContextPill::Suggested {                name,                icon_path: _,                kind,                focused,                on_click,            } => base_pill                .cursor_pointer()                .pr_1()                .when(*focused, |this| {                    this.bg(color.element_background.opacity(0.5))                })                .border_dashed()                .border_color(if *focused {                    color.border_focused                } else {                    color.border                })                .hover(|style| style.bg(color.element_hover.opacity(0.5)))                .child(                    div().px_0p5().max_w_64().child(                        Label::new(name.clone())                            .size(LabelSize::Small)                            .color(Color::Muted)                            .truncate(),                    ),                )                .child(                    Label::new(match kind {                        ContextKind::File => "Active Tab",                        ContextKind::Thread                        | ContextKind::Directory                        | ContextKind::FetchedUrl                        | ContextKind::Symbol => "Active",                    })                    .size(LabelSize::XSmall)                    .color(Color::Muted),                )                .child(                    Icon::new(IconName::Plus)                        .size(IconSize::XSmall)                        .into_any_element(),                )                .tooltip(|window, cx| {                    Tooltip::with_meta("Suggested Context", None, "Click to add it", window, cx)                })                .when_some(on_click.as_ref(), |element, on_click| {                    let on_click = on_click.clone();                    element.on_click(move |event, window, cx| on_click(event, window, cx))                })                .into_any(),        }    }} pub struct AddedContext {    pub id: ContextId,    pub kind: ContextKind,    pub name: SharedString,    pub parent: Option<SharedString>,    pub tooltip: Option<SharedString>,    pub icon_path: Option<SharedString>,    pub summarizing: bool,} impl AddedContext {    pub fn new(context: &AssistantContext, cx: &App) -> AddedContext {        match context {            AssistantContext::File(file_context) => {                let full_path = file_context.context_buffer.file.full_path(cx);                let full_path_string: SharedString =                    full_path.to_string_lossy().into_owned().into();                let name = full_path                    .file_name()                    .map(|n| n.to_string_lossy().into_owned().into())                    .unwrap_or_else(|| full_path_string.clone());                let parent = full_path                    .parent()                    .and_then(|p| p.file_name())                    .map(|n| n.to_string_lossy().into_owned().into());                AddedContext {                    id: file_context.id,                    kind: ContextKind::File,                    name,                    parent,                    tooltip: Some(full_path_string),                    icon_path: FileIcons::get_icon(&full_path, cx),                    summarizing: false,                }            }             AssistantContext::Directory(directory_context) => {                let full_path = directory_context                    .worktree                    .read(cx)                    .full_path(&directory_context.path);                let full_path_string: SharedString =                    full_path.to_string_lossy().into_owned().into();                let name = full_path                    .file_name()                    .map(|n| n.to_string_lossy().into_owned().into())                    .unwrap_or_else(|| full_path_string.clone());                let parent = full_path                    .parent()                    .and_then(|p| p.file_name())                    .map(|n| n.to_string_lossy().into_owned().into());                AddedContext {                    id: directory_context.id,                    kind: ContextKind::Directory,                    name,                    parent,                    tooltip: Some(full_path_string),                    icon_path: None,                    summarizing: false,                }            }             AssistantContext::Symbol(symbol_context) => AddedContext {                id: symbol_context.id,                kind: ContextKind::Symbol,                name: symbol_context.context_symbol.id.name.clone(),                parent: None,                tooltip: None,                icon_path: None,                summarizing: false,            },             AssistantContext::FetchedUrl(fetched_url_context) => AddedContext {                id: fetched_url_context.id,                kind: ContextKind::FetchedUrl,                name: fetched_url_context.url.clone(),                parent: None,                tooltip: None,                icon_path: None,                summarizing: false,            },             AssistantContext::Thread(thread_context) => AddedContext {                id: thread_context.id,                kind: ContextKind::Thread,                name: thread_context.summary(cx),                parent: None,                tooltip: None,                icon_path: None,                summarizing: thread_context                    .thread                    .read(cx)                    .is_generating_detailed_summary(),            },        }    }}