yank.rs

  1use std::{ops::Range, 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_ranges(
 77            editor,
 78            linewise,
 79            true,
 80            editor
 81                .selections
 82                .all_adjusted(cx)
 83                .iter()
 84                .map(|s| s.range())
 85                .collect(),
 86            cx,
 87        )
 88    }
 89
 90    pub fn copy_selections_content(
 91        &mut self,
 92        editor: &mut Editor,
 93        linewise: bool,
 94        cx: &mut ViewContext<Editor>,
 95    ) {
 96        self.copy_ranges(
 97            editor,
 98            linewise,
 99            false,
100            editor
101                .selections
102                .all_adjusted(cx)
103                .iter()
104                .map(|s| s.range())
105                .collect(),
106            cx,
107        )
108    }
109
110    pub(crate) fn copy_ranges(
111        &mut self,
112        editor: &mut Editor,
113        linewise: bool,
114        is_yank: bool,
115        selections: Vec<Range<Point>>,
116        cx: &mut ViewContext<Editor>,
117    ) {
118        let buffer = editor.buffer().read(cx).snapshot(cx);
119        let mut text = String::new();
120        let mut clipboard_selections = Vec::with_capacity(selections.len());
121        let mut ranges_to_highlight = Vec::new();
122
123        self.marks.insert(
124            "[".to_string(),
125            selections
126                .iter()
127                .map(|s| buffer.anchor_before(s.start))
128                .collect(),
129        );
130        self.marks.insert(
131            "]".to_string(),
132            selections
133                .iter()
134                .map(|s| buffer.anchor_after(s.end))
135                .collect(),
136        );
137
138        {
139            let mut is_first = true;
140            for selection in selections.iter() {
141                let mut start = selection.start;
142                let end = selection.end;
143                if is_first {
144                    is_first = false;
145                } else {
146                    text.push('\n');
147                }
148                let initial_len = text.len();
149
150                // if the file does not end with \n, and our line-mode selection ends on
151                // that line, we will have expanded the start of the selection to ensure it
152                // contains a newline (so that delete works as expected). We undo that change
153                // here.
154                let is_last_line = linewise
155                    && end.row == buffer.max_buffer_row().0
156                    && buffer.max_point().column > 0
157                    && start.row < buffer.max_buffer_row().0
158                    && start == Point::new(start.row, buffer.line_len(MultiBufferRow(start.row)));
159
160                if is_last_line {
161                    start = Point::new(start.row + 1, 0);
162                }
163
164                let start_anchor = buffer.anchor_after(start);
165                let end_anchor = buffer.anchor_before(end);
166                ranges_to_highlight.push(start_anchor..end_anchor);
167
168                for chunk in buffer.text_for_range(start..end) {
169                    text.push_str(chunk);
170                }
171                if is_last_line {
172                    text.push('\n');
173                }
174                clipboard_selections.push(ClipboardSelection {
175                    len: text.len() - initial_len,
176                    is_entire_line: linewise,
177                    first_line_indent: buffer.indent_size_for_line(MultiBufferRow(start.row)).len,
178                });
179            }
180        }
181
182        let selected_register = self.selected_register.take();
183        Vim::update_globals(cx, |globals, cx| {
184            globals.write_registers(
185                Register {
186                    text: text.into(),
187                    clipboard_selections: Some(clipboard_selections),
188                },
189                selected_register,
190                is_yank,
191                linewise,
192                cx,
193            )
194        });
195
196        if !is_yank || self.mode == Mode::Visual {
197            return;
198        }
199
200        editor.highlight_background::<HighlightOnYank>(
201            &ranges_to_highlight,
202            |colors| colors.editor_document_highlight_read_background,
203            cx,
204        );
205        cx.spawn(|this, mut cx| async move {
206            cx.background_executor()
207                .timer(Duration::from_millis(200))
208                .await;
209            this.update(&mut cx, |editor, cx| {
210                editor.clear_background_highlights::<HighlightOnYank>(cx)
211            })
212            .ok();
213        })
214        .detach();
215    }
216}