From 47c501bcfbd24356009d1db5b4dbe2e27a148d9a Mon Sep 17 00:00:00 2001 From: Sergey Parilin Date: Fri, 22 Mar 2019 13:29:58 +0300 Subject: Move folding_ranges to ra_ide_api --- crates/ra_ide_api/src/folding_ranges.rs | 326 ++++++++++++++++++++++++++ crates/ra_ide_api/src/lib.rs | 6 +- crates/ra_ide_api_light/src/folding_ranges.rs | 326 -------------------------- crates/ra_ide_api_light/src/lib.rs | 2 - 4 files changed, 330 insertions(+), 330 deletions(-) create mode 100644 crates/ra_ide_api/src/folding_ranges.rs delete mode 100644 crates/ra_ide_api_light/src/folding_ranges.rs (limited to 'crates') diff --git a/crates/ra_ide_api/src/folding_ranges.rs b/crates/ra_ide_api/src/folding_ranges.rs new file mode 100644 index 000000000..b96145f05 --- /dev/null +++ b/crates/ra_ide_api/src/folding_ranges.rs @@ -0,0 +1,326 @@ +use rustc_hash::FxHashSet; + +use ra_syntax::{ + AstNode, Direction, SourceFile, SyntaxNode, TextRange, + SyntaxKind::{self, *}, + ast::{self, VisibilityOwner}, +}; + +#[derive(Debug, PartialEq, Eq)] +pub enum FoldKind { + Comment, + Imports, + Mods, + Block, +} + +#[derive(Debug)] +pub struct Fold { + pub range: TextRange, + pub kind: FoldKind, +} + +pub(crate) fn folding_ranges(file: &SourceFile) -> Vec { + let mut res = vec![]; + let mut visited_comments = FxHashSet::default(); + let mut visited_imports = FxHashSet::default(); + let mut visited_mods = FxHashSet::default(); + + for node in file.syntax().descendants() { + // Fold items that span multiple lines + if let Some(kind) = fold_kind(node.kind()) { + if node.text().contains('\n') { + res.push(Fold { range: node.range(), kind }); + } + } + + // Fold groups of comments + if node.kind() == COMMENT && !visited_comments.contains(&node) { + if let Some(range) = contiguous_range_for_comment(node, &mut visited_comments) { + res.push(Fold { range, kind: FoldKind::Comment }) + } + } + + // Fold groups of imports + if node.kind() == USE_ITEM && !visited_imports.contains(&node) { + if let Some(range) = contiguous_range_for_group(node, &mut visited_imports) { + res.push(Fold { range, kind: FoldKind::Imports }) + } + } + + // Fold groups of mods + if node.kind() == MODULE && !has_visibility(&node) && !visited_mods.contains(&node) { + if let Some(range) = + contiguous_range_for_group_unless(node, has_visibility, &mut visited_mods) + { + res.push(Fold { range, kind: FoldKind::Mods }) + } + } + } + + res +} + +fn fold_kind(kind: SyntaxKind) -> Option { + match kind { + COMMENT => Some(FoldKind::Comment), + USE_ITEM => Some(FoldKind::Imports), + NAMED_FIELD_DEF_LIST | FIELD_PAT_LIST | ITEM_LIST | EXTERN_ITEM_LIST | USE_TREE_LIST + | BLOCK | ENUM_VARIANT_LIST | TOKEN_TREE => Some(FoldKind::Block), + _ => None, + } +} + +fn has_visibility(node: &SyntaxNode) -> bool { + ast::Module::cast(node).and_then(|m| m.visibility()).is_some() +} + +fn contiguous_range_for_group<'a>( + first: &'a SyntaxNode, + visited: &mut FxHashSet<&'a SyntaxNode>, +) -> Option { + contiguous_range_for_group_unless(first, |_| false, visited) +} + +fn contiguous_range_for_group_unless<'a>( + first: &'a SyntaxNode, + unless: impl Fn(&'a SyntaxNode) -> bool, + visited: &mut FxHashSet<&'a SyntaxNode>, +) -> Option { + visited.insert(first); + + let mut last = first; + for node in first.siblings(Direction::Next) { + if let Some(ws) = ast::Whitespace::cast(node) { + // There is a blank line, which means that the group ends here + if ws.count_newlines_lazy().take(2).count() == 2 { + break; + } + + // Ignore whitespace without blank lines + continue; + } + + // Stop if we find a node that doesn't belong to the group + if node.kind() != first.kind() || unless(node) { + break; + } + + visited.insert(node); + last = node; + } + + if first != last { + Some(TextRange::from_to(first.range().start(), last.range().end())) + } else { + // The group consists of only one element, therefore it cannot be folded + None + } +} + +fn contiguous_range_for_comment<'a>( + first: &'a SyntaxNode, + visited: &mut FxHashSet<&'a SyntaxNode>, +) -> Option { + visited.insert(first); + + // Only fold comments of the same flavor + let group_flavor = ast::Comment::cast(first)?.flavor(); + + let mut last = first; + for node in first.siblings(Direction::Next) { + if let Some(ws) = ast::Whitespace::cast(node) { + // There is a blank line, which means the group ends here + if ws.count_newlines_lazy().take(2).count() == 2 { + break; + } + + // Ignore whitespace without blank lines + continue; + } + + match ast::Comment::cast(node) { + Some(next_comment) if next_comment.flavor() == group_flavor => { + visited.insert(node); + last = node; + } + // The comment group ends because either: + // * An element of a different kind was reached + // * A comment of a different flavor was reached + _ => break, + } + } + + if first != last { + Some(TextRange::from_to(first.range().start(), last.range().end())) + } else { + // The group consists of only one element, therefore it cannot be folded + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + use test_utils::extract_ranges; + + fn do_check(text: &str, fold_kinds: &[FoldKind]) { + let (ranges, text) = extract_ranges(text, "fold"); + let file = SourceFile::parse(&text); + let folds = folding_ranges(&file); + + assert_eq!( + folds.len(), + ranges.len(), + "The amount of folds is different than the expected amount" + ); + assert_eq!( + folds.len(), + fold_kinds.len(), + "The amount of fold kinds is different than the expected amount" + ); + for ((fold, range), fold_kind) in + folds.into_iter().zip(ranges.into_iter()).zip(fold_kinds.into_iter()) + { + assert_eq!(fold.range.start(), range.start()); + assert_eq!(fold.range.end(), range.end()); + assert_eq!(&fold.kind, fold_kind); + } + } + + #[test] + fn test_fold_comments() { + let text = r#" +// Hello +// this is a multiline +// comment +// + +// But this is not + +fn main() { + // We should + // also + // fold + // this one. + //! But this one is different + //! because it has another flavor + /* As does this + multiline comment */ +}"#; + + let fold_kinds = &[ + FoldKind::Comment, + FoldKind::Block, + FoldKind::Comment, + FoldKind::Comment, + FoldKind::Comment, + ]; + do_check(text, fold_kinds); + } + + #[test] + fn test_fold_imports() { + let text = r#" +use std::{ + str, + vec, + io as iop +}; + +fn main() { +}"#; + + let folds = &[FoldKind::Imports, FoldKind::Block, FoldKind::Block]; + do_check(text, folds); + } + + #[test] + fn test_fold_mods() { + let text = r#" + +pub mod foo; +mod after_pub; +mod after_pub_next; + +mod before_pub; +mod before_pub_next; +pub mod bar; + +mod not_folding_single; +pub mod foobar; +pub not_folding_single_next; + +#[cfg(test)] +mod with_attribute; +mod with_attribute_next; + +fn main() { +}"#; + + let folds = &[FoldKind::Mods, FoldKind::Mods, FoldKind::Mods, FoldKind::Block]; + do_check(text, folds); + } + + #[test] + fn test_fold_import_groups() { + let text = r#" +use std::str; +use std::vec; +use std::io as iop; + +use std::mem; +use std::f64; + +use std::collections::HashMap; +// Some random comment +use std::collections::VecDeque; + +fn main() { +}"#; + + let folds = &[FoldKind::Imports, FoldKind::Imports, FoldKind::Block]; + do_check(text, folds); + } + + #[test] + fn test_fold_import_and_groups() { + let text = r#" +use std::str; +use std::vec; +use std::io as iop; + +use std::mem; +use std::f64; + +use std::collections::{ + HashMap, + VecDeque, +}; +// Some random comment + +fn main() { +}"#; + + let folds = &[ + FoldKind::Imports, + FoldKind::Imports, + FoldKind::Imports, + FoldKind::Block, + FoldKind::Block, + ]; + do_check(text, folds); + } + + #[test] + fn test_folds_macros() { + let text = r#" +macro_rules! foo { + ($($tt:tt)*) => { $($tt)* } +} +"#; + + let folds = &[FoldKind::Block]; + do_check(text, folds); + } +} diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs index 35f38fbb7..d6f63490d 100644 --- a/crates/ra_ide_api/src/lib.rs +++ b/crates/ra_ide_api/src/lib.rs @@ -34,6 +34,7 @@ mod assists; mod diagnostics; mod syntax_tree; mod line_index; +mod folding_ranges; mod line_index_utils; #[cfg(test)] @@ -64,9 +65,10 @@ pub use crate::{ hover::{HoverResult}, line_index::{LineIndex, LineCol}, line_index_utils::translate_offset_with_edit, + folding_ranges::{Fold, FoldKind}, }; pub use ra_ide_api_light::{ - Fold, FoldKind, HighlightedRange, Severity, StructureNode, LocalEdit, + HighlightedRange, Severity, StructureNode, LocalEdit, }; pub use ra_db::{ Canceled, CrateGraph, CrateId, FileId, FilePosition, FileRange, SourceRootId, @@ -314,7 +316,7 @@ impl Analysis { /// Returns the set of folding ranges. pub fn folding_ranges(&self, file_id: FileId) -> Vec { let file = self.db.parse(file_id); - ra_ide_api_light::folding_ranges(&file) + folding_ranges::folding_ranges(&file) } /// Fuzzy searches for a symbol. diff --git a/crates/ra_ide_api_light/src/folding_ranges.rs b/crates/ra_ide_api_light/src/folding_ranges.rs deleted file mode 100644 index 357a7dee1..000000000 --- a/crates/ra_ide_api_light/src/folding_ranges.rs +++ /dev/null @@ -1,326 +0,0 @@ -use rustc_hash::FxHashSet; - -use ra_syntax::{ - AstNode, Direction, SourceFile, SyntaxNode, TextRange, - SyntaxKind::{self, *}, - ast::{self, VisibilityOwner}, -}; - -#[derive(Debug, PartialEq, Eq)] -pub enum FoldKind { - Comment, - Imports, - Mods, - Block, -} - -#[derive(Debug)] -pub struct Fold { - pub range: TextRange, - pub kind: FoldKind, -} - -pub fn folding_ranges(file: &SourceFile) -> Vec { - let mut res = vec![]; - let mut visited_comments = FxHashSet::default(); - let mut visited_imports = FxHashSet::default(); - let mut visited_mods = FxHashSet::default(); - - for node in file.syntax().descendants() { - // Fold items that span multiple lines - if let Some(kind) = fold_kind(node.kind()) { - if node.text().contains('\n') { - res.push(Fold { range: node.range(), kind }); - } - } - - // Fold groups of comments - if node.kind() == COMMENT && !visited_comments.contains(&node) { - if let Some(range) = contiguous_range_for_comment(node, &mut visited_comments) { - res.push(Fold { range, kind: FoldKind::Comment }) - } - } - - // Fold groups of imports - if node.kind() == USE_ITEM && !visited_imports.contains(&node) { - if let Some(range) = contiguous_range_for_group(node, &mut visited_imports) { - res.push(Fold { range, kind: FoldKind::Imports }) - } - } - - // Fold groups of mods - if node.kind() == MODULE && !has_visibility(&node) && !visited_mods.contains(&node) { - if let Some(range) = - contiguous_range_for_group_unless(node, has_visibility, &mut visited_mods) - { - res.push(Fold { range, kind: FoldKind::Mods }) - } - } - } - - res -} - -fn fold_kind(kind: SyntaxKind) -> Option { - match kind { - COMMENT => Some(FoldKind::Comment), - USE_ITEM => Some(FoldKind::Imports), - NAMED_FIELD_DEF_LIST | FIELD_PAT_LIST | ITEM_LIST | EXTERN_ITEM_LIST | USE_TREE_LIST - | BLOCK | ENUM_VARIANT_LIST | TOKEN_TREE => Some(FoldKind::Block), - _ => None, - } -} - -fn has_visibility(node: &SyntaxNode) -> bool { - ast::Module::cast(node).and_then(|m| m.visibility()).is_some() -} - -fn contiguous_range_for_group<'a>( - first: &'a SyntaxNode, - visited: &mut FxHashSet<&'a SyntaxNode>, -) -> Option { - contiguous_range_for_group_unless(first, |_| false, visited) -} - -fn contiguous_range_for_group_unless<'a>( - first: &'a SyntaxNode, - unless: impl Fn(&'a SyntaxNode) -> bool, - visited: &mut FxHashSet<&'a SyntaxNode>, -) -> Option { - visited.insert(first); - - let mut last = first; - for node in first.siblings(Direction::Next) { - if let Some(ws) = ast::Whitespace::cast(node) { - // There is a blank line, which means that the group ends here - if ws.count_newlines_lazy().take(2).count() == 2 { - break; - } - - // Ignore whitespace without blank lines - continue; - } - - // Stop if we find a node that doesn't belong to the group - if node.kind() != first.kind() || unless(node) { - break; - } - - visited.insert(node); - last = node; - } - - if first != last { - Some(TextRange::from_to(first.range().start(), last.range().end())) - } else { - // The group consists of only one element, therefore it cannot be folded - None - } -} - -fn contiguous_range_for_comment<'a>( - first: &'a SyntaxNode, - visited: &mut FxHashSet<&'a SyntaxNode>, -) -> Option { - visited.insert(first); - - // Only fold comments of the same flavor - let group_flavor = ast::Comment::cast(first)?.flavor(); - - let mut last = first; - for node in first.siblings(Direction::Next) { - if let Some(ws) = ast::Whitespace::cast(node) { - // There is a blank line, which means the group ends here - if ws.count_newlines_lazy().take(2).count() == 2 { - break; - } - - // Ignore whitespace without blank lines - continue; - } - - match ast::Comment::cast(node) { - Some(next_comment) if next_comment.flavor() == group_flavor => { - visited.insert(node); - last = node; - } - // The comment group ends because either: - // * An element of a different kind was reached - // * A comment of a different flavor was reached - _ => break, - } - } - - if first != last { - Some(TextRange::from_to(first.range().start(), last.range().end())) - } else { - // The group consists of only one element, therefore it cannot be folded - None - } -} - -#[cfg(test)] -mod tests { - use super::*; - use test_utils::extract_ranges; - - fn do_check(text: &str, fold_kinds: &[FoldKind]) { - let (ranges, text) = extract_ranges(text, "fold"); - let file = SourceFile::parse(&text); - let folds = folding_ranges(&file); - - assert_eq!( - folds.len(), - ranges.len(), - "The amount of folds is different than the expected amount" - ); - assert_eq!( - folds.len(), - fold_kinds.len(), - "The amount of fold kinds is different than the expected amount" - ); - for ((fold, range), fold_kind) in - folds.into_iter().zip(ranges.into_iter()).zip(fold_kinds.into_iter()) - { - assert_eq!(fold.range.start(), range.start()); - assert_eq!(fold.range.end(), range.end()); - assert_eq!(&fold.kind, fold_kind); - } - } - - #[test] - fn test_fold_comments() { - let text = r#" -// Hello -// this is a multiline -// comment -// - -// But this is not - -fn main() { - // We should - // also - // fold - // this one. - //! But this one is different - //! because it has another flavor - /* As does this - multiline comment */ -}"#; - - let fold_kinds = &[ - FoldKind::Comment, - FoldKind::Block, - FoldKind::Comment, - FoldKind::Comment, - FoldKind::Comment, - ]; - do_check(text, fold_kinds); - } - - #[test] - fn test_fold_imports() { - let text = r#" -use std::{ - str, - vec, - io as iop -}; - -fn main() { -}"#; - - let folds = &[FoldKind::Imports, FoldKind::Block, FoldKind::Block]; - do_check(text, folds); - } - - #[test] - fn test_fold_mods() { - let text = r#" - -pub mod foo; -mod after_pub; -mod after_pub_next; - -mod before_pub; -mod before_pub_next; -pub mod bar; - -mod not_folding_single; -pub mod foobar; -pub not_folding_single_next; - -#[cfg(test)] -mod with_attribute; -mod with_attribute_next; - -fn main() { -}"#; - - let folds = &[FoldKind::Mods, FoldKind::Mods, FoldKind::Mods, FoldKind::Block]; - do_check(text, folds); - } - - #[test] - fn test_fold_import_groups() { - let text = r#" -use std::str; -use std::vec; -use std::io as iop; - -use std::mem; -use std::f64; - -use std::collections::HashMap; -// Some random comment -use std::collections::VecDeque; - -fn main() { -}"#; - - let folds = &[FoldKind::Imports, FoldKind::Imports, FoldKind::Block]; - do_check(text, folds); - } - - #[test] - fn test_fold_import_and_groups() { - let text = r#" -use std::str; -use std::vec; -use std::io as iop; - -use std::mem; -use std::f64; - -use std::collections::{ - HashMap, - VecDeque, -}; -// Some random comment - -fn main() { -}"#; - - let folds = &[ - FoldKind::Imports, - FoldKind::Imports, - FoldKind::Imports, - FoldKind::Block, - FoldKind::Block, - ]; - do_check(text, folds); - } - - #[test] - fn test_folds_macros() { - let text = r#" -macro_rules! foo { - ($($tt:tt)*) => { $($tt)* } -} -"#; - - let folds = &[FoldKind::Block]; - do_check(text, folds); - } -} diff --git a/crates/ra_ide_api_light/src/lib.rs b/crates/ra_ide_api_light/src/lib.rs index 556d44c25..4036a598e 100644 --- a/crates/ra_ide_api_light/src/lib.rs +++ b/crates/ra_ide_api_light/src/lib.rs @@ -3,7 +3,6 @@ //! This usually means functions which take syntax tree as an input and produce //! an edit or some auxiliary info. -mod folding_ranges; mod structure; #[cfg(test)] mod test_utils; @@ -20,7 +19,6 @@ use ra_syntax::{ }; pub use crate::{ - folding_ranges::{folding_ranges, Fold, FoldKind}, structure::{file_structure, StructureNode}, join_lines::join_lines, typing::{on_enter, on_dot_typed, on_eq_typed}, -- cgit v1.2.3