use crossterm::event::{KeyCode, KeyModifiers};
fn char_len(line: &str) -> usize {
line.chars().count()
}
fn byte_offset(line: &str, char_idx: usize) -> usize {
line.char_indices()
.nth(char_idx)
.map(|(offset, _)| offset)
.unwrap_or(line.len())
}
#[derive(Debug, Clone, Copy)]
pub enum Direction {
Up,
Down,
Left,
Right,
}
#[derive(Debug, Clone)]
pub struct InputState {
lines: Vec<String>,
cursor_row: usize,
cursor_col: usize,
}
impl Default for InputState {
fn default() -> Self {
Self {
lines: vec![String::new()],
cursor_row: 0,
cursor_col: 0,
}
}
}
impl InputState {
pub fn new() -> Self {
Self::default()
}
pub fn from_lines(lines: Vec<String>) -> Self {
if lines.is_empty() {
Self::default()
} else {
let cursor_row = lines.len() - 1;
let cursor_col = char_len(&lines[cursor_row]);
Self {
lines,
cursor_row,
cursor_col,
}
}
}
pub fn lines(&self) -> &[String] {
&self.lines
}
pub fn text(&self) -> String {
self.lines.join("\n")
}
pub fn cursor_position(&self) -> (usize, usize) {
(self.cursor_row, self.cursor_col)
}
pub fn clear(&mut self) {
self.lines = vec![String::new()];
self.cursor_row = 0;
self.cursor_col = 0;
}
pub fn insert_char(&mut self, c: char) {
let offset = byte_offset(&self.lines[self.cursor_row], self.cursor_col);
self.lines[self.cursor_row].insert(offset, c);
self.cursor_col += 1;
}
pub fn insert_newline(&mut self) {
let offset = byte_offset(&self.lines[self.cursor_row], self.cursor_col);
let rest = self.lines[self.cursor_row][offset..].to_string();
self.lines[self.cursor_row].truncate(offset);
self.cursor_row += 1;
self.lines.insert(self.cursor_row, rest);
self.cursor_col = 0;
}
pub fn delete_char(&mut self) {
if self.cursor_col > 0 {
let offset = byte_offset(&self.lines[self.cursor_row], self.cursor_col - 1);
self.lines[self.cursor_row].remove(offset);
self.cursor_col -= 1;
} else if self.cursor_row > 0 {
let current_line = self.lines.remove(self.cursor_row);
self.cursor_row -= 1;
self.cursor_col = char_len(&self.lines[self.cursor_row]);
self.lines[self.cursor_row].push_str(¤t_line);
}
}
pub fn delete_char_forward(&mut self) {
if self.cursor_col < char_len(&self.lines[self.cursor_row]) {
let offset = byte_offset(&self.lines[self.cursor_row], self.cursor_col);
self.lines[self.cursor_row].remove(offset);
} else if self.cursor_row < self.lines.len() - 1 {
let next_line = self.lines.remove(self.cursor_row + 1);
self.lines[self.cursor_row].push_str(&next_line);
}
}
pub fn delete_to_end_of_line(&mut self) {
let offset = byte_offset(&self.lines[self.cursor_row], self.cursor_col);
self.lines[self.cursor_row].truncate(offset);
}
pub fn delete_line(&mut self) {
self.lines[self.cursor_row].clear();
self.cursor_col = 0;
}
pub fn delete_word(&mut self) {
if self.cursor_col == 0 {
return;
}
let line = &mut self.lines[self.cursor_row];
let chars: Vec<char> = line.chars().collect();
let end_char = self.cursor_col.min(chars.len());
let mut new_col = end_char;
while new_col > 0 && chars[new_col - 1].is_whitespace() {
new_col -= 1;
}
while new_col > 0 && !chars[new_col - 1].is_whitespace() {
new_col -= 1;
}
let byte_start = byte_offset(line, new_col);
let byte_end = byte_offset(line, end_char);
line.replace_range(byte_start..byte_end, "");
self.cursor_col = new_col;
}
pub fn delete_word_forward(&mut self) {
let line = &mut self.lines[self.cursor_row];
let chars: Vec<char> = line.chars().collect();
if self.cursor_col >= chars.len() {
return;
}
let mut end_col = self.cursor_col;
while end_col < chars.len() && chars[end_col].is_whitespace() {
end_col += 1;
}
while end_col < chars.len() && !chars[end_col].is_whitespace() {
end_col += 1;
}
let byte_start = byte_offset(line, self.cursor_col);
let byte_end = byte_offset(line, end_col);
line.replace_range(byte_start..byte_end, "");
}
pub fn move_cursor_word_left(&mut self) {
let line = &self.lines[self.cursor_row];
if self.cursor_col == 0 {
if self.cursor_row > 0 {
self.cursor_row -= 1;
self.cursor_col = char_len(&self.lines[self.cursor_row]);
}
return;
}
let chars: Vec<char> = line.chars().collect();
let mut new_col = self.cursor_col.min(chars.len());
while new_col > 0 && chars[new_col - 1].is_whitespace() {
new_col -= 1;
}
while new_col > 0 && !chars[new_col - 1].is_whitespace() {
new_col -= 1;
}
self.cursor_col = new_col;
}
pub fn move_cursor_word_right(&mut self) {
let line = &self.lines[self.cursor_row];
let chars: Vec<char> = line.chars().collect();
if self.cursor_col >= chars.len() {
if self.cursor_row < self.lines.len() - 1 {
self.cursor_row += 1;
self.cursor_col = 0;
}
return;
}
let mut new_col = self.cursor_col;
if new_col < chars.len() && !chars[new_col].is_whitespace() {
while new_col < chars.len() && !chars[new_col].is_whitespace() {
new_col += 1;
}
}
while new_col < chars.len() && chars[new_col].is_whitespace() {
new_col += 1;
}
self.cursor_col = new_col;
}
pub fn move_cursor(&mut self, direction: Direction) {
match direction {
Direction::Up => {
if self.cursor_row > 0 {
self.cursor_row -= 1;
let line_len = char_len(&self.lines[self.cursor_row]);
self.cursor_col = self.cursor_col.min(line_len);
}
}
Direction::Down => {
if self.cursor_row < self.lines.len() - 1 {
self.cursor_row += 1;
let line_len = char_len(&self.lines[self.cursor_row]);
self.cursor_col = self.cursor_col.min(line_len);
}
}
Direction::Left => {
if self.cursor_col > 0 {
self.cursor_col -= 1;
} else if self.cursor_row > 0 {
self.cursor_row -= 1;
self.cursor_col = char_len(&self.lines[self.cursor_row]);
}
}
Direction::Right => {
let line_len = char_len(&self.lines[self.cursor_row]);
if self.cursor_col < line_len {
self.cursor_col += 1;
} else if self.cursor_row < self.lines.len() - 1 {
self.cursor_row += 1;
self.cursor_col = 0;
}
}
}
}
pub fn move_to_start_of_line(&mut self) {
self.cursor_col = 0;
}
pub fn move_to_end_of_line(&mut self) {
self.cursor_col = char_len(&self.lines[self.cursor_row]);
}
pub fn move_to_top(&mut self) {
self.cursor_row = 0;
self.cursor_col = 0;
}
pub fn move_to_bottom(&mut self) {
self.cursor_row = self.lines.len() - 1;
self.cursor_col = char_len(&self.lines[self.cursor_row]);
}
pub fn is_on_first_line(&self) -> bool {
self.cursor_row == 0
}
pub fn is_on_last_line(&self) -> bool {
self.cursor_row == self.lines.len() - 1
}
pub fn handle_key(&mut self, key_code: KeyCode, modifiers: KeyModifiers) -> bool {
match key_code {
KeyCode::Char(c) => {
if modifiers.contains(KeyModifiers::CONTROL)
|| modifiers.contains(KeyModifiers::ALT)
{
return false;
}
self.insert_char(c);
true
}
KeyCode::Backspace => {
self.delete_char();
true
}
KeyCode::Delete => {
self.delete_char_forward();
true
}
KeyCode::Left => {
self.move_cursor(Direction::Left);
true
}
KeyCode::Right => {
self.move_cursor(Direction::Right);
true
}
KeyCode::Up => {
self.move_cursor(Direction::Up);
true
}
KeyCode::Down => {
self.move_cursor(Direction::Down);
true
}
KeyCode::Home => {
self.move_to_start_of_line();
true
}
KeyCode::End => {
self.move_to_end_of_line();
true
}
_ => false,
}
}
}