use unicode_segmentation::UnicodeSegmentation;
use crate::{
display::DisplayColor,
input::{Event, KeyCode, KeyEvent, KeyModifiers},
view::{LineSegment, LineSegmentOptions},
};
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum EditAction {
CursorMove,
ContentUpdate,
None,
}
pub(crate) struct EditableLine {
content: String,
cursor_position: usize,
label: Option<LineSegment>,
read_only: bool,
}
impl EditableLine {
pub(crate) const fn new() -> Self {
Self {
content: String::new(),
cursor_position: 0,
label: None,
read_only: false,
}
}
pub(crate) fn set_label(&mut self, label: LineSegment) {
self.label = Some(label);
}
pub(crate) fn set_content(&mut self, content: &str) {
self.content = String::from(content);
self.cursor_position = UnicodeSegmentation::graphemes(content, true).count();
}
pub(crate) fn set_read_only(&mut self, read_only: bool) {
self.read_only = read_only;
}
pub(crate) fn clear(&mut self) {
self.content.clear();
self.cursor_position = 0;
}
pub(crate) fn get_content(&self) -> &str {
self.content.as_str()
}
pub(crate) const fn cursor_position(&self) -> usize {
self.cursor_position
}
pub(crate) fn line_segments(&self) -> Vec<LineSegment> {
if self.read_only {
return vec![LineSegment::new(self.get_content())];
}
let line = self.content.as_str();
let pointer = self.cursor_position;
let graphemes = UnicodeSegmentation::graphemes(line, true);
let start = graphemes.clone().take(pointer).collect::<String>();
let indicator = graphemes.clone().skip(pointer).take(1).collect::<String>();
let end = graphemes.skip(pointer + 1).collect::<String>();
let mut segments = vec![];
if let Some(label) = self.label.as_ref() {
segments.push(label.clone());
}
if !start.is_empty() {
segments.push(LineSegment::new(start.as_str()));
}
segments.push(
if indicator.is_empty() {
LineSegment::new_with_color_and_style(" ", DisplayColor::Normal, LineSegmentOptions::UNDERLINED)
}
else {
LineSegment::new_with_color_and_style(
indicator.as_str(),
DisplayColor::Normal,
LineSegmentOptions::UNDERLINED,
)
},
);
if !end.is_empty() {
segments.push(LineSegment::new(end.as_str()));
}
segments
}
pub(crate) fn handle_event(&mut self, event: Event) -> EditAction {
if self.read_only {
return EditAction::None;
}
match event {
Event::Key(KeyEvent {
code: KeyCode::Backspace,
modifiers: KeyModifiers::NONE,
}) => {
if self.cursor_position == 0 {
EditAction::None
}
else {
let start = UnicodeSegmentation::graphemes(self.content.as_str(), true)
.take(self.cursor_position - 1)
.collect::<String>();
let end = UnicodeSegmentation::graphemes(self.content.as_str(), true)
.skip(self.cursor_position)
.collect::<String>();
self.content = format!("{start}{end}");
self.cursor_position -= 1;
EditAction::ContentUpdate
}
},
Event::Key(KeyEvent {
code: KeyCode::Delete,
modifiers: KeyModifiers::NONE,
}) => {
let length = UnicodeSegmentation::graphemes(self.content.as_str(), true).count();
if self.cursor_position == length {
EditAction::None
}
else {
let start = UnicodeSegmentation::graphemes(self.content.as_str(), true)
.take(self.cursor_position)
.collect::<String>();
let end = UnicodeSegmentation::graphemes(self.content.as_str(), true)
.skip(self.cursor_position + 1)
.collect::<String>();
self.content = format!("{start}{end}");
EditAction::ContentUpdate
}
},
Event::Key(KeyEvent {
code: KeyCode::Home,
modifiers: KeyModifiers::NONE,
}) => {
if self.cursor_position == 0 {
EditAction::None
}
else {
self.cursor_position = 0;
EditAction::CursorMove
}
},
Event::Key(KeyEvent {
code: KeyCode::End,
modifiers: KeyModifiers::NONE,
}) => {
let new_position = UnicodeSegmentation::graphemes(self.content.as_str(), true).count();
if new_position == self