mirror of
https://github.com/gitui-org/gitui
synced 2026-05-24 09:28:21 +00:00
Add cursor to TextInputComponent for better commit message support (#117)
see #46
This commit is contained in:
parent
0a541a398e
commit
09c3fe9cbb
1 changed files with 106 additions and 14 deletions
|
|
@ -8,12 +8,11 @@ use crate::{
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use crossterm::event::{Event, KeyCode, KeyModifiers};
|
use crossterm::event::{Event, KeyCode, KeyModifiers};
|
||||||
use std::borrow::Cow;
|
|
||||||
use strings::commands;
|
use strings::commands;
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
layout::Rect,
|
layout::Rect,
|
||||||
style::Style,
|
style::{Modifier, Style},
|
||||||
widgets::{Clear, Text},
|
widgets::{Clear, Text},
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
|
|
@ -25,6 +24,7 @@ pub struct TextInputComponent {
|
||||||
msg: String,
|
msg: String,
|
||||||
visible: bool,
|
visible: bool,
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
|
cursor_position: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextInputComponent {
|
impl TextInputComponent {
|
||||||
|
|
@ -40,19 +40,56 @@ impl TextInputComponent {
|
||||||
theme: *theme,
|
theme: *theme,
|
||||||
title: title.to_string(),
|
title: title.to_string(),
|
||||||
default_msg: default_msg.to_string(),
|
default_msg: default_msg.to_string(),
|
||||||
|
cursor_position: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
/// Clear the `msg`.
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.msg.clear();
|
self.msg.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
/// Get the `msg`.
|
||||||
pub const fn get_text(&self) -> &String {
|
pub const fn get_text(&self) -> &String {
|
||||||
&self.msg
|
&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) {
|
pub fn set_text(&mut self, msg: String) {
|
||||||
self.msg = msg;
|
self.msg = msg;
|
||||||
|
|
@ -71,16 +108,43 @@ impl DrawableComponent for TextInputComponent {
|
||||||
_rect: Rect,
|
_rect: Rect,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if self.visible {
|
if self.visible {
|
||||||
let txt = if self.msg.is_empty() {
|
let mut txt: Vec<tui::widgets::Text> = Vec::new();
|
||||||
[Text::Styled(
|
if self.msg.is_empty() {
|
||||||
Cow::from(self.default_msg.as_str()),
|
txt.push(Text::styled(
|
||||||
|
self.default_msg.as_str(),
|
||||||
self.theme.text(false, false),
|
self.theme.text(false, false),
|
||||||
)]
|
));
|
||||||
} else {
|
} else {
|
||||||
[Text::Styled(
|
// the portion of the text before the cursor is added
|
||||||
Cow::from(self.msg.clone()),
|
// if the cursor is not at the first character
|
||||||
Style::default(),
|
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());
|
let area = ui::centered_rect(60, 20, f.size());
|
||||||
|
|
@ -128,11 +192,39 @@ impl Component for TextInputComponent {
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
KeyCode::Char(c) if !is_ctrl => {
|
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);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
KeyCode::Backspace => {
|
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);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue