From db524bbfaaeccd6e63aa30381f50ebebb6224bf8 Mon Sep 17 00:00:00 2001
From: Jeremy Soller <jackpot51@gmail.com>
Date: Tue, 8 Nov 2016 15:46:13 -0700
Subject: [PATCH] Browsing links

---
 src/browser/main.rs | 381 +++++++++++++++++++++++++++++++-------------
 1 file changed, 269 insertions(+), 112 deletions(-)

diff --git a/src/browser/main.rs b/src/browser/main.rs
index 8bfd1e1..15d1ee7 100644
--- a/src/browser/main.rs
+++ b/src/browser/main.rs
@@ -4,9 +4,12 @@ extern crate orbclient;
 extern crate orbfont;
 extern crate tendril;
 
-use std::{cmp, env};
+use std::{cmp, env, str};
 use std::iter::repeat;
 use std::default::Default;
+use std::fs::File;
+use std::io::{stderr, Read, Write};
+use std::net::TcpStream;
 use std::string::String;
 
 use html5ever::parse_document;
@@ -15,7 +18,37 @@ use orbclient::{Color, Window, EventOption, K_ESC, K_DOWN, K_UP};
 use orbfont::Font;
 use tendril::TendrilSink;
 
-// This is not proper HTML serialization, of course.
+#[derive(Clone, Debug)]
+struct Url {
+    scheme: String,
+    host: String,
+    port: u16,
+    path: Vec<String>
+}
+
+impl Url {
+    fn new(url: &str) -> Url {
+        let (scheme, reference) = url.split_at(url.find(':').unwrap_or(0));
+
+        let mut parts = reference.split('/').skip(2); //skip first two slashes
+        let remote = parts.next().unwrap_or("");
+        let mut remote_parts = remote.split(':');
+        let host = remote_parts.next().unwrap_or("");
+        let port = remote_parts.next().unwrap_or("").parse::<u16>().unwrap_or(80);
+
+        let mut path = Vec::new();
+        for part in parts {
+            path.push(part.to_string());
+        }
+
+        Url {
+            scheme: scheme.to_string(),
+            host: host.to_string(),
+            port: port,
+            path: path
+        }
+    }
+}
 
 struct Block<'a> {
     x: i32,
@@ -23,10 +56,19 @@ struct Block<'a> {
     w: i32,
     h: i32,
     color: Color,
+    string: String,
+    link: Option<String>,
     text: orbfont::Text<'a>
 }
 
 impl<'a> Block<'a> {
+    fn contains(&self, m_x: i32, m_y: i32, offset: i32) -> bool {
+        let x = self.x;
+        let y = self.y - offset;
+
+        m_x >= x && m_x < x + self.w && m_y >= y && m_y < y + self.h
+    }
+
     fn draw(&self, window: &mut Window, offset: i32) {
         let x = self.x;
         let y = self.y - offset;
@@ -36,7 +78,7 @@ impl<'a> Block<'a> {
     }
 }
 
-fn walk<'a>(handle: Handle, indent: usize, x: &mut i32, y: &mut i32, mut size: f32, mut bold: bool, mut color: Color, mut ignore: bool, whitespace: &mut bool, font: &'a Font, font_bold: &'a Font, blocks: &mut Vec<Block<'a>>) {
+fn walk<'a>(handle: Handle, indent: usize, x: &mut i32, y: &mut i32, mut size: f32, mut bold: bool, mut color: Color, mut ignore: bool, whitespace: &mut bool, mut link: Option<String>, font: &'a Font, font_bold: &'a Font, blocks: &mut Vec<Block<'a>>) {
     let node = handle.borrow();
 
     let mut new_line = false;
@@ -114,6 +156,8 @@ fn walk<'a>(handle: Handle, indent: usize, x: &mut i32, y: &mut i32, mut size: f
                                 w: w,
                                 h: h,
                                 color: color,
+                                string: word.to_string(),
+                                link: link.clone(),
                                 text: text
                             });
 
@@ -132,20 +176,24 @@ fn walk<'a>(handle: Handle, indent: usize, x: &mut i32, y: &mut i32, mut size: f
                 println!("<!-- {} -->", escape_default(text))
             },
 
-        Element(ref name, _, ref _attrs) => {
+        Element(ref name, _, ref attrs) => {
             assert!(name.ns == ns!(html));
             print!("<{}", name.local);
-            /*
             for attr in attrs.iter() {
                 assert!(attr.name.ns == ns!());
                 print!(" {}=\"{}\"", attr.name.local, attr.value);
             }
-            */
             println!(">");
 
             match &*name.local {
                 "a" => {
                     color = Color::rgb(0, 0, 255);
+                    for attr in attrs.iter() {
+                        match &*attr.name.local {
+                            "href" => link = Some(attr.value.to_string()),
+                            _ => ()
+                        }
+                    }
                 },
                 "b" => {
                     bold = true;
@@ -209,7 +257,7 @@ fn walk<'a>(handle: Handle, indent: usize, x: &mut i32, y: &mut i32, mut size: f
     }
 
     for child in node.children.iter() {
-        walk(child.clone(), indent + 4, x, y, size, bold, color, ignore, whitespace, font, font_bold, blocks);
+        walk(child.clone(), indent + 4, x, y, size, bold, color, ignore, whitespace, link.clone(), font, font_bold, blocks);
     }
 
     if new_line {
@@ -224,132 +272,241 @@ pub fn escape_default(s: &str) -> String {
     s.chars().flat_map(|c| c.escape_default()).collect()
 }
 
-fn event_loop(window: &mut Window){
+fn read_blocks<'a, R: Read>(r: &mut R, font: &'a Font, font_bold: &'a Font) -> Vec<Block<'a>> {
+    let mut blocks = vec![];
+
+    let dom = parse_document(RcDom::default(), Default::default())
+        .from_utf8()
+        .read_from(r)
+        .unwrap();
+
+    let mut x = 0;
+    let mut y = 0;
+    let mut whitespace = false;
+    walk(dom.document, 0, &mut x, &mut y, 16.0, false, Color::rgb(0, 0, 0), false, &mut whitespace, None, font, font_bold, &mut blocks);
+
+    if !dom.errors.is_empty() {
+        /*
+        println!("\nParse errors:");
+        for err in dom.errors.into_iter() {
+            println!("    {}", err);
+        }
+        */
+    }
+
+    blocks
+}
+
+fn file_blocks<'a>(url: &Url, font: &'a Font, font_bold: &'a Font) -> Vec<Block<'a>> {
+    let mut parts = url.path.iter();
+    let mut path = parts.next().map_or(String::new(), |s| s.clone());
+    for part in parts {
+        path.push('/');
+        path.push_str(part);
+    }
+
+    if let Ok(mut file) = File::open(&path) {
+        read_blocks(&mut file, &font, &font_bold)
+    } else {
+        vec![]
+    }
+}
+
+fn http_blocks<'a>(url: &Url, font: &'a Font, font_bold: &'a Font) -> Vec<Block<'a>> {
+    let mut parts = url.path.iter();
+    let mut path = parts.next().map_or(String::new(), |s| s.clone());
+    for part in parts {
+        path.push('/');
+        path.push_str(part);
+    }
+
+    write!(stderr(), "* Connecting to {}:{}\n", url.host, url.port).unwrap();
+
+    let mut stream = TcpStream::connect((url.host.as_str(), url.port)).unwrap();
+
+    write!(stderr(), "* Requesting {}\n", path).unwrap();
+
+    let request = format!("GET /{} HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n\r\n", path, url.host);
+    stream.write(request.as_bytes()).unwrap();
+    stream.flush().unwrap();
+
+    write!(stderr(), "* Waiting for response\n").unwrap();
+
+    let mut response = Vec::new();
+
     loop {
-        for event in window.events() {
-            if let EventOption::Key(key_event) = event.to_option() {
-                if key_event.pressed && key_event.scancode == K_ESC {
-                    return;
-                }
-            }
-            if let EventOption::Quit(_) = event.to_option() {
-                return;
-            }
+        let mut buf = [0; 65536];
+        let count = stream.read(&mut buf).unwrap();
+        if count == 0 {
+            break;
         }
+        response.extend_from_slice(&buf[.. count]);
     }
+
+    write!(stderr(), "* Received {} bytes\n", response.len()).unwrap();
+
+    let mut header_end = 0;
+    while header_end < response.len() {
+        if response[header_end..].starts_with(b"\r\n\r\n") {
+            break;
+        }
+        header_end += 1;
+    }
+
+    for line in unsafe { str::from_utf8_unchecked(&response[..header_end]) }.lines() {
+        write!(stderr(), "> {}\n", line).unwrap();
+    }
+
+    read_blocks(&mut &response[header_end + 4 ..], font, font_bold)
 }
 
-fn error_msg(window: &mut Window, msg: &str) {
-    let mut x = 0;
-    for c in msg.chars() {
-        window.char(x, 0, c, Color::rgb(255, 255, 255));
-        x += 8;
+fn url_blocks<'a>(url: &Url, font: &'a Font, font_bold: &'a Font) -> Vec<Block<'a>> {
+    if url.scheme == "http" {
+        http_blocks(url, font, font_bold)
+    } else if url.scheme == "file" || url.scheme.is_empty() {
+        file_blocks(url, font, font_bold)
+    } else {
+        vec![]
     }
 }
 
-fn main() {
-    let title = match env::args().nth(1) {
-        Some(arg) => arg.clone(),
-        None => "".to_string(),
-    };
+fn main_window(arg: &str, font: &Font, font_bold: &Font) {
+    let mut url = Url::new(arg);
 
-    match Font::find(None, None, None) {
-        Ok(font) => match Font::find(None, None, Some("Bold")) {
-            Ok(font_bold) => {
-                let dom = parse_document(RcDom::default(), Default::default())
-                    .from_utf8()
-                    .from_file(env::args().nth(1).expect("browser: no file provided"))
-                    .unwrap();
-
-                let mut blocks = vec![];
-                let mut x = 0;
-                let mut y = 0;
-                let mut whitespace = false;
-                walk(dom.document, 0, &mut x, &mut y, 16.0, false, Color::rgb(0, 0, 0), false, &mut whitespace, &font, &font_bold, &mut blocks);
-
-                if !dom.errors.is_empty() {
-                    /*
-                    println!("\nParse errors:");
-                    for err in dom.errors.into_iter() {
-                        println!("    {}", err);
-                    }
-                    */
-                }
+    let mut window = Window::new(-1, -1, 640, 480,  &format!("Browser ({})", arg)).unwrap();
 
-                let mut window = Window::new(-1,
-                                             -1,
-                                             640,
-                                             480,
-                                             &("Browser (".to_string() + &title + ")"))
-                                     .unwrap();
-
-                let mut offset = 0;
-                let mut max_offset = 0;
-                for block in blocks.iter() {
-                    if block.y + block.h > max_offset {
-                        max_offset = block.y + block.h;
-                    }
-                }
+    let mut blocks = url_blocks(&url, &font, &font_bold);
+
+    let mut offset = 0;
+    let mut max_offset = 0;
+    for block in blocks.iter() {
+        if block.y + block.h > max_offset {
+            max_offset = block.y + block.h;
+        }
+    }
+
+    let mut mouse_down = false;
 
-                let mut redraw = true;
-                loop {
-                    if redraw {
-                        redraw = false;
+    let mut redraw = true;
+    loop {
+        if redraw {
+            redraw = false;
+
+            window.set(Color::rgb(255, 255, 255));
+
+            for block in blocks.iter() {
+                block.draw(&mut window, offset);
+            }
+
+            window.sync();
+        }
 
-                        window.set(Color::rgb(255, 255, 255));
-                        for block in blocks.iter() {
-                            block.draw(&mut window, offset);
+        for event in window.events() {
+            match event.to_option() {
+                EventOption::Key(key_event) => if key_event.pressed {
+                    match key_event.scancode {
+                        K_ESC => return,
+                        K_UP => {
+                            redraw = true;
+                            offset = cmp::max(0, offset - 128);
+                        },
+                        K_DOWN => {
+                            redraw = true;
+                            offset = cmp::min(max_offset, offset + 128);
+                        },
+                        _ => ()
+                    }
+                },
+                EventOption::Mouse(mouse_event) => if mouse_event.left_button {
+                    mouse_down = true;
+                } else if mouse_down {
+                    mouse_down = false;
+
+                    let mut link_opt = None;
+                    for block in blocks.iter() {
+                        if block.contains(mouse_event.x, mouse_event.y, offset) {
+                            println!("Click {}", block.string);
+                            if let Some(ref link) = block.link {
+                                link_opt = Some(link.clone());
+                                break;
+                            }
                         }
-                        window.sync();
                     }
 
-                    for event in window.events() {
-                        if let EventOption::Key(key_event) = event.to_option() {
-                            if key_event.pressed {
-                                match key_event.scancode {
-                                    K_ESC => return,
-                                    K_UP => {
-                                        redraw = true;
-                                        offset = cmp::max(0, offset - 128);
-                                    },
-                                    K_DOWN => {
-                                        redraw = true;
-                                        offset = cmp::min(max_offset, offset + 128);
-                                    },
-                                    _ => ()
+                    if let Some(link) = link_opt {
+                        if link.starts_with('#') {
+                            println!("Find anchor {}", link);
+                        } else {
+                            if link.find(':').is_some() {
+                                url = Url::new(&link);
+                            } else if link.starts_with('/') {
+                                url.path.clear();
+                                for part in link[1..].split('/') {
+                                    url.path.push(part.to_string());
+                                }
+                            } else {
+                                url.path.push(link.clone());
+                            };
+
+                            println!("Navigate {}: {:#?}", link, url);
+
+                            blocks = url_blocks(&url, &font, &font_bold);
+
+                            offset = 0;
+                            max_offset = 0;
+                            for block in blocks.iter() {
+                                if block.y + block.h > max_offset {
+                                    max_offset = block.y + block.h;
                                 }
                             }
+
+                            redraw = true;
                         }
-                        if let EventOption::Quit(_) = event.to_option() {
-                            return;
-                        }
+                    }
+                },
+                EventOption::Quit(_) => return,
+                _ => ()
+            }
+        }
+    }
+}
+
+fn main() {
+    let err_window = |msg: &str| {
+        let mut window = Window::new(-1, -1, 320, 32, "Browser").unwrap();
+
+        window.set(Color::rgb(0, 0, 0));
+
+        let mut x = 0;
+        for c in msg.chars() {
+            window.char(x, 0, c, Color::rgb(255, 255, 255));
+            x += 8;
+        }
+
+        window.sync();
+
+        loop {
+            for event in window.events() {
+                if let EventOption::Key(key_event) = event.to_option() {
+                    if key_event.pressed && key_event.scancode == K_ESC {
+                        return;
                     }
                 }
-            },
-            Err(err) => {
-                let mut window = Window::new(-1,
-                                             -1,
-                                             320,
-                                             32,
-                                             &("Browser (".to_string() + &title + ")"))
-                                     .unwrap();
-                window.set(Color::rgb(0, 0, 0));
-                error_msg(&mut window, &format!("{}", err));
-                window.sync();
-                event_loop(&mut window);
+                if let EventOption::Quit(_) = event.to_option() {
+                    return;
+                }
             }
-        },
-        Err(err) => {
-            let mut window = Window::new(-1,
-                                         -1,
-                                         320,
-                                         32,
-                                         &("Browser (".to_string() + &title + ")"))
-                                 .unwrap();
-            window.set(Color::rgb(0, 0, 0));
-            error_msg(&mut window, &format!("{}", err));
-            window.sync();
-            event_loop(&mut window);
         }
+    };
+
+    match env::args().nth(1) {
+        Some(path) => match Font::find(None, None, None) {
+            Ok(font) => match Font::find(None, None, Some("Bold")) {
+                Ok(font_bold) => main_window(&path, &font, &font_bold),
+                Err(err) => err_window(&format!("{}", err))
+            },
+            Err(err) => err_window(&format!("{}", err))
+        },
+        None => err_window("no file argument")
     }
 }
-- 
GitLab