224 lines
6.4 KiB
Rust
224 lines
6.4 KiB
Rust
|
use cursive::{
|
||
|
theme::{Effect, Style},
|
||
|
traits::*,
|
||
|
utils::markup::StyledString,
|
||
|
views::{Dialog, EditView, ResizedView, SelectView, TextView},
|
||
|
Cursive,
|
||
|
};
|
||
|
use maj::{self, Response};
|
||
|
use std::str;
|
||
|
|
||
|
/// The state of the browser.
|
||
|
#[derive(Default, Clone, Debug)]
|
||
|
pub struct State {
|
||
|
url: Option<url::Url>,
|
||
|
history: Vec<url::Url>,
|
||
|
links: Vec<(url::Url, Option<String>)>,
|
||
|
}
|
||
|
|
||
|
pub fn history(siv: &mut Cursive) {
|
||
|
let st = siv.user_data::<State>().unwrap();
|
||
|
|
||
|
let mut select = SelectView::new().autojump();
|
||
|
|
||
|
for entry in st.history.iter() {
|
||
|
select.add_item(
|
||
|
format!("{}{}", entry.host_str().unwrap(), entry.path()),
|
||
|
entry.to_string(),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
select.set_on_submit(open);
|
||
|
|
||
|
siv.add_layer(
|
||
|
Dialog::around(select.scrollable())
|
||
|
.title("History")
|
||
|
.button("Cancel", |s| {
|
||
|
s.pop_layer();
|
||
|
}),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
pub fn links(siv: &mut Cursive) {
|
||
|
let st = siv.user_data::<State>().unwrap();
|
||
|
|
||
|
let mut select = SelectView::new().autojump();
|
||
|
|
||
|
for link in st.links.iter() {
|
||
|
select.add_item(
|
||
|
match &link.1 {
|
||
|
None => link.0.to_string(),
|
||
|
Some(name) => format!("{}: {}", name, link.0.to_string()),
|
||
|
},
|
||
|
link.0.to_string(),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
select.set_on_submit(open);
|
||
|
|
||
|
siv.add_layer(
|
||
|
Dialog::around(select.scrollable())
|
||
|
.title("Links")
|
||
|
.button("Cancel", |s| {
|
||
|
s.pop_layer();
|
||
|
}),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
pub fn open_prompt(siv: &mut Cursive) {
|
||
|
siv.add_layer(
|
||
|
Dialog::around(
|
||
|
EditView::new()
|
||
|
.on_submit(open)
|
||
|
.with_name("url")
|
||
|
.fixed_width(50),
|
||
|
)
|
||
|
.title("Enter a Gemini URL")
|
||
|
.button("Ok", |s| {
|
||
|
let url = s
|
||
|
.call_on_name("url", |view: &mut EditView| view.get_content())
|
||
|
.unwrap();
|
||
|
open(s, &url);
|
||
|
})
|
||
|
.button("Cancel", |s| {
|
||
|
s.pop_layer();
|
||
|
}),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
pub fn open(siv: &mut Cursive, url: &str) {
|
||
|
siv.pop_layer();
|
||
|
log::debug!("got URL: {}", url);
|
||
|
let mut st = siv.user_data::<State>().unwrap();
|
||
|
|
||
|
let mut url: String = url.to_string();
|
||
|
if !url.starts_with("gemini://") {
|
||
|
url = format!("gemini://{}", url);
|
||
|
}
|
||
|
|
||
|
let url = url::Url::parse(url.as_str()).unwrap();
|
||
|
|
||
|
st.url = Some(url.clone());
|
||
|
|
||
|
match maj::get(url.to_string()) {
|
||
|
Ok(resp) => {
|
||
|
st.history.push(url.clone());
|
||
|
show(siv, url.to_string().as_str(), resp);
|
||
|
}
|
||
|
Err(why) => {
|
||
|
log::error!("got response error: {:?}", why);
|
||
|
siv.add_layer(Dialog::info(format!("Error fetching response: {:?}", why)));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn show(siv: &mut Cursive, url: &str, resp: Response) {
|
||
|
use maj::StatusCode::*;
|
||
|
|
||
|
match resp.status {
|
||
|
Success => match str::from_utf8(&resp.body) {
|
||
|
Ok(content) => {
|
||
|
let content: StyledString = if resp.meta.starts_with("text/gemini") {
|
||
|
populate_links(siv, content);
|
||
|
render(content)
|
||
|
} else {
|
||
|
StyledString::plain(content)
|
||
|
};
|
||
|
siv.add_fullscreen_layer(ResizedView::with_full_screen(
|
||
|
Dialog::around(TextView::new(content).scrollable())
|
||
|
.title(format!("{}: {}", url, resp.meta)),
|
||
|
));
|
||
|
}
|
||
|
Err(why) => {
|
||
|
siv.add_layer(Dialog::info(format!(
|
||
|
"UTF/8 decoding error for {}: {:?}",
|
||
|
url, why
|
||
|
)));
|
||
|
}
|
||
|
},
|
||
|
|
||
|
TemporaryRedirect => {
|
||
|
let st = siv.user_data::<State>().unwrap();
|
||
|
st.history.pop();
|
||
|
open(siv, resp.meta.as_str());
|
||
|
}
|
||
|
|
||
|
PermanentRedirect => {
|
||
|
let st = siv.user_data::<State>().unwrap();
|
||
|
st.history.pop();
|
||
|
open(siv, resp.meta.as_str());
|
||
|
}
|
||
|
|
||
|
Input => {
|
||
|
siv.add_layer(
|
||
|
Dialog::around(EditView::new().with_name("input").fixed_width(50))
|
||
|
.title(resp.meta)
|
||
|
.button("Ok", |s| {
|
||
|
let inp = s
|
||
|
.call_on_name("input", |view: &mut EditView| view.get_content())
|
||
|
.unwrap();
|
||
|
let url = {
|
||
|
let st = s.user_data::<State>().unwrap();
|
||
|
let url = st.url.as_ref().unwrap();
|
||
|
let mut u = url.clone();
|
||
|
u.set_query(Some(&inp));
|
||
|
&u.to_string()
|
||
|
};
|
||
|
open(s, url);
|
||
|
}),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
_ => {
|
||
|
siv.add_layer(Dialog::info(format!("{:?}: {}", resp.status, resp.meta)));
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn populate_links(siv: &mut Cursive, body: &str) {
|
||
|
use maj::gemini::Node::*;
|
||
|
let doc = maj::gemini::parse(body);
|
||
|
let st = siv.user_data::<State>().unwrap();
|
||
|
st.links.clear();
|
||
|
let u = st.url.as_ref().unwrap();
|
||
|
|
||
|
for node in doc {
|
||
|
match node {
|
||
|
Link { to, name } => st.links.push((u.join(&to).unwrap(), name)),
|
||
|
_ => {}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn render(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(name, Style::from(Effect::Underline)))
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
Preformatted(data) => styled.append(StyledString::plain(data)),
|
||
|
Heading { level, body } => styled.append(StyledString::styled(
|
||
|
format!("{} {}", "#".repeat(level as usize), 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
|
||
|
}
|