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