Start on copy-paste

Antonio Scandurra created

Change summary

gpui/src/app.rs                   |  4 ++
gpui/src/platform/mac/platform.rs | 14 +++++++
gpui/src/platform/mod.rs          |  1 
gpui/src/platform/test.rs         |  4 ++
zed/src/editor/buffer/mod.rs      | 15 +++----
zed/src/editor/buffer_view.rs     | 58 ++++++++++++++++++++++++++++++++
6 files changed, 87 insertions(+), 9 deletions(-)

Detailed changes

gpui/src/app.rs 🔗

@@ -1215,6 +1215,10 @@ impl MutableAppContext {
     pub fn copy(&self, text: &str) {
         self.platform.copy(text);
     }
+
+    pub fn paste(&self) -> Option<String> {
+        self.platform.paste()
+    }
 }
 
 impl ReadModel for MutableAppContext {

gpui/src/platform/mac/platform.rs 🔗

@@ -26,6 +26,7 @@ use std::{
     path::PathBuf,
     ptr,
     rc::Rc,
+    slice,
     sync::Arc,
 };
 
@@ -299,6 +300,19 @@ impl platform::Platform for MacPlatform {
         }
     }
 
+    fn paste(&self) -> Option<String> {
+        unsafe {
+            let pasteboard = NSPasteboard::generalPasteboard(nil);
+            let data = pasteboard.dataForType(NSPasteboardTypeString);
+            if data == nil {
+                None
+            } else {
+                let bytes = slice::from_raw_parts(data.bytes() as *mut u8, data.length() as usize);
+                Some(String::from_utf8_unchecked(bytes.to_vec()))
+            }
+        }
+    }
+
     fn set_menus(&self, menus: Vec<Menu>) {
         unsafe {
             let app: id = msg_send![APP_CLASS, sharedApplication];

gpui/src/platform/mod.rs 🔗

@@ -43,6 +43,7 @@ pub trait Platform {
     fn prompt_for_paths(&self, options: PathPromptOptions) -> Option<Vec<PathBuf>>;
     fn quit(&self);
     fn copy(&self, text: &str);
+    fn paste(&self) -> Option<String>;
     fn set_menus(&self, menus: Vec<Menu>);
 }
 

gpui/src/platform/test.rs 🔗

@@ -73,6 +73,10 @@ impl super::Platform for Platform {
     }
 
     fn copy(&self, _: &str) {}
+
+    fn paste(&self) -> Option<String> {
+        None
+    }
 }
 
 impl Window {

zed/src/editor/buffer/mod.rs 🔗

@@ -563,10 +563,13 @@ impl Buffer {
         self.chars().collect()
     }
 
-    pub fn text_for_range<T: ToOffset>(&self, range: Range<T>) -> Result<String> {
+    pub fn text_for_range<'a, T: ToOffset>(
+        &'a self,
+        range: Range<T>,
+    ) -> Result<impl 'a + Iterator<Item = char>> {
         let start = range.start.to_offset(self)?;
         let end = range.end.to_offset(self)?;
-        Ok(self.chars_at(start)?.take(end - start).collect())
+        Ok(self.chars_at(start)?.take(end - start))
     }
 
     pub fn chars(&self) -> CharIter {
@@ -2470,13 +2473,9 @@ mod tests {
                     let old_len = old_range.end - old_range.start;
                     let new_len = new_range.end - new_range.start;
                     let old_start = (old_range.start as isize + delta) as usize;
-
+                    let new_text: String = buffer.text_for_range(new_range).unwrap().collect();
                     old_buffer
-                        .edit(
-                            Some(old_start..old_start + old_len),
-                            buffer.text_for_range(new_range).unwrap(),
-                            None,
-                        )
+                        .edit(Some(old_start..old_start + old_len), new_text, None)
                         .unwrap();
 
                     delta += new_len as isize - old_len as isize;

zed/src/editor/buffer_view.rs 🔗

@@ -1,6 +1,6 @@
 use super::{
     buffer, movement, Anchor, Bias, Buffer, BufferElement, DisplayMap, DisplayPoint, Point,
-    Selection, SelectionSetId, ToOffset,
+    Selection, SelectionSetId, ToOffset, ToPoint,
 };
 use crate::{settings::Settings, watch, workspace};
 use anyhow::Result;
@@ -28,6 +28,9 @@ pub fn init(app: &mut MutableAppContext) {
     app.add_bindings(vec![
         Binding::new("backspace", "buffer:backspace", Some("BufferView")),
         Binding::new("enter", "buffer:newline", Some("BufferView")),
+        Binding::new("cmd-x", "buffer:cut", Some("BufferView")),
+        Binding::new("cmd-c", "buffer:copy", Some("BufferView")),
+        Binding::new("cmd-v", "buffer:paste", Some("BufferView")),
         Binding::new("cmd-z", "buffer:undo", Some("BufferView")),
         Binding::new("cmd-shift-Z", "buffer:redo", Some("BufferView")),
         Binding::new("up", "buffer:move_up", Some("BufferView")),
@@ -54,6 +57,9 @@ pub fn init(app: &mut MutableAppContext) {
     app.add_action("buffer:insert", BufferView::insert);
     app.add_action("buffer:newline", BufferView::newline);
     app.add_action("buffer:backspace", BufferView::backspace);
+    app.add_action("buffer:cut", BufferView::cut);
+    app.add_action("buffer:copy", BufferView::copy);
+    app.add_action("buffer:paste", BufferView::paste);
     app.add_action("buffer:undo", BufferView::undo);
     app.add_action("buffer:redo", BufferView::redo);
     app.add_action("buffer:move_up", BufferView::move_up);
@@ -449,6 +455,56 @@ impl BufferView {
         self.end_transaction(ctx);
     }
 
+    pub fn cut(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
+        self.start_transaction(ctx);
+        let mut text = String::new();
+        let mut selections = self.selections(ctx.app()).to_vec();
+        {
+            let buffer = self.buffer.read(ctx);
+            let max_point = buffer.max_point();
+            for selection in &mut selections {
+                let mut start = selection.start.to_point(buffer).expect("invalid start");
+                let mut end = selection.end.to_point(buffer).expect("invalid end");
+                if start == end {
+                    start = Point::new(start.row, 0);
+                    end = cmp::min(max_point, Point::new(start.row + 1, 0));
+                    selection.start = buffer.anchor_before(start).unwrap();
+                    selection.end = buffer.anchor_after(end).unwrap();
+                }
+                text.extend(buffer.text_for_range(start..end).unwrap());
+            }
+        }
+        self.update_selections(selections, ctx);
+        self.changed_selections(ctx);
+        self.insert(&String::new(), ctx);
+        self.end_transaction(ctx);
+
+        ctx.app_mut().copy(&text);
+    }
+
+    pub fn copy(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
+        let buffer = self.buffer.read(ctx);
+        let max_point = buffer.max_point();
+        let mut text = String::new();
+        for selection in self.selections(ctx.app()) {
+            let mut start = selection.start.to_point(buffer).expect("invalid start");
+            let mut end = selection.end.to_point(buffer).expect("invalid end");
+            if start == end {
+                start = Point::new(start.row, 0);
+                end = cmp::min(max_point, Point::new(start.row + 1, 0));
+            }
+            text.extend(buffer.text_for_range(start..end).unwrap());
+        }
+
+        ctx.app_mut().copy(&text);
+    }
+
+    pub fn paste(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
+        if let Some(text) = ctx.app_mut().paste() {
+            self.insert(&text, ctx);
+        }
+    }
+
     pub fn undo(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
         self.buffer
             .update(ctx, |buffer, ctx| buffer.undo(Some(ctx)));