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, true, 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}