diff options
Diffstat (limited to 'tree-viz')
-rw-r--r-- | tree-viz/.gitignore | 1 | ||||
-rw-r--r-- | tree-viz/Cargo.toml | 19 | ||||
-rw-r--r-- | tree-viz/src/app.rs | 230 | ||||
-rw-r--r-- | tree-viz/src/config.rs | 24 | ||||
-rw-r--r-- | tree-viz/src/main.rs | 96 |
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] | ||
2 | name = "tree-viz" | ||
3 | version = "0.1.0" | ||
4 | edition = "2021" | ||
5 | |||
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
7 | |||
8 | [dependencies] | ||
9 | tree-sitter-elm = "5.6.3" | ||
10 | tree-sitter-rust = "0.20.1" | ||
11 | tree-sitter = "^0.20.8" | ||
12 | console = "^0.15" | ||
13 | once_cell = "1.14.0" | ||
14 | tree-sitter-go = "0.19.1" | ||
15 | tree-sitter-typescript = "0.20.1" | ||
16 | |||
17 | [dependencies.notify] | ||
18 | version = "5.0.0" | ||
19 | default-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 @@ | |||
1 | use crate::{config::Config, LANGUAGE}; | ||
2 | |||
3 | use std::{ | ||
4 | collections::HashMap, | ||
5 | fmt::Write, | ||
6 | path::{Path, PathBuf}, | ||
7 | }; | ||
8 | |||
9 | use console::{style, Style, Term}; | ||
10 | use tree_sitter::{Node, Parser, Query, QueryCursor, Range, Tree}; | ||
11 | |||
12 | pub 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 | |||
21 | impl 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 | ||
228 | fn 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 @@ | |||
1 | use std::default::Default; | ||
2 | |||
3 | #[derive(Clone, Copy)] | ||
4 | pub struct Config { | ||
5 | pub indent_level: usize, | ||
6 | pub show_ranges: bool, | ||
7 | pub show_src: bool, | ||
8 | } | ||
9 | |||
10 | impl Default for Config { | ||
11 | fn default() -> Self { | ||
12 | Config::new() | ||
13 | } | ||
14 | } | ||
15 | |||
16 | impl 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 @@ | |||
1 | mod app; | ||
2 | mod config; | ||
3 | |||
4 | use std::{ | ||
5 | env, fs, | ||
6 | path::Path, | ||
7 | sync::{mpsc, Arc, RwLock}, | ||
8 | thread, | ||
9 | time::Duration, | ||
10 | }; | ||
11 | |||
12 | use app::App; | ||
13 | use console::{Key, Term}; | ||
14 | use notify::{Event as WatchEvent, EventKind as WatchEventKind, RecursiveMode, Watcher}; | ||
15 | use once_cell::sync::Lazy; | ||
16 | use tree_sitter::Language; | ||
17 | |||
18 | //pub static LANGUAGE: Lazy<Language> = Lazy::new(tree_sitter_rust::language); | ||
19 | pub static LANGUAGE: Lazy<Language> = Lazy::new(tree_sitter_typescript::language_typescript); | ||
20 | |||
21 | fn 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 | } | ||