From d96a7a85bf1bfd64fe2070513a32f8cf5bd2f93a Mon Sep 17 00:00:00 2001 From: Christine Dodrill Date: Sat, 25 Jul 2020 14:05:23 -0400 Subject: [PATCH] majc: render gemini pages --- majc/src/help.gmi | 8 +++-- majc/src/main.rs | 80 +++++++++++++++++++++++++++++++++++++---------- src/gemini.rs | 27 ++++++++++++---- 3 files changed, 90 insertions(+), 25 deletions(-) diff --git a/majc/src/help.gmi b/majc/src/help.gmi index 7c0bdc4..5d9749d 100644 --- a/majc/src/help.gmi +++ b/majc/src/help.gmi @@ -1,22 +1,24 @@ - __ +# majc + +``` + __ _____ _____ |__| ____ / \ \__ \ | |_/ ___\ | Y Y \ / __ \_ | |\ \___ |__|_| /(____ //\__| | \___ > \/ \/ \______| \/ +``` A curses client for Gemini! => gemini://gemini.circumlunar.space/ Gemini homepage ## Homepage - The main homepage for majc is on tulpa.dev: => https://tulpa.dev/cadey/maj ## Important Keys - : opens the menubar c: closes the active window o: prompts to open a URL diff --git a/majc/src/main.rs b/majc/src/main.rs index bdb50e3..b8d96fa 100644 --- a/majc/src/main.rs +++ b/majc/src/main.rs @@ -1,7 +1,9 @@ use cursive::{ event::Key, menu::MenuTree, + theme::{BaseColor, Color, Effect, Style}, traits::*, + utils::markup::StyledString, views::{Dialog, EditView, Panel, ResizedView, TextView}, Cursive, }; @@ -51,7 +53,7 @@ fn help(siv: &mut Cursive) { let content = include_str!("./help.gmi"); siv.add_layer( - Dialog::around(Panel::new(TextView::new(content).scrollable())) + Dialog::around(Panel::new(TextView::new(render_gemini(content)).scrollable())) .title("Help") .dismiss_button("Ok"), ); @@ -94,23 +96,69 @@ fn open(siv: &mut Cursive, url: &str) { } fn show(siv: &mut Cursive, url: &str, resp: Response) { - if resp.status != StatusCode::Success { - siv.add_layer(Dialog::info(format!("{:?}: {}", resp.status, resp.meta))); - return; - } + use StatusCode::*; - match str::from_utf8(&resp.body) { - Ok(content) => { - siv.add_fullscreen_layer(ResizedView::with_full_screen( - Dialog::around(TextView::new(content).scrollable()) - .title(format!("{}: {}", url, resp.meta)), - )); + match resp.status { + Success => { + match str::from_utf8(&resp.body) { + Ok(content) => { + siv.add_fullscreen_layer(ResizedView::with_full_screen( + Dialog::around(TextView::new(render_gemini(content)).scrollable()) + .title(format!("{}: {}", url, resp.meta)), + )); + } + Err(why) => { + siv.add_layer(Dialog::info(format!( + "UTF/8 decoding error for {}: {:?}", + url, why + ))); + } + } } - Err(why) => { - siv.add_layer(Dialog::info(format!( - "UTF/8 decoding error for {}: {:?}", - url, why - ))); + + TemporaryRedirect => { + open(siv, resp.meta.as_str()); + } + + PermanentRedirect => { + open(siv, resp.meta.as_str()); + } + + _ => { + siv.add_layer(Dialog::info(format!("{:?}: {}", resp.status, resp.meta))); + return; } } } + +fn render_gemini(body: &str) -> StyledString { + let doc = maj::gemini::parse(body); + let mut styled = StyledString::new(); + + use maj::gemini::Node::*; + + for node in doc { + match node { + Text(line) => styled.append(StyledString::plain(line)), + Link { to, name } => match name { + None => styled.append(StyledString::styled( + to, + Style::from(Effect::Underline), + )), + Some(name) => styled.append(StyledString::styled( + format!("{}: {}", to, name), + Style::from(Effect::Underline), + )), + }, + Preformatted(data) => styled.append(StyledString::plain(data)), + Heading { level: _, body } => { + styled.append(StyledString::styled(body, Style::from(Effect::Bold))) + } + ListItem(item) => styled.append(StyledString::plain(format!("* {}", item))), + Quote(quote) => styled.append(StyledString::plain(format!("> {}", quote))), + } + styled.append(StyledString::plain("\n")); + } + + styled +} diff --git a/src/gemini.rs b/src/gemini.rs index 96f1d7b..faf88a2 100644 --- a/src/gemini.rs +++ b/src/gemini.rs @@ -93,7 +93,10 @@ pub fn parse(doc: &str) -> Vec { collect_preformatted = !collect_preformatted; if !collect_preformatted { result.push(Node::Preformatted( - String::from_utf8(preformatted_buffer).unwrap(), + String::from_utf8(preformatted_buffer) + .unwrap() + .trim_end() + .to_string(), )); preformatted_buffer = vec![]; } @@ -145,8 +148,14 @@ pub fn parse(doc: &str) -> Vec { let sp = line[2..].split_ascii_whitespace().collect::>(); match sp.len() { - 1 => result.push(Node::Link { to: sp[0].trim().to_string(), name: None }), - _ => result.push(Node::Link { to: sp[0].trim().to_string(), name: Some(sp[1..].join(" ").trim().to_string()) }), + 1 => result.push(Node::Link { + to: sp[0].trim().to_string(), + name: None, + }), + _ => result.push(Node::Link { + to: sp[0].trim().to_string(), + name: Some(sp[1..].join(" ").trim().to_string()), + }), } continue; @@ -194,7 +203,7 @@ mod tests { \n\ Test\n"; let expected: Vec = vec![ - Node::Preformatted("hi there\n".to_string()), + Node::Preformatted("hi there".to_string()), Node::Text(String::new()), Node::Text("Test".to_string()), ]; @@ -227,8 +236,14 @@ mod tests { let _ = pretty_env_logger::try_init(); let msg = "=>/\n=> / Go home"; let expected: Vec = vec![ - Node::Link{to: "/".to_string(), name: None}, - Node::Link{to: "/".to_string(), name: Some("Go home".to_string()) }, + Node::Link { + to: "/".to_string(), + name: None, + }, + Node::Link { + to: "/".to_string(), + name: Some("Go home".to_string()), + }, ]; assert_eq!(expected, parse(msg)); }