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. Load more
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| {