pub struct CursorManager {
input_cursor_position: usize,
_visual_cursor: (usize, usize),
table_cursor: (usize, usize),
horizontal_scroll: u16,
vertical_scroll: usize,
}
impl Default for CursorManager {
fn default() -> Self {
Self::new()
}
}
impl CursorManager {
#[must_use]
pub fn new() -> Self {
Self {
input_cursor_position: 0,
_visual_cursor: (0, 0),
table_cursor: (0, 0),
horizontal_scroll: 0,
vertical_scroll: 0,
}
}
pub fn move_word_forward(&mut self, text: &str) -> usize {
let chars: Vec<char> = text.chars().collect();
let mut pos = self.input_cursor_position;
while pos < chars.len() && !chars[pos].is_whitespace() {
pos += 1;
}
while pos < chars.len() && chars[pos].is_whitespace() {
pos += 1;
}
self.input_cursor_position = pos;
pos
}
pub fn move_word_backward(&mut self, text: &str) -> usize {
let chars: Vec<char> = text.chars().collect();
let mut pos = self.input_cursor_position;
if pos == 0 {
return 0;
}
pos -= 1;
while pos > 0 && chars[pos].is_whitespace() {
pos -= 1;
}
while pos > 0 && !chars[pos - 1].is_whitespace() {
pos -= 1;
}
self.input_cursor_position = pos;
pos
}
pub fn move_to_line_start(&mut self) -> usize {
self.input_cursor_position = 0;
0
}
pub fn move_to_line_end(&mut self, text: &str) -> usize {
self.input_cursor_position = text.len();
text.len()
}
pub fn move_left(&mut self) -> usize {
if self.input_cursor_position > 0 {
self.input_cursor_position -= 1;
}
self.input_cursor_position
}
pub fn move_right(&mut self, text: &str) -> usize {
if self.input_cursor_position < text.len() {
self.input_cursor_position += 1;
}
self.input_cursor_position
}
pub fn set_position(&mut self, pos: usize) {
self.input_cursor_position = pos;
}
#[must_use]
pub fn position(&self) -> usize {
self.input_cursor_position
}
pub fn move_table_up(&mut self) -> (usize, usize) {
if self.table_cursor.0 > 0 {
self.table_cursor.0 -= 1;
}
self.table_cursor
}
pub fn move_table_down(&mut self, max_rows: usize) -> (usize, usize) {
if self.table_cursor.0 < max_rows.saturating_sub(1) {
self.table_cursor.0 += 1;
}
self.table_cursor
}
pub fn move_table_left(&mut self) -> (usize, usize) {
if self.table_cursor.1 > 0 {
self.table_cursor.1 -= 1;
}
self.table_cursor
}
pub fn move_table_right(&mut self, max_cols: usize) -> (usize, usize) {
if self.table_cursor.1 < max_cols.saturating_sub(1) {
self.table_cursor.1 += 1;
}
self.table_cursor
}
pub fn move_table_home(&mut self) -> (usize, usize) {
self.table_cursor.0 = 0;
self.table_cursor
}
pub fn move_table_end(&mut self, max_rows: usize) -> (usize, usize) {
self.table_cursor.0 = max_rows.saturating_sub(1);
self.table_cursor
}
pub fn page_up(&mut self, page_size: usize) -> (usize, usize) {
self.table_cursor.0 = self.table_cursor.0.saturating_sub(page_size);
self.table_cursor
}
pub fn page_down(&mut self, page_size: usize, max_rows: usize) -> (usize, usize) {
self.table_cursor.0 = (self.table_cursor.0 + page_size).min(max_rows.saturating_sub(1));
self.table_cursor
}
#[must_use]
pub fn table_position(&self) -> (usize, usize) {
self.table_cursor
}
pub fn reset_table_cursor(&mut self) {
self.table_cursor = (0, 0);
}
pub fn update_horizontal_scroll(&mut self, cursor_col: usize, viewport_width: u16) {
let cursor_x = cursor_col as u16;
if cursor_x >= self.horizontal_scroll + viewport_width {
self.horizontal_scroll = cursor_x.saturating_sub(viewport_width - 1);
}
if cursor_x < self.horizontal_scroll {
self.horizontal_scroll = cursor_x;
}
}
pub fn update_vertical_scroll(&mut self, cursor_row: usize, viewport_height: usize) {
if cursor_row >= self.vertical_scroll + viewport_height {
self.vertical_scroll = cursor_row.saturating_sub(viewport_height - 1);
}
if cursor_row < self.vertical_scroll {
self.vertical_scroll = cursor_row;
}
}
#[must_use]
pub fn scroll_offsets(&self) -> (u16, usize) {
(self.horizontal_scroll, self.vertical_scroll)
}
pub fn set_scroll_offsets(&mut self, horizontal: u16, vertical: usize) {
self.horizontal_scroll = horizontal;
self.vertical_scroll = vertical;
}
pub fn reset_horizontal_scroll(&mut self) {
self.horizontal_scroll = 0;
}
#[must_use]
pub fn get_word_at_cursor(&self, text: &str) -> Option<(usize, usize, String)> {
if text.is_empty() || self.input_cursor_position > text.len() {
return None;
}
let chars: Vec<char> = text.chars().collect();
let mut start = self.input_cursor_position;
let mut end = self.input_cursor_position;
if start < chars.len() && chars[start].is_whitespace() {
return None;
}
while start > 0 && !chars[start - 1].is_whitespace() {
start -= 1;
}
while end < chars.len() && !chars[end].is_whitespace() {
end += 1;
}
let word: String = chars[start..end].iter().collect();
Some((start, end, word))
}
#[must_use]
pub fn get_partial_word_before_cursor(&self, text: &str) -> Option<String> {
if self.input_cursor_position == 0 {
return None;
}
let before_cursor = &text[..self.input_cursor_position];
let last_space = before_cursor.rfind(' ').map_or(0, |i| i + 1);
if last_space < self.input_cursor_position {
Some(before_cursor[last_space..].to_string())
} else {
None
}
}
}
pub trait CursorBuffer {
fn cursor_manager(&self) -> &CursorManager;
fn cursor_manager_mut(&mut self) -> &mut CursorManager;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_word_navigation() {
let mut cm = CursorManager::new();
let text = "SELECT * FROM table WHERE id = 1";
assert_eq!(cm.position(), 0);
cm.move_word_forward(text);
assert_eq!(cm.position(), 7);
cm.move_word_forward(text);
assert_eq!(cm.position(), 9);
cm.move_word_backward(text);
assert_eq!(cm.position(), 7);
cm.move_word_backward(text);
assert_eq!(cm.position(), 0); }
#[test]
fn test_table_navigation() {
let mut cm = CursorManager::new();
cm.move_table_down(10);
assert_eq!(cm.table_position(), (1, 0));
cm.move_table_right(5);
assert_eq!(cm.table_position(), (1, 1));
cm.move_table_end(10);
assert_eq!(cm.table_position(), (9, 1));
cm.move_table_home();
assert_eq!(cm.table_position(), (0, 1));
}
#[test]
fn test_scroll_management() {
let mut cm = CursorManager::new();
cm.update_horizontal_scroll(100, 80);
assert_eq!(cm.scroll_offsets().0, 21);
cm.update_vertical_scroll(50, 20);
assert_eq!(cm.scroll_offsets().1, 31); }
#[test]
fn test_word_extraction() {
let mut cm = CursorManager::new();
let text = "SELECT column FROM table";
cm.set_position(7);
let word = cm.get_word_at_cursor(text);
assert_eq!(word, Some((7, 13, "column".to_string())));
cm.set_position(6);
let word = cm.get_word_at_cursor(text);
assert_eq!(word, None);
cm.set_position(10); let partial = cm.get_partial_word_before_cursor(text);
assert_eq!(partial, Some("col".to_string()));
}
}