208 lines
7.1 KiB
Rust
208 lines
7.1 KiB
Rust
use std::env;
|
|
use std::rc::Rc;
|
|
|
|
use ruin_reactivity::effect_in;
|
|
use ruin_reactivity_parsing::{
|
|
ReactiveRope, ReactiveTokenTree, RopeEdit, TextRange, TokenTreeNode, TokenTreeParse,
|
|
TokenTreeUpdate,
|
|
};
|
|
use ruin_runtime::{async_main, fs, stdio};
|
|
|
|
enum Command {
|
|
Insert { offset: usize, text: String },
|
|
Replace { start: usize, end: usize, text: String },
|
|
ReplaceAll { text: String },
|
|
Delete { start: usize, end: usize },
|
|
Print,
|
|
Help,
|
|
Quit,
|
|
}
|
|
|
|
#[async_main]
|
|
async fn main() {
|
|
let path = env::args()
|
|
.nth(1)
|
|
.expect("usage: cargo run -p ruin-reactivity-parsing --example tokentree_reactive_parser -- <path/to/file>");
|
|
let initial_text = fs::read_to_string(&path)
|
|
.await
|
|
.expect("source file should be readable");
|
|
|
|
let reactor = ruin_reactivity::Reactor::new();
|
|
let rope = ReactiveRope::from_text(&reactor, &initial_text);
|
|
let parser = Rc::new(ReactiveTokenTree::new(&rope));
|
|
|
|
effect_in(&reactor, {
|
|
let parser = Rc::clone(&parser);
|
|
let rope = rope.clone();
|
|
move || {
|
|
parser.with_snapshot(|snapshot| {
|
|
let last_update = parser.last_update();
|
|
print_snapshot(snapshot, &rope, last_update.as_ref());
|
|
});
|
|
}
|
|
})
|
|
.leak();
|
|
|
|
println!("Commands:");
|
|
println!(" insert <byte_offset> <text>");
|
|
println!(" replace <start> <end> <text>");
|
|
println!(" replace_all <text>");
|
|
println!(" delete <start> <end>");
|
|
println!(" print");
|
|
println!(" help");
|
|
println!(" quit");
|
|
|
|
let mut input = stdio::stdin().expect("stdin should be available");
|
|
while let Some(line) = input.read_line().await.expect("stdin read should succeed") {
|
|
let trimmed = line.trim_end();
|
|
if trimmed.is_empty() {
|
|
continue;
|
|
}
|
|
|
|
match parse_command(trimmed) {
|
|
Ok(Command::Insert { offset, text }) => {
|
|
let _ = rope.edit(RopeEdit::insert(offset, text));
|
|
}
|
|
Ok(Command::Replace { start, end, text }) => {
|
|
let _ = rope.edit(RopeEdit::replace(TextRange::new(start, end), text));
|
|
}
|
|
Ok(Command::ReplaceAll { text }) => {
|
|
let _ = rope.edit(RopeEdit::replace(TextRange::new(0, rope.len_bytes()), text));
|
|
}
|
|
Ok(Command::Delete { start, end }) => {
|
|
let _ = rope.edit(RopeEdit::delete(TextRange::new(start, end)));
|
|
}
|
|
Ok(Command::Print) => {
|
|
let snapshot = parser.snapshot();
|
|
let last_update = parser.last_update();
|
|
print_snapshot(&snapshot, &rope, last_update.as_ref());
|
|
}
|
|
Ok(Command::Help) => {
|
|
println!("Commands:");
|
|
println!(" insert <byte_offset> <text>");
|
|
println!(" replace <start> <end> <text>");
|
|
println!(" replace_all <text>");
|
|
println!(" delete <start> <end>");
|
|
println!(" print");
|
|
println!(" help");
|
|
println!(" quit");
|
|
}
|
|
Ok(Command::Quit) => break,
|
|
Err(error) => eprintln!("{error}"),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn parse_command(input: &str) -> Result<Command, String> {
|
|
if input == "quit" {
|
|
return Ok(Command::Quit);
|
|
}
|
|
if input == "print" {
|
|
return Ok(Command::Print);
|
|
}
|
|
if input == "help" {
|
|
return Ok(Command::Help);
|
|
}
|
|
if let Some(rest) = input.strip_prefix("insert ") {
|
|
let (offset, text) = rest
|
|
.split_once(' ')
|
|
.ok_or_else(|| "insert expects: insert <offset> <text>".to_string())?;
|
|
return Ok(Command::Insert {
|
|
offset: offset.parse().map_err(|_| "invalid insert offset".to_string())?,
|
|
text: text.to_string(),
|
|
});
|
|
}
|
|
if let Some(rest) = input.strip_prefix("replace ") {
|
|
let mut parts = rest.splitn(3, ' ');
|
|
let start = parts
|
|
.next()
|
|
.ok_or_else(|| "replace expects: replace <start> <end> <text>".to_string())?;
|
|
let end = parts
|
|
.next()
|
|
.ok_or_else(|| "replace expects: replace <start> <end> <text>".to_string())?;
|
|
let text = parts
|
|
.next()
|
|
.ok_or_else(|| "replace expects: replace <start> <end> <text>".to_string())?;
|
|
return Ok(Command::Replace {
|
|
start: start.parse().map_err(|_| "invalid replace start".to_string())?,
|
|
end: end.parse().map_err(|_| "invalid replace end".to_string())?,
|
|
text: text.to_string(),
|
|
});
|
|
}
|
|
if let Some(text) = input.strip_prefix("replace_all ") {
|
|
return Ok(Command::ReplaceAll {
|
|
text: text.to_string(),
|
|
});
|
|
}
|
|
if let Some(rest) = input.strip_prefix("delete ") {
|
|
let (start, end) = rest
|
|
.split_once(' ')
|
|
.ok_or_else(|| "delete expects: delete <start> <end>".to_string())?;
|
|
return Ok(Command::Delete {
|
|
start: start.parse().map_err(|_| "invalid delete start".to_string())?,
|
|
end: end.parse().map_err(|_| "invalid delete end".to_string())?,
|
|
});
|
|
}
|
|
Err("unknown command; type `help`".to_string())
|
|
}
|
|
|
|
fn print_snapshot(snapshot: &TokenTreeParse, rope: &ReactiveRope, update: Option<&TokenTreeUpdate>) {
|
|
let metrics = rope.snapshot_metrics();
|
|
println!();
|
|
println!("Document revision {}", metrics.revision);
|
|
println!(
|
|
"Text bytes={}, leaves={}, nodes={}, reused_on_last_edit={}",
|
|
metrics.text.bytes,
|
|
metrics.leaves,
|
|
snapshot.stats.total_nodes,
|
|
snapshot.stats.reused_nodes
|
|
);
|
|
if let Some(update) = update {
|
|
println!(
|
|
"Last edit: [{}..{}) -> {:?}",
|
|
update.delta.range.start,
|
|
update.delta.range.end,
|
|
update.delta.inserted_text
|
|
);
|
|
println!(
|
|
"Diagnostics changed={}, trivia changed={}",
|
|
update.diagnostics_changed,
|
|
update.trivia_changed
|
|
);
|
|
}
|
|
println!("Parse tree:");
|
|
print_tree(snapshot, rope, &snapshot.root, 0);
|
|
if !snapshot.diagnostics.is_empty() {
|
|
println!("Diagnostics:");
|
|
for diagnostic in &snapshot.diagnostics {
|
|
println!(
|
|
" {:?} [{}..{}): {}",
|
|
diagnostic.kind, diagnostic.range.start, diagnostic.range.end, diagnostic.message
|
|
);
|
|
}
|
|
}
|
|
if !snapshot.trivia.comments().is_empty() {
|
|
println!("Comments:");
|
|
for comment in snapshot.trivia.comments() {
|
|
println!(
|
|
" {:?} [{}..{}): {}",
|
|
comment.kind, comment.range.start, comment.range.end, comment.text
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn print_tree(snapshot: &TokenTreeParse, rope: &ReactiveRope, node: &TokenTreeNode, depth: usize) {
|
|
let indent = " ".repeat(depth);
|
|
let (start, end) = snapshot
|
|
.absolute_span(rope, node.id)
|
|
.expect("node span should be available");
|
|
println!(
|
|
"{}- id={} kind={} span=({}:{})..({}:{})",
|
|
indent, node.id, node.kind, start.line, start.column, end.line, end.column
|
|
);
|
|
for child in &node.children {
|
|
print_tree(snapshot, rope, child, depth + 1);
|
|
}
|
|
}
|