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