use crate::app::Environment;
use crate::keys::key_match;
use crate::ui::Size;
use crate::{
components::{
visibility_blocking, CommandBlocking, CommandInfo, Component,
DrawableComponent, EventState,
},
keys::SharedKeyConfig,
strings,
ui::{self, style::SharedTheme},
};
use anyhow::Result;
use crossterm::event::Event;
use ratatui::widgets::{Block, Borders};
use ratatui::{
layout::{Alignment, Rect},
widgets::{Clear, Paragraph},
Frame,
};
use std::cell::Cell;
use std::cell::OnceCell;
use tui_textarea::{CursorMove, Input, Key, Scrolling, TextArea};
///
#[derive(PartialEq, Eq)]
pub enum InputType {
Singleline,
Multiline,
Password,
}
#[derive(PartialEq, Eq)]
enum SelectionState {
Selecting,
NotSelecting,
SelectionEndPending,
}
type TextAreaComponent = TextArea<'static>;
///
pub struct TextInputComponent {
title: String,
default_msg: String,
selected: Option<bool>,
msg: OnceCell<String>,
show_char_count: bool,
theme: SharedTheme,
key_config: SharedKeyConfig,
input_type: InputType,
current_area: Cell<Rect>,
embed: bool,
textarea: Option<TextAreaComponent>,
select_state: SelectionState,
}
impl TextInputComponent {
///
pub fn new(
env: &Environment,
title: &str,
default_msg: &str,
show_char_count: bool,
) -> Self {
Self {
msg: OnceCell::default(),
theme: env.theme.clone(),
key_config: env.key_config.clone(),
show_char_count,
title: title.to_string(),
default_msg: default_msg.to_string(),
selected: None,
input_type: InputType::Multiline,
current_area: Cell::new(Rect::default()),
embed: false,
textarea: None,
select_state: SelectionState::NotSelecting,
}
}
///
pub const fn with_input_type(
mut self,
input_type: InputType,
) -> Self {
self.input_type = input_type;
self
}
/// Clear the `msg`.
pub fn clear(&mut self) {
self.msg.take();
if self.is_visible() {
self.show_inner_textarea();
}
}
/// Get the `msg`.
pub fn get_text(&self) -> &str {
// the fancy footwork with the OnceCell is to allow
// the reading of msg as a &str.
// tui_textarea returns its lines to the caller as &[String]
// gitui wants &str of \n delimited text
// it would be simple if this was a mut method. You could
// just load up msg from the lines area and return an &str pointing at it
// but its not a mut method. So we need to store the text in a OnceCell
// The methods that change msg call take() on the cell. That makes
// get_or_init run again
self.msg.get_or_init(|| {
self.textarea
.as_ref()
.map_or_else(String::new, |ta| ta.lines().join("\n"))
})
}
/// screen area (last time we got drawn)
pub fn get_area(&self) -> Rect {
self.current_area.get()
}
/// embed into parent draw area
pub fn embed(&mut self) {
self.embed = true;
}
///
pub fn enabled(&mut self, enable: bool) {
self.selected = Some(enable);
}
fn show_inner_textarea(&mut self) {
// create the textarea and then load it with the text
// from self.msg
let lines: Vec<String> = self
.msg
.get()
.unwrap_or(&String::new())
.split('\n')
.map(ToString::to_string)
.collect();
self.textarea = Some({
let mut text_area = TextArea::new(lines);
if self.input_type == InputType::Password {
text_area.set_mask_char('*');
}
text_area
.set_cursor_line_style(self.theme.text(true, false));
text_area.set_placeholder_text(self.default_msg.clone());
text_area.set_placeholder_style(
self.theme
.text(self.selected.unwrap_or_default(), false),
);
text_area.set_style(
self.theme.text(self.selected.unwrap_or(true), false),
);
if !self.embed {
text_area.set_block(
Block::default()
.borders(Borders::ALL)
.border_style(
ratatui::style::Style::default()
.add_modifier(
ratatui::style::Modifier::BOLD,