yank.rs

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