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
96 | TOKEN_TREE => Some(FoldKind::Block),
101 fn has_visibility(node: &SyntaxNode) -> bool {
102 ast::Module::cast(node.clone()).and_then(|m| m.visibility()).is_some()
105 fn contiguous_range_for_group(
107 visited: &mut FxHashSet<SyntaxNode>,
108 ) -> Option<TextRange> {
109 contiguous_range_for_group_unless(first, |_| false, visited)
112 fn contiguous_range_for_group_unless(
114 unless: impl Fn(&SyntaxNode) -> bool,
115 visited: &mut FxHashSet<SyntaxNode>,
116 ) -> Option<TextRange> {
117 visited.insert(first.clone());
119 let mut last = first.clone();
120 for element in first.siblings_with_tokens(Direction::Next) {
121 let node = match element {
122 NodeOrToken::Token(token) => {
123 if let Some(ws) = ast::Whitespace::cast(token) {
124 if !ws.spans_multiple_lines() {
125 // Ignore whitespace without blank lines
129 // There is a blank line or another token, which means that the
133 NodeOrToken::Node(node) => node,
136 // Stop if we find a node that doesn't belong to the group
137 if node.kind() != first.kind() || unless(&node) {
141 visited.insert(node.clone());
146 Some(TextRange::new(first.text_range().start(), last.text_range().end()))
148 // The group consists of only one element, therefore it cannot be folded
153 fn contiguous_range_for_comment(
155 visited: &mut FxHashSet<ast::Comment>,
156 ) -> Option<TextRange> {
157 visited.insert(first.clone());
159 // Only fold comments of the same flavor
160 let group_kind = first.kind();
161 if !group_kind.shape.is_line() {
165 let mut last = first.clone();
166 for element in first.syntax().siblings_with_tokens(Direction::Next) {
168 NodeOrToken::Token(token) => {
169 if let Some(ws) = ast::Whitespace::cast(token.clone()) {
170 if !ws.spans_multiple_lines() {
171 // Ignore whitespace without blank lines
175 if let Some(c) = ast::Comment::cast(token) {
176 if c.kind() == group_kind {
177 visited.insert(c.clone());
182 // The comment group ends because either:
183 // * An element of a different kind was reached
184 // * A comment of a different flavor was reached
187 NodeOrToken::Node(_) => break,
192 Some(TextRange::new(first.syntax().text_range().start(), last.syntax().text_range().end()))
194 // The group consists of only one element, therefore it cannot be folded
201 use test_utils::extract_tags;
205 fn check(ra_fixture: &str) {
206 let (ranges, text) = extract_tags(ra_fixture, "fold");
208 let parse = SourceFile::parse(&text);
209 let folds = folding_ranges(&parse.tree());
213 "The amount of folds is different than the expected amount"
216 for (fold, (range, attr)) in folds.iter().zip(ranges.into_iter()) {
217 assert_eq!(fold.range.start(), range.start());
218 assert_eq!(fold.range.end(), range.end());
220 let kind = match fold.kind {
221 FoldKind::Comment => "comment",
222 FoldKind::Imports => "imports",
223 FoldKind::Mods => "mods",
224 FoldKind::Block => "block",
225 FoldKind::ArgList => "arglist",
227 assert_eq!(kind, &attr.unwrap());
232 fn test_fold_comments() {
235 <fold comment>// Hello
236 // this is a multiline
242 fn main() <fold block>{
243 <fold comment>// We should
247 <fold comment>//! But this one is different
248 //! because it has another flavor</fold>
249 <fold comment>/* As does this
250 multiline comment */</fold>
256 fn test_fold_imports() {
259 <fold imports>use std::<fold block>{
265 fn main() <fold block>{
271 fn test_fold_mods() {
276 <fold mods>mod after_pub;
277 mod after_pub_next;</fold>
279 <fold mods>mod before_pub;
280 mod before_pub_next;</fold>
283 mod not_folding_single;
285 pub not_folding_single_next;
287 <fold mods>#[cfg(test)]
289 mod with_attribute_next;</fold>
291 fn main() <fold block>{
297 fn test_fold_import_groups() {
300 <fold imports>use std::str;
302 use std::io as iop;</fold>
304 <fold imports>use std::mem;
307 use std::collections::HashMap;
308 // Some random comment
309 use std::collections::VecDeque;
311 fn main() <fold block>{
317 fn test_fold_import_and_groups() {
320 <fold imports>use std::str;
322 use std::io as iop;</fold>
324 <fold imports>use std::mem;
327 <fold imports>use std::collections::<fold block>{
331 // Some random comment
333 fn main() <fold block>{
339 fn test_folds_macros() {
342 macro_rules! foo <fold block>{
343 ($($tt:tt)*) => { $($tt)* }
350 fn test_fold_match_arms() {
353 fn main() <fold block>{
354 match 0 <fold block>{
363 fn fold_big_calls() {
366 fn main() <fold block>{
367 frobnicate<fold arglist>(