yank.rs

  1use std::{ops::Range, time::Duration};
  2
  3use crate::{
  4    Vim, VimSettings,
  5    motion::{Motion, MotionKind},
  6    object::Object,
  7    state::{Mode, Register},
  8};
  9use collections::HashMap;
 10use editor::{ClipboardSelection, Editor, HighlightKey, SelectionEffects};
 11use gpui::Context;
 12use gpui::Window;
 13use language::Point;
 14use settings::Settings;
 15
 16impl Vim {
 17    pub fn yank_motion(
 18        &mut self,
 19        motion: Motion,
 20        times: Option<usize>,
 21        forced_motion: bool,
 22        window: &mut Window,
 23        cx: &mut Context<Self>,
 24    ) {
 25        self.update_editor(cx, |vim, editor, cx| {
 26            let text_layout_details = editor.text_layout_details(window, cx);
 27            editor.transact(window, cx, |editor, window, cx| {
 28                editor.set_clip_at_line_ends(false, cx);
 29                let mut original_positions: HashMap<_, _> = Default::default();
 30                let mut kind = None;
 31                editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 32                    s.move_with(&mut |map, selection| {
 33                        let original_position = (selection.head(), selection.goal);
 34                        kind = motion.expand_selection(
 35                            map,
 36                            selection,
 37                            times,
 38                            &text_layout_details,
 39                            forced_motion,
 40                        );
 41                        if kind == Some(MotionKind::Exclusive) {
 42                            original_positions
 43                                .insert(selection.id, (selection.start, selection.goal));
 44                        } else {
 45                            original_positions.insert(selection.id, original_position);
 46                        }
 47                    })
 48                });
 49                let Some(kind) = kind else { return };
 50                vim.yank_selections_content(editor, kind, window, cx);
 51                editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 52                    s.move_with(&mut |_, selection| {
 53                        let (head, goal) = original_positions.remove(&selection.id).unwrap();
 54                        selection.collapse_to(head, goal);
 55                    });
 56                });
 57            });
 58        });
 59        self.exit_temporary_normal(window, cx);
 60    }
 61
 62    pub fn yank_object(
 63        &mut self,
 64        object: Object,
 65        around: bool,
 66        times: Option<usize>,
 67        window: &mut Window,
 68        cx: &mut Context<Self>,
 69    ) {
 70        self.update_editor(cx, |vim, editor, cx| {
 71            editor.transact(window, cx, |editor, window, cx| {
 72                editor.set_clip_at_line_ends(false, cx);
 73                let mut start_positions: HashMap<_, _> = Default::default();
 74                editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 75                    s.move_with(&mut |map, selection| {
 76                        object.expand_selection(map, selection, around, times);
 77                        let start_position = (selection.start, selection.goal);
 78                        start_positions.insert(selection.id, start_position);
 79                    });
 80                });
 81                let kind = match object.target_visual_mode(vim.mode, around) {
 82                    Mode::VisualLine => MotionKind::Linewise,
 83                    _ => MotionKind::Exclusive,
 84                };
 85                vim.yank_selections_content(editor, kind, window, cx);
 86                editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 87                    s.move_with(&mut |_, selection| {
 88                        let (head, goal) = start_positions.remove(&selection.id).unwrap();
 89                        selection.collapse_to(head, goal);
 90                    });
 91                });
 92            });
 93        });
 94        self.exit_temporary_normal(window, cx);
 95    }
 96
 97    pub fn yank_selections_content(
 98        &mut self,
 99        editor: &mut Editor,
100        kind: MotionKind,
101        window: &mut Window,
102        cx: &mut Context<Editor>,
103    ) {
104        self.copy_ranges(
105            editor,
106            kind,
107            true,
108            editor
109                .selections
110                .all_adjusted(&editor.display_snapshot(cx))
111                .iter()
112                .map(|s| s.range())
113                .collect(),
114            window,
115            cx,
116        )
117    }
118
119    pub fn copy_selections_content(
120        &mut self,
121        editor: &mut Editor,
122        kind: MotionKind,
123        window: &mut Window,
124        cx: &mut Context<Editor>,
125    ) {
126        self.copy_ranges(
127            editor,
128            kind,
129            false,
130            editor
131                .selections
132                .all_adjusted(&editor.display_snapshot(cx))
133                .iter()
134                .map(|s| s.range())
135                .collect(),
136            window,
137            cx,
138        )
139    }
140
141    pub(crate) fn copy_ranges(
142        &mut self,
143        editor: &mut Editor,
144        kind: MotionKind,
145        is_yank: bool,
146        selections: Vec<Range<Point>>,
147        window: &mut Window,
148        cx: &mut Context<Editor>,
149    ) {
150        let buffer = editor.buffer().read(cx).snapshot(cx);
151        self.set_mark(
152            "[".to_string(),
153            selections
154                .iter()
155                .map(|s| buffer.anchor_before(s.start))
156                .collect(),
157            editor.buffer(),
158            window,
159            cx,
160        );
161        self.set_mark(
162            "]".to_string(),
163            selections
164                .iter()
165                .map(|s| buffer.anchor_after(s.end))
166                .collect(),
167            editor.buffer(),
168            window,
169            cx,
170        );
171
172        let mut text = String::new();
173        let mut clipboard_selections = Vec::with_capacity(selections.len());
174        let mut ranges_to_highlight = Vec::new();
175
176        {
177            let mut is_first = true;
178            for selection in selections.iter() {
179                let start = selection.start;
180                let end = selection.end;
181                if is_first {
182                    is_first = false;
183                } else {
184                    text.push('\n');
185                }
186                let initial_len = text.len();
187
188                let start_anchor = buffer.anchor_after(start);
189                let end_anchor = buffer.anchor_before(end);
190                ranges_to_highlight.push(start_anchor..end_anchor);
191
192                for chunk in buffer.text_for_range(start..end) {
193                    text.push_str(chunk);
194                }
195                if kind.linewise() {
196                    text.push('\n');
197                }
198                clipboard_selections.push(ClipboardSelection::for_buffer(
199                    text.len() - initial_len,
200                    false,
201                    start..end,
202                    &buffer,
203                    editor.project(),
204                    cx,
205                ));
206            }
207        }
208
209        let selected_register = self.selected_register.take();
210        Vim::update_globals(cx, |globals, cx| {
211            globals.write_registers(
212                Register {
213                    text: text.into(),
214                    clipboard_selections: Some(clipboard_selections),
215                },
216                selected_register,
217                is_yank,
218                kind,
219                cx,
220            )
221        });
222
223        let highlight_duration = VimSettings::get_global(cx).highlight_on_yank_duration;
224        if !is_yank || self.mode == Mode::Visual || highlight_duration == 0 {
225            return;
226        }
227
228        editor.highlight_background(
229            HighlightKey::HighlightOnYank,
230            &ranges_to_highlight,
231            |_, colors| colors.colors().vim_yank_background,
232            cx,
233        );
234        cx.spawn(async move |this, cx| {
235            cx.background_executor()
236                .timer(Duration::from_millis(highlight_duration))
237                .await;
238            this.update(cx, |editor, cx| {
239                editor.clear_background_highlights(HighlightKey::HighlightOnYank, cx)
240            })
241            .ok();
242        })
243        .detach();
244    }
245}