diff options
Diffstat (limited to 'tree-viz/src/app.rs')
-rw-r--r-- | tree-viz/src/app.rs | 230 |
1 files changed, 230 insertions, 0 deletions
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 | } | ||