1use std::time::Duration;
2
3use editor::{ClipboardSelection, Editor};
4use gpui::ViewContext;
5use language::{CharKind, Point};
6use multi_buffer::MultiBufferRow;
7
8use crate::{state::Mode, Vim};
9
10pub const SYSTEM_CLIPBOARD: char = '\0';
11
12pub struct HighlightOnYank;
13
14pub fn yank_selections_content(
15 vim: &mut Vim,
16 editor: &mut Editor,
17 linewise: bool,
18 cx: &mut ViewContext<Editor>,
19) {
20 copy_selections_content_internal(vim, editor, linewise, true, cx);
21}
22
23pub fn copy_selections_content(
24 vim: &mut Vim,
25 editor: &mut Editor,
26 linewise: bool,
27 cx: &mut ViewContext<Editor>,
28) {
29 copy_selections_content_internal(vim, editor, linewise, false, cx);
30}
31
32fn copy_selections_content_internal(
33 vim: &mut Vim,
34 editor: &mut Editor,
35 linewise: bool,
36 is_yank: bool,
37 cx: &mut ViewContext<Editor>,
38) {
39 let selections = editor.selections.all_adjusted(cx);
40 let buffer = editor.buffer().read(cx).snapshot(cx);
41 let mut text = String::new();
42 let mut clipboard_selections = Vec::with_capacity(selections.len());
43 let mut ranges_to_highlight = Vec::new();
44
45 vim.update_state(|state| {
46 state.marks.insert(
47 "[".to_string(),
48 selections
49 .iter()
50 .map(|s| buffer.anchor_before(s.start))
51 .collect(),
52 );
53 state.marks.insert(
54 "]".to_string(),
55 selections
56 .iter()
57 .map(|s| buffer.anchor_after(s.end))
58 .collect(),
59 )
60 });
61
62 {
63 let mut is_first = true;
64 for selection in selections.iter() {
65 let mut start = selection.start;
66 let end = selection.end;
67 if is_first {
68 is_first = false;
69 } else {
70 text.push_str("\n");
71 }
72 let initial_len = text.len();
73
74 // if the file does not end with \n, and our line-mode selection ends on
75 // that line, we will have expanded the start of the selection to ensure it
76 // contains a newline (so that delete works as expected). We undo that change
77 // here.
78 let is_last_line = linewise
79 && end.row == buffer.max_buffer_row().0
80 && buffer.max_point().column > 0
81 && start.row < buffer.max_buffer_row().0
82 && start == Point::new(start.row, buffer.line_len(MultiBufferRow(start.row)));
83
84 if is_last_line {
85 start = Point::new(start.row + 1, 0);
86 }
87
88 let start_anchor = buffer.anchor_after(start);
89 let end_anchor = buffer.anchor_before(end);
90 ranges_to_highlight.push(start_anchor..end_anchor);
91
92 for chunk in buffer.text_for_range(start..end) {
93 text.push_str(chunk);
94 }
95 if is_last_line {
96 text.push_str("\n");
97 }
98 clipboard_selections.push(ClipboardSelection {
99 len: text.len() - initial_len,
100 is_entire_line: linewise,
101 first_line_indent: buffer.indent_size_for_line(MultiBufferRow(start.row)).len,
102 });
103 }
104 }
105
106 vim.write_registers(is_yank, linewise, text, clipboard_selections, cx);
107
108 if !is_yank || vim.state().mode == Mode::Visual {
109 return;
110 }
111
112 editor.highlight_background::<HighlightOnYank>(
113 &ranges_to_highlight,
114 |colors| colors.editor_document_highlight_read_background,
115 cx,
116 );
117 cx.spawn(|this, mut cx| async move {
118 cx.background_executor()
119 .timer(Duration::from_millis(200))
120 .await;
121 this.update(&mut cx, |editor, cx| {
122 editor.clear_background_highlights::<HighlightOnYank>(cx)
123 })
124 .ok();
125 })
126 .detach();
127}
128
129pub fn coerce_punctuation(kind: CharKind, treat_punctuation_as_word: bool) -> CharKind {
130 if treat_punctuation_as_word && kind == CharKind::Punctuation {
131 CharKind::Word
132 } else {
133 kind
134 }
135}