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