1use std::{borrow::Cow, fmt, io};
4
5use crate::{tree_sitter::SyntaxNode, FormatterResult};
6
7fn escape(input: &str) -> Cow<str> {
10 let mut buffer = String::new();
12
13 let mut start: usize = 0;
14 let length = input.len();
15
16 let append = |buffer: &mut String, from: &mut usize, to: usize, suffix: &str| {
17 if buffer.is_empty() {
19 buffer.reserve(length * 3);
23 }
24
25 *buffer += &input[*from..to];
28 *buffer += suffix;
29
30 *from = to + 1;
32 };
33
34 for (idx, current) in input.chars().enumerate() {
35 match current {
36 '\n' => append(&mut buffer, &mut start, idx, r#"\\n"#),
38 '\t' => append(&mut buffer, &mut start, idx, r#"\\t"#),
39
40 otherwise => {
41 let mut escaped = otherwise.escape_default().peekable();
44 if escaped.peek() == Some(&'\\') {
45 append(
46 &mut buffer,
47 &mut start,
48 idx,
49 &otherwise.escape_default().to_string(),
50 );
51 }
52 }
53 }
54 }
55
56 if buffer.is_empty() {
57 input.into()
58 } else {
59 buffer += &input[start..length];
61 buffer.into()
62 }
63}
64
65impl fmt::Display for SyntaxNode {
66 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
67 let shape = if self.is_named { "ellipse" } else { "box" };
68
69 writeln!(
70 f,
71 " {} [label=\"{}\", shape={shape}];",
72 self.id,
73 escape(&self.kind)
74 )?;
75
76 for child in &self.children {
77 writeln!(f, " {} -- {};", self.id, child.id)?;
78 write!(f, "{child}")?;
79 }
80
81 Ok(())
82 }
83}
84
85pub fn write(output: &mut dyn io::Write, root: &SyntaxNode) -> FormatterResult<()> {
87 writeln!(output, "graph {{")?;
88 write!(output, "{root}")?;
89 writeln!(output, "}}")?;
90
91 Ok(())
92}
93
94#[cfg(test)]
95mod test {
96 use super::escape;
97 use std::borrow::Cow;
98
99 #[test]
100 fn double_escape() {
101 assert_eq!(escape("foo"), "foo");
103 assert_eq!(escape("'"), r#"\'"#);
104 assert_eq!(escape("\n"), r#"\\n"#);
105 assert_eq!(escape("\t"), r#"\\t"#);
106 assert_eq!(
107 escape("Here's something\nlonger"),
108 r#"Here\'s something\\nlonger"#
109 );
110 }
111
112 #[test]
113 fn escape_borrowed() {
114 match escape("foo") {
115 Cow::Borrowed("foo") => (),
116 _ => panic!("Expected a borrowed, unmodified str"),
117 }
118 }
119
120 #[test]
121 fn escape_owned() {
122 match escape("'") {
123 Cow::Owned(s) => assert_eq!(s, r#"\'"#),
124 _ => panic!("Expected an owned, escaped string"),
125 }
126 }
127}