@@ -134,7 +134,7 @@
"ctrl-k z": "editor::ToggleSoftWrap",
"ctrl-f": "buffer_search::Deploy",
"ctrl-h": "buffer_search::DeployReplace",
- "ctrl-shift-.": "assistant::QuoteSelection",
+ "ctrl-shift-.": "agent::QuoteSelection",
"ctrl-shift-,": "assistant::InsertIntoEditor",
"shift-alt-e": "editor::SelectEnclosingSymbol",
"ctrl-shift-backspace": "editor::GoToPreviousChange",
@@ -244,7 +244,7 @@
"ctrl-shift-i": "agent::ToggleOptionsMenu",
// "ctrl-shift-alt-n": "agent::ToggleNewThreadMenu",
"shift-alt-escape": "agent::ExpandMessageEditor",
- "ctrl-shift-.": "assistant::QuoteSelection",
+ "ctrl-shift-.": "agent::QuoteSelection",
"shift-alt-e": "agent::RemoveAllContext",
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-shift-enter": "agent::ContinueThread",
@@ -4972,10 +4972,12 @@ impl AcpThreadView {
})
}
+ /// Inserts the selected text into the message editor or the message being
+ /// edited, if any.
pub(crate) fn insert_selections(&self, window: &mut Window, cx: &mut Context<Self>) {
- self.message_editor.update(cx, |message_editor, cx| {
- message_editor.insert_selections(window, cx);
- })
+ self.active_editor(cx).update(cx, |editor, cx| {
+ editor.insert_selections(window, cx);
+ });
}
fn render_thread_retry_status_callout(
@@ -5386,6 +5388,23 @@ impl AcpThreadView {
};
task.detach_and_log_err(cx);
}
+
+ /// Returns the currently active editor, either for a message that is being
+ /// edited or the editor for a new message.
+ fn active_editor(&self, cx: &App) -> Entity<MessageEditor> {
+ if let Some(index) = self.editing_message
+ && let Some(editor) = self
+ .entry_view_state
+ .read(cx)
+ .entry(index)
+ .and_then(|e| e.message_editor())
+ .cloned()
+ {
+ editor
+ } else {
+ self.message_editor.clone()
+ }
+ }
}
fn loading_contents_spinner(size: IconSize) -> AnyElement {
@@ -5400,7 +5419,7 @@ impl Focusable for AcpThreadView {
fn focus_handle(&self, cx: &App) -> FocusHandle {
match self.thread_state {
ThreadState::Loading { .. } | ThreadState::Ready { .. } => {
- self.message_editor.focus_handle(cx)
+ self.active_editor(cx).focus_handle(cx)
}
ThreadState::LoadError(_) | ThreadState::Unauthenticated { .. } => {
self.focus_handle.clone()
@@ -6661,4 +6680,146 @@ pub(crate) mod tests {
)
});
}
+
+ #[gpui::test]
+ async fn test_message_editing_insert_selections(cx: &mut TestAppContext) {
+ init_test(cx);
+
+ let connection = StubAgentConnection::new();
+ connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk {
+ content: acp::ContentBlock::Text(acp::TextContent {
+ text: "Response".into(),
+ annotations: None,
+ meta: None,
+ }),
+ }]);
+
+ let (thread_view, cx) = setup_thread_view(StubAgentServer::new(connection), cx).await;
+ add_to_workspace(thread_view.clone(), cx);
+
+ let message_editor = cx.read(|cx| thread_view.read(cx).message_editor.clone());
+ message_editor.update_in(cx, |editor, window, cx| {
+ editor.set_text("Original message to edit", window, cx)
+ });
+ thread_view.update_in(cx, |thread_view, window, cx| thread_view.send(window, cx));
+ cx.run_until_parked();
+
+ let user_message_editor = thread_view.read_with(cx, |thread_view, cx| {
+ thread_view
+ .entry_view_state
+ .read(cx)
+ .entry(0)
+ .expect("Should have at least one entry")
+ .message_editor()
+ .expect("Should have message editor")
+ .clone()
+ });
+
+ cx.focus(&user_message_editor);
+ thread_view.read_with(cx, |thread_view, _cx| {
+ assert_eq!(thread_view.editing_message, Some(0));
+ });
+
+ // Ensure to edit the focused message before proceeding otherwise, since
+ // its content is not different from what was sent, focus will be lost.
+ user_message_editor.update_in(cx, |editor, window, cx| {
+ editor.set_text("Original message to edit with ", window, cx)
+ });
+
+ // Create a simple buffer with some text so we can create a selection
+ // that will then be added to the message being edited.
+ let (workspace, project) = thread_view.read_with(cx, |thread_view, _cx| {
+ (thread_view.workspace.clone(), thread_view.project.clone())
+ });
+ let buffer = project.update(cx, |project, cx| {
+ project.create_local_buffer("let a = 10 + 10;", None, false, cx)
+ });
+
+ workspace
+ .update_in(cx, |workspace, window, cx| {
+ let editor = cx.new(|cx| {
+ let mut editor =
+ Editor::for_buffer(buffer.clone(), Some(project.clone()), window, cx);
+
+ editor.change_selections(Default::default(), window, cx, |selections| {
+ selections.select_ranges([8..15]);
+ });
+
+ editor
+ });
+ workspace.add_item_to_active_pane(Box::new(editor), None, false, window, cx);
+ })
+ .unwrap();
+
+ thread_view.update_in(cx, |thread_view, window, cx| {
+ assert_eq!(thread_view.editing_message, Some(0));
+ thread_view.insert_selections(window, cx);
+ });
+
+ user_message_editor.read_with(cx, |editor, cx| {
+ let text = editor.editor().read(cx).text(cx);
+ let expected_text = String::from("Original message to edit with selection ");
+
+ assert_eq!(text, expected_text);
+ });
+ }
+
+ #[gpui::test]
+ async fn test_insert_selections(cx: &mut TestAppContext) {
+ init_test(cx);
+
+ let connection = StubAgentConnection::new();
+ connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk {
+ content: acp::ContentBlock::Text(acp::TextContent {
+ text: "Response".into(),
+ annotations: None,
+ meta: None,
+ }),
+ }]);
+
+ let (thread_view, cx) = setup_thread_view(StubAgentServer::new(connection), cx).await;
+ add_to_workspace(thread_view.clone(), cx);
+
+ let message_editor = cx.read(|cx| thread_view.read(cx).message_editor.clone());
+ message_editor.update_in(cx, |editor, window, cx| {
+ editor.set_text("Can you review this snippet ", window, cx)
+ });
+
+ // Create a simple buffer with some text so we can create a selection
+ // that will then be added to the message being edited.
+ let (workspace, project) = thread_view.read_with(cx, |thread_view, _cx| {
+ (thread_view.workspace.clone(), thread_view.project.clone())
+ });
+ let buffer = project.update(cx, |project, cx| {
+ project.create_local_buffer("let a = 10 + 10;", None, false, cx)
+ });
+
+ workspace
+ .update_in(cx, |workspace, window, cx| {
+ let editor = cx.new(|cx| {
+ let mut editor =
+ Editor::for_buffer(buffer.clone(), Some(project.clone()), window, cx);
+
+ editor.change_selections(Default::default(), window, cx, |selections| {
+ selections.select_ranges([8..15]);
+ });
+
+ editor
+ });
+ workspace.add_item_to_active_pane(Box::new(editor), None, false, window, cx);
+ })
+ .unwrap();
+
+ thread_view.update_in(cx, |thread_view, window, cx| {
+ assert_eq!(thread_view.editing_message, None);
+ thread_view.insert_selections(window, cx);
+ });
+
+ thread_view.read_with(cx, |thread_view, cx| {
+ let text = thread_view.message_editor.read(cx).text(cx);
+ let expected_txt = String::from("Can you review this snippet selection ");
+
+ assert_eq!(text, expected_txt);
+ })
+ }
}