diff options
Diffstat (limited to 'src/app.rs')
-rw-r--r-- | src/app.rs | 259 |
1 files changed, 259 insertions, 0 deletions
diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..285034b --- /dev/null +++ b/src/app.rs | |||
@@ -0,0 +1,259 @@ | |||
1 | use crate::config::Config; | ||
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 | language: tree_sitter::Language, | ||
15 | path: PathBuf, | ||
16 | query: Option<Query>, | ||
17 | query_path: Option<PathBuf>, | ||
18 | src: Vec<u8>, | ||
19 | tree: Tree, | ||
20 | } | ||
21 | |||
22 | impl App { | ||
23 | pub fn new<'a, P: AsRef<Path>>( | ||
24 | src: &'a [u8], | ||
25 | path: P, | ||
26 | query_path: Option<P>, | ||
27 | language: tree_sitter::Language, | ||
28 | ) -> Self { | ||
29 | let path = path.as_ref().to_owned(); | ||
30 | |||
31 | let mut parser = Parser::new(); | ||
32 | parser.set_language(&language).unwrap(); | ||
33 | |||
34 | let tree = parser.parse(&src, None).unwrap(); | ||
35 | let query_path = query_path.map(|q| q.as_ref().to_owned()); | ||
36 | let query = query_path.as_ref().map(|p| { | ||
37 | let query_src = std::fs::read_to_string(&p).expect("unable to read query"); | ||
38 | Query::new(&language, &query_src).expect("query parse error") | ||
39 | }); | ||
40 | |||
41 | Self { | ||
42 | config: Default::default(), | ||
43 | path, | ||
44 | query, | ||
45 | query_path, | ||
46 | src: src.to_owned(), | ||
47 | tree, | ||
48 | language, | ||
49 | } | ||
50 | } | ||
51 | |||
52 | pub fn draw(&self) { | ||
53 | let term = Term::stdout(); | ||
54 | term.clear_screen().unwrap(); | ||
55 | let mut done = false; | ||
56 | let mut depth = 0; | ||
57 | let mut in_capture: Option<Range> = None; | ||
58 | let mut cursor = self.tree.walk(); | ||
59 | |||
60 | let capture_names = self | ||
61 | .query | ||
62 | .as_ref() | ||
63 | .map(|q| q.capture_names()) | ||
64 | .unwrap_or_default(); | ||
65 | let capture_map = self | ||
66 | .query | ||
67 | .as_ref() | ||
68 | .map(|query| { | ||
69 | QueryCursor::new() | ||
70 | .matches(&query, self.tree.root_node(), self.src.as_slice()) | ||
71 | .flat_map(|match_| match_.captures) | ||
72 | .fold( | ||
73 | HashMap::new(), | ||
74 | |mut map: HashMap<Node, Vec<u32>>, capture| { | ||
75 | map.entry(capture.node) | ||
76 | .and_modify(|idxs| idxs.push(capture.index)) | ||
77 | .or_insert_with(|| vec![capture.index]); | ||
78 | map | ||
79 | }, | ||
80 | ) | ||
81 | }) | ||
82 | .unwrap_or_default(); | ||
83 | |||
84 | while !done { | ||
85 | let node = cursor.node(); | ||
86 | let mut tree_string = String::new(); | ||
87 | in_capture = match in_capture { | ||
88 | Some(range) | ||
89 | if !contains(&range, &node.range()) && capture_map.contains_key(&node) => | ||
90 | { | ||
91 | Some(node.range()) | ||
92 | } | ||
93 | Some(range) if !contains(&range, &node.range()) => None, | ||
94 | None if capture_map.contains_key(&node) => Some(node.range()), | ||
95 | i => i, | ||
96 | }; | ||
97 | |||
98 | write!( | ||
99 | tree_string, | ||
100 | "{}", | ||
101 | (if in_capture.is_some() { | ||
102 | Style::new().on_yellow().on_bright() | ||
103 | } else { | ||
104 | Style::new() | ||
105 | }) | ||
106 | .bright() | ||
107 | .black() | ||
108 | .apply_to( | ||
109 | format!("{}{}", "|", " ".repeat(self.config.indent_level)) | ||
110 | .repeat(depth as usize) | ||
111 | ) | ||
112 | ) | ||
113 | .unwrap(); | ||
114 | |||
115 | if self.config.show_field_name { | ||
116 | if let Some(f) = cursor.field_name() { | ||
117 | write!( | ||
118 | tree_string, | ||
119 | "{} ", | ||
120 | if in_capture.is_some() { | ||
121 | Style::new().on_yellow().on_bright() | ||
122 | } else { | ||
123 | Style::new() | ||
124 | } | ||
125 | .yellow() | ||
126 | .apply_to(f) | ||
127 | ) | ||
128 | .unwrap() | ||
129 | } | ||
130 | } | ||
131 | |||
132 | write!( | ||
133 | tree_string, | ||
134 | "{} ", | ||
135 | if node.is_error() { | ||
136 | Style::new().red() | ||
137 | } else if in_capture.is_some() { | ||
138 | Style::new().on_yellow().on_bright() | ||
139 | } else { | ||
140 | Style::new() | ||
141 | } | ||
142 | .apply_to(node.kind()), | ||
143 | ) | ||
144 | .unwrap(); | ||
145 | |||
146 | if let Some(idxs) = capture_map.get(&node) { | ||
147 | for index in idxs { | ||
148 | write!( | ||
149 | tree_string, | ||
150 | "@{} ", | ||
151 | style(capture_names[*index as usize]).magenta() | ||
152 | ) | ||
153 | .unwrap(); | ||
154 | } | ||
155 | } | ||
156 | |||
157 | if self.config.show_ranges { | ||
158 | let range = node.range(); | ||
159 | write!( | ||
160 | tree_string, | ||
161 | " {}", | ||
162 | style(format!("{:?}..{:?}", range.start_byte, range.end_byte,)) | ||
163 | .bright() | ||
164 | .black() | ||
165 | ) | ||
166 | .unwrap(); | ||
167 | } | ||
168 | |||
169 | if self.config.show_src { | ||
170 | write!( | ||
171 | tree_string, | ||
172 | " {:.?}", | ||
173 | style(node.utf8_text(&self.src).unwrap()).cyan() | ||
174 | ) | ||
175 | .unwrap(); | ||
176 | } | ||
177 | |||
178 | term.write_line(&tree_string).unwrap(); | ||
179 | term.clear_to_end_of_screen().unwrap(); | ||
180 | |||
181 | if cursor.goto_first_child() { | ||
182 | depth += 1; | ||
183 | continue; | ||
184 | } | ||
185 | if cursor.goto_next_sibling() { | ||
186 | continue; | ||
187 | } | ||
188 | |||
189 | loop { | ||
190 | if !cursor.goto_parent() { | ||
191 | done = true; | ||
192 | break; | ||
193 | } else { | ||
194 | depth -= 1; | ||
195 | } | ||
196 | |||
197 | if cursor.goto_next_sibling() { | ||
198 | break; | ||
199 | } | ||
200 | } | ||
201 | } | ||
202 | |||
203 | // see https://github.com/console-rs/console/issues/36#issuecomment-624731432 | ||
204 | // for the reasoning behing this hackjob | ||
205 | |||
206 | term.write_line("\n(>) increase indent").unwrap(); | ||
207 | term.clear_to_end_of_screen().unwrap(); | ||
208 | |||
209 | term.write_line("(<) decrease indent ").unwrap(); | ||
210 | term.clear_to_end_of_screen().unwrap(); | ||
211 | |||
212 | term.write_line("(n) toggle ranges").unwrap(); | ||
213 | term.clear_to_end_of_screen().unwrap(); | ||
214 | |||
215 | term.write_line("(s) toggle source text").unwrap(); | ||
216 | term.clear_to_end_of_screen().unwrap(); | ||
217 | |||
218 | term.write_line("(r) reload from disk").unwrap(); | ||
219 | term.clear_to_end_of_screen().unwrap(); | ||
220 | |||
221 | term.write_line("(C-c) quit").unwrap(); | ||
222 | term.clear_to_end_of_screen().unwrap(); | ||
223 | } | ||
224 | |||
225 | pub fn increase_indent(&mut self) { | ||
226 | self.config.indent_level = self.config.indent_level.saturating_add(1); | ||
227 | } | ||
228 | |||
229 | pub fn decrease_indent(&mut self) { | ||
230 | self.config.indent_level = self.config.indent_level.saturating_sub(1); | ||
231 | } | ||
232 | |||
233 | pub fn toggle_ranges(&mut self) { | ||
234 | self.config.show_ranges = !self.config.show_ranges; | ||
235 | } | ||
236 | |||
237 | pub fn toggle_source(&mut self) { | ||
238 | self.config.show_src = !self.config.show_src; | ||
239 | } | ||
240 | |||
241 | pub fn reload(&mut self) { | ||
242 | let src = std::fs::read_to_string(&self.path).unwrap(); | ||
243 | let new = Self::new( | ||
244 | src.as_bytes(), | ||
245 | &self.path, | ||
246 | self.query_path.as_ref(), | ||
247 | self.language.clone(), | ||
248 | ); | ||
249 | *self = Self { | ||
250 | config: self.config, | ||
251 | ..new | ||
252 | }; | ||
253 | } | ||
254 | } | ||
255 | |||
256 | // does a encompass b | ||
257 | fn contains(a: &Range, b: &Range) -> bool { | ||
258 | a.start_byte <= b.start_byte && a.end_byte >= b.end_byte | ||
259 | } | ||