use super::{Cursor, Line, ScreenBuffer};
#[non_exhaustive]
#[derive(Clone, Debug)]
pub enum UICommand {
ScrollUp(usize),
ScrollDown(usize),
ScrollBottom,
ScrollTop,
StartSelection(u16, u16),
UpdateSelection(u16, u16),
CopySelection,
ClearBuffer,
}
pub(crate) trait UIAction {
fn scroll_up(&mut self, lines: usize);
fn scroll_down(&mut self, lines: usize);
fn scroll_to_bottom(&mut self);
fn scroll_to_top(&mut self);
fn start_selection(&mut self, screen_x: u16, screen_y: u16);
fn update_selection(&mut self, screen_x: u16, screen_y: u16);
fn clear_selection(&mut self);
fn copy_to_clipboard(&mut self) -> std::io::Result<()>;
fn clear_buffer(&mut self);
}
impl UIAction for ScreenBuffer {
fn scroll_up(&mut self, lines: usize) {
if self.view_start >= lines {
self.view_start -= lines;
} else {
self.view_start = 0;
}
self.clear_selection();
self.needs_render = true;
}
fn scroll_down(&mut self, lines: usize) {
let max_view_start = self.lines.len().saturating_sub(self.height as usize);
self.view_start = (self.view_start + lines).min(max_view_start);
self.clear_selection();
self.needs_render = true;
}
fn scroll_to_bottom(&mut self) {
self.view_start = self.lines.len().saturating_sub(self.height as usize);
self.needs_render = true;
}
fn scroll_to_top(&mut self) {
self.view_start = 0;
self.needs_render = true;
}
fn start_selection(&mut self, screen_x: u16, screen_y: u16) {
let absolute_line = self.view_start + screen_y as usize;
self.clear_selection();
self.selection_start = Some((screen_x, absolute_line));
self.needs_render = true;
}
fn update_selection(&mut self, screen_x: u16, screen_y: u16) {
let absolute_line = self.view_start + screen_y as usize;
self.selection_end = Some((screen_x, absolute_line));
self.update_selection_highlighting();
self.needs_render = true;
}
fn clear_selection(&mut self) {
for line in &mut self.lines {
line.clear_selection();
}
self.selection_start = None;
self.selection_end = None;
self.needs_render = true;
}
fn copy_to_clipboard(&mut self) -> std::io::Result<()> {
use crossterm::{clipboard, execute};
let selected_text = self.get_selected_text();
if !selected_text.is_empty() {
execute!(
std::io::stdout(),
clipboard::CopyToClipboard::to_clipboard_from(selected_text)
)?;
}
self.clear_selection();
Ok(())
}
fn clear_buffer(&mut self) {
self.lines.clear();
self.view_start = 0;
self.set_cursor_pos((0_u16, 0_usize));
self.lines.push_back(Line::new(self.width as usize));
self.needs_render = true;
}
}
impl ScreenBuffer {
fn update_selection_highlighting(&mut self) {
for line in &mut self.lines {
line.clear_selection();
}
if let (Some((start_x, start_line)), Some((end_x, end_line))) =
(self.selection_start, self.selection_end)
{
let (start_line, start_x, end_line, end_x) =
if start_line < end_line || (start_line == end_line && start_x <= end_x) {
(start_line, start_x, end_line, end_x)
} else {
(end_line, end_x, start_line, start_x)
};
for line_idx in start_line..=end_line {
if let Some(line) = self.lines.get_mut(line_idx) {
let line_start_x = if line_idx == start_line { start_x } else { 0 };
let line_end_x = if line_idx == end_line {
end_x
} else {
self.width - 1
};
for x in line_start_x..=line_end_x.min(self.width - 1) {
if let Some(cell) = line.get_mut_cell(x as usize) {
cell.is_selected = true;
}
}
}
}
}
}
fn get_selected_text(&self) -> String {
if let (Some((start_x, start_line)), Some((end_x, end_line))) =
(self.selection_start, self.selection_end)
{
let (start_line, start_x, end_line, end_x) =
if start_line < end_line || (start_line == end_line && start_x <= end_x) {
(start_line, start_x, end_line, end_x)
} else {
(end_line, end_x, start_line, start_x)
};
let mut result = String::new();
for line_idx in start_line..=end_line {
if let Some(line) = self.lines.get(line_idx) {
let line_start_x = if line_idx == start_line { start_x } else { 0 };
let line_end_x = if line_idx == end_line {
end_x
} else {
self.width - 1
};
for x in line_start_x..=line_end_x.min(self.width - 1) {
if let Some(cell) = line.get_cell(x as usize) {
result.push(cell.character);
}
}
if line_idx < end_line {
result.push('\n');
}
}
}
result.trim_end().to_string()
} else {
String::new()
}
}
}