diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index c66d20f13f..349c980e8b 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -729,7 +729,6 @@ "use_key_equivalents": true, "bindings": { "space": "menu::Confirm", - "shift-r": "agent::RenameSelectedThread", }, }, { diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 61e719d712..23fd201b0d 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -785,7 +785,6 @@ "use_key_equivalents": true, "bindings": { "space": "menu::Confirm", - "shift-r": "agent::RenameSelectedThread", }, }, { diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json index 56a3bc4972..3eece808cc 100644 --- a/assets/keymaps/default-windows.json +++ b/assets/keymaps/default-windows.json @@ -732,7 +732,6 @@ "use_key_equivalents": true, "bindings": { "space": "menu::Confirm", - "shift-r": "agent::RenameSelectedThread", }, }, { diff --git a/crates/agent/src/db.rs b/crates/agent/src/db.rs index 341529fd32..a34290742a 100644 --- a/crates/agent/src/db.rs +++ b/crates/agent/src/db.rs @@ -596,68 +596,6 @@ impl ThreadsDatabase { .spawn(async move { Self::save_thread_sync(&connection, id, thread, &folder_paths) }) } - pub fn update_thread_title( - &self, - id: acp::SessionId, - title: SharedString, - ) -> Task> { - let connection = self.connection.clone(); - - self.executor - .spawn(async move { Self::update_thread_title_sync(&connection, id, title) }) - } - - fn update_thread_title_sync( - connection: &Arc>, - id: acp::SessionId, - title: SharedString, - ) -> Result { - const COMPRESSION_LEVEL: i32 = 3; - - #[derive(Serialize)] - struct SerializedThread { - #[serde(flatten)] - thread: DbThread, - version: &'static str, - } - - let connection = connection.lock(); - let mut select = connection.select_bound::, (DataType, Vec)>(indoc! {" - SELECT data_type, data FROM threads WHERE id = ? LIMIT 1 - "})?; - - let Some((data_type, data)) = select(id.0.clone())?.into_iter().next() else { - return Ok(false); - }; - - let json_data = match data_type { - DataType::Zstd => { - let decompressed = zstd::decode_all(&data[..])?; - String::from_utf8(decompressed)? - } - DataType::Json => String::from_utf8(data)?, - }; - let mut thread = DbThread::from_json(json_data.as_bytes())?; - thread.title = title.clone(); - - let json_data = serde_json::to_string(&SerializedThread { - thread, - version: DbThread::VERSION, - })?; - let compressed = zstd::encode_all(json_data.as_bytes(), COMPRESSION_LEVEL)?; - let data_type = DataType::Zstd; - let data = compressed; - let title = title.to_string(); - - let mut update = - connection.exec_bound::<(String, DataType, Vec, Arc)>(indoc! {" - UPDATE threads SET summary = ?1, data_type = ?2, data = ?3 WHERE id = ?4 - "})?; - - update((title, data_type, data, id.0))?; - Ok(true) - } - pub fn delete_thread(&self, id: acp::SessionId) -> Task> { let connection = self.connection.clone(); diff --git a/crates/agent/src/thread_store.rs b/crates/agent/src/thread_store.rs index 3477e7f8b7..e3c8b18645 100644 --- a/crates/agent/src/thread_store.rs +++ b/crates/agent/src/thread_store.rs @@ -2,7 +2,7 @@ use crate::{DbThread, DbThreadMetadata, ThreadsDatabase}; use agent_client_protocol::schema as acp; use anyhow::{Result, anyhow}; use futures::{FutureExt, future::Shared}; -use gpui::{App, Context, Entity, Global, SharedString, Task, prelude::*}; +use gpui::{App, Context, Entity, Global, Task, prelude::*}; use util::path_list::PathList; struct GlobalThreadStore(Entity); @@ -74,32 +74,6 @@ impl ThreadStore { }) } - pub fn update_thread_title( - &mut self, - id: acp::SessionId, - title: SharedString, - cx: &mut Context, - ) -> Task> { - let database_future = ThreadsDatabase::connect(cx); - cx.spawn(async move |this, cx| { - let database = database_future.await.map_err(|err| anyhow!(err))?; - let updated = database - .update_thread_title(id.clone(), title.clone()) - .await?; - if updated { - this.update(cx, |this, cx| { - if let Some(thread) = this.threads.iter_mut().find(|thread| thread.id == id) { - thread.title = title; - cx.notify(); - } else { - this.reload(cx); - } - })?; - } - Ok(updated) - }) - } - pub fn delete_thread( &mut self, id: acp::SessionId, @@ -257,40 +231,6 @@ mod tests { assert!(thread_store.read_with(cx, |store, _cx| store.is_empty())); } - #[gpui::test] - async fn test_update_thread_title_updates_metadata_and_thread_data(cx: &mut TestAppContext) { - let thread_store = cx.new(|cx| ThreadStore::new(cx)); - cx.run_until_parked(); - - let thread_id = session_id("thread-a"); - let updated_at = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(); - let thread = make_thread("Old Title", updated_at); - - let save_task = thread_store.update(cx, |store, cx| { - store.save_thread(thread_id.clone(), thread, PathList::default(), cx) - }); - save_task.await.unwrap(); - - let update_task = thread_store.update(cx, |store, cx| { - store.update_thread_title(thread_id.clone(), "New Title".into(), cx) - }); - assert!(update_task.await.unwrap()); - cx.run_until_parked(); - - let entries: Vec<_> = thread_store.read_with(cx, |store, _cx| store.entries().collect()); - let entry = entries - .iter() - .find(|entry| entry.id == thread_id) - .expect("updated thread should be present"); - assert_eq!(entry.title.as_ref(), "New Title"); - assert_eq!(entry.updated_at, updated_at); - - let load_task = thread_store.update(cx, |store, cx| store.load_thread(thread_id, cx)); - let loaded_thread = load_task.await.unwrap().expect("thread should load"); - assert_eq!(loaded_thread.title.as_ref(), "New Title"); - assert_eq!(loaded_thread.updated_at, updated_at); - } - #[gpui::test] async fn test_delete_thread_removes_only_target(cx: &mut TestAppContext) { let thread_store = cx.new(|cx| ThreadStore::new(cx)); diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index e2dee75c71..9dcb3d11e4 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -1211,7 +1211,7 @@ impl AgentPanel { pub(crate) fn new( workspace: &Workspace, prompt_store: Option>, - window: &mut Window, + _window: &mut Window, cx: &mut Context, ) -> Self { let fs = workspace.app_state().fs.clone(); @@ -1270,21 +1270,12 @@ impl AgentPanel { _ => {} }); - let _thread_metadata_store_subscription = cx.subscribe_in( + let _thread_metadata_store_subscription = cx.subscribe( &ThreadMetadataStore::global(cx), - window, - |this, _store, event, window, cx| match event { - ThreadMetadataStoreEvent::ThreadArchived(thread_id) => { - if this.retained_threads.remove(thread_id).is_some() { - cx.notify(); - } - } - ThreadMetadataStoreEvent::ThreadTitleUpdated { thread_id, title } => { - let thread_id = *thread_id; - let title = title.clone(); - cx.defer_in(window, move |this, window, cx| { - this.update_loaded_thread_title(thread_id, title, window, cx); - }); + |this, _store, event, cx| { + let ThreadMetadataStoreEvent::ThreadArchived(thread_id) = event; + if this.retained_threads.remove(thread_id).is_some() { + cx.notify(); } }, ); @@ -1645,7 +1636,7 @@ impl AgentPanel { None, Some(thread_id), Some(metadata.folder_paths().clone()), - metadata.title(), + metadata.title.clone(), initial_content, AgentThreadSource::AgentPanel, window, @@ -2836,42 +2827,6 @@ impl AgentPanel { } } - fn update_loaded_thread_title( - &mut self, - thread_id: ThreadId, - title: SharedString, - window: &mut Window, - cx: &mut Context, - ) { - let mut updated_loaded_thread = false; - let conversation_views = self.conversation_views(); - for conversation_view in conversation_views { - if conversation_view.read(cx).thread_id != thread_id { - continue; - } - let Some(thread_view) = conversation_view.read(cx).root_thread_view() else { - continue; - }; - updated_loaded_thread = true; - let (title_editor, thread) = thread_view.read_with(cx, |thread_view, _| { - (thread_view.title_editor.clone(), thread_view.thread.clone()) - }); - if title_editor.read(cx).text(cx) != title { - title_editor.update(cx, |editor, cx| { - editor.set_text(title.clone(), window, cx); - }); - } - thread.update(cx, |thread, cx| { - if thread.can_set_title(cx) { - thread.set_title(title.clone(), cx).detach_and_log_err(cx); - } - }); - } - if updated_loaded_thread { - cx.notify(); - } - } - /// Drops a thread — retained or the active ephemeral draft — from /// the panel and deletes its metadata row. Used by the sidebar when /// the user dismisses a parked draft. diff --git a/crates/agent_ui/src/agent_ui.rs b/crates/agent_ui/src/agent_ui.rs index 277b967552..209976c090 100644 --- a/crates/agent_ui/src/agent_ui.rs +++ b/crates/agent_ui/src/agent_ui.rs @@ -199,8 +199,6 @@ actions!( ArchiveSelectedThread, /// Removes the currently selected thread. RemoveSelectedThread, - /// Renames the currently selected thread. - RenameSelectedThread, /// Starts a chat conversation with follow-up enabled. ChatWithFollow, /// Cycles to the next inline assist suggestion. diff --git a/crates/agent_ui/src/thread_metadata_store.rs b/crates/agent_ui/src/thread_metadata_store.rs index c814ef4c92..f3bbdaa0c9 100644 --- a/crates/agent_ui/src/thread_metadata_store.rs +++ b/crates/agent_ui/src/thread_metadata_store.rs @@ -704,50 +704,18 @@ impl ThreadMetadataStore { thread_id: ThreadId, title_override: SharedString, cx: &mut Context, - ) -> bool { - let Some(existing) = self.entry(thread_id).cloned() else { - return false; + ) { + let Some(existing) = self.entry(thread_id) else { + return; }; if existing.title_override.as_ref() == Some(&title_override) { - return false; + return; } let metadata = ThreadMetadata { - title_override: Some(title_override.clone()), - ..existing + title_override: Some(title_override), + ..existing.clone() }; self.save(metadata, cx); - cx.emit(ThreadMetadataStoreEvent::ThreadTitleUpdated { - thread_id, - title: title_override, - }); - true - } - - pub fn rename_thread( - &mut self, - thread_id: ThreadId, - title: SharedString, - cx: &mut Context, - ) -> Task> { - let metadata = self.entry(thread_id).cloned(); - self.set_title_override(thread_id, title.clone(), cx); - - let Some(metadata) = metadata else { - return Task::ready(Ok(())); - }; - let Some(session_id) = metadata.session_id else { - return Task::ready(Ok(())); - }; - if metadata.agent_id.as_ref() != ZED_AGENT_ID.as_ref() { - return Task::ready(Ok(())); - } - let Some(thread_store) = ThreadStore::try_global(cx) else { - return Task::ready(Ok(())); - }; - let update_title = thread_store.update(cx, |store, cx| { - store.update_thread_title(session_id, title, cx) - }); - cx.spawn(async move |_, _| update_title.await.map(|_| ())) } fn save_internal(&mut self, metadata: ThreadMetadata) { @@ -1373,10 +1341,6 @@ impl Global for ThreadMetadataStore {} #[derive(Clone, Debug)] pub enum ThreadMetadataStoreEvent { ThreadArchived(ThreadId), - ThreadTitleUpdated { - thread_id: ThreadId, - title: SharedString, - }, } impl gpui::EventEmitter for ThreadMetadataStore {} @@ -2020,60 +1984,6 @@ mod tests { }); } - #[gpui::test] - async fn test_rename_thread_updates_native_thread_database(cx: &mut TestAppContext) { - init_test(cx); - - let session_id = acp::SessionId::new("session-1"); - let updated_at = Utc::now(); - let save_task = cx.update(|cx| { - ThreadStore::global(cx).update(cx, |store, cx| { - store.save_thread( - session_id.clone(), - make_db_thread("Agent Generated Title", updated_at), - PathList::default(), - cx, - ) - }) - }); - save_task.await.unwrap(); - - let metadata = make_metadata( - "session-1", - "Agent Generated Title", - updated_at, - PathList::default(), - ); - let thread_id = metadata.thread_id; - let rename_task = cx.update(|cx| { - let store = ThreadMetadataStore::global(cx); - store.update(cx, |store, cx| { - store.save(metadata, cx); - store.rename_thread(thread_id, "User Title".into(), cx) - }) - }); - rename_task.await.unwrap(); - cx.run_until_parked(); - - cx.update(|cx| { - let store = ThreadMetadataStore::global(cx); - let metadata = store - .read(cx) - .entry(thread_id) - .expect("metadata should be cached") - .clone(); - assert_eq!(metadata.title.as_deref(), Some("Agent Generated Title")); - assert_eq!(metadata.title_override.as_deref(), Some("User Title")); - }); - - let load_task = cx.update(|cx| { - ThreadStore::global(cx).update(cx, |store, cx| store.load_thread(session_id, cx)) - }); - let loaded_thread = load_task.await.unwrap().expect("thread should load"); - assert_eq!(loaded_thread.title.as_ref(), "User Title"); - assert_eq!(loaded_thread.updated_at, updated_at); - } - #[gpui::test] async fn test_store_initializes_cache_from_database(cx: &mut TestAppContext) { let first_paths = PathList::new(&[Path::new("/project-a")]); diff --git a/crates/sidebar/src/sidebar.rs b/crates/sidebar/src/sidebar.rs index 54573b70c3..6eef174192 100644 --- a/crates/sidebar/src/sidebar.rs +++ b/crates/sidebar/src/sidebar.rs @@ -18,8 +18,8 @@ use agent_ui::threads_archive_view::{ use agent_ui::{ AcpThreadImportOnboarding, Agent, AgentPanel, AgentPanelEvent, AgentThreadSource, ArchiveSelectedThread, CrossChannelImportOnboarding, DEFAULT_THREAD_TITLE, NewTerminalThread, - NewThread, RenameSelectedThread, TerminalId, ThreadId, ThreadImportModal, - channels_with_threads, import_threads_from_other_channels, + NewThread, TerminalId, ThreadId, ThreadImportModal, channels_with_threads, + import_threads_from_other_channels, }; use chrono::{DateTime, Utc}; use editor::Editor; @@ -637,7 +637,6 @@ pub struct Sidebar { width: Pixels, focus_handle: FocusHandle, filter_editor: Entity, - thread_rename_editor: Entity, list_state: ListState, contents: SidebarContents, /// The index of the list item that currently has the keyboard focus @@ -647,7 +646,6 @@ pub struct Sidebar { /// Tracks which sidebar entry is currently active (highlighted). active_entry: Option, hovered_thread_index: Option, - renaming_thread_id: Option, /// Updated only in response to explicit user actions (clicking a /// thread, confirming in the thread switcher, etc.) — never from @@ -693,7 +691,6 @@ impl Sidebar { editor.set_placeholder_text("Search threads…", window, cx); editor }); - let thread_rename_editor = cx.new(|cx| Editor::single_line(window, cx)); cx.subscribe_in( &multi_workspace, @@ -730,17 +727,6 @@ impl Sidebar { }) .detach(); - cx.subscribe_in( - &thread_rename_editor, - window, - |this, _, event, window, cx| { - if matches!(event, editor::EditorEvent::Blurred) { - this.confirm_thread_rename(window, cx); - } - }, - ) - .detach(); - cx.observe(&ThreadMetadataStore::global(cx), |this, _store, cx| { this.update_entries(cx); }) @@ -770,13 +756,11 @@ impl Sidebar { width: DEFAULT_WIDTH, focus_handle, filter_editor, - thread_rename_editor, list_state: ListState::new(0, gpui::ListAlignment::Top, px(1000.)), contents: SidebarContents::default(), selection: None, active_entry: None, hovered_thread_index: None, - renaming_thread_id: None, thread_last_accessed: HashMap::new(), terminal_last_accessed: HashMap::new(), @@ -1691,8 +1675,12 @@ impl Sidebar { let mut matched_threads: Vec = Vec::new(); for mut thread in threads { - let title = thread.metadata.display_title(); - if let Some(positions) = fuzzy_match_positions(&query, title.as_ref()) { + let title: &str = thread + .metadata + .title + .as_ref() + .map_or(DEFAULT_THREAD_TITLE, |t| t.as_ref()); + if let Some(positions) = fuzzy_match_positions(&query, title) { thread.highlight_positions = positions; } let mut worktree_matched = false; @@ -2744,17 +2732,10 @@ impl Sidebar { let is_archived_search_focused = matches!(&self.view, SidebarView::Archive(archive) if archive.read(cx).is_filter_editor_focused(window, cx)); - let is_renaming_thread = self - .thread_rename_editor - .focus_handle(cx) - .is_focused(window); - let identifier = if self.filter_editor.focus_handle(cx).is_focused(window) || is_archived_search_focused { "searching" - } else if is_renaming_thread { - "editing" } else { "not_searching" }; @@ -2779,11 +2760,6 @@ impl Sidebar { } fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context) { - if self.renaming_thread_id.is_some() { - self.cancel_thread_rename(window, cx); - return; - } - if self.filter_editor.read(cx).is_focused(window) { if self.reset_filter_editor_text(window, cx) { self.selection = None; @@ -2844,61 +2820,6 @@ impl Sidebar { !self.filter_editor.read(cx).text(cx).is_empty() } - fn start_renaming_thread( - &mut self, - ix: usize, - thread_id: ThreadId, - title: SharedString, - window: &mut Window, - cx: &mut Context, - ) { - if self.renaming_thread_id.is_some() && self.renaming_thread_id != Some(thread_id) { - self.confirm_thread_rename(window, cx); - } - - self.selection = Some(ix); - self.renaming_thread_id = Some(thread_id); - self.list_state.scroll_to_reveal_item(ix); - self.thread_rename_editor.update(cx, |editor, cx| { - editor.set_text(title, window, cx); - editor.select_all(&editor::actions::SelectAll, window, cx); - editor.focus_handle(cx).focus(window, cx); - }); - cx.notify(); - } - - fn confirm_thread_rename(&mut self, window: &mut Window, cx: &mut Context) -> bool { - let Some(thread_id) = self.renaming_thread_id.take() else { - return false; - }; - - let new_title = self - .thread_rename_editor - .read(cx) - .text(cx) - .trim() - .to_string(); - if !new_title.is_empty() { - let title = SharedString::from(new_title); - ThreadMetadataStore::global(cx).update(cx, |store, cx| { - store - .rename_thread(thread_id, title, cx) - .detach_and_log_err(cx); - }); - } - - self.focus_handle.focus(window, cx); - self.update_entries(cx); - true - } - - fn cancel_thread_rename(&mut self, window: &mut Window, cx: &mut Context) { - if self.renaming_thread_id.take().is_some() { - self.focus_handle.focus(window, cx); - cx.notify(); - } - } - fn editor_move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context) { self.select_next(&SelectNext, window, cx); if self.selection.is_some() { @@ -2973,10 +2894,6 @@ impl Sidebar { } fn confirm(&mut self, _: &Confirm, window: &mut Window, cx: &mut Context) { - if self.confirm_thread_rename(window, cx) { - return; - } - let Some(ix) = self.selection else { return }; let Some(entry) = self.contents.entries.get(ix) else { return; @@ -3068,7 +2985,7 @@ impl Sidebar { Agent::from(metadata.agent_id.clone()), metadata.thread_id, Some(metadata.folder_paths().clone()), - metadata.title(), + metadata.title.clone(), focus, AgentThreadSource::Sidebar, window, @@ -5077,23 +4994,6 @@ impl Sidebar { } } - fn rename_selected_thread( - &mut self, - _: &RenameSelectedThread, - window: &mut Window, - cx: &mut Context, - ) { - let Some(ix) = self.selection else { - return; - }; - let Some(ListEntry::Thread(thread)) = self.contents.entries.get(ix) else { - return; - }; - let thread_id = thread.metadata.thread_id; - let title = thread.metadata.display_title(); - self.start_renaming_thread(ix, thread_id, title, window, cx); - } - fn record_thread_access(&mut self, id: &ThreadId) { self.thread_last_accessed.insert(*id, Utc::now()); } @@ -5524,12 +5424,10 @@ impl Sidebar { thread.status, AgentThreadStatus::Running | AgentThreadStatus::WaitingForConfirmation ); - let is_renaming = self.renaming_thread_id == Some(thread.metadata.thread_id); let thread_id_for_actions = thread.metadata.thread_id; let session_id_for_delete = thread.metadata.session_id.clone(); let focus_handle = self.focus_handle.clone(); - let title_editor = self.thread_rename_editor.clone(); let id = SharedString::from(format!("thread-entry-{}", ix)); @@ -5553,7 +5451,7 @@ impl Sidebar { (thread.icon, thread.icon_from_external_svg.clone()) }; - ThreadItem::new(id, title.clone()) + ThreadItem::new(id, title) .base_bg(sidebar_bg) .icon(icon) .when(is_draft, |this| { @@ -5586,84 +5484,22 @@ impl Sidebar { } cx.notify(); })) - .when(is_renaming, |this| { - this.title_slot( - div() - .h_6() - .min_w_0() - .flex_1() - .capture_action(cx.listener( - |this, _: &editor::actions::Newline, window, cx| { - this.confirm_thread_rename(window, cx); - }, - )) - .on_action(cx.listener(|this, _: &Confirm, window, cx| { - this.confirm_thread_rename(window, cx); - })) - .on_action( - cx.listener(|this, _: &editor::actions::Cancel, window, cx| { - this.cancel_thread_rename(window, cx); - }), - ) - .child(title_editor), - ) - }) - .when(is_hovered && !is_renaming, |this| { - let rename_button = IconButton::new(("rename-thread", ix), IconName::Pencil) - .icon_size(IconSize::Small) - .icon_color(Color::Muted) - .tooltip({ - let focus_handle = focus_handle.clone(); - move |_window, cx| { - Tooltip::for_action_in( - "Rename Thread", - &RenameSelectedThread, - &focus_handle, - cx, - ) - } - }) - .on_click({ - let title = title.clone(); - cx.listener(move |this, _, window, cx| { - this.start_renaming_thread( - ix, - thread_id_for_actions, - title.clone(), - window, - cx, - ); - }) - }); - - let contextual_action = if is_running { + .when(is_hovered && is_running, |this| { + this.action_slot( IconButton::new("stop-thread", IconName::Stop) .icon_size(IconSize::Small) .icon_color(Color::Error) .style(ButtonStyle::Tinted(TintColor::Error)) .tooltip(Tooltip::text("Stop Generation")) - .on_click(cx.listener(move |this, _, _window, cx| { - this.stop_thread(&thread_id_for_actions, cx); - })) - .into_any_element() - } else if is_draft { - IconButton::new("discard_thread", IconName::Close) - .icon_size(IconSize::Small) - .icon_color(Color::Muted) - .tooltip(Tooltip::text("Discard Draft")) .on_click({ - let thread_workspace = thread_workspace.clone(); - cx.listener(move |this, _, window, cx| { - this.remove_draft( - thread_id_for_actions, - &thread_workspace, - window, - cx, - ); + cx.listener(move |this, _, _window, cx| { + this.stop_thread(&thread_id_for_actions, cx); }) - }) - .into_any_element() - } else { + }), + ) + }) + .when(is_hovered && !is_running && !is_draft, |this| { + this.action_slot( IconButton::new("archive-thread", IconName::Archive) .icon_size(IconSize::Small) .icon_color(Color::Muted) @@ -5685,15 +5521,26 @@ impl Sidebar { this.archive_thread(session_id, window, cx); } }) - }) - .into_any_element() - }; - + }), + ) + }) + .when(is_hovered && !is_running && is_draft, |this| { this.action_slot( - h_flex() - .gap_0p5() - .child(rename_button) - .child(contextual_action), + IconButton::new("discard_thread", IconName::Close) + .icon_size(IconSize::Small) + .icon_color(Color::Muted) + .tooltip(Tooltip::text("Discard Draft")) + .on_click({ + let thread_workspace = thread_workspace.clone(); + cx.listener(move |this, _, window, cx| { + this.remove_draft( + thread_id_for_actions, + &thread_workspace, + window, + cx, + ); + }) + }), ) }) .on_click({ @@ -7120,7 +6967,6 @@ impl Render for Sidebar { .on_action(cx.listener(Self::unfold_all)) .on_action(cx.listener(Self::cancel)) .on_action(cx.listener(Self::archive_selected_thread)) - .on_action(cx.listener(Self::rename_selected_thread)) .on_action(cx.listener(Self::new_thread_in_group)) .on_action(cx.listener(Self::new_terminal_thread)) .on_action(cx.listener(Self::toggle_archive)) diff --git a/crates/sidebar/src/sidebar_tests.rs b/crates/sidebar/src/sidebar_tests.rs index 36818a83b8..a7a7263daa 100644 --- a/crates/sidebar/src/sidebar_tests.rs +++ b/crates/sidebar/src/sidebar_tests.rs @@ -4739,194 +4739,6 @@ async fn test_thread_title_update_propagates_to_sidebar(cx: &mut TestAppContext) ); } -#[gpui::test] -async fn test_rename_thread_from_sidebar_updates_title_override(cx: &mut TestAppContext) { - let project = init_test_project_with_agent_panel("/my-project", cx).await; - let (multi_workspace, cx) = - cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx)); - let (sidebar, panel) = setup_sidebar_with_agent_panel(&multi_workspace, cx); - - let connection = StubAgentConnection::new(); - connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk( - acp::ContentChunk::new("Hi there!".into()), - )]); - open_thread_with_connection(&panel, connection, cx); - send_message(&panel, cx); - - let session_id = active_session_id(&panel, cx); - save_test_thread_metadata(&session_id, &project, cx).await; - cx.run_until_parked(); - - let (entry_ix, thread_id, title) = sidebar.read_with(cx, |sidebar, _cx| { - sidebar - .contents - .entries - .iter() - .enumerate() - .find_map(|(ix, entry)| match entry { - ListEntry::Thread(thread) => Some(( - ix, - thread.metadata.thread_id, - thread.metadata.display_title(), - )), - ListEntry::ProjectHeader { .. } | ListEntry::Terminal(_) => None, - }) - .expect("sidebar should have a thread entry") - }); - - let renamed_title = "abcdefghijklmnopqrstuvwxyé renamed"; - sidebar.update_in(cx, |sidebar, window, cx| { - sidebar.start_renaming_thread(entry_ix, thread_id, title, window, cx); - sidebar.thread_rename_editor.update(cx, |editor, cx| { - editor.set_text(renamed_title, window, cx); - }); - sidebar.confirm_thread_rename(window, cx); - }); - cx.run_until_parked(); - - let metadata = cx.update(|_, cx| { - ThreadMetadataStore::global(cx) - .read(cx) - .entry(thread_id) - .cloned() - .expect("thread metadata should exist") - }); - assert_eq!(metadata.title_override.as_deref(), Some(renamed_title)); - assert_eq!( - visible_entries_as_strings(&sidebar, cx), - vec![ - // - "v [my-project]", - " abcdefghijklmnopqrstuvwxyé renamed * <== selected", - ] - ); - - let active_thread = panel.read_with(cx, |panel, cx| panel.active_agent_thread(cx).unwrap()); - assert_eq!( - active_thread.read_with(cx, |thread, _| thread.title()), - Some(renamed_title.into()) - ); - let active_thread_view = panel.read_with(cx, |panel, cx| panel.active_thread_view(cx).unwrap()); - let title_editor_text = - active_thread_view.read_with(cx, |view, cx| view.title_editor.read(cx).text(cx)); - assert_eq!(title_editor_text, renamed_title); - - active_thread.update(cx, |thread, cx| { - thread - .set_title("abcdefghijklmnopqrstuvwxyz0".into(), cx) - .detach(); - }); - cx.run_until_parked(); - - assert_eq!( - visible_entries_as_strings(&sidebar, cx), - vec![ - // - "v [my-project]", - " abcdefghijklmnopqrstuvwxyé renamed * <== selected", - ] - ); - - type_in_search(&sidebar, "0", cx); - assert_eq!( - visible_entries_as_strings(&sidebar, cx), - Vec::::new() - ); - - type_in_search(&sidebar, "é", cx); - assert_eq!( - visible_entries_as_strings(&sidebar, cx), - vec![ - // - "v [my-project]", - " abcdefghijklmnopqrstuvwxyé renamed * <== selected", - ] - ); - sidebar.read_with(cx, |sidebar, _cx| { - let thread = sidebar - .contents - .entries - .iter() - .find_map(|entry| match entry { - ListEntry::Thread(thread) => Some(thread), - ListEntry::ProjectHeader { .. } | ListEntry::Terminal(_) => None, - }) - .expect("renamed thread should match the search"); - let title = thread.metadata.display_title(); - assert!( - thread - .highlight_positions - .iter() - .all(|position| { title.is_char_boundary(*position) }) - ); - }); -} - -#[gpui::test] -async fn test_rename_selected_thread_action_renames_selected_thread(cx: &mut TestAppContext) { - let project = init_test_project_with_agent_panel("/my-project", cx).await; - let (multi_workspace, cx) = - cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx)); - let (sidebar, panel) = setup_sidebar_with_agent_panel(&multi_workspace, cx); - - let connection = StubAgentConnection::new(); - connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk( - acp::ContentChunk::new("Hi there!".into()), - )]); - open_thread_with_connection(&panel, connection, cx); - send_message(&panel, cx); - - let session_id = active_session_id(&panel, cx); - save_test_thread_metadata(&session_id, &project, cx).await; - cx.run_until_parked(); - - let (entry_ix, thread_id) = sidebar.read_with(cx, |sidebar, _cx| { - sidebar - .contents - .entries - .iter() - .enumerate() - .find_map(|(ix, entry)| match entry { - ListEntry::Thread(thread) => Some((ix, thread.metadata.thread_id)), - ListEntry::ProjectHeader { .. } | ListEntry::Terminal(_) => None, - }) - .expect("sidebar should have a thread entry") - }); - - focus_sidebar(&sidebar, cx); - sidebar.update_in(cx, |sidebar, _window, _cx| { - sidebar.selection = Some(entry_ix); - }); - cx.dispatch_action(RenameSelectedThread); - cx.run_until_parked(); - - sidebar.read_with(cx, |sidebar, _cx| { - assert_eq!( - sidebar.renaming_thread_id, - Some(thread_id), - "dispatching RenameSelectedThread should start renaming the selected thread" - ); - }); - - let renamed_title = "Renamed via action"; - sidebar.update_in(cx, |sidebar, window, cx| { - sidebar.thread_rename_editor.update(cx, |editor, cx| { - editor.set_text(renamed_title, window, cx); - }); - sidebar.confirm_thread_rename(window, cx); - }); - cx.run_until_parked(); - - let metadata = cx.update(|_, cx| { - ThreadMetadataStore::global(cx) - .read(cx) - .entry(thread_id) - .cloned() - .expect("thread metadata should exist") - }); - assert_eq!(metadata.title_override.as_deref(), Some(renamed_title)); -} - #[gpui::test] async fn test_focused_thread_tracks_user_intent(cx: &mut TestAppContext) { let project_a = init_test_project_with_agent_panel("/project-a", cx).await; diff --git a/crates/ui/src/components/ai/thread_item.rs b/crates/ui/src/components/ai/thread_item.rs index a0a5ae3808..c962aa4f9f 100644 --- a/crates/ui/src/components/ai/thread_item.rs +++ b/crates/ui/src/components/ai/thread_item.rs @@ -39,7 +39,6 @@ pub struct ThreadItem { icon_visible: bool, custom_icon_from_external_svg: Option, title: SharedString, - title_slot: Option, title_label_color: Option, title_generating: bool, highlight_positions: Vec, @@ -72,7 +71,6 @@ impl ThreadItem { icon_visible: true, custom_icon_from_external_svg: None, title: title.into(), - title_slot: None, title_label_color: None, title_generating: false, highlight_positions: Vec::new(), @@ -142,11 +140,6 @@ impl ThreadItem { self } - pub fn title_slot(mut self, element: impl IntoElement) -> Self { - self.title_slot = Some(element.into_any_element()); - self - } - pub fn highlight_positions(mut self, positions: Vec) -> Self { self.highlight_positions = positions; self @@ -324,9 +317,7 @@ impl RenderOnce for ThreadItem { let title = self.title; let highlight_positions = self.highlight_positions; - let title_label = if let Some(title_slot) = self.title_slot { - title_slot - } else if self.title_generating { + let title_label = if self.title_generating { Label::new(title) .color(Color::Muted) .with_animation(