Skip to content

Commit

Permalink
Merge pull request #20 from tofubert/feature/textarea
Browse files Browse the repository at this point in the history
use tui_textarea for input box
  • Loading branch information
tofubert authored Aug 29, 2024
2 parents c9f8594 + 68824c1 commit a5121f6
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 63 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ libc = "0.2.158"
strip-ansi-escapes = "0.2.0"
tracing = "0.1.40"
cfg-if = "1.0.0"
tui-textarea = "0.6.1"

[lints.clippy]
pedantic = "warn"
31 changes: 18 additions & 13 deletions src/ui/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::{
};
use ratatui::{prelude::*, widgets::Paragraph};
use strum_macros::Display;
use tui_textarea::Input;

#[derive(PartialEq, Clone, Copy, Display)]
pub enum CurrentScreen {
Expand All @@ -23,7 +24,7 @@ pub struct App<'a> {
title: TitleBar<'a>,
chat: ChatBox<'a>,
pub selector: ChatSelector<'a>,
input: InputBox,
input: InputBox<'a>,
help: HelpBox,
}

Expand All @@ -38,7 +39,7 @@ impl<'a> App<'a> {
.to_string(),
),
selector: ChatSelector::new(&backend),
input: InputBox::default(),
input: InputBox::new(""),
chat: {
let mut chat = ChatBox::new();
chat.update_messages(&backend);
Expand Down Expand Up @@ -96,11 +97,19 @@ impl<'a> App<'a> {
}

pub async fn send_message(&mut self) -> Result<(), Box<dyn std::error::Error>> {
self.backend.send_message(self.input.to_string()).await?;
self.input.clear();
self.update_ui()?;
self.chat.select_last_message();
Ok(())
if self.input.is_empty() {
Ok(())
} else {
self.backend
.send_message(self.input.lines().join("\n"))
.await?;
self.input.select_all();
self.input.cut();
self.input.select_all();
self.update_ui()?;
self.chat.select_last_message();
Ok(())
}
}

pub async fn select_room(&mut self) -> Result<(), Box<dyn std::error::Error>> {
Expand Down Expand Up @@ -130,12 +139,8 @@ impl<'a> App<'a> {
Ok(())
}

pub fn pop_input(&mut self) {
self.input.pop();
}

pub fn append_input(&mut self, new_input: char) {
self.input.push(new_input);
pub fn new_input_key(&mut self, key: Input) {
self.input.input(key);
}

pub fn scroll_up(&mut self) {
Expand Down
47 changes: 15 additions & 32 deletions src/ui/input_box.rs
Original file line number Diff line number Diff line change
@@ -1,53 +1,36 @@
use ratatui::{
prelude::*,
widgets::{Block, Borders, Paragraph},
widgets::{Block, Borders},
};
use tui_textarea::TextArea;

#[derive(Default)]
pub struct InputBox {
current_text: String,
pub struct InputBox<'a> {
textarea: TextArea<'a>,
}

impl InputBox {
pub fn new(initial_message: &str) -> InputBox {
InputBox {
current_text: initial_message.to_string(),
}
impl<'a> InputBox<'a> {
pub fn new(initial_message: &str) -> InputBox<'a> {
let mut textarea = TextArea::new(vec![initial_message.into()]);
textarea.set_block(Block::default().borders(Borders::TOP));
InputBox { textarea }
}

pub fn render_area(&self, frame: &mut Frame, area: Rect) {
frame.render_widget(self, area);
frame.render_widget(&self.textarea, area);
}
}

impl Widget for &InputBox {
fn render(self, area: Rect, buf: &mut Buffer) {
let text: Vec<Line> = textwrap::wrap(
("> ".to_string() + &self.current_text).as_str(),
area.width as usize,
)
.into_iter()
.map(std::borrow::Cow::into_owned)
.map(Line::from)
.collect();
Paragraph::new(text)
.block(Block::default().borders(Borders::TOP))
.style(Style::new().white().on_black())
.alignment(Alignment::Left)
.render(area, buf);
}
}

impl std::ops::Deref for InputBox {
type Target = String;
impl<'a> std::ops::Deref for InputBox<'a> {
type Target = TextArea<'a>;

fn deref(&self) -> &Self::Target {
&self.current_text
&self.textarea
}
}

impl std::ops::DerefMut for InputBox {
impl<'a> std::ops::DerefMut for InputBox<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.current_text
&mut self.textarea
}
}
54 changes: 36 additions & 18 deletions src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ use color_eyre::{
use crossterm::{
event::{
poll, read, DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste,
EnableMouseCapture, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseEventKind,
EnableMouseCapture, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers,
KeyboardEnhancementFlags, MouseEventKind, PopKeyboardEnhancementFlags,
PushKeyboardEnhancementFlags,
},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::prelude::*;
use tracing::error;
use tui_textarea::{Input, Key};

pub fn install_hooks() -> eyre::Result<()> {
let (panic_hook, eyre_hook) = HookBuilder::default()
Expand Down Expand Up @@ -90,6 +93,17 @@ pub fn init() -> eyre::Result<Tui> {

enable_raw_mode()?;
execute!(stdout(), EnterAlternateScreen)?;
if execute!(
stdout(),
PushKeyboardEnhancementFlags(
KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
| KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES
)
)
.is_err()
{
log::warn!("Consider using a Terminal that supports KeyboardEnhancementFlags.");
}
if config::get().get_enable_mouse() {
execute!(stdout(), EnableMouseCapture)?;
}
Expand All @@ -111,6 +125,8 @@ pub fn restore() -> eyre::Result<()> {
if config::get().get_enable_mouse() {
execute!(stdout(), DisableMouseCapture)?;
}
//proceed here regardless of error, since this will fail if the terminal doesnt support this.
let _ = execute!(stdout(), PopKeyboardEnhancementFlags);
execute!(stdout(), LeaveAlternateScreen)?;
disable_raw_mode()?;
Ok(())
Expand Down Expand Up @@ -166,8 +182,8 @@ async fn process_event(
// It's guaranteed that `read` won't block, because `poll` returned
// `Ok(true)`.
match event {
Event::Key(key) if key.kind != KeyEventKind::Release => {
log::debug!("Processing key event {:?}", key.code);
Event::Key(key) => {
log::debug!("Processing key event {:?}", key);
match app.current_screen {
CurrentScreen::Helping => handle_key_in_help(key, app),
CurrentScreen::Reading => handle_key_in_reading(key, app).await?,
Expand All @@ -176,7 +192,9 @@ async fn process_event(
return value;
}
}
CurrentScreen::Editing => handle_key_in_editing(key, app).await?,
CurrentScreen::Editing => {
handle_key_in_editing(Input::from(event.clone()), app).await?;
}
CurrentScreen::Opening => handle_key_in_opening(key, app).await?,
}
}
Expand Down Expand Up @@ -217,22 +235,22 @@ async fn handle_key_in_opening(
}

async fn handle_key_in_editing(
key: KeyEvent,
key: Input,
app: &mut App<'_>,
) -> Result<(), Box<dyn std::error::Error>> {
if key.kind == KeyEventKind::Press {
match key.code {
KeyCode::Enter => {
// SEND MEssage
app.current_screen = CurrentScreen::Reading;
app.send_message().await?;
}
KeyCode::Backspace => app.pop_input(),
KeyCode::Esc => app.current_screen = CurrentScreen::Reading,
KeyCode::Char(value) => app.append_input(value),
_ => (),
};
}
match key {
Input { key: Key::Esc, .. } => app.current_screen = CurrentScreen::Reading,
Input {
key: Key::Enter,
shift: false,
..
} => {
// SEND MEssage
app.current_screen = CurrentScreen::Reading;
app.send_message().await?;
}
_ => app.new_input_key(key),
};

Ok(())
}
Expand Down

0 comments on commit a5121f6

Please sign in to comment.