Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion gpui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ pathfinder_geometry = "0.5"
rand = "0.8.3"
replace_with = "0.1.7"
resvg = "0.14"
serde = "1.0.125"
seahash = "4.1"
serde = { version = "1.0.125", features = ["derive"] }
serde_json = "1.0.64"
smallvec = "1.6.1"
smol = "1.2"
Expand Down
10 changes: 7 additions & 3 deletions gpui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
platform::{self, WindowOptions},
presenter::Presenter,
util::post_inc,
AssetCache, AssetSource, FontCache, TextLayoutCache,
AssetCache, AssetSource, ClipboardItem, FontCache, TextLayoutCache,
};
use anyhow::{anyhow, Result};
use async_std::sync::Condvar;
Expand Down Expand Up @@ -1212,8 +1212,12 @@ impl MutableAppContext {
}
}

pub fn copy(&self, text: &str) {
self.platform.copy(text);
pub fn write_to_clipboard(&self, item: ClipboardItem) {
self.platform.write_to_clipboard(item);
}

pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
self.platform.read_from_clipboard()
}
}

Expand Down
42 changes: 42 additions & 0 deletions gpui/src/clipboard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use seahash::SeaHasher;
use serde::{Deserialize, Serialize};
use std::hash::{Hash, Hasher};

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ClipboardItem {
pub(crate) text: String,
pub(crate) metadata: Option<String>,
}

impl ClipboardItem {
pub fn new(text: String) -> Self {
Self {
text,
metadata: None,
}
}

pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
self.metadata = Some(serde_json::to_string(&metadata).unwrap());
self
}

pub fn text(&self) -> &String {
&self.text
}

pub fn metadata<T>(&self) -> Option<T>
where
T: for<'a> Deserialize<'a>,
{
self.metadata
.as_ref()
.and_then(|m| serde_json::from_str(m).ok())
}

pub(crate) fn text_hash(text: &str) -> u64 {
let mut hasher = SeaHasher::new();
text.hash(&mut hasher);
hasher.finish()
}
}
2 changes: 2 additions & 0 deletions gpui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ pub use assets::*;
pub mod elements;
pub mod font_cache;
pub use font_cache::FontCache;
mod clipboard;
pub use clipboard::ClipboardItem;
pub mod fonts;
pub mod geometry;
mod presenter;
Expand Down
135 changes: 127 additions & 8 deletions gpui/src/platform/mac/platform.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::{BoolExt as _, Dispatcher, FontSystem, Window};
use crate::{executor, keymap::Keystroke, platform, Event, Menu, MenuItem};
use crate::{executor, keymap::Keystroke, platform, ClipboardItem, Event, Menu, MenuItem};
use cocoa::{
appkit::{
NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
Expand All @@ -21,11 +21,13 @@ use ptr::null_mut;
use std::{
any::Any,
cell::RefCell,
convert::TryInto,
ffi::{c_void, CStr},
os::raw::c_char,
path::PathBuf,
ptr,
rc::Rc,
slice, str,
sync::Arc,
};

Expand Down Expand Up @@ -77,6 +79,9 @@ pub struct MacPlatform {
fonts: Arc<FontSystem>,
callbacks: RefCell<Callbacks>,
menu_item_actions: RefCell<Vec<(String, Option<Box<dyn Any>>)>>,
pasteboard: id,
text_hash_pasteboard_type: id,
metadata_pasteboard_type: id,
}

#[derive(Default)]
Expand All @@ -96,6 +101,9 @@ impl MacPlatform {
fonts: Arc::new(FontSystem::new()),
callbacks: Default::default(),
menu_item_actions: Default::default(),
pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },
text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") },
metadata_pasteboard_type: unsafe { ns_string("zed-metadata") },
}
}

Expand Down Expand Up @@ -176,6 +184,18 @@ impl MacPlatform {

menu_bar
}

unsafe fn read_from_pasteboard(&self, kind: id) -> Option<&[u8]> {
let data = self.pasteboard.dataForType(kind);
if data == nil {
None
} else {
Some(slice::from_raw_parts(
data.bytes() as *mut u8,
data.length() as usize,
))
}
}
}

impl platform::Platform for MacPlatform {
Expand Down Expand Up @@ -286,16 +306,72 @@ impl platform::Platform for MacPlatform {
}
}

fn copy(&self, text: &str) {
fn write_to_clipboard(&self, item: ClipboardItem) {
unsafe {
let data = NSData::dataWithBytes_length_(
self.pasteboard.clearContents();

let text_bytes = NSData::dataWithBytes_length_(
nil,
text.as_ptr() as *const c_void,
text.len() as u64,
item.text.as_ptr() as *const c_void,
item.text.len() as u64,
);
let pasteboard = NSPasteboard::generalPasteboard(nil);
pasteboard.clearContents();
pasteboard.setData_forType(data, NSPasteboardTypeString);
self.pasteboard
.setData_forType(text_bytes, NSPasteboardTypeString);

if let Some(metadata) = item.metadata.as_ref() {
let hash_bytes = ClipboardItem::text_hash(&item.text).to_be_bytes();
let hash_bytes = NSData::dataWithBytes_length_(
nil,
hash_bytes.as_ptr() as *const c_void,
hash_bytes.len() as u64,
);
self.pasteboard
.setData_forType(hash_bytes, self.text_hash_pasteboard_type);

let metadata_bytes = NSData::dataWithBytes_length_(
nil,
metadata.as_ptr() as *const c_void,
metadata.len() as u64,
);
self.pasteboard
.setData_forType(metadata_bytes, self.metadata_pasteboard_type);
}
}
}

fn read_from_clipboard(&self) -> Option<ClipboardItem> {
unsafe {
if let Some(text_bytes) = self.read_from_pasteboard(NSPasteboardTypeString) {
let text = String::from_utf8_lossy(&text_bytes).to_string();
let hash_bytes = self
.read_from_pasteboard(self.text_hash_pasteboard_type)
.and_then(|bytes| bytes.try_into().ok())
.map(u64::from_be_bytes);
let metadata_bytes = self
.read_from_pasteboard(self.metadata_pasteboard_type)
.and_then(|bytes| String::from_utf8(bytes.to_vec()).ok());

if let Some((hash, metadata)) = hash_bytes.zip(metadata_bytes) {
if hash == ClipboardItem::text_hash(&text) {
Some(ClipboardItem {
text,
metadata: Some(metadata),
})
} else {
Some(ClipboardItem {
text,
metadata: None,
})
}
} else {
Some(ClipboardItem {
text,
metadata: None,
})
}
} else {
None
}
}
}

Expand Down Expand Up @@ -392,3 +468,46 @@ extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) {
unsafe fn ns_string(string: &str) -> id {
NSString::alloc(nil).init_str(string).autorelease()
}

#[cfg(test)]
mod tests {
use crate::platform::Platform;

use super::*;

#[test]
fn test_clipboard() {
let platform = build_platform();
assert_eq!(platform.read_from_clipboard(), None);

let item = ClipboardItem::new("1".to_string());
platform.write_to_clipboard(item.clone());
assert_eq!(platform.read_from_clipboard(), Some(item));

let item = ClipboardItem::new("2".to_string()).with_metadata(vec![3, 4]);
platform.write_to_clipboard(item.clone());
assert_eq!(platform.read_from_clipboard(), Some(item));

let text_from_other_app = "text from other app";
unsafe {
let bytes = NSData::dataWithBytes_length_(
nil,
text_from_other_app.as_ptr() as *const c_void,
text_from_other_app.len() as u64,
);
platform
.pasteboard
.setData_forType(bytes, NSPasteboardTypeString);
}
assert_eq!(
platform.read_from_clipboard(),
Some(ClipboardItem::new(text_from_other_app.to_string()))
);
}

fn build_platform() -> MacPlatform {
let mut platform = MacPlatform::new();
platform.pasteboard = unsafe { NSPasteboard::pasteboardWithUniqueName(nil) };
platform
}
}
5 changes: 3 additions & 2 deletions gpui/src/platform/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::{
vector::Vector2F,
},
text_layout::Line,
Menu, Scene,
ClipboardItem, Menu, Scene,
};
use async_task::Runnable;
pub use event::Event;
Expand All @@ -42,7 +42,8 @@ pub trait Platform {
fn key_window_id(&self) -> Option<usize>;
fn prompt_for_paths(&self, options: PathPromptOptions) -> Option<Vec<PathBuf>>;
fn quit(&self);
fn copy(&self, text: &str);
fn write_to_clipboard(&self, item: ClipboardItem);
fn read_from_clipboard(&self) -> Option<ClipboardItem>;
fn set_menus(&self, menus: Vec<Menu>);
}

Expand Down
14 changes: 11 additions & 3 deletions gpui/src/platform/test.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use crate::ClipboardItem;
use pathfinder_geometry::vector::Vector2F;
use std::sync::Arc;
use std::{any::Any, rc::Rc};
use std::{any::Any, cell::RefCell, rc::Rc, sync::Arc};

struct Platform {
dispatcher: Arc<dyn super::Dispatcher>,
fonts: Arc<dyn super::FontSystem>,
current_clipboard_item: RefCell<Option<ClipboardItem>>,
}

struct Dispatcher;
Expand All @@ -22,6 +23,7 @@ impl Platform {
Self {
dispatcher: Arc::new(Dispatcher),
fonts: Arc::new(super::current::FontSystem::new()),
current_clipboard_item: RefCell::new(None),
}
}
}
Expand Down Expand Up @@ -72,7 +74,13 @@ impl super::Platform for Platform {
None
}

fn copy(&self, _: &str) {}
fn write_to_clipboard(&self, item: ClipboardItem) {
*self.current_clipboard_item.borrow_mut() = Some(item);
}

fn read_from_clipboard(&self) -> Option<ClipboardItem> {
self.current_clipboard_item.borrow().clone()
}
}

impl Window {
Expand Down
1 change: 1 addition & 0 deletions zed/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ rand = "0.8.3"
rust-embed = "5.9.0"
seahash = "4.1"
simplelog = "0.9"
serde = { version = "1", features = ["derive"] }
smallvec = "1.6.1"
smol = "1.2.5"

Expand Down
Loading