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, times);
80 let start_position = (selection.start, selection.goal);
81 start_positions.insert(selection.id, start_position);
82 });
83 });
84 let kind = match object.target_visual_mode(vim.mode, around) {
85 Mode::VisualLine => MotionKind::Linewise,
86 _ => MotionKind::Exclusive,
87 };
88 vim.yank_selections_content(editor, kind, window, cx);
89 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
90 s.move_with(|_, selection| {
91 let (head, goal) = start_positions.remove(&selection.id).unwrap();
92 selection.collapse_to(head, goal);
93 });
94 });
95 });
96 });
97 self.exit_temporary_normal(window, cx);
98 }
99
100 pub fn yank_selections_content(
101 &mut self,
102 editor: &mut Editor,
103 kind: MotionKind,
104 window: &mut Window,
105 cx: &mut Context<Editor>,
106 ) {
107 self.copy_ranges(
108 editor,
109 kind,
110 true,
111 editor
112 .selections
113 .all_adjusted(&editor.display_snapshot(cx))
114 .iter()
115 .map(|s| s.range())
116 .collect(),
117 window,
118 cx,
119 )
120 }
121
122 pub fn copy_selections_content(
123 &mut self,
124 editor: &mut Editor,
125 kind: MotionKind,
126 window: &mut Window,
127 cx: &mut Context<Editor>,
128 ) {
129 self.copy_ranges(
130 editor,
131 kind,
132 false,
133 editor
134 .selections
135 .all_adjusted(&editor.display_snapshot(cx))
136 .iter()
137 .map(|s| s.range())
138 .collect(),
139 window,
140 cx,
141 )
142 }
143
144 pub(crate) fn copy_ranges(
145 &mut self,
146 editor: &mut Editor,
147 kind: MotionKind,
148 is_yank: bool,
149 selections: Vec<Range<Point>>,
150 window: &mut Window,
151 cx: &mut Context<Editor>,
152 ) {
153 let buffer = editor.buffer().read(cx).snapshot(cx);
154 self.set_mark(
155 "[".to_string(),
156 selections
157 .iter()
158 .map(|s| buffer.anchor_before(s.start))
159 .collect(),
160 editor.buffer(),
161 window,
162 cx,
163 );
164 self.set_mark(
165 "]".to_string(),
166 selections
167 .iter()
168 .map(|s| buffer.anchor_after(s.end))
169 .collect(),
170 editor.buffer(),
171 window,
172 cx,
173 );
174
175 let mut text = String::new();
176 let mut clipboard_selections = Vec::with_capacity(selections.len());
177 let mut ranges_to_highlight = Vec::new();
178
179 {
180 let mut is_first = true;
181 for selection in selections.iter() {
182 let start = selection.start;
183 let end = selection.end;
184 if is_first {
185 is_first = false;
186 } else {
187 text.push('\n');
188 }
189 let initial_len = text.len();
190
191 let start_anchor = buffer.anchor_after(start);
192 let end_anchor = buffer.anchor_before(end);
193 ranges_to_highlight.push(start_anchor..end_anchor);
194
195 for chunk in buffer.text_for_range(start..end) {
196 text.push_str(chunk);
197 }
198 if kind.linewise() {
199 text.push('\n');
200 }
201 clipboard_selections.push(ClipboardSelection {
202 len: text.len() - initial_len,
203 is_entire_line: false,
204 first_line_indent: buffer.indent_size_for_line(MultiBufferRow(start.row)).len,
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::<HighlightOnYank>(
229 &ranges_to_highlight,
230 |_, colors| colors.colors().editor_document_highlight_read_background,
231 cx,
232 );
233 cx.spawn(async move |this, cx| {
234 cx.background_executor()
235 .timer(Duration::from_millis(highlight_duration))
236 .await;
237 this.update(cx, |editor, cx| {
238 editor.clear_background_highlights::<HighlightOnYank>(cx)
239 })
240 .ok();
241 })
242 .detach();
243 }
244}