diff --git a/src/main.rs b/src/main.rs index 5d6b8b2..db5eadf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,9 @@ use cursive::align::HAlign; -use cursive::event::EventResult; use cursive::traits::*; use cursive::view::Scrollable; -use cursive::views::{Dialog, EditView, OnEventView, Panel, SelectView, TextView}; +use cursive::views::{Dialog, EditView, Panel, SelectView}; +use cursive::utils::markup::StyledString; +use cursive::theme::Effect; use cursive::Cursive; extern crate native_tls; @@ -20,25 +21,63 @@ use std::str; use std::str::FromStr; use url::Url; -struct Status { +#[derive(Debug)] +struct Header { code: i8, - meta: String, + meta: String } -impl FromStr for Status { +#[derive(Debug)] +struct Link { + url: String, + label: String +} + +impl FromStr for Header { + type Err = std::string::ParseError; + + // Parses a string into an instance of 'Header' + fn from_str(line: &str) -> Result { + let text = format!("{}", line); + let link_regexp = Regex::new(r"^(\d*)\t(.*)$").unwrap(); + + let caps = link_regexp.captures(&text).unwrap(); + let code_str = caps.get(1).map_or("", |m| m.as_str()); + let meta_str = caps.get(2).map_or("", |m| m.as_str()); + + let code = code_str.parse::().unwrap(); + let meta = meta_str.to_string(); + + Ok(Header {code, meta}) + } +} + +impl FromStr for Link { type Err = std::string::ParseError; - // Parses a string into an instance of 'Status' - fn from_str(status_line: &str) -> Result { - let mut splits = status_line.split("\t"); - let code: i8 = splits.nth(1).unwrap().parse().unwrap(); - let meta: String = String::from(splits.nth(2).unwrap()); + // Parses a string into an instance of 'Link' + fn from_str(line: &str) -> Result { + let text = format!("{}", line); + let link_regexp = Regex::new(r"^=>\s(\S*)\s*(.*)?$").unwrap(); + + let caps = link_regexp.captures(&text).unwrap(); + let url_str = caps.get(1).map_or("", |m| m.as_str()); + let label_str = caps.get(2).map_or("", |m| m.as_str()); + + let url = url_str.to_string(); + let label = if label_str.is_empty() { + url_str.to_string() + } else { + label_str.to_string() + }; - Ok(Status {code, meta}) + Ok(Link {url, label}) } } const HELP: &'static str = " +Welcome to Asuka Gemini browser! + Press g to visit an URL Press b to go back Press q to exit @@ -54,16 +93,6 @@ fn main() { select.add_all_str(HELP.lines()); select.set_on_submit(follow_link); - // let select_view = OnEventView::new(select) - // .on_pre_event_inner('k', |s, _| { - // s.select_up(1); - // Some(EventResult::Consumed(None)) - // }) - // .on_pre_event_inner('j', |s, _| { - // s.select_down(1); - // Some(EventResult::Consumed(None)) - // }); - siv.add_fullscreen_layer( Dialog::around(Panel::new( select.with_id("main").scrollable().full_screen() @@ -74,11 +103,6 @@ fn main() { .with_id("container") ); - // Show a welcome popup - // siv.add_layer(Dialog::info( - // "Welcome to Asuka, the first ncurses Gemini browser!\n", - // )); - // We can quit by pressing q siv.add_global_callback('q', |s| s.quit()); // pressing g prompt for an URL @@ -97,12 +121,23 @@ fn prompt_for_url(s: &mut Cursive) { .padding((1, 1, 1, 0)) .content( EditView::new() - .on_submit(visit_url) + .on_submit(goto_url) .fixed_width(20) ).with_id("url_popup") ); } +fn goto_url(s: &mut Cursive, url: &str) { + // Prepend gemini scheme if needed + let url_s = if url.starts_with("gemini://") { + url.to_owned() + } else { + format!("gemini://{}", url) + }; + + visit_url(s, &url_s) +} + fn visit_url(s: &mut Cursive, url_s: &str) { // Close URL popup if any if s.find_id::("url_popup").is_some() { @@ -113,13 +148,7 @@ fn visit_url(s: &mut Cursive, url_s: &str) { Ok(url) => { match get_data(url) { Ok(new_content) => { - let mut main_view = s.find_id::("main").unwrap(); - let mut container = s.find_id::("container").unwrap(); - - container.set_title(url_s); - main_view.clear(); - main_view.add_all_str(new_content.lines()); - main_view.set_on_submit(follow_link); + draw_content(s, url_s, new_content); } Err(msg) => { s.add_layer(Dialog::info(msg)); @@ -132,21 +161,8 @@ fn visit_url(s: &mut Cursive, url_s: &str) { } } -fn follow_link(s: &mut Cursive, line: &str) { - let text = format!("{}", line); - let link_regexp = Regex::new(r"^=>\s(\S*)(.*)?$").unwrap(); - - if link_regexp.is_match(&text) { - let caps = link_regexp.captures(&text).unwrap(); - let url = caps.get(1).map_or("", |m| m.as_str()); - let next_url = parse_link(url); - visit_url(s, next_url.expect("Not an URL").as_str()) - } else { - () - } -} - fn parse_link(url: &str) -> Result { + // Creates an absolute link if needed match get_last_host() { Some(host) => { let url_s = if url.starts_with("gemini://") { @@ -171,6 +187,60 @@ fn parse_link(url: &str) -> Result { } } +fn draw_content(s: &mut Cursive, url: &str, content: String) { + let mut main_view = s.find_id::("main").unwrap(); + let mut container = s.find_id::("container").unwrap(); + + container.set_title(url); + main_view.clear(); + + for line in content.lines() { + if is_header(line) { + // let _header = Header::from_str(line); + } else if is_link(line) { + let link = Link::from_str(line).unwrap(); + let mut formatted = StyledString::new(); + formatted.append(StyledString::styled( + link.label, + Effect::Underline, + )); + + main_view.add_item(formatted, link.url) + } else { + main_view.add_item(line, "0".to_owned()) + } + } + + main_view.set_on_submit(follow_link); +} + +fn is_header(line: &str) -> bool { + let text = format!("{}", line); + let header_regexp = Regex::new(r"^(\d*)\t(\S*)$").unwrap(); + + header_regexp.is_match(&text) +} + +fn is_link(line: &str) -> bool { + let text = format!("{}", line); + let link_regexp = Regex::new(r"^=>\s(\S*)(.*)?$").unwrap(); + + link_regexp.is_match(&text) +} + +fn is_gemini_link(line: &str) -> bool { + line != "0" +} + +fn follow_link (s: &mut Cursive, line: &str) { + if is_gemini_link(&line) { + let next_url = parse_link(&line); + visit_url(s, next_url.expect("Not an URL").as_str()) + } else { + () + } +} + fn get_data(url: url::Url) -> Result { let host = url.host_str().unwrap(); let path = url.path(); @@ -237,6 +307,7 @@ fn get_previous_url() -> Option { let lines_count = content.lines().count(); if lines_count > 1 { + // Return before last line Some(content.lines().nth(lines_count - 2).unwrap().to_owned()) } else { None