use std::cell::RefCell;use std::ops::Range;use std::path::Path;use std::rc::Rc;use std::sync::Arc;use std::sync::atomic::AtomicBool; use anyhow::Result;use editor::{CompletionProvider, Editor, ExcerptId};use file_icons::FileIcons;use gpui::{App, Entity, Task, WeakEntity};use http_client::HttpClientWithUrl;use language::{Buffer, CodeLabel, HighlightId};use lsp::CompletionContext;use project::{Completion, CompletionIntent, ProjectPath, Symbol, WorktreeId};use rope::Point;use text::{Anchor, ToPoint};use ui::prelude::*;use workspace::Workspace; use crate::context_picker::file_context_picker::search_files;use crate::context_picker::symbol_context_picker::search_symbols;use crate::context_store::ContextStore;use crate::thread_store::ThreadStore; use super::fetch_context_picker::fetch_url_content;use super::file_context_picker::FileMatch;use super::symbol_context_picker::SymbolMatch;use super::thread_context_picker::{ThreadContextEntry, ThreadMatch, search_threads};use super::{ ContextPickerMode, MentionLink, RecentEntry, recent_context_picker_entries, supported_context_picker_modes,}; pub(crate) enum Match { Symbol(SymbolMatch), File(FileMatch), Thread(ThreadMatch), Fetch(SharedString), Mode(ContextPickerMode),} fn search( mode: Option<ContextPickerMode>, query: String, cancellation_flag: Arc<AtomicBool>, recent_entries: Vec<RecentEntry>, thread_store: Option<WeakEntity<ThreadStore>>, workspace: Entity<Workspace>, cx: &mut App,) -> Task<Vec<Match>> { match mode { Some(ContextPickerMode::File) => { let search_files_task = search_files(query.clone(), cancellation_flag.clone(), &workspace, cx); cx.background_spawn(async move { search_files_task .await .into_iter() .map(Match::File) .collect() }) } Some(ContextPickerMode::Symbol) => { let search_symbols_task = search_symbols(query.clone(), cancellation_flag.clone(), &workspace, cx); cx.background_spawn(async move { search_symbols_task .await .into_iter() .map(Match::Symbol) .collect() }) } Some(ContextPickerMode::Thread) => { if let Some(thread_store) = thread_store.as_ref().and_then(|t| t.upgrade()) { let search_threads_task = search_threads(query.clone(), cancellation_flag.clone(), thread_store, cx); cx.background_spawn(async move { search_threads_task .await .into_iter() .map(Match::Thread) .collect() }) } else { Task::ready(Vec::new()) } } Some(ContextPickerMode::Fetch) => { if !query.is_empty() { Task::ready(vec![Match::Fetch(query.into())]) } else { Task::ready(Vec::new()) } } None => { if query.is_empty() { let mut matches = recent_entries .into_iter() .map(|entry| match entry { super::RecentEntry::File { project_path, path_prefix, } => Match::File(FileMatch { mat: fuzzy::PathMatch { score: 1., positions: Vec::new(), worktree_id: project_path.worktree_id.to_usize(), path: project_path.path, path_prefix, is_dir: false, distance_to_relative_ancestor: 0, }, is_recent: true, }), super::RecentEntry::Thread(thread_context_entry) => { Match::Thread(ThreadMatch { thread: thread_context_entry, is_recent: true, }) } }) .collect::<Vec<_>>(); matches.extend( supported_context_picker_modes(&thread_store) .into_iter() .map(Match::Mode), ); Task::ready(matches) } else { let search_files_task = search_files(query.clone(), cancellation_flag.clone(), &workspace, cx); cx.background_spawn(async move { search_files_task .await .into_iter() .map(Match::File) .collect() }) } } }} pub struct ContextPickerCompletionProvider { workspace: WeakEntity<Workspace>, context_store: WeakEntity<ContextStore>, thread_store: Option<WeakEntity<ThreadStore>>, editor: WeakEntity<Editor>,} impl ContextPickerCompletionProvider { pub fn new( workspace: WeakEntity<Workspace>, context_store: WeakEntity<ContextStore>, thread_store: Option<WeakEntity<ThreadStore>>, editor: WeakEntity<Editor>, ) -> Self { Self { workspace, context_store, thread_store, editor, } } fn completion_for_mode(source_range: Range<Anchor>, mode: ContextPickerMode) -> Completion { Completion { replace_range: source_range.clone(), new_text: format!("@{} ", mode.mention_prefix()), label: CodeLabel::plain(mode.label().to_string(), None), icon_path: Some(mode.icon().path().into()), documentation: None, source: project::CompletionSource::Custom, insert_text_mode: None, // This ensures that when a user accepts this completion, the // completion menu will still be shown after "@category " is // inserted confirm: Some(Arc::new(|_, _, _| true)), } } fn completion_for_thread( thread_entry: ThreadContextEntry, excerpt_id: ExcerptId, source_range: Range<Anchor>, recent: bool, editor: Entity<Editor>, context_store: Entity<ContextStore>, thread_store: Entity<ThreadStore>, ) -> Completion { let icon_for_completion = if recent { IconName::HistoryRerun } else { IconName::MessageBubbles }; let new_text = MentionLink::for_thread(&thread_entry); let new_text_len = new_text.len(); Completion { replace_range: source_range.clone(), new_text, label: CodeLabel::plain(thread_entry.summary.to_string(), None), documentation: None, insert_text_mode: None, source: project::CompletionSource::Custom, icon_path: Some(icon_for_completion.path().into()), confirm: Some(confirm_completion_callback( IconName::MessageBubbles.path().into(), thread_entry.summary.clone(), excerpt_id, source_range.start, new_text_len, editor.clone(), move |cx| { let thread_id = thread_entry.id.clone(); let context_store = context_store.clone(); let thread_store = thread_store.clone(); cx.spawn(async move |cx| { let thread = thread_store .update(cx, |thread_store, cx| { thread_store.open_thread(&thread_id, cx) })? .await?; context_store.update(cx, |context_store, cx| { context_store.add_thread(thread, false, cx) }) }) .detach_and_log_err(cx); }, )), } } fn completion_for_fetch( source_range: Range<Anchor>, url_to_fetch: SharedString, excerpt_id: ExcerptId, editor: Entity<Editor>, context_store: Entity<ContextStore>, http_client: Arc<HttpClientWithUrl>, ) -> Completion { let new_text = MentionLink::for_fetch(&url_to_fetch); let new_text_len = new_text.len(); Completion { replace_range: source_range.clone(), new_text, label: CodeLabel::plain(url_to_fetch.to_string(), None), documentation: None, source: project::CompletionSource::Custom, icon_path: Some(IconName::Globe.path().into()), insert_text_mode: None, confirm: Some(confirm_completion_callback( IconName::Globe.path().into(), url_to_fetch.clone(), excerpt_id, source_range.start, new_text_len, editor.clone(), move |cx| { let context_store = context_store.clone(); let http_client = http_client.clone(); let url_to_fetch = url_to_fetch.clone(); cx.spawn(async move |cx| { if context_store.update(cx, |context_store, _| { context_store.includes_url(&url_to_fetch).is_some() })? { return Ok(()); } let content = cx .background_spawn(fetch_url_content( http_client, url_to_fetch.to_string(), )) .await?; context_store.update(cx, |context_store, cx| { context_store.add_fetched_url(url_to_fetch.to_string(), content, cx) }) }) .detach_and_log_err(cx); }, )), } } fn completion_for_path( project_path: ProjectPath, path_prefix: &str, is_recent: bool, is_directory: bool, excerpt_id: ExcerptId, source_range: Range<Anchor>, editor: Entity<Editor>, context_store: Entity<ContextStore>, cx: &App, ) -> Completion { let (file_name, directory) = super::file_context_picker::extract_file_name_and_directory( &project_path.path, path_prefix, ); let label = build_code_label_for_full_path(&file_name, directory.as_ref().map(|s| s.as_ref()), cx); let full_path = if let Some(directory) = directory { format!("{}{}", directory, file_name) } else { file_name.to_string() }; let crease_icon_path = if is_directory { FileIcons::get_folder_icon(false, cx).unwrap_or_else(|| IconName::Folder.path().into()) } else { FileIcons::get_icon(Path::new(&full_path), cx) .unwrap_or_else(|| IconName::File.path().into()) }; let completion_icon_path = if is_recent { IconName::HistoryRerun.path().into() } else { crease_icon_path.clone() }; let new_text = MentionLink::for_file(&file_name, &full_path); let new_text_len = new_text.len(); Completion { replace_range: source_range.clone(), new_text, label, documentation: None, source: project::CompletionSource::Custom, icon_path: Some(completion_icon_path), insert_text_mode: None, confirm: Some(confirm_completion_callback( crease_icon_path, file_name, excerpt_id, source_range.start, new_text_len, editor, move |cx| { context_store.update(cx, |context_store, cx| { let task = if is_directory { context_store.add_directory(project_path.clone(), false, cx) } else { context_store.add_file_from_path(project_path.clone(), false, cx) }; task.detach_and_log_err(cx); }) }, )), } } fn completion_for_symbol( symbol: Symbol, excerpt_id: ExcerptId, source_range: Range<Anchor>, editor: Entity<Editor>, context_store: Entity<ContextStore>, workspace: Entity<Workspace>, cx: &mut App, ) -> Option<Completion> { let path_prefix = workspace .read(cx) .project() .read(cx) .worktree_for_id(symbol.path.worktree_id, cx)? .read(cx) .root_name(); let (file_name, directory) = super::file_context_picker::extract_file_name_and_directory( &symbol.path.path, path_prefix, ); let full_path = if let Some(directory) = directory { format!("{}{}", directory, file_name) } else { file_name.to_string() }; let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId); let mut label = CodeLabel::plain(symbol.name.clone(), None); label.push_str(" ", None); label.push_str(&file_name, comment_id); let new_text = MentionLink::for_symbol(&symbol.name, &full_path); let new_text_len = new_text.len(); Some(Completion { replace_range: source_range.clone(), new_text, label, documentation: None, source: project::CompletionSource::Custom, icon_path: Some(IconName::Code.path().into()), insert_text_mode: None, confirm: Some(confirm_completion_callback( IconName::Code.path().into(), symbol.name.clone().into(), excerpt_id, source_range.start, new_text_len, editor.clone(), move |cx| { let symbol = symbol.clone(); let context_store = context_store.clone(); let workspace = workspace.clone(); super::symbol_context_picker::add_symbol( symbol.clone(), false, workspace.clone(), context_store.downgrade(), cx, ) .detach_and_log_err(cx); }, )), }) }} fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx: &App) -> CodeLabel { let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId); let mut label = CodeLabel::default(); label.push_str(&file_name, None); label.push_str(" ", None); if let Some(directory) = directory { label.push_str(&directory, comment_id); } label.filter_range = 0..label.text().len(); label} impl CompletionProvider for ContextPickerCompletionProvider { fn completions( &self, excerpt_id: ExcerptId, buffer: &Entity<Buffer>, buffer_position: Anchor, _trigger: CompletionContext, _window: &mut Window, cx: &mut Context<Editor>, ) -> Task<Result<Option<Vec<Completion>>>> { let state = buffer.update(cx, |buffer, _cx| { let position = buffer_position.to_point(buffer); let line_start = Point::new(position.row, 0); let offset_to_line = buffer.point_to_offset(line_start); let mut lines = buffer.text_for_range(line_start..position).lines(); let line = lines.next()?; MentionCompletion::try_parse(line, offset_to_line) }); let Some(state) = state else { return Task::ready(Ok(None)); }; let Some((workspace, context_store)) = self.workspace.upgrade().zip(self.context_store.upgrade()) else { return Task::ready(Ok(None)); }; let snapshot = buffer.read(cx).snapshot(); let source_range = snapshot.anchor_before(state.source_range.start) ..snapshot.anchor_before(state.source_range.end); let thread_store = self.thread_store.clone(); let editor = self.editor.clone(); let http_client = workspace.read(cx).client().http_client().clone(); let MentionCompletion { mode, argument, .. } = state; let query = argument.unwrap_or_else(|| "".to_string()); let recent_entries = recent_context_picker_entries( context_store.clone(), thread_store.clone(), workspace.clone(), cx, ); let search_task = search( mode, query, Arc::<AtomicBool>::default(), recent_entries, thread_store.clone(), workspace.clone(), cx, ); cx.spawn(async move |_, cx| { let matches = search_task.await; let Some(editor) = editor.upgrade() else { return Ok(None); }; Ok(Some(cx.update(|cx| {Lines 1–500 of 1106. Load more
use std::cell::RefCell; use std::ops::Range; use std::path::Path; use std::rc::Rc; use std::sync::Arc; use std::sync::atomic::AtomicBool; use anyhow::Result; use editor::{CompletionProvider, Editor, ExcerptId}; use file_icons::FileIcons; use gpui::{App, Entity, Task, WeakEntity}; use http_client::HttpClientWithUrl; use language::{Buffer, CodeLabel, HighlightId}; use lsp::CompletionContext; use project::{Completion, CompletionIntent, ProjectPath, Symbol, WorktreeId}; use rope::Point; use text::{Anchor, ToPoint}; use ui::prelude::*; use workspace::Workspace; use crate::context_picker::file_context_picker::search_files; use crate::context_picker::symbol_context_picker::search_symbols; use crate::context_store::ContextStore; use crate::thread_store::ThreadStore; use super::fetch_context_picker::fetch_url_content; use super::file_context_picker::FileMatch; use super::symbol_context_picker::SymbolMatch; use super::thread_context_picker::{ThreadContextEntry, ThreadMatch, search_threads}; use super::{ ContextPickerMode, MentionLink, RecentEntry, recent_context_picker_entries, supported_context_picker_modes, }; pub(crate) enum Match { Symbol(SymbolMatch), File(FileMatch), Thread(ThreadMatch), Fetch(SharedString), Mode(ContextPickerMode), } fn search( mode: Option<ContextPickerMode>, query: String, cancellation_flag: Arc<AtomicBool>, recent_entries: Vec<RecentEntry>, thread_store: Option<WeakEntity<ThreadStore>>, workspace: Entity<Workspace>, cx: &mut App, ) -> Task<Vec<Match>> { match mode { Some(ContextPickerMode::File) => { let search_files_task = search_files(query.clone(), cancellation_flag.clone(), &workspace, cx); cx.background_spawn(async move { search_files_task .await .into_iter() .map(Match::File) .collect() }) } Some(ContextPickerMode::Symbol) => { let search_symbols_task = search_symbols(query.clone(), cancellation_flag.clone(), &workspace, cx); cx.background_spawn(async move { search_symbols_task .await .into_iter() .map(Match::Symbol) .collect() }) } Some(ContextPickerMode::Thread) => { if let Some(thread_store) = thread_store.as_ref().and_then(|t| t.upgrade()) { let search_threads_task = search_threads(query.clone(), cancellation_flag.clone(), thread_store, cx); cx.background_spawn(async move { search_threads_task .await .into_iter() .map(Match::Thread) .collect() }) } else { Task::ready(Vec::new()) } } Some(ContextPickerMode::Fetch) => { if !query.is_empty() { Task::ready(vec![Match::Fetch(query.into())]) } else { Task::ready(Vec::new()) } } None => { if query.is_empty() { let mut matches = recent_entries .into_iter() .map(|entry| match entry { super::RecentEntry::File { project_path, path_prefix, } => Match::File(FileMatch { mat: fuzzy::PathMatch { score: 1., positions: Vec::new(), worktree_id: project_path.worktree_id.to_usize(), path: project_path.path, path_prefix, is_dir: false, distance_to_relative_ancestor: 0, }, is_recent: true, }), super::RecentEntry::Thread(thread_context_entry) => { Match::Thread(ThreadMatch { thread: thread_context_entry, is_recent: true, }) } }) .collect::<Vec<_>>(); matches.extend( supported_context_picker_modes(&thread_store) .into_iter() .map(Match::Mode), ); Task::ready(matches) } else { let search_files_task = search_files(query.clone(), cancellation_flag.clone(), &workspace, cx); cx.background_spawn(async move { search_files_task .await .into_iter() .map(Match::File) .collect() }) } } } } pub struct ContextPickerCompletionProvider { workspace: WeakEntity<Workspace>, context_store: WeakEntity<ContextStore>, thread_store: Option<WeakEntity<ThreadStore>>, editor: WeakEntity<Editor>, } impl ContextPickerCompletionProvider { pub fn new( workspace: WeakEntity<Workspace>, context_store: WeakEntity<ContextStore>, thread_store: Option<WeakEntity<ThreadStore>>, editor: WeakEntity<Editor>, ) -> Self { Self { workspace, context_store, thread_store, editor, } } fn completion_for_mode(source_range: Range<Anchor>, mode: ContextPickerMode) -> Completion { Completion { replace_range: source_range.clone(), new_text: format!("@{} ", mode.mention_prefix()), label: CodeLabel::plain(mode.label().to_string(), None), icon_path: Some(mode.icon().path().into()), documentation: None, source: project::CompletionSource::Custom, insert_text_mode: None, // This ensures that when a user accepts this completion, the // completion menu will still be shown after "@category " is // inserted confirm: Some(Arc::new(|_, _, _| true)), } } fn completion_for_thread( thread_entry: ThreadContextEntry, excerpt_id: ExcerptId, source_range: Range<Anchor>, recent: bool, editor: Entity<Editor>, context_store: Entity<ContextStore>, thread_store: Entity<ThreadStore>, ) -> Completion { let icon_for_completion = if recent { IconName::HistoryRerun } else { IconName::MessageBubbles }; let new_text = MentionLink::for_thread(&thread_entry); let new_text_len = new_text.len(); Completion { replace_range: source_range.clone(), new_text, label: CodeLabel::plain(thread_entry.summary.to_string(), None), documentation: None, insert_text_mode: None, source: project::CompletionSource::Custom, icon_path: Some(icon_for_completion.path().into()), confirm: Some(confirm_completion_callback( IconName::MessageBubbles.path().into(), thread_entry.summary.clone(), excerpt_id, source_range.start, new_text_len, editor.clone(), move |cx| { let thread_id = thread_entry.id.clone(); let context_store = context_store.clone(); let thread_store = thread_store.clone(); cx.spawn(async move |cx| { let thread = thread_store .update(cx, |thread_store, cx| { thread_store.open_thread(&thread_id, cx) })? .await?; context_store.update(cx, |context_store, cx| { context_store.add_thread(thread, false, cx) }) }) .detach_and_log_err(cx); }, )), } } fn completion_for_fetch( source_range: Range<Anchor>, url_to_fetch: SharedString, excerpt_id: ExcerptId, editor: Entity<Editor>, context_store: Entity<ContextStore>, http_client: Arc<HttpClientWithUrl>, ) -> Completion { let new_text = MentionLink::for_fetch(&url_to_fetch); let new_text_len = new_text.len(); Completion { replace_range: source_range.clone(), new_text, label: CodeLabel::plain(url_to_fetch.to_string(), None), documentation: None, source: project::CompletionSource::Custom, icon_path: Some(IconName::Globe.path().into()), insert_text_mode: None, confirm: Some(confirm_completion_callback( IconName::Globe.path().into(), url_to_fetch.clone(), excerpt_id, source_range.start, new_text_len, editor.clone(), move |cx| { let context_store = context_store.clone(); let http_client = http_client.clone(); let url_to_fetch = url_to_fetch.clone(); cx.spawn(async move |cx| { if context_store.update(cx, |context_store, _| { context_store.includes_url(&url_to_fetch).is_some() })? { return Ok(()); } let content = cx .background_spawn(fetch_url_content( http_client, url_to_fetch.to_string(), )) .await?; context_store.update(cx, |context_store, cx| { context_store.add_fetched_url(url_to_fetch.to_string(), content, cx) }) }) .detach_and_log_err(cx); }, )), } } fn completion_for_path( project_path: ProjectPath, path_prefix: &str, is_recent: bool, is_directory: bool, excerpt_id: ExcerptId, source_range: Range<Anchor>, editor: Entity<Editor>, context_store: Entity<ContextStore>, cx: &App, ) -> Completion { let (file_name, directory) = super::file_context_picker::extract_file_name_and_directory( &project_path.path, path_prefix, ); let label = build_code_label_for_full_path(&file_name, directory.as_ref().map(|s| s.as_ref()), cx); let full_path = if let Some(directory) = directory { format!("{}{}", directory, file_name) } else { file_name.to_string() }; let crease_icon_path = if is_directory { FileIcons::get_folder_icon(false, cx).unwrap_or_else(|| IconName::Folder.path().into()) } else { FileIcons::get_icon(Path::new(&full_path), cx) .unwrap_or_else(|| IconName::File.path().into()) }; let completion_icon_path = if is_recent { IconName::HistoryRerun.path().into() } else { crease_icon_path.clone() }; let new_text = MentionLink::for_file(&file_name, &full_path); let new_text_len = new_text.len(); Completion { replace_range: source_range.clone(), new_text, label, documentation: None, source: project::CompletionSource::Custom, icon_path: Some(completion_icon_path), insert_text_mode: None, confirm: Some(confirm_completion_callback( crease_icon_path, file_name, excerpt_id, source_range.start, new_text_len, editor, move |cx| { context_store.update(cx, |context_store, cx| { let task = if is_directory { context_store.add_directory(project_path.clone(), false, cx) } else { context_store.add_file_from_path(project_path.clone(), false, cx) }; task.detach_and_log_err(cx); }) }, )), } } fn completion_for_symbol( symbol: Symbol, excerpt_id: ExcerptId, source_range: Range<Anchor>, editor: Entity<Editor>, context_store: Entity<ContextStore>, workspace: Entity<Workspace>, cx: &mut App, ) -> Option<Completion> { let path_prefix = workspace .read(cx) .project() .read(cx) .worktree_for_id(symbol.path.worktree_id, cx)? .read(cx) .root_name(); let (file_name, directory) = super::file_context_picker::extract_file_name_and_directory( &symbol.path.path, path_prefix, ); let full_path = if let Some(directory) = directory { format!("{}{}", directory, file_name) } else { file_name.to_string() }; let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId); let mut label = CodeLabel::plain(symbol.name.clone(), None); label.push_str(" ", None); label.push_str(&file_name, comment_id); let new_text = MentionLink::for_symbol(&symbol.name, &full_path); let new_text_len = new_text.len(); Some(Completion { replace_range: source_range.clone(), new_text, label, documentation: None, source: project::CompletionSource::Custom, icon_path: Some(IconName::Code.path().into()), insert_text_mode: None, confirm: Some(confirm_completion_callback( IconName::Code.path().into(), symbol.name.clone().into(), excerpt_id, source_range.start, new_text_len, editor.clone(), move |cx| { let symbol = symbol.clone(); let context_store = context_store.clone(); let workspace = workspace.clone(); super::symbol_context_picker::add_symbol( symbol.clone(), false, workspace.clone(), context_store.downgrade(), cx, ) .detach_and_log_err(cx); }, )), }) } } fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx: &App) -> CodeLabel { let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId); let mut label = CodeLabel::default(); label.push_str(&file_name, None); label.push_str(" ", None); if let Some(directory) = directory { label.push_str(&directory, comment_id); } label.filter_range = 0..label.text().len(); label } impl CompletionProvider for ContextPickerCompletionProvider { fn completions( &self, excerpt_id: ExcerptId, buffer: &Entity<Buffer>, buffer_position: Anchor, _trigger: CompletionContext, _window: &mut Window, cx: &mut Context<Editor>, ) -> Task<Result<Option<Vec<Completion>>>> { let state = buffer.update(cx, |buffer, _cx| { let position = buffer_position.to_point(buffer); let line_start = Point::new(position.row, 0); let offset_to_line = buffer.point_to_offset(line_start); let mut lines = buffer.text_for_range(line_start..position).lines(); let line = lines.next()?; MentionCompletion::try_parse(line, offset_to_line) }); let Some(state) = state else { return Task::ready(Ok(None)); }; let Some((workspace, context_store)) = self.workspace.upgrade().zip(self.context_store.upgrade()) else { return Task::ready(Ok(None)); }; let snapshot = buffer.read(cx).snapshot(); let source_range = snapshot.anchor_before(state.source_range.start) ..snapshot.anchor_before(state.source_range.end); let thread_store = self.thread_store.clone(); let editor = self.editor.clone(); let http_client = workspace.read(cx).client().http_client().clone(); let MentionCompletion { mode, argument, .. } = state; let query = argument.unwrap_or_else(|| "".to_string()); let recent_entries = recent_context_picker_entries( context_store.clone(), thread_store.clone(), workspace.clone(), cx, ); let search_task = search( mode, query, Arc::<AtomicBool>::default(), recent_entries, thread_store.clone(), workspace.clone(), cx, ); cx.spawn(async move |_, cx| { let matches = search_task.await; let Some(editor) = editor.upgrade() else { return Ok(None); }; Ok(Some(cx.update(|cx| {