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}