From 2b74163a48c3d7cec5c326b17d2f6d98ca9b7b24 Mon Sep 17 00:00:00 2001 From: Bennet Bo Fenner Date: Tue, 13 May 2025 23:23:19 +0200 Subject: [PATCH] context_editor: Allow copying entire line when selection is empty (#30612) Closes #27879 Release Notes: - Allow copying entire line when selection is empty in text threads --- .../src/context_editor.rs | 100 +++++++++++++++++- 1 file changed, 96 insertions(+), 4 deletions(-) diff --git a/crates/assistant_context_editor/src/context_editor.rs b/crates/assistant_context_editor/src/context_editor.rs index b3c20e7720..42ca88e2f2 100644 --- a/crates/assistant_context_editor/src/context_editor.rs +++ b/crates/assistant_context_editor/src/context_editor.rs @@ -1590,7 +1590,7 @@ impl ContextEditor { &mut self, cx: &mut Context, ) -> (String, CopyMetadata, Vec>) { - let (selection, creases) = self.editor.update(cx, |editor, cx| { + let (mut selection, creases) = self.editor.update(cx, |editor, cx| { let mut selection = editor.selections.newest_adjusted(cx); let snapshot = editor.buffer().read(cx).snapshot(cx); @@ -1648,7 +1648,18 @@ impl ContextEditor { } 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() { + if range.is_empty() { + let snapshot = context.buffer().read(cx).snapshot(); + let point = snapshot.offset_to_point(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()) { + text.push_str(chunk); + } + } else { for chunk in context.buffer().read(cx).text_for_range(range) { text.push_str(chunk); } @@ -3202,9 +3213,77 @@ pub fn make_lsp_adapter_delegate( #[cfg(test)] mod tests { use super::*; - use gpui::App; - use language::Buffer; + use fs::FakeFs; + use gpui::{App, TestAppContext, VisualTestContext}; + use language::{Buffer, LanguageRegistry}; + use prompt_store::PromptBuilder; use unindent::Unindent; + use util::path; + + #[gpui::test] + async fn test_copy_paste_no_selection(cx: &mut TestAppContext) { + cx.update(init_test); + + let fs = FakeFs::new(cx.executor()); + let registry = Arc::new(LanguageRegistry::test(cx.executor())); + let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); + let context = cx.new(|cx| { + AssistantContext::local( + registry, + None, + None, + prompt_builder.clone(), + Arc::new(SlashCommandWorkingSet::default()), + 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 cx = &mut VisualTestContext::from_window(*window, cx); + + let context_editor = window + .update(cx, |_, window, cx| { + cx.new(|cx| { + ContextEditor::for_context( + context, + fs, + workspace.downgrade(), + project, + None, + window, + cx, + ) + }) + }) + .unwrap(); + + context_editor.update_in(cx, |context_editor, window, cx| { + context_editor.editor.update(cx, |editor, cx| { + editor.set_text("abc\ndef\nghi", window, cx); + editor.move_to_beginning(&Default::default(), window, cx); + }) + }); + + context_editor.update_in(cx, |context_editor, window, cx| { + context_editor.editor.update(cx, |editor, cx| { + editor.copy(&Default::default(), window, cx); + editor.paste(&Default::default(), window, cx); + + assert_eq!(editor.text(cx), "abc\nabc\ndef\nghi"); + }) + }); + + context_editor.update_in(cx, |context_editor, window, cx| { + context_editor.editor.update(cx, |editor, cx| { + editor.cut(&Default::default(), window, cx); + assert_eq!(editor.text(cx), "abc\ndef\nghi"); + + editor.paste(&Default::default(), window, cx); + assert_eq!(editor.text(cx), "abc\nabc\ndef\nghi"); + }) + }); + } #[gpui::test] fn test_find_code_blocks(cx: &mut App) { @@ -3279,4 +3358,17 @@ mod tests { assert_eq!(range, expected, "unexpected result on row {:?}", row); } } + + fn init_test(cx: &mut App) { + let settings_store = SettingsStore::test(cx); + prompt_store::init(cx); + LanguageModelRegistry::test(cx); + cx.set_global(settings_store); + language::init(cx); + assistant_settings::init(cx); + Project::init_settings(cx); + theme::init(theme::LoadThemes::JustBase, cx); + workspace::init_settings(cx); + editor::init_settings(cx); + } }