aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAkshay <[email protected]>2024-07-14 19:31:42 +0100
committerAkshay <[email protected]>2024-07-14 19:31:42 +0100
commit80ac5c569114e9f99a37119d97a6050b438f573a (patch)
tree9b389374e53ce304f31df44b2ebbd2eeb6b363b8 /src
init
Diffstat (limited to 'src')
-rw-r--r--src/app.rs259
-rw-r--r--src/config.rs26
-rw-r--r--src/main.rs102
3 files changed, 387 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 @@
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 tree_sitter::{Node, Parser, Query, QueryCursor, Range, Tree};
11
12pub 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
22impl 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
257fn contains(a: &Range, b: &Range) -> bool {
258 a.start_byte <= b.start_byte && a.end_byte >= b.end_byte
259}
diff --git a/src/config.rs b/src/config.rs
new file mode 100644
index 0000000..18ed5cc
--- /dev/null
+++ b/src/config.rs
@@ -0,0 +1,26 @@
1use std::default::Default;
2
3#[derive(Clone, Copy)]
4pub struct Config {
5 pub indent_level: usize,
6 pub show_ranges: bool,
7 pub show_src: bool,
8 pub show_field_name: bool,
9}
10
11impl Default for Config {
12 fn default() -> Self {
13 Config::new()
14 }
15}
16
17impl Config {
18 fn new() -> Self {
19 Self {
20 indent_level: 2,
21 show_ranges: true,
22 show_src: true,
23 show_field_name: true,
24 }
25 }
26}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..ce7c84c
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,102 @@
1mod app;
2mod config;
3
4use std::{
5 env, fs,
6 path::Path,
7 sync::{mpsc, Arc, RwLock},
8 thread,
9 time::Duration,
10};
11
12use app::App;
13use console::{Key, Term};
14use notify::{Event as WatchEvent, EventKind as WatchEventKind, RecursiveMode, Watcher};
15
16fn main() {
17 let mut args = env::args();
18 let _ = args.next();
19
20 let language = match args.next().as_ref().map(|s| s.as_str()) {
21 Some("rust") => tree_sitter_rust::language(),
22 Some("tsx") | Some("typescript") => tree_sitter_typescript::language_tsx(),
23 Some("javascript") => tree_sitter_javascript::language(),
24 Some("python") => tree_sitter_python::language(),
25 Some("ruby") => tree_sitter_ruby::language(),
26 Some("markdown") => tree_sitter_md::language(),
27 Some(s) => panic!("invalid language passed: {s}"),
28 None => panic!("no language passed"),
29 };
30 let path = args.next().expect("no arg passed");
31 let query_path = args.next();
32 let src = fs::read_to_string(&path).expect("unable to read file");
33
34 let app = Arc::new(RwLock::new(App::new(
35 src.as_bytes(),
36 &path,
37 query_path.as_ref(),
38 language,
39 )));
40
41 let watch_fn = |watcher_app: Arc<RwLock<App>>| {
42 move |ev| {
43 if let Ok(WatchEvent {
44 kind: WatchEventKind::Modify(..),
45 ..
46 }) = ev
47 {
48 if let Ok(mut locked) = watcher_app.try_write() {
49 locked.reload();
50 locked.draw();
51 };
52 }
53 }
54 };
55
56 let mut watcher1 = notify::recommended_watcher(watch_fn(Arc::clone(&app))).unwrap();
57 watcher1
58 .watch(Path::new(&path), RecursiveMode::NonRecursive)
59 .unwrap();
60
61 let mut watcher2 = notify::recommended_watcher(watch_fn(Arc::clone(&app))).unwrap();
62 if let Some(query_path) = query_path {
63 watcher2
64 .watch(Path::new(&query_path), RecursiveMode::NonRecursive)
65 .unwrap();
66 }
67
68 let (tx, rx) = mpsc::channel();
69 let tx0 = tx.clone();
70 thread::spawn(move || {
71 let term = Term::stdout();
72 loop {
73 if let Ok(Key::Char(ev)) = term.read_key() {
74 tx0.send(ev).unwrap();
75 }
76 }
77 });
78
79 if let Ok(locked) = app.try_read() {
80 locked.draw();
81 }
82
83 loop {
84 match rx.try_recv() {
85 Ok(ev) => {
86 if let Ok(mut locked) = app.try_write() {
87 match ev {
88 '>' => locked.increase_indent(),
89 '<' => locked.decrease_indent(),
90 'n' => locked.toggle_ranges(),
91 's' => locked.toggle_source(),
92 'r' => locked.reload(),
93 _ => (),
94 }
95 locked.draw();
96 }
97 }
98 _ => (),
99 }
100 thread::sleep(Duration::from_millis(10));
101 }
102}