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
This commit is contained in:
Bennet Bo Fenner 2025-05-13 23:23:19 +02:00 committed by GitHub
parent 71ea7aee3b
commit 2b74163a48
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1590,7 +1590,7 @@ impl ContextEditor {
&mut self, &mut self,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> (String, CopyMetadata, Vec<text::Selection<usize>>) { ) -> (String, CopyMetadata, Vec<text::Selection<usize>>) {
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 mut selection = editor.selections.newest_adjusted(cx);
let snapshot = editor.buffer().read(cx).snapshot(cx); let snapshot = editor.buffer().read(cx).snapshot(cx);
@ -1648,7 +1648,18 @@ impl ContextEditor {
} else if message.offset_range.end >= selection.range().start { } else if message.offset_range.end >= selection.range().start {
let range = cmp::max(message.offset_range.start, selection.range().start) let range = cmp::max(message.offset_range.start, selection.range().start)
..cmp::min(message.offset_range.end, selection.range().end); ..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) { for chunk in context.buffer().read(cx).text_for_range(range) {
text.push_str(chunk); text.push_str(chunk);
} }
@ -3202,9 +3213,77 @@ pub fn make_lsp_adapter_delegate(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use gpui::App; use fs::FakeFs;
use language::Buffer; use gpui::{App, TestAppContext, VisualTestContext};
use language::{Buffer, LanguageRegistry};
use prompt_store::PromptBuilder;
use unindent::Unindent; 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] #[gpui::test]
fn test_find_code_blocks(cx: &mut App) { fn test_find_code_blocks(cx: &mut App) {
@ -3279,4 +3358,17 @@ mod tests {
assert_eq!(range, expected, "unexpected result on row {:?}", row); 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);
}
} }