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