diff options
Diffstat (limited to 'src/shell/readline.rs')
-rw-r--r-- | src/shell/readline.rs | 223 |
1 files changed, 223 insertions, 0 deletions
diff --git a/src/shell/readline.rs b/src/shell/readline.rs new file mode 100644 index 0000000..654d264 --- /dev/null +++ b/src/shell/readline.rs @@ -0,0 +1,223 @@ +use crate::shell::prelude::*; + +use unicode_width::{UnicodeWidthChar as _, UnicodeWidthStr as _}; + +pub struct Readline { + size: (u16, u16), + input_line: String, + scroll: usize, + pos: usize, +} + +impl Readline { + pub fn new() -> Self { + Self { + size: (24, 80), + input_line: "".into(), + scroll: 0, + pos: 0, + } + } + + pub fn render( + &self, + out: &mut impl textmode::Textmode, + env: &Env, + git: Option<&super::inputs::GitInfo>, + focus: bool, + offset: time::UtcOffset, + ) -> Result<()> { + let pwd = env.pwd(); + let user = crate::info::user()?; + let hostname = crate::info::hostname()?; + let time = crate::info::time(offset)?; + let prompt_char = crate::info::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(0x56, 0x1b, 0x8b)); + } else if env.idx() % 2 == 0 { + out.set_bgcolor(textmode::Color::Rgb(0x24, 0x21, 0x00)); + } else { + out.set_bgcolor(textmode::Color::Rgb(0x20, 0x20, 0x20)); + } + out.write(b"\x1b[K"); + out.set_fgcolor(textmode::color::YELLOW); + out.write_str(&format!("{}", env.idx() + 1)); + out.reset_attributes(); + if focus { + out.set_bgcolor(textmode::Color::Rgb(0x56, 0x1b, 0x8b)); + } else if env.idx() % 2 == 0 { + out.set_bgcolor(textmode::Color::Rgb(0x24, 0x21, 0x00)); + } else { + out.set_bgcolor(textmode::Color::Rgb(0x20, 0x20, 0x20)); + } + out.write_str(" ("); + out.write_str(&crate::format::path(pwd)); + if let Some(info) = git { + out.write_str(&format!("|{}", info)); + } + 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.visible_input()); + out.reset_attributes(); + out.move_to(self.size.0 - 1, 2 + self.pos_width()); + if focus { + out.hide_cursor(false); + } + Ok(()) + } + + pub fn resize(&mut self, size: (u16, u16)) { + self.size = size; + } + + // self will be used eventually + #[allow(clippy::unused_self)] + pub fn lines(&self) -> usize { + 2 // XXX handle wrapping + } + + pub fn input(&self) -> &str { + &self.input_line + } + + pub fn add_input(&mut self, s: &str) { + self.input_line.insert_str(self.byte_pos(), s); + self.inc_pos(s.chars().count()); + } + + pub fn set_input(&mut self, s: String) { + self.set_pos(s.chars().count()); + self.input_line = s; + } + + pub fn backspace(&mut self) { + while self.pos > 0 { + self.dec_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.set_pos(0); + } + + pub fn clear_backwards(&mut self) { + self.input_line = self.input_line.chars().skip(self.pos).collect(); + self.set_pos(0); + } + + pub fn cursor_left(&mut self) { + if self.pos == 0 { + return; + } + self.dec_pos(1); + while let Some(c) = self.input_line.chars().nth(self.pos) { + if c.width().unwrap_or(0) == 0 { + self.dec_pos(1); + } else { + break; + } + } + } + + pub fn cursor_right(&mut self) { + if self.pos == self.input_line.chars().count() { + return; + } + self.inc_pos(1); + while let Some(c) = self.input_line.chars().nth(self.pos) { + if c.width().unwrap_or(0) == 0 { + self.inc_pos(1); + } else { + break; + } + } + } + + fn set_pos(&mut self, pos: usize) { + self.pos = pos; + if self.pos < self.scroll || self.pos_width() > self.size.1 - 2 { + self.scroll = self.pos; + let mut extra_scroll = usize::from(self.size.1) / 2; + while extra_scroll > 0 && self.scroll > 0 { + self.scroll -= 1; + extra_scroll -= self + .input_line + .chars() + .nth(self.scroll) + .unwrap() + .width() + .unwrap_or(1); + } + } + } + + fn inc_pos(&mut self, inc: usize) { + self.set_pos(self.pos + inc); + } + + fn dec_pos(&mut self, dec: usize) { + self.set_pos(self.pos - dec); + } + + fn pos_width(&self) -> u16 { + let start = self + .input_line + .char_indices() + .nth(self.scroll) + .map_or(self.input_line.len(), |(i, _)| i); + let end = self + .input_line + .char_indices() + .nth(self.pos) + .map_or(self.input_line.len(), |(i, _)| i); + self.input_line[start..end].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) + } + + fn visible_input(&self) -> &str { + let start = self + .input_line + .char_indices() + .nth(self.scroll) + .map_or(self.input_line.len(), |(i, _)| i); + let mut end = self.input_line.len(); + let mut width = 0; + for (i, c) in self.input_line.char_indices().skip(self.scroll) { + if width >= usize::from(self.size.1) - 2 { + end = i; + break; + } + width += c.width().unwrap_or(1); + } + &self.input_line[start..end] + } +} |