Skip to content

Add cursor to TextInputComponent for better commit message support (#46) #117

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jun 13, 2020
120 changes: 106 additions & 14 deletions src/components/textinput.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand All @@ -25,6 +24,7 @@ pub struct TextInputComponent {
msg: String,
visible: bool,
theme: Theme,
cursor_position: usize,
}

impl TextInputComponent {
Expand All @@ -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<usize> {
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;
Expand All @@ -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<tui::widgets::Text> = 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());
Expand Down Expand Up @@ -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);
}
_ => (),
Expand Down