929 lines · 33.9 KB · Rust
RawDownload
use std::{ops::Range, path::Path, rc::Rc, sync::Arc, time::Duration}; use file_icons::FileIcons;use futures::FutureExt as _;use gpui::{    Animation, AnimationExt as _, AnyView, ClickEvent, Entity, Image, MouseButton, Task,    pulsating_between,};use language_model::LanguageModelImage;use project::Project;use prompt_store::PromptStore;use rope::Point;use ui::{IconButtonShape, Tooltip, prelude::*, tooltip_container}; use crate::context::{    AgentContext, AgentContextHandle, ContextId, ContextKind, DirectoryContext,    DirectoryContextHandle, FetchedUrlContext, FileContext, FileContextHandle, ImageContext,    ImageStatus, RulesContext, RulesContextHandle, SelectionContext, SelectionContextHandle,    SymbolContext, SymbolContextHandle, TextThreadContext, TextThreadContextHandle, ThreadContext,    ThreadContextHandle,}; #[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, .. } => context.handle.element_id("context-pill".into()),            Self::Suggested { .. } => "suggested-context-pill".into(),        }    }     pub fn icon(&self) -> Icon {        match self {            Self::Suggested {                icon_path: Some(icon_path),                ..            } => Icon::from_path(icon_path),            Self::Suggested { kind, .. } => Icon::new(kind.icon()),            Self::Added { context, .. } => context.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,            } => {                let status_is_error = matches!(context.status, ContextStatus::Error { .. });                let status_is_warning = matches!(context.status, ContextStatus::Warning { .. });                 base_pill                    .pr(if on_remove.is_some() { px(2.) } else { px(4.) })                    .map(|pill| {                        if status_is_error {                            pill.bg(cx.theme().status().error_background)                                .border_color(cx.theme().status().error_border)                        } else if status_is_warning {                            pill.bg(cx.theme().status().warning_background)                                .border_color(cx.theme().status().warning_border)                        } else if *focused {                            pill.bg(color.element_background)                                .border_color(color.border_focused)                        } else {                            pill.bg(color.element_background)                                .border_color(color.border.opacity(0.5))                        }                    })                    .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()))                            })                            .map(|element| match &context.status {                                ContextStatus::Ready => element                                    .when_some(                                        context.render_hover.as_ref(),                                        |element, render_hover| {                                            let render_hover = render_hover.clone();                                            element.hoverable_tooltip(move |window, cx| {                                                render_hover(window, cx)                                            })                                        },                                    )                                    .into_any(),                                ContextStatus::Loading { message } => element                                    .tooltip(ui::Tooltip::text(message.clone()))                                    .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(),                                ContextStatus::Warning { message }                                | ContextStatus::Error { message } => element                                    .tooltip(ui::Tooltip::text(message.clone()))                                    .into_any_element(),                            }),                    )                    .when_some(on_remove.as_ref(), |element, on_remove| {                        element.child(                            IconButton::new(                                context.handle.element_id("remove".into()),                                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);                            cx.stop_propagation();                        })                    })                    .into_any_element()            }            ContextPill::Suggested {                name,                icon_path: _,                kind: _,                focused,                on_click,            } => base_pill                .cursor_pointer()                .pr_1()                .border_dashed()                .map(|pill| {                    if *focused {                        pill.border_color(color.border_focused)                            .bg(color.element_background.opacity(0.5))                    } else {                        pill.border_color(color.border)                    }                })                .hover(|style| style.bg(color.element_hover.opacity(0.5)))                .child(                    div().max_w_64().child(                        Label::new(name.clone())                            .size(LabelSize::Small)                            .color(Color::Muted)                            .truncate(),                    ),                )                .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);                        cx.stop_propagation();                    })                })                .into_any(),        }    }} pub enum ContextStatus {    Ready,    Loading { message: SharedString },    Error { message: SharedString },    Warning { message: SharedString },} #[derive(RegisterComponent)]pub struct AddedContext {    pub handle: AgentContextHandle,    pub kind: ContextKind,    pub name: SharedString,    pub parent: Option<SharedString>,    pub tooltip: Option<SharedString>,    pub icon_path: Option<SharedString>,    pub status: ContextStatus,    pub render_hover: Option<Rc<dyn Fn(&mut Window, &mut App) -> AnyView + 'static>>,} impl AddedContext {    pub fn icon(&self) -> Icon {        match &self.status {            ContextStatus::Warning { .. } => Icon::new(IconName::Warning).color(Color::Warning),            ContextStatus::Error { .. } => Icon::new(IconName::XCircle).color(Color::Error),            _ => {                if let Some(icon_path) = &self.icon_path {                    Icon::from_path(icon_path)                } else {                    Icon::new(self.kind.icon())                }            }        }    }    /// Creates an `AddedContext` by retrieving relevant details of `AgentContext`. This returns a    /// `None` if `DirectoryContext` or `RulesContext` no longer exist.    ///    /// TODO: `None` cases are unremovable from `ContextStore` and so are a very minor memory leak.    pub fn new_pending(        handle: AgentContextHandle,        prompt_store: Option<&Entity<PromptStore>>,        project: &Project,        model: Option<&Arc<dyn language_model::LanguageModel>>,        cx: &App,    ) -> Option<AddedContext> {        match handle {            AgentContextHandle::File(handle) => Self::pending_file(handle, cx),            AgentContextHandle::Directory(handle) => Self::pending_directory(handle, project, cx),            AgentContextHandle::Symbol(handle) => Self::pending_symbol(handle, cx),            AgentContextHandle::Selection(handle) => Self::pending_selection(handle, cx),            AgentContextHandle::FetchedUrl(handle) => Some(Self::fetched_url(handle)),            AgentContextHandle::Thread(handle) => Some(Self::pending_thread(handle, cx)),            AgentContextHandle::TextThread(handle) => Some(Self::pending_text_thread(handle, cx)),            AgentContextHandle::Rules(handle) => Self::pending_rules(handle, prompt_store, cx),            AgentContextHandle::Image(handle) => Some(Self::image(handle, model, cx)),        }    }     pub fn new_attached(        context: &AgentContext,        model: Option<&Arc<dyn language_model::LanguageModel>>,        cx: &App,    ) -> AddedContext {        match context {            AgentContext::File(context) => Self::attached_file(context, cx),            AgentContext::Directory(context) => Self::attached_directory(context),            AgentContext::Symbol(context) => Self::attached_symbol(context, cx),            AgentContext::Selection(context) => Self::attached_selection(context, cx),            AgentContext::FetchedUrl(context) => Self::fetched_url(context.clone()),            AgentContext::Thread(context) => Self::attached_thread(context),            AgentContext::TextThread(context) => Self::attached_text_thread(context),            AgentContext::Rules(context) => Self::attached_rules(context),            AgentContext::Image(context) => Self::image(context.clone(), model, cx),        }    }     fn pending_file(handle: FileContextHandle, cx: &App) -> Option<AddedContext> {        let full_path = handle.buffer.read(cx).file()?.full_path(cx);        Some(Self::file(handle, &full_path, cx))    }     fn attached_file(context: &FileContext, cx: &App) -> AddedContext {        Self::file(context.handle.clone(), &context.full_path, cx)    }     fn file(handle: FileContextHandle, full_path: &Path, cx: &App) -> AddedContext {        let full_path_string: SharedString = full_path.to_string_lossy().into_owned().into();        let (name, parent) =            extract_file_name_and_directory_from_full_path(full_path, &full_path_string);        AddedContext {            kind: ContextKind::File,            name,            parent,            tooltip: Some(full_path_string),            icon_path: FileIcons::get_icon(&full_path, cx),            status: ContextStatus::Ready,            render_hover: None,            handle: AgentContextHandle::File(handle),        }    }     fn pending_directory(        handle: DirectoryContextHandle,        project: &Project,        cx: &App,    ) -> Option<AddedContext> {        let worktree = project.worktree_for_entry(handle.entry_id, cx)?.read(cx);        let entry = worktree.entry_for_id(handle.entry_id)?;        let full_path = worktree.full_path(&entry.path);        Some(Self::directory(handle, &full_path))    }     fn attached_directory(context: &DirectoryContext) -> AddedContext {        Self::directory(context.handle.clone(), &context.full_path)    }     fn directory(handle: DirectoryContextHandle, full_path: &Path) -> AddedContext {        let full_path_string: SharedString = full_path.to_string_lossy().into_owned().into();        let (name, parent) =            extract_file_name_and_directory_from_full_path(full_path, &full_path_string);        AddedContext {            kind: ContextKind::Directory,            name,            parent,            tooltip: Some(full_path_string),            icon_path: None,            status: ContextStatus::Ready,            render_hover: None,            handle: AgentContextHandle::Directory(handle),        }    }     fn pending_symbol(handle: SymbolContextHandle, cx: &App) -> Option<AddedContext> {        let excerpt =            ContextFileExcerpt::new(&handle.full_path(cx)?, handle.enclosing_line_range(cx), cx);        Some(AddedContext {            kind: ContextKind::Symbol,            name: handle.symbol.clone(),            parent: Some(excerpt.file_name_and_range.clone()),            tooltip: None,            icon_path: None,            status: ContextStatus::Ready,            render_hover: {                let handle = handle.clone();                Some(Rc::new(move |_, cx| {                    excerpt.hover_view(handle.text(cx), cx).into()                }))            },            handle: AgentContextHandle::Symbol(handle),        })    }     fn attached_symbol(context: &SymbolContext, cx: &App) -> AddedContext {        let excerpt = ContextFileExcerpt::new(&context.full_path, context.line_range.clone(), cx);        AddedContext {            kind: ContextKind::Symbol,            name: context.handle.symbol.clone(),            parent: Some(excerpt.file_name_and_range.clone()),            tooltip: None,            icon_path: None,            status: ContextStatus::Ready,            render_hover: {                let text = context.text.clone();                Some(Rc::new(move |_, cx| {                    excerpt.hover_view(text.clone(), cx).into()                }))            },            handle: AgentContextHandle::Symbol(context.handle.clone()),        }    }     fn pending_selection(handle: SelectionContextHandle, cx: &App) -> Option<AddedContext> {        let excerpt = ContextFileExcerpt::new(&handle.full_path(cx)?, handle.line_range(cx), cx);        Some(AddedContext {            kind: ContextKind::Selection,            name: excerpt.file_name_and_range.clone(),            parent: excerpt.parent_name.clone(),            tooltip: None,            icon_path: excerpt.icon_path.clone(),            status: ContextStatus::Ready,            render_hover: {                let handle = handle.clone();                Some(Rc::new(move |_, cx| {                    excerpt.hover_view(handle.text(cx), cx).into()                }))            },            handle: AgentContextHandle::Selection(handle),        })    }     fn attached_selection(context: &SelectionContext, cx: &App) -> AddedContext {        let excerpt = ContextFileExcerpt::new(&context.full_path, context.line_range.clone(), cx);        AddedContext {            kind: ContextKind::Selection,            name: excerpt.file_name_and_range.clone(),            parent: excerpt.parent_name.clone(),            tooltip: None,            icon_path: excerpt.icon_path.clone(),            status: ContextStatus::Ready,            render_hover: {                let text = context.text.clone();                Some(Rc::new(move |_, cx| {                    excerpt.hover_view(text.clone(), cx).into()                }))            },            handle: AgentContextHandle::Selection(context.handle.clone()),        }    }     fn fetched_url(context: FetchedUrlContext) -> AddedContext {        AddedContext {            kind: ContextKind::FetchedUrl,            name: context.url.clone(),            parent: None,            tooltip: None,            icon_path: None,            status: ContextStatus::Ready,            render_hover: None,            handle: AgentContextHandle::FetchedUrl(context),        }    }     fn pending_thread(handle: ThreadContextHandle, cx: &App) -> AddedContext {        AddedContext {            kind: ContextKind::Thread,            name: handle.title(cx),            parent: None,            tooltip: None,            icon_path: None,            status: if handle.thread.read(cx).is_generating_detailed_summary() {                ContextStatus::Loading {                    message: "Summarizing…".into(),                }            } else {                ContextStatus::Ready            },            render_hover: {                let thread = handle.thread.clone();                Some(Rc::new(move |_, cx| {

Lines 1–500 of 929.