pull/6/head
dvkt 5 years ago
parent ffd4530472
commit 62bb8b89a2

@ -49,9 +49,6 @@
### Basics
- [ ] MENU: page up/page down show next page, highlight first link
- [ ] search functionality
- [ ] status() helper
- [ ] show errors in status()
- [ ] replace all panic! with errors
- [ ] replace all unwrap/expect with errors
- [ ] TLS

@ -71,6 +71,7 @@ pub fn fetch_url(url: &str) -> io::Result<String> {
// Fetches a URL by its component parts and returns a raw Gopher response.
pub fn fetch(host: &str, port: &str, selector: &str) -> io::Result<String> {
let mut body = String::new();
let selector = selector.replace('?', "\t"); // search queries
let stream = TcpStream::connect(format!("{}:{}", host, port))
.and_then(|mut stream| {
stream.write(format!("{}\r\n", selector).as_ref());
@ -87,6 +88,7 @@ pub fn fetch(host: &str, port: &str, selector: &str) -> io::Result<String> {
}
}
// url parsing states
enum Parsing {
Host,
Port,

@ -10,6 +10,14 @@ pub struct MenuView {
pub link: usize, // selected link
pub scroll: usize, // scrolling offset
pub size: (usize, usize), // cols, rows
pub prompt: String, // input prompt, if any
pub state: State, // view state
}
#[derive(PartialEq)]
pub enum State {
Default, // regular
Search, // entering search term
}
pub struct Menu {
@ -61,6 +69,8 @@ impl MenuView {
link: 0,
scroll: 0,
size: (0, 0),
state: State::Default,
prompt: String::new(),
}
}
@ -165,12 +175,13 @@ impl MenuView {
// fill in empty space
out.push_str(&" \r\n".repeat(rows - 1 - self.lines().len()).to_string());
}
out.push_str(&self.prompt);
out.push_str(&self.input);
out
}
fn redraw_input(&self) -> Action {
print!("\r\x1b[K{}", self.input);
print!("\r\x1b[K{}{}", self.prompt, self.input);
stdout().flush();
Action::None
}
@ -183,6 +194,7 @@ impl MenuView {
match dir {
LinkDir::Above => {
let scroll = self.scroll;
// TODO not working
if let Some(&pos) =
self.links().iter().skip(self.link).find(|&&i| i >= scroll)
{
@ -306,17 +318,76 @@ impl MenuView {
}
}
fn process_key(&mut self, key: Key) -> Action {
fn action_open(&mut self) -> Action {
self.input.clear();
if let Some(line) = self.link(self.link) {
let url = line.url.to_string();
let (typ, _, _, _) = gopher::parse_url(&url);
if typ == Type::Search {
self.prompt = format!("{}> ", line.name);
self.state = State::Search;
self.redraw_input()
} else {
Action::Open(url)
}
} else {
Action::None
}
}
fn process_search_keypress(&mut self, key: Key) -> Action {
match key {
Key::Char('\n') => {
self.input.clear();
if let Some(line) = self.link(self.link) {
let url = line.url.to_string();
self.state = State::Default;
self.prompt.clear();
if let Some(link) = self.link(self.link) {
let url = format!("{}?{}", link.url, self.input);
self.input.clear();
Action::Open(url)
} else {
Action::None
self.input.clear();
Action::Error("Error searching".to_string())
}
}
Key::Backspace | Key::Delete => {
self.input.pop();
self.redraw_input()
}
Key::Esc => {
if !self.input.is_empty() {
self.input.clear();
self.redraw_input()
} else {
self.state = State::Default;
self.prompt.clear();
self.redraw_input()
}
}
Key::Ctrl('c') => {
if !self.input.is_empty() {
self.input.clear();
self.redraw_input()
} else {
self.state = State::Default;
self.prompt.clear();
self.redraw_input()
}
}
Key::Char(c) => {
self.input.push(c);
self.redraw_input()
}
_ => Action::Unknown,
}
}
fn process_key(&mut self, key: Key) -> Action {
if self.state == State::Search {
return self.process_search_keypress(key);
}
match key {
Key::Char('\n') => self.action_open(),
Key::Up | Key::Ctrl('p') => self.action_up(),
Key::Down | Key::Ctrl('n') => self.action_down(),
Key::Backspace | Key::Delete => {
@ -419,8 +490,9 @@ impl MenuView {
}
}
self.link = 0;
Action::Redraw
// self.link = 0;
// Action::Redraw
self.redraw_input()
}
_ => Action::Unknown,
}

@ -111,12 +111,15 @@ impl UI {
let (typ, host, port, sel) = gopher::parse_url(url);
gopher::fetch(host, port, sel)
.and_then(|response| match typ {
Type::Menu => Ok(self.add_page(MenuView::from(url.to_string(), response))),
Type::Text => Ok(self.add_page(TextView::from(url.to_string(), response))),
Type::HTML => Ok(self.add_page(TextView::from(url.to_string(), response))),
Type::Menu | Type::Search => {
Ok(self.add_page(MenuView::from(url.to_string(), response)))
}
Type::Text | Type::HTML => {
Ok(self.add_page(TextView::from(url.to_string(), response)))
}
_ => Err(io::Error::new(
io::ErrorKind::Other,
format!("Unsupported Gopher Type: {:?}", typ),
format!("Unsupported Gopher Response: {:?}", typ),
)),
})
.map_err(|e| io::Error::new(e.kind(), format!("Error loading {}: {}", url, e)))
@ -170,8 +173,12 @@ impl UI {
fn process_page_input(&mut self) -> Action {
let stdin = stdin();
let page = self.pages.get_mut(self.page).expect("expected Page"); // TODO
let page_opt = self.pages.get_mut(self.page);
if page_opt.is_none() {
return Action::None;
}
let page = page_opt.unwrap();
for c in stdin.keys() {
let key = c.expect("UI error on stdin.keys"); // TODO
match page.process_input(key) {

Loading…
Cancel
Save