1use arborium_tree_sitter::Node;
4use serde::ser::{SerializeMap, SerializeSeq};
5use serde::{Serialize, Serializer};
6
7use crate::Colors;
8
9#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct NodeHandle {
15 pub kind: String,
17 pub text: String,
19 pub span: (u32, u32),
21}
22
23impl NodeHandle {
24 pub fn from_node(node: Node<'_>, source: &str) -> Self {
26 let text = node
27 .utf8_text(source.as_bytes())
28 .expect("node text extraction failed")
29 .to_owned();
30 Self {
31 kind: node.kind().to_owned(),
32 text,
33 span: (node.start_byte() as u32, node.end_byte() as u32),
34 }
35 }
36}
37
38impl Serialize for NodeHandle {
39 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
40 where
41 S: Serializer,
42 {
43 use serde::ser::SerializeStruct;
44 let mut s = serializer.serialize_struct("NodeHandle", 3)?;
45 s.serialize_field("kind", &self.kind)?;
46 s.serialize_field("text", &self.text)?;
47 s.serialize_field("span", &[self.span.0, self.span.1])?;
48 s.end()
49 }
50}
51
52#[derive(Clone, Debug, PartialEq)]
56pub enum Value {
57 Null,
58 String(String),
59 Node(NodeHandle),
60 Array(Vec<Value>),
61 Object(Vec<(String, Value)>),
63 Tagged {
65 tag: String,
66 data: Option<Box<Value>>,
67 },
68}
69
70impl Serialize for Value {
71 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
72 where
73 S: Serializer,
74 {
75 match self {
76 Value::Null => serializer.serialize_none(),
77 Value::String(s) => serializer.serialize_str(s),
78 Value::Node(h) => h.serialize(serializer),
79 Value::Array(arr) => {
80 let mut seq = serializer.serialize_seq(Some(arr.len()))?;
81 for item in arr {
82 seq.serialize_element(item)?;
83 }
84 seq.end()
85 }
86 Value::Object(fields) => {
87 let mut map = serializer.serialize_map(Some(fields.len()))?;
88 for (key, value) in fields {
89 map.serialize_entry(key, value)?;
90 }
91 map.end()
92 }
93 Value::Tagged { tag, data } => {
94 let len = if data.is_some() { 2 } else { 1 };
95 let mut map = serializer.serialize_map(Some(len))?;
96 map.serialize_entry("$tag", tag)?;
97 if let Some(d) = data {
98 map.serialize_entry("$data", d)?;
99 }
100 map.end()
101 }
102 }
103 }
104}
105
106impl Value {
107 pub fn format(&self, pretty: bool, colors: Colors) -> String {
116 let mut out = String::new();
117 format_value(&mut out, self, &colors, pretty, 0);
118 out
119 }
120}
121
122fn format_value(out: &mut String, value: &Value, c: &Colors, pretty: bool, indent: usize) {
123 match value {
124 Value::Null => {
125 out.push_str(c.dim);
126 out.push_str("null");
127 out.push_str(c.reset);
128 }
129 Value::String(s) => {
130 out.push_str(c.green);
131 out.push('"');
132 out.push_str(&escape_json_string(s));
133 out.push('"');
134 out.push_str(c.reset);
135 }
136 Value::Node(h) => {
137 format_node_handle(out, h, c, pretty, indent);
138 }
139 Value::Array(arr) => {
140 format_array(out, arr, c, pretty, indent);
141 }
142 Value::Object(fields) => {
143 format_object(out, fields, c, pretty, indent);
144 }
145 Value::Tagged { tag, data } => {
146 format_tagged(out, tag, data, c, pretty, indent);
147 }
148 }
149}
150
151fn format_node_handle(out: &mut String, h: &NodeHandle, c: &Colors, pretty: bool, indent: usize) {
152 out.push_str(c.dim);
153 out.push('{');
154 out.push_str(c.reset);
155
156 let field_indent = if pretty { indent + 2 } else { 0 };
157
158 if pretty {
160 out.push('\n');
161 out.push_str(&" ".repeat(field_indent));
162 }
163 out.push_str(c.blue);
164 out.push_str("\"kind\"");
165 out.push_str(c.reset);
166 out.push_str(c.dim);
167 out.push(':');
168 out.push_str(c.reset);
169 if pretty {
170 out.push(' ');
171 }
172 out.push_str(c.green);
173 out.push('"');
174 out.push_str(&escape_json_string(&h.kind));
175 out.push('"');
176 out.push_str(c.reset);
177
178 out.push_str(c.dim);
180 out.push(',');
181 out.push_str(c.reset);
182 if pretty {
183 out.push('\n');
184 out.push_str(&" ".repeat(field_indent));
185 }
186 out.push_str(c.blue);
187 out.push_str("\"text\"");
188 out.push_str(c.reset);
189 out.push_str(c.dim);
190 out.push(':');
191 out.push_str(c.reset);
192 if pretty {
193 out.push(' ');
194 }
195 out.push_str(c.green);
196 out.push('"');
197 out.push_str(&escape_json_string(&h.text));
198 out.push('"');
199 out.push_str(c.reset);
200
201 out.push_str(c.dim);
203 out.push(',');
204 out.push_str(c.reset);
205 if pretty {
206 out.push('\n');
207 out.push_str(&" ".repeat(field_indent));
208 }
209 out.push_str(c.blue);
210 out.push_str("\"span\"");
211 out.push_str(c.reset);
212 out.push_str(c.dim);
213 out.push(':');
214 out.push_str(c.reset);
215 if pretty {
216 out.push(' ');
217 }
218 out.push_str(c.dim);
219 out.push('[');
220 out.push_str(c.reset);
221 out.push_str(&h.span.0.to_string());
222 out.push_str(c.dim);
223 out.push_str(", ");
224 out.push_str(c.reset);
225 out.push_str(&h.span.1.to_string());
226 out.push_str(c.dim);
227 out.push(']');
228 out.push_str(c.reset);
229
230 if pretty {
231 out.push('\n');
232 out.push_str(&" ".repeat(indent));
233 }
234
235 out.push_str(c.dim);
236 out.push('}');
237 out.push_str(c.reset);
238}
239
240fn format_array(out: &mut String, arr: &[Value], c: &Colors, pretty: bool, indent: usize) {
241 out.push_str(c.dim);
242 out.push('[');
243 out.push_str(c.reset);
244
245 if arr.is_empty() {
246 out.push_str(c.dim);
247 out.push(']');
248 out.push_str(c.reset);
249 return;
250 }
251
252 let elem_indent = if pretty { indent + 2 } else { 0 };
253
254 for (i, item) in arr.iter().enumerate() {
255 if i > 0 {
256 out.push_str(c.dim);
257 out.push(',');
258 out.push_str(c.reset);
259 }
260
261 if pretty {
262 out.push('\n');
263 out.push_str(&" ".repeat(elem_indent));
264 }
265
266 format_value(out, item, c, pretty, elem_indent);
267 }
268
269 if pretty {
270 out.push('\n');
271 out.push_str(&" ".repeat(indent));
272 }
273
274 out.push_str(c.dim);
275 out.push(']');
276 out.push_str(c.reset);
277}
278
279fn format_object(
280 out: &mut String,
281 fields: &[(String, Value)],
282 c: &Colors,
283 pretty: bool,
284 indent: usize,
285) {
286 out.push_str(c.dim);
287 out.push('{');
288 out.push_str(c.reset);
289
290 if fields.is_empty() {
291 out.push_str(c.dim);
292 out.push('}');
293 out.push_str(c.reset);
294 return;
295 }
296
297 let field_indent = if pretty { indent + 2 } else { 0 };
298
299 for (i, (key, value)) in fields.iter().enumerate() {
300 if i > 0 {
301 out.push_str(c.dim);
302 out.push(',');
303 out.push_str(c.reset);
304 }
305
306 if pretty {
307 out.push('\n');
308 out.push_str(&" ".repeat(field_indent));
309 }
310
311 out.push_str(c.blue);
313 out.push('"');
314 out.push_str(&escape_json_string(key));
315 out.push('"');
316 out.push_str(c.reset);
317
318 out.push_str(c.dim);
319 out.push(':');
320 out.push_str(c.reset);
321
322 if pretty {
323 out.push(' ');
324 }
325
326 format_value(out, value, c, pretty, field_indent);
327 }
328
329 if pretty {
330 out.push('\n');
331 out.push_str(&" ".repeat(indent));
332 }
333
334 out.push_str(c.dim);
335 out.push('}');
336 out.push_str(c.reset);
337}
338
339fn format_tagged(
340 out: &mut String,
341 tag: &str,
342 data: &Option<Box<Value>>,
343 c: &Colors,
344 pretty: bool,
345 indent: usize,
346) {
347 out.push_str(c.dim);
348 out.push('{');
349 out.push_str(c.reset);
350
351 let field_indent = if pretty { indent + 2 } else { 0 };
352
353 if pretty {
354 out.push('\n');
355 out.push_str(&" ".repeat(field_indent));
356 }
357
358 out.push_str(c.blue);
360 out.push_str("\"$tag\"");
361 out.push_str(c.reset);
362
363 out.push_str(c.dim);
364 out.push(':');
365 out.push_str(c.reset);
366
367 if pretty {
368 out.push(' ');
369 }
370
371 out.push_str(c.green);
373 out.push('"');
374 out.push_str(&escape_json_string(tag));
375 out.push('"');
376 out.push_str(c.reset);
377
378 if let Some(d) = data {
380 out.push_str(c.dim);
381 out.push(',');
382 out.push_str(c.reset);
383
384 if pretty {
385 out.push('\n');
386 out.push_str(&" ".repeat(field_indent));
387 }
388
389 out.push_str(c.blue);
391 out.push_str("\"$data\"");
392 out.push_str(c.reset);
393
394 out.push_str(c.dim);
395 out.push(':');
396 out.push_str(c.reset);
397
398 if pretty {
399 out.push(' ');
400 }
401
402 format_value(out, d, c, pretty, field_indent);
403 }
404
405 if pretty {
406 out.push('\n');
407 out.push_str(&" ".repeat(indent));
408 }
409
410 out.push_str(c.dim);
411 out.push('}');
412 out.push_str(c.reset);
413}
414
415fn escape_json_string(s: &str) -> String {
416 let mut result = String::with_capacity(s.len());
417 for ch in s.chars() {
418 match ch {
419 '"' => result.push_str("\\\""),
420 '\\' => result.push_str("\\\\"),
421 '\n' => result.push_str("\\n"),
422 '\r' => result.push_str("\\r"),
423 '\t' => result.push_str("\\t"),
424 c if c.is_control() => {
425 result.push_str(&format!("\\u{:04x}", c as u32));
426 }
427 c => result.push(c),
428 }
429 }
430 result
431}