summaryrefslogtreecommitdiff
path: root/tree-viz/src/app.rs
diff options
context:
space:
mode:
Diffstat (limited to 'tree-viz/src/app.rs')
-rw-r--r--tree-viz/src/app.rs230
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 @@
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}