From c4d1abde8aa3a7f939e7d8ddd4f7896d025b925d Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 26 Dec 2023 22:08:04 +0000 Subject: [PATCH] End-to-end PoC: we format and display a sample toot! --- src/main.rs | 154 +++++++++++++++++++++++++++++++++++++++------------ src/types.rs | 2 +- 2 files changed, 121 insertions(+), 35 deletions(-) diff --git a/src/main.rs b/src/main.rs index 76c85d8..538f1b8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,9 @@ -use mastodonochrome::types::*; -use mastodonochrome::html::{render}; -use mastodonochrome::html; -use std::collections::HashMap; use std::io::Write; +use unicode_width::UnicodeWidthStr; + +use mastodonochrome::client::Client; +use mastodonochrome::coloured_string::ColouredStringSlice; +use mastodonochrome::text::{parse_html, Paragraph, TextFragment}; use crossterm::{ event::{self, Event, KeyCode, KeyEventKind}, @@ -13,7 +14,7 @@ use crossterm::{ ExecutableCommand, }; use ratatui::{ - prelude::{CrosstermBackend, Terminal}, + prelude::{Buffer, CrosstermBackend, Terminal}, style::{Style, Color, Modifier}, }; use std::io::stdout; @@ -63,8 +64,105 @@ fn streaming() -> Result<(), mastodonochrome::OurError> { } */ +fn ratatui_style_from_colour(colour: char) -> Style { + match colour { + // default + ' ' => Style::default(), + + // message separator line, other than the date + 'S' => Style::default().fg(Color::White).bg(Color::Blue) + .add_modifier(Modifier::REVERSED | Modifier::BOLD), + + // date on a message separator line + 'D' => Style::default().fg(Color::White).bg(Color::Blue) + .add_modifier(Modifier::REVERSED), + + // username in a From line + 'F' => Style::default().fg(Color::Green) + .add_modifier(Modifier::BOLD), + + // username in other headers like Via + 'f' => Style::default().fg(Color::Green), + + // tags + 'c' => Style::default().fg(Color::Yellow), + + // #hashtags + '#' => Style::default().fg(Color::Cyan), + + // @mentions of a user + '@' => Style::default().fg(Color::Green), + + // tags + '_' => Style::default().add_modifier(Modifier::UNDERLINED), + + // tags + 's' => Style::default().add_modifier(Modifier::BOLD), + + // URL + 'u' => Style::default().fg(Color::Blue) + .add_modifier(Modifier::BOLD | Modifier::UNDERLINED), + + // media URL + 'M' => Style::default().fg(Color::Magenta) + .add_modifier(Modifier::BOLD), + + // media description + 'm' => Style::default().fg(Color::Magenta), + + // Mastodonochrome logo in file headers + 'J' => Style::default().fg(Color::Blue).bg(Color::White) + .add_modifier(Modifier::REVERSED | Modifier::BOLD), + + // ~~~~~ underline in file headers + '~' => Style::default().fg(Color::Blue), + + // actual header text in file headers + 'H' => Style::default().fg(Color::Cyan), + + // keypress / keypath names in file headers + 'K' => Style::default().fg(Color::Cyan) + .add_modifier(Modifier::BOLD), + + // keypresses in file status lines + 'k' => Style::default().add_modifier(Modifier::BOLD), + + // separator line between editor header and content + '-' => Style::default().fg(Color::Cyan).bg(Color::Black) + .add_modifier(Modifier::REVERSED), + + // something really boring, like 'none' in place of data + '0' => Style::default().fg(Color::Blue), + + // red nastinesses like blocking/muting in Examine User + 'r' => Style::default().fg(Color::Red), + + // # reverse-video > indicating a truncated too-long line + '>' => Style::default().add_modifier(Modifier::REVERSED), + + // # error report, or by default any unrecognised colour character + '!' | _ => Style::default().fg(Color::Red).bg(Color::Yellow). + add_modifier(Modifier::REVERSED | Modifier::BOLD) + } +} + +fn ratatui_set_string(buf: &mut Buffer, y: usize, x: usize, + text: &ColouredStringSlice<'_>) { + let mut x = x; + if let Ok(y) = y.try_into() { + for (frag, colour) in text.frags() { + if let Ok(x) = x.try_into() { + buf.set_string(x, y, frag, ratatui_style_from_colour(colour)); + } else { + break; + } + x += UnicodeWidthStr::width(frag); + } + } +} + #[allow(unused)] -fn tui() -> std::io::Result<()> { +fn tui(paras: Vec) -> std::io::Result<()> { stdout().execute(EnterAlternateScreen)?; enable_raw_mode()?; let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?; @@ -82,12 +180,17 @@ fn tui() -> std::io::Result<()> { loop { terminal.draw(|frame| { - dbg!(std::time::SystemTime::now()); - let _area = frame.size(); - let buf = frame.buffer_mut(); - buf.reset(); - buf.set_string(2, 2, "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod temp🔭or incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", Style::default().fg(Color::Red).bg(Color::Yellow).add_modifier(Modifier::REVERSED | Modifier::BOLD)); - })?; + let area = frame.size(); + let buf = frame.buffer_mut(); + buf.reset(); + let mut y = 0; + for para in ¶s { + for line in para.render(area.width as usize) { + ratatui_set_string(buf, y, 0, &line.slice()); + y += 1; + } + } + })?; match receiver.recv() { Err(_) => break, @@ -113,27 +216,10 @@ fn tui() -> std::io::Result<()> { Ok(()) } -struct TestReceiver {} - -impl html::Receiver for TestReceiver { - fn start_tag(&mut self, tag: &str, attrs: &HashMap) { - dbg!("start", tag, attrs); - } - fn end_tag(&mut self, tag: &str, attrs: &HashMap) { - dbg!("end", tag, attrs); +fn main() { + let mut client = Client::new().unwrap(); + if let Some(st) = client.status_by_id("111602135142646031") { + let paras = parse_html(&st.content); + tui(paras).unwrap(); } - fn text(&mut self, text: &str) { - dbg!("text", text); - } -} - -fn main() -> std::io::Result<()> { - let client = reqwest::blocking::Client::new(); - let body = client.get( - "https://hachyderm.io/api/v1/statuses/111602135142646031") - .send().unwrap().text().unwrap(); - let st: Status = serde_json::from_str(&body).unwrap(); - let mut t = TestReceiver {}; - render(&st.content, &mut t); - Ok(()) } diff --git a/src/types.rs b/src/types.rs index b5c357f..0b7f2df 100644 --- a/src/types.rs +++ b/src/types.rs @@ -113,5 +113,5 @@ pub struct Status { pub muted: Option, pub bookmarked: Option, pub pinned: Option, - pub filtered: Option, + // pub filtered: Option>, } -- 2.30.2