vtcode_tui/core_tui/session/
mouse_selection.rs1use ratatui::buffer::Buffer;
2use ratatui::crossterm::{clipboard::CopyToClipboard, execute};
3use ratatui::layout::Rect;
4use std::io::Write;
5
6#[derive(Debug, Default)]
8pub struct MouseSelectionState {
9 pub is_selecting: bool,
11 pub start: (u16, u16),
13 pub end: (u16, u16),
15 pub has_selection: bool,
17 copied: bool,
19}
20
21impl MouseSelectionState {
22 pub fn new() -> Self {
23 Self::default()
24 }
25
26 pub fn start_selection(&mut self, col: u16, row: u16) {
28 self.is_selecting = true;
29 self.has_selection = false;
30 self.copied = false;
31 self.start = (col, row);
32 self.end = (col, row);
33 }
34
35 pub fn update_selection(&mut self, col: u16, row: u16) {
37 if self.is_selecting {
38 self.end = (col, row);
39 self.has_selection = true;
40 }
41 }
42
43 pub fn finish_selection(&mut self, col: u16, row: u16) {
45 if self.is_selecting {
46 self.end = (col, row);
47 self.is_selecting = false;
48 self.has_selection = self.start != self.end;
50 }
51 }
52
53 #[allow(dead_code)]
55 pub fn clear(&mut self) {
56 self.is_selecting = false;
57 self.has_selection = false;
58 }
59
60 fn normalized(&self) -> ((u16, u16), (u16, u16)) {
62 let (s, e) = (self.start, self.end);
63 if s.1 < e.1 || (s.1 == e.1 && s.0 <= e.0) {
64 (s, e)
65 } else {
66 (e, s)
67 }
68 }
69
70 pub fn extract_text(&self, buf: &Buffer, area: Rect) -> String {
72 if !self.has_selection && !self.is_selecting {
73 return String::new();
74 }
75
76 let ((start_col, start_row), (end_col, end_row)) = self.normalized();
77 let mut result = String::new();
78
79 for row in start_row..=end_row {
80 if row < area.y || row >= area.y + area.height {
81 continue;
82 }
83 let line_start = if row == start_row {
84 start_col.max(area.x)
85 } else {
86 area.x
87 };
88 let line_end = if row == end_row {
89 end_col.min(area.x + area.width)
90 } else {
91 area.x + area.width
92 };
93
94 for col in line_start..line_end {
95 if col < area.x || col >= area.x + area.width {
96 continue;
97 }
98 let cell = &buf[(col, row)];
99 let symbol = cell.symbol();
100 if !symbol.is_empty() {
101 result.push_str(symbol);
102 }
103 }
104
105 if row < end_row {
107 let trimmed = result.trim_end().len();
109 result.truncate(trimmed);
110 result.push('\n');
111 }
112 }
113
114 let trimmed = result.trim_end();
116 trimmed.to_string()
117 }
118
119 pub fn apply_highlight(&self, buf: &mut Buffer, area: Rect) {
121 if !self.has_selection && !self.is_selecting {
122 return;
123 }
124
125 let ((start_col, start_row), (end_col, end_row)) = self.normalized();
126
127 for row in start_row..=end_row {
128 if row < area.y || row >= area.y + area.height {
129 continue;
130 }
131 let line_start = if row == start_row {
132 start_col.max(area.x)
133 } else {
134 area.x
135 };
136 let line_end = if row == end_row {
137 end_col.min(area.x + area.width)
138 } else {
139 area.x + area.width
140 };
141
142 for col in line_start..line_end {
143 if col < area.x || col >= area.x + area.width {
144 continue;
145 }
146 let cell = &mut buf[(col, row)];
147 let fg = cell.fg;
149 let bg = cell.bg;
150 cell.set_fg(bg);
151 cell.set_bg(fg);
152 }
153 }
154 }
155
156 pub fn needs_copy(&self) -> bool {
158 self.has_selection && !self.is_selecting && !self.copied
159 }
160
161 pub fn mark_copied(&mut self) {
163 self.copied = true;
164 }
165
166 pub fn copy_to_clipboard(text: &str) {
168 if text.is_empty() {
169 return;
170 }
171 let _ = execute!(
172 std::io::stderr(),
173 CopyToClipboard::to_clipboard_from(text.as_bytes())
174 );
175 let _ = std::io::stderr().flush();
176 }
177}