Start work on web UI command
This commit is contained in:
parent
7bc7306afb
commit
a1ed12f4f4
8 changed files with 316 additions and 14 deletions
|
|
@ -9,6 +9,7 @@ pub mod properties;
|
|||
pub mod test;
|
||||
pub mod util;
|
||||
pub mod wasm;
|
||||
pub mod web_ui;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use std::path::Path;
|
|||
use std::process::exit;
|
||||
use std::{u64, usize};
|
||||
use tree_sitter_cli::{
|
||||
config, error, generate, highlight, loader, logger, parse, properties, test, wasm,
|
||||
config, error, generate, highlight, loader, logger, parse, properties, test, wasm, web_ui,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
|
|
@ -95,6 +95,7 @@ fn run() -> error::Result<()> {
|
|||
.about("Compile a parser to WASM")
|
||||
.arg(Arg::with_name("path").index(1).multiple(true)),
|
||||
)
|
||||
.subcommand(SubCommand::with_name("ui").about("Test a parser interactively in the browser"))
|
||||
.get_matches();
|
||||
|
||||
let home_dir = dirs::home_dir().expect("Failed to read home directory");
|
||||
|
|
@ -245,6 +246,8 @@ fn run() -> error::Result<()> {
|
|||
} else if let Some(matches) = matches.subcommand_matches("build-wasm") {
|
||||
let grammar_path = current_dir.join(matches.value_of("path").unwrap_or(""));
|
||||
wasm::compile_language_to_wasm(&grammar_path)?;
|
||||
} else if matches.subcommand_matches("ui").is_some() {
|
||||
web_ui::serve(¤t_dir);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -5,10 +5,7 @@ use std::fs;
|
|||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
pub fn compile_language_to_wasm(language_dir: &Path) -> Result<()> {
|
||||
let src_dir = language_dir.join("src");
|
||||
|
||||
// Parse the grammar.json to find out the language name.
|
||||
pub fn get_grammar_name(src_dir: &Path) -> Result<String> {
|
||||
let grammar_json_path = src_dir.join("grammar.json");
|
||||
let grammar_json = fs::read_to_string(&grammar_json_path).map_err(|e| {
|
||||
format!(
|
||||
|
|
@ -22,7 +19,13 @@ pub fn compile_language_to_wasm(language_dir: &Path) -> Result<()> {
|
|||
grammar_json_path, e
|
||||
)
|
||||
})?;
|
||||
let output_filename = format!("tree-sitter-{}.wasm", grammar.name);
|
||||
Ok(grammar.name)
|
||||
}
|
||||
|
||||
pub fn compile_language_to_wasm(language_dir: &Path) -> Result<()> {
|
||||
let src_dir = language_dir.join("src");
|
||||
let grammar_name = get_grammar_name(&src_dir)?;
|
||||
let output_filename = format!("tree-sitter-{}.wasm", grammar_name);
|
||||
|
||||
// Get the current user id so that files created in the docker container will have
|
||||
// the same owner.
|
||||
|
|
@ -54,7 +57,7 @@ pub fn compile_language_to_wasm(language_dir: &Path) -> Result<()> {
|
|||
"-s",
|
||||
"TOTAL_MEMORY=33554432",
|
||||
"-s",
|
||||
&format!("EXPORTED_FUNCTIONS=[\"_tree_sitter_{}\"]", grammar.name),
|
||||
&format!("EXPORTED_FUNCTIONS=[\"_tree_sitter_{}\"]", grammar_name),
|
||||
"-fno-exceptions",
|
||||
"-I",
|
||||
"src",
|
||||
|
|
|
|||
111
cli/src/web_ui.html
Normal file
111
cli/src/web_ui.html
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
<head>
|
||||
<title>Tree-sitter</title>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.45.0/codemirror.min.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/clusterize.js/0.18.0/clusterize.min.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="playground-container">
|
||||
<header>
|
||||
<div class=header-item>
|
||||
<label for="logging-checkbox">Log</label>
|
||||
<input id="logging-checkbox" type="checkbox"></input>
|
||||
</div>
|
||||
|
||||
<div class=header-item>
|
||||
<label for="update-time">Update time: </label>
|
||||
<span id="update-time"></span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<select id="language-select" style="display: none;">
|
||||
<option value="parser">Parser</option>
|
||||
</select>
|
||||
|
||||
<textarea id="code-input"></textarea>
|
||||
|
||||
<div id="output-container-scroll">
|
||||
<pre id="output-container" class="highlight"></pre>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script
|
||||
src="https://code.jquery.com/jquery-3.3.1.min.js"
|
||||
crossorigin="anonymous">
|
||||
</script>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.45.0/codemirror.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/clusterize.js/0.18.0/clusterize.min.js"></script>
|
||||
|
||||
<script>LANGUAGE_BASE_URL = "";</script>
|
||||
<script src=tree-sitter.js></script>
|
||||
<script src=playground.js></script>
|
||||
|
||||
<style>
|
||||
#playground-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
header {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
padding: 20px;
|
||||
height: 60px;
|
||||
border-bottom: 1px solid #aaa;
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.header-item {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
border-right: 1px solid #aaa;
|
||||
}
|
||||
|
||||
#output-container-scroll {
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#output-container {
|
||||
padding: 0 10px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h4, select, .field {
|
||||
display: inline-block;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
#logging-checkbox {
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
.CodeMirror div.CodeMirror-cursor {
|
||||
border-left: 3px solid red;
|
||||
}
|
||||
|
||||
a.highlighted {
|
||||
background-color: #ddd;
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
60
cli/src/web_ui.rs
Normal file
60
cli/src/web_ui.rs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
use super::wasm;
|
||||
use std::fs;
|
||||
use std::net::TcpListener;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use tiny_http::{Header, Response, Server};
|
||||
use webbrowser;
|
||||
|
||||
const PLAYGROUND_JS: &'static [u8] = include_bytes!("../../docs/assets/js/playground.js");
|
||||
const LIB_JS: &'static [u8] = include_bytes!("../../lib/binding_web/tree-sitter.js");
|
||||
const LIB_WASM: &'static [u8] = include_bytes!("../../lib/binding_web/tree-sitter.wasm");
|
||||
const HTML: &'static [u8] = include_bytes!("./web_ui.html");
|
||||
|
||||
pub fn serve(grammar_path: &Path) {
|
||||
let port = get_available_port().expect("Couldn't find an available port");
|
||||
let url = format!("127.0.0.1:{}", port);
|
||||
let server = Server::http(&url).expect("Failed to start web server");
|
||||
let grammar_name = wasm::get_grammar_name(&grammar_path.join("src"))
|
||||
.map_err(|e| format!("Failed to get wasm filename: {:?}", e))
|
||||
.unwrap();
|
||||
let language_wasm = fs::read(format!("./tree-sitter-{}.wasm", grammar_name)).unwrap();
|
||||
|
||||
webbrowser::open(&format!("http://127.0.0.1:{}", port))
|
||||
.map_err(|e| format!("Failed to open '{}' in a web browser. Error: {}", url, e))
|
||||
.unwrap();
|
||||
|
||||
let html_header = Header::from_str("Content-type: text/html").unwrap();
|
||||
let js_header = Header::from_str("Content-type: application/javascript").unwrap();
|
||||
let wasm_header = Header::from_str("Content-type: application/wasm").unwrap();
|
||||
|
||||
for request in server.incoming_requests() {
|
||||
let (body, header) = match request.url() {
|
||||
"/" => (HTML, &html_header),
|
||||
"/playground.js" => (PLAYGROUND_JS, &js_header),
|
||||
"/tree-sitter.js" => (LIB_JS, &js_header),
|
||||
"/tree-sitter.wasm" => (LIB_WASM, &wasm_header),
|
||||
"/tree-sitter-parser.wasm" => (language_wasm.as_slice(), &wasm_header),
|
||||
_ => {
|
||||
request
|
||||
.respond(Response::from_string("Not found").with_status_code(404))
|
||||
.expect("Failed to write HTTP response");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let response = Response::from_string("")
|
||||
.with_data(body, Some(body.len()))
|
||||
.with_header(header.clone());
|
||||
request
|
||||
.respond(response)
|
||||
.expect("Failed to write HTTP response");
|
||||
}
|
||||
}
|
||||
|
||||
fn get_available_port() -> Option<u16> {
|
||||
(8000..12000).find(port_is_available)
|
||||
}
|
||||
|
||||
fn port_is_available(port: &u16) -> bool {
|
||||
TcpListener::bind(("127.0.0.1", *port)).is_ok()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue