tree_sitter_cli/
parse.rs

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/// Sets the color used in the output of `tree-sitter parse --cst`
49#[derive(Debug, Copy, Clone)]
50pub struct ParseTheme {
51    /// The color of node kinds
52    pub node_kind: Option<Color>,
53    /// The color of text associated with a node
54    pub node_text: Option<Color>,
55    /// The color of node fields
56    pub field: Option<Color>,
57    /// The color of the range information for unnamed nodes
58    pub row_color: Option<Color>,
59    /// The color of the range information for named nodes
60    pub row_color_named: Option<Color>,
61    /// The color of extra nodes
62    pub extra: Option<Color>,
63    /// The color of ERROR nodes
64    pub error: Option<Color>,
65    /// The color of MISSING nodes and their associated text
66    pub missing: Option<Color>,
67    /// The color of newline characters
68    pub line_feed: Option<Color>,
69    /// The color of backticks
70    pub backtick: Option<Color>,
71    /// The color of literals
72    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/// A position in a multi-line text document, in terms of rows and columns.
182///
183/// Rows and columns are zero-based.
184///
185/// This serves as a serializable wrapper for `Point`
186#[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    // Render an HTML graph if `--debug-graph` was passed
271    if opts.debug_graph {
272        _log_session = Some(util::log_graphs(parser, "log.html", opts.open_log)?);
273    }
274    // Log to stderr if `--debug` was passed
275    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    // If the `--cancel` flag was passed, then cancel the parse
341    // when the user types a newline.
342    //
343    // Additionally, if the `--time` flag was passed, end the parse
344    // after the specified number of microseconds.
345    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                        // we only write a line in the case where it's the last sibling
563                        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 there's no more children, even though some outer node has an error,
650                // then that means that the first error is hidden, but the later error could be
651                // visible. So, we walk back up to the child of the first node with an error,
652                // and then check its siblings for errors.
653                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            // For each line of text, adjust the row by shifting it down `i` rows,
814            // and adjust the column by setting it to the length of *this* line.
815            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            // Node text from a pattern or external scanner
956            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        // Terminal literals, like "fn"
975        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    // Three whitespace-separated parts:
1019    // * edit position
1020    // * deleted length
1021    // * inserted text
1022    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    // Position can either be a byte_offset or row,column pair, separated by a comma
1028    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    // Deleted length must be a byte count.
1042    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}