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