diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ff6778125cd3349b2046a82097030634ddb86767..1134567a84e4c4a0371f4fa90057b060a50cff15 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 199d0294e60915d11ddddc111c7570ab724b728d..2e957f313c0b4a5984b4a0b3940ce095a1555cba 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 87225e1972a4cc4e502487ff8459bdabbe571cc3..e7d9ac93065c168ba9993fab4422cc0ec193d653 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 62be295632affcbe35207a9d2aa4b62b63bc51f1..23cd8bce52a9da1314a476f1429f8bb36c209992 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 fdad4018510e08c41ea3aa7d280f76653e9aacbf..578c4a6c3ba7190ec9e11d27079a52b4a7ac62d0 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 36311fced4b16df94f4b1d3bbc742f5493365722..6bf70ad46dfe2bc86946f764c25f57a9b9fe07cd 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 1103bfe3e890a645e0909bb829861556c6bd3565..bf287956a4991b3f4d84878c4f30fdc97d4e2685 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 ae1723f30cc97b23446ade2aabac473a24c188f4..74b11c27584a3bda32617f5ecd66f4f5743be7ff 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 7166206cb736e137e8821c63cd28b8861db44ff3..57728986935cab9610239420f7bedaa5b0865d22 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 f54f60f045e513df4ba05b30fd0c98aaa32ef97e..14618e2584ad12bf351bba3420cdeb9c54c7d653 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 47bb06443a9eeb453844818b9dc130ce712bcf88..41baf4fe679514bc87301204959045521aedb633 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 63fc4b279322195d7d981c48b199828bd0fd1ff4..c30b87c01520f63c16526a0aa6ff78c66cd93891 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 53baa4ea25307dcc11f3a30d969fe0cd219a02e3..5f06d7f038de89d04c2c81839c2a93cf51db3233 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); }, ), );