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