yank.rs

  1use std::time::Duration;
  2
  3use crate::{
  4    motion::Motion,
  5    object::Object,
  6    state::{Mode, Register},
  7    Vim,
  8};
  9use collections::HashMap;
 10use editor::{ClipboardSelection, Editor};
 11use gpui::ViewContext;
 12use language::Point;
 13use multi_buffer::MultiBufferRow;
 14
 15struct HighlightOnYank;
 16
 17impl Vim {
 18    pub fn yank_motion(
 19        &mut self,
 20        motion: Motion,
 21        times: Option<usize>,
 22        cx: &mut ViewContext<Self>,
 23    ) {
 24        self.update_editor(cx, |vim, editor, cx| {
 25            let text_layout_details = editor.text_layout_details(cx);
 26            editor.transact(cx, |editor, cx| {
 27                editor.set_clip_at_line_ends(false, cx);
 28                let mut original_positions: HashMap<_, _> = Default::default();
 29                editor.change_selections(None, cx, |s| {
 30                    s.move_with(|map, selection| {
 31                        let original_position = (selection.head(), selection.goal);
 32                        original_positions.insert(selection.id, original_position);
 33                        motion.expand_selection(map, selection, times, true, &text_layout_details);
 34                    });
 35                });
 36                vim.yank_selections_content(editor, motion.linewise(), cx);
 37                editor.change_selections(None, cx, |s| {
 38                    s.move_with(|_, selection| {
 39                        let (head, goal) = original_positions.remove(&selection.id).unwrap();
 40                        selection.collapse_to(head, goal);
 41                    });
 42                });
 43            });
 44        });
 45    }
 46
 47    pub fn yank_object(&mut self, object: Object, around: bool, cx: &mut ViewContext<Self>) {
 48        self.update_editor(cx, |vim, editor, cx| {
 49            editor.transact(cx, |editor, cx| {
 50                editor.set_clip_at_line_ends(false, cx);
 51                let mut original_positions: HashMap<_, _> = Default::default();
 52                editor.change_selections(None, cx, |s| {
 53                    s.move_with(|map, selection| {
 54                        let original_position = (selection.head(), selection.goal);
 55                        object.expand_selection(map, selection, around);
 56                        original_positions.insert(selection.id, original_position);
 57                    });
 58                });
 59                vim.yank_selections_content(editor, false, cx);
 60                editor.change_selections(None, cx, |s| {
 61                    s.move_with(|_, selection| {
 62                        let (head, goal) = original_positions.remove(&selection.id).unwrap();
 63                        selection.collapse_to(head, goal);
 64                    });
 65                });
 66            });
 67        });
 68    }
 69
 70    pub fn yank_selections_content(
 71        &mut self,
 72        editor: &mut Editor,
 73        linewise: bool,
 74        cx: &mut ViewContext<Editor>,
 75    ) {
 76        self.copy_selections_content_internal(editor, linewise, true, cx);
 77    }
 78
 79    pub fn copy_selections_content(
 80        &mut self,
 81        editor: &mut Editor,
 82        linewise: bool,
 83        cx: &mut ViewContext<Editor>,
 84    ) {
 85        self.copy_selections_content_internal(editor, linewise, false, cx);
 86    }
 87
 88    fn copy_selections_content_internal(
 89        &mut self,
 90        editor: &mut Editor,
 91        linewise: bool,
 92        is_yank: bool,
 93        cx: &mut ViewContext<Editor>,
 94    ) {
 95        let selections = editor.selections.all_adjusted(cx);
 96        let buffer = editor.buffer().read(cx).snapshot(cx);
 97        let mut text = String::new();
 98        let mut clipboard_selections = Vec::with_capacity(selections.len());
 99        let mut ranges_to_highlight = Vec::new();
100
101        self.marks.insert(
102            "[".to_string(),
103            selections
104                .iter()
105                .map(|s| buffer.anchor_before(s.start))
106                .collect(),
107        );
108        self.marks.insert(
109            "]".to_string(),
110            selections
111                .iter()
112                .map(|s| buffer.anchor_after(s.end))
113                .collect(),
114        );
115
116        {
117            let mut is_first = true;
118            for selection in selections.iter() {
119                let mut start = selection.start;
120                let end = selection.end;
121                if is_first {
122                    is_first = false;
123                } else {
124                    text.push('\n');
125                }
126                let initial_len = text.len();
127
128                // if the file does not end with \n, and our line-mode selection ends on
129                // that line, we will have expanded the start of the selection to ensure it
130                // contains a newline (so that delete works as expected). We undo that change
131                // here.
132                let is_last_line = linewise
133                    && end.row == buffer.max_buffer_row().0
134                    && buffer.max_point().column > 0
135                    && start.row < buffer.max_buffer_row().0
136                    && start == Point::new(start.row, buffer.line_len(MultiBufferRow(start.row)));
137
138                if is_last_line {
139                    start = Point::new(start.row + 1, 0);
140                }
141
142                let start_anchor = buffer.anchor_after(start);
143                let end_anchor = buffer.anchor_before(end);
144                ranges_to_highlight.push(start_anchor..end_anchor);
145
146                for chunk in buffer.text_for_range(start..end) {
147                    text.push_str(chunk);
148                }
149                if is_last_line {
150                    text.push('\n');
151                }
152                clipboard_selections.push(ClipboardSelection {
153                    len: text.len() - initial_len,
154                    is_entire_line: linewise,
155                    first_line_indent: buffer.indent_size_for_line(MultiBufferRow(start.row)).len,
156                });
157            }
158        }
159
160        let selected_register = self.selected_register.take();
161        Vim::update_globals(cx, |globals, cx| {
162            globals.write_registers(
163                Register {
164                    text: text.into(),
165                    clipboard_selections: Some(clipboard_selections),
166                },
167                selected_register,
168                is_yank,
169                linewise,
170                cx,
171            )
172        });
173
174        if !is_yank || self.mode == Mode::Visual {
175            return;
176        }
177
178        editor.highlight_background::<HighlightOnYank>(
179            &ranges_to_highlight,
180            |colors| colors.editor_document_highlight_read_background,
181            cx,
182        );
183        cx.spawn(|this, mut cx| async move {
184            cx.background_executor()
185                .timer(Duration::from_millis(200))
186                .await;
187            this.update(&mut cx, |editor, cx| {
188                editor.clear_background_highlights::<HighlightOnYank>(cx)
189            })
190            .ok();
191        })
192        .detach();
193    }
194}