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