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