You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
phetch/src/menu.rs

243 lines
7.8 KiB
Rust

use gopher::Type;
5 years ago
use std::io;
5 years ago
use ui::{Action, Key, View};
#[derive(Debug)]
pub struct MenuView {
5 years ago
pub input: String, // user's inputted value
pub menu: Menu, // data
5 years ago
pub line: usize, // selected line
pub scroll: usize, // scrolling offset
}
#[derive(Debug)]
pub struct Menu {
5 years ago
url: String, // gopher url
5 years ago
lines: Vec<Line>, // lines
raw: String, // raw gopher response
}
5 years ago
#[derive(Debug)]
5 years ago
pub struct Line {
5 years ago
name: String,
5 years ago
url: String,
5 years ago
typ: Type,
}
impl View for MenuView {
5 years ago
fn render(&self) -> String {
5 years ago
self.render_lines()
5 years ago
}
5 years ago
fn process_input(&mut self, key: Key) -> Action {
5 years ago
match self.process_key(key) {
a @ Action::Unknown => return a,
a => a,
}
}
}
impl MenuView {
pub fn from(url: String, response: String) -> MenuView {
MenuView {
menu: Menu::from(url, response),
input: String::new(),
line: 0,
scroll: 0,
}
}
5 years ago
fn lines(&self) -> &Vec<Line> {
&self.menu.lines
}
fn render_lines(&self) -> String {
let mut out = String::new();
for line in self.lines() {
self.render_line_into(line, &mut out);
}
out
}
fn render_line_into(&self, line: &Line, s: &mut String) {
macro_rules! push {
($c:expr, $e:expr) => {{
s.push_str("\x1b[");
s.push_str($c);
s.push_str("m");
s.push_str($e);
s.push_str("\x1b[0m");
}};
}
s.push('\t');
match line.typ {
Type::Text => push!("90", &line.name),
Type::Menu => push!("94", &line.name),
Type::Info => push!("93", &line.name),
Type::HTML => push!("92", &line.name),
_ => {}
}
s.push('\n');
}
5 years ago
fn action_page_down(&self) {}
fn action_page_up(&self) {}
fn action_up(&self) {}
fn action_down(&self) {}
fn process_key(&mut self, key: Key) -> Action {
5 years ago
match key {
5 years ago
Key::Char('\n') => {
if let Some(line) = self.menu.lines.get(self.line) {
Action::Open(line.url.to_string())
} else {
Action::None
}
}
Key::Up | Key::Ctrl('p') => {
self.action_up();
Action::None
}
Key::Down | Key::Ctrl('n') => {
self.action_down();
Action::None
}
5 years ago
Key::Backspace => {
if self.input.is_empty() {
Action::Back
} else {
self.input.pop();
5 years ago
Action::Input
5 years ago
}
}
Key::Delete => {
self.input.pop();
5 years ago
Action::Input
5 years ago
}
Key::Ctrl('c') => {
if self.input.len() > 0 {
self.input.clear();
5 years ago
Action::Input
5 years ago
} else {
Action::Quit
}
}
Key::Char('-') => {
if self.input.is_empty() {
5 years ago
self.action_page_up();
Action::None
5 years ago
} else {
self.input.push('-');
5 years ago
Action::Input
5 years ago
}
}
Key::Char(' ') => {
if self.input.is_empty() {
5 years ago
self.action_page_down();
Action::None
5 years ago
} else {
self.input.push(' ');
5 years ago
Action::Input
5 years ago
}
}
Key::Char(c) => {
self.input.push(c);
for (i, link) in self.menu.lines.iter().enumerate() {
5 years ago
// jump to number
let count = self.menu.lines.len();
5 years ago
if count < 10 && c == '1' && i == 0 {
return Action::FollowLink(i);
} else if count < 20 && c == '2' && i == 1 {
return Action::FollowLink(i);
} else if count < 30 && c == '3' && i == 2 {
return Action::FollowLink(i);
} else if count < 40 && c == '4' && i == 3 {
return Action::FollowLink(i);
} else if count < 50 && c == '5' && i == 4 {
return Action::FollowLink(i);
} else if count < 60 && c == '6' && i == 5 {
return Action::FollowLink(i);
} else if count < 70 && c == '7' && i == 6 {
return Action::FollowLink(i);
} else if count < 80 && c == '8' && i == 7 {
return Action::FollowLink(i);
} else if count < 90 && c == '9' && i == 8 {
return Action::FollowLink(i);
} else if self.input.len() > 1 && self.input == (i + 1).to_string() {
return Action::FollowLink(i);
} else if self.input.len() == 1 && self.input == (i + 1).to_string() {
return Action::FollowLink(i);
} else {
if link
.name
.to_ascii_lowercase()
.contains(&self.input.to_ascii_lowercase())
{
return Action::FollowLink(i);
}
}
}
5 years ago
Action::Input
5 years ago
}
5 years ago
_ => Action::Unknown,
5 years ago
}
}
}
impl Menu {
pub fn from(url: String, gopher_response: String) -> Menu {
Self::parse(url, gopher_response)
5 years ago
}
fn parse(url: String, raw: String) -> Menu {
5 years ago
let mut lines = vec![];
for line in raw.split_terminator("\n") {
if let Some(c) = line.chars().nth(0) {
let typ = match c {
'0' => Type::Text,
'1' => Type::Menu,
5 years ago
'2' => panic!("CSOEntity not supported"), // TODO
'3' => Type::Error,
5 years ago
'4' => panic!("Binhex not supported"), // TODO
'5' => panic!("DOSFile not supported"), // TODO
'6' => panic!("UUEncoded not supported"), // TODO
'7' => panic!("Search not supported"), // TODO
'8' => panic!("Telnet not supported"), // TODO
'9' => panic!("Binary not supported"), // TODO
'+' => panic!("Mirrors not supported"), // TODO
'g' => panic!("GIF not supported"), // TODO
'T' => panic!("Telnet3270 not supported"), // TODO
'h' => Type::HTML,
'i' => Type::Info,
5 years ago
's' => panic!("Sound not supported"), // TODO
'd' => panic!("Document not supported"), // TODO
5 years ago
'\n' => continue,
5 years ago
_ => {
eprintln!("unknown line type: {}", c);
continue;
5 years ago
}
};
let parts: Vec<&str> = line.split_terminator("\t").collect();
5 years ago
let mut url = String::from("gopher://");
if parts.len() > 2 {
url.push_str(parts[2]); // host
}
if parts.len() > 3 {
url.push(':');
url.push_str(parts[3].trim_end_matches('\r')); // port
}
if parts.len() > 1 {
url.push_str(parts[1]); // selector
}
let name = parts[0][1..].to_string();
lines.push(Line { name, url, typ });
5 years ago
}
}
Menu { raw, url, lines }
5 years ago
}
5 years ago
}