linux: Primary clipboard (#10534)
Implements copying from and pasting to the primary selection. Release Notes: - N/A
This commit is contained in:
parent
98db7fa61e
commit
b31df39ab0
13 changed files with 173 additions and 16 deletions
|
@ -1828,6 +1828,29 @@ impl Editor {
|
||||||
old_cursor_position: &Anchor,
|
old_cursor_position: &Anchor,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
|
// Copy selections to primary selection buffer
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
if local {
|
||||||
|
let selections = self.selections.all::<usize>(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::<String>();
|
||||||
|
|
||||||
|
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() {
|
if self.focus_handle.is_focused(cx) && self.leader_peer_id.is_none() {
|
||||||
self.buffer.update(cx, |buffer, cx| {
|
self.buffer.update(cx, |buffer, cx| {
|
||||||
buffer.set_active_selections(
|
buffer.set_active_selections(
|
||||||
|
|
|
@ -502,6 +502,34 @@ impl EditorElement {
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn mouse_middle_down(
|
||||||
|
editor: &mut Editor,
|
||||||
|
event: &MouseDownEvent,
|
||||||
|
position_map: &PositionMap,
|
||||||
|
text_hitbox: &Hitbox,
|
||||||
|
cx: &mut ViewContext<Editor>,
|
||||||
|
) {
|
||||||
|
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(
|
fn mouse_up(
|
||||||
editor: &mut Editor,
|
editor: &mut Editor,
|
||||||
event: &MouseUpEvent,
|
event: &MouseUpEvent,
|
||||||
|
@ -2903,6 +2931,9 @@ impl EditorElement {
|
||||||
MouseButton::Right => editor.update(cx, |editor, cx| {
|
MouseButton::Right => editor.update(cx, |editor, cx| {
|
||||||
Self::mouse_right_down(editor, event, &position_map, &text_hitbox, 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);
|
||||||
|
}),
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -547,11 +547,23 @@ impl AppContext {
|
||||||
self.platform.window_appearance()
|
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.
|
/// Writes data to the platform clipboard.
|
||||||
pub fn write_to_clipboard(&self, item: ClipboardItem) {
|
pub fn write_to_clipboard(&self, item: ClipboardItem) {
|
||||||
self.platform.write_to_clipboard(item)
|
self.platform.write_to_clipboard(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reads data from the primary selection buffer.
|
||||||
|
/// Only available on Linux.
|
||||||
|
pub fn read_from_primary(&self) -> Option<ClipboardItem> {
|
||||||
|
self.platform.read_from_primary()
|
||||||
|
}
|
||||||
|
|
||||||
/// Reads data from the platform clipboard.
|
/// Reads data from the platform clipboard.
|
||||||
pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
|
pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
|
||||||
self.platform.read_from_clipboard()
|
self.platform.read_from_clipboard()
|
||||||
|
|
|
@ -151,7 +151,9 @@ pub(crate) trait Platform: 'static {
|
||||||
fn set_cursor_style(&self, style: CursorStyle);
|
fn set_cursor_style(&self, style: CursorStyle);
|
||||||
fn should_auto_hide_scrollbars(&self) -> bool;
|
fn should_auto_hide_scrollbars(&self) -> bool;
|
||||||
|
|
||||||
|
fn write_to_primary(&self, item: ClipboardItem);
|
||||||
fn write_to_clipboard(&self, item: ClipboardItem);
|
fn write_to_clipboard(&self, item: ClipboardItem);
|
||||||
|
fn read_from_primary(&self) -> Option<ClipboardItem>;
|
||||||
fn read_from_clipboard(&self) -> Option<ClipboardItem>;
|
fn read_from_clipboard(&self) -> Option<ClipboardItem>;
|
||||||
|
|
||||||
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
|
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
|
||||||
|
|
|
@ -79,8 +79,14 @@ impl LinuxClient for HeadlessClient {
|
||||||
//todo(linux)
|
//todo(linux)
|
||||||
fn set_cursor_style(&self, _style: CursorStyle) {}
|
fn set_cursor_style(&self, _style: CursorStyle) {}
|
||||||
|
|
||||||
|
fn write_to_primary(&self, item: crate::ClipboardItem) {}
|
||||||
|
|
||||||
fn write_to_clipboard(&self, item: crate::ClipboardItem) {}
|
fn write_to_clipboard(&self, item: crate::ClipboardItem) {}
|
||||||
|
|
||||||
|
fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
|
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,9 @@ pub trait LinuxClient {
|
||||||
options: WindowParams,
|
options: WindowParams,
|
||||||
) -> Box<dyn PlatformWindow>;
|
) -> Box<dyn PlatformWindow>;
|
||||||
fn set_cursor_style(&self, style: CursorStyle);
|
fn set_cursor_style(&self, style: CursorStyle);
|
||||||
|
fn write_to_primary(&self, item: ClipboardItem);
|
||||||
fn write_to_clipboard(&self, item: ClipboardItem);
|
fn write_to_clipboard(&self, item: ClipboardItem);
|
||||||
|
fn read_from_primary(&self) -> Option<ClipboardItem>;
|
||||||
fn read_from_clipboard(&self) -> Option<ClipboardItem>;
|
fn read_from_clipboard(&self) -> Option<ClipboardItem>;
|
||||||
fn run(&self);
|
fn run(&self);
|
||||||
}
|
}
|
||||||
|
@ -406,7 +408,6 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//todo(linux): add trait methods for accessing the primary selection
|
|
||||||
fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
|
fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
|
||||||
let url = url.to_string();
|
let url = url.to_string();
|
||||||
self.background_executor().spawn(async move {
|
self.background_executor().spawn(async move {
|
||||||
|
@ -461,10 +462,18 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||||
Task::ready(Err(anyhow!("register_url_scheme unimplemented")))
|
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) {
|
fn write_to_clipboard(&self, item: ClipboardItem) {
|
||||||
self.write_to_clipboard(item)
|
self.write_to_clipboard(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_from_primary(&self) -> Option<ClipboardItem> {
|
||||||
|
self.read_from_primary()
|
||||||
|
}
|
||||||
|
|
||||||
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
|
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
|
||||||
self.read_from_clipboard()
|
self.read_from_clipboard()
|
||||||
}
|
}
|
||||||
|
|
|
@ -377,10 +377,26 @@ impl LinuxClient for WaylandClient {
|
||||||
.log_err();
|
.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) {
|
fn write_to_clipboard(&self, item: crate::ClipboardItem) {
|
||||||
self.0.borrow_mut().clipboard.set_contents(item.text);
|
self.0.borrow_mut().clipboard.set_contents(item.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
|
||||||
|
self.0
|
||||||
|
.borrow_mut()
|
||||||
|
.primary
|
||||||
|
.get_contents()
|
||||||
|
.ok()
|
||||||
|
.map(|s| crate::ClipboardItem {
|
||||||
|
text: s,
|
||||||
|
metadata: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
|
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
|
||||||
self.0
|
self.0
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
|
|
|
@ -603,10 +603,26 @@ impl LinuxClient for X11Client {
|
||||||
//todo(linux)
|
//todo(linux)
|
||||||
fn set_cursor_style(&self, _style: CursorStyle) {}
|
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) {
|
fn write_to_clipboard(&self, item: crate::ClipboardItem) {
|
||||||
self.0.borrow_mut().clipboard.set_contents(item.text);
|
self.0.borrow_mut().clipboard.set_contents(item.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
|
||||||
|
self.0
|
||||||
|
.borrow_mut()
|
||||||
|
.primary
|
||||||
|
.get_contents()
|
||||||
|
.ok()
|
||||||
|
.map(|text| crate::ClipboardItem {
|
||||||
|
text,
|
||||||
|
metadata: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
|
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
|
||||||
self.0
|
self.0
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
|
|
|
@ -849,6 +849,8 @@ impl Platform for MacPlatform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_to_primary(&self, _item: ClipboardItem) {}
|
||||||
|
|
||||||
fn write_to_clipboard(&self, item: ClipboardItem) {
|
fn write_to_clipboard(&self, item: ClipboardItem) {
|
||||||
let state = self.0.lock();
|
let state = self.0.lock();
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -886,6 +888,10 @@ impl Platform for MacPlatform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_from_primary(&self) -> Option<ClipboardItem> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
|
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
|
||||||
let state = self.0.lock();
|
let state = self.0.lock();
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|
|
@ -23,6 +23,7 @@ pub(crate) struct TestPlatform {
|
||||||
active_display: Rc<dyn PlatformDisplay>,
|
active_display: Rc<dyn PlatformDisplay>,
|
||||||
active_cursor: Mutex<CursorStyle>,
|
active_cursor: Mutex<CursorStyle>,
|
||||||
current_clipboard_item: Mutex<Option<ClipboardItem>>,
|
current_clipboard_item: Mutex<Option<ClipboardItem>>,
|
||||||
|
current_primary_item: Mutex<Option<ClipboardItem>>,
|
||||||
pub(crate) prompts: RefCell<TestPrompts>,
|
pub(crate) prompts: RefCell<TestPrompts>,
|
||||||
pub opened_url: RefCell<Option<String>>,
|
pub opened_url: RefCell<Option<String>>,
|
||||||
weak: Weak<Self>,
|
weak: Weak<Self>,
|
||||||
|
@ -44,6 +45,7 @@ impl TestPlatform {
|
||||||
active_display: Rc::new(TestDisplay::new()),
|
active_display: Rc::new(TestDisplay::new()),
|
||||||
active_window: Default::default(),
|
active_window: Default::default(),
|
||||||
current_clipboard_item: Mutex::new(None),
|
current_clipboard_item: Mutex::new(None),
|
||||||
|
current_primary_item: Mutex::new(None),
|
||||||
weak: weak.clone(),
|
weak: weak.clone(),
|
||||||
opened_url: Default::default(),
|
opened_url: Default::default(),
|
||||||
})
|
})
|
||||||
|
@ -282,10 +284,18 @@ impl Platform for TestPlatform {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_to_primary(&self, item: ClipboardItem) {
|
||||||
|
*self.current_primary_item.lock() = Some(item);
|
||||||
|
}
|
||||||
|
|
||||||
fn write_to_clipboard(&self, item: ClipboardItem) {
|
fn write_to_clipboard(&self, item: ClipboardItem) {
|
||||||
*self.current_clipboard_item.lock() = Some(item);
|
*self.current_clipboard_item.lock() = Some(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_from_primary(&self) -> Option<ClipboardItem> {
|
||||||
|
self.current_primary_item.lock().clone()
|
||||||
|
}
|
||||||
|
|
||||||
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
|
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
|
||||||
self.current_clipboard_item.lock().clone()
|
self.current_clipboard_item.lock().clone()
|
||||||
}
|
}
|
||||||
|
|
|
@ -692,6 +692,8 @@ impl Platform for WindowsPlatform {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_to_primary(&self, _item: ClipboardItem) {}
|
||||||
|
|
||||||
fn write_to_clipboard(&self, item: ClipboardItem) {
|
fn write_to_clipboard(&self, item: ClipboardItem) {
|
||||||
if item.text.len() > 0 {
|
if item.text.len() > 0 {
|
||||||
let mut ctx = ClipboardContext::new().unwrap();
|
let mut ctx = ClipboardContext::new().unwrap();
|
||||||
|
@ -699,6 +701,10 @@ impl Platform for WindowsPlatform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_from_primary(&self) -> Option<ClipboardItem> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
|
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
|
||||||
let mut ctx = ClipboardContext::new().unwrap();
|
let mut ctx = ClipboardContext::new().unwrap();
|
||||||
let content = ctx.get_contents().unwrap();
|
let content = ctx.get_contents().unwrap();
|
||||||
|
|
|
@ -750,6 +750,11 @@ impl Terminal {
|
||||||
InternalEvent::SetSelection(selection) => {
|
InternalEvent::SetSelection(selection) => {
|
||||||
term.selection = selection.as_ref().map(|(sel, _)| sel.clone());
|
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 {
|
if let Some((_, head)) = selection {
|
||||||
self.selection_head = Some(*head);
|
self.selection_head = Some(*head);
|
||||||
}
|
}
|
||||||
|
@ -766,6 +771,11 @@ impl Terminal {
|
||||||
selection.update(point, side);
|
selection.update(point, side);
|
||||||
term.selection = Some(selection);
|
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);
|
self.selection_head = Some(point);
|
||||||
cx.emit(Event::SelectionsChanged)
|
cx.emit(Event::SelectionsChanged)
|
||||||
}
|
}
|
||||||
|
@ -1192,7 +1202,12 @@ impl Terminal {
|
||||||
Some(scroll_delta)
|
Some(scroll_delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mouse_down(&mut self, e: &MouseDownEvent, origin: Point<Pixels>) {
|
pub fn mouse_down(
|
||||||
|
&mut self,
|
||||||
|
e: &MouseDownEvent,
|
||||||
|
origin: Point<Pixels>,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
let position = e.position - origin;
|
let position = e.position - origin;
|
||||||
let point = grid_point(
|
let point = grid_point(
|
||||||
position,
|
position,
|
||||||
|
@ -1229,6 +1244,11 @@ impl Terminal {
|
||||||
self.events
|
self.events
|
||||||
.push_back(InternalEvent::SetSelection(Some((sel, point))));
|
.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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -429,7 +429,7 @@ impl TerminalElement {
|
||||||
move |e, cx| {
|
move |e, cx| {
|
||||||
cx.focus(&focus);
|
cx.focus(&focus);
|
||||||
terminal.update(cx, |terminal, cx| {
|
terminal.update(cx, |terminal, cx| {
|
||||||
terminal.mouse_down(&e, origin);
|
terminal.mouse_down(&e, origin, cx);
|
||||||
cx.notify();
|
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({
|
self.interactivity.on_scroll_wheel({
|
||||||
let terminal = terminal.clone();
|
let terminal = terminal.clone();
|
||||||
move |e, cx| {
|
move |e, cx| {
|
||||||
|
@ -498,19 +509,8 @@ impl TerminalElement {
|
||||||
terminal.clone(),
|
terminal.clone(),
|
||||||
origin,
|
origin,
|
||||||
focus.clone(),
|
focus.clone(),
|
||||||
move |terminal, origin, e, _cx| {
|
move |terminal, origin, e, cx| {
|
||||||
terminal.mouse_down(&e, origin);
|
terminal.mouse_down(&e, origin, cx);
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
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);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue