oak_pretty_print/comment/
mod.rs1#[derive(Debug, Clone, PartialEq)]
3pub struct Position {
4 pub line: usize,
5 pub column: usize,
6 pub offset: usize,
7}
8
9#[derive(Debug, Clone, PartialEq)]
11pub struct SourceSpan {
12 pub start: Position,
13 pub end: Position,
14}
15
16#[derive(Debug, Clone, PartialEq)]
18pub enum CommentKind {
19 Line,
21 Block,
23 Doc,
25}
26
27#[derive(Debug, Clone, PartialEq)]
29pub struct Comment {
30 pub kind: CommentKind,
32 pub content: String,
34 pub span: SourceSpan,
36 pub is_trailing: bool,
38 pub indent_level: usize,
40}
41
42impl Comment {
43 pub fn new(kind: CommentKind, content: String, span: SourceSpan) -> Self {
44 Self { kind, content, span, is_trailing: false, indent_level: 0 }
45 }
46
47 pub fn line(content: String, span: SourceSpan) -> Self {
48 Self::new(CommentKind::Line, content, span)
49 }
50
51 pub fn block(content: String, span: SourceSpan) -> Self {
52 Self::new(CommentKind::Block, content, span)
53 }
54
55 pub fn doc(content: String, span: SourceSpan) -> Self {
56 Self::new(CommentKind::Doc, content, span)
57 }
58
59 pub fn with_trailing(mut self, is_trailing: bool) -> Self {
60 self.is_trailing = is_trailing;
61 self
62 }
63
64 pub fn with_indent_level(mut self, level: usize) -> Self {
65 self.indent_level = level;
66 self
67 }
68
69 pub fn formatted_text(&self, indent: &str) -> String {
71 let prefix = match self.kind {
72 CommentKind::Line => "//",
73 CommentKind::Block => "/*",
74 CommentKind::Doc => "///",
75 };
76
77 let suffix = match self.kind {
78 CommentKind::Block => " */",
79 _ => "",
80 };
81
82 if self.content.trim().is_empty() {
83 format!("{}{}{}", prefix, self.content, suffix)
84 }
85 else {
86 match self.kind {
87 CommentKind::Line | CommentKind::Doc => {
88 format!("{} {}", prefix, self.content.trim())
89 }
90 CommentKind::Block => {
91 if self.content.contains('\n') {
92 let lines: Vec<&str> = self.content.lines().collect();
94 let mut result = String::new();
95 result.push_str("/*\n");
96 for line in lines {
97 result.push_str(indent);
98 result.push_str(" * ");
99 result.push_str(line.trim());
100 result.push('\n');
101 }
102 result.push_str(indent);
103 result.push_str(" */");
104 result
105 }
106 else {
107 format!("/* {} */", self.content.trim())
109 }
110 }
111 }
112 }
113 }
114}
115
116#[derive(Debug, Clone, PartialEq)]
118pub struct CommentCollector {
119 comments: Vec<Comment>,
120}
121
122impl CommentCollector {
123 pub fn new() -> Self {
124 Self { comments: Vec::new() }
125 }
126
127 pub fn add_comment(&mut self, comment: Comment) {
128 self.comments.push(comment);
129 }
130
131 pub fn comments(&self) -> &[Comment] {
132 &self.comments
133 }
134
135 pub fn comments_mut(&mut self) -> &mut Vec<Comment> {
136 &mut self.comments
137 }
138
139 pub fn comments_in_range(&self, start: Position, end: Position) -> Vec<&Comment> {
141 self.comments.iter().filter(|comment| comment.span.start.offset >= start.offset && comment.span.end.offset <= end.offset).collect()
142 }
143
144 pub fn comments_before(&self, position: Position) -> Vec<&Comment> {
146 self.comments.iter().filter(|comment| comment.span.end.offset <= position.offset).collect()
147 }
148
149 pub fn comments_after(&self, position: Position) -> Vec<&Comment> {
151 self.comments.iter().filter(|comment| comment.span.start.offset >= position.offset).collect()
152 }
153
154 pub fn trailing_comments(&self) -> Vec<&Comment> {
156 self.comments.iter().filter(|comment| comment.is_trailing).collect()
157 }
158
159 pub fn clear(&mut self) {
161 self.comments.clear();
162 }
163
164 pub fn sort_by_position(&mut self) {
166 self.comments.sort_by(|a, b| a.span.start.offset.cmp(&b.span.start.offset));
167 }
168}
169
170impl Default for CommentCollector {
171 fn default() -> Self {
172 Self::new()
173 }
174}
175
176#[derive(Debug, Clone)]
178pub struct CommentProcessor {
179 collector: CommentCollector,
180 preserve_comments: bool,
181 format_comments: bool,
182}
183
184impl CommentProcessor {
185 pub fn new() -> Self {
186 Self { collector: CommentCollector::new(), preserve_comments: true, format_comments: true }
187 }
188
189 pub fn with_preserve_comments(mut self, preserve: bool) -> Self {
190 self.preserve_comments = preserve;
191 self
192 }
193
194 pub fn with_format_comments(mut self, format: bool) -> Self {
195 self.format_comments = format;
196 self
197 }
198
199 pub fn collector(&self) -> &CommentCollector {
200 &self.collector
201 }
202
203 pub fn collector_mut(&mut self) -> &mut CommentCollector {
204 &mut self.collector
205 }
206
207 pub fn process_comments(&self, comments: &[Comment], indent: &str) -> Vec<String> {
209 if !self.preserve_comments {
210 return Vec::new();
211 }
212
213 comments
214 .iter()
215 .map(|comment| {
216 if self.format_comments {
217 comment.formatted_text(indent)
218 }
219 else {
220 comment.content.clone()
222 }
223 })
224 .collect()
225 }
226
227 pub fn insert_comments_at_position(&self, output: &mut String, position: Position, indent: &str) {
229 let comments = self.collector.comments_before(position);
230 for comment in comments {
231 if !comment.is_trailing {
232 output.push_str(indent);
233 output.push_str(&comment.formatted_text(indent));
234 output.push('\n');
235 }
236 }
237 }
238
239 pub fn insert_trailing_comment(&self, output: &mut String, position: Position) {
241 let comments = self.collector.comments_after(position);
242 for comment in comments {
243 if comment.is_trailing {
244 output.push(' ');
245 output.push_str(&comment.formatted_text(""));
246 break; }
248 }
249 }
250}
251
252impl Default for CommentProcessor {
253 fn default() -> Self {
254 Self::new()
255 }
256}