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