diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ff6778125c..1134567a84 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1828,6 +1828,29 @@ impl Editor { old_cursor_position: &Anchor, cx: &mut ViewContext, ) { + // Copy selections to primary selection buffer + #[cfg(target_os = "linux")] + if local { + let selections = self.selections.all::(cx); + let buffer_handle = self.buffer.read(cx).read(cx); + + let mut text = String::new(); + for (index, selection) in selections.iter().enumerate() { + let text_for_selection = buffer_handle + .text_for_range(selection.start..selection.end) + .collect::(); + + text.push_str(&text_for_selection); + if index != selections.len() - 1 { + text.push('\n'); + } + } + + if !text.is_empty() { + cx.write_to_primary(ClipboardItem::new(text)); + } + } + if self.focus_handle.is_focused(cx) && self.leader_peer_id.is_none() { self.buffer.update(cx, |buffer, cx| { buffer.set_active_selections( diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 199d0294e6..2e957f313c 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -502,6 +502,34 @@ impl EditorElement { cx.stop_propagation(); } + fn mouse_middle_down( + editor: &mut Editor, + event: &MouseDownEvent, + position_map: &PositionMap, + text_hitbox: &Hitbox, + cx: &mut ViewContext, + ) { + if !text_hitbox.is_hovered(cx) || editor.read_only(cx) { + return; + } + + if let Some(item) = cx.read_from_primary() { + let point_for_position = + position_map.point_for_position(text_hitbox.bounds, event.position); + let position = point_for_position.previous_valid; + + editor.select( + SelectPhase::Begin { + position, + add: false, + click_count: 1, + }, + cx, + ); + editor.insert(item.text(), cx); + } + } + fn mouse_up( editor: &mut Editor, event: &MouseUpEvent, @@ -2903,6 +2931,9 @@ impl EditorElement { MouseButton::Right => editor.update(cx, |editor, cx| { Self::mouse_right_down(editor, event, &position_map, &text_hitbox, cx); }), + MouseButton::Middle => editor.update(cx, |editor, cx| { + Self::mouse_middle_down(editor, event, &position_map, &text_hitbox, cx); + }), _ => {} }; } diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 87225e1972..e7d9ac9306 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -547,11 +547,23 @@ impl AppContext { self.platform.window_appearance() } + /// Writes data to the primary selection buffer. + /// Only available on Linux. + pub fn write_to_primary(&self, item: ClipboardItem) { + self.platform.write_to_primary(item) + } + /// Writes data to the platform clipboard. pub fn write_to_clipboard(&self, item: ClipboardItem) { self.platform.write_to_clipboard(item) } + /// Reads data from the primary selection buffer. + /// Only available on Linux. + pub fn read_from_primary(&self) -> Option { + self.platform.read_from_primary() + } + /// Reads data from the platform clipboard. pub fn read_from_clipboard(&self) -> Option { self.platform.read_from_clipboard() diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 62be295632..23cd8bce52 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -151,7 +151,9 @@ pub(crate) trait Platform: 'static { fn set_cursor_style(&self, style: CursorStyle); fn should_auto_hide_scrollbars(&self) -> bool; + fn write_to_primary(&self, item: ClipboardItem); fn write_to_clipboard(&self, item: ClipboardItem); + fn read_from_primary(&self) -> Option; fn read_from_clipboard(&self) -> Option; fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task>; diff --git a/crates/gpui/src/platform/linux/headless/client.rs b/crates/gpui/src/platform/linux/headless/client.rs index fdad401851..578c4a6c3b 100644 --- a/crates/gpui/src/platform/linux/headless/client.rs +++ b/crates/gpui/src/platform/linux/headless/client.rs @@ -79,8 +79,14 @@ impl LinuxClient for HeadlessClient { //todo(linux) fn set_cursor_style(&self, _style: CursorStyle) {} + fn write_to_primary(&self, item: crate::ClipboardItem) {} + fn write_to_clipboard(&self, item: crate::ClipboardItem) {} + fn read_from_primary(&self) -> Option { + None + } + fn read_from_clipboard(&self) -> Option { None } diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index 36311fced4..6bf70ad46d 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -56,7 +56,9 @@ pub trait LinuxClient { options: WindowParams, ) -> Box; fn set_cursor_style(&self, style: CursorStyle); + fn write_to_primary(&self, item: ClipboardItem); fn write_to_clipboard(&self, item: ClipboardItem); + fn read_from_primary(&self) -> Option; fn read_from_clipboard(&self) -> Option; fn run(&self); } @@ -406,7 +408,6 @@ impl Platform for P { }) } - //todo(linux): add trait methods for accessing the primary selection fn read_credentials(&self, url: &str) -> Task)>>> { let url = url.to_string(); self.background_executor().spawn(async move { @@ -461,10 +462,18 @@ impl Platform for P { Task::ready(Err(anyhow!("register_url_scheme unimplemented"))) } + fn write_to_primary(&self, item: ClipboardItem) { + self.write_to_primary(item) + } + fn write_to_clipboard(&self, item: ClipboardItem) { self.write_to_clipboard(item) } + fn read_from_primary(&self) -> Option { + self.read_from_primary() + } + fn read_from_clipboard(&self) -> Option { self.read_from_clipboard() } diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs index 1103bfe3e8..bf287956a4 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/crates/gpui/src/platform/linux/wayland/client.rs @@ -377,10 +377,26 @@ impl LinuxClient for WaylandClient { .log_err(); } + fn write_to_primary(&self, item: crate::ClipboardItem) { + self.0.borrow_mut().primary.set_contents(item.text); + } + fn write_to_clipboard(&self, item: crate::ClipboardItem) { self.0.borrow_mut().clipboard.set_contents(item.text); } + fn read_from_primary(&self) -> Option { + self.0 + .borrow_mut() + .primary + .get_contents() + .ok() + .map(|s| crate::ClipboardItem { + text: s, + metadata: None, + }) + } + fn read_from_clipboard(&self) -> Option { self.0 .borrow_mut() diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index ae1723f30c..74b11c2758 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/crates/gpui/src/platform/linux/x11/client.rs @@ -603,10 +603,26 @@ impl LinuxClient for X11Client { //todo(linux) fn set_cursor_style(&self, _style: CursorStyle) {} + fn write_to_primary(&self, item: crate::ClipboardItem) { + self.0.borrow_mut().primary.set_contents(item.text); + } + fn write_to_clipboard(&self, item: crate::ClipboardItem) { self.0.borrow_mut().clipboard.set_contents(item.text); } + fn read_from_primary(&self) -> Option { + self.0 + .borrow_mut() + .primary + .get_contents() + .ok() + .map(|text| crate::ClipboardItem { + text, + metadata: None, + }) + } + fn read_from_clipboard(&self) -> Option { self.0 .borrow_mut() diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 7166206cb7..5772898693 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -849,6 +849,8 @@ impl Platform for MacPlatform { } } + fn write_to_primary(&self, _item: ClipboardItem) {} + fn write_to_clipboard(&self, item: ClipboardItem) { let state = self.0.lock(); unsafe { @@ -886,6 +888,10 @@ impl Platform for MacPlatform { } } + fn read_from_primary(&self) -> Option { + None + } + fn read_from_clipboard(&self) -> Option { let state = self.0.lock(); unsafe { diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index f54f60f045..14618e2584 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -23,6 +23,7 @@ pub(crate) struct TestPlatform { active_display: Rc, active_cursor: Mutex, current_clipboard_item: Mutex>, + current_primary_item: Mutex>, pub(crate) prompts: RefCell, pub opened_url: RefCell>, weak: Weak, @@ -44,6 +45,7 @@ impl TestPlatform { active_display: Rc::new(TestDisplay::new()), active_window: Default::default(), current_clipboard_item: Mutex::new(None), + current_primary_item: Mutex::new(None), weak: weak.clone(), opened_url: Default::default(), }) @@ -282,10 +284,18 @@ impl Platform for TestPlatform { false } + fn write_to_primary(&self, item: ClipboardItem) { + *self.current_primary_item.lock() = Some(item); + } + fn write_to_clipboard(&self, item: ClipboardItem) { *self.current_clipboard_item.lock() = Some(item); } + fn read_from_primary(&self) -> Option { + self.current_primary_item.lock().clone() + } + fn read_from_clipboard(&self) -> Option { self.current_clipboard_item.lock().clone() } diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index 47bb06443a..41baf4fe67 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/crates/gpui/src/platform/windows/platform.rs @@ -692,6 +692,8 @@ impl Platform for WindowsPlatform { false } + fn write_to_primary(&self, _item: ClipboardItem) {} + fn write_to_clipboard(&self, item: ClipboardItem) { if item.text.len() > 0 { let mut ctx = ClipboardContext::new().unwrap(); @@ -699,6 +701,10 @@ impl Platform for WindowsPlatform { } } + fn read_from_primary(&self) -> Option { + None + } + fn read_from_clipboard(&self) -> Option { let mut ctx = ClipboardContext::new().unwrap(); let content = ctx.get_contents().unwrap(); diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 63fc4b2793..c30b87c015 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -750,6 +750,11 @@ impl Terminal { InternalEvent::SetSelection(selection) => { term.selection = selection.as_ref().map(|(sel, _)| sel.clone()); + #[cfg(target_os = "linux")] + if let Some(selection_text) = term.selection_to_string() { + cx.write_to_primary(ClipboardItem::new(selection_text)); + } + if let Some((_, head)) = selection { self.selection_head = Some(*head); } @@ -766,6 +771,11 @@ impl Terminal { selection.update(point, side); term.selection = Some(selection); + #[cfg(target_os = "linux")] + if let Some(selection_text) = term.selection_to_string() { + cx.write_to_primary(ClipboardItem::new(selection_text)); + } + self.selection_head = Some(point); cx.emit(Event::SelectionsChanged) } @@ -1192,7 +1202,12 @@ impl Terminal { Some(scroll_delta) } - pub fn mouse_down(&mut self, e: &MouseDownEvent, origin: Point) { + pub fn mouse_down( + &mut self, + e: &MouseDownEvent, + origin: Point, + cx: &mut ModelContext, + ) { let position = e.position - origin; let point = grid_point( position, @@ -1229,6 +1244,11 @@ impl Terminal { self.events .push_back(InternalEvent::SetSelection(Some((sel, point)))); } + } else if e.button == MouseButton::Middle { + if let Some(item) = cx.read_from_primary() { + let text = item.text().to_string(); + self.input(text); + } } } diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 53baa4ea25..5f06d7f038 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -429,7 +429,7 @@ impl TerminalElement { move |e, cx| { cx.focus(&focus); terminal.update(cx, |terminal, cx| { - terminal.mouse_down(&e, origin); + terminal.mouse_down(&e, origin, cx); cx.notify(); }) } @@ -479,6 +479,17 @@ impl TerminalElement { }, ), ); + self.interactivity.on_mouse_down( + MouseButton::Middle, + TerminalElement::generic_button_handler( + terminal.clone(), + origin, + focus.clone(), + move |terminal, origin, e, cx| { + terminal.mouse_down(&e, origin, cx); + }, + ), + ); self.interactivity.on_scroll_wheel({ let terminal = terminal.clone(); move |e, cx| { @@ -498,19 +509,8 @@ impl TerminalElement { terminal.clone(), origin, focus.clone(), - move |terminal, origin, e, _cx| { - terminal.mouse_down(&e, origin); - }, - ), - ); - self.interactivity.on_mouse_down( - MouseButton::Middle, - TerminalElement::generic_button_handler( - terminal.clone(), - origin, - focus.clone(), - move |terminal, origin, e, _cx| { - terminal.mouse_down(&e, origin); + move |terminal, origin, e, cx| { + terminal.mouse_down(&e, origin, cx); }, ), );