2020-07-25 16:39:10 +00:00
|
|
|
use cursive::{
|
|
|
|
event::Key,
|
|
|
|
menu::MenuTree,
|
2020-07-27 20:05:22 +00:00
|
|
|
theme::{BaseColor, BorderStyle, Color, Effect, PaletteColor, Style, Theme},
|
2020-07-25 16:39:10 +00:00
|
|
|
traits::*,
|
2020-07-25 18:05:23 +00:00
|
|
|
utils::markup::StyledString,
|
2020-07-25 16:39:10 +00:00
|
|
|
views::{Dialog, EditView, Panel, ResizedView, TextView},
|
|
|
|
Cursive,
|
|
|
|
};
|
2020-07-25 16:50:34 +00:00
|
|
|
use maj::{self, Response, StatusCode};
|
|
|
|
use std::str;
|
2020-07-25 16:39:10 +00:00
|
|
|
|
|
|
|
fn main() {
|
|
|
|
cursive::logger::init();
|
|
|
|
|
|
|
|
let mut siv = cursive::default();
|
|
|
|
|
2020-07-25 18:42:51 +00:00
|
|
|
let theme = custom_theme_from_cursive(&siv);
|
|
|
|
siv.set_theme(theme);
|
|
|
|
|
2020-07-25 16:50:34 +00:00
|
|
|
siv.add_global_callback('c', |s| {
|
|
|
|
s.pop_layer();
|
|
|
|
});
|
2020-07-25 16:39:10 +00:00
|
|
|
siv.add_global_callback('q', cursive::Cursive::quit);
|
|
|
|
siv.add_global_callback('~', cursive::Cursive::toggle_debug_console);
|
|
|
|
siv.add_global_callback('o', open_prompt);
|
|
|
|
siv.add_global_callback('?', help);
|
|
|
|
|
|
|
|
siv.menubar()
|
|
|
|
.add_subtree(
|
|
|
|
"majc",
|
|
|
|
MenuTree::new()
|
|
|
|
.leaf("About", move |s| {
|
|
|
|
s.add_layer(Dialog::info(format!(
|
2020-07-25 17:43:04 +00:00
|
|
|
"{} {}\n\nby {}\n\nSee https://tulpa.dev/cadey/maj for more information",
|
2020-07-25 16:39:10 +00:00
|
|
|
env!("CARGO_PKG_NAME"),
|
2020-07-25 17:43:04 +00:00
|
|
|
env!("CARGO_PKG_VERSION"),
|
|
|
|
env!("CARGO_PKG_AUTHORS"),
|
2020-07-25 16:39:10 +00:00
|
|
|
)));
|
|
|
|
})
|
2020-07-27 20:05:22 +00:00
|
|
|
.leaf("Help", help)
|
|
|
|
.leaf("Quit", cursive::Cursive::quit),
|
2020-07-25 16:39:10 +00:00
|
|
|
)
|
|
|
|
.add_leaf("Open", |s| open_prompt(s));
|
|
|
|
|
2020-07-25 16:50:34 +00:00
|
|
|
siv.set_autohide_menu(false);
|
2020-07-25 16:39:10 +00:00
|
|
|
siv.add_global_callback(Key::Esc, |s| s.select_menubar());
|
|
|
|
|
|
|
|
help(&mut siv);
|
|
|
|
|
|
|
|
siv.run();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn help(siv: &mut Cursive) {
|
|
|
|
let content = include_str!("./help.gmi");
|
|
|
|
|
|
|
|
siv.add_layer(
|
2020-07-25 18:16:31 +00:00
|
|
|
Dialog::around(Panel::new(
|
|
|
|
TextView::new(render_gemini(content)).scrollable(),
|
|
|
|
))
|
|
|
|
.title("Help")
|
|
|
|
.dismiss_button("Ok"),
|
2020-07-25 16:39:10 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn open(siv: &mut Cursive, url: &str) {
|
|
|
|
siv.pop_layer();
|
|
|
|
log::debug!("got URL: {}", url);
|
|
|
|
|
2020-07-25 16:50:34 +00:00
|
|
|
match maj::get(url.to_string()) {
|
2020-07-25 16:39:10 +00:00
|
|
|
Ok(resp) => {
|
2020-07-25 16:50:34 +00:00
|
|
|
show(siv, url, resp);
|
|
|
|
}
|
|
|
|
Err(why) => {
|
|
|
|
log::error!("got response error: {:?}", why);
|
|
|
|
siv.add_layer(Dialog::info(format!("Error fetching response: {:?}", why)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn show(siv: &mut Cursive, url: &str, resp: Response) {
|
2020-07-25 18:05:23 +00:00
|
|
|
use StatusCode::*;
|
2020-07-25 16:39:10 +00:00
|
|
|
|
2020-07-25 18:05:23 +00:00
|
|
|
match resp.status {
|
2020-07-25 18:16:31 +00:00
|
|
|
Success => match str::from_utf8(&resp.body) {
|
|
|
|
Ok(content) => {
|
|
|
|
let content: StyledString = if resp.meta.starts_with("text/gemini") {
|
|
|
|
render_gemini(content)
|
|
|
|
} else {
|
|
|
|
StyledString::plain(content)
|
|
|
|
};
|
|
|
|
siv.add_fullscreen_layer(ResizedView::with_full_screen(
|
|
|
|
Dialog::around(TextView::new(content).scrollable())
|
|
|
|
.title(format!("{}: {}", url, resp.meta)),
|
|
|
|
));
|
2020-07-25 18:05:23 +00:00
|
|
|
}
|
2020-07-25 18:16:31 +00:00
|
|
|
Err(why) => {
|
|
|
|
siv.add_layer(Dialog::info(format!(
|
|
|
|
"UTF/8 decoding error for {}: {:?}",
|
|
|
|
url, why
|
|
|
|
)));
|
|
|
|
}
|
|
|
|
},
|
2020-07-25 18:05:23 +00:00
|
|
|
|
|
|
|
TemporaryRedirect => {
|
|
|
|
open(siv, resp.meta.as_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
PermanentRedirect => {
|
|
|
|
open(siv, resp.meta.as_str());
|
|
|
|
}
|
|
|
|
|
2020-07-25 18:42:51 +00:00
|
|
|
Input => {
|
|
|
|
siv.add_layer(Dialog::info("needs input support"));
|
|
|
|
return;
|
|
|
|
// 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 mut u = url::Url::parse(url).unwrap();
|
|
|
|
// u.set_query(Some(&inp));
|
|
|
|
// u.as_str()
|
|
|
|
// };
|
|
|
|
// open(s, url);
|
|
|
|
// }),
|
|
|
|
// );
|
|
|
|
}
|
|
|
|
|
2020-07-25 18:05:23 +00:00
|
|
|
_ => {
|
|
|
|
siv.add_layer(Dialog::info(format!("{:?}: {}", resp.status, resp.meta)));
|
|
|
|
return;
|
2020-07-25 16:39:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-07-25 18:05:23 +00:00
|
|
|
|
|
|
|
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 {
|
2020-07-25 18:16:31 +00:00
|
|
|
None => styled.append(StyledString::styled(to, Style::from(Effect::Underline))),
|
2020-07-27 20:05:22 +00:00
|
|
|
Some(name) => {
|
|
|
|
styled.append(StyledString::styled(name, Style::from(Effect::Underline)))
|
|
|
|
}
|
2020-07-25 18:05:23 +00:00
|
|
|
},
|
|
|
|
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
|
|
|
|
}
|
2020-07-25 18:42:51 +00:00
|
|
|
|
|
|
|
fn custom_theme_from_cursive(siv: &Cursive) -> Theme {
|
|
|
|
// We'll return the current theme with a small modification.
|
|
|
|
let mut theme = siv.current_theme().clone();
|
|
|
|
|
|
|
|
theme.palette[PaletteColor::Background] = Color::TerminalDefault;
|
2020-07-27 20:05:22 +00:00
|
|
|
theme.palette[PaletteColor::View] = Color::TerminalDefault;
|
|
|
|
theme.palette[PaletteColor::Primary] = Color::Dark(BaseColor::White);
|
|
|
|
theme.palette[PaletteColor::TitlePrimary] = Color::Light(BaseColor::White);
|
|
|
|
|
|
|
|
theme.shadow = false;
|
|
|
|
theme.borders = BorderStyle::Simple;
|
2020-07-25 18:42:51 +00:00
|
|
|
|
|
|
|
theme
|
|
|
|
}
|