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