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