1 use super::wasm;
2 use anyhow::Context;
3 use std::env;
4 use std::fs;
5 use std::net::TcpListener;
6 use std::path::{Path, PathBuf};
7 use std::str::FromStr;
8 use tiny_http::{Header, Response, Server};
9 use webbrowser;
10 
11 macro_rules! resource {
12     ($name: tt, $path: tt) => {
13         #[cfg(TREE_SITTER_EMBED_WASM_BINDING)]
14         fn $name(tree_sitter_dir: &Option<PathBuf>) -> Vec<u8> {
15             if let Some(tree_sitter_dir) = tree_sitter_dir {
16                 fs::read(tree_sitter_dir.join($path)).unwrap()
17             } else {
18                 include_bytes!(concat!("../../", $path)).to_vec()
19             }
20         }
21 
22         #[cfg(not(TREE_SITTER_EMBED_WASM_BINDING))]
23         fn $name(tree_sitter_dir: &Option<PathBuf>) -> Vec<u8> {
24             if let Some(tree_sitter_dir) = tree_sitter_dir {
25                 fs::read(tree_sitter_dir.join($path)).unwrap()
26             } else {
27                 Vec::new()
28             }
29         }
30     };
31 }
32 
33 resource!(get_main_html, "cli/src/playground.html");
34 resource!(get_playground_js, "docs/assets/js/playground.js");
35 resource!(get_lib_js, "lib/binding_web/tree-sitter.js");
36 resource!(get_lib_wasm, "lib/binding_web/tree-sitter.wasm");
37 
serve(grammar_path: &Path, open_in_browser: bool)38 pub fn serve(grammar_path: &Path, open_in_browser: bool) {
39     let port = get_available_port().expect("Couldn't find an available port");
40     let addr = format!("127.0.0.1:{}", port);
41     let server = Server::http(&addr).expect("Failed to start web server");
42     let grammar_name = wasm::get_grammar_name(&grammar_path.join("src"))
43         .with_context(|| "Failed to get wasm filename")
44         .unwrap();
45     let wasm_filename = format!("tree-sitter-{}.wasm", grammar_name);
46     let language_wasm = fs::read(grammar_path.join(&wasm_filename))
47         .with_context(|| {
48             format!(
49                 "Failed to read {}. Run `tree-sitter build-wasm` first.",
50                 wasm_filename
51             )
52         })
53         .unwrap();
54     let url = format!("http://{}", addr);
55     println!("Started playground on: {}", url);
56     if open_in_browser {
57         if let Err(_) = webbrowser::open(&url) {
58             eprintln!("Failed to open '{}' in a web browser", url);
59         }
60     }
61 
62     let tree_sitter_dir = env::var("TREE_SITTER_BASE_DIR").map(PathBuf::from).ok();
63     let main_html = String::from_utf8(get_main_html(&tree_sitter_dir))
64         .unwrap()
65         .replace("THE_LANGUAGE_NAME", &grammar_name)
66         .into_bytes();
67     let playground_js = get_playground_js(&tree_sitter_dir);
68     let lib_js = get_lib_js(&tree_sitter_dir);
69     let lib_wasm = get_lib_wasm(&tree_sitter_dir);
70 
71     let html_header = Header::from_str("Content-Type: text/html").unwrap();
72     let js_header = Header::from_str("Content-Type: application/javascript").unwrap();
73     let wasm_header = Header::from_str("Content-Type: application/wasm").unwrap();
74 
75     for request in server.incoming_requests() {
76         let res = match request.url() {
77             "/" => response(&main_html, &html_header),
78             "/tree-sitter-parser.wasm" => response(&language_wasm, &wasm_header),
79             "/playground.js" => {
80                 if playground_js.is_empty() {
81                     redirect("https://tree-sitter.github.io/tree-sitter/assets/js/playground.js")
82                 } else {
83                     response(&playground_js, &js_header)
84                 }
85             }
86             "/tree-sitter.js" => {
87                 if lib_js.is_empty() {
88                     redirect("https://tree-sitter.github.io/tree-sitter.js")
89                 } else {
90                     response(&lib_js, &js_header)
91                 }
92             }
93             "/tree-sitter.wasm" => {
94                 if lib_wasm.is_empty() {
95                     redirect("https://tree-sitter.github.io/tree-sitter.wasm")
96                 } else {
97                     response(&lib_wasm, &wasm_header)
98                 }
99             }
100             _ => response(b"Not found", &html_header).with_status_code(404),
101         };
102         request.respond(res).expect("Failed to write HTTP response");
103     }
104 }
105 
redirect<'a>(url: &'a str) -> Response<&'a [u8]>106 fn redirect<'a>(url: &'a str) -> Response<&'a [u8]> {
107     Response::empty(302)
108         .with_data("".as_bytes(), Some(0))
109         .with_header(Header::from_bytes("Location", url.as_bytes()).unwrap())
110 }
111 
response<'a>(data: &'a [u8], header: &Header) -> Response<&'a [u8]>112 fn response<'a>(data: &'a [u8], header: &Header) -> Response<&'a [u8]> {
113     Response::empty(200)
114         .with_data(data, Some(data.len()))
115         .with_header(header.clone())
116 }
117 
get_available_port() -> Option<u16>118 fn get_available_port() -> Option<u16> {
119     (8000..12000).find(port_is_available)
120 }
121 
port_is_available(port: &u16) -> bool122 fn port_is_available(port: &u16) -> bool {
123     TcpListener::bind(("127.0.0.1", *port)).is_ok()
124 }
125