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