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