diff --git a/.chatty.default.toml b/.chatty.default.toml index 962b14c..44b2054 100644 --- a/.chatty.default.toml +++ b/.chatty.default.toml @@ -12,6 +12,8 @@ bubble_width_percent = 60 # If set, the initialize screen will auto close after the inititalization # is done. auto_start = false +# Enable bubble chat or not. Default is true +bubble = true [log] # Default log level is "info" diff --git a/src/app/app.rs b/src/app/app.rs index 5b57bd3..11a5478 100644 --- a/src/app/app.rs +++ b/src/app/app.rs @@ -271,7 +271,7 @@ impl<'a> App<'a> { self.input.paste(); } - Event::KeyboardAltEnter => { + Event::KeyboardNewLine => { if !self.on_waiting_backend(false) { self.input.insert_newline(); } diff --git a/src/app/mod.rs b/src/app/mod.rs index a90b183..13fee14 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -12,10 +12,14 @@ pub use initializer::Initializer; use crossterm::{ cursor, - event::{DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture}, + event::{ + DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste, + EnableFocusChange, EnableMouseCapture, KeyboardEnhancementFlags, + PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags, + }, terminal::{ - EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode, - is_raw_mode_enabled, + Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, + enable_raw_mode, is_raw_mode_enabled, }, }; use eyre::{Context, Result}; @@ -26,9 +30,11 @@ pub fn destruct_terminal() { let _ = disable_raw_mode(); let _ = crossterm::execute!( io::stdout(), - LeaveAlternateScreen, DisableMouseCapture, - DisableBracketedPaste + PopKeyboardEnhancementFlags, + DisableBracketedPaste, + DisableFocusChange, + LeaveAlternateScreen, ); let _ = crossterm::execute!(io::stdout(), cursor::Show); } @@ -41,8 +47,14 @@ pub fn init_terminal() -> Result<()> { crossterm::execute!( stdout, EnterAlternateScreen, + EnableFocusChange, + Clear(ClearType::All), EnableMouseCapture, - EnableBracketedPaste + EnableBracketedPaste, + PushKeyboardEnhancementFlags( + KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES + | KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS + ) )?; Ok(()) } diff --git a/src/app/services/events.rs b/src/app/services/events.rs index da15eeb..0b168c6 100644 --- a/src/app/services/events.rs +++ b/src/app/services/events.rs @@ -22,8 +22,8 @@ impl EventService { }, CrosstermEvent::Key(key_event) => { let input: Input = key_event.into(); - if input.alt && input.key == Key::Enter { - return Some(Event::KeyboardAltEnter); + if input.key == Key::Enter && (input.shift || input.alt || input.ctrl) { + return Some(Event::KeyboardNewLine); } // Map ctrl events diff --git a/src/app/ui/bubble.rs b/src/app/ui/bubble.rs index 4f8dae2..d1a422f 100644 --- a/src/app/ui/bubble.rs +++ b/src/app/ui/bubble.rs @@ -4,9 +4,10 @@ mod tests; use crate::{config, models::Message}; use ratatui::{ - style::{Color, Style}, + style::{Color, Style, Stylize}, text::{Line, Span}, }; +use ratatui_macros::span; use syntect::highlighting::Theme; use unicode_width::UnicodeWidthStr; @@ -71,15 +72,51 @@ impl<'a> Bubble<'_> { } pub fn as_lines(&mut self, theme: &'a Theme) -> Vec> { - let max_line_len = self.get_max_line_length(); + let bubble = config::instance().general.bubble.unwrap_or_default(); + let max_line_len = if bubble { + self.get_max_line_length() + } else { + self.max_width - 5 + }; let lines = utils::build_message_lines(self.message.text(), max_line_len, theme, |line| { - self.format_spans(line.spans, max_line_len) + if bubble { + self.format_spans(line.spans, max_line_len) + } else { + let mut new_line = line.clone(); + new_line + .spans + .insert(0, self.highlighted_span("┃ ".to_string())); + new_line + } }); + if !bubble { + return self.format_inline_message(lines); + } + self.wrap_lines_in_bubble(lines, max_line_len) } + fn format_inline_message(&self, mut lines: Vec>) -> Vec> { + let time = self + .message + .created_at() + .with_timezone(&chrono::Local) + .format("%H:%M %m/%d") + .to_string(); + let padding = self.max_width - time.width() - self.message.issuer_str().width() - 5; + let header = vec![ + self.highlighted_span("┃ ".to_string()), + self.highlighted_span(self.message.issuer_str().to_string()), + span!(" ".repeat(padding).to_string()), + self.highlighted_span(time), + ]; + lines.insert(0, Line::from(header).bold()); + lines.push("".to_string().into()); + lines + } + fn wrap_lines_in_bubble(&self, lines: Vec>, max_line_len: usize) -> Vec> { // Replace top bar ─ with the issuer string let issuer = self.message.issuer_str(); diff --git a/src/app/ui/mod.rs b/src/app/ui/mod.rs index 0d87372..d732763 100644 --- a/src/app/ui/mod.rs +++ b/src/app/ui/mod.rs @@ -15,6 +15,7 @@ pub mod utils; pub use bubble::Bubble; pub use bubble_list::BubbleList; + pub use edit::EditScreen; pub use help::HelpScreen; pub use history::HistoryScreen; diff --git a/src/config/models.rs b/src/config/models.rs index 095ddaa..7c31df4 100644 --- a/src/config/models.rs +++ b/src/config/models.rs @@ -51,6 +51,9 @@ pub struct GeneralConfig { #[serde(default)] pub auto_start: Option, + + #[serde(default = "default_option_true")] + pub bubble: Option, } #[derive(Default, Deserialize, Serialize, Debug, Clone)] @@ -270,6 +273,7 @@ impl Default for GeneralConfig { show_usage: None, bubble_width_percent: 80, auto_start: None, + bubble: default_option_true(), } } } diff --git a/src/models/event.rs b/src/models/event.rs index bd54048..f14c97c 100644 --- a/src/models/event.rs +++ b/src/models/event.rs @@ -20,7 +20,7 @@ pub enum Event { KeyboardCharInput(Input), KeyboardEsc, KeyboardEnter, - KeyboardAltEnter, + KeyboardNewLine, KeyboardCtrlC, KeyboardCtrlR, KeyboardCtrlN, @@ -72,7 +72,7 @@ impl Event { Event::KeyboardCharInput(_) | Event::KeyboardEsc | Event::KeyboardEnter - | Event::KeyboardAltEnter + | Event::KeyboardNewLine | Event::KeyboardCtrlC | Event::KeyboardCtrlR | Event::KeyboardCtrlN