use crate::diagnostics::{DiagnosticsOptions, codeblock_fence_for_path, collect_diagnostics};use acp_thread::{MentionUri, selection_name};use agent::{ThreadStore, outline};use agent_client_protocol::schema::v1 as acp;use agent_servers::{AgentServer, AgentServerDelegate};use anyhow::{Context as _, Result, anyhow};use collections::{HashMap, HashSet};use editor::{ Anchor, Editor, EditorSnapshot, FoldPlaceholder, ToOffset, display_map::{Crease, CreaseId, CreaseMetadata, FoldId}, scroll::Autoscroll,};use futures::{AsyncReadExt as _, FutureExt as _, future::Shared};use gpui::{ AppContext, ClipboardEntry, Context, Empty, Entity, EntityId, Image, ImageFormat, Img, SharedString, Task, WeakEntity,};use http_client::{AsyncBody, HttpClientWithUrl};use itertools::Either;use language::Buffer;use language_model::{LanguageModelImage, LanguageModelImageExt};use multi_buffer::MultiBufferRow;use postage::stream::Stream as _;use project::{Project, ProjectItem, ProjectPath, Worktree};use rope::Point;use std::{ cell::RefCell, ffi::OsStr, fmt::Write, ops::{Range, RangeInclusive}, path::{Path, PathBuf}, rc::Rc, sync::Arc,};use text::OffsetRangeExt;use ui::{Disclosure, Toggleable, prelude::*};use util::{ResultExt, debug_panic, rel_path::RelPath};use workspace::{Workspace, notifications::NotifyResultExt as _}; use crate::ui::MentionCrease; pub type MentionTask = Shared<Task<Result<Mention, String>>>; #[derive(Debug, Clone, Eq, PartialEq)]pub enum Mention { Text { content: String, tracked_buffers: Vec<Entity<Buffer>>, }, Image(MentionImage), Link,} #[derive(Clone, Debug, Eq, PartialEq)]pub struct MentionImage { pub data: SharedString, pub format: ImageFormat,} pub struct MentionSet { project: WeakEntity<Project>, thread_store: Option<Entity<ThreadStore>>, mentions: HashMap<CreaseId, (MentionUri, MentionTask)>, crease_entities: HashMap<CreaseId, Entity<LoadingContext>>,} impl MentionSet { pub fn new(project: WeakEntity<Project>, thread_store: Option<Entity<ThreadStore>>) -> Self { Self { project, thread_store, mentions: HashMap::default(), crease_entities: HashMap::default(), } } pub fn contents( &self, full_mention_content: bool, cx: &mut App, ) -> Task<Result<HashMap<CreaseId, (MentionUri, Mention)>>> { let Some(project) = self.project.upgrade() else { return Task::ready(Err(anyhow!("Project not found"))); }; let mentions = self.mentions.clone(); cx.spawn(async move |cx| { let mut contents = HashMap::default(); for (crease_id, (mention_uri, task)) in mentions { let content = if full_mention_content && let MentionUri::Directory { abs_path } = &mention_uri { cx.update(|cx| full_mention_for_directory(&project, abs_path, cx)) .await? } else { task.await.map_err(|e| anyhow!("{e}"))? }; contents.insert(crease_id, (mention_uri, content)); } Ok(contents) }) } pub fn remove_invalid(&mut self, snapshot: &EditorSnapshot) { for (crease_id, crease) in snapshot.crease_snapshot.creases() { if !crease.range().start.is_valid(snapshot.buffer_snapshot()) { self.mentions.remove(&crease_id); self.crease_entities.remove(&crease_id); } } } pub fn insert_mention( &mut self, crease_id: CreaseId, uri: MentionUri, task: MentionTask, crease_entity: Option<Entity<LoadingContext>>, cx: &mut App, ) { self.mentions.insert(crease_id, (uri, task)); if let Some(entity) = crease_entity { self.crease_entities.insert(crease_id, entity); } self.recompute_disambiguation(cx); } /// Creates the appropriate confirmation task for a mention based on its URI type. /// This is used when pasting mention links to properly load their content. pub fn confirm_mention_for_uri( &mut self, mention_uri: MentionUri, supports_images: bool, http_client: Arc<HttpClientWithUrl>, cx: &mut Context<Self>, ) -> Task<Result<Mention>> { match mention_uri { MentionUri::Fetch { url } => self.confirm_mention_for_fetch(url, http_client, cx), MentionUri::Directory { .. } => Task::ready(Ok(Mention::Link)), MentionUri::Thread { id, .. } => self.confirm_mention_for_thread(id, cx), MentionUri::File { abs_path } => { self.confirm_mention_for_file(abs_path, supports_images, cx) } MentionUri::Symbol { abs_path, line_range, .. } => self.confirm_mention_for_symbol(abs_path, line_range, cx), MentionUri::Skill { skill_file_path, .. } => self.confirm_mention_for_skill(skill_file_path, cx), MentionUri::Diagnostics { include_errors, include_warnings, } => self.confirm_mention_for_diagnostics(include_errors, include_warnings, cx), MentionUri::GitDiff { base_ref } => { self.confirm_mention_for_git_diff(base_ref.into(), cx) } MentionUri::Selection { abs_path: Some(abs_path), line_range, .. } => self.confirm_mention_for_symbol(abs_path, line_range, cx), MentionUri::Selection { abs_path: None, .. } => Task::ready(Err(anyhow!( "Untitled buffer selection mentions are not supported for paste" ))), MentionUri::PastedImage { .. } | MentionUri::TerminalSelection { .. } | MentionUri::MergeConflict { .. } | MentionUri::Rule { .. } => { Task::ready(Err(anyhow!("Unsupported mention URI type for paste"))) } } } pub fn remove_mention(&mut self, crease_id: &CreaseId, cx: &mut App) { self.mentions.remove(crease_id); self.crease_entities.remove(crease_id); self.recompute_disambiguation(cx); } pub fn creases(&self) -> HashSet<CreaseId> { self.mentions.keys().cloned().collect() } pub fn is_empty(&self) -> bool { self.mentions.is_empty() } pub fn mentions(&self) -> HashSet<MentionUri> { self.mentions.values().map(|(uri, _)| uri.clone()).collect() } pub fn mention_uri_for_crease(&self, crease_id: &CreaseId) -> Option<MentionUri> { self.mentions.get(crease_id).map(|(uri, _)| uri.clone()) } /// Returns the resolved mention for a crease, if any. pub fn resolved_mention_for_crease( &self, crease_id: &CreaseId, ) -> Option<(MentionUri, Option<Mention>)> { let (uri, task) = self.mentions.get(crease_id)?; let mention = task.clone().now_or_never().and_then(|result| result.ok()); Some((uri.clone(), mention)) } pub fn set_mentions(&mut self, mentions: HashMap<CreaseId, (MentionUri, MentionTask)>) { self.crease_entities .retain(|id, _| mentions.contains_key(id)); self.mentions = mentions; } pub fn clear(&mut self) -> impl Iterator<Item = (CreaseId, (MentionUri, MentionTask))> { self.crease_entities.clear(); self.mentions.drain() } fn recompute_disambiguation(&self, cx: &mut App) { let labels = compute_disambiguated_labels(self.mentions.iter().map(|(id, (uri, _))| (*id, uri))); for (crease_id, new_label) in labels { if let Some(entity) = self.crease_entities.get(&crease_id) { entity.update(cx, |loading_ctx, cx| { if loading_ctx.label != new_label { loading_ctx.label = new_label; cx.notify(); } }); } } } pub fn confirm_mention_completion( &mut self, crease_text: SharedString, start: text::Anchor, content_len: usize, mention_uri: MentionUri, supports_images: bool, editor: Entity<Editor>, workspace: &Entity<Workspace>, window: &mut Window, cx: &mut Context<Self>, ) -> Task<()> { let Some(project) = self.project.upgrade() else { return Task::ready(()); }; let snapshot = editor.update(cx, |editor, cx| editor.snapshot(window, cx)); let Some(start_anchor) = snapshot.buffer_snapshot().anchor_in_excerpt(start) else { return Task::ready(()); }; let end_anchor = snapshot.buffer_snapshot().anchor_before( start_anchor.to_offset(&snapshot.buffer_snapshot()) + content_len + 1usize, ); let crease = if let MentionUri::File { abs_path } = &mention_uri && is_raster_image_path(abs_path) { let Some(project_path) = project .read(cx) .project_path_for_absolute_path(&abs_path, cx) else { log::error!("project path not found for image mention {abs_path:?}"); return Task::ready(()); }; let image_task = project.update(cx, |project, cx| project.open_image(project_path, cx)); let image = cx .spawn(async move |_, cx| { let image = image_task.await.map_err(|e| e.to_string())?; let image = image.update(cx, |image, _| image.image.clone()); Ok(image) }) .shared(); insert_crease_for_mention( start, content_len, mention_uri.name().into(), IconName::Image.path().into(), mention_uri.tooltip_text(), Some(mention_uri.clone()), Some(workspace.downgrade()), Some(image), editor.clone(), window, cx, ) } else { insert_crease_for_mention( start, content_len, crease_text, mention_uri.icon_path(cx), mention_uri.tooltip_text(), Some(mention_uri.clone()), Some(workspace.downgrade()), None, editor.clone(), window, cx, ) }; let Some((crease_id, tx, crease_entity)) = crease else { return Task::ready(()); }; let task = match mention_uri.clone() { MentionUri::Fetch { url } => { self.confirm_mention_for_fetch(url, workspace.read(cx).client().http_client(), cx) } MentionUri::Directory { .. } => Task::ready(Ok(Mention::Link)), MentionUri::Thread { id, .. } => self.confirm_mention_for_thread(id, cx), MentionUri::File { abs_path } => { self.confirm_mention_for_file(abs_path, supports_images, cx) } MentionUri::Symbol { abs_path, line_range, .. } => self.confirm_mention_for_symbol(abs_path, line_range, cx), MentionUri::Skill { skill_file_path, .. } => self.confirm_mention_for_skill(skill_file_path, cx), MentionUri::Diagnostics { include_errors, include_warnings, } => self.confirm_mention_for_diagnostics(include_errors, include_warnings, cx), MentionUri::PastedImage { .. } => { debug_panic!("pasted image URI should not be included in completions"); Task::ready(Err(anyhow!( "pasted imaged URI should not be included in completions" ))) } MentionUri::Selection { .. } => { debug_panic!("unexpected selection URI"); Task::ready(Err(anyhow!("unexpected selection URI"))) } MentionUri::TerminalSelection { .. } => { debug_panic!("unexpected terminal URI"); Task::ready(Err(anyhow!("unexpected terminal URI"))) } MentionUri::GitDiff { base_ref } => { self.confirm_mention_for_git_diff(base_ref.into(), cx) } MentionUri::MergeConflict { .. } => { debug_panic!("unexpected merge conflict URI"); Task::ready(Err(anyhow!("unexpected merge conflict URI"))) } MentionUri::Rule { .. } => { debug_panic!("unexpected rule URI"); Task::ready(Err(anyhow!("unexpected rule URI"))) } }; let task = cx .spawn(async move |_, _| task.await.map_err(|e| e.to_string())) .shared(); self.mentions.insert(crease_id, (mention_uri, task.clone())); if let Some(entity) = crease_entity { self.crease_entities.insert(crease_id, entity); } self.recompute_disambiguation(cx); // Notify the user if we failed to load the mentioned context let workspace = workspace.downgrade(); cx.spawn(async move |this, mut cx| { let result = task.await.notify_workspace_async_err(workspace, &mut cx); drop(tx); if result.is_none() { this.update(cx, |this, cx| { editor.update(cx, |editor, cx| { // Remove mention editor.edit([(start_anchor..end_anchor, "")], cx); }); this.mentions.remove(&crease_id); this.crease_entities.remove(&crease_id); }) .ok(); } }) } pub fn confirm_mention_for_file( &self, abs_path: PathBuf, supports_images: bool, cx: &mut Context<Self>, ) -> Task<Result<Mention>> { let Some(project) = self.project.upgrade() else { return Task::ready(Err(anyhow!("project not found"))); }; let Some(project_path) = project .read(cx) .project_path_for_absolute_path(&abs_path, cx) else { return Task::ready(Err(anyhow!( "project path not found for file mention {abs_path:?}" ))); }; if is_raster_image_path(&abs_path) { if !supports_images { return Task::ready(Err(anyhow!("This model does not support images yet"))); } let task = project.update(cx, |project, cx| project.open_image(project_path, cx)); return cx.spawn(async move |_, cx| { let image = task.await?; let image = image.update(cx, |image, _| image.image.clone()); let image = cx .update(|cx| LanguageModelImage::from_image(image, cx)) .await; if let Some(image) = image { Ok(Mention::Image(MentionImage { data: image.source, format: LanguageModelImage::FORMAT, })) } else { Err(anyhow!("Failed to convert image")) } }); } let buffer = project.update(cx, |project, cx| project.open_buffer(project_path, cx)); cx.spawn(async move |_, cx| { let buffer = match buffer.await { Ok(buffer) => buffer, Err(_error) => { return Ok(Mention::Link); } }; let buffer_content = outline::get_buffer_content_or_outline( buffer.clone(), Some(&abs_path.to_string_lossy()), &cx, ) .await?; Ok(Mention::Text { content: buffer_content.text, tracked_buffers: vec![buffer], }) }) } fn confirm_mention_for_fetch( &self, url: url::Url, http_client: Arc<HttpClientWithUrl>, cx: &mut Context<Self>, ) -> Task<Result<Mention>> { cx.background_executor().spawn(async move { let content = fetch_url_content(http_client, url.to_string()).await?; Ok(Mention::Text { content, tracked_buffers: Vec::new(), }) }) } fn confirm_mention_for_symbol( &self, abs_path: PathBuf, line_range: RangeInclusive<u32>, cx: &mut Context<Self>, ) -> Task<Result<Mention>> { let Some(project) = self.project.upgrade() else { return Task::ready(Err(anyhow!("project not found"))); }; let Some(project_path) = project .read(cx) .project_path_for_absolute_path(&abs_path, cx) else { return Task::ready(Err(anyhow!( "project path not found for symbol mention {abs_path:?}" ))); }; let buffer = project.update(cx, |project, cx| project.open_buffer(project_path, cx)); cx.spawn(async move |_, cx| { let buffer = buffer.await?; let mention = buffer.update(cx, |buffer, cx| { let start = Point::new(*line_range.start(), 0).min(buffer.max_point()); let end = Point::new(*line_range.end() + 1, 0).min(buffer.max_point()); let content = buffer.text_for_range(start..end).collect(); Mention::Text { content, tracked_buffers: vec![cx.entity()], } }); Ok(mention) }) } fn confirm_mention_for_skill( &self, skill_file_path: PathBuf, cx: &mut Context<Self>, ) -> Task<Result<Mention>> { // Built-in skills have synthetic paths that don't exist on disk;Lines 1–500 of 1521. Load more
use crate::diagnostics::{DiagnosticsOptions, codeblock_fence_for_path, collect_diagnostics}; use acp_thread::{MentionUri, selection_name}; use agent::{ThreadStore, outline}; use agent_client_protocol::schema::v1 as acp; use agent_servers::{AgentServer, AgentServerDelegate}; use anyhow::{Context as _, Result, anyhow}; use collections::{HashMap, HashSet}; use editor::{ Anchor, Editor, EditorSnapshot, FoldPlaceholder, ToOffset, display_map::{Crease, CreaseId, CreaseMetadata, FoldId}, scroll::Autoscroll, }; use futures::{AsyncReadExt as _, FutureExt as _, future::Shared}; use gpui::{ AppContext, ClipboardEntry, Context, Empty, Entity, EntityId, Image, ImageFormat, Img, SharedString, Task, WeakEntity, }; use http_client::{AsyncBody, HttpClientWithUrl}; use itertools::Either; use language::Buffer; use language_model::{LanguageModelImage, LanguageModelImageExt}; use multi_buffer::MultiBufferRow; use postage::stream::Stream as _; use project::{Project, ProjectItem, ProjectPath, Worktree}; use rope::Point; use std::{ cell::RefCell, ffi::OsStr, fmt::Write, ops::{Range, RangeInclusive}, path::{Path, PathBuf}, rc::Rc, sync::Arc, }; use text::OffsetRangeExt; use ui::{Disclosure, Toggleable, prelude::*}; use util::{ResultExt, debug_panic, rel_path::RelPath}; use workspace::{Workspace, notifications::NotifyResultExt as _}; use crate::ui::MentionCrease; pub type MentionTask = Shared<Task<Result<Mention, String>>>; #[derive(Debug, Clone, Eq, PartialEq)] pub enum Mention { Text { content: String, tracked_buffers: Vec<Entity<Buffer>>, }, Image(MentionImage), Link, } #[derive(Clone, Debug, Eq, PartialEq)] pub struct MentionImage { pub data: SharedString, pub format: ImageFormat, } pub struct MentionSet { project: WeakEntity<Project>, thread_store: Option<Entity<ThreadStore>>, mentions: HashMap<CreaseId, (MentionUri, MentionTask)>, crease_entities: HashMap<CreaseId, Entity<LoadingContext>>, } impl MentionSet { pub fn new(project: WeakEntity<Project>, thread_store: Option<Entity<ThreadStore>>) -> Self { Self { project, thread_store, mentions: HashMap::default(), crease_entities: HashMap::default(), } } pub fn contents( &self, full_mention_content: bool, cx: &mut App, ) -> Task<Result<HashMap<CreaseId, (MentionUri, Mention)>>> { let Some(project) = self.project.upgrade() else { return Task::ready(Err(anyhow!("Project not found"))); }; let mentions = self.mentions.clone(); cx.spawn(async move |cx| { let mut contents = HashMap::default(); for (crease_id, (mention_uri, task)) in mentions { let content = if full_mention_content && let MentionUri::Directory { abs_path } = &mention_uri { cx.update(|cx| full_mention_for_directory(&project, abs_path, cx)) .await? } else { task.await.map_err(|e| anyhow!("{e}"))? }; contents.insert(crease_id, (mention_uri, content)); } Ok(contents) }) } pub fn remove_invalid(&mut self, snapshot: &EditorSnapshot) { for (crease_id, crease) in snapshot.crease_snapshot.creases() { if !crease.range().start.is_valid(snapshot.buffer_snapshot()) { self.mentions.remove(&crease_id); self.crease_entities.remove(&crease_id); } } } pub fn insert_mention( &mut self, crease_id: CreaseId, uri: MentionUri, task: MentionTask, crease_entity: Option<Entity<LoadingContext>>, cx: &mut App, ) { self.mentions.insert(crease_id, (uri, task)); if let Some(entity) = crease_entity { self.crease_entities.insert(crease_id, entity); } self.recompute_disambiguation(cx); } /// Creates the appropriate confirmation task for a mention based on its URI type. /// This is used when pasting mention links to properly load their content. pub fn confirm_mention_for_uri( &mut self, mention_uri: MentionUri, supports_images: bool, http_client: Arc<HttpClientWithUrl>, cx: &mut Context<Self>, ) -> Task<Result<Mention>> { match mention_uri { MentionUri::Fetch { url } => self.confirm_mention_for_fetch(url, http_client, cx), MentionUri::Directory { .. } => Task::ready(Ok(Mention::Link)), MentionUri::Thread { id, .. } => self.confirm_mention_for_thread(id, cx), MentionUri::File { abs_path } => { self.confirm_mention_for_file(abs_path, supports_images, cx) } MentionUri::Symbol { abs_path, line_range, .. } => self.confirm_mention_for_symbol(abs_path, line_range, cx), MentionUri::Skill { skill_file_path, .. } => self.confirm_mention_for_skill(skill_file_path, cx), MentionUri::Diagnostics { include_errors, include_warnings, } => self.confirm_mention_for_diagnostics(include_errors, include_warnings, cx), MentionUri::GitDiff { base_ref } => { self.confirm_mention_for_git_diff(base_ref.into(), cx) } MentionUri::Selection { abs_path: Some(abs_path), line_range, .. } => self.confirm_mention_for_symbol(abs_path, line_range, cx), MentionUri::Selection { abs_path: None, .. } => Task::ready(Err(anyhow!( "Untitled buffer selection mentions are not supported for paste" ))), MentionUri::PastedImage { .. } | MentionUri::TerminalSelection { .. } | MentionUri::MergeConflict { .. } | MentionUri::Rule { .. } => { Task::ready(Err(anyhow!("Unsupported mention URI type for paste"))) } } } pub fn remove_mention(&mut self, crease_id: &CreaseId, cx: &mut App) { self.mentions.remove(crease_id); self.crease_entities.remove(crease_id); self.recompute_disambiguation(cx); } pub fn creases(&self) -> HashSet<CreaseId> { self.mentions.keys().cloned().collect() } pub fn is_empty(&self) -> bool { self.mentions.is_empty() } pub fn mentions(&self) -> HashSet<MentionUri> { self.mentions.values().map(|(uri, _)| uri.clone()).collect() } pub fn mention_uri_for_crease(&self, crease_id: &CreaseId) -> Option<MentionUri> { self.mentions.get(crease_id).map(|(uri, _)| uri.clone()) } /// Returns the resolved mention for a crease, if any. pub fn resolved_mention_for_crease( &self, crease_id: &CreaseId, ) -> Option<(MentionUri, Option<Mention>)> { let (uri, task) = self.mentions.get(crease_id)?; let mention = task.clone().now_or_never().and_then(|result| result.ok()); Some((uri.clone(), mention)) } pub fn set_mentions(&mut self, mentions: HashMap<CreaseId, (MentionUri, MentionTask)>) { self.crease_entities .retain(|id, _| mentions.contains_key(id)); self.mentions = mentions; } pub fn clear(&mut self) -> impl Iterator<Item = (CreaseId, (MentionUri, MentionTask))> { self.crease_entities.clear(); self.mentions.drain() } fn recompute_disambiguation(&self, cx: &mut App) { let labels = compute_disambiguated_labels(self.mentions.iter().map(|(id, (uri, _))| (*id, uri))); for (crease_id, new_label) in labels { if let Some(entity) = self.crease_entities.get(&crease_id) { entity.update(cx, |loading_ctx, cx| { if loading_ctx.label != new_label { loading_ctx.label = new_label; cx.notify(); } }); } } } pub fn confirm_mention_completion( &mut self, crease_text: SharedString, start: text::Anchor, content_len: usize, mention_uri: MentionUri, supports_images: bool, editor: Entity<Editor>, workspace: &Entity<Workspace>, window: &mut Window, cx: &mut Context<Self>, ) -> Task<()> { let Some(project) = self.project.upgrade() else { return Task::ready(()); }; let snapshot = editor.update(cx, |editor, cx| editor.snapshot(window, cx)); let Some(start_anchor) = snapshot.buffer_snapshot().anchor_in_excerpt(start) else { return Task::ready(()); }; let end_anchor = snapshot.buffer_snapshot().anchor_before( start_anchor.to_offset(&snapshot.buffer_snapshot()) + content_len + 1usize, ); let crease = if let MentionUri::File { abs_path } = &mention_uri && is_raster_image_path(abs_path) { let Some(project_path) = project .read(cx) .project_path_for_absolute_path(&abs_path, cx) else { log::error!("project path not found for image mention {abs_path:?}"); return Task::ready(()); }; let image_task = project.update(cx, |project, cx| project.open_image(project_path, cx)); let image = cx .spawn(async move |_, cx| { let image = image_task.await.map_err(|e| e.to_string())?; let image = image.update(cx, |image, _| image.image.clone()); Ok(image) }) .shared(); insert_crease_for_mention( start, content_len, mention_uri.name().into(), IconName::Image.path().into(), mention_uri.tooltip_text(), Some(mention_uri.clone()), Some(workspace.downgrade()), Some(image), editor.clone(), window, cx, ) } else { insert_crease_for_mention( start, content_len, crease_text, mention_uri.icon_path(cx), mention_uri.tooltip_text(), Some(mention_uri.clone()), Some(workspace.downgrade()), None, editor.clone(), window, cx, ) }; let Some((crease_id, tx, crease_entity)) = crease else { return Task::ready(()); }; let task = match mention_uri.clone() { MentionUri::Fetch { url } => { self.confirm_mention_for_fetch(url, workspace.read(cx).client().http_client(), cx) } MentionUri::Directory { .. } => Task::ready(Ok(Mention::Link)), MentionUri::Thread { id, .. } => self.confirm_mention_for_thread(id, cx), MentionUri::File { abs_path } => { self.confirm_mention_for_file(abs_path, supports_images, cx) } MentionUri::Symbol { abs_path, line_range, .. } => self.confirm_mention_for_symbol(abs_path, line_range, cx), MentionUri::Skill { skill_file_path, .. } => self.confirm_mention_for_skill(skill_file_path, cx), MentionUri::Diagnostics { include_errors, include_warnings, } => self.confirm_mention_for_diagnostics(include_errors, include_warnings, cx), MentionUri::PastedImage { .. } => { debug_panic!("pasted image URI should not be included in completions"); Task::ready(Err(anyhow!( "pasted imaged URI should not be included in completions" ))) } MentionUri::Selection { .. } => { debug_panic!("unexpected selection URI"); Task::ready(Err(anyhow!("unexpected selection URI"))) } MentionUri::TerminalSelection { .. } => { debug_panic!("unexpected terminal URI"); Task::ready(Err(anyhow!("unexpected terminal URI"))) } MentionUri::GitDiff { base_ref } => { self.confirm_mention_for_git_diff(base_ref.into(), cx) } MentionUri::MergeConflict { .. } => { debug_panic!("unexpected merge conflict URI"); Task::ready(Err(anyhow!("unexpected merge conflict URI"))) } MentionUri::Rule { .. } => { debug_panic!("unexpected rule URI"); Task::ready(Err(anyhow!("unexpected rule URI"))) } }; let task = cx .spawn(async move |_, _| task.await.map_err(|e| e.to_string())) .shared(); self.mentions.insert(crease_id, (mention_uri, task.clone())); if let Some(entity) = crease_entity { self.crease_entities.insert(crease_id, entity); } self.recompute_disambiguation(cx); // Notify the user if we failed to load the mentioned context let workspace = workspace.downgrade(); cx.spawn(async move |this, mut cx| { let result = task.await.notify_workspace_async_err(workspace, &mut cx); drop(tx); if result.is_none() { this.update(cx, |this, cx| { editor.update(cx, |editor, cx| { // Remove mention editor.edit([(start_anchor..end_anchor, "")], cx); }); this.mentions.remove(&crease_id); this.crease_entities.remove(&crease_id); }) .ok(); } }) } pub fn confirm_mention_for_file( &self, abs_path: PathBuf, supports_images: bool, cx: &mut Context<Self>, ) -> Task<Result<Mention>> { let Some(project) = self.project.upgrade() else { return Task::ready(Err(anyhow!("project not found"))); }; let Some(project_path) = project .read(cx) .project_path_for_absolute_path(&abs_path, cx) else { return Task::ready(Err(anyhow!( "project path not found for file mention {abs_path:?}" ))); }; if is_raster_image_path(&abs_path) { if !supports_images { return Task::ready(Err(anyhow!("This model does not support images yet"))); } let task = project.update(cx, |project, cx| project.open_image(project_path, cx)); return cx.spawn(async move |_, cx| { let image = task.await?; let image = image.update(cx, |image, _| image.image.clone()); let image = cx .update(|cx| LanguageModelImage::from_image(image, cx)) .await; if let Some(image) = image { Ok(Mention::Image(MentionImage { data: image.source, format: LanguageModelImage::FORMAT, })) } else { Err(anyhow!("Failed to convert image")) } }); } let buffer = project.update(cx, |project, cx| project.open_buffer(project_path, cx)); cx.spawn(async move |_, cx| { let buffer = match buffer.await { Ok(buffer) => buffer, Err(_error) => { return Ok(Mention::Link); } }; let buffer_content = outline::get_buffer_content_or_outline( buffer.clone(), Some(&abs_path.to_string_lossy()), &cx, ) .await?; Ok(Mention::Text { content: buffer_content.text, tracked_buffers: vec![buffer], }) }) } fn confirm_mention_for_fetch( &self, url: url::Url, http_client: Arc<HttpClientWithUrl>, cx: &mut Context<Self>, ) -> Task<Result<Mention>> { cx.background_executor().spawn(async move { let content = fetch_url_content(http_client, url.to_string()).await?; Ok(Mention::Text { content, tracked_buffers: Vec::new(), }) }) } fn confirm_mention_for_symbol( &self, abs_path: PathBuf, line_range: RangeInclusive<u32>, cx: &mut Context<Self>, ) -> Task<Result<Mention>> { let Some(project) = self.project.upgrade() else { return Task::ready(Err(anyhow!("project not found"))); }; let Some(project_path) = project .read(cx) .project_path_for_absolute_path(&abs_path, cx) else { return Task::ready(Err(anyhow!( "project path not found for symbol mention {abs_path:?}" ))); }; let buffer = project.update(cx, |project, cx| project.open_buffer(project_path, cx)); cx.spawn(async move |_, cx| { let buffer = buffer.await?; let mention = buffer.update(cx, |buffer, cx| { let start = Point::new(*line_range.start(), 0).min(buffer.max_point()); let end = Point::new(*line_range.end() + 1, 0).min(buffer.max_point()); let content = buffer.text_for_range(start..end).collect(); Mention::Text { content, tracked_buffers: vec![cx.entity()], } }); Ok(mention) }) } fn confirm_mention_for_skill( &self, skill_file_path: PathBuf, cx: &mut Context<Self>, ) -> Task<Result<Mention>> { // Built-in skills have synthetic paths that don't exist on disk;