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

293 lines
8.5 KiB
Rust

use gopher::Type;
5 years ago
use ui::{Action, Key, View};
pub struct MenuView {
5 years ago
pub input: String, // user's inputted value
pub menu: Menu, // data
5 years ago
pub link: usize, // selected link
5 years ago
pub scroll: usize, // scrolling offset
}
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
5 years ago
pub struct Line {
5 years ago
name: String,
5 years ago
url: String,
5 years ago
typ: Type,
5 years ago
link: usize, // link #, if any
5 years ago
}
impl View for MenuView {
fn render(&self, w: u16, h: u16) -> String {
self.render_lines(w, h)
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(),
5 years ago
link: 0,
5 years ago
scroll: 0,
}
}
5 years ago
fn lines(&self) -> &Vec<Line> {
&self.menu.lines
}
5 years ago
fn links(&self) -> impl Iterator<Item = &Line> {
self.menu.links()
}
fn render_lines(&self, _cols: u16, rows: u16) -> String {
5 years ago
let mut out = String::new();
macro_rules! push {
($c:expr, $e:expr) => {{
5 years ago
out.push_str("\x1b[");
out.push_str($c);
out.push_str("m");
out.push_str($e);
out.push_str("\x1b[0m");
5 years ago
}};
}
5 years ago
let mut links = 0;
for (i, line) in self.lines().iter().enumerate() {
if i as u16 >= rows - 4 {
return out;
}
5 years ago
if line.typ == Type::Info {
5 years ago
out.push_str(" ");
5 years ago
} else {
links += 1;
5 years ago
if links == self.link {
out.push('*');
} else {
out.push(' ');
}
5 years ago
out.push(' ');
out.push_str("\x1b[95m");
if links < 10 {
out.push(' ');
}
out.push_str(&links.to_string());
out.push_str(".\x1b[0m ");
}
match line.typ {
Type::Text => push!("96", &line.name),
Type::Menu => push!("94", &line.name),
Type::Info => push!("93", &line.name),
Type::HTML => push!("92", &line.name),
_ => {}
}
out.push('\n');
5 years ago
}
5 years ago
out
5 years ago
}
5 years ago
fn redraw_input(&self) -> Action {
// code to redraw input...
Action::None
}
5 years ago
fn action_page_down(&self) {}
fn action_page_up(&self) {}
fn action_up(&self) {}
fn action_down(&self) {}
5 years ago
fn action_select_link(&mut self, line: usize) -> Action {
if line < self.links().count() {
self.link = line;
Action::Redraw
} else {
Action::None
}
}
5 years ago
fn action_follow_link(&mut self, link: usize) -> Action {
self.input.clear();
if let Some(line) = self.links().nth(link) {
let url = line.url.to_string();
Action::Open(url)
5 years ago
} else {
Action::None
}
}
5 years ago
fn process_key(&mut self, key: Key) -> Action {
5 years ago
match key {
5 years ago
Key::Char('\n') => {
5 years ago
if let Some(line) = self.lines().get(self.link) {
5 years ago
let url = line.url.to_string();
self.input.clear();
Action::Open(url)
5 years ago
} 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
self.redraw_input()
5 years ago
}
}
Key::Delete => {
self.input.pop();
5 years ago
self.redraw_input()
5 years ago
}
Key::Ctrl('c') => {
if self.input.len() > 0 {
self.input.clear();
5 years ago
self.redraw_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
self.redraw_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
self.redraw_input()
5 years ago
}
}
Key::Char(c) => {
self.input.push(c);
5 years ago
let count = self.links().count();
5 years ago
let input = &self.input;
5 years ago
for i in 0..count {
5 years ago
// jump to number
5 years ago
for z in 1..9 {
if count < (z * 10) && c == to_char(z as u32) && i == z - 1 {
return self.action_follow_link(i);
}
}
if input.len() > 1 && input == &(i + 1).to_string() {
5 years ago
return self.action_select_link(i);
5 years ago
} else {
5 years ago
let name = if let Some(link) = self.links().nth(i) {
link.name.to_ascii_lowercase()
} else {
"".to_string()
};
if name.contains(&self.input.to_ascii_lowercase()) {
5 years ago
return self.action_select_link(i);
5 years ago
}
}
}
5 years ago
Action::None
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
}
5 years ago
pub fn links(&self) -> impl Iterator<Item = &Line> {
self.lines.iter().filter(|&line| line.link > 0)
}
fn parse(url: String, raw: String) -> Menu {
5 years ago
let mut lines = vec![];
5 years ago
let mut link = 0;
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' => Type::CSOEntity,
'3' => Type::Error,
5 years ago
'4' => Type::Binhex,
'5' => Type::DOSFile,
'6' => Type::UUEncoded,
'7' => Type::Search,
'8' => Type::Telnet,
'9' => Type::Binary,
'+' => Type::Mirror,
'g' => Type::GIF,
'T' => Type::Telnet3270,
'h' => Type::HTML,
'i' => Type::Info,
5 years ago
's' => Type::Sound,
'd' => Type::Document,
5 years ago
'.' => continue,
5 years ago
'\n' => continue,
5 years ago
_ => continue,
};
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();
5 years ago
link += 1;
5 years ago
let link = if typ == Type::Info { 0 } else { link };
5 years ago
5 years ago
lines.push(Line {
name,
url,
typ,
5 years ago
link,
5 years ago
});
5 years ago
}
}
Menu { raw, url, lines }
5 years ago
}
5 years ago
}
5 years ago
// number -> char of that number
fn to_char(c: u32) -> char {
if let Some(ch) = std::char::from_digit(c, 10) {
ch
} else {
'0'
}
}