Skip to content

Commit

Permalink
fix: Window resize event, crashes on small windows
Browse files Browse the repository at this point in the history
  • Loading branch information
Dustin Blackman committed Dec 7, 2023
1 parent 02c1285 commit 3c981fd
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 42 deletions.
30 changes: 29 additions & 1 deletion src/application/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crossterm::terminal::EnterAlternateScreen;
use crossterm::terminal::LeaveAlternateScreen;
use ratatui::backend::CrosstermBackend;
use ratatui::prelude::*;
use ratatui::widgets::Paragraph;
use ratatui::widgets::Scrollbar;
use ratatui::widgets::ScrollbarOrientation;
use ratatui::Terminal;
Expand All @@ -30,8 +31,27 @@ use crate::domain::models::SlashCommand;
use crate::domain::models::TextArea;
use crate::domain::services::events::EventsService;
use crate::domain::services::AppState;
use crate::domain::services::Bubble;
use crate::infrastructure::editors::EditorManager;

/// Verifies that the current window size is large enough to handle the bare
/// minimum width that includes the model name, username, bubbles, and padding.
fn is_line_width_sufficient(line_width: u16) -> bool {
let author_lengths = vec![Author::User, Author::Oatmeal, Author::Model]
.into_iter()
.map(|e| return e.to_string().len())
.max()
.unwrap();

let bubble_style = Bubble::style_confg();
let min_width =
(author_lengths + bubble_style.magic_spacing + bubble_style.border_elements_length) as i32;
let trimmed_line_width =
((line_width as f32 * (1.0 - bubble_style.outer_padding_percentage)).ceil()) as i32;

return trimmed_line_width >= min_width;
}

async fn start_loop<B: Backend>(
terminal: &mut Terminal<B>,
app_state: &mut AppState<'_>,
Expand All @@ -50,6 +70,14 @@ async fn start_loop<B: Backend>(

loop {
terminal.draw(|frame| {
if !is_line_width_sufficient(frame.size().width) {
frame.render_widget(
Paragraph::new("I'm too small, make me bigger!").alignment(Alignment::Left),
frame.size(),
);
return;
}

let textarea_len = (textarea.lines().len() + 3).try_into().unwrap();
let layout = Layout::default()
.direction(Direction::Vertical)
Expand Down Expand Up @@ -180,7 +208,7 @@ async fn start_loop<B: Backend>(
textarea.set_yank_text(text.replace('\r', "\n"));
textarea.paste();
}
Event::UIResize() => {
Event::UITick() => {
continue;
}
Event::UIScrollDown() => {
Expand Down
22 changes: 22 additions & 0 deletions src/domain/models/author.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use serde_derive::Deserialize;
use serde_derive::Serialize;

use crate::config::Config;
use crate::config::ConfigKey;

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Author {
User,
Oatmeal,
Model,
}

impl ToString for Author {
fn to_string(&self) -> String {
match self {
Author::User => return Config::get(ConfigKey::Username),
Author::Oatmeal => return String::from("Oatmeal"),
Author::Model => return Config::get(ConfigKey::Model),
}
}
}
2 changes: 1 addition & 1 deletion src/domain/models/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub enum Event {
KeyboardCTRLR(),
KeyboardEnter(),
KeyboardPaste(String),
UIResize(),
UITick(),
UIScrollDown(),
UIScrollUp(),
UIScrollPageDown(),
Expand Down
21 changes: 1 addition & 20 deletions src/domain/models/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,7 @@ mod tests;
use serde_derive::Deserialize;
use serde_derive::Serialize;

use crate::config::Config;
use crate::config::ConfigKey;

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Author {
User,
Oatmeal,
Model,
}
use super::Author;

#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum MessageType {
Expand All @@ -24,23 +16,13 @@ pub enum MessageType {
pub struct Message {
pub author: Author,
pub text: String,
pub author_formatted: String,
mtype: MessageType,
}

fn formatted_author(author: Author) -> String {
return match author {
Author::User => Config::get(ConfigKey::Username),
Author::Oatmeal => "Oatmeal".to_string(),
Author::Model => Config::get(ConfigKey::Model),
};
}

impl Message {
pub fn new(author: Author, text: &str) -> Message {
return Message {
author: author.clone(),
author_formatted: formatted_author(author),
text: text.to_string().replace('\t', " "),
mtype: MessageType::Normal,
};
Expand All @@ -49,7 +31,6 @@ impl Message {
pub fn new_with_type(author: Author, mtype: MessageType, text: &str) -> Message {
return Message {
author: author.clone(),
author_formatted: formatted_author(author),
text: text.to_string().replace('\t', " "),
mtype,
};
Expand Down
8 changes: 4 additions & 4 deletions src/domain/models/message_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use super::MessageType;
fn it_executes_new() {
let msg = Message::new(Author::Oatmeal, "Hi there!");
assert_eq!(msg.author, Author::Oatmeal);
assert_eq!(msg.author_formatted, "Oatmeal");
assert_eq!(msg.author.to_string(), "Oatmeal");
assert_eq!(msg.text, "Hi there!".to_string());
assert_eq!(msg.mtype, MessageType::Normal);
}
Expand All @@ -17,7 +17,7 @@ fn it_executes_new() {
fn it_executes_new_replacing_tabs() {
let msg = Message::new(Author::Oatmeal, "\t\tHi there!");
assert_eq!(msg.author, Author::Oatmeal);
assert_eq!(msg.author_formatted, "Oatmeal");
assert_eq!(msg.author.to_string(), "Oatmeal");
assert_eq!(msg.text, " Hi there!".to_string());
assert_eq!(msg.mtype, MessageType::Normal);
}
Expand All @@ -26,7 +26,7 @@ fn it_executes_new_replacing_tabs() {
fn it_executes_new_with_type() {
let msg = Message::new_with_type(Author::Oatmeal, MessageType::Error, "It broke!");
assert_eq!(msg.author, Author::Oatmeal);
assert_eq!(msg.author_formatted, "Oatmeal");
assert_eq!(msg.author.to_string(), "Oatmeal");
assert_eq!(msg.text, "It broke!".to_string());
assert_eq!(msg.mtype, MessageType::Error);
}
Expand All @@ -35,7 +35,7 @@ fn it_executes_new_with_type() {
fn it_executes_new_with_type_replacing_tabs() {
let msg = Message::new_with_type(Author::Oatmeal, MessageType::Error, "\t\tIt broke!");
assert_eq!(msg.author, Author::Oatmeal);
assert_eq!(msg.author_formatted, "Oatmeal");
assert_eq!(msg.author.to_string(), "Oatmeal");
assert_eq!(msg.text, " It broke!".to_string());
assert_eq!(msg.mtype, MessageType::Error);
}
Expand Down
2 changes: 2 additions & 0 deletions src/domain/models/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod action;
mod author;
mod backend;
mod editor;
mod event;
Expand All @@ -9,6 +10,7 @@ mod slash_commands;
mod textarea;

pub use action::*;
pub use author::*;
pub use backend::*;
pub use editor::*;
pub use event::*;
Expand Down
2 changes: 1 addition & 1 deletion src/domain/services/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ fn copy_messages(messages: Vec<Message>, tx: &mpsc::UnboundedSender<Event>) -> R
let formatted = messages
.iter()
.map(|message| {
return format!("{}: {}", message.author_formatted, message.text);
return format!("{}: {}", message.author.to_string(), message.text);
})
.collect::<Vec<String>>()
.join("\n\n");
Expand Down
66 changes: 54 additions & 12 deletions src/domain/services/bubble.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,30 @@ pub struct Bubble {
codeblock_counter: usize,
}

pub struct BubbleConfig {
pub magic_spacing: usize,
pub border_elements_length: usize,
pub outer_padding_percentage: f32,
}

fn repeat_from_subtractions(text: &str, subtractions: Vec<usize>) -> String {
let count = subtractions
.into_iter()
.map(|e| {
return i32::try_from(e).unwrap();
})
.reduce(|a, b| {
return a - b;
})
.unwrap();

if count <= 0 {
return "".to_string();
}

return [text].repeat(count.try_into().unwrap()).join("");
}

impl<'a> Bubble {
pub fn new(
message: Message,
Expand All @@ -43,6 +67,17 @@ impl<'a> Bubble {
};
}

pub fn style_confg() -> BubbleConfig {
return BubbleConfig {
// TODO wtf is 8
magic_spacing: 8,
// left border + left padding + (text, not counted) + right padding + right border +
// scrollbar.
border_elements_length: 5,
outer_padding_percentage: 0.04,
};
}

pub fn as_lines(&mut self, theme: &Theme) -> Vec<Line<'a>> {
// Lazy default
let mut highlight = HighlightLines::new(Syntaxes::get("text"), theme);
Expand Down Expand Up @@ -106,7 +141,8 @@ impl<'a> Bubble {
}
}

let bubble_padding = [" "].repeat(self.window_max_width - line_length).join("");
let bubble_padding =
repeat_from_subtractions(" ", vec![self.window_max_width, line_length]);

if self.alignment == BubbleAlignment::Left {
spans.push(Span::from(bubble_padding));
Expand All @@ -123,11 +159,13 @@ impl<'a> Bubble {

fn get_message_lines(&self) -> (Vec<String>, usize) {
// Add a minimum 4% of padding on the side.
let min_bubble_padding_length = ((self.window_max_width as f32 * 0.04).ceil()) as usize;
let min_bubble_padding_length = ((self.window_max_width as f32
* Bubble::style_confg().outer_padding_percentage)
.ceil()) as usize;

// left border + left padding + (text, not counted) + right padding + right
// border + scrollbar. And then minimum bubble padding.
let line_border_width = 5 + min_bubble_padding_length;
// Border elements + minimum bubble padding.
let line_border_width =
Bubble::style_confg().border_elements_length + min_bubble_padding_length;

let message_lines = self
.message
Expand All @@ -141,7 +179,7 @@ impl<'a> Bubble {
.max()
.unwrap();

let username = &self.message.author_formatted;
let username = &self.message.author.to_string();
if max_line_length < username.len() {
max_line_length = username.len();
}
Expand All @@ -155,12 +193,16 @@ impl<'a> Bubble {
let top_left_border = "╭";
let mut top_bar = format!("{top_left_border}{inner_bar}╮");
let bottom_bar = format!("╰{inner_bar}╯");
let bar_bubble_padding = [" "]
// TODO WTF is 8?
.repeat(self.window_max_width - max_line_length - 8)
.join("");
let bar_bubble_padding = repeat_from_subtractions(
" ",
vec![
self.window_max_width,
max_line_length,
Bubble::style_confg().magic_spacing,
],
);

let username = &self.message.author_formatted;
let username = &self.message.author.to_string();

if self.alignment == BubbleAlignment::Left {
let top_replace = ["─"].repeat(username.len()).join("");
Expand Down Expand Up @@ -193,7 +235,7 @@ impl<'a> Bubble {
max_line_length: usize,
mut spans: Vec<Span<'a>>,
) -> (Vec<Span<'a>>, usize) {
let fill = [" "].repeat(max_line_length - line_str.len()).join("");
let fill = repeat_from_subtractions(" ", vec![max_line_length, line_str.len()]);
let line_length = format!("│ {line_str}{fill} │").len();

let mut spans_res = vec![self.highlight_span("│ ".to_string())];
Expand Down
5 changes: 2 additions & 3 deletions src/domain/services/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crossterm::event::Event as CrosstermEvent;
use crossterm::event::EventStream;
use futures::StreamExt;
use tokio::sync::mpsc;
use tokio::time;
use tui_textarea::Input;
use tui_textarea::Key;

Expand All @@ -23,9 +24,6 @@ impl EventsService {

fn handle_crossterm(&self, event: CrosstermEvent) -> Option<Event> {
match event {
CrosstermEvent::Resize(_, _) => {
return Some(Event::UIResize());
}
CrosstermEvent::Paste(text) => {
return Some(Event::KeyboardPaste(text));
}
Expand Down Expand Up @@ -110,6 +108,7 @@ impl EventsService {
Some(Err(_)) => None,
None => None
},
_ = time::sleep(time::Duration::from_millis(500)) => Some(Event::UITick())
};

if let Some(event) = evt {
Expand Down

0 comments on commit 3c981fd

Please sign in to comment.