yank.rs

  1use std::time::Duration;
  2
  3use crate::{
  4    motion::Motion,
  5    object::Object,
  6    state::{Mode, Register},
  7    Vim,
  8};
  9use collections::HashMap;
 10use editor::{ClipboardSelection, Editor};
 11use gpui::WindowContext;
 12use language::Point;
 13use multi_buffer::MultiBufferRow;
 14use ui::ViewContext;
 15
 16pub fn yank_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
 17    vim.update_active_editor(cx, |vim, editor, cx| {
 18        let text_layout_details = editor.text_layout_details(cx);
 19        editor.transact(cx, |editor, cx| {
 20            editor.set_clip_at_line_ends(false, cx);
 21            let mut original_positions: HashMap<_, _> = Default::default();
 22            editor.change_selections(None, cx, |s| {
 23                s.move_with(|map, selection| {
 24                    let original_position = (selection.head(), selection.goal);
 25                    original_positions.insert(selection.id, original_position);
 26                    motion.expand_selection(map, selection, times, true, &text_layout_details);
 27                });
 28            });
 29            yank_selections_content(vim, editor, motion.linewise(), cx);
 30            editor.change_selections(None, cx, |s| {
 31                s.move_with(|_, selection| {
 32                    let (head, goal) = original_positions.remove(&selection.id).unwrap();
 33                    selection.collapse_to(head, goal);
 34                });
 35            });
 36        });
 37    });
 38}
 39
 40pub fn yank_object(vim: &mut Vim, object: Object, around: bool, cx: &mut WindowContext) {
 41    vim.update_active_editor(cx, |vim, editor, cx| {
 42        editor.transact(cx, |editor, cx| {
 43            editor.set_clip_at_line_ends(false, cx);
 44            let mut original_positions: HashMap<_, _> = Default::default();
 45            editor.change_selections(None, cx, |s| {
 46                s.move_with(|map, selection| {
 47                    let original_position = (selection.head(), selection.goal);
 48                    object.expand_selection(map, selection, around);
 49                    original_positions.insert(selection.id, original_position);
 50                });
 51            });
 52            yank_selections_content(vim, editor, false, cx);
 53            editor.change_selections(None, cx, |s| {
 54                s.move_with(|_, selection| {
 55                    let (head, goal) = original_positions.remove(&selection.id).unwrap();
 56                    selection.collapse_to(head, goal);
 57                });
 58            });
 59        });
 60    });
 61}
 62
 63pub fn yank_selections_content(
 64    vim: &mut Vim,
 65    editor: &mut Editor,
 66    linewise: bool,
 67    cx: &mut ViewContext<Editor>,
 68) {
 69    copy_selections_content_internal(vim, editor, linewise, true, cx);
 70}
 71
 72pub fn copy_selections_content(
 73    vim: &mut Vim,
 74    editor: &mut Editor,
 75    linewise: bool,
 76    cx: &mut ViewContext<Editor>,
 77) {
 78    copy_selections_content_internal(vim, editor, linewise, false, cx);
 79}
 80
 81struct HighlightOnYank;
 82
 83fn copy_selections_content_internal(
 84    vim: &mut Vim,
 85    editor: &mut Editor,
 86    linewise: bool,
 87    is_yank: bool,
 88    cx: &mut ViewContext<Editor>,
 89) {
 90    let selections = editor.selections.all_adjusted(cx);
 91    let buffer = editor.buffer().read(cx).snapshot(cx);
 92    let mut text = String::new();
 93    let mut clipboard_selections = Vec::with_capacity(selections.len());
 94    let mut ranges_to_highlight = Vec::new();
 95
 96    vim.update_state(|state| {
 97        state.marks.insert(
 98            "[".to_string(),
 99            selections
100                .iter()
101                .map(|s| buffer.anchor_before(s.start))
102                .collect(),
103        );
104        state.marks.insert(
105            "]".to_string(),
106            selections
107                .iter()
108                .map(|s| buffer.anchor_after(s.end))
109                .collect(),
110        )
111    });
112
113    {
114        let mut is_first = true;
115        for selection in selections.iter() {
116            let mut start = selection.start;
117            let end = selection.end;
118            if is_first {
119                is_first = false;
120            } else {
121                text.push_str("\n");
122            }
123            let initial_len = text.len();
124
125            // if the file does not end with \n, and our line-mode selection ends on
126            // that line, we will have expanded the start of the selection to ensure it
127            // contains a newline (so that delete works as expected). We undo that change
128            // here.
129            let is_last_line = linewise
130                && end.row == buffer.max_buffer_row().0
131                && buffer.max_point().column > 0
132                && start.row < buffer.max_buffer_row().0
133                && start == Point::new(start.row, buffer.line_len(MultiBufferRow(start.row)));
134
135            if is_last_line {
136                start = Point::new(start.row + 1, 0);
137            }
138
139            let start_anchor = buffer.anchor_after(start);
140            let end_anchor = buffer.anchor_before(end);
141            ranges_to_highlight.push(start_anchor..end_anchor);
142
143            for chunk in buffer.text_for_range(start..end) {
144                text.push_str(chunk);
145            }
146            if is_last_line {
147                text.push_str("\n");
148            }
149            clipboard_selections.push(ClipboardSelection {
150                len: text.len() - initial_len,
151                is_entire_line: linewise,
152                first_line_indent: buffer.indent_size_for_line(MultiBufferRow(start.row)).len,
153            });
154        }
155    }
156
157    let selected_register = vim.update_state(|state| state.selected_register.take());
158    vim.write_registers(
159        Register {
160            text: text.into(),
161            clipboard_selections: Some(clipboard_selections),
162        },
163        selected_register,
164        is_yank,
165        linewise,
166        cx,
167    );
168
169    if !is_yank || vim.state().mode == Mode::Visual {
170        return;
171    }
172
173    editor.highlight_background::<HighlightOnYank>(
174        &ranges_to_highlight,
175        |colors| colors.editor_document_highlight_read_background,
176        cx,
177    );
178    cx.spawn(|this, mut cx| async move {
179        cx.background_executor()
180            .timer(Duration::from_millis(200))
181            .await;
182        this.update(&mut cx, |editor, cx| {
183            editor.clear_background_highlights::<HighlightOnYank>(cx)
184        })
185        .ok();
186    })
187    .detach();
188}