Rename assistant_context crate to assistant_text_thread (#41024)

Previously we had `Context` and `ContextStore` in both `agent_ui` (used
to store context for the inline assistant) and `assistant_context` (used
for text threads) which is confusing.
This PR makes it so that the `assistant_context` concepts are now called
`TextThread*`, the crate was renamed to `assistant_text_thread`

Release Notes:

- N/A
This commit is contained in:
Bennet Fenner
2025-10-23 17:17:41 +00:00
committed by GitHub
parent 63fe1eae59
commit 11eba64e68
31 changed files with 1035 additions and 995 deletions
+3 -3
View File
@@ -402,7 +402,7 @@ mod tests {
use agent::HistoryStore;
use agent_client_protocol as acp;
use agent_settings::AgentSettings;
use assistant_context::ContextStore;
use assistant_text_thread::TextThreadStore;
use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind};
use editor::{EditorSettings, RowInfo};
use fs::FakeFs;
@@ -466,8 +466,8 @@ mod tests {
connection.send_update(session_id, acp::SessionUpdate::ToolCall(tool_call), cx)
});
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let view_state = cx.new(|_cx| {
EntryViewState::new(
+14 -14
View File
@@ -629,12 +629,12 @@ impl MessageEditor {
path: PathBuf,
cx: &mut Context<Self>,
) -> Task<Result<Mention>> {
let context = self.history_store.update(cx, |store, cx| {
let text_thread_task = self.history_store.update(cx, |store, cx| {
store.load_text_thread(path.as_path().into(), cx)
});
cx.spawn(async move |_, cx| {
let context = context.await?;
let xml = context.update(cx, |context, cx| context.to_xml(cx))?;
let text_thread = text_thread_task.await?;
let xml = text_thread.update(cx, |text_thread, cx| text_thread.to_xml(cx))?;
Ok(Mention::Text {
content: xml,
tracked_buffers: Vec::new(),
@@ -1591,7 +1591,7 @@ mod tests {
use acp_thread::MentionUri;
use agent::{HistoryStore, outline};
use agent_client_protocol as acp;
use assistant_context::ContextStore;
use assistant_text_thread::TextThreadStore;
use editor::{AnchorRangeExt as _, Editor, EditorMode};
use fs::FakeFs;
use futures::StreamExt as _;
@@ -1622,8 +1622,8 @@ mod tests {
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let message_editor = cx.update(|window, cx| {
cx.new(|cx| {
@@ -1727,8 +1727,8 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let prompt_capabilities = Rc::new(RefCell::new(acp::PromptCapabilities::default()));
// Start with no available commands - simulating Claude which doesn't support slash commands
let available_commands = Rc::new(RefCell::new(vec![]));
@@ -1891,8 +1891,8 @@ mod tests {
let mut cx = VisualTestContext::from_window(*window, cx);
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let prompt_capabilities = Rc::new(RefCell::new(acp::PromptCapabilities::default()));
let available_commands = Rc::new(RefCell::new(vec![
acp::AvailableCommand {
@@ -2131,8 +2131,8 @@ mod tests {
opened_editors.push(buffer);
}
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let prompt_capabilities = Rc::new(RefCell::new(acp::PromptCapabilities::default()));
let (message_editor, editor) = workspace.update_in(&mut cx, |workspace, window, cx| {
@@ -2658,8 +2658,8 @@ mod tests {
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let message_editor = cx.update(|window, cx| {
cx.new(|cx| {
+4 -4
View File
@@ -324,8 +324,8 @@ impl AcpThreadHistory {
HistoryEntry::AcpThread(thread) => self
.history_store
.update(cx, |this, cx| this.delete_thread(thread.id.clone(), cx)),
HistoryEntry::TextThread(context) => self.history_store.update(cx, |this, cx| {
this.delete_text_thread(context.path.clone(), cx)
HistoryEntry::TextThread(text_thread) => self.history_store.update(cx, |this, cx| {
this.delete_text_thread(text_thread.path.clone(), cx)
}),
};
task.detach_and_log_err(cx);
@@ -635,12 +635,12 @@ impl RenderOnce for AcpHistoryEntryElement {
});
}
}
HistoryEntry::TextThread(context) => {
HistoryEntry::TextThread(text_thread) => {
if let Some(panel) = workspace.read(cx).panel::<AgentPanel>(cx) {
panel.update(cx, |panel, cx| {
panel
.open_saved_text_thread(
context.path.clone(),
text_thread.path.clone(),
window,
cx,
)
+12 -10
View File
@@ -5414,9 +5414,11 @@ impl AcpThreadView {
HistoryEntry::AcpThread(thread) => self.history_store.update(cx, |history, cx| {
history.delete_thread(thread.id.clone(), cx)
}),
HistoryEntry::TextThread(context) => self.history_store.update(cx, |history, cx| {
history.delete_text_thread(context.path.clone(), cx)
}),
HistoryEntry::TextThread(text_thread) => {
self.history_store.update(cx, |history, cx| {
history.delete_text_thread(text_thread.path.clone(), cx)
})
}
};
task.detach_and_log_err(cx);
}
@@ -5735,7 +5737,7 @@ fn terminal_command_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
pub(crate) mod tests {
use acp_thread::StubAgentConnection;
use agent_client_protocol::SessionId;
use assistant_context::ContextStore;
use assistant_text_thread::TextThreadStore;
use editor::EditorSettings;
use fs::FakeFs;
use gpui::{EventEmitter, SemanticVersion, TestAppContext, VisualTestContext};
@@ -5898,10 +5900,10 @@ pub(crate) mod tests {
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let context_store =
cx.update(|_window, cx| cx.new(|cx| ContextStore::fake(project.clone(), cx)));
let text_thread_store =
cx.update(|_window, cx| cx.new(|cx| TextThreadStore::fake(project.clone(), cx)));
let history_store =
cx.update(|_window, cx| cx.new(|cx| HistoryStore::new(context_store, cx)));
cx.update(|_window, cx| cx.new(|cx| HistoryStore::new(text_thread_store, cx)));
let thread_view = cx.update(|window, cx| {
cx.new(|cx| {
@@ -6170,10 +6172,10 @@ pub(crate) mod tests {
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let context_store =
cx.update(|_window, cx| cx.new(|cx| ContextStore::fake(project.clone(), cx)));
let text_thread_store =
cx.update(|_window, cx| cx.new(|cx| TextThreadStore::fake(project.clone(), cx)));
let history_store =
cx.update(|_window, cx| cx.new(|cx| HistoryStore::new(context_store, cx)));
cx.update(|_window, cx| cx.new(|cx| HistoryStore::new(text_thread_store, cx)));
let connection = Rc::new(StubAgentConnection::new());
let thread_view = cx.update(|window, cx| {
+81 -73
View File
@@ -36,8 +36,8 @@ use crate::{
use agent_settings::AgentSettings;
use ai_onboarding::AgentPanelOnboarding;
use anyhow::{Result, anyhow};
use assistant_context::{AssistantContext, ContextEvent, ContextSummary};
use assistant_slash_command::SlashCommandWorkingSet;
use assistant_text_thread::{TextThread, TextThreadEvent, TextThreadSummary};
use client::{UserStore, zed_urls};
use cloud_llm_client::{Plan, PlanV1, PlanV2, UsageLimit};
use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
@@ -199,7 +199,7 @@ enum ActiveView {
thread_view: Entity<AcpThreadView>,
},
TextThread {
context_editor: Entity<TextThreadEditor>,
text_thread_editor: Entity<TextThreadEditor>,
title_editor: Entity<Editor>,
buffer_search_bar: Entity<BufferSearchBar>,
_subscriptions: Vec<gpui::Subscription>,
@@ -301,13 +301,13 @@ impl ActiveView {
}
pub fn text_thread(
context_editor: Entity<TextThreadEditor>,
text_thread_editor: Entity<TextThreadEditor>,
acp_history_store: Entity<agent::HistoryStore>,
language_registry: Arc<LanguageRegistry>,
window: &mut Window,
cx: &mut App,
) -> Self {
let title = context_editor.read(cx).title(cx).to_string();
let title = text_thread_editor.read(cx).title(cx).to_string();
let editor = cx.new(|cx| {
let mut editor = Editor::single_line(window, cx);
@@ -323,7 +323,7 @@ impl ActiveView {
let subscriptions = vec![
window.subscribe(&editor, cx, {
{
let context_editor = context_editor.clone();
let text_thread_editor = text_thread_editor.clone();
move |editor, event, window, cx| match event {
EditorEvent::BufferEdited => {
if suppress_first_edit {
@@ -332,19 +332,19 @@ impl ActiveView {
}
let new_summary = editor.read(cx).text(cx);
context_editor.update(cx, |context_editor, cx| {
context_editor
.context()
.update(cx, |assistant_context, cx| {
assistant_context.set_custom_summary(new_summary, cx);
text_thread_editor.update(cx, |text_thread_editor, cx| {
text_thread_editor
.text_thread()
.update(cx, |text_thread, cx| {
text_thread.set_custom_summary(new_summary, cx);
})
})
}
EditorEvent::Blurred => {
if editor.read(cx).text(cx).is_empty() {
let summary = context_editor
let summary = text_thread_editor
.read(cx)
.context()
.text_thread()
.read(cx)
.summary()
.or_default();
@@ -358,17 +358,17 @@ impl ActiveView {
}
}
}),
window.subscribe(&context_editor.read(cx).context().clone(), cx, {
window.subscribe(&text_thread_editor.read(cx).text_thread().clone(), cx, {
let editor = editor.clone();
move |assistant_context, event, window, cx| match event {
ContextEvent::SummaryGenerated => {
let summary = assistant_context.read(cx).summary().or_default();
move |text_thread, event, window, cx| match event {
TextThreadEvent::SummaryGenerated => {
let summary = text_thread.read(cx).summary().or_default();
editor.update(cx, |editor, cx| {
editor.set_text(summary, window, cx);
})
}
ContextEvent::PathChanged { old_path, new_path } => {
TextThreadEvent::PathChanged { old_path, new_path } => {
acp_history_store.update(cx, |history_store, cx| {
if let Some(old_path) = old_path {
history_store
@@ -389,11 +389,11 @@ impl ActiveView {
let buffer_search_bar =
cx.new(|cx| BufferSearchBar::new(Some(language_registry), window, cx));
buffer_search_bar.update(cx, |buffer_search_bar, cx| {
buffer_search_bar.set_active_pane_item(Some(&context_editor), window, cx)
buffer_search_bar.set_active_pane_item(Some(&text_thread_editor), window, cx)
});
Self::TextThread {
context_editor,
text_thread_editor,
title_editor: editor,
buffer_search_bar,
_subscriptions: subscriptions,
@@ -410,7 +410,7 @@ pub struct AgentPanel {
language_registry: Arc<LanguageRegistry>,
acp_history: Entity<AcpThreadHistory>,
history_store: Entity<agent::HistoryStore>,
text_thread_store: Entity<assistant_context::ContextStore>,
text_thread_store: Entity<assistant_text_thread::TextThreadStore>,
prompt_store: Option<Entity<PromptStore>>,
context_server_registry: Entity<ContextServerRegistry>,
inline_assist_context_store: Entity<ContextStore>,
@@ -474,7 +474,7 @@ impl AgentPanel {
let text_thread_store = workspace
.update(cx, |workspace, cx| {
let project = workspace.project().clone();
assistant_context::ContextStore::new(
assistant_text_thread::TextThreadStore::new(
project,
prompt_builder,
slash_commands,
@@ -512,7 +512,7 @@ impl AgentPanel {
fn new(
workspace: &Workspace,
text_thread_store: Entity<assistant_context::ContextStore>,
text_thread_store: Entity<assistant_text_thread::TextThreadStore>,
prompt_store: Option<Entity<PromptStore>>,
window: &mut Window,
cx: &mut Context<Self>,
@@ -565,8 +565,8 @@ impl AgentPanel {
DefaultView::TextThread => {
let context = text_thread_store.update(cx, |store, cx| store.create(cx));
let lsp_adapter_delegate = make_lsp_adapter_delegate(&project.clone(), cx).unwrap();
let context_editor = cx.new(|cx| {
let mut editor = TextThreadEditor::for_context(
let text_thread_editor = cx.new(|cx| {
let mut editor = TextThreadEditor::for_text_thread(
context,
fs.clone(),
workspace.clone(),
@@ -579,7 +579,7 @@ impl AgentPanel {
editor
});
ActiveView::text_thread(
context_editor,
text_thread_editor,
history_store.clone(),
language_registry.clone(),
window,
@@ -736,8 +736,8 @@ impl AgentPanel {
.log_err()
.flatten();
let context_editor = cx.new(|cx| {
let mut editor = TextThreadEditor::for_context(
let text_thread_editor = cx.new(|cx| {
let mut editor = TextThreadEditor::for_text_thread(
context,
self.fs.clone(),
self.workspace.clone(),
@@ -757,7 +757,7 @@ impl AgentPanel {
self.set_active_view(
ActiveView::text_thread(
context_editor.clone(),
text_thread_editor.clone(),
self.history_store.clone(),
self.language_registry.clone(),
window,
@@ -766,7 +766,7 @@ impl AgentPanel {
window,
cx,
);
context_editor.focus_handle(cx).focus(window);
text_thread_editor.focus_handle(cx).focus(window);
}
fn external_thread(
@@ -905,20 +905,20 @@ impl AgentPanel {
window: &mut Window,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
let context = self
let text_thread_task = self
.history_store
.update(cx, |store, cx| store.load_text_thread(path, cx));
cx.spawn_in(window, async move |this, cx| {
let context = context.await?;
let text_thread = text_thread_task.await?;
this.update_in(cx, |this, window, cx| {
this.open_text_thread(context, window, cx);
this.open_text_thread(text_thread, window, cx);
})
})
}
pub(crate) fn open_text_thread(
&mut self,
context: Entity<AssistantContext>,
text_thread: Entity<TextThread>,
window: &mut Window,
cx: &mut Context<Self>,
) {
@@ -926,8 +926,8 @@ impl AgentPanel {
.log_err()
.flatten();
let editor = cx.new(|cx| {
TextThreadEditor::for_context(
context,
TextThreadEditor::for_text_thread(
text_thread,
self.fs.clone(),
self.workspace.clone(),
self.project.clone(),
@@ -965,8 +965,10 @@ impl AgentPanel {
ActiveView::ExternalAgentThread { thread_view } => {
thread_view.focus_handle(cx).focus(window);
}
ActiveView::TextThread { context_editor, .. } => {
context_editor.focus_handle(cx).focus(window);
ActiveView::TextThread {
text_thread_editor, ..
} => {
text_thread_editor.focus_handle(cx).focus(window);
}
ActiveView::History | ActiveView::Configuration => {}
}
@@ -1183,9 +1185,11 @@ impl AgentPanel {
}
}
pub(crate) fn active_context_editor(&self) -> Option<Entity<TextThreadEditor>> {
pub(crate) fn active_text_thread_editor(&self) -> Option<Entity<TextThreadEditor>> {
match &self.active_view {
ActiveView::TextThread { context_editor, .. } => Some(context_editor.clone()),
ActiveView::TextThread {
text_thread_editor, ..
} => Some(text_thread_editor.clone()),
_ => None,
}
}
@@ -1206,16 +1210,16 @@ impl AgentPanel {
let new_is_special = new_is_history || new_is_config;
match &new_view {
ActiveView::TextThread { context_editor, .. } => {
self.history_store.update(cx, |store, cx| {
if let Some(path) = context_editor.read(cx).context().read(cx).path() {
store.push_recently_opened_entry(
agent::HistoryEntryId::TextThread(path.clone()),
cx,
)
}
})
}
ActiveView::TextThread {
text_thread_editor, ..
} => self.history_store.update(cx, |store, cx| {
if let Some(path) = text_thread_editor.read(cx).text_thread().read(cx).path() {
store.push_recently_opened_entry(
agent::HistoryEntryId::TextThread(path.clone()),
cx,
)
}
}),
ActiveView::ExternalAgentThread { .. } => {}
ActiveView::History | ActiveView::Configuration => {}
}
@@ -1372,7 +1376,9 @@ impl Focusable for AgentPanel {
match &self.active_view {
ActiveView::ExternalAgentThread { thread_view, .. } => thread_view.focus_handle(cx),
ActiveView::History => self.acp_history.focus_handle(cx),
ActiveView::TextThread { context_editor, .. } => context_editor.focus_handle(cx),
ActiveView::TextThread {
text_thread_editor, ..
} => text_thread_editor.focus_handle(cx),
ActiveView::Configuration => {
if let Some(configuration) = self.configuration.as_ref() {
configuration.focus_handle(cx)
@@ -1507,17 +1513,17 @@ impl AgentPanel {
}
ActiveView::TextThread {
title_editor,
context_editor,
text_thread_editor,
..
} => {
let summary = context_editor.read(cx).context().read(cx).summary();
let summary = text_thread_editor.read(cx).text_thread().read(cx).summary();
match summary {
ContextSummary::Pending => Label::new(ContextSummary::DEFAULT)
TextThreadSummary::Pending => Label::new(TextThreadSummary::DEFAULT)
.color(Color::Muted)
.truncate()
.into_any_element(),
ContextSummary::Content(summary) => {
TextThreadSummary::Content(summary) => {
if summary.done {
div()
.w_full()
@@ -1530,17 +1536,17 @@ impl AgentPanel {
.into_any_element()
}
}
ContextSummary::Error => h_flex()
TextThreadSummary::Error => h_flex()
.w_full()
.child(title_editor.clone())
.child(
IconButton::new("retry-summary-generation", IconName::RotateCcw)
.icon_size(IconSize::Small)
.on_click({
let context_editor = context_editor.clone();
let text_thread_editor = text_thread_editor.clone();
move |_, _window, cx| {
context_editor.update(cx, |context_editor, cx| {
context_editor.regenerate_summary(cx);
text_thread_editor.update(cx, |text_thread_editor, cx| {
text_thread_editor.regenerate_summary(cx);
});
}
})
@@ -2243,7 +2249,7 @@ impl AgentPanel {
fn render_text_thread(
&self,
context_editor: &Entity<TextThreadEditor>,
text_thread_editor: &Entity<TextThreadEditor>,
buffer_search_bar: &Entity<BufferSearchBar>,
window: &mut Window,
cx: &mut Context<Self>,
@@ -2277,7 +2283,7 @@ impl AgentPanel {
)
})
})
.child(context_editor.clone())
.child(text_thread_editor.clone())
.child(self.render_drag_target(cx))
}
@@ -2353,10 +2359,12 @@ impl AgentPanel {
thread_view.insert_dragged_files(paths, added_worktrees, window, cx);
});
}
ActiveView::TextThread { context_editor, .. } => {
context_editor.update(cx, |context_editor, cx| {
ActiveView::TextThread {
text_thread_editor, ..
} => {
text_thread_editor.update(cx, |text_thread_editor, cx| {
TextThreadEditor::insert_dragged_files(
context_editor,
text_thread_editor,
paths,
added_worktrees,
window,
@@ -2427,7 +2435,7 @@ impl Render for AgentPanel {
.child(self.render_drag_target(cx)),
ActiveView::History => parent.child(self.acp_history.clone()),
ActiveView::TextThread {
context_editor,
text_thread_editor,
buffer_search_bar,
..
} => {
@@ -2450,7 +2458,7 @@ impl Render for AgentPanel {
}
})
.child(self.render_text_thread(
context_editor,
text_thread_editor,
buffer_search_bar,
window,
cx,
@@ -2528,17 +2536,17 @@ impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
pub struct ConcreteAssistantPanelDelegate;
impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
fn active_context_editor(
fn active_text_thread_editor(
&self,
workspace: &mut Workspace,
_window: &mut Window,
cx: &mut Context<Workspace>,
) -> Option<Entity<TextThreadEditor>> {
let panel = workspace.panel::<AgentPanel>(cx)?;
panel.read(cx).active_context_editor()
panel.read(cx).active_text_thread_editor()
}
fn open_saved_context(
fn open_local_text_thread(
&self,
workspace: &mut Workspace,
path: Arc<Path>,
@@ -2554,10 +2562,10 @@ impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
})
}
fn open_remote_context(
fn open_remote_text_thread(
&self,
_workspace: &mut Workspace,
_context_id: assistant_context::ContextId,
_text_thread_id: assistant_text_thread::TextThreadId,
_window: &mut Window,
_cx: &mut Context<Workspace>,
) -> Task<Result<Entity<TextThreadEditor>>> {
@@ -2588,15 +2596,15 @@ impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
thread_view.update(cx, |thread_view, cx| {
thread_view.insert_selections(window, cx);
});
} else if let Some(context_editor) = panel.active_context_editor() {
} else if let Some(text_thread_editor) = panel.active_text_thread_editor() {
let snapshot = buffer.read(cx).snapshot(cx);
let selection_ranges = selection_ranges
.into_iter()
.map(|range| range.to_point(&snapshot))
.collect::<Vec<_>>();
context_editor.update(cx, |context_editor, cx| {
context_editor.quote_ranges(selection_ranges, snapshot, window, cx)
text_thread_editor.update(cx, |text_thread_editor, cx| {
text_thread_editor.quote_ranges(selection_ranges, snapshot, window, cx)
});
}
});
+1 -1
View File
@@ -250,7 +250,7 @@ pub fn init(
) {
AgentSettings::register(cx);
assistant_context::init(client.clone(), cx);
assistant_text_thread::init(client.clone(), cx);
rules_library::init(cx);
if !is_eval {
// Initializing the language model from the user settings messes with the eval, so we only initialize them when
+6 -6
View File
@@ -1,5 +1,5 @@
use agent::outline;
use assistant_context::AssistantContext;
use assistant_text_thread::TextThread;
use futures::future;
use futures::{FutureExt, future::Shared};
use gpui::{App, AppContext as _, ElementId, Entity, SharedString, Task};
@@ -581,7 +581,7 @@ impl Display for ThreadContext {
#[derive(Debug, Clone)]
pub struct TextThreadContextHandle {
pub context: Entity<AssistantContext>,
pub text_thread: Entity<TextThread>,
pub context_id: ContextId,
}
@@ -595,20 +595,20 @@ pub struct TextThreadContext {
impl TextThreadContextHandle {
// pub fn lookup_key() ->
pub fn eq_for_key(&self, other: &Self) -> bool {
self.context == other.context
self.text_thread == other.text_thread
}
pub fn hash_for_key<H: Hasher>(&self, state: &mut H) {
self.context.hash(state)
self.text_thread.hash(state)
}
pub fn title(&self, cx: &App) -> SharedString {
self.context.read(cx).summary().or_default()
self.text_thread.read(cx).summary().or_default()
}
fn load(self, cx: &App) -> Task<Option<AgentContext>> {
let title = self.title(cx);
let text = self.context.read(cx).to_xml(cx);
let text = self.text_thread.read(cx).to_xml(cx);
let context = AgentContext::TextThread(TextThreadContext {
title,
text: text.into(),
+12 -25
View File
@@ -5,7 +5,7 @@ use crate::context::{
};
use agent_client_protocol as acp;
use anyhow::{Context as _, Result, anyhow};
use assistant_context::AssistantContext;
use assistant_text_thread::TextThread;
use collections::{HashSet, IndexSet};
use futures::{self, FutureExt};
use gpui::{App, Context, Entity, EventEmitter, Image, SharedString, Task, WeakEntity};
@@ -200,13 +200,13 @@ impl ContextStore {
pub fn add_text_thread(
&mut self,
context: Entity<AssistantContext>,
text_thread: Entity<TextThread>,
remove_if_exists: bool,
cx: &mut Context<Self>,
) -> Option<AgentContextHandle> {
let context_id = self.next_context_id.post_inc();
let context = AgentContextHandle::TextThread(TextThreadContextHandle {
context,
text_thread,
context_id,
});
@@ -353,21 +353,15 @@ impl ContextStore {
);
};
}
// SuggestedContext::Thread { thread, name: _ } => {
// if let Some(thread) = thread.upgrade() {
// let context_id = self.next_context_id.post_inc();
// self.insert_context(
// AgentContextHandle::Thread(ThreadContextHandle { thread, context_id }),
// cx,
// );
// }
// }
SuggestedContext::TextThread { context, name: _ } => {
if let Some(context) = context.upgrade() {
SuggestedContext::TextThread {
text_thread,
name: _,
} => {
if let Some(text_thread) = text_thread.upgrade() {
let context_id = self.next_context_id.post_inc();
self.insert_context(
AgentContextHandle::TextThread(TextThreadContextHandle {
context,
text_thread,
context_id,
}),
cx,
@@ -392,7 +386,7 @@ impl ContextStore {
// }
AgentContextHandle::TextThread(text_thread_context) => {
self.context_text_thread_paths
.extend(text_thread_context.context.read(cx).path().cloned());
.extend(text_thread_context.text_thread.read(cx).path().cloned());
}
_ => {}
}
@@ -414,7 +408,7 @@ impl ContextStore {
.remove(thread_context.thread.read(cx).id());
}
AgentContextHandle::TextThread(text_thread_context) => {
if let Some(path) = text_thread_context.context.read(cx).path() {
if let Some(path) = text_thread_context.text_thread.read(cx).path() {
self.context_text_thread_paths.remove(path);
}
}
@@ -538,13 +532,9 @@ pub enum SuggestedContext {
icon_path: Option<SharedString>,
buffer: WeakEntity<Buffer>,
},
// Thread {
// name: SharedString,
// thread: WeakEntity<Thread>,
// },
TextThread {
name: SharedString,
context: WeakEntity<AssistantContext>,
text_thread: WeakEntity<TextThread>,
},
}
@@ -552,7 +542,6 @@ impl SuggestedContext {
pub fn name(&self) -> &SharedString {
match self {
Self::File { name, .. } => name,
// Self::Thread { name, .. } => name,
Self::TextThread { name, .. } => name,
}
}
@@ -560,7 +549,6 @@ impl SuggestedContext {
pub fn icon_path(&self) -> Option<SharedString> {
match self {
Self::File { icon_path, .. } => icon_path.clone(),
// Self::Thread { .. } => None,
Self::TextThread { .. } => None,
}
}
@@ -568,7 +556,6 @@ impl SuggestedContext {
pub fn kind(&self) -> ContextKind {
match self {
Self::File { .. } => ContextKind::File,
// Self::Thread { .. } => ContextKind::Thread,
Self::TextThread { .. } => ContextKind::TextThread,
}
}
+8 -8
View File
@@ -132,19 +132,19 @@ impl ContextStrip {
let workspace = self.workspace.upgrade()?;
let panel = workspace.read(cx).panel::<AgentPanel>(cx)?.read(cx);
if let Some(active_context_editor) = panel.active_context_editor() {
let context = active_context_editor.read(cx).context();
let weak_context = context.downgrade();
let context = context.read(cx);
let path = context.path()?;
if let Some(active_text_thread_editor) = panel.active_text_thread_editor() {
let text_thread = active_text_thread_editor.read(cx).text_thread();
let weak_text_thread = text_thread.downgrade();
let text_thread = text_thread.read(cx);
let path = text_thread.path()?;
if self.context_store.read(cx).includes_text_thread(path) {
return None;
}
Some(SuggestedContext::TextThread {
name: context.summary().or_default(),
context: weak_context,
name: text_thread.summary().or_default(),
text_thread: weak_text_thread,
})
} else {
None
@@ -332,7 +332,7 @@ impl ContextStrip {
AgentContextHandle::TextThread(text_thread_context) => {
workspace.update(cx, |workspace, cx| {
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
let context = text_thread_context.context.clone();
let context = text_thread_context.text_thread.clone();
window.defer(cx, move |window, cx| {
panel.update(cx, |panel, cx| {
panel.open_text_thread(context, window, cx)
+4 -4
View File
@@ -1508,8 +1508,8 @@ impl InlineAssistant {
return Some(InlineAssistTarget::Terminal(terminal_view));
}
let context_editor = agent_panel
.and_then(|panel| panel.read(cx).active_context_editor())
let text_thread_editor = agent_panel
.and_then(|panel| panel.read(cx).active_text_thread_editor())
.and_then(|editor| {
let editor = &editor.read(cx).editor().clone();
if editor.read(cx).is_focused(window) {
@@ -1519,8 +1519,8 @@ impl InlineAssistant {
}
});
if let Some(context_editor) = context_editor {
Some(InlineAssistTarget::Editor(context_editor))
if let Some(text_thread_editor) = text_thread_editor {
Some(InlineAssistTarget::Editor(text_thread_editor))
} else if let Some(workspace_editor) = workspace
.active_item(cx)
.and_then(|item| item.act_as::<Editor>(cx))
+2 -2
View File
@@ -155,8 +155,8 @@ impl PickerDelegate for SlashCommandDelegate {
match command {
SlashCommandEntry::Info(info) => {
self.active_context_editor
.update(cx, |context_editor, cx| {
context_editor.insert_command(&info.name, window, cx)
.update(cx, |text_thread_editor, cx| {
text_thread_editor.insert_command(&info.name, window, cx)
})
.ok();
}
+183 -169
View File
@@ -74,10 +74,10 @@ use workspace::{
use zed_actions::agent::{AddSelectionToThread, ToggleModelSelector};
use crate::{slash_command::SlashCommandCompletionProvider, slash_command_picker};
use assistant_context::{
AssistantContext, CacheStatus, Content, ContextEvent, ContextId, InvokedSlashCommandId,
InvokedSlashCommandStatus, Message, MessageId, MessageMetadata, MessageStatus,
PendingSlashCommandStatus, ThoughtProcessOutputSection,
use assistant_text_thread::{
CacheStatus, Content, InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId,
MessageMetadata, MessageStatus, PendingSlashCommandStatus, TextThread, TextThreadEvent,
TextThreadId, ThoughtProcessOutputSection,
};
actions!(
@@ -126,14 +126,14 @@ pub enum ThoughtProcessStatus {
}
pub trait AgentPanelDelegate {
fn active_context_editor(
fn active_text_thread_editor(
&self,
workspace: &mut Workspace,
window: &mut Window,
cx: &mut Context<Workspace>,
) -> Option<Entity<TextThreadEditor>>;
fn open_saved_context(
fn open_local_text_thread(
&self,
workspace: &mut Workspace,
path: Arc<Path>,
@@ -141,10 +141,10 @@ pub trait AgentPanelDelegate {
cx: &mut Context<Workspace>,
) -> Task<Result<()>>;
fn open_remote_context(
fn open_remote_text_thread(
&self,
workspace: &mut Workspace,
context_id: ContextId,
text_thread_id: TextThreadId,
window: &mut Window,
cx: &mut Context<Workspace>,
) -> Task<Result<Entity<TextThreadEditor>>>;
@@ -177,7 +177,7 @@ struct GlobalAssistantPanelDelegate(Arc<dyn AgentPanelDelegate>);
impl Global for GlobalAssistantPanelDelegate {}
pub struct TextThreadEditor {
context: Entity<AssistantContext>,
text_thread: Entity<TextThread>,
fs: Arc<dyn Fs>,
slash_commands: Arc<SlashCommandWorkingSet>,
workspace: WeakEntity<Workspace>,
@@ -223,8 +223,8 @@ impl TextThreadEditor {
.detach();
}
pub fn for_context(
context: Entity<AssistantContext>,
pub fn for_text_thread(
text_thread: Entity<TextThread>,
fs: Arc<dyn Fs>,
workspace: WeakEntity<Workspace>,
project: Entity<Project>,
@@ -233,14 +233,14 @@ impl TextThreadEditor {
cx: &mut Context<Self>,
) -> Self {
let completion_provider = SlashCommandCompletionProvider::new(
context.read(cx).slash_commands().clone(),
text_thread.read(cx).slash_commands().clone(),
Some(cx.entity().downgrade()),
Some(workspace.clone()),
);
let editor = cx.new(|cx| {
let mut editor =
Editor::for_buffer(context.read(cx).buffer().clone(), None, window, cx);
Editor::for_buffer(text_thread.read(cx).buffer().clone(), None, window, cx);
editor.disable_scrollbars_and_minimap(window, cx);
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
editor.set_show_line_numbers(false, cx);
@@ -264,18 +264,24 @@ impl TextThreadEditor {
});
let _subscriptions = vec![
cx.observe(&context, |_, _, cx| cx.notify()),
cx.subscribe_in(&context, window, Self::handle_context_event),
cx.observe(&text_thread, |_, _, cx| cx.notify()),
cx.subscribe_in(&text_thread, window, Self::handle_text_thread_event),
cx.subscribe_in(&editor, window, Self::handle_editor_event),
cx.subscribe_in(&editor, window, Self::handle_editor_search_event),
cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
];
let slash_command_sections = context.read(cx).slash_command_output_sections().to_vec();
let thought_process_sections = context.read(cx).thought_process_output_sections().to_vec();
let slash_commands = context.read(cx).slash_commands().clone();
let slash_command_sections = text_thread
.read(cx)
.slash_command_output_sections()
.to_vec();
let thought_process_sections = text_thread
.read(cx)
.thought_process_output_sections()
.to_vec();
let slash_commands = text_thread.read(cx).slash_commands().clone();
let mut this = Self {
context,
text_thread,
slash_commands,
editor,
lsp_adapter_delegate,
@@ -337,8 +343,8 @@ impl TextThreadEditor {
});
}
pub fn context(&self) -> &Entity<AssistantContext> {
&self.context
pub fn text_thread(&self) -> &Entity<TextThread> {
&self.text_thread
}
pub fn editor(&self) -> &Entity<Editor> {
@@ -350,9 +356,9 @@ impl TextThreadEditor {
self.editor.update(cx, |editor, cx| {
editor.insert(&format!("/{command_name}\n\n"), window, cx)
});
let command = self.context.update(cx, |context, cx| {
context.reparse(cx);
context.parsed_slash_commands()[0].clone()
let command = self.text_thread.update(cx, |text_thread, cx| {
text_thread.reparse(cx);
text_thread.parsed_slash_commands()[0].clone()
});
self.run_command(
command.source_range,
@@ -375,11 +381,14 @@ impl TextThreadEditor {
fn send_to_model(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.last_error = None;
if let Some(user_message) = self.context.update(cx, |context, cx| context.assist(cx)) {
if let Some(user_message) = self
.text_thread
.update(cx, |text_thread, cx| text_thread.assist(cx))
{
let new_selection = {
let cursor = user_message
.start
.to_offset(self.context.read(cx).buffer().read(cx));
.to_offset(self.text_thread.read(cx).buffer().read(cx));
cursor..cursor
};
self.editor.update(cx, |editor, cx| {
@@ -403,8 +412,8 @@ impl TextThreadEditor {
self.last_error = None;
if self
.context
.update(cx, |context, cx| context.cancel_last_assist(cx))
.text_thread
.update(cx, |text_thread, cx| text_thread.cancel_last_assist(cx))
{
return;
}
@@ -419,13 +428,13 @@ impl TextThreadEditor {
cx: &mut Context<Self>,
) {
let cursors = self.cursors(cx);
self.context.update(cx, |context, cx| {
let messages = context
self.text_thread.update(cx, |text_thread, cx| {
let messages = text_thread
.messages_for_offsets(cursors, cx)
.into_iter()
.map(|message| message.id)
.collect();
context.cycle_message_roles(messages, cx)
text_thread.cycle_message_roles(messages, cx)
});
}
@@ -491,11 +500,11 @@ impl TextThreadEditor {
let selections = self.editor.read(cx).selections.disjoint_anchors_arc();
let mut commands_by_range = HashMap::default();
let workspace = self.workspace.clone();
self.context.update(cx, |context, cx| {
context.reparse(cx);
self.text_thread.update(cx, |text_thread, cx| {
text_thread.reparse(cx);
for selection in selections.iter() {
if let Some(command) =
context.pending_command_for_position(selection.head().text_anchor, cx)
text_thread.pending_command_for_position(selection.head().text_anchor, cx)
{
commands_by_range
.entry(command.source_range.clone())
@@ -533,14 +542,14 @@ impl TextThreadEditor {
cx: &mut Context<Self>,
) {
if let Some(command) = self.slash_commands.command(name, cx) {
let context = self.context.read(cx);
let sections = context
let text_thread = self.text_thread.read(cx);
let sections = text_thread
.slash_command_output_sections()
.iter()
.filter(|section| section.is_valid(context.buffer().read(cx)))
.filter(|section| section.is_valid(text_thread.buffer().read(cx)))
.cloned()
.collect::<Vec<_>>();
let snapshot = context.buffer().read(cx).snapshot();
let snapshot = text_thread.buffer().read(cx).snapshot();
let output = command.run(
arguments,
&sections,
@@ -550,8 +559,8 @@ impl TextThreadEditor {
window,
cx,
);
self.context.update(cx, |context, cx| {
context.insert_command_output(
self.text_thread.update(cx, |text_thread, cx| {
text_thread.insert_command_output(
command_range,
name,
output,
@@ -562,32 +571,32 @@ impl TextThreadEditor {
}
}
fn handle_context_event(
fn handle_text_thread_event(
&mut self,
_: &Entity<AssistantContext>,
event: &ContextEvent,
_: &Entity<TextThread>,
event: &TextThreadEvent,
window: &mut Window,
cx: &mut Context<Self>,
) {
let context_editor = cx.entity().downgrade();
let text_thread_editor = cx.entity().downgrade();
match event {
ContextEvent::MessagesEdited => {
TextThreadEvent::MessagesEdited => {
self.update_message_headers(cx);
self.update_image_blocks(cx);
self.context.update(cx, |context, cx| {
context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
self.text_thread.update(cx, |text_thread, cx| {
text_thread.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
});
}
ContextEvent::SummaryChanged => {
TextThreadEvent::SummaryChanged => {
cx.emit(EditorEvent::TitleChanged);
self.context.update(cx, |context, cx| {
context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
self.text_thread.update(cx, |text_thread, cx| {
text_thread.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
});
}
ContextEvent::SummaryGenerated => {}
ContextEvent::PathChanged { .. } => {}
ContextEvent::StartedThoughtProcess(range) => {
TextThreadEvent::SummaryGenerated => {}
TextThreadEvent::PathChanged { .. } => {}
TextThreadEvent::StartedThoughtProcess(range) => {
let creases = self.insert_thought_process_output_sections(
[(
ThoughtProcessOutputSection {
@@ -600,7 +609,7 @@ impl TextThreadEditor {
);
self.pending_thought_process = Some((creases[0], range.start));
}
ContextEvent::EndedThoughtProcess(end) => {
TextThreadEvent::EndedThoughtProcess(end) => {
if let Some((crease_id, start)) = self.pending_thought_process.take() {
self.editor.update(cx, |editor, cx| {
let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
@@ -626,7 +635,7 @@ impl TextThreadEditor {
);
}
}
ContextEvent::StreamedCompletion => {
TextThreadEvent::StreamedCompletion => {
self.editor.update(cx, |editor, cx| {
if let Some(scroll_position) = self.scroll_position {
let snapshot = editor.snapshot(window, cx);
@@ -641,7 +650,7 @@ impl TextThreadEditor {
}
});
}
ContextEvent::ParsedSlashCommandsUpdated { removed, updated } => {
TextThreadEvent::ParsedSlashCommandsUpdated { removed, updated } => {
self.editor.update(cx, |editor, cx| {
let buffer = editor.buffer().read(cx).snapshot(cx);
let (&excerpt_id, _, _) = buffer.as_singleton().unwrap();
@@ -657,12 +666,12 @@ impl TextThreadEditor {
updated.iter().map(|command| {
let workspace = self.workspace.clone();
let confirm_command = Arc::new({
let context_editor = context_editor.clone();
let text_thread_editor = text_thread_editor.clone();
let command = command.clone();
move |window: &mut Window, cx: &mut App| {
context_editor
.update(cx, |context_editor, cx| {
context_editor.run_command(
text_thread_editor
.update(cx, |text_thread_editor, cx| {
text_thread_editor.run_command(
command.source_range.clone(),
&command.name,
&command.arguments,
@@ -712,17 +721,17 @@ impl TextThreadEditor {
);
})
}
ContextEvent::InvokedSlashCommandChanged { command_id } => {
TextThreadEvent::InvokedSlashCommandChanged { command_id } => {
self.update_invoked_slash_command(*command_id, window, cx);
}
ContextEvent::SlashCommandOutputSectionAdded { section } => {
TextThreadEvent::SlashCommandOutputSectionAdded { section } => {
self.insert_slash_command_output_sections([section.clone()], false, window, cx);
}
ContextEvent::Operation(_) => {}
ContextEvent::ShowAssistError(error_message) => {
TextThreadEvent::Operation(_) => {}
TextThreadEvent::ShowAssistError(error_message) => {
self.last_error = Some(AssistError::Message(error_message.clone()));
}
ContextEvent::ShowPaymentRequiredError => {
TextThreadEvent::ShowPaymentRequiredError => {
self.last_error = Some(AssistError::PaymentRequired);
}
}
@@ -735,14 +744,14 @@ impl TextThreadEditor {
cx: &mut Context<Self>,
) {
if let Some(invoked_slash_command) =
self.context.read(cx).invoked_slash_command(&command_id)
self.text_thread.read(cx).invoked_slash_command(&command_id)
&& let InvokedSlashCommandStatus::Finished = invoked_slash_command.status
{
let run_commands_in_ranges = invoked_slash_command.run_commands_in_ranges.clone();
for range in run_commands_in_ranges {
let commands = self.context.update(cx, |context, cx| {
context.reparse(cx);
context
let commands = self.text_thread.update(cx, |text_thread, cx| {
text_thread.reparse(cx);
text_thread
.pending_commands_for_range(range.clone(), cx)
.to_vec()
});
@@ -763,7 +772,7 @@ impl TextThreadEditor {
self.editor.update(cx, |editor, cx| {
if let Some(invoked_slash_command) =
self.context.read(cx).invoked_slash_command(&command_id)
self.text_thread.read(cx).invoked_slash_command(&command_id)
{
if let InvokedSlashCommandStatus::Finished = invoked_slash_command.status {
let buffer = editor.buffer().read(cx).snapshot(cx);
@@ -790,7 +799,7 @@ impl TextThreadEditor {
let buffer = editor.buffer().read(cx).snapshot(cx);
let (&excerpt_id, _buffer_id, _buffer_snapshot) =
buffer.as_singleton().unwrap();
let context = self.context.downgrade();
let context = self.text_thread.downgrade();
let range = buffer
.anchor_range_in_excerpt(excerpt_id, invoked_slash_command.range.clone())
.unwrap();
@@ -1020,7 +1029,7 @@ impl TextThreadEditor {
let render_block = |message: MessageMetadata| -> RenderBlock {
Arc::new({
let context = self.context.clone();
let text_thread = self.text_thread.clone();
move |cx| {
let message_id = MessageId(message.timestamp);
@@ -1093,10 +1102,10 @@ impl TextThreadEditor {
)
})
.on_click({
let context = context.clone();
let text_thread = text_thread.clone();
move |_, _window, cx| {
context.update(cx, |context, cx| {
context.cycle_message_roles(
text_thread.update(cx, |text_thread, cx| {
text_thread.cycle_message_roles(
HashSet::from_iter(Some(message_id)),
cx,
)
@@ -1158,11 +1167,11 @@ impl TextThreadEditor {
.icon_position(IconPosition::Start)
.tooltip(Tooltip::text("View Details"))
.on_click({
let context = context.clone();
let text_thread = text_thread.clone();
let error = error.clone();
move |_, _window, cx| {
context.update(cx, |_, cx| {
cx.emit(ContextEvent::ShowAssistError(
text_thread.update(cx, |_, cx| {
cx.emit(TextThreadEvent::ShowAssistError(
error.clone(),
));
});
@@ -1205,7 +1214,7 @@ impl TextThreadEditor {
};
let mut new_blocks = vec![];
let mut block_index_to_message = vec![];
for message in self.context.read(cx).messages(cx) {
for message in self.text_thread.read(cx).messages(cx) {
if blocks_to_remove.remove(&message.id).is_some() {
// This is an old message that we might modify.
let Some((meta, block_id)) = old_blocks.get_mut(&message.id) else {
@@ -1246,18 +1255,18 @@ impl TextThreadEditor {
) -> Option<(String, bool)> {
const CODE_FENCE_DELIMITER: &str = "```";
let context_editor = context_editor_view.read(cx).editor.clone();
context_editor.update(cx, |context_editor, cx| {
let display_map = context_editor.display_snapshot(cx);
if context_editor
let text_thread_editor = context_editor_view.read(cx).editor.clone();
text_thread_editor.update(cx, |text_thread_editor, cx| {
let display_map = text_thread_editor.display_snapshot(cx);
if text_thread_editor
.selections
.newest::<Point>(&display_map)
.is_empty()
{
let snapshot = context_editor.buffer().read(cx).snapshot(cx);
let snapshot = text_thread_editor.buffer().read(cx).snapshot(cx);
let (_, _, snapshot) = snapshot.as_singleton()?;
let head = context_editor
let head = text_thread_editor
.selections
.newest::<Point>(&display_map)
.head();
@@ -1277,8 +1286,8 @@ impl TextThreadEditor {
(!text.is_empty()).then_some((text, true))
} else {
let selection = context_editor.selections.newest_adjusted(&display_map);
let buffer = context_editor.buffer().read(cx).snapshot(cx);
let selection = text_thread_editor.selections.newest_adjusted(&display_map);
let buffer = text_thread_editor.buffer().read(cx).snapshot(cx);
let selected_text = buffer.text_for_range(selection.range()).collect::<String>();
(!selected_text.is_empty()).then_some((selected_text, false))
@@ -1296,7 +1305,7 @@ impl TextThreadEditor {
return;
};
let Some(context_editor_view) =
agent_panel_delegate.active_context_editor(workspace, window, cx)
agent_panel_delegate.active_text_thread_editor(workspace, window, cx)
else {
return;
};
@@ -1324,7 +1333,7 @@ impl TextThreadEditor {
let result = maybe!({
let agent_panel_delegate = <dyn AgentPanelDelegate>::try_global(cx)?;
let context_editor_view =
agent_panel_delegate.active_context_editor(workspace, window, cx)?;
agent_panel_delegate.active_text_thread_editor(workspace, window, cx)?;
Self::get_selection_or_code_block(&context_editor_view, cx)
});
let Some((text, is_code_block)) = result else {
@@ -1361,7 +1370,7 @@ impl TextThreadEditor {
return;
};
let Some(context_editor_view) =
agent_panel_delegate.active_context_editor(workspace, window, cx)
agent_panel_delegate.active_text_thread_editor(workspace, window, cx)
else {
return;
};
@@ -1622,29 +1631,33 @@ impl TextThreadEditor {
)
});
let context = self.context.read(cx);
let text_thread = self.text_thread.read(cx);
let mut text = String::new();
// If selection is empty, we want to copy the entire line
if selection.range().is_empty() {
let snapshot = context.buffer().read(cx).snapshot();
let snapshot = text_thread.buffer().read(cx).snapshot();
let point = snapshot.offset_to_point(selection.range().start);
selection.start = snapshot.point_to_offset(Point::new(point.row, 0));
selection.end = snapshot
.point_to_offset(cmp::min(Point::new(point.row + 1, 0), snapshot.max_point()));
for chunk in context.buffer().read(cx).text_for_range(selection.range()) {
for chunk in text_thread
.buffer()
.read(cx)
.text_for_range(selection.range())
{
text.push_str(chunk);
}
} else {
for message in context.messages(cx) {
for message in text_thread.messages(cx) {
if message.offset_range.start >= selection.range().end {
break;
} else if message.offset_range.end >= selection.range().start {
let range = cmp::max(message.offset_range.start, selection.range().start)
..cmp::min(message.offset_range.end, selection.range().end);
if !range.is_empty() {
for chunk in context.buffer().read(cx).text_for_range(range) {
for chunk in text_thread.buffer().read(cx).text_for_range(range) {
text.push_str(chunk);
}
if message.offset_range.end < selection.range().end {
@@ -1755,7 +1768,7 @@ impl TextThreadEditor {
});
});
self.context.update(cx, |context, cx| {
self.text_thread.update(cx, |text_thread, cx| {
for image in images {
let Some(render_image) = image.to_image_data(cx.svg_renderer()).log_err()
else {
@@ -1765,7 +1778,7 @@ impl TextThreadEditor {
let image_task = LanguageModelImage::from_image(Arc::new(image), cx).shared();
for image_position in image_positions.iter() {
context.insert_content(
text_thread.insert_content(
Content::Image {
anchor: image_position.text_anchor,
image_id,
@@ -1786,7 +1799,7 @@ impl TextThreadEditor {
let excerpt_id = *buffer.as_singleton().unwrap().0;
let old_blocks = std::mem::take(&mut self.image_blocks);
let new_blocks = self
.context
.text_thread
.read(cx)
.contents(cx)
.map(
@@ -1834,36 +1847,36 @@ impl TextThreadEditor {
}
fn split(&mut self, _: &Split, _window: &mut Window, cx: &mut Context<Self>) {
self.context.update(cx, |context, cx| {
self.text_thread.update(cx, |text_thread, cx| {
let selections = self.editor.read(cx).selections.disjoint_anchors_arc();
for selection in selections.as_ref() {
let buffer = self.editor.read(cx).buffer().read(cx).snapshot(cx);
let range = selection
.map(|endpoint| endpoint.to_offset(&buffer))
.range();
context.split_message(range, cx);
text_thread.split_message(range, cx);
}
});
}
fn save(&mut self, _: &Save, _window: &mut Window, cx: &mut Context<Self>) {
self.context.update(cx, |context, cx| {
context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx)
self.text_thread.update(cx, |text_thread, cx| {
text_thread.save(Some(Duration::from_millis(500)), self.fs.clone(), cx)
});
}
pub fn title(&self, cx: &App) -> SharedString {
self.context.read(cx).summary().or_default()
self.text_thread.read(cx).summary().or_default()
}
pub fn regenerate_summary(&mut self, cx: &mut Context<Self>) {
self.context
.update(cx, |context, cx| context.summarize(true, cx));
self.text_thread
.update(cx, |text_thread, cx| text_thread.summarize(true, cx));
}
fn render_remaining_tokens(&self, cx: &App) -> Option<impl IntoElement + use<>> {
let (token_count_color, token_count, max_token_count, tooltip) =
match token_state(&self.context, cx)? {
match token_state(&self.text_thread, cx)? {
TokenState::NoTokensLeft {
max_token_count,
token_count,
@@ -1911,7 +1924,7 @@ impl TextThreadEditor {
fn render_send_button(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let focus_handle = self.focus_handle(cx);
let (style, tooltip) = match token_state(&self.context, cx) {
let (style, tooltip) = match token_state(&self.text_thread, cx) {
Some(TokenState::NoTokensLeft { .. }) => (
ButtonStyle::Tinted(TintColor::Error),
Some(Tooltip::text("Token limit reached")(window, cx)),
@@ -1986,7 +1999,7 @@ impl TextThreadEditor {
}
fn render_burn_mode_toggle(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
let context = self.context().read(cx);
let text_thread = self.text_thread().read(cx);
let active_model = LanguageModelRegistry::read_global(cx)
.default_model()
.map(|default| default.model)?;
@@ -1994,7 +2007,7 @@ impl TextThreadEditor {
return None;
}
let active_completion_mode = context.completion_mode();
let active_completion_mode = text_thread.completion_mode();
let burn_mode_enabled = active_completion_mode == CompletionMode::Burn;
let icon = if burn_mode_enabled {
IconName::ZedBurnModeOn
@@ -2009,8 +2022,8 @@ impl TextThreadEditor {
.toggle_state(burn_mode_enabled)
.selected_icon_color(Color::Error)
.on_click(cx.listener(move |this, _event, _window, cx| {
this.context().update(cx, |context, _cx| {
context.set_completion_mode(match active_completion_mode {
this.text_thread().update(cx, |text_thread, _cx| {
text_thread.set_completion_mode(match active_completion_mode {
CompletionMode::Burn => CompletionMode::Normal,
CompletionMode::Normal => CompletionMode::Burn,
});
@@ -2637,10 +2650,10 @@ impl FollowableItem for TextThreadEditor {
}
fn to_state_proto(&self, window: &Window, cx: &App) -> Option<proto::view::Variant> {
let context = self.context.read(cx);
let text_thread = self.text_thread.read(cx);
Some(proto::view::Variant::ContextEditor(
proto::view::ContextEditor {
context_id: context.id().to_proto(),
context_id: text_thread.id().to_proto(),
editor: if let Some(proto::view::Variant::Editor(proto)) =
self.editor.read(cx).to_state_proto(window, cx)
{
@@ -2666,22 +2679,22 @@ impl FollowableItem for TextThreadEditor {
unreachable!()
};
let context_id = ContextId::from_proto(state.context_id);
let text_thread_id = TextThreadId::from_proto(state.context_id);
let editor_state = state.editor?;
let project = workspace.read(cx).project().clone();
let agent_panel_delegate = <dyn AgentPanelDelegate>::try_global(cx)?;
let context_editor_task = workspace.update(cx, |workspace, cx| {
agent_panel_delegate.open_remote_context(workspace, context_id, window, cx)
let text_thread_editor_task = workspace.update(cx, |workspace, cx| {
agent_panel_delegate.open_remote_text_thread(workspace, text_thread_id, window, cx)
});
Some(window.spawn(cx, async move |cx| {
let context_editor = context_editor_task.await?;
context_editor
.update_in(cx, |context_editor, window, cx| {
context_editor.remote_id = Some(id);
context_editor.editor.update(cx, |editor, cx| {
let text_thread_editor = text_thread_editor_task.await?;
text_thread_editor
.update_in(cx, |text_thread_editor, window, cx| {
text_thread_editor.remote_id = Some(id);
text_thread_editor.editor.update(cx, |editor, cx| {
editor.apply_update_proto(
&project,
proto::update_view::Variant::Editor(proto::update_view::Editor {
@@ -2698,7 +2711,7 @@ impl FollowableItem for TextThreadEditor {
})
})?
.await?;
Ok(context_editor)
Ok(text_thread_editor)
}))
}
@@ -2745,7 +2758,7 @@ impl FollowableItem for TextThreadEditor {
}
fn dedup(&self, existing: &Self, _window: &Window, cx: &App) -> Option<item::Dedup> {
if existing.context.read(cx).id() == self.context.read(cx).id() {
if existing.text_thread.read(cx).id() == self.text_thread.read(cx).id() {
Some(item::Dedup::KeepExisting)
} else {
None
@@ -2757,17 +2770,17 @@ enum PendingSlashCommand {}
fn invoked_slash_command_fold_placeholder(
command_id: InvokedSlashCommandId,
context: WeakEntity<AssistantContext>,
text_thread: WeakEntity<TextThread>,
) -> FoldPlaceholder {
FoldPlaceholder {
constrain_width: false,
merge_adjacent: false,
render: Arc::new(move |fold_id, _, cx| {
let Some(context) = context.upgrade() else {
let Some(text_thread) = text_thread.upgrade() else {
return Empty.into_any();
};
let Some(command) = context.read(cx).invoked_slash_command(&command_id) else {
let Some(command) = text_thread.read(cx).invoked_slash_command(&command_id) else {
return Empty.into_any();
};
@@ -2808,14 +2821,15 @@ enum TokenState {
},
}
fn token_state(context: &Entity<AssistantContext>, cx: &App) -> Option<TokenState> {
fn token_state(text_thread: &Entity<TextThread>, cx: &App) -> Option<TokenState> {
const WARNING_TOKEN_THRESHOLD: f32 = 0.8;
let model = LanguageModelRegistry::read_global(cx)
.default_model()?
.model;
let token_count = context.read(cx).token_count()?;
let max_token_count = model.max_token_count_for_mode(context.read(cx).completion_mode().into());
let token_count = text_thread.read(cx).token_count()?;
let max_token_count =
model.max_token_count_for_mode(text_thread.read(cx).completion_mode().into());
let token_state = if max_token_count.saturating_sub(token_count) == 0 {
TokenState::NoTokensLeft {
max_token_count,
@@ -2927,7 +2941,7 @@ mod tests {
#[gpui::test]
async fn test_copy_paste_whole_message(cx: &mut TestAppContext) {
let (context, context_editor, mut cx) = setup_context_editor_text(vec![
let (context, text_thread_editor, mut cx) = setup_text_thread_editor_text(vec![
(Role::User, "What is the Zed editor?"),
(
Role::Assistant,
@@ -2937,8 +2951,8 @@ mod tests {
],cx).await;
// Select & Copy whole user message
assert_copy_paste_context_editor(
&context_editor,
assert_copy_paste_text_thread_editor(
&text_thread_editor,
message_range(&context, 0, &mut cx),
indoc! {"
What is the Zed editor?
@@ -2949,8 +2963,8 @@ mod tests {
);
// Select & Copy whole assistant message
assert_copy_paste_context_editor(
&context_editor,
assert_copy_paste_text_thread_editor(
&text_thread_editor,
message_range(&context, 1, &mut cx),
indoc! {"
What is the Zed editor?
@@ -2964,7 +2978,7 @@ mod tests {
#[gpui::test]
async fn test_copy_paste_no_selection(cx: &mut TestAppContext) {
let (context, context_editor, mut cx) = setup_context_editor_text(
let (context, text_thread_editor, mut cx) = setup_text_thread_editor_text(
vec![
(Role::User, "user1"),
(Role::Assistant, "assistant1"),
@@ -2977,8 +2991,8 @@ mod tests {
// Copy and paste first assistant message
let message_2_range = message_range(&context, 1, &mut cx);
assert_copy_paste_context_editor(
&context_editor,
assert_copy_paste_text_thread_editor(
&text_thread_editor,
message_2_range.start..message_2_range.start,
indoc! {"
user1
@@ -2991,8 +3005,8 @@ mod tests {
// Copy and cut second assistant message
let message_3_range = message_range(&context, 2, &mut cx);
assert_copy_paste_context_editor(
&context_editor,
assert_copy_paste_text_thread_editor(
&text_thread_editor,
message_3_range.start..message_3_range.start,
indoc! {"
user1
@@ -3079,29 +3093,29 @@ mod tests {
}
}
async fn setup_context_editor_text(
async fn setup_text_thread_editor_text(
messages: Vec<(Role, &str)>,
cx: &mut TestAppContext,
) -> (
Entity<AssistantContext>,
Entity<TextThread>,
Entity<TextThreadEditor>,
VisualTestContext,
) {
cx.update(init_test);
let fs = FakeFs::new(cx.executor());
let context = create_context_with_messages(messages, cx);
let text_thread = create_text_thread_with_messages(messages, cx);
let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let workspace = window.root(cx).unwrap();
let mut cx = VisualTestContext::from_window(*window, cx);
let context_editor = window
let text_thread_editor = window
.update(&mut cx, |_, window, cx| {
cx.new(|cx| {
TextThreadEditor::for_context(
context.clone(),
TextThreadEditor::for_text_thread(
text_thread.clone(),
fs,
workspace.downgrade(),
project,
@@ -3113,59 +3127,59 @@ mod tests {
})
.unwrap();
(context, context_editor, cx)
(text_thread, text_thread_editor, cx)
}
fn message_range(
context: &Entity<AssistantContext>,
text_thread: &Entity<TextThread>,
message_ix: usize,
cx: &mut TestAppContext,
) -> Range<usize> {
context.update(cx, |context, cx| {
context
text_thread.update(cx, |text_thread, cx| {
text_thread
.messages(cx)
.nth(message_ix)
.unwrap()
.anchor_range
.to_offset(&context.buffer().read(cx).snapshot())
.to_offset(&text_thread.buffer().read(cx).snapshot())
})
}
fn assert_copy_paste_context_editor<T: editor::ToOffset>(
context_editor: &Entity<TextThreadEditor>,
fn assert_copy_paste_text_thread_editor<T: editor::ToOffset>(
text_thread_editor: &Entity<TextThreadEditor>,
range: Range<T>,
expected_text: &str,
cx: &mut VisualTestContext,
) {
context_editor.update_in(cx, |context_editor, window, cx| {
context_editor.editor.update(cx, |editor, cx| {
text_thread_editor.update_in(cx, |text_thread_editor, window, cx| {
text_thread_editor.editor.update(cx, |editor, cx| {
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
s.select_ranges([range])
});
});
context_editor.copy(&Default::default(), window, cx);
text_thread_editor.copy(&Default::default(), window, cx);
context_editor.editor.update(cx, |editor, cx| {
text_thread_editor.editor.update(cx, |editor, cx| {
editor.move_to_end(&Default::default(), window, cx);
});
context_editor.paste(&Default::default(), window, cx);
text_thread_editor.paste(&Default::default(), window, cx);
context_editor.editor.update(cx, |editor, cx| {
text_thread_editor.editor.update(cx, |editor, cx| {
assert_eq!(editor.text(cx), expected_text);
});
});
}
fn create_context_with_messages(
fn create_text_thread_with_messages(
mut messages: Vec<(Role, &str)>,
cx: &mut TestAppContext,
) -> Entity<AssistantContext> {
) -> Entity<TextThread> {
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
cx.new(|cx| {
let mut context = AssistantContext::local(
let mut text_thread = TextThread::local(
registry,
None,
None,
@@ -3173,33 +3187,33 @@ mod tests {
Arc::new(SlashCommandWorkingSet::default()),
cx,
);
let mut message_1 = context.messages(cx).next().unwrap();
let mut message_1 = text_thread.messages(cx).next().unwrap();
let (role, text) = messages.remove(0);
loop {
if role == message_1.role {
context.buffer().update(cx, |buffer, cx| {
text_thread.buffer().update(cx, |buffer, cx| {
buffer.edit([(message_1.offset_range, text)], None, cx);
});
break;
}
let mut ids = HashSet::default();
ids.insert(message_1.id);
context.cycle_message_roles(ids, cx);
message_1 = context.messages(cx).next().unwrap();
text_thread.cycle_message_roles(ids, cx);
message_1 = text_thread.messages(cx).next().unwrap();
}
let mut last_message_id = message_1.id;
for (role, text) in messages {
context.insert_message_after(last_message_id, role, MessageStatus::Done, cx);
let message = context.messages(cx).last().unwrap();
text_thread.insert_message_after(last_message_id, role, MessageStatus::Done, cx);
let message = text_thread.messages(cx).last().unwrap();
last_message_id = message.id;
context.buffer().update(cx, |buffer, cx| {
text_thread.buffer().update(cx, |buffer, cx| {
buffer.edit([(message.offset_range, text)], None, cx);
})
}
context
text_thread
})
}
+2 -2
View File
@@ -497,9 +497,9 @@ impl AddedContext {
icon_path: None,
status: ContextStatus::Ready,
render_hover: {
let context = handle.context.clone();
let text_thread = handle.text_thread.clone();
Some(Rc::new(move |_, cx| {
let text = context.read(cx).to_xml(cx);
let text = text_thread.read(cx).to_xml(cx);
ContextPillHover::new_text(text.into(), cx).into()
}))
},