diff --git a/src/client.rs b/src/client.rs index 126c069..516af6c 100644 --- a/src/client.rs +++ b/src/client.rs @@ -35,8 +35,9 @@ use ncurses::*; mod terminal; use terminal::{ - format_chat_msg, format_chat_msg_fmt, ChatClosedCommand, PrintChatCommand, PrintCommand, - SetChatMessagesCommand, TextStyle, WindowCommand, WindowManager, WindowPipe, + format_chat_msg, format_chat_msg_fmt, AppCurrentState, ChatClosedCommand, PrintChatCommand, + PrintCommand, SetAppStateCommand, SetChatMessagesCommand, TextStyle, WindowCommand, + WindowManager, WindowPipe, }; #[derive(Parser)] @@ -101,6 +102,14 @@ async fn chat_reset_messages() { })) .await; } +async fn set_app_state(state: AppCurrentState) { + PIPE.get() + .unwrap() + .send(WindowCommand::SetAppState(SetAppStateCommand { + state: AppCurrentState::Commands, + })) + .await; +} async fn println_chat_message(chatid: String, message: String) { PIPE.get() .unwrap() @@ -336,11 +345,8 @@ async fn cb_closed(public_key: String, _session_id: String) { )) .await; - // Spawn a new process - tokio::spawn(async move { - tokio::time::sleep(Duration::from_secs(1)).await; - chat_reset_messages().await; - }); + set_app_state(AppCurrentState::Commands).await; + chat_reset_messages().await; } _ => {} } @@ -569,6 +575,8 @@ async fn launch_terminal_program( .await; keep_running = false; + + terminate(session_tx.clone()).await; continue; } let input = input.unwrap(); diff --git a/src/session/middleware/mod.rs b/src/session/middleware/mod.rs index 637ad61..6f4f8a2 100644 --- a/src/session/middleware/mod.rs +++ b/src/session/middleware/mod.rs @@ -93,9 +93,7 @@ impl ZenohHandler { impl MessagebleTopicAsync for ZenohHandler { async fn read_message(&self, topic: &str) -> Result { let s = self.session.lock().await; - let subscriber = s.declare_subscriber(topic) - .await - .unwrap(); + let subscriber = s.declare_subscriber(topic).await.unwrap(); match subscriber.recv_async().await { Ok(incoming) => { let incoming = incoming @@ -121,9 +119,7 @@ impl MessagebleTopicAsync for ZenohHandler { let message = message.serialize().unwrap(); let s = self.session.lock().await; - s.put(topic, message) - .await - .unwrap(); + s.put(topic, message).await.unwrap(); Ok(()) } } @@ -136,9 +132,7 @@ impl MessagebleTopicAsyncPublishReads for ZenohHandler { ) -> Result<(), MessagingError> { let mut topic_in = topic.to_string(); let s = self.session.lock().await; - let subscriber = s.declare_subscriber(topic) - .await - .unwrap(); + let subscriber = s.declare_subscriber(topic).await.unwrap(); loop { let msg = match subscriber.recv_async().await { Ok(incoming) => { @@ -180,13 +174,10 @@ impl MessagebleTopicAsyncReadTimeout for ZenohHandler { timeout_duration: std::time::Duration, ) -> Result { let s = self.session.lock().await; - let subscriber = s.declare_subscriber(topic) - .await - .unwrap(); + let subscriber = s.declare_subscriber(topic).await.unwrap(); match timeout(timeout_duration, subscriber.recv_async()).await { Ok(incoming) => { - let incoming = incoming - .unwrap(); + let incoming = incoming.unwrap(); let incoming = incoming .payload() .try_to_string() @@ -207,9 +198,7 @@ impl MessagebleTopicAsyncReadTimeout for ZenohHandler { ) -> Result, MessagingError> { let mut messages = Vec::new(); let s = self.session.lock().await; - let subscriber = s.declare_subscriber(topic) - .await - .unwrap(); + let subscriber = s.declare_subscriber(topic).await.unwrap(); let end_time = Instant::now() + timeout_duration; diff --git a/src/session/mod.rs b/src/session/mod.rs index 0c81b43..c2d156d 100644 --- a/src/session/mod.rs +++ b/src/session/mod.rs @@ -312,8 +312,7 @@ impl Session { if session_init_ok_msg.is_some() { let zc = self.middleware_config.clone(); let zenoh_config = Config::from_file(zc).unwrap(); - let zenoh_session = - Arc::new(Mutex::new(zenoh::open(zenoh_config).await.unwrap())); + let zenoh_session = Arc::new(Mutex::new(zenoh::open(zenoh_config).await.unwrap())); let handler = ZenohHandler::new(zenoh_session); let msg = session_init_ok_msg.unwrap().clone(); let _ = self diff --git a/src/terminal.rs b/src/terminal.rs index cf629b0..dee02d2 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -60,6 +60,10 @@ pub struct NewWindowCommand { pub struct SetChatMessagesCommand { pub chat_messages: Vec, } +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct SetAppStateCommand { + pub state: AppCurrentState, +} #[derive(Serialize, Deserialize, Clone, Debug)] pub enum WindowCommand { @@ -70,6 +74,7 @@ pub enum WindowCommand { New(NewWindowCommand), ChatClosed(ChatClosedCommand), SetChatMessages(SetChatMessagesCommand), + SetAppState(SetAppStateCommand), Init(), Shutdown(), } @@ -166,6 +171,13 @@ impl WindowManager { } } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub enum AppCurrentState { + Commands, + Chat, + Request, +} + #[derive(Clone, Debug, Deserialize, Serialize)] pub enum TextStyle { Italic, @@ -188,6 +200,7 @@ struct AppState { pub horizontal_position_commands: usize, pub scrollstate_chat: ScrollbarState, pub scrollstate_commands: ScrollbarState, + pub app_current_state: AppCurrentState, } /// App holds the state of the application @@ -219,6 +232,7 @@ impl App { horizontal_position_commands: 0, scrollstate_chat: ScrollbarState::default(), scrollstate_commands: ScrollbarState::default(), + app_current_state: AppCurrentState::Commands, })), should_run: Arc::new(Mutex::new(true)), tx: tx, @@ -239,7 +253,7 @@ impl App { } async fn is_in_chat_mode(&self) -> bool { - self.state.lock().await.chat_messages.len() > 0 + self.state.lock().await.app_current_state == AppCurrentState::Chat } async fn move_cursor_left(&mut self, len: usize) { @@ -427,9 +441,17 @@ impl App { .scrollstate_chat .content_length(state.chat_messages.len()); } - async fn write_new_chat_message(&mut self, chatid: String, message: String) { + async fn set_app_state(&mut self, state: AppCurrentState) { + self.state.lock().await.app_current_state = state; + } + async fn set_chatid(&mut self, chatid: String) { let mut state = self.state.lock().await; state.chatid = chatid; + } + + async fn write_new_chat_message(&mut self, chatid: String, message: String) { + let mut state = self.state.lock().await; + state.chatid = format!("Chatting with {}", chatid); state.chat_messages.push(message); state.scrollstate_chat = state .scrollstate_chat @@ -478,14 +500,18 @@ impl App { } WindowCommand::ChatClosed(cmd) => { app.write_new_message(cmd.message, TextStyle::Bold).await; - app.clear_chat().await; + app.set_app_state(AppCurrentState::Commands).await; } WindowCommand::PrintChat(cmd) => { + app.set_app_state(AppCurrentState::Chat).await; app.write_new_chat_message(cmd.chatid, cmd.message).await; } WindowCommand::SetChatMessages(cmd) => { app.set_chat_messages(cmd.chat_messages).await; } + WindowCommand::SetAppState(cmd) => { + app.set_app_state(cmd.state).await; + } _ => {} }, Ok(Err(_)) => { @@ -561,7 +587,7 @@ impl App { TextStyle::Bold, ) .await; - app.clear_chat().await; + app.set_chatid("Closing this chat..".to_string()).await; let _ = tx .send(Some(WindowCommand::ChatClosed(ChatClosedCommand { message: "Closing the chat".to_string(), @@ -603,195 +629,212 @@ impl App { h3.await.unwrap(); } - fn draw(frame: &mut Frame, state: &mut AppState) { + fn draw_commands_section(frame: &mut Frame, state: &mut AppState) { let messages = &state.messages; let input = &state.input; let input_mode = &state.input_mode; let character_index = state.character_index; let chat_messages = &state.chat_messages; let chatid = &state.chatid; + let vertical = Layout::vertical([ + Constraint::Length(1), + Constraint::Length(3), + Constraint::Min(1), + ]); + let [help_area, input_area, messages_area] = vertical.areas(frame.area()); + if messages.len() > 0 { + } else { + } + let (msg, style) = match input_mode { + InputMode::Normal => ( + vec![ + "Press ".into(), + "q".bold(), + " to exit, ".into(), + "Space".bold(), + " to write".into(), + ], + Style::default().add_modifier(Modifier::RAPID_BLINK), + ), + InputMode::Editing => ( + vec![ + "Press ".into(), + "Esc".bold(), + " to stop editing, ".into(), + "Enter".bold(), + " to submit the message".into(), + ], + Style::default(), + ), + }; + let text = Text::from(Line::from(msg)).patch_style(style); + let help_message = Paragraph::new(text); + frame.render_widget(help_message, help_area); + + let input = Paragraph::new(input.as_str()) + .style(match input_mode { + InputMode::Normal => Style::default(), + InputMode::Editing => Style::default().fg(Color::Yellow), + }) + .block(Block::bordered().title("Input")); + frame.render_widget(input, input_area); + match input_mode { + // Hide the cursor. `Frame` does this by default, so we don't need to do anything here + InputMode::Normal => {} + + // Make the cursor visible and ask ratatui to put it at the specified coordinates after + // rendering + #[allow(clippy::cast_possible_truncation)] + InputMode::Editing => frame.set_cursor_position(Position::new( + // Draw the cursor at the current position in the input field. + // This position is can be controlled via the left and right arrow key + input_area.x + character_index as u16 + 1, + // Move one line down, from the border to the input line + input_area.y + 1, + )), + } - if chat_messages.len() == 0 { - let vertical = Layout::vertical([ - Constraint::Length(1), - Constraint::Length(3), - Constraint::Min(1), - ]); - let [help_area, input_area, messages_area] = vertical.areas(frame.area()); - if messages.len() > 0 { - } else { - } - let (msg, style) = match input_mode { - InputMode::Normal => ( - vec![ - "Press ".into(), - "q".bold(), - " to exit, ".into(), - "Space".bold(), - " to write".into(), - ], - Style::default().add_modifier(Modifier::RAPID_BLINK), - ), - InputMode::Editing => ( - vec![ - "Press ".into(), - "Esc".bold(), - " to stop editing, ".into(), - "Enter".bold(), - " to submit the message".into(), - ], - Style::default(), - ), - }; - let text = Text::from(Line::from(msg)).patch_style(style); - let help_message = Paragraph::new(text); - frame.render_widget(help_message, help_area); - - let input = Paragraph::new(input.as_str()) - .style(match input_mode { - InputMode::Normal => Style::default(), - InputMode::Editing => Style::default().fg(Color::Yellow), - }) - .block(Block::bordered().title("Input")); - frame.render_widget(input, input_area); - match input_mode { - // Hide the cursor. `Frame` does this by default, so we don't need to do anything here - InputMode::Normal => {} - - // Make the cursor visible and ask ratatui to put it at the specified coordinates after - // rendering - #[allow(clippy::cast_possible_truncation)] - InputMode::Editing => frame.set_cursor_position(Position::new( - // Draw the cursor at the current position in the input field. - // This position is can be controlled via the left and right arrow key - input_area.x + character_index as u16 + 1, - // Move one line down, from the border to the input line - input_area.y + 1, - )), - } - - let messages: Vec = messages - .iter() - .map(|m| { - let message = &m.0; - let style = &m.1; - let mut s = Span::raw(format!("{message}")); - match style { - TextStyle::Normal => {} - TextStyle::Italic => { - s = s.italic(); - } - TextStyle::Bold => { - s = s.bold(); - } - TextStyle::Blinking => { - s = s.add_modifier(Modifier::RAPID_BLINK); - } + let messages: Vec = messages + .iter() + .map(|m| { + let message = &m.0; + let style = &m.1; + let mut s = Span::raw(format!("{message}")); + match style { + TextStyle::Normal => {} + TextStyle::Italic => { + s = s.italic(); } - Line::from(s) - }) - .collect(); - let messages = Paragraph::new(messages) - .block(Block::bordered().title("Commands")) - .scroll(( - state.vertical_position_commands.try_into().unwrap(), - state.horizontal_position_chat.try_into().unwrap(), - )); - let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight) - .begin_symbol(Some("↑")) - .end_symbol(Some("↓")); - frame.render_widget(messages, messages_area); - frame.render_stateful_widget( - scrollbar, - messages_area.inner(Margin { - // using an inner vertical margin of 1 unit makes the scrollbar inside the block - vertical: 1, - horizontal: 0, - }), - &mut state.scrollstate_commands, - ); - } else { - let vertical = Layout::vertical([ - Constraint::Min(1), - Constraint::Length(1), - Constraint::Length(3), - ]); - let [chat_area, help_area, input_area] = vertical.areas(frame.area()); - let (msg, style) = match input_mode { - InputMode::Normal => ( - vec![ - "Press ".into(), - "q".bold(), - " to exit, ".into(), - "e".bold(), - " to write".into(), - ], - Style::default().add_modifier(Modifier::RAPID_BLINK), - ), - InputMode::Editing => ( - vec![ - "Press ".into(), - "Esc".bold(), - " to stop editing, ".into(), - "Enter".bold(), - " to submit the message".into(), - ], - Style::default(), - ), - }; - let text = Text::from(Line::from(msg)).patch_style(style); - let help_message = Paragraph::new(text); - frame.render_widget(help_message, help_area); - - let input = Paragraph::new(input.as_str()) - .style(match input_mode { - InputMode::Normal => Style::default(), - InputMode::Editing => Style::default().fg(Color::Yellow), - }) - .block(Block::bordered().title("Input")); - frame.render_widget(input, input_area); - match input_mode { - // Hide the cursor. `Frame` does this by default, so we don't need to do anything here - InputMode::Normal => {} - - // Make the cursor visible and ask ratatui to put it at the specified coordinates after - // rendering - #[allow(clippy::cast_possible_truncation)] - InputMode::Editing => frame.set_cursor_position(Position::new( - // Draw the cursor at the current position in the input field. - // This position is can be controlled via the left and right arrow key - input_area.x + character_index as u16 + 1, - // Move one line down, from the border to the input line - input_area.y + 1, - )), - } + TextStyle::Bold => { + s = s.bold(); + } + TextStyle::Blinking => { + s = s.add_modifier(Modifier::RAPID_BLINK); + } + } + Line::from(s) + }) + .collect(); + let messages = Paragraph::new(messages) + .block(Block::bordered().title("Commands")) + .scroll(( + state.vertical_position_commands.try_into().unwrap(), + state.horizontal_position_chat.try_into().unwrap(), + )); + let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight) + .begin_symbol(Some("↑")) + .end_symbol(Some("↓")); + frame.render_widget(messages, messages_area); + frame.render_stateful_widget( + scrollbar, + messages_area.inner(Margin { + // using an inner vertical margin of 1 unit makes the scrollbar inside the block + vertical: 1, + horizontal: 0, + }), + &mut state.scrollstate_commands, + ); + } + + fn draw_commands_chat(frame: &mut Frame, state: &mut AppState) { + let messages = &state.messages; + let input = &state.input; + let input_mode = &state.input_mode; + let character_index = state.character_index; + let chat_messages = &state.chat_messages; + let chatid = &state.chatid; + let vertical = Layout::vertical([ + Constraint::Min(1), + Constraint::Length(1), + Constraint::Length(3), + ]); + let [chat_area, help_area, input_area] = vertical.areas(frame.area()); + let (msg, style) = match input_mode { + InputMode::Normal => ( + vec![ + "Press ".into(), + "q".bold(), + " to exit, ".into(), + "e".bold(), + " to write".into(), + ], + Style::default().add_modifier(Modifier::RAPID_BLINK), + ), + InputMode::Editing => ( + vec![ + "Press ".into(), + "Esc".bold(), + " to stop editing, ".into(), + "Enter".bold(), + " to submit the message".into(), + ], + Style::default(), + ), + }; + let text = Text::from(Line::from(msg)).patch_style(style); + let help_message = Paragraph::new(text); + frame.render_widget(help_message, help_area); + + let input = Paragraph::new(input.as_str()) + .style(match input_mode { + InputMode::Normal => Style::default(), + InputMode::Editing => Style::default().fg(Color::Yellow), + }) + .block(Block::bordered().title("Input")); + frame.render_widget(input, input_area); + match input_mode { + // Hide the cursor. `Frame` does this by default, so we don't need to do anything here + InputMode::Normal => {} + + // Make the cursor visible and ask ratatui to put it at the specified coordinates after + // rendering + #[allow(clippy::cast_possible_truncation)] + InputMode::Editing => frame.set_cursor_position(Position::new( + // Draw the cursor at the current position in the input field. + // This position is can be controlled via the left and right arrow key + input_area.x + character_index as u16 + 1, + // Move one line down, from the border to the input line + input_area.y + 1, + )), + } - let messages: Vec = chat_messages - .iter() - .map(|m| Line::from(Span::raw(format!("{m}")))) - .collect(); - let messages = Paragraph::new(messages) - .block(Block::bordered().title(format!("Chat with {}", chatid))) - .scroll(( - state.vertical_position_chat.try_into().unwrap(), - state.horizontal_position_chat.try_into().unwrap(), - )); - - let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight) - .begin_symbol(Some("↑")) - .end_symbol(Some("↓")); - - frame.render_widget(messages, chat_area); - - frame.render_stateful_widget( - scrollbar, - chat_area.inner(Margin { - // using an inner vertical margin of 1 unit makes the scrollbar inside the block - vertical: 1, - horizontal: 0, - }), - &mut state.scrollstate_chat, - ); + let messages: Vec = chat_messages + .iter() + .map(|m| Line::from(Span::raw(format!("{m}")))) + .collect(); + let messages = Paragraph::new(messages) + .block(Block::bordered().title(format!("{}", chatid))) + .scroll(( + state.vertical_position_chat.try_into().unwrap(), + state.horizontal_position_chat.try_into().unwrap(), + )); + + let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight) + .begin_symbol(Some("↑")) + .end_symbol(Some("↓")); + + frame.render_widget(messages, chat_area); + + frame.render_stateful_widget( + scrollbar, + chat_area.inner(Margin { + // using an inner vertical margin of 1 unit makes the scrollbar inside the block + vertical: 1, + horizontal: 0, + }), + &mut state.scrollstate_chat, + ); + } + + fn draw(frame: &mut Frame, state: &mut AppState) { + match state.app_current_state { + AppCurrentState::Commands => { + Self::draw_commands_section(frame, state); + } + AppCurrentState::Chat => { + Self::draw_commands_chat(frame, state); + } + AppCurrentState::Request => {} } } }