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                vim.yank_selections_content(editor, MotionKind::Exclusive, window, cx);
 85                editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 86                    s.move_with(|_, selection| {
 87                        let (head, goal) = start_positions.remove(&selection.id).unwrap();
 88                        selection.collapse_to(head, goal);
 89                    });
 90                });
 91            });
 92        });
 93        self.exit_temporary_normal(window, cx);
 94    }
 95
 96    pub fn yank_selections_content(
 97        &mut self,
 98        editor: &mut Editor,
 99        kind: MotionKind,
100        window: &mut Window,
101        cx: &mut Context<Editor>,
102    ) {
103        self.copy_ranges(
104            editor,
105            kind,
106            true,
107            editor
108                .selections
109                .all_adjusted(&editor.display_snapshot(cx))
110                .iter()
111                .map(|s| s.range())
112                .collect(),
113            window,
114            cx,
115        )
116    }
117
118    pub fn copy_selections_content(
119        &mut self,
120        editor: &mut Editor,
121        kind: MotionKind,
122        window: &mut Window,
123        cx: &mut Context<Editor>,
124    ) {
125        self.copy_ranges(
126            editor,
127            kind,
128            false,
129            editor
130                .selections
131                .all_adjusted(&editor.display_snapshot(cx))
132                .iter()
133                .map(|s| s.range())
134                .collect(),
135            window,
136            cx,
137        )
138    }
139
140    pub(crate) fn copy_ranges(
141        &mut self,
142        editor: &mut Editor,
143        kind: MotionKind,
144        is_yank: bool,
145        selections: Vec<Range<Point>>,
146        window: &mut Window,
147        cx: &mut Context<Editor>,
148    ) {
149        let buffer = editor.buffer().read(cx).snapshot(cx);
150        self.set_mark(
151            "[".to_string(),
152            selections
153                .iter()
154                .map(|s| buffer.anchor_before(s.start))
155                .collect(),
156            editor.buffer(),
157            window,
158            cx,
159        );
160        self.set_mark(
161            "]".to_string(),
162            selections
163                .iter()
164                .map(|s| buffer.anchor_after(s.end))
165                .collect(),
166            editor.buffer(),
167            window,
168            cx,
169        );
170
171        let mut text = String::new();
172        let mut clipboard_selections = Vec::with_capacity(selections.len());
173        let mut ranges_to_highlight = Vec::new();
174
175        {
176            let mut is_first = true;
177            for selection in selections.iter() {
178                let start = selection.start;
179                let end = selection.end;
180                if is_first {
181                    is_first = false;
182                } else {
183                    text.push('\n');
184                }
185                let initial_len = text.len();
186
187                let start_anchor = buffer.anchor_after(start);
188                let end_anchor = buffer.anchor_before(end);
189                ranges_to_highlight.push(start_anchor..end_anchor);
190
191                for chunk in buffer.text_for_range(start..end) {
192                    text.push_str(chunk);
193                }
194                if kind.linewise() {
195                    text.push('\n');
196                }
197                clipboard_selections.push(ClipboardSelection {
198                    len: text.len() - initial_len,
199                    is_entire_line: false,
200                    first_line_indent: buffer.indent_size_for_line(MultiBufferRow(start.row)).len,
201                });
202            }
203        }
204
205        let selected_register = self.selected_register.take();
206        Vim::update_globals(cx, |globals, cx| {
207            globals.write_registers(
208                Register {
209                    text: text.into(),
210                    clipboard_selections: Some(clipboard_selections),
211                },
212                selected_register,
213                is_yank,
214                kind,
215                cx,
216            )
217        });
218
219        let highlight_duration = VimSettings::get_global(cx).highlight_on_yank_duration;
220        if !is_yank || self.mode == Mode::Visual || highlight_duration == 0 {
221            return;
222        }
223
224        editor.highlight_background::<HighlightOnYank>(
225            &ranges_to_highlight,
226            |colors| colors.colors().editor_document_highlight_read_background,
227            cx,
228        );
229        cx.spawn(async move |this, cx| {
230            cx.background_executor()
231                .timer(Duration::from_millis(highlight_duration))
232                .await;
233            this.update(cx, |editor, cx| {
234                editor.clear_background_highlights::<HighlightOnYank>(cx)
235            })
236            .ok();
237        })
238        .detach();
239    }
240}