1use clap::{Parser, Subcommand};
2use crossterm::event::KeyEvent;
3use matrix_sdk::ruma::OwnedUserId;
4use matrix_sdk_ui::room_list_service::Room;
5use ratatui::{prelude::*, widgets::*};
6use style::palette::tailwind;
7use tui_textarea::TextArea;
89#[derive(Debug, Parser)]
10#[command(name = "multiverse", disable_help_flag = true, disable_help_subcommand = true)]
11struct Cli {
12#[command(subcommand)]
13command: Command,
14}
1516#[derive(Debug, Subcommand)]
17pub enum Command {
18 Invite { user_id: OwnedUserId },
19}
2021pub enum MessageOrCommand {
22 Message(String),
23 Command(Command),
24}
2526/// A widget representing a text input to send messages to a room.
27#[derive(Default)]
28pub struct Input {
29/// The text area that will keep track of what the user has input.
30textarea: TextArea<'static>,
31}
3233impl Input {
34/// Create a new empty [`Input`] widget.
35pub fn new() -> Self {
36let textarea = TextArea::default();
3738Self { textarea }
39 }
4041/// Receive a key press event and handle it.
42pub fn handle_key_press(&mut self, event: KeyEvent) {
43self.textarea.input(event);
44 }
4546/// Get the currently input text.
47pub fn get_input(&self) -> Result<MessageOrCommand, clap::Error> {
48let input = self.textarea.lines().join("\n");
4950if let Some(input) = input.strip_prefix("/") {
51let arguments = input.split_whitespace();
5253// Clap expects the first argument to be the binary name, like when a command is
54 // invoked on the command line. Since we aren't a command line, but
55 // still find clap a neat command parser, let's give it what it
56 // expects so we can parse our commands.
57Cli::try_parse_from(std::iter::once("multiverse").chain(arguments))
58 .map(|cli| MessageOrCommand::Command(cli.command))
59 } else {
60Ok(MessageOrCommand::Message(input))
61 }
62 }
6364/// Is the input area empty, returns false if the user hasn't input anything
65 /// yet.
66pub fn is_empty(&self) -> bool {
67self.textarea.is_empty()
68 }
6970/// Clear the text from the input area.
71pub fn clear(&mut self) {
72self.textarea = TextArea::default();
73 }
74}
7576impl<'a> StatefulWidget for &'a mut Input {
77type State = Option<&'a Room>;
7879fn render(self, area: Rect, buf: &mut Buffer, room: &mut Self::State)
80where
81Self: Sized,
82 {
83// Set the placeholder text depending on the encryption state of the room.
84 //
85 // We assume that the encryption state is synced because the RoomListService
86 // sets it as required state.
87if let Some(room) = room.as_deref() {
88let is_encrypted = room.encryption_state().is_encrypted();
8990if is_encrypted {
91self.textarea.set_placeholder_text("(Send an encrypted message)");
92 } else {
93self.textarea.set_placeholder_text("(Send an unencrypted message)");
94 }
95 } else {
96self.textarea.set_placeholder_text("(No room selected)");
97 }
9899// Let's first create a block to set the background color.
100let input_block = Block::new().borders(Borders::NONE).bg(tailwind::BLUE.c400);
101102// Now we set the block and we render the textarea.
103self.textarea.set_block(input_block);
104self.textarea.render(area, buf);
105 }
106}