1 //! FIXME: write short doc here
3 use rustc_hash::FxHashSet;
6 ast::{self, AstNode, AstToken, VisibilityOwner},
7 Direction, NodeOrToken, SourceFile,
12 #[derive(Debug, PartialEq, Eq)]
27 pub(crate) fn folding_ranges(file: &SourceFile) -> Vec<Fold> {
29 let mut visited_comments = FxHashSet::default();
30 let mut visited_imports = FxHashSet::default();
31 let mut visited_mods = FxHashSet::default();
33 for element in file.syntax().descendants_with_tokens() {
34 // Fold items that span multiple lines
35 if let Some(kind) = fold_kind(element.kind()) {
36 let is_multiline = match &element {
37 NodeOrToken::Node(node) => node.text().contains_char('\n'),
38 NodeOrToken::Token(token) => token.text().contains('\n'),
41 res.push(Fold { range: element.text_range(), kind });
47 NodeOrToken::Token(token) => {
48 // Fold groups of comments
49 if let Some(comment) = ast::Comment::cast(token) {
50 if !visited_comments.contains(&comment) {
52 contiguous_range_for_comment(comment, &mut visited_comments)
54 res.push(Fold { range, kind: FoldKind::Comment })
59 NodeOrToken::Node(node) => {
60 // Fold groups of imports
61 if node.kind() == USE_ITEM && !visited_imports.contains(&node) {
62 if let Some(range) = contiguous_range_for_group(&node, &mut visited_imports) {
63 res.push(Fold { range, kind: FoldKind::Imports })
67 // Fold groups of mods
68 if node.kind() == MODULE && !has_visibility(&node) && !visited_mods.contains(&node)
71 contiguous_range_for_group_unless(&node, has_visibility, &mut visited_mods)
73 res.push(Fold { range, kind: FoldKind::Mods })
83 fn fold_kind(kind: SyntaxKind) -> Option<FoldKind> {
85 COMMENT => Some(FoldKind::Comment),
86 USE_ITEM => Some(FoldKind::Imports),
87 ARG_LIST => Some(FoldKind::ArgList),
89 | RECORD_FIELD_PAT_LIST
97 | TOKEN_TREE => Some(FoldKind::Block),
102 fn has_visibility(node: &SyntaxNode) -> bool {
103 ast::Module::cast(node.clone()).and_then(|m| m.visibility()).is_some()
106 fn contiguous_range_for_group(
108 visited: &mut FxHashSet<SyntaxNode>,
109 ) -> Option<TextRange> {
110 contiguous_range_for_group_unless(first, |_| false, visited)
113 fn contiguous_range_for_group_unless(
115 unless: impl Fn(&SyntaxNode) -> bool,
116 visited: &mut FxHashSet<SyntaxNode>,
117 ) -> Option<TextRange> {
118 visited.insert(first.clone());
120 let mut last = first.clone();
121 for element in first.siblings_with_tokens(Direction::Next) {
122 let node = match element {
123 NodeOrToken::Token(token) => {
124 if let Some(ws) = ast::Whitespace::cast(token) {
125 if !ws.spans_multiple_lines() {
126 // Ignore whitespace without blank lines
130 // There is a blank line or another token, which means that the
134 NodeOrToken::Node(node) => node,
137 // Stop if we find a node that doesn't belong to the group
138 if node.kind() != first.kind() || unless(&node) {
142 visited.insert(node.clone());
147 Some(TextRange::new(first.text_range().start(), last.text_range().end()))
149 // The group consists of only one element, therefore it cannot be folded
154 fn contiguous_range_for_comment(
156 visited: &mut FxHashSet<ast::Comment>,
157 ) -> Option<TextRange> {
158 visited.insert(first.clone());
160 // Only fold comments of the same flavor
161 let group_kind = first.kind();
162 if !group_kind.shape.is_line() {
166 let mut last = first.clone();
167 for element in first.syntax().siblings_with_tokens(Direction::Next) {
169 NodeOrToken::Token(token) => {
170 if let Some(ws) = ast::Whitespace::cast(token.clone()) {
171 if !ws.spans_multiple_lines() {
172 // Ignore whitespace without blank lines
176 if let Some(c) = ast::Comment::cast(token) {
177 if c.kind() == group_kind {
178 visited.insert(c.clone());
183 // The comment group ends because either:
184 // * An element of a different kind was reached
185 // * A comment of a different flavor was reached
188 NodeOrToken::Node(_) => break,
193 Some(TextRange::new(first.syntax().text_range().start(), last.syntax().text_range().end()))
195 // The group consists of only one element, therefore it cannot be folded
202 use test_utils::extract_tags;
206 fn check(ra_fixture: &str) {
207 let (ranges, text) = extract_tags(ra_fixture, "fold");
209 let parse = SourceFile::parse(&text);
210 let folds = folding_ranges(&parse.tree());
214 "The amount of folds is different than the expected amount"
217 for (fold, (range, attr)) in folds.iter().zip(ranges.into_iter()) {
218 assert_eq!(fold.range.start(), range.start());
219 assert_eq!(fold.range.end(), range.end());
221 let kind = match fold.kind {
222 FoldKind::Comment => "comment",
223 FoldKind::Imports => "imports",
224 FoldKind::Mods => "mods",
225 FoldKind::Block => "block",
226 FoldKind::ArgList => "arglist",
228 assert_eq!(kind, &attr.unwrap());
233 fn test_fold_comments() {
236 <fold comment>// Hello
237 // this is a multiline
243 fn main() <fold block>{
244 <fold comment>// We should
248 <fold comment>//! But this one is different
249 //! because it has another flavor</fold>
250 <fold comment>/* As does this
251 multiline comment */</fold>
257 fn test_fold_imports() {
260 <fold imports>use std::<fold block>{
266 fn main() <fold block>{
272 fn test_fold_mods() {
277 <fold mods>mod after_pub;
278 mod after_pub_next;</fold>
280 <fold mods>mod before_pub;
281 mod before_pub_next;</fold>
284 mod not_folding_single;
286 pub not_folding_single_next;
288 <fold mods>#[cfg(test)]
290 mod with_attribute_next;</fold>
292 fn main() <fold block>{
298 fn test_fold_import_groups() {
301 <fold imports>use std::str;
303 use std::io as iop;</fold>
305 <fold imports>use std::mem;
308 use std::collections::HashMap;
309 // Some random comment
310 use std::collections::VecDeque;
312 fn main() <fold block>{
318 fn test_fold_import_and_groups() {
321 <fold imports>use std::str;
323 use std::io as iop;</fold>
325 <fold imports>use std::mem;
328 <fold imports>use std::collections::<fold block>{
332 // Some random comment
334 fn main() <fold block>{
340 fn test_folds_macros() {
343 macro_rules! foo <fold block>{
344 ($($tt:tt)*) => { $($tt)* }
351 fn test_fold_match_arms() {
354 fn main() <fold block>{
355 match 0 <fold block>{
365 fn fold_big_calls() {
368 fn main() <fold block>{
369 frobnicate<fold arglist>(
380 fn fold_record_literals() {
383 const _: S = S <fold block>{