1use std::{
2 fmt, fs,
3 io::{self, StdoutLock, Write},
4 path::{Path, PathBuf},
5 sync::atomic::{AtomicUsize, Ordering},
6 time::{Duration, Instant},
7};
8
9use anstyle::{AnsiColor, Color, RgbColor};
10use anyhow::{anyhow, Context, Result};
11use clap::ValueEnum;
12use serde::{Deserialize, Serialize};
13use tree_sitter::{
14 ffi, InputEdit, Language, LogType, ParseOptions, ParseState, Parser, Point, Range, Tree,
15 TreeCursor,
16};
17
18use super::util;
19use crate::{fuzz::edits::Edit, test::paint};
20
21#[derive(Debug, Default, Serialize)]
22pub struct Stats {
23 pub successful_parses: usize,
24 pub total_parses: usize,
25 pub total_bytes: usize,
26 pub total_duration: Duration,
27}
28
29impl fmt::Display for Stats {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 let duration_us = self.total_duration.as_micros();
32 writeln!(
33 f,
34 "Total parses: {}; successful parses: {}; failed parses: {}; success percentage: {:.2}%; average speed: {} bytes/ms",
35 self.total_parses,
36 self.successful_parses,
37 self.total_parses - self.successful_parses,
38 ((self.successful_parses as f64) / (self.total_parses as f64)) * 100.0,
39 if duration_us != 0 {
40 ((self.total_bytes as u128) * 1_000) / duration_us
41 } else {
42 0
43 }
44 )
45 }
46}
47
48#[derive(Debug, Copy, Clone)]
50pub struct ParseTheme {
51 pub node_kind: Option<Color>,
53 pub node_text: Option<Color>,
55 pub field: Option<Color>,
57 pub row_color: Option<Color>,
59 pub row_color_named: Option<Color>,
61 pub extra: Option<Color>,
63 pub error: Option<Color>,
65 pub missing: Option<Color>,
67 pub line_feed: Option<Color>,
69 pub backtick: Option<Color>,
71 pub literal: Option<Color>,
73}
74
75impl ParseTheme {
76 const GRAY: Color = Color::Rgb(RgbColor(118, 118, 118));
77 const LIGHT_GRAY: Color = Color::Rgb(RgbColor(166, 172, 181));
78 const ORANGE: Color = Color::Rgb(RgbColor(255, 153, 51));
79 const YELLOW: Color = Color::Rgb(RgbColor(219, 219, 173));
80 const GREEN: Color = Color::Rgb(RgbColor(101, 192, 67));
81
82 #[must_use]
83 pub const fn empty() -> Self {
84 Self {
85 node_kind: None,
86 node_text: None,
87 field: None,
88 row_color: None,
89 row_color_named: None,
90 extra: None,
91 error: None,
92 missing: None,
93 line_feed: None,
94 backtick: None,
95 literal: None,
96 }
97 }
98}
99
100impl Default for ParseTheme {
101 fn default() -> Self {
102 Self {
103 node_kind: Some(AnsiColor::BrightCyan.into()),
104 node_text: Some(Self::GRAY),
105 field: Some(AnsiColor::Blue.into()),
106 row_color: Some(AnsiColor::White.into()),
107 row_color_named: Some(AnsiColor::BrightCyan.into()),
108 extra: Some(AnsiColor::BrightMagenta.into()),
109 error: Some(AnsiColor::Red.into()),
110 missing: Some(Self::ORANGE),
111 line_feed: Some(Self::LIGHT_GRAY),
112 backtick: Some(Self::GREEN),
113 literal: Some(Self::YELLOW),
114 }
115 }
116}
117
118#[derive(Debug, Copy, Clone, Deserialize, Serialize)]
119pub struct Rgb(pub u8, pub u8, pub u8);
120
121impl From<Rgb> for RgbColor {
122 fn from(val: Rgb) -> Self {
123 Self(val.0, val.1, val.2)
124 }
125}
126
127#[derive(Debug, Copy, Clone, Default, Deserialize, Serialize)]
128#[serde(rename_all = "kebab-case")]
129pub struct Config {
130 pub parse_theme: Option<ParseThemeRaw>,
131}
132
133#[derive(Debug, Copy, Clone, Default, Deserialize, Serialize)]
134#[serde(rename_all = "kebab-case")]
135pub struct ParseThemeRaw {
136 pub node_kind: Option<Rgb>,
137 pub node_text: Option<Rgb>,
138 pub field: Option<Rgb>,
139 pub row_color: Option<Rgb>,
140 pub row_color_named: Option<Rgb>,
141 pub extra: Option<Rgb>,
142 pub error: Option<Rgb>,
143 pub missing: Option<Rgb>,
144 pub line_feed: Option<Rgb>,
145 pub backtick: Option<Rgb>,
146 pub literal: Option<Rgb>,
147}
148
149impl From<ParseThemeRaw> for ParseTheme {
150 fn from(value: ParseThemeRaw) -> Self {
151 let val_or_default = |val: Option<Rgb>, default: Option<Color>| -> Option<Color> {
152 val.map_or(default, |v| Some(Color::Rgb(v.into())))
153 };
154 let default = Self::default();
155
156 Self {
157 node_kind: val_or_default(value.node_kind, default.node_kind),
158 node_text: val_or_default(value.node_text, default.node_text),
159 field: val_or_default(value.field, default.field),
160 row_color: val_or_default(value.row_color, default.row_color),
161 row_color_named: val_or_default(value.row_color_named, default.row_color_named),
162 extra: val_or_default(value.extra, default.extra),
163 error: val_or_default(value.error, default.error),
164 missing: val_or_default(value.missing, default.missing),
165 line_feed: val_or_default(value.line_feed, default.line_feed),
166 backtick: val_or_default(value.backtick, default.backtick),
167 literal: val_or_default(value.literal, default.literal),
168 }
169 }
170}
171
172#[derive(Copy, Clone, PartialEq, Eq)]
173pub enum ParseOutput {
174 Normal,
175 Quiet,
176 Xml,
177 Cst,
178 Dot,
179}
180
181#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
187pub struct ParsePoint {
188 pub row: usize,
189 pub column: usize,
190}
191
192impl From<Point> for ParsePoint {
193 fn from(value: Point) -> Self {
194 Self {
195 row: value.row,
196 column: value.column,
197 }
198 }
199}
200
201#[derive(Serialize, Default, Debug, Clone)]
202pub struct ParseSummary {
203 pub file: PathBuf,
204 pub successful: bool,
205 pub start: Option<ParsePoint>,
206 pub end: Option<ParsePoint>,
207 pub duration: Option<Duration>,
208 pub bytes: Option<usize>,
209}
210
211impl ParseSummary {
212 #[must_use]
213 pub fn new(path: &Path) -> Self {
214 Self {
215 file: path.to_path_buf(),
216 successful: false,
217 ..Default::default()
218 }
219 }
220}
221
222#[derive(Serialize, Debug, Default)]
223pub struct ParseStats {
224 pub parse_summaries: Vec<ParseSummary>,
225 pub cumulative_stats: Stats,
226}
227
228#[derive(Serialize, ValueEnum, Debug, Clone, Default, Eq, PartialEq)]
229pub enum ParseDebugType {
230 #[default]
231 Quiet,
232 Normal,
233 Pretty,
234}
235
236pub struct ParseFileOptions<'a> {
237 pub edits: &'a [&'a str],
238 pub output: ParseOutput,
239 pub stats: &'a mut ParseStats,
240 pub print_time: bool,
241 pub timeout: u64,
242 pub debug: ParseDebugType,
243 pub debug_graph: bool,
244 pub cancellation_flag: Option<&'a AtomicUsize>,
245 pub encoding: Option<u32>,
246 pub open_log: bool,
247 pub no_ranges: bool,
248 pub parse_theme: &'a ParseTheme,
249}
250
251#[derive(Copy, Clone)]
252pub struct ParseResult {
253 pub successful: bool,
254 pub bytes: usize,
255 pub duration: Option<Duration>,
256}
257
258pub fn parse_file_at_path(
259 parser: &mut Parser,
260 language: &Language,
261 path: &Path,
262 name: &str,
263 max_path_length: usize,
264 opts: &mut ParseFileOptions,
265) -> Result<()> {
266 let mut _log_session = None;
267 parser.set_language(language)?;
268 let mut source_code = fs::read(path).with_context(|| format!("Error reading {name:?}"))?;
269
270 if opts.debug_graph {
272 _log_session = Some(util::log_graphs(parser, "log.html", opts.open_log)?);
273 }
274 else if opts.debug != ParseDebugType::Quiet {
276 let mut curr_version: usize = 0usize;
277 let use_color = std::env::var("NO_COLOR").map_or(true, |v| v != "1");
278 parser.set_logger(Some(Box::new(|log_type, message| {
279 if opts.debug == ParseDebugType::Normal {
280 if log_type == LogType::Lex {
281 write!(&mut io::stderr(), " ").unwrap();
282 }
283 writeln!(&mut io::stderr(), "{message}").unwrap();
284 } else {
285 let colors = &[
286 AnsiColor::White,
287 AnsiColor::Red,
288 AnsiColor::Blue,
289 AnsiColor::Green,
290 AnsiColor::Cyan,
291 AnsiColor::Yellow,
292 ];
293 if message.starts_with("process version:") {
294 let comma_idx = message.find(',').unwrap();
295 curr_version = message["process version:".len()..comma_idx]
296 .parse()
297 .unwrap();
298 }
299 let color = if use_color {
300 Some(colors[curr_version])
301 } else {
302 None
303 };
304 let mut out = if log_type == LogType::Lex {
305 " ".to_string()
306 } else {
307 String::new()
308 };
309 out += &paint(color, message);
310 writeln!(&mut io::stderr(), "{out}").unwrap();
311 }
312 })));
313 }
314
315 let parse_time = Instant::now();
316
317 #[inline(always)]
318 fn is_utf16_le_bom(bom_bytes: &[u8]) -> bool {
319 bom_bytes == [0xFF, 0xFE]
320 }
321
322 #[inline(always)]
323 fn is_utf16_be_bom(bom_bytes: &[u8]) -> bool {
324 bom_bytes == [0xFE, 0xFF]
325 }
326
327 let encoding = match opts.encoding {
328 None if source_code.len() >= 2 => {
329 if is_utf16_le_bom(&source_code[0..2]) {
330 Some(ffi::TSInputEncodingUTF16LE)
331 } else if is_utf16_be_bom(&source_code[0..2]) {
332 Some(ffi::TSInputEncodingUTF16BE)
333 } else {
334 None
335 }
336 }
337 _ => opts.encoding,
338 };
339
340 let start_time = Instant::now();
346 let progress_callback = &mut |_: &ParseState| {
347 if let Some(cancellation_flag) = opts.cancellation_flag {
348 if cancellation_flag.load(Ordering::SeqCst) != 0 {
349 return true;
350 }
351 }
352
353 if opts.timeout > 0 && start_time.elapsed().as_micros() > opts.timeout as u128 {
354 return true;
355 }
356
357 false
358 };
359
360 let parse_opts = ParseOptions::new().progress_callback(progress_callback);
361
362 let tree = match encoding {
363 Some(encoding) if encoding == ffi::TSInputEncodingUTF16LE => {
364 let source_code_utf16 = source_code
365 .chunks_exact(2)
366 .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
367 .collect::<Vec<_>>();
368 parser.parse_utf16_le_with_options(
369 &mut |i, _| {
370 if i < source_code_utf16.len() {
371 &source_code_utf16[i..]
372 } else {
373 &[]
374 }
375 },
376 None,
377 Some(parse_opts),
378 )
379 }
380 Some(encoding) if encoding == ffi::TSInputEncodingUTF16BE => {
381 let source_code_utf16 = source_code
382 .chunks_exact(2)
383 .map(|chunk| u16::from_be_bytes([chunk[0], chunk[1]]))
384 .collect::<Vec<_>>();
385 parser.parse_utf16_be_with_options(
386 &mut |i, _| {
387 if i < source_code_utf16.len() {
388 &source_code_utf16[i..]
389 } else {
390 &[]
391 }
392 },
393 None,
394 Some(parse_opts),
395 )
396 }
397 _ => parser.parse_with_options(
398 &mut |i, _| {
399 if i < source_code.len() {
400 &source_code[i..]
401 } else {
402 &[]
403 }
404 },
405 None,
406 Some(parse_opts),
407 ),
408 };
409 let parse_duration = parse_time.elapsed();
410
411 let stdout = io::stdout();
412 let mut stdout = stdout.lock();
413
414 if let Some(mut tree) = tree {
415 if opts.debug_graph && !opts.edits.is_empty() {
416 println!("BEFORE:\n{}", String::from_utf8_lossy(&source_code));
417 }
418
419 let edit_time = Instant::now();
420 for (i, edit) in opts.edits.iter().enumerate() {
421 let edit = parse_edit_flag(&source_code, edit)?;
422 perform_edit(&mut tree, &mut source_code, &edit)?;
423 tree = parser.parse(&source_code, Some(&tree)).unwrap();
424
425 if opts.debug_graph {
426 println!("AFTER {i}:\n{}", String::from_utf8_lossy(&source_code));
427 }
428 }
429 let edit_duration = edit_time.elapsed();
430
431 parser.stop_printing_dot_graphs();
432
433 let parse_duration_ms = parse_duration.as_micros() as f64 / 1e3;
434 let edit_duration_ms = edit_duration.as_micros() as f64 / 1e3;
435 let mut cursor = tree.walk();
436
437 if opts.output == ParseOutput::Normal {
438 let mut needs_newline = false;
439 let mut indent_level = 0;
440 let mut did_visit_children = false;
441 loop {
442 let node = cursor.node();
443 let is_named = node.is_named();
444 if did_visit_children {
445 if is_named {
446 stdout.write_all(b")")?;
447 needs_newline = true;
448 }
449 if cursor.goto_next_sibling() {
450 did_visit_children = false;
451 } else if cursor.goto_parent() {
452 did_visit_children = true;
453 indent_level -= 1;
454 } else {
455 break;
456 }
457 } else {
458 if is_named {
459 if needs_newline {
460 stdout.write_all(b"\n")?;
461 }
462 for _ in 0..indent_level {
463 stdout.write_all(b" ")?;
464 }
465 let start = node.start_position();
466 let end = node.end_position();
467 if let Some(field_name) = cursor.field_name() {
468 write!(&mut stdout, "{field_name}: ")?;
469 }
470 write!(&mut stdout, "({}", node.kind())?;
471 if !opts.no_ranges {
472 write!(
473 &mut stdout,
474 " [{}, {}] - [{}, {}]",
475 start.row, start.column, end.row, end.column
476 )?;
477 }
478 needs_newline = true;
479 }
480 if cursor.goto_first_child() {
481 did_visit_children = false;
482 indent_level += 1;
483 } else {
484 did_visit_children = true;
485 }
486 }
487 }
488 cursor.reset(tree.root_node());
489 println!();
490 }
491
492 if opts.output == ParseOutput::Cst {
493 let lossy_source_code = String::from_utf8_lossy(&source_code);
494 let total_width = lossy_source_code
495 .lines()
496 .enumerate()
497 .map(|(row, col)| {
498 (row as f64).log10() as usize + (col.len() as f64).log10() as usize + 1
499 })
500 .max()
501 .unwrap_or(1);
502 let mut indent_level = 1;
503 let mut did_visit_children = false;
504 let mut in_error = false;
505 loop {
506 if did_visit_children {
507 if cursor.goto_next_sibling() {
508 did_visit_children = false;
509 } else if cursor.goto_parent() {
510 did_visit_children = true;
511 indent_level -= 1;
512 if !cursor.node().has_error() {
513 in_error = false;
514 }
515 } else {
516 break;
517 }
518 } else {
519 cst_render_node(
520 opts,
521 &mut cursor,
522 &source_code,
523 &mut stdout,
524 total_width,
525 indent_level,
526 in_error,
527 )?;
528 if cursor.goto_first_child() {
529 did_visit_children = false;
530 indent_level += 1;
531 if cursor.node().has_error() {
532 in_error = true;
533 }
534 } else {
535 did_visit_children = true;
536 }
537 }
538 }
539 cursor.reset(tree.root_node());
540 println!();
541 }
542
543 if opts.output == ParseOutput::Xml {
544 let mut needs_newline = false;
545 let mut indent_level = 0;
546 let mut did_visit_children = false;
547 let mut had_named_children = false;
548 let mut tags = Vec::<&str>::new();
549 writeln!(&mut stdout, "<?xml version=\"1.0\"?>")?;
550 loop {
551 let node = cursor.node();
552 let is_named = node.is_named();
553 if did_visit_children {
554 if is_named {
555 let tag = tags.pop();
556 if had_named_children {
557 for _ in 0..indent_level {
558 stdout.write_all(b" ")?;
559 }
560 }
561 write!(&mut stdout, "</{}>", tag.expect("there is a tag"))?;
562 if let Some(parent) = node.parent() {
564 if parent.child(parent.child_count() - 1).unwrap() == node {
565 stdout.write_all(b"\n")?;
566 }
567 }
568 needs_newline = true;
569 }
570 if cursor.goto_next_sibling() {
571 did_visit_children = false;
572 had_named_children = false;
573 } else if cursor.goto_parent() {
574 did_visit_children = true;
575 had_named_children = is_named;
576 indent_level -= 1;
577 if !is_named && needs_newline {
578 stdout.write_all(b"\n")?;
579 for _ in 0..indent_level {
580 stdout.write_all(b" ")?;
581 }
582 }
583 } else {
584 break;
585 }
586 } else {
587 if is_named {
588 if needs_newline {
589 stdout.write_all(b"\n")?;
590 }
591 for _ in 0..indent_level {
592 stdout.write_all(b" ")?;
593 }
594 write!(&mut stdout, "<{}", node.kind())?;
595 if let Some(field_name) = cursor.field_name() {
596 write!(&mut stdout, " field=\"{field_name}\"")?;
597 }
598 let start = node.start_position();
599 let end = node.end_position();
600 write!(&mut stdout, " srow=\"{}\"", start.row)?;
601 write!(&mut stdout, " scol=\"{}\"", start.column)?;
602 write!(&mut stdout, " erow=\"{}\"", end.row)?;
603 write!(&mut stdout, " ecol=\"{}\"", end.column)?;
604 write!(&mut stdout, ">")?;
605 tags.push(node.kind());
606 needs_newline = true;
607 }
608 if cursor.goto_first_child() {
609 did_visit_children = false;
610 had_named_children = false;
611 indent_level += 1;
612 } else {
613 did_visit_children = true;
614 let start = node.start_byte();
615 let end = node.end_byte();
616 let value =
617 std::str::from_utf8(&source_code[start..end]).expect("has a string");
618 if !is_named && needs_newline {
619 stdout.write_all(b"\n")?;
620 for _ in 0..indent_level {
621 stdout.write_all(b" ")?;
622 }
623 }
624 write!(&mut stdout, "{}", html_escape::encode_text(value))?;
625 }
626 }
627 }
628 cursor.reset(tree.root_node());
629 println!();
630 }
631
632 if opts.output == ParseOutput::Dot {
633 util::print_tree_graph(&tree, "log.html", opts.open_log).unwrap();
634 }
635
636 let mut first_error = None;
637 let mut earliest_node_with_error = None;
638 'outer: loop {
639 let node = cursor.node();
640 if node.has_error() {
641 if earliest_node_with_error.is_none() {
642 earliest_node_with_error = Some(node);
643 }
644 if node.is_error() || node.is_missing() {
645 first_error = Some(node);
646 break;
647 }
648
649 if !cursor.goto_first_child() {
654 let earliest = earliest_node_with_error.unwrap();
655 while cursor.goto_parent() {
656 if cursor.node().parent().is_some_and(|p| p == earliest) {
657 while cursor.goto_next_sibling() {
658 let sibling = cursor.node();
659 if sibling.is_error() || sibling.is_missing() {
660 first_error = Some(sibling);
661 break 'outer;
662 }
663 if sibling.has_error() && cursor.goto_first_child() {
664 continue 'outer;
665 }
666 }
667 break;
668 }
669 }
670 break;
671 }
672 } else if !cursor.goto_next_sibling() {
673 break;
674 }
675 }
676
677 if first_error.is_some() || opts.print_time {
678 let path = path.to_string_lossy();
679 write!(
680 &mut stdout,
681 "{:width$}\tParse: {parse_duration_ms:>7.2} ms\t{:>6} bytes/ms",
682 name,
683 (source_code.len() as u128 * 1_000_000) / parse_duration.as_nanos(),
684 width = max_path_length
685 )?;
686 if let Some(node) = first_error {
687 let start = node.start_position();
688 let end = node.end_position();
689 write!(&mut stdout, "\t(")?;
690 if node.is_missing() {
691 if node.is_named() {
692 write!(&mut stdout, "MISSING {}", node.kind())?;
693 } else {
694 write!(
695 &mut stdout,
696 "MISSING \"{}\"",
697 node.kind().replace('\n', "\\n")
698 )?;
699 }
700 } else {
701 write!(&mut stdout, "{}", node.kind())?;
702 }
703 write!(
704 &mut stdout,
705 " [{}, {}] - [{}, {}])",
706 start.row, start.column, end.row, end.column
707 )?;
708 }
709 if !opts.edits.is_empty() {
710 write!(
711 &mut stdout,
712 "\n{:width$}\tEdit: {edit_duration_ms:>7.2} ms",
713 " ".repeat(path.len()),
714 width = max_path_length,
715 )?;
716 }
717 writeln!(&mut stdout)?;
718 }
719
720 opts.stats.parse_summaries.push(ParseSummary {
721 file: path.to_path_buf(),
722 successful: first_error.is_none(),
723 start: Some(tree.root_node().start_position().into()),
724 end: Some(tree.root_node().end_position().into()),
725 duration: Some(parse_duration),
726 bytes: Some(source_code.len()),
727 });
728
729 return Ok(());
730 }
731 parser.stop_printing_dot_graphs();
732
733 if opts.print_time {
734 let duration = parse_time.elapsed();
735 let duration_ms = duration.as_micros() as f64 / 1e3;
736 writeln!(
737 &mut stdout,
738 "{:width$}\tParse: {duration_ms:>7.2} ms\t(timed out)",
739 path.to_str().unwrap(),
740 width = max_path_length
741 )?;
742 }
743
744 opts.stats.parse_summaries.push(ParseSummary {
745 file: path.to_path_buf(),
746 successful: false,
747 start: None,
748 end: None,
749 duration: None,
750 bytes: Some(source_code.len()),
751 });
752
753 Ok(())
754}
755
756const fn escape_invisible(c: char) -> Option<&'static str> {
757 Some(match c {
758 '\n' => "\\n",
759 '\r' => "\\r",
760 '\t' => "\\t",
761 '\0' => "\\0",
762 '\\' => "\\\\",
763 '\x0b' => "\\v",
764 '\x0c' => "\\f",
765 _ => return None,
766 })
767}
768
769fn render_node_text(source: &str) -> String {
770 source
771 .chars()
772 .fold(String::with_capacity(source.len()), |mut acc, c| {
773 if let Some(esc) = escape_invisible(c) {
774 acc.push_str(esc);
775 } else {
776 acc.push(c);
777 }
778 acc
779 })
780}
781
782fn write_node_text(
783 opts: &ParseFileOptions,
784 stdout: &mut StdoutLock<'static>,
785 cursor: &TreeCursor,
786 is_named: bool,
787 source: &str,
788 color: Option<impl Into<Color> + Copy>,
789 text_info: (usize, usize),
790) -> Result<()> {
791 let (total_width, indent_level) = text_info;
792 let (quote, quote_color) = if is_named {
793 ('`', opts.parse_theme.backtick)
794 } else {
795 ('\"', color.map(|c| c.into()))
796 };
797
798 if !is_named {
799 write!(
800 stdout,
801 "{}{}{}",
802 paint(quote_color, &String::from(quote)),
803 paint(color, &render_node_text(source)),
804 paint(quote_color, &String::from(quote)),
805 )?;
806 } else {
807 let multiline = source.contains('\n');
808 for (i, line) in source.split_inclusive('\n').enumerate() {
809 if line.is_empty() {
810 break;
811 }
812 let mut node_range = cursor.node().range();
813 node_range.start_point.row += i;
816 node_range.end_point.row = node_range.start_point.row;
817 node_range.end_point.column = line.len()
818 + if i == 0 {
819 node_range.start_point.column
820 } else {
821 0
822 };
823 let formatted_line = render_line_feed(line, opts);
824 if !opts.no_ranges {
825 write!(
826 stdout,
827 "{}{}{}{}{}{}",
828 if multiline { "\n" } else { "" },
829 if multiline {
830 render_node_range(opts, cursor, is_named, true, total_width, node_range)
831 } else {
832 String::new()
833 },
834 if multiline {
835 " ".repeat(indent_level + 1)
836 } else {
837 String::new()
838 },
839 paint(quote_color, &String::from(quote)),
840 &paint(color, &render_node_text(&formatted_line)),
841 paint(quote_color, &String::from(quote)),
842 )?;
843 } else {
844 write!(
845 stdout,
846 "\n{}{}{}{}",
847 " ".repeat(indent_level + 1),
848 paint(quote_color, &String::from(quote)),
849 &paint(color, &render_node_text(&formatted_line)),
850 paint(quote_color, &String::from(quote)),
851 )?;
852 }
853 }
854 }
855
856 Ok(())
857}
858
859fn render_line_feed(source: &str, opts: &ParseFileOptions) -> String {
860 if cfg!(windows) {
861 source.replace("\r\n", &paint(opts.parse_theme.line_feed, "\r\n"))
862 } else {
863 source.replace('\n', &paint(opts.parse_theme.line_feed, "\n"))
864 }
865}
866
867fn render_node_range(
868 opts: &ParseFileOptions,
869 cursor: &TreeCursor,
870 is_named: bool,
871 is_multiline: bool,
872 total_width: usize,
873 range: Range,
874) -> String {
875 let has_field_name = cursor.field_name().is_some();
876 let range_color = if is_named && !is_multiline && !has_field_name {
877 opts.parse_theme.row_color_named
878 } else {
879 opts.parse_theme.row_color
880 };
881
882 let remaining_width_start = (total_width
883 - (range.start_point.row as f64).log10() as usize
884 - (range.start_point.column as f64).log10() as usize)
885 .max(1);
886 let remaining_width_end = (total_width
887 - (range.end_point.row as f64).log10() as usize
888 - (range.end_point.column as f64).log10() as usize)
889 .max(1);
890 paint(
891 range_color,
892 &format!(
893 "{}:{}{:remaining_width_start$}- {}:{}{:remaining_width_end$}",
894 range.start_point.row,
895 range.start_point.column,
896 ' ',
897 range.end_point.row,
898 range.end_point.column,
899 ' ',
900 ),
901 )
902}
903
904fn cst_render_node(
905 opts: &ParseFileOptions,
906 cursor: &mut TreeCursor,
907 source_code: &[u8],
908 stdout: &mut StdoutLock<'static>,
909 total_width: usize,
910 indent_level: usize,
911 in_error: bool,
912) -> Result<()> {
913 let node = cursor.node();
914 let is_named = node.is_named();
915 if !opts.no_ranges {
916 write!(
917 stdout,
918 "{}",
919 render_node_range(opts, cursor, is_named, false, total_width, node.range())
920 )?;
921 }
922 write!(
923 stdout,
924 "{}{}",
925 " ".repeat(indent_level),
926 if in_error && !node.has_error() {
927 " "
928 } else {
929 ""
930 }
931 )?;
932 if is_named {
933 if let Some(field_name) = cursor.field_name() {
934 write!(
935 stdout,
936 "{}",
937 paint(opts.parse_theme.field, &format!("{field_name}: "))
938 )?;
939 }
940
941 if node.has_error() || node.is_error() {
942 write!(stdout, "{}", paint(opts.parse_theme.error, "•"))?;
943 }
944
945 let kind_color = if node.is_error() {
946 opts.parse_theme.error
947 } else if node.is_extra() || node.parent().is_some_and(|p| p.is_extra() && !p.is_error()) {
948 opts.parse_theme.extra
949 } else {
950 opts.parse_theme.node_kind
951 };
952 write!(stdout, "{} ", paint(kind_color, node.kind()))?;
953
954 if node.child_count() == 0 {
955 write_node_text(
957 opts,
958 stdout,
959 cursor,
960 is_named,
961 &String::from_utf8_lossy(&source_code[node.start_byte()..node.end_byte()]),
962 opts.parse_theme.node_text,
963 (total_width, indent_level),
964 )?;
965 }
966 } else if node.is_missing() {
967 write!(stdout, "{}: ", paint(opts.parse_theme.missing, "MISSING"))?;
968 write!(
969 stdout,
970 "\"{}\"",
971 paint(opts.parse_theme.missing, node.kind())
972 )?;
973 } else {
974 write_node_text(
976 opts,
977 stdout,
978 cursor,
979 is_named,
980 node.kind(),
981 opts.parse_theme.literal,
982 (total_width, indent_level),
983 )?;
984 }
985 writeln!(stdout)?;
986
987 Ok(())
988}
989
990pub fn perform_edit(tree: &mut Tree, input: &mut Vec<u8>, edit: &Edit) -> Result<InputEdit> {
991 let start_byte = edit.position;
992 let old_end_byte = edit.position + edit.deleted_length;
993 let new_end_byte = edit.position + edit.inserted_text.len();
994 let start_position = position_for_offset(input, start_byte)?;
995 let old_end_position = position_for_offset(input, old_end_byte)?;
996 input.splice(start_byte..old_end_byte, edit.inserted_text.iter().copied());
997 let new_end_position = position_for_offset(input, new_end_byte)?;
998 let edit = InputEdit {
999 start_byte,
1000 old_end_byte,
1001 new_end_byte,
1002 start_position,
1003 old_end_position,
1004 new_end_position,
1005 };
1006 tree.edit(&edit);
1007 Ok(edit)
1008}
1009
1010fn parse_edit_flag(source_code: &[u8], flag: &str) -> Result<Edit> {
1011 let error = || {
1012 anyhow!(concat!(
1013 "Invalid edit string '{}'. ",
1014 "Edit strings must match the pattern '<START_BYTE_OR_POSITION> <REMOVED_LENGTH> <NEW_TEXT>'"
1015 ), flag)
1016 };
1017
1018 let mut parts = flag.split(' ');
1023 let position = parts.next().ok_or_else(error)?;
1024 let deleted_length = parts.next().ok_or_else(error)?;
1025 let inserted_text = parts.collect::<Vec<_>>().join(" ").into_bytes();
1026
1027 let position = if position == "$" {
1029 source_code.len()
1030 } else if position.contains(',') {
1031 let mut parts = position.split(',');
1032 let row = parts.next().ok_or_else(error)?;
1033 let row = row.parse::<usize>().map_err(|_| error())?;
1034 let column = parts.next().ok_or_else(error)?;
1035 let column = column.parse::<usize>().map_err(|_| error())?;
1036 offset_for_position(source_code, Point { row, column })?
1037 } else {
1038 position.parse::<usize>().map_err(|_| error())?
1039 };
1040
1041 let deleted_length = deleted_length.parse::<usize>().map_err(|_| error())?;
1043
1044 Ok(Edit {
1045 position,
1046 deleted_length,
1047 inserted_text,
1048 })
1049}
1050
1051pub fn offset_for_position(input: &[u8], position: Point) -> Result<usize> {
1052 let mut row = 0;
1053 let mut offset = 0;
1054 let mut iter = memchr::memchr_iter(b'\n', input);
1055 loop {
1056 if let Some(pos) = iter.next() {
1057 if row < position.row {
1058 row += 1;
1059 offset = pos;
1060 continue;
1061 }
1062 }
1063 offset += 1;
1064 break;
1065 }
1066 if position.row - row > 0 {
1067 return Err(anyhow!("Failed to address a row: {}", position.row));
1068 }
1069 if let Some(pos) = iter.next() {
1070 if (pos - offset < position.column) || (input[offset] == b'\n' && position.column > 0) {
1071 return Err(anyhow!("Failed to address a column: {}", position.column));
1072 }
1073 } else if input.len() - offset < position.column {
1074 return Err(anyhow!("Failed to address a column over the end"));
1075 }
1076 Ok(offset + position.column)
1077}
1078
1079pub fn position_for_offset(input: &[u8], offset: usize) -> Result<Point> {
1080 if offset > input.len() {
1081 return Err(anyhow!("Failed to address an offset: {offset}"));
1082 }
1083 let mut result = Point { row: 0, column: 0 };
1084 let mut last = 0;
1085 for pos in memchr::memchr_iter(b'\n', &input[..offset]) {
1086 result.row += 1;
1087 last = pos;
1088 }
1089 result.column = if result.row > 0 {
1090 offset - last - 1
1091 } else {
1092 offset
1093 };
1094 Ok(result)
1095}