summaryrefslogtreecommitdiff
path: root/tree-viz
diff options
context:
space:
mode:
authorAkshay <[email protected]>2022-10-04 11:55:20 +0100
committerAkshay <[email protected]>2022-10-04 11:55:20 +0100
commit91d2eb736e18e33972d20faab389257f578cbd7b (patch)
treebdd361c5288e904acdcb9c34fadac2b0682b0243 /tree-viz
init
Diffstat (limited to 'tree-viz')
-rw-r--r--tree-viz/.gitignore1
-rw-r--r--tree-viz/Cargo.toml19
-rw-r--r--tree-viz/src/app.rs230
-rw-r--r--tree-viz/src/config.rs24
-rw-r--r--tree-viz/src/main.rs96
5 files changed, 370 insertions, 0 deletions
diff --git a/tree-viz/.gitignore b/tree-viz/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/tree-viz/.gitignore
@@ -0,0 +1 @@
/target
diff --git a/tree-viz/Cargo.toml b/tree-viz/Cargo.toml
new file mode 100644
index 0000000..04107d3
--- /dev/null
+++ b/tree-viz/Cargo.toml
@@ -0,0 +1,19 @@
1[package]
2name = "tree-viz"
3version = "0.1.0"
4edition = "2021"
5
6# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7
8[dependencies]
9tree-sitter-elm = "5.6.3"
10tree-sitter-rust = "0.20.1"
11tree-sitter = "^0.20.8"
12console = "^0.15"
13once_cell = "1.14.0"
14tree-sitter-go = "0.19.1"
15tree-sitter-typescript = "0.20.1"
16
17[dependencies.notify]
18version = "5.0.0"
19default-features = false
diff --git a/tree-viz/src/app.rs b/tree-viz/src/app.rs
new file mode 100644
index 0000000..68d3641
--- /dev/null
+++ b/tree-viz/src/app.rs
@@ -0,0 +1,230 @@
1use crate::{config::Config, LANGUAGE};
2
3use std::{
4 collections::HashMap,
5 fmt::Write,
6 path::{Path, PathBuf},
7};
8
9use console::{style, Style, Term};
10use tree_sitter::{Node, Parser, Query, QueryCursor, Range, Tree};
11
12pub struct App {
13 config: Config,
14 path: PathBuf,
15 query: Option<Query>,
16 query_path: Option<PathBuf>,
17 src: Vec<u8>,
18 tree: Tree,
19}
20
21impl App {
22 pub fn new<'a, P: AsRef<Path>>(src: &'a [u8], path: P, query_path: Option<P>) -> Self {
23 let path = path.as_ref().to_owned();
24
25 let mut parser = Parser::new();
26 parser.set_language(*LANGUAGE).unwrap();
27
28 let tree = parser.parse(&src, None).unwrap();
29 let query_path = query_path.map(|q| q.as_ref().to_owned());
30 let query = query_path.as_ref().map(|p| {
31 let query_src = std::fs::read_to_string(&p).expect("unable to read query");
32 Query::new(*LANGUAGE, &query_src).expect("query parse error")
33 });
34
35 Self {
36 config: Default::default(),
37 path,
38 query,
39 query_path,
40 src: src.to_owned(),
41 tree,
42 }
43 }
44
45 pub fn draw(&self) {
46 let term = Term::stdout();
47 term.clear_screen().unwrap();
48 let mut done = false;
49 let mut depth = 0;
50 let mut in_capture: Option<Range> = None;
51 let mut cursor = self.tree.walk();
52
53 let capture_names = self
54 .query
55 .as_ref()
56 .map(|q| q.capture_names())
57 .unwrap_or_default();
58 let capture_map = self
59 .query
60 .as_ref()
61 .map(|query| {
62 QueryCursor::new()
63 .matches(&query, self.tree.root_node(), self.src.as_slice())
64 .flat_map(|match_| match_.captures)
65 .fold(
66 HashMap::new(),
67 |mut map: HashMap<Node, Vec<u32>>, capture| {
68 map.entry(capture.node)
69 .and_modify(|idxs| idxs.push(capture.index))
70 .or_insert_with(|| vec![capture.index]);
71 map
72 },
73 )
74 })
75 .unwrap_or_default();
76
77 while !done {
78 let node = cursor.node();
79 let mut tree_string = String::new();
80 in_capture = match in_capture {
81 Some(range)
82 if !contains(&range, &node.range()) && capture_map.contains_key(&node) =>
83 {
84 Some(node.range())
85 }
86 Some(range) if !contains(&range, &node.range()) => None,
87 None if capture_map.contains_key(&node) => Some(node.range()),
88 i => i,
89 };
90
91 write!(
92 tree_string,
93 "{}",
94 (if in_capture.is_some() {
95 Style::new().on_yellow().on_bright()
96 } else {
97 Style::new()
98 })
99 .bright()
100 .black()
101 .apply_to(
102 format!("{}{}", "|", " ".repeat(self.config.indent_level))
103 .repeat(depth as usize)
104 )
105 )
106 .unwrap();
107
108 write!(
109 tree_string,
110 "{} ",
111 if node.is_error() {
112 Style::new().red()
113 } else if in_capture.is_some() {
114 Style::new().on_yellow().on_bright()
115 } else {
116 Style::new()
117 }
118 .apply_to(node.kind()),
119 )
120 .unwrap();
121
122 if let Some(idxs) = capture_map.get(&node) {
123 for index in idxs {
124 write!(
125 tree_string,
126 "@{} ",
127 style(capture_names[*index as usize].as_str()).magenta()
128 )
129 .unwrap();
130 }
131 }
132
133 if self.config.show_ranges {
134 let range = node.range();
135 write!(
136 tree_string,
137 " {}",
138 style(format!("{:?}..{:?}", range.start_byte, range.end_byte,))
139 .bright()
140 .black()
141 )
142 .unwrap();
143 }
144
145 if self.config.show_src {
146 write!(
147 tree_string,
148 " {:.?}",
149 style(node.utf8_text(&self.src).unwrap()).cyan()
150 )
151 .unwrap();
152 }
153
154 term.write_line(&tree_string).unwrap();
155 term.clear_to_end_of_screen().unwrap();
156
157 if cursor.goto_first_child() {
158 depth += 1;
159 continue;
160 }
161 if cursor.goto_next_sibling() {
162 continue;
163 }
164
165 loop {
166 if !cursor.goto_parent() {
167 done = true;
168 break;
169 } else {
170 depth -= 1;
171 }
172
173 if cursor.goto_next_sibling() {
174 break;
175 }
176 }
177 }
178
179 // see https://github.com/console-rs/console/issues/36#issuecomment-624731432
180 // for the reasoning behing this hackjob
181
182 term.write_line("\n(>) increase indent").unwrap();
183 term.clear_to_end_of_screen().unwrap();
184
185 term.write_line("(<) decrease indent ").unwrap();
186 term.clear_to_end_of_screen().unwrap();
187
188 term.write_line("(n) toggle ranges").unwrap();
189 term.clear_to_end_of_screen().unwrap();
190
191 term.write_line("(s) toggle source text").unwrap();
192 term.clear_to_end_of_screen().unwrap();
193
194 term.write_line("(r) reload from disk").unwrap();
195 term.clear_to_end_of_screen().unwrap();
196
197 term.write_line("(C-c) quit").unwrap();
198 term.clear_to_end_of_screen().unwrap();
199 }
200
201 pub fn increase_indent(&mut self) {
202 self.config.indent_level = self.config.indent_level.saturating_add(1);
203 }
204
205 pub fn decrease_indent(&mut self) {
206 self.config.indent_level = self.config.indent_level.saturating_sub(1);
207 }
208
209 pub fn toggle_ranges(&mut self) {
210 self.config.show_ranges = !self.config.show_ranges;
211 }
212
213 pub fn toggle_source(&mut self) {
214 self.config.show_src = !self.config.show_src;
215 }
216
217 pub fn reload(&mut self) {
218 let src = std::fs::read_to_string(&self.path).unwrap();
219 let new = Self::new(src.as_bytes(), &self.path, self.query_path.as_ref());
220 *self = Self {
221 config: self.config,
222 ..new
223 };
224 }
225}
226
227// does a encompass b
228fn contains(a: &Range, b: &Range) -> bool {
229 a.start_byte <= b.start_byte && a.end_byte >= b.end_byte
230}
diff --git a/tree-viz/src/config.rs b/tree-viz/src/config.rs
new file mode 100644
index 0000000..6f34291
--- /dev/null
+++ b/tree-viz/src/config.rs
@@ -0,0 +1,24 @@
1use std::default::Default;
2
3#[derive(Clone, Copy)]
4pub struct Config {
5 pub indent_level: usize,
6 pub show_ranges: bool,
7 pub show_src: bool,
8}
9
10impl Default for Config {
11 fn default() -> Self {
12 Config::new()
13 }
14}
15
16impl Config {
17 fn new() -> Self {
18 Self {
19 indent_level: 2,
20 show_ranges: true,
21 show_src: true,
22 }
23 }
24}
diff --git a/tree-viz/src/main.rs b/tree-viz/src/main.rs
new file mode 100644
index 0000000..79964cb
--- /dev/null
+++ b/tree-viz/src/main.rs
@@ -0,0 +1,96 @@
1mod app;
2mod config;
3
4use std::{
5 env, fs,
6 path::Path,
7 sync::{mpsc, Arc, RwLock},
8 thread,
9 time::Duration,
10};
11
12use app::App;
13use console::{Key, Term};
14use notify::{Event as WatchEvent, EventKind as WatchEventKind, RecursiveMode, Watcher};
15use once_cell::sync::Lazy;
16use tree_sitter::Language;
17
18//pub static LANGUAGE: Lazy<Language> = Lazy::new(tree_sitter_rust::language);
19pub static LANGUAGE: Lazy<Language> = Lazy::new(tree_sitter_typescript::language_typescript);
20
21fn main() {
22 let mut args = env::args();
23 let _ = args.next();
24
25 let path = args.next().expect("no arg passed");
26 let query_path = args.next();
27 let src = fs::read_to_string(&path).expect("unable to read file");
28
29 let app = Arc::new(RwLock::new(App::new(
30 src.as_bytes(),
31 &path,
32 query_path.as_ref(),
33 )));
34
35 let watch_fn = |watcher_app: Arc<RwLock<App>>| {
36 move |ev| {
37 if let Ok(WatchEvent {
38 kind: WatchEventKind::Modify(..),
39 ..
40 }) = ev
41 {
42 if let Ok(mut locked) = watcher_app.try_write() {
43 locked.reload();
44 locked.draw();
45 };
46 }
47 }
48 };
49
50 let mut watcher1 = notify::recommended_watcher(watch_fn(Arc::clone(&app))).unwrap();
51 watcher1
52 .watch(Path::new(&path), RecursiveMode::NonRecursive)
53 .unwrap();
54
55 let mut watcher2 = notify::recommended_watcher(watch_fn(Arc::clone(&app))).unwrap();
56 if let Some(query_path) = query_path {
57 watcher2
58 .watch(Path::new(&query_path), RecursiveMode::NonRecursive)
59 .unwrap();
60 }
61
62 let (tx, rx) = mpsc::channel();
63 let tx0 = tx.clone();
64 thread::spawn(move || {
65 let term = Term::stdout();
66 loop {
67 if let Ok(Key::Char(ev)) = term.read_key() {
68 tx0.send(ev).unwrap();
69 }
70 }
71 });
72
73 if let Ok(locked) = app.try_read() {
74 locked.draw();
75 }
76
77 loop {
78 match rx.try_recv() {
79 Ok(ev) => {
80 if let Ok(mut locked) = app.try_write() {
81 match ev {
82 '>' => locked.increase_indent(),
83 '<' => locked.decrease_indent(),
84 'n' => locked.toggle_ranges(),
85 's' => locked.toggle_source(),
86 'r' => locked.reload(),
87 _ => (),
88 }
89 locked.draw();
90 }
91 }
92 _ => (),
93 }
94 thread::sleep(Duration::from_millis(10));
95 }
96}