diff options
Diffstat (limited to 'src/state/readline.rs')
-rw-r--r-- | src/state/readline.rs | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/src/state/readline.rs b/src/state/readline.rs new file mode 100644 index 0000000..585d6b6 --- /dev/null +++ b/src/state/readline.rs @@ -0,0 +1,161 @@ +use unicode_width::{UnicodeWidthChar as _, UnicodeWidthStr as _}; + +pub struct Readline { + size: (u16, u16), + input_line: String, + pos: usize, +} + +impl Readline { + pub fn new() -> Self { + Self { + size: (24, 80), + input_line: "".into(), + pos: 0, + } + } + + pub async fn render( + &self, + out: &mut impl textmode::Textmode, + entry_count: usize, + focus: bool, + offset: time::UtcOffset, + ) -> anyhow::Result<()> { + let pwd = crate::env::pwd()?; + let user = crate::env::user()?; + let hostname = crate::env::hostname()?; + let time = crate::env::time(offset)?; + let prompt_char = crate::env::prompt_char()?; + + let id = format!("{}@{}", user, hostname); + let idlen: u16 = id.len().try_into().unwrap(); + let timelen: u16 = time.len().try_into().unwrap(); + + out.move_to(self.size.0 - 2, 0); + if focus { + out.set_bgcolor(textmode::Color::Rgb(32, 32, 64)); + } else { + out.set_bgcolor(textmode::Color::Rgb(32, 32, 32)); + } + out.write(b"\x1b[K"); + out.set_fgcolor(textmode::color::YELLOW); + out.write_str(&format!("{}", entry_count + 1)); + out.reset_attributes(); + if focus { + out.set_bgcolor(textmode::Color::Rgb(32, 32, 64)); + } else { + out.set_bgcolor(textmode::Color::Rgb(32, 32, 32)); + } + out.write_str(" ("); + out.write_str(&pwd); + out.write_str(")"); + out.move_to(self.size.0 - 2, self.size.1 - 4 - idlen - timelen); + out.write_str(&id); + out.write_str(" ["); + out.write_str(&time); + out.write_str("]"); + + out.move_to(self.size.0 - 1, 0); + out.reset_attributes(); + out.write_str(&prompt_char); + out.write_str(" "); + out.reset_attributes(); + out.write(b"\x1b[K"); + out.write_str(&self.input_line); + out.reset_attributes(); + out.move_to(self.size.0 - 1, 2 + self.pos_width()); + if focus { + out.hide_cursor(false); + } + Ok(()) + } + + pub async fn resize(&mut self, size: (u16, u16)) { + self.size = size; + } + + pub fn lines(&self) -> usize { + 2 // XXX handle wrapping + } + + pub fn input(&self) -> String { + self.input_line.clone() + } + + pub fn add_input(&mut self, s: &str) { + self.input_line.insert_str(self.byte_pos(), s); + self.pos += s.chars().count(); + } + + pub fn set_input(&mut self, s: &str) { + self.input_line = s.to_string(); + self.pos = s.chars().count(); + } + + pub fn backspace(&mut self) { + while self.pos > 0 { + self.pos -= 1; + let width = + self.input_line.remove(self.byte_pos()).width().unwrap_or(0); + if width > 0 { + break; + } + } + } + + pub fn clear_input(&mut self) { + self.input_line.clear(); + self.pos = 0; + } + + pub fn clear_backwards(&mut self) { + self.input_line = self.input_line.chars().skip(self.pos).collect(); + self.pos = 0; + } + + pub fn cursor_left(&mut self) { + if self.pos == 0 { + return; + } + self.pos -= 1; + while let Some(c) = self.input_line.chars().nth(self.pos) { + if c.width().unwrap_or(0) == 0 { + self.pos -= 1; + } else { + break; + } + } + } + + pub fn cursor_right(&mut self) { + if self.pos == self.input_line.chars().count() { + return; + } + self.pos += 1; + while let Some(c) = self.input_line.chars().nth(self.pos) { + if c.width().unwrap_or(0) == 0 { + self.pos += 1; + } else { + break; + } + } + } + + fn pos_width(&self) -> u16 { + self.input_line + .chars() + .take(self.pos) + .collect::<String>() + .width() + .try_into() + .unwrap() + } + + fn byte_pos(&self) -> usize { + self.input_line + .char_indices() + .nth(self.pos) + .map_or(self.input_line.len(), |(i, _)| i) + } +} |