chiark / gitweb /
End-to-end PoC: we format and display a sample toot!
authorSimon Tatham <anakin@pobox.com>
Tue, 26 Dec 2023 22:08:04 +0000 (22:08 +0000)
committerSimon Tatham <anakin@pobox.com>
Tue, 26 Dec 2023 22:08:04 +0000 (22:08 +0000)
src/main.rs
src/types.rs

index 76c85d8bc5829ca9e990793cbbf00d197a3f5a32..538f1b8247d0090b482e6d4951a8cc815327f75d 100644 (file)
@@ -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),
+
+        // <code> tags
+        'c' => Style::default().fg(Color::Yellow),
+
+        // #hashtags
+        '#' => Style::default().fg(Color::Cyan),
+
+        // @mentions of a user
+        '@' => Style::default().fg(Color::Green),
+
+        // <em> tags
+        '_' => Style::default().add_modifier(Modifier::UNDERLINED),
+
+        // <strong> 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<Paragraph>) -> 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 &paras {
+                    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<String, String>) {
-        dbg!("start", tag, attrs);
-    }
-    fn end_tag(&mut self, tag: &str, attrs: &HashMap<String, String>) {
-        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(())
 }
index b5c357f2fb40c4eab81afa42bed5dfea64af9bfc..0b7f2df7c5c5edd298f2f0b0835a90970a26c8d4 100644 (file)
@@ -113,5 +113,5 @@ pub struct Status {
     pub muted: Option<bool>,
     pub bookmarked: Option<bool>,
     pub pinned: Option<bool>,
-    pub filtered: Option<bool>,
+    // pub filtered: Option<Vec<FilterResult>>,
 }