Detailed changes
@@ -24,7 +24,7 @@ pub enum AgentConnectionEntry {
#[derive(Clone)]
pub struct AgentConnectedState {
pub connection: Rc<dyn AgentConnection>,
- pub history: Entity<ThreadHistory>,
+ pub history: Option<Entity<ThreadHistory>>,
}
impl AgentConnectionEntry {
@@ -38,7 +38,7 @@ impl AgentConnectionEntry {
pub fn history(&self) -> Option<&Entity<ThreadHistory>> {
match self {
- AgentConnectionEntry::Connected(state) => Some(&state.history),
+ AgentConnectionEntry::Connected(state) => state.history.as_ref(),
_ => None,
}
}
@@ -163,7 +163,9 @@ impl AgentConnectionStore {
let connect_task = server.connect(delegate, cx);
let connect_task = cx.spawn(async move |_this, cx| match connect_task.await {
Ok(connection) => cx.update(|cx| {
- let history = cx.new(|cx| ThreadHistory::new(connection.session_list(cx), cx));
+ let history = connection
+ .session_list(cx)
+ .map(|session_list| cx.new(|cx| ThreadHistory::new(session_list, cx)));
Ok(AgentConnectedState {
connection,
history,
@@ -1461,13 +1461,9 @@ impl AgentPanel {
.read(cx)
.history()?
.clone();
- if history.read(cx).has_session_list() {
- Some(History::AgentThreads {
- view: self.create_thread_history_view(agent, history, window, cx),
- })
- } else {
- None
- }
+ Some(History::AgentThreads {
+ view: self.create_thread_history_view(agent, history, window, cx),
+ })
}
}
}
@@ -4650,16 +4646,13 @@ impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
let Some(panel) = workspace.read(cx).panel::<AgentPanel>(cx) else {
return;
};
- let Some(history) = panel
+ let history = panel
.read(cx)
.connection_store()
.read(cx)
.entry(&crate::Agent::NativeAgent)
.and_then(|s| s.read(cx).history())
- else {
- log::error!("No connection entry found for native agent");
- return;
- };
+ .map(|h| h.downgrade());
let project = workspace.read(cx).project().downgrade();
let panel = panel.read(cx);
let thread_store = panel.thread_store().clone();
@@ -4669,7 +4662,7 @@ impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
project,
thread_store,
None,
- history.downgrade(),
+ history,
initial_prompt,
window,
cx,
@@ -223,7 +223,7 @@ pub struct PromptCompletionProvider<T: PromptCompletionProviderDelegate> {
source: Arc<T>,
editor: WeakEntity<Editor>,
mention_set: Entity<MentionSet>,
- history: WeakEntity<ThreadHistory>,
+ history: Option<WeakEntity<ThreadHistory>>,
prompt_store: Option<Entity<PromptStore>>,
workspace: WeakEntity<Workspace>,
}
@@ -233,7 +233,7 @@ impl<T: PromptCompletionProviderDelegate> PromptCompletionProvider<T> {
source: T,
editor: WeakEntity<Editor>,
mention_set: Entity<MentionSet>,
- history: WeakEntity<ThreadHistory>,
+ history: Option<WeakEntity<ThreadHistory>>,
prompt_store: Option<Entity<PromptStore>>,
workspace: WeakEntity<Workspace>,
) -> Self {
@@ -920,7 +920,7 @@ impl<T: PromptCompletionProviderDelegate> PromptCompletionProvider<T> {
}
Some(PromptContextType::Thread) => {
- if let Some(history) = self.history.upgrade() {
+ if let Some(history) = self.history.as_ref().and_then(|h| h.upgrade()) {
let sessions = history
.read(cx)
.sessions()
@@ -1146,7 +1146,7 @@ impl<T: PromptCompletionProviderDelegate> PromptCompletionProvider<T> {
return Task::ready(recent);
}
- if let Some(history) = self.history.upgrade() {
+ if let Some(history) = self.history.as_ref().and_then(|h| h.upgrade()) {
const RECENT_COUNT: usize = 2;
recent.extend(
history
@@ -421,7 +421,7 @@ pub struct ConnectedServerState {
active_id: Option<acp::SessionId>,
threads: HashMap<acp::SessionId, Entity<ThreadView>>,
connection: Rc<dyn AgentConnection>,
- history: Entity<ThreadHistory>,
+ history: Option<Entity<ThreadHistory>>,
conversation: Entity<Conversation>,
_connection_entry_subscription: Subscription,
}
@@ -816,7 +816,7 @@ impl ConversationView {
conversation: Entity<Conversation>,
resumed_without_history: bool,
initial_content: Option<AgentInitialContent>,
- history: Entity<ThreadHistory>,
+ history: Option<Entity<ThreadHistory>>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Entity<ThreadView> {
@@ -833,7 +833,7 @@ impl ConversationView {
self.workspace.clone(),
self.project.downgrade(),
self.thread_store.clone(),
- history.downgrade(),
+ history.as_ref().map(|h| h.downgrade()),
self.prompt_store.clone(),
prompt_capabilities.clone(),
available_commands.clone(),
@@ -1082,7 +1082,7 @@ impl ConversationView {
threads: HashMap::default(),
connection,
conversation: cx.new(|_cx| Conversation::default()),
- history: cx.new(|cx| ThreadHistory::new(None, cx)),
+ history: None,
_connection_entry_subscription: Subscription::new(|| {}),
}),
cx,
@@ -2213,7 +2213,7 @@ impl ConversationView {
let Some(connected) = self.as_connected() else {
return;
};
- let history = connected.history.downgrade();
+ let history = connected.history.as_ref().map(|h| h.downgrade());
let Some(thread) = connected.active_view() else {
return;
};
@@ -2601,7 +2601,7 @@ impl ConversationView {
}
pub fn history(&self) -> Option<&Entity<ThreadHistory>> {
- self.as_connected().map(|c| &c.history)
+ self.as_connected().and_then(|c| c.history.as_ref())
}
pub fn delete_history_entry(&mut self, session_id: &acp::SessionId, cx: &mut Context<Self>) {
@@ -2609,9 +2609,10 @@ impl ConversationView {
return;
};
- let task = connected
- .history
- .update(cx, |history, cx| history.delete_session(&session_id, cx));
+ let Some(history) = &connected.history else {
+ return;
+ };
+ let task = history.update(cx, |history, cx| history.delete_session(&session_id, cx));
task.detach_and_log_err(cx);
if let Some(store) = ThreadMetadataStore::try_global(cx) {
@@ -2902,60 +2903,14 @@ pub(crate) mod tests {
let session_a = AgentSessionInfo::new(SessionId::new("session-a"));
let session_b = AgentSessionInfo::new(SessionId::new("session-b"));
- let fs = FakeFs::new(cx.executor());
- let project = Project::test(fs, [], cx).await;
- let (multi_workspace, cx) =
- cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
- let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
-
- let thread_store = cx.update(|_window, cx| cx.new(|cx| ThreadStore::new(cx)));
- let connection_store =
- cx.update(|_window, cx| cx.new(|cx| AgentConnectionStore::new(project.clone(), cx)));
-
- let conversation_view = cx.update(|window, cx| {
- cx.new(|cx| {
- ConversationView::new(
- Rc::new(StubAgentServer::default_response()),
- connection_store,
- Agent::Custom { id: "Test".into() },
- None,
- None,
- None,
- None,
- workspace.downgrade(),
- project,
- Some(thread_store),
- None,
- window,
- cx,
- )
- })
- });
-
- // Wait for connection to establish
- cx.run_until_parked();
-
- let history = cx.update(|_window, cx| {
- conversation_view
- .read(cx)
- .history()
- .expect("Missing history")
- .clone()
- });
-
- // Initially empty because StubAgentConnection.session_list() returns None
- active_thread(&conversation_view, cx).read_with(cx, |view, _cx| {
- assert_eq!(view.recent_history_entries.len(), 0);
- });
-
- // Now set the session list - this simulates external agents providing their history
- let list_a: Rc<dyn AgentSessionList> =
- Rc::new(StubSessionList::new(vec![session_a.clone()]));
- history.update(cx, |history, cx| {
- history.set_session_list(Some(list_a), cx);
- });
- cx.run_until_parked();
+ // Use a connection that provides a session list so ThreadHistory is created
+ let (conversation_view, history, cx) = setup_thread_view_with_history(
+ StubAgentServer::new(SessionHistoryConnection::new(vec![session_a.clone()])),
+ cx,
+ )
+ .await;
+ // Initially has session_a from the connection's session list
active_thread(&conversation_view, cx).read_with(cx, |view, _cx| {
assert_eq!(view.recent_history_entries.len(), 1);
assert_eq!(
@@ -2964,11 +2919,11 @@ pub(crate) mod tests {
);
});
- // Update to a different session list
+ // Swap to a different session list
let list_b: Rc<dyn AgentSessionList> =
Rc::new(StubSessionList::new(vec![session_b.clone()]));
history.update(cx, |history, cx| {
- history.set_session_list(Some(list_b), cx);
+ history.set_session_list(list_b, cx);
});
cx.run_until_parked();
@@ -2986,19 +2941,12 @@ pub(crate) mod tests {
init_test(cx);
let session = AgentSessionInfo::new(SessionId::new("history-session"));
- let (conversation_view, history, cx) = setup_thread_view_with_history(
+ let (conversation_view, _history, cx) = setup_thread_view_with_history(
StubAgentServer::new(SessionHistoryConnection::new(vec![session.clone()])),
cx,
)
.await;
- history.read_with(cx, |history, _cx| {
- assert!(
- history.has_session_list(),
- "session list should be attached after thread creation"
- );
- });
-
active_thread(&conversation_view, cx).read_with(cx, |view, _cx| {
assert_eq!(view.recent_history_entries.len(), 1);
assert_eq!(
@@ -228,8 +228,8 @@ pub struct ThreadView {
pub hovered_recent_history_item: Option<usize>,
pub show_external_source_prompt_warning: bool,
pub show_codex_windows_warning: bool,
- pub history: Entity<ThreadHistory>,
- pub _history_subscription: Subscription,
+ pub history: Option<Entity<ThreadHistory>>,
+ pub _history_subscription: Option<Subscription>,
}
impl Focusable for ThreadView {
fn focus_handle(&self, cx: &App) -> FocusHandle {
@@ -273,7 +273,7 @@ impl ThreadView {
resumed_without_history: bool,
project: WeakEntity<Project>,
thread_store: Option<Entity<ThreadStore>>,
- history: Entity<ThreadHistory>,
+ history: Option<Entity<ThreadHistory>>,
prompt_store: Option<Entity<PromptStore>>,
initial_content: Option<AgentInitialContent>,
mut subscriptions: Vec<Subscription>,
@@ -284,8 +284,10 @@ impl ThreadView {
let placeholder = placeholder_text(agent_display_name.as_ref(), false);
- let history_subscription = cx.observe(&history, |this, history, cx| {
- this.update_recent_history_from_cache(&history, cx);
+ let history_subscription = history.as_ref().map(|h| {
+ cx.observe(h, |this, history, cx| {
+ this.update_recent_history_from_cache(&history, cx);
+ })
});
let mut should_auto_submit = false;
@@ -296,7 +298,7 @@ impl ThreadView {
workspace.clone(),
project.clone(),
thread_store,
- history.downgrade(),
+ history.as_ref().map(|h| h.downgrade()),
prompt_store,
prompt_capabilities.clone(),
available_commands.clone(),
@@ -392,7 +394,10 @@ impl ThreadView {
}));
}));
- let recent_history_entries = history.read(cx).get_recent_sessions(3);
+ let recent_history_entries = history
+ .as_ref()
+ .map(|h| h.read(cx).get_recent_sessions(3))
+ .unwrap_or_default();
let mut this = Self {
id,
@@ -7502,7 +7507,10 @@ impl ThreadView {
),
)
.child(v_flex().p_1().pr_1p5().gap_1().children({
- let supports_delete = self.history.read(cx).supports_delete();
+ let supports_delete = self
+ .history
+ .as_ref()
+ .map_or(false, |h| h.read(cx).supports_delete());
recent_history
.into_iter()
.enumerate()
@@ -26,7 +26,7 @@ pub struct EntryViewState {
workspace: WeakEntity<Workspace>,
project: WeakEntity<Project>,
thread_store: Option<Entity<ThreadStore>>,
- history: WeakEntity<ThreadHistory>,
+ history: Option<WeakEntity<ThreadHistory>>,
prompt_store: Option<Entity<PromptStore>>,
entries: Vec<Entry>,
prompt_capabilities: Rc<RefCell<acp::PromptCapabilities>>,
@@ -39,7 +39,7 @@ impl EntryViewState {
workspace: WeakEntity<Workspace>,
project: WeakEntity<Project>,
thread_store: Option<Entity<ThreadStore>>,
- history: WeakEntity<ThreadHistory>,
+ history: Option<WeakEntity<ThreadHistory>>,
prompt_store: Option<Entity<PromptStore>>,
prompt_capabilities: Rc<RefCell<acp::PromptCapabilities>>,
available_commands: Rc<RefCell<Vec<acp::AvailableCommand>>>,
@@ -510,14 +510,14 @@ mod tests {
});
let thread_store = None;
- let history = cx.update(|_window, cx| cx.new(|cx| crate::ThreadHistory::new(None, cx)));
+ let history: Option<gpui::WeakEntity<crate::ThreadHistory>> = None;
let view_state = cx.new(|_cx| {
EntryViewState::new(
workspace.downgrade(),
project.downgrade(),
thread_store,
- history.downgrade(),
+ history,
None,
Default::default(),
Default::default(),
@@ -278,15 +278,11 @@ impl InlineAssistant {
let prompt_store = agent_panel.prompt_store().as_ref().cloned();
let thread_store = agent_panel.thread_store().clone();
- let Some(history) = agent_panel
+ let history = agent_panel
.connection_store()
.read(cx)
.entry(&crate::Agent::NativeAgent)
- .and_then(|s| s.read(cx).history().cloned())
- else {
- log::error!("No connection entry found for native agent");
- return;
- };
+ .and_then(|s| s.read(cx).history().cloned());
let handle_assist =
|window: &mut Window, cx: &mut Context<Workspace>| match inline_assist_target {
@@ -298,7 +294,7 @@ impl InlineAssistant {
workspace.project().downgrade(),
thread_store,
prompt_store,
- history.downgrade(),
+ history.as_ref().map(|h| h.downgrade()),
action.prompt.clone(),
window,
cx,
@@ -313,7 +309,7 @@ impl InlineAssistant {
workspace.project().downgrade(),
thread_store,
prompt_store,
- history.downgrade(),
+ history.as_ref().map(|h| h.downgrade()),
action.prompt.clone(),
window,
cx,
@@ -495,7 +491,7 @@ impl InlineAssistant {
project: WeakEntity<Project>,
thread_store: Entity<ThreadStore>,
prompt_store: Option<Entity<PromptStore>>,
- history: WeakEntity<ThreadHistory>,
+ history: Option<WeakEntity<ThreadHistory>>,
initial_prompt: Option<String>,
window: &mut Window,
codegen_ranges: &[Range<Anchor>],
@@ -634,7 +630,7 @@ impl InlineAssistant {
project: WeakEntity<Project>,
thread_store: Entity<ThreadStore>,
prompt_store: Option<Entity<PromptStore>>,
- history: WeakEntity<ThreadHistory>,
+ history: Option<WeakEntity<ThreadHistory>>,
initial_prompt: Option<String>,
window: &mut Window,
cx: &mut App,
@@ -679,7 +675,7 @@ impl InlineAssistant {
workspace: Entity<Workspace>,
thread_store: Entity<ThreadStore>,
prompt_store: Option<Entity<PromptStore>>,
- history: WeakEntity<ThreadHistory>,
+ history: Option<WeakEntity<ThreadHistory>>,
window: &mut Window,
cx: &mut App,
) -> InlineAssistId {
@@ -1983,8 +1979,7 @@ impl CodeActionProvider for AssistantCodeActionProvider {
.read(cx)
.entry(&crate::Agent::NativeAgent)
.and_then(|e| e.read(cx).history())
- .context("no history found for native agent")?
- .downgrade();
+ .map(|h| h.downgrade());
anyhow::Ok((panel.thread_store().clone(), history))
})??;
@@ -2155,7 +2150,7 @@ pub mod test {
setup(cx);
- let (_editor, buffer, _history) = cx.update(|window, cx| {
+ let (_editor, buffer) = cx.update(|window, cx| {
let buffer = cx.new(|cx| Buffer::local("", cx));
let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
let editor = cx.new(|cx| Editor::for_multibuffer(multibuffer, None, window, cx));
@@ -2172,7 +2167,6 @@ pub mod test {
});
let thread_store = cx.new(|cx| ThreadStore::new(cx));
- let history = cx.new(|cx| crate::ThreadHistory::new(None, cx));
// Add editor to workspace
workspace.update(cx, |workspace, cx| {
@@ -2188,7 +2182,7 @@ pub mod test {
project.downgrade(),
thread_store,
None,
- history.downgrade(),
+ None,
Some(prompt),
window,
cx,
@@ -2198,7 +2192,7 @@ pub mod test {
inline_assistant.start_assist(assist_id, window, cx);
});
- (editor, buffer, history)
+ (editor, buffer)
});
cx.run_until_parked();
@@ -64,7 +64,7 @@ pub struct PromptEditor<T> {
pub editor: Entity<Editor>,
mode: PromptEditorMode,
mention_set: Entity<MentionSet>,
- history: WeakEntity<ThreadHistory>,
+ history: Option<WeakEntity<ThreadHistory>>,
prompt_store: Option<Entity<PromptStore>>,
workspace: WeakEntity<Workspace>,
model_selector: Entity<AgentModelSelector>,
@@ -1227,7 +1227,7 @@ impl PromptEditor<BufferCodegen> {
fs: Arc<dyn Fs>,
thread_store: Entity<ThreadStore>,
prompt_store: Option<Entity<PromptStore>>,
- history: WeakEntity<ThreadHistory>,
+ history: Option<WeakEntity<ThreadHistory>>,
project: WeakEntity<Project>,
workspace: WeakEntity<Workspace>,
window: &mut Window,
@@ -1386,7 +1386,7 @@ impl PromptEditor<TerminalCodegen> {
fs: Arc<dyn Fs>,
thread_store: Entity<ThreadStore>,
prompt_store: Option<Entity<PromptStore>>,
- history: WeakEntity<ThreadHistory>,
+ history: Option<WeakEntity<ThreadHistory>>,
project: WeakEntity<Project>,
workspace: WeakEntity<Workspace>,
window: &mut Window,
@@ -110,7 +110,7 @@ impl MessageEditor {
workspace: WeakEntity<Workspace>,
project: WeakEntity<Project>,
thread_store: Option<Entity<ThreadStore>>,
- history: WeakEntity<ThreadHistory>,
+ history: Option<WeakEntity<ThreadHistory>>,
prompt_store: Option<Entity<PromptStore>>,
prompt_capabilities: Rc<RefCell<acp::PromptCapabilities>>,
available_commands: Rc<RefCell<Vec<acp::AvailableCommand>>>,
@@ -1789,7 +1789,6 @@ mod tests {
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
let thread_store = None;
- let history = cx.update(|_window, cx| cx.new(|cx| crate::ThreadHistory::new(None, cx)));
let message_editor = cx.update(|window, cx| {
cx.new(|cx| {
@@ -1797,7 +1796,7 @@ mod tests {
workspace.downgrade(),
project.downgrade(),
thread_store.clone(),
- history.downgrade(),
+ None,
None,
Default::default(),
Default::default(),
@@ -1902,7 +1901,6 @@ mod tests {
let (multi_workspace, cx) =
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
- let history = cx.update(|_window, cx| cx.new(|cx| crate::ThreadHistory::new(None, cx)));
let workspace_handle = workspace.downgrade();
let message_editor = workspace.update_in(cx, |_, window, cx| {
cx.new(|cx| {
@@ -1910,7 +1908,7 @@ mod tests {
workspace_handle.clone(),
project.downgrade(),
thread_store.clone(),
- history.downgrade(),
+ None,
None,
prompt_capabilities.clone(),
available_commands.clone(),
@@ -2057,7 +2055,6 @@ mod tests {
let mut cx = VisualTestContext::from_window(window.into(), cx);
let thread_store = None;
- let history = cx.update(|_window, cx| cx.new(|cx| crate::ThreadHistory::new(None, cx)));
let prompt_capabilities = Rc::new(RefCell::new(acp::PromptCapabilities::default()));
let available_commands = Rc::new(RefCell::new(vec![
acp::AvailableCommand::new("quick-math", "2 + 2 = 4 - 1 = 3"),
@@ -2075,7 +2072,7 @@ mod tests {
workspace_handle,
project.downgrade(),
thread_store.clone(),
- history.downgrade(),
+ None,
None,
prompt_capabilities.clone(),
available_commands.clone(),
@@ -2291,7 +2288,6 @@ mod tests {
}
let thread_store = cx.new(|cx| ThreadStore::new(cx));
- let history = cx.update(|_window, cx| cx.new(|cx| crate::ThreadHistory::new(None, cx)));
let prompt_capabilities = Rc::new(RefCell::new(acp::PromptCapabilities::default()));
let (message_editor, editor) = workspace.update_in(&mut cx, |workspace, window, cx| {
@@ -2301,7 +2297,7 @@ mod tests {
workspace_handle,
project.downgrade(),
Some(thread_store),
- history.downgrade(),
+ None,
None,
prompt_capabilities.clone(),
Default::default(),
@@ -2786,7 +2782,6 @@ mod tests {
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
let thread_store = Some(cx.new(|cx| ThreadStore::new(cx)));
- let history = cx.update(|_window, cx| cx.new(|cx| crate::ThreadHistory::new(None, cx)));
let message_editor = cx.update(|window, cx| {
cx.new(|cx| {
@@ -2794,7 +2789,7 @@ mod tests {
workspace.downgrade(),
project.downgrade(),
thread_store.clone(),
- history.downgrade(),
+ None,
None,
Default::default(),
Default::default(),
@@ -2886,7 +2881,6 @@ mod tests {
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
let thread_store = Some(cx.new(|cx| ThreadStore::new(cx)));
- let history = cx.update(|_window, cx| cx.new(|cx| crate::ThreadHistory::new(None, cx)));
let session_id = acp::SessionId::new("thread-123");
let title = Some("Previous Conversation".into());
@@ -2897,7 +2891,7 @@ mod tests {
workspace.downgrade(),
project.downgrade(),
thread_store.clone(),
- history.downgrade(),
+ None,
None,
Default::default(),
Default::default(),
@@ -2961,7 +2955,6 @@ mod tests {
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
let thread_store = None;
- let history = cx.update(|_window, cx| cx.new(|cx| crate::ThreadHistory::new(None, cx)));
let message_editor = cx.update(|window, cx| {
cx.new(|cx| {
@@ -2969,7 +2962,7 @@ mod tests {
workspace.downgrade(),
project.downgrade(),
thread_store.clone(),
- history.downgrade(),
+ None,
None,
Default::default(),
Default::default(),
@@ -3017,7 +3010,6 @@ mod tests {
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
let thread_store = None;
- let history = cx.update(|_window, cx| cx.new(|cx| crate::ThreadHistory::new(None, cx)));
let message_editor = cx.update(|window, cx| {
cx.new(|cx| {
@@ -3025,7 +3017,7 @@ mod tests {
workspace.downgrade(),
project.downgrade(),
thread_store.clone(),
- history.downgrade(),
+ None,
None,
Default::default(),
Default::default(),
@@ -3071,7 +3063,6 @@ mod tests {
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
let thread_store = Some(cx.new(|cx| ThreadStore::new(cx)));
- let history = cx.update(|_window, cx| cx.new(|cx| crate::ThreadHistory::new(None, cx)));
let message_editor = cx.update(|window, cx| {
cx.new(|cx| {
@@ -3079,7 +3070,7 @@ mod tests {
workspace.downgrade(),
project.downgrade(),
thread_store.clone(),
- history.downgrade(),
+ None,
None,
Default::default(),
Default::default(),
@@ -3126,7 +3117,6 @@ mod tests {
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
let thread_store = Some(cx.new(|cx| ThreadStore::new(cx)));
- let history = cx.update(|_window, cx| cx.new(|cx| crate::ThreadHistory::new(None, cx)));
let message_editor = cx.update(|window, cx| {
cx.new(|cx| {
@@ -3134,7 +3124,7 @@ mod tests {
workspace.downgrade(),
project.downgrade(),
thread_store.clone(),
- history.downgrade(),
+ None,
None,
Default::default(),
Default::default(),
@@ -3190,7 +3180,6 @@ mod tests {
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
let thread_store = Some(cx.new(|cx| ThreadStore::new(cx)));
- let history = cx.update(|_window, cx| cx.new(|cx| crate::ThreadHistory::new(None, cx)));
let (message_editor, editor) = workspace.update_in(cx, |workspace, window, cx| {
let workspace_handle = cx.weak_entity();
@@ -3199,7 +3188,7 @@ mod tests {
workspace_handle,
project.downgrade(),
thread_store.clone(),
- history.downgrade(),
+ None,
None,
Default::default(),
Default::default(),
@@ -3349,7 +3338,6 @@ mod tests {
});
let thread_store = Some(cx.new(|cx| ThreadStore::new(cx)));
- let history = cx.update(|_window, cx| cx.new(|cx| crate::ThreadHistory::new(None, cx)));
// Create a new `MessageEditor`. The `EditorMode::full()` has to be used
// to ensure we have a fixed viewport, so we can eventually actually
@@ -3361,7 +3349,7 @@ mod tests {
workspace_handle,
project.downgrade(),
thread_store.clone(),
- history.downgrade(),
+ None,
None,
Default::default(),
Default::default(),
@@ -3469,7 +3457,6 @@ mod tests {
let mut cx = VisualTestContext::from_window(window.into(), cx);
let thread_store = cx.new(|cx| ThreadStore::new(cx));
- let history = cx.update(|_window, cx| cx.new(|cx| crate::ThreadHistory::new(None, cx)));
let (message_editor, editor) = workspace.update_in(&mut cx, |workspace, window, cx| {
let workspace_handle = cx.weak_entity();
@@ -3478,7 +3465,7 @@ mod tests {
workspace_handle,
project.downgrade(),
Some(thread_store),
- history.downgrade(),
+ None,
None,
Default::default(),
Default::default(),
@@ -3551,7 +3538,6 @@ mod tests {
let mut cx = VisualTestContext::from_window(window.into(), cx);
let thread_store = cx.new(|cx| ThreadStore::new(cx));
- let history = cx.update(|_window, cx| cx.new(|cx| crate::ThreadHistory::new(None, cx)));
let (message_editor, editor) = workspace.update_in(&mut cx, |workspace, window, cx| {
let workspace_handle = cx.weak_entity();
@@ -3560,7 +3546,7 @@ mod tests {
workspace_handle,
project.downgrade(),
Some(thread_store),
- history.downgrade(),
+ None,
None,
Default::default(),
Default::default(),
@@ -3635,7 +3621,6 @@ mod tests {
let (multi_workspace, cx) =
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
- let history = cx.update(|_window, cx| cx.new(|cx| crate::ThreadHistory::new(None, cx)));
let message_editor = cx.update(|window, cx| {
cx.new(|cx| {
@@ -3643,7 +3628,7 @@ mod tests {
workspace.downgrade(),
project.downgrade(),
None,
- history.downgrade(),
+ None,
None,
Default::default(),
Default::default(),
@@ -3787,7 +3772,6 @@ mod tests {
let (multi_workspace, cx) =
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
- let history = cx.update(|_window, cx| cx.new(|cx| crate::ThreadHistory::new(None, cx)));
let message_editor = cx.update(|window, cx| {
cx.new(|cx| {
@@ -3795,7 +3779,7 @@ mod tests {
workspace.downgrade(),
project.downgrade(),
None,
- history.downgrade(),
+ None,
None,
Default::default(),
Default::default(),
@@ -64,7 +64,7 @@ impl TerminalInlineAssistant {
project: WeakEntity<Project>,
thread_store: Entity<ThreadStore>,
prompt_store: Option<Entity<PromptStore>>,
- history: WeakEntity<ThreadHistory>,
+ history: Option<WeakEntity<ThreadHistory>>,
initial_prompt: Option<String>,
window: &mut Window,
cx: &mut App,
@@ -5,59 +5,48 @@ use std::rc::Rc;
use ui::prelude::*;
pub struct ThreadHistory {
- session_list: Option<Rc<dyn AgentSessionList>>,
+ session_list: Rc<dyn AgentSessionList>,
sessions: Vec<AgentSessionInfo>,
_refresh_task: Task<()>,
_watch_task: Option<Task<()>>,
}
impl ThreadHistory {
- pub fn new(session_list: Option<Rc<dyn AgentSessionList>>, cx: &mut Context<Self>) -> Self {
+ pub fn new(session_list: Rc<dyn AgentSessionList>, cx: &mut Context<Self>) -> Self {
let mut this = Self {
- session_list: None,
+ session_list,
sessions: Vec::new(),
_refresh_task: Task::ready(()),
_watch_task: None,
};
- this.set_session_list_impl(session_list, cx);
+
+ this.start_watching(cx);
this
}
#[cfg(any(test, feature = "test-support"))]
pub fn set_session_list(
&mut self,
- session_list: Option<Rc<dyn AgentSessionList>>,
+ session_list: Rc<dyn AgentSessionList>,
cx: &mut Context<Self>,
) {
- self.set_session_list_impl(session_list, cx);
- }
-
- fn set_session_list_impl(
- &mut self,
- session_list: Option<Rc<dyn AgentSessionList>>,
- cx: &mut Context<Self>,
- ) {
- if let (Some(current), Some(next)) = (&self.session_list, &session_list)
- && Rc::ptr_eq(current, next)
- {
+ if Rc::ptr_eq(&self.session_list, &session_list) {
return;
}
self.session_list = session_list;
self.sessions.clear();
self._refresh_task = Task::ready(());
+ self.start_watching(cx);
+ }
- let Some(session_list) = self.session_list.as_ref() else {
- self._watch_task = None;
- cx.notify();
- return;
- };
- let Some(rx) = session_list.watch(cx) else {
+ fn start_watching(&mut self, cx: &mut Context<Self>) {
+ let Some(rx) = self.session_list.watch(cx) else {
self._watch_task = None;
self.refresh_sessions(false, cx);
return;
};
- session_list.notify_refresh();
+ self.session_list.notify_refresh();
self._watch_task = Some(cx.spawn(async move |this, cx| {
while let Ok(first_update) = rx.recv().await {
@@ -132,10 +121,7 @@ impl ThreadHistory {
}
fn refresh_sessions(&mut self, load_all_pages: bool, cx: &mut Context<Self>) {
- let Some(session_list) = self.session_list.clone() else {
- cx.notify();
- return;
- };
+ let session_list = self.session_list.clone();
self._refresh_task = cx.spawn(async move |this, cx| {
let mut cursor: Option<String> = None;
@@ -196,14 +182,8 @@ impl ThreadHistory {
self.sessions.is_empty()
}
- pub fn has_session_list(&self) -> bool {
- self.session_list.is_some()
- }
-
pub fn refresh(&mut self, _cx: &mut Context<Self>) {
- if let Some(session_list) = &self.session_list {
- session_list.notify_refresh();
- }
+ self.session_list.notify_refresh();
}
pub fn session_for_id(&self, session_id: &acp::SessionId) -> Option<AgentSessionInfo> {
@@ -222,10 +202,7 @@ impl ThreadHistory {
}
pub fn supports_delete(&self) -> bool {
- self.session_list
- .as_ref()
- .map(|sl| sl.supports_delete())
- .unwrap_or(false)
+ self.session_list.supports_delete()
}
pub(crate) fn delete_session(
@@ -233,19 +210,11 @@ impl ThreadHistory {
session_id: &acp::SessionId,
cx: &mut App,
) -> Task<anyhow::Result<()>> {
- if let Some(session_list) = self.session_list.as_ref() {
- session_list.delete_session(session_id, cx)
- } else {
- Task::ready(Ok(()))
- }
+ self.session_list.delete_session(session_id, cx)
}
pub(crate) fn delete_sessions(&self, cx: &mut App) -> Task<anyhow::Result<()>> {
- if let Some(session_list) = self.session_list.as_ref() {
- session_list.delete_sessions(cx)
- } else {
- Task::ready(Ok(()))
- }
+ self.session_list.delete_sessions(cx)
}
}
@@ -425,7 +394,7 @@ mod tests {
vec![test_session("session-2", "Second")],
));
- let history = cx.new(|cx| ThreadHistory::new(Some(session_list.clone()), cx));
+ let history = cx.new(|cx| ThreadHistory::new(session_list.clone(), cx));
cx.run_until_parked();
history.update(cx, |history, _cx| {
@@ -447,7 +416,7 @@ mod tests {
vec![test_session("session-2", "Second")],
));
- let history = cx.new(|cx| ThreadHistory::new(Some(session_list.clone()), cx));
+ let history = cx.new(|cx| ThreadHistory::new(session_list.clone(), cx));
cx.run_until_parked();
session_list.clear_requested_cursors();
@@ -482,7 +451,7 @@ mod tests {
vec![test_session("session-2", "Second")],
));
- let history = cx.new(|cx| ThreadHistory::new(Some(session_list.clone()), cx));
+ let history = cx.new(|cx| ThreadHistory::new(session_list.clone(), cx));
cx.run_until_parked();
history.update(cx, |history, cx| history.refresh_full_history(cx));
@@ -513,7 +482,7 @@ mod tests {
vec![test_session("session-2", "Second")],
));
- let history = cx.new(|cx| ThreadHistory::new(Some(session_list.clone()), cx));
+ let history = cx.new(|cx| ThreadHistory::new(session_list.clone(), cx));
cx.run_until_parked();
history.update(cx, |history, cx| history.refresh_full_history(cx));
@@ -542,7 +511,7 @@ mod tests {
vec![test_session("session-2", "Second")],
));
- let history = cx.new(|cx| ThreadHistory::new(Some(session_list.clone()), cx));
+ let history = cx.new(|cx| ThreadHistory::new(session_list.clone(), cx));
cx.run_until_parked();
history.update(cx, |history, cx| history.refresh_full_history(cx));
@@ -585,7 +554,7 @@ mod tests {
.with_async_responses(),
);
- let history = cx.new(|cx| ThreadHistory::new(Some(session_list.clone()), cx));
+ let history = cx.new(|cx| ThreadHistory::new(session_list.clone(), cx));
cx.run_until_parked();
session_list.clear_requested_cursors();
@@ -616,7 +585,7 @@ mod tests {
}];
let session_list = Rc::new(TestSessionList::new(sessions));
- let history = cx.new(|cx| ThreadHistory::new(Some(session_list.clone()), cx));
+ let history = cx.new(|cx| ThreadHistory::new(session_list.clone(), cx));
cx.run_until_parked();
session_list.send_update(SessionListUpdate::SessionInfo {
@@ -649,7 +618,7 @@ mod tests {
}];
let session_list = Rc::new(TestSessionList::new(sessions));
- let history = cx.new(|cx| ThreadHistory::new(Some(session_list.clone()), cx));
+ let history = cx.new(|cx| ThreadHistory::new(session_list.clone(), cx));
cx.run_until_parked();
session_list.send_update(SessionListUpdate::SessionInfo {
@@ -679,7 +648,7 @@ mod tests {
}];
let session_list = Rc::new(TestSessionList::new(sessions));
- let history = cx.new(|cx| ThreadHistory::new(Some(session_list.clone()), cx));
+ let history = cx.new(|cx| ThreadHistory::new(session_list.clone(), cx));
cx.run_until_parked();
session_list.send_update(SessionListUpdate::SessionInfo {
@@ -712,7 +681,7 @@ mod tests {
}];
let session_list = Rc::new(TestSessionList::new(sessions));
- let history = cx.new(|cx| ThreadHistory::new(Some(session_list.clone()), cx));
+ let history = cx.new(|cx| ThreadHistory::new(session_list.clone(), cx));
cx.run_until_parked();
session_list.send_update(SessionListUpdate::SessionInfo {
@@ -749,7 +718,7 @@ mod tests {
}];
let session_list = Rc::new(TestSessionList::new(sessions));
- let history = cx.new(|cx| ThreadHistory::new(Some(session_list.clone()), cx));
+ let history = cx.new(|cx| ThreadHistory::new(session_list.clone(), cx));
cx.run_until_parked();
session_list.send_update(SessionListUpdate::SessionInfo {
@@ -783,7 +752,7 @@ mod tests {
}];
let session_list = Rc::new(TestSessionList::new(sessions));
- let history = cx.new(|cx| ThreadHistory::new(Some(session_list.clone()), cx));
+ let history = cx.new(|cx| ThreadHistory::new(session_list.clone(), cx));
cx.run_until_parked();
session_list.send_update(SessionListUpdate::SessionInfo {
@@ -91,6 +91,22 @@ fn fuzzy_match_positions(query: &str, text: &str) -> Option<Vec<usize>> {
}
}
+fn archive_empty_state_message(
+ has_history: bool,
+ is_empty: bool,
+ has_query: bool,
+) -> Option<&'static str> {
+ if !is_empty {
+ None
+ } else if !has_history {
+ Some("This agent does not support viewing archived threads.")
+ } else if has_query {
+ Some("No threads match your search.")
+ } else {
+ Some("No archived threads yet.")
+ }
+}
+
pub enum ThreadsArchiveViewEvent {
Close,
OpenThread {
@@ -171,6 +187,7 @@ impl ThreadsArchiveView {
fn set_selected_agent(&mut self, agent: Agent, window: &mut Window, cx: &mut Context<Self>) {
self.selected_agent = agent.clone();
self.is_loading = true;
+ self.reset_history_subscription();
self.history = None;
self.items.clear();
self.selection = None;
@@ -193,25 +210,33 @@ impl ThreadsArchiveView {
cx.notify();
}
- fn set_history(&mut self, history: Entity<ThreadHistory>, cx: &mut Context<Self>) {
- self._history_subscription = cx.observe(&history, |this, _, cx| {
- this.update_items(cx);
- });
- history.update(cx, |history, cx| {
- history.refresh_full_history(cx);
- });
- self.history = Some(history);
+ fn reset_history_subscription(&mut self) {
+ self._history_subscription = Subscription::new(|| {});
+ }
+
+ fn set_history(&mut self, history: Option<Entity<ThreadHistory>>, cx: &mut Context<Self>) {
+ self.reset_history_subscription();
+
+ if let Some(history) = &history {
+ self._history_subscription = cx.observe(history, |this, _, cx| {
+ this.update_items(cx);
+ });
+ history.update(cx, |history, cx| {
+ history.refresh_full_history(cx);
+ });
+ }
+ self.history = history;
self.is_loading = false;
self.update_items(cx);
cx.notify();
}
fn update_items(&mut self, cx: &mut Context<Self>) {
- let Some(history) = self.history.as_ref() else {
- return;
- };
-
- let sessions = history.read(cx).sessions().to_vec();
+ let sessions = self
+ .history
+ .as_ref()
+ .map(|h| h.read(cx).sessions().to_vec())
+ .unwrap_or_default();
let query = self.filter_editor.read(cx).text(cx).to_lowercase();
let today = Local::now().naive_local().date();
@@ -696,6 +721,12 @@ impl Focusable for ThreadsArchiveView {
}
}
+impl ThreadsArchiveView {
+ fn empty_state_message(&self, is_empty: bool, has_query: bool) -> Option<&'static str> {
+ archive_empty_state_message(self.history.is_some(), is_empty, has_query)
+ }
+}
+
impl Render for ThreadsArchiveView {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let is_empty = self.items.is_empty();
@@ -713,24 +744,13 @@ impl Render for ThreadsArchiveView {
.with_rotate_animation(2),
)
.into_any_element()
- } else if is_empty && has_query {
+ } else if let Some(message) = self.empty_state_message(is_empty, has_query) {
v_flex()
.flex_1()
.justify_center()
.items_center()
.child(
- Label::new("No threads match your search.")
- .size(LabelSize::Small)
- .color(Color::Muted),
- )
- .into_any_element()
- } else if is_empty {
- v_flex()
- .flex_1()
- .justify_center()
- .items_center()
- .child(
- Label::new("No archived threads yet.")
+ Label::new(message)
.size(LabelSize::Small)
.color(Color::Muted),
)
@@ -768,3 +788,38 @@ impl Render for ThreadsArchiveView {
.child(content)
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::archive_empty_state_message;
+
+ #[test]
+ fn empty_state_message_returns_none_when_archive_has_items() {
+ assert_eq!(archive_empty_state_message(false, false, false), None);
+ assert_eq!(archive_empty_state_message(true, false, true), None);
+ }
+
+ #[test]
+ fn empty_state_message_distinguishes_unsupported_history() {
+ assert_eq!(
+ archive_empty_state_message(false, true, false),
+ Some("This agent does not support viewing archived threads.")
+ );
+ assert_eq!(
+ archive_empty_state_message(false, true, true),
+ Some("This agent does not support viewing archived threads.")
+ );
+ }
+
+ #[test]
+ fn empty_state_message_distinguishes_empty_history_and_search_results() {
+ assert_eq!(
+ archive_empty_state_message(true, true, false),
+ Some("No archived threads yet.")
+ );
+ assert_eq!(
+ archive_empty_state_message(true, true, true),
+ Some("No threads match your search.")
+ );
+ }
+}