From 09c3fe9cbb8a32d785cf3e71c07510858a284b01 Mon Sep 17 00:00:00 2001 From: "A. Carscadden" <7132621+alistaircarscadden@users.noreply.github.com> Date: Sat, 13 Jun 2020 18:56:35 -0400 Subject: [PATCH] Add cursor to TextInputComponent for better commit message support (#117) see #46 --- src/components/textinput.rs | 120 +++++++++++++++++++++++++++++++----- 1 file changed, 106 insertions(+), 14 deletions(-) diff --git a/src/components/textinput.rs b/src/components/textinput.rs index 04a28d4b77..210e4d618a 100644 --- a/src/components/textinput.rs +++ b/src/components/textinput.rs @@ -8,12 +8,11 @@ use crate::{ }; use anyhow::Result; use crossterm::event::{Event, KeyCode, KeyModifiers}; -use std::borrow::Cow; use strings::commands; use tui::{ backend::Backend, layout::Rect, - style::Style, + style::{Modifier, Style}, widgets::{Clear, Text}, Frame, }; @@ -25,6 +24,7 @@ pub struct TextInputComponent { msg: String, visible: bool, theme: Theme, + cursor_position: usize, } impl TextInputComponent { @@ -40,19 +40,56 @@ impl TextInputComponent { theme: *theme, title: title.to_string(), default_msg: default_msg.to_string(), + cursor_position: 0, } } - /// + /// Clear the `msg`. pub fn clear(&mut self) { self.msg.clear(); } - /// + /// Get the `msg`. pub const fn get_text(&self) -> &String { &self.msg } + /// Move the cursor right one char. + fn incr_cursor(&mut self) { + if let Some(pos) = self.next_char_position() { + self.cursor_position = pos; + } + } + + /// Move the cursor left one char. + fn decr_cursor(&mut self) { + let mut new_pos: usize = 0; + for (bytes, _) in self.msg.char_indices() { + if bytes >= self.cursor_position { + break; + } + new_pos = bytes; + } + self.cursor_position = new_pos; + } + + /// Get the position of the next char, or, if the cursor points + /// to the last char, the `msg.len()`. + /// Returns None when the cursor is already at `msg.len()`. + fn next_char_position(&self) -> Option { + let mut char_indices = + self.msg[self.cursor_position..].char_indices(); + if char_indices.next().is_some() { + if let Some((bytes, _)) = char_indices.next() { + Some(self.cursor_position + bytes) + } else { + Some(self.msg.len()) + } + } else { + None + } + } + /// pub fn set_text(&mut self, msg: String) { self.msg = msg; @@ -71,16 +108,43 @@ impl DrawableComponent for TextInputComponent { _rect: Rect, ) -> Result<()> { if self.visible { - let txt = if self.msg.is_empty() { - [Text::Styled( - Cow::from(self.default_msg.as_str()), + let mut txt: Vec = Vec::new(); + if self.msg.is_empty() { + txt.push(Text::styled( + self.default_msg.as_str(), self.theme.text(false, false), - )] + )); } else { - [Text::Styled( - Cow::from(self.msg.clone()), - Style::default(), - )] + // the portion of the text before the cursor is added + // if the cursor is not at the first character + if self.cursor_position > 0 { + txt.push(Text::styled( + &self.msg[..self.cursor_position], + Style::default(), + )); + } + + txt.push(Text::styled( + if let Some(pos) = self.next_char_position() { + &self.msg[self.cursor_position..pos] + } else { + // if the cursor is at the end of the msg + // a whitespace is used to underline + " " + }, + Style::default().modifier(Modifier::UNDERLINED), + )); + + // the final portion of the text is added if there is + // still remaining characters + if let Some(pos) = self.next_char_position() { + if pos < self.msg.len() { + txt.push(Text::styled( + &self.msg[pos..], + Style::default(), + )); + } + } }; let area = ui::centered_rect(60, 20, f.size()); @@ -128,11 +192,39 @@ impl Component for TextInputComponent { return Ok(true); } KeyCode::Char(c) if !is_ctrl => { - self.msg.push(c); + self.msg.insert(self.cursor_position, c); + self.incr_cursor(); + return Ok(true); + } + KeyCode::Delete => { + if self.cursor_position < self.msg.len() { + self.msg.remove(self.cursor_position); + } return Ok(true); } KeyCode::Backspace => { - self.msg.pop(); + if self.cursor_position > 0 { + self.decr_cursor(); + if self.cursor_position < self.msg.len() { + } + self.msg.remove(self.cursor_position); + } + return Ok(true); + } + KeyCode::Left => { + self.decr_cursor(); + return Ok(true); + } + KeyCode::Right => { + self.incr_cursor(); + return Ok(true); + } + KeyCode::Home => { + self.cursor_position = 0; + return Ok(true); + } + KeyCode::End => { + self.cursor_position = self.msg.len(); return Ok(true); } _ => (),