Cargo Format

Run `cargo fmt` and ignore generated files
This commit is contained in:
Jeremy A. Kolb 2018-10-15 17:44:23 -04:00
parent 39cb6c6d3f
commit 61f3a438d3
76 changed files with 1936 additions and 1530 deletions

View file

@ -1,17 +1,14 @@
use join_to_string::join;
use ra_syntax::{
File, TextUnit, TextRange, Direction,
ast::{self, AstNode, AttrsOwner, TypeParamsOwner, NameOwner},
algo::{find_covering_node, find_leaf_at_offset},
ast::{self, AstNode, AttrsOwner, NameOwner, TypeParamsOwner},
Direction, File,
SyntaxKind::{COMMA, WHITESPACE},
SyntaxNodeRef,
algo::{
find_leaf_at_offset,
find_covering_node,
},
SyntaxNodeRef, TextRange, TextUnit,
};
use crate::{EditBuilder, Edit, find_node_at_offset};
use crate::{find_node_at_offset, Edit, EditBuilder};
#[derive(Debug)]
pub struct LocalEdit {
@ -52,9 +49,7 @@ pub fn add_derive<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce()
edit.insert(node_start, "#[derive()]\n".to_string());
node_start + TextUnit::of_str("#[derive(")
}
Some(tt) => {
tt.syntax().range().end() - TextUnit::of_char(')')
}
Some(tt) => tt.syntax().range().end() - TextUnit::of_char(')'),
};
LocalEdit {
edit: edit.finish(),
@ -74,14 +69,19 @@ pub fn add_impl<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() ->
let mut buf = String::new();
buf.push_str("\n\nimpl");
if let Some(type_params) = type_params {
type_params.syntax().text()
.push_to(&mut buf);
type_params.syntax().text().push_to(&mut buf);
}
buf.push_str(" ");
buf.push_str(name.text().as_str());
if let Some(type_params) = type_params {
let lifetime_params = type_params.lifetime_params().filter_map(|it| it.lifetime()).map(|it| it.text());
let type_params = type_params.type_params().filter_map(|it| it.name()).map(|it| it.text());
let lifetime_params = type_params
.lifetime_params()
.filter_map(|it| it.lifetime())
.map(|it| it.text());
let type_params = type_params
.type_params()
.filter_map(|it| it.name())
.map(|it| it.text());
join(lifetime_params.chain(type_params))
.surround_with("<", ">")
.to_buf(&mut buf);
@ -97,10 +97,17 @@ pub fn add_impl<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() ->
})
}
pub fn introduce_variable<'a>(file: &'a File, range: TextRange) -> Option<impl FnOnce() -> LocalEdit + 'a> {
pub fn introduce_variable<'a>(
file: &'a File,
range: TextRange,
) -> Option<impl FnOnce() -> LocalEdit + 'a> {
let node = find_covering_node(file.syntax(), range);
let expr = node.ancestors().filter_map(ast::Expr::cast).next()?;
let anchor_stmt = expr.syntax().ancestors().filter_map(ast::Stmt::cast).next()?;
let anchor_stmt = expr
.syntax()
.ancestors()
.filter_map(ast::Stmt::cast)
.next()?;
let indent = anchor_stmt.syntax().prev_sibling()?;
if indent.kind() != WHITESPACE {
return None;
@ -191,7 +198,8 @@ mod tests {
"
fn foo() {
foo(<|>1 + 1<|>);
}", "
}",
"
fn foo() {
let <|>var_name = 1 + 1;
foo(var_name);
@ -201,11 +209,12 @@ fn foo() {
}
#[test]
fn test_intrdoduce_var_expr_stmt() {
check_action_range(
check_action_range(
"
fn foo() {
<|>1 + 1<|>;
}", "
}",
"
fn foo() {
let <|>var_name = 1 + 1;
}",

View file

@ -1,17 +1,18 @@
use rustc_hash::{FxHashMap, FxHashSet};
use ra_syntax::{
File, TextUnit, AstNode, SyntaxNodeRef, SyntaxKind::*,
algo::visit::{visitor, visitor_ctx, Visitor, VisitorCtx},
ast::{self, LoopBodyOwner, ModuleItemOwner},
algo::{
visit::{visitor, Visitor, visitor_ctx, VisitorCtx},
},
text_utils::is_subrange,
AstNode, File,
SyntaxKind::*,
SyntaxNodeRef, TextUnit,
};
use crate::{
AtomEdit, find_node_at_offset,
find_node_at_offset,
scope::{FnScopes, ModuleScope},
AtomEdit,
};
#[derive(Debug)]
@ -21,7 +22,7 @@ pub struct CompletionItem {
/// What string is used for filtering, defaults to label
pub lookup: Option<String>,
/// What is inserted, defaults to label
pub snippet: Option<String>
pub snippet: Option<String>,
}
pub fn scope_completion(file: &File, offset: TextUnit) -> Option<Vec<CompletionItem>> {
@ -40,7 +41,12 @@ pub fn scope_completion(file: &File, offset: TextUnit) -> Option<Vec<CompletionI
param_completions(name_ref.syntax(), &mut res);
}
let name_range = name_ref.syntax().range();
let top_node = name_ref.syntax().ancestors().take_while(|it| it.range() == name_range).last().unwrap();
let top_node = name_ref
.syntax()
.ancestors()
.take_while(|it| it.range() == name_range)
.last()
.unwrap();
match top_node.parent().map(|it| it.kind()) {
Some(ROOT) | Some(ITEM_LIST) => complete_mod_item_snippets(&mut res),
_ => (),
@ -68,21 +74,23 @@ fn complete_name_ref(file: &File, name_ref: ast::NameRef, acc: &mut Vec<Completi
if let Some(items) = visitor()
.visit::<ast::Root, _>(|it| Some(it.items()))
.visit::<ast::Module, _>(|it| Some(it.item_list()?.items()))
.accept(node) {
.accept(node)
{
if let Some(items) = items {
let scope = ModuleScope::new(items);
acc.extend(
scope.entries().iter()
scope
.entries()
.iter()
.filter(|entry| entry.syntax() != name_ref.syntax())
.map(|entry| CompletionItem {
label: entry.name().to_string(),
lookup: None,
snippet: None,
})
}),
);
}
break;
} else if !visited_fn {
if let Some(fn_def) = ast::FnDef::cast(node) {
visited_fn = true;
@ -103,26 +111,34 @@ fn param_completions(ctx: SyntaxNodeRef, acc: &mut Vec<CompletionItem>) {
.visit::<ast::ItemList, _>(process)
.accept(node);
}
params.into_iter()
params
.into_iter()
.filter_map(|(label, (count, param))| {
let lookup = param.pat()?.syntax().text().to_string();
if count < 2 { None } else { Some((label, lookup)) }
if count < 2 {
None
} else {
Some((label, lookup))
}
})
.for_each(|(label, lookup)| {
acc.push(CompletionItem {
label, lookup: Some(lookup), snippet: None
label,
lookup: Some(lookup),
snippet: None,
})
});
fn process<'a, N: ast::FnDefOwner<'a>>(node: N, params: &mut FxHashMap<String, (u32, ast::Param<'a>)>) {
fn process<'a, N: ast::FnDefOwner<'a>>(
node: N,
params: &mut FxHashMap<String, (u32, ast::Param<'a>)>,
) {
node.functions()
.filter_map(|it| it.param_list())
.flat_map(|it| it.params())
.for_each(|param| {
let text = param.syntax().text().to_string();
params.entry(text)
.or_insert((0, param))
.0 += 1;
params.entry(text).or_insert((0, param)).0 += 1;
})
}
}
@ -134,8 +150,12 @@ fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool {
}
}
fn complete_expr_keywords(file: &File, fn_def: ast::FnDef, name_ref: ast::NameRef, acc: &mut Vec<CompletionItem>) {
fn complete_expr_keywords(
file: &File,
fn_def: ast::FnDef,
name_ref: ast::NameRef,
acc: &mut Vec<CompletionItem>,
) {
acc.push(keyword("if", "if $0 {}"));
acc.push(keyword("match", "match $0 {}"));
acc.push(keyword("while", "while $0 {}"));
@ -186,9 +206,14 @@ fn complete_return(fn_def: ast::FnDef, name_ref: ast::NameRef) -> Option<Complet
// return None;
// }
let is_stmt = match name_ref.syntax().ancestors().filter_map(ast::ExprStmt::cast).next() {
let is_stmt = match name_ref
.syntax()
.ancestors()
.filter_map(ast::ExprStmt::cast)
.next()
{
None => false,
Some(expr_stmt) => expr_stmt.syntax().range() == name_ref.syntax().range()
Some(expr_stmt) => expr_stmt.syntax().range() == name_ref.syntax().range(),
};
let snip = match (is_stmt, fn_def.ret_type().is_some()) {
(true, true) => "return $0;",
@ -209,39 +234,37 @@ fn keyword(kw: &str, snip: &str) -> CompletionItem {
fn complete_expr_snippets(acc: &mut Vec<CompletionItem>) {
acc.push(CompletionItem {
label: "pd".to_string(),
lookup: None,
snippet: Some("eprintln!(\"$0 = {:?}\", $0);".to_string()),
}
);
label: "pd".to_string(),
lookup: None,
snippet: Some("eprintln!(\"$0 = {:?}\", $0);".to_string()),
});
acc.push(CompletionItem {
label: "ppd".to_string(),
lookup: None,
snippet: Some("eprintln!(\"$0 = {:#?}\", $0);".to_string()),
}
);
label: "ppd".to_string(),
lookup: None,
snippet: Some("eprintln!(\"$0 = {:#?}\", $0);".to_string()),
});
}
fn complete_mod_item_snippets(acc: &mut Vec<CompletionItem>) {
acc.push(CompletionItem {
label: "tfn".to_string(),
lookup: None,
snippet: Some("#[test]\nfn $1() {\n $0\n}".to_string()),
}
);
label: "tfn".to_string(),
lookup: None,
snippet: Some("#[test]\nfn $1() {\n $0\n}".to_string()),
});
}
fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Vec<CompletionItem>) {
let mut shadowed = FxHashSet::default();
acc.extend(
scopes.scope_chain(name_ref.syntax())
scopes
.scope_chain(name_ref.syntax())
.flat_map(|scope| scopes.entries(scope).iter())
.filter(|entry| shadowed.insert(entry.name()))
.map(|entry| CompletionItem {
label: entry.name().to_string(),
lookup: None,
snippet: None,
})
}),
);
if scopes.self_param.is_some() {
acc.push(CompletionItem {
@ -281,20 +304,24 @@ mod tests {
#[test]
fn test_completion_let_scope() {
check_scope_completion(r"
check_scope_completion(
r"
fn quux(x: i32) {
let y = 92;
1 + <|>;
let z = ();
}
", r#"[CompletionItem { label: "y", lookup: None, snippet: None },
",
r#"[CompletionItem { label: "y", lookup: None, snippet: None },
CompletionItem { label: "x", lookup: None, snippet: None },
CompletionItem { label: "quux", lookup: None, snippet: None }]"#);
CompletionItem { label: "quux", lookup: None, snippet: None }]"#,
);
}
#[test]
fn test_completion_if_let_scope() {
check_scope_completion(r"
check_scope_completion(
r"
fn quux() {
if let Some(x) = foo() {
let y = 92;
@ -304,67 +331,85 @@ mod tests {
1 + <|>
}
}
", r#"[CompletionItem { label: "b", lookup: None, snippet: None },
",
r#"[CompletionItem { label: "b", lookup: None, snippet: None },
CompletionItem { label: "a", lookup: None, snippet: None },
CompletionItem { label: "quux", lookup: None, snippet: None }]"#);
CompletionItem { label: "quux", lookup: None, snippet: None }]"#,
);
}
#[test]
fn test_completion_for_scope() {
check_scope_completion(r"
check_scope_completion(
r"
fn quux() {
for x in &[1, 2, 3] {
<|>
}
}
", r#"[CompletionItem { label: "x", lookup: None, snippet: None },
CompletionItem { label: "quux", lookup: None, snippet: None }]"#);
",
r#"[CompletionItem { label: "x", lookup: None, snippet: None },
CompletionItem { label: "quux", lookup: None, snippet: None }]"#,
);
}
#[test]
fn test_completion_mod_scope() {
check_scope_completion(r"
check_scope_completion(
r"
struct Foo;
enum Baz {}
fn quux() {
<|>
}
", r#"[CompletionItem { label: "Foo", lookup: None, snippet: None },
",
r#"[CompletionItem { label: "Foo", lookup: None, snippet: None },
CompletionItem { label: "Baz", lookup: None, snippet: None },
CompletionItem { label: "quux", lookup: None, snippet: None }]"#);
CompletionItem { label: "quux", lookup: None, snippet: None }]"#,
);
}
#[test]
fn test_completion_mod_scope_no_self_use() {
check_scope_completion(r"
check_scope_completion(
r"
use foo<|>;
", r#"[]"#);
",
r#"[]"#,
);
}
#[test]
fn test_completion_mod_scope_nested() {
check_scope_completion(r"
check_scope_completion(
r"
struct Foo;
mod m {
struct Bar;
fn quux() { <|> }
}
", r#"[CompletionItem { label: "Bar", lookup: None, snippet: None },
CompletionItem { label: "quux", lookup: None, snippet: None }]"#);
",
r#"[CompletionItem { label: "Bar", lookup: None, snippet: None },
CompletionItem { label: "quux", lookup: None, snippet: None }]"#,
);
}
#[test]
fn test_complete_type() {
check_scope_completion(r"
check_scope_completion(
r"
struct Foo;
fn x() -> <|>
", r#"[CompletionItem { label: "Foo", lookup: None, snippet: None },
CompletionItem { label: "x", lookup: None, snippet: None }]"#)
",
r#"[CompletionItem { label: "Foo", lookup: None, snippet: None },
CompletionItem { label: "x", lookup: None, snippet: None }]"#,
)
}
#[test]
fn test_complete_shadowing() {
check_scope_completion(r"
check_scope_completion(
r"
fn foo() -> {
let bar = 92;
{
@ -372,15 +417,20 @@ mod tests {
<|>
}
}
", r#"[CompletionItem { label: "bar", lookup: None, snippet: None },
CompletionItem { label: "foo", lookup: None, snippet: None }]"#)
",
r#"[CompletionItem { label: "bar", lookup: None, snippet: None },
CompletionItem { label: "foo", lookup: None, snippet: None }]"#,
)
}
#[test]
fn test_complete_self() {
check_scope_completion(r"
check_scope_completion(
r"
impl S { fn foo(&self) { <|> } }
", r#"[CompletionItem { label: "self", lookup: None, snippet: None }]"#)
",
r#"[CompletionItem { label: "self", lookup: None, snippet: None }]"#,
)
}
#[test]

View file

@ -1,8 +1,5 @@
use crate::{TextRange, TextUnit};
use ra_syntax::{
AtomEdit,
text_utils::contains_offset_nonstrict,
};
use ra_syntax::{text_utils::contains_offset_nonstrict, AtomEdit};
#[derive(Debug, Clone)]
pub struct Edit {
@ -11,7 +8,7 @@ pub struct Edit {
#[derive(Debug)]
pub struct EditBuilder {
atoms: Vec<AtomEdit>
atoms: Vec<AtomEdit>,
}
impl EditBuilder {
@ -36,7 +33,9 @@ impl EditBuilder {
Edit { atoms }
}
pub fn invalidates_offset(&self, offset: TextUnit) -> bool {
self.atoms.iter().any(|atom| contains_offset_nonstrict(atom.delete, offset))
self.atoms
.iter()
.any(|atom| contains_offset_nonstrict(atom.delete, offset))
}
}
@ -74,7 +73,7 @@ impl Edit {
break;
}
if offset < atom.delete.end() {
return None
return None;
}
res += TextUnit::of_str(&atom.insert);
res -= atom.delete.len();

View file

@ -1,7 +1,8 @@
use ra_syntax::{
File, TextRange, SyntaxNodeRef, TextUnit, Direction,
algo::{find_covering_node, find_leaf_at_offset, LeafAtOffset},
Direction, File,
SyntaxKind::*,
algo::{find_leaf_at_offset, LeafAtOffset, find_covering_node},
SyntaxNodeRef, TextRange, TextUnit,
};
pub fn extend_selection(file: &File, range: TextRange) -> Option<TextRange> {
@ -20,11 +21,11 @@ pub(crate) fn extend(root: SyntaxNodeRef, range: TextRange) -> Option<TextRange>
LeafAtOffset::None => return None,
LeafAtOffset::Single(l) => {
if l.kind() == COMMENT {
extend_single_word_in_comment(l, offset).unwrap_or_else(||l.range())
extend_single_word_in_comment(l, offset).unwrap_or_else(|| l.range())
} else {
l.range()
}
},
}
LeafAtOffset::Between(l, r) => pick_best(l, r).range(),
};
return Some(leaf_range);
@ -66,7 +67,7 @@ fn extend_ws(root: SyntaxNodeRef, ws: SyntaxNodeRef, offset: TextUnit) -> TextRa
if let Some(node) = ws.next_sibling() {
let start = match ws_prefix.rfind('\n') {
Some(idx) => ws.range().start() + TextUnit::from((idx + 1) as u32),
None => node.range().start()
None => node.range().start(),
};
let end = if root.text().char_at(node.range().end()) == Some('\n') {
node.range().end() + TextUnit::of_char('\n')
@ -94,10 +95,7 @@ fn extend_comments(node: SyntaxNodeRef) -> Option<TextRange> {
let prev = adj_comments(node, Direction::Prev);
let next = adj_comments(node, Direction::Next);
if prev != next {
Some(TextRange::from_to(
prev.range().start(),
next.range().end(),
))
Some(TextRange::from_to(prev.range().start(), next.range().end()))
} else {
None
}
@ -109,7 +107,7 @@ fn adj_comments(node: SyntaxNodeRef, dir: Direction) -> SyntaxNodeRef {
match node.kind() {
COMMENT => res = node,
WHITESPACE if !node.leaf_text().unwrap().as_str().contains("\n\n") => (),
_ => break
_ => break,
}
}
res
@ -125,8 +123,7 @@ mod tests {
let file = File::parse(&before);
let mut range = TextRange::offset_len(cursor, 0.into());
for &after in afters {
range = extend_selection(&file, range)
.unwrap();
range = extend_selection(&file, range).unwrap();
let actual = &before[range];
assert_eq!(after, actual);
}
@ -134,10 +131,7 @@ mod tests {
#[test]
fn test_extend_selection_arith() {
do_check(
r#"fn foo() { <|>1 + 1 }"#,
&["1", "1 + 1", "{ 1 + 1 }"],
);
do_check(r#"fn foo() { <|>1 + 1 }"#, &["1", "1 + 1", "{ 1 + 1 }"]);
}
#[test]
@ -149,7 +143,7 @@ impl S {
}
}"#,
&[" fn foo() {\n\n }\n"]
&[" fn foo() {\n\n }\n"],
);
}
@ -165,7 +159,11 @@ struct B {
<|>
}
"#,
&["\n \n", "{\n \n}", "/// bla\n/// bla\nstruct B {\n \n}"]
&[
"\n \n",
"{\n \n}",
"/// bla\n/// bla\nstruct B {\n \n}",
],
)
}
@ -181,7 +179,7 @@ fn bar(){}
// fn foo(){}
"#,
&["// 1 + 1", "// fn foo() {\n// 1 + 1\n// }"]
&["// 1 + 1", "// fn foo() {\n// 1 + 1\n// }"],
);
}
@ -191,42 +189,34 @@ fn bar(){}
r#"
fn main() { foo<|>+bar;}
"#,
&["foo", "foo+bar"]
&["foo", "foo+bar"],
);
do_check(
r#"
fn main() { foo+<|>bar;}
"#,
&["bar", "foo+bar"]
&["bar", "foo+bar"],
);
}
#[test]
fn test_extend_selection_prefer_lifetimes() {
do_check(
r#"fn foo<<|>'a>() {}"#,
&["'a", "<'a>"]
);
do_check(
r#"fn foo<'a<|>>() {}"#,
&["'a", "<'a>"]
);
do_check(r#"fn foo<<|>'a>() {}"#, &["'a", "<'a>"]);
do_check(r#"fn foo<'a<|>>() {}"#, &["'a", "<'a>"]);
}
#[test]
fn test_extend_selection_select_first_word() {
do_check(r#"// foo bar b<|>az quxx"#, &["baz", "// foo bar baz quxx"]);
do_check(
r#"// foo bar b<|>az quxx"#,
&["baz", "// foo bar baz quxx"]
);
do_check(r#"
r#"
impl S {
fn foo() {
// hel<|>lo world
}
}
"#,
&["hello", "// hello world"]
&["hello", "// hello world"],
);
}
}

View file

@ -1,11 +1,9 @@
use rustc_hash::FxHashSet;
use ra_syntax::{
ast,
AstNode,
File, TextRange, SyntaxNodeRef,
ast, AstNode, Direction, File,
SyntaxKind::{self, *},
Direction,
SyntaxNodeRef, TextRange,
};
#[derive(Debug, PartialEq, Eq)]
@ -28,7 +26,10 @@ pub fn folding_ranges(file: &File) -> Vec<Fold> {
// Fold items that span multiple lines
if let Some(kind) = fold_kind(node.kind()) {
if has_newline(node) {
res.push(Fold { range: node.range(), kind });
res.push(Fold {
range: node.range(),
kind,
});
}
}
@ -37,8 +38,12 @@ pub fn folding_ranges(file: &File) -> Vec<Fold> {
continue;
}
if node.kind() == COMMENT {
contiguous_range_for_comment(node, &mut visited_comments)
.map(|range| res.push(Fold { range, kind: FoldKind::Comment }));
contiguous_range_for_comment(node, &mut visited_comments).map(|range| {
res.push(Fold {
range,
kind: FoldKind::Comment,
})
});
}
}
@ -49,13 +54,11 @@ fn fold_kind(kind: SyntaxKind) -> Option<FoldKind> {
match kind {
COMMENT => Some(FoldKind::Comment),
USE_ITEM => Some(FoldKind::Imports),
_ => None
_ => None,
}
}
fn has_newline(
node: SyntaxNodeRef,
) -> bool {
fn has_newline(node: SyntaxNodeRef) -> bool {
for descendant in node.descendants() {
if let Some(ws) = ast::Whitespace::cast(descendant) {
if ws.has_newlines() {
@ -100,9 +103,7 @@ fn contiguous_range_for_comment<'a>(
// The comment group ends because either:
// * An element of a different kind was reached
// * A comment of a different flavor was reached
_ => {
break
}
_ => break,
}
}
@ -128,7 +129,11 @@ mod tests {
let folds = folding_ranges(&file);
assert_eq!(folds.len(), ranges.len());
for ((fold, range), fold_kind) in folds.into_iter().zip(ranges.into_iter()).zip(fold_kinds.into_iter()) {
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);
@ -181,5 +186,4 @@ fn main() {
do_check(text, folds);
}
}

View file

@ -1,44 +1,41 @@
extern crate ra_syntax;
extern crate superslice;
extern crate itertools;
extern crate join_to_string;
extern crate ra_syntax;
extern crate rustc_hash;
extern crate superslice;
#[cfg(test)]
#[macro_use]
extern crate test_utils as _test_utils;
mod extend_selection;
mod symbols;
mod line_index;
mod edit;
mod folding_ranges;
mod code_actions;
mod typing;
mod completion;
mod edit;
mod extend_selection;
mod folding_ranges;
mod line_index;
mod scope;
mod symbols;
#[cfg(test)]
mod test_utils;
mod typing;
use ra_syntax::{
File, TextUnit, TextRange, SmolStr, SyntaxNodeRef,
ast::{self, AstNode, NameOwner},
algo::find_leaf_at_offset,
SyntaxKind::{self, *},
pub use self::{
code_actions::{add_derive, add_impl, flip_comma, introduce_variable, LocalEdit},
completion::{scope_completion, CompletionItem},
edit::{Edit, EditBuilder},
extend_selection::extend_selection,
folding_ranges::{folding_ranges, Fold, FoldKind},
line_index::{LineCol, LineIndex},
symbols::{file_structure, file_symbols, FileSymbol, StructureNode},
typing::{join_lines, on_enter, on_eq_typed},
};
pub use ra_syntax::AtomEdit;
pub use self::{
line_index::{LineIndex, LineCol},
extend_selection::extend_selection,
symbols::{StructureNode, file_structure, FileSymbol, file_symbols},
edit::{EditBuilder, Edit},
code_actions::{
LocalEdit,
flip_comma, add_derive, add_impl,
introduce_variable,
},
typing::{join_lines, on_eq_typed, on_enter},
completion::{scope_completion, CompletionItem},
folding_ranges::{Fold, FoldKind, folding_ranges}
use ra_syntax::{
algo::find_leaf_at_offset,
ast::{self, AstNode, NameOwner},
File, SmolStr,
SyntaxKind::{self, *},
SyntaxNodeRef, TextRange, TextUnit,
};
#[derive(Debug)]
@ -67,10 +64,7 @@ pub enum RunnableKind {
pub fn matching_brace(file: &File, offset: TextUnit) -> Option<TextUnit> {
const BRACES: &[SyntaxKind] = &[
L_CURLY, R_CURLY,
L_BRACK, R_BRACK,
L_PAREN, R_PAREN,
L_ANGLE, R_ANGLE,
L_CURLY, R_CURLY, L_BRACK, R_BRACK, L_PAREN, R_PAREN, L_ANGLE, R_ANGLE,
];
let (brace_node, brace_idx) = find_leaf_at_offset(file.syntax(), offset)
.filter_map(|node| {
@ -80,7 +74,8 @@ pub fn matching_brace(file: &File, offset: TextUnit) -> Option<TextUnit> {
.next()?;
let parent = brace_node.parent()?;
let matching_kind = BRACES[brace_idx ^ 1];
let matching_node = parent.children()
let matching_node = parent
.children()
.find(|node| node.kind() == matching_kind)?;
Some(matching_node.range().start())
}
@ -108,10 +103,13 @@ pub fn highlight(file: &File) -> Vec<HighlightedRange> {
}
pub fn diagnostics(file: &File) -> Vec<Diagnostic> {
file.errors().into_iter().map(|err| Diagnostic {
range: TextRange::offset_len(err.offset, 1.into()),
msg: "Syntax Error: ".to_string() + &err.msg,
}).collect()
file.errors()
.into_iter()
.map(|err| Diagnostic {
range: TextRange::offset_len(err.offset, 1.into()),
msg: "Syntax Error: ".to_string() + &err.msg,
})
.collect()
}
pub fn syntax_tree(file: &File) -> String {
@ -119,7 +117,8 @@ pub fn syntax_tree(file: &File) -> String {
}
pub fn runnables(file: &File) -> Vec<Runnable> {
file.syntax().descendants()
file.syntax()
.descendants()
.filter_map(ast::FnDef::cast)
.filter_map(|f| {
let name = f.name()?.text();
@ -127,7 +126,7 @@ pub fn runnables(file: &File) -> Vec<Runnable> {
RunnableKind::Bin
} else if f.has_atom_attr("test") {
RunnableKind::Test {
name: name.to_string()
name: name.to_string(),
}
} else {
return None;
@ -145,15 +144,18 @@ pub fn find_node_at_offset<'a, N: AstNode<'a>>(
offset: TextUnit,
) -> Option<N> {
let leaves = find_leaf_at_offset(syntax, offset);
let leaf = leaves.clone()
let leaf = leaves
.clone()
.find(|leaf| !leaf.kind().is_trivia())
.or_else(|| leaves.right_biased())?;
leaf.ancestors()
.filter_map(N::cast)
.next()
leaf.ancestors().filter_map(N::cast).next()
}
pub fn resolve_local_name(file: &File, offset: TextUnit, name_ref: ast::NameRef) -> Option<(SmolStr, TextRange)> {
pub fn resolve_local_name(
file: &File,
offset: TextUnit,
name_ref: ast::NameRef,
) -> Option<(SmolStr, TextRange)> {
let fn_def = find_node_at_offset::<ast::FnDef>(file.syntax(), offset)?;
let scopes = scope::FnScopes::new(fn_def);
let scope_entry = scope::resolve_local_name(name_ref, &scopes)?;
@ -164,15 +166,17 @@ pub fn resolve_local_name(file: &File, offset: TextUnit, name_ref: ast::NameRef)
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{assert_eq_dbg, extract_offset, add_cursor};
use crate::test_utils::{add_cursor, assert_eq_dbg, extract_offset};
#[test]
fn test_highlighting() {
let file = File::parse(r#"
let file = File::parse(
r#"
// comment
fn main() {}
println!("Hello, {}!", 92);
"#);
"#,
);
let hls = highlight(&file);
assert_eq_dbg(
r#"[HighlightedRange { range: [1; 11), tag: "comment" },
@ -187,7 +191,8 @@ fn main() {}
#[test]
fn test_runnables() {
let file = File::parse(r#"
let file = File::parse(
r#"
fn main() {}
#[test]
@ -196,7 +201,8 @@ fn test_foo() {}
#[test]
#[ignore]
fn test_foo() {}
"#);
"#,
);
let runnables = runnables(&file);
assert_eq_dbg(
r#"[Runnable { range: [1; 13), kind: Bin },
@ -219,9 +225,6 @@ fn test_foo() {}
assert_eq_text!(after, &actual);
}
do_check(
"struct Foo { a: i32, }<|>",
"struct Foo <|>{ a: i32, }",
);
do_check("struct Foo { a: i32, }<|>", "struct Foo <|>{ a: i32, }");
}
}

View file

@ -1,5 +1,5 @@
use superslice::Ext;
use crate::TextUnit;
use superslice::Ext;
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct LineIndex {
@ -29,7 +29,10 @@ impl LineIndex {
let line = self.newlines.upper_bound(&offset) - 1;
let line_start_offset = self.newlines[line];
let col = offset - line_start_offset;
return LineCol { line: line as u32, col };
return LineCol {
line: line as u32,
col,
};
}
pub fn offset(&self, line_col: LineCol) -> TextUnit {
@ -42,21 +45,105 @@ impl LineIndex {
fn test_line_index() {
let text = "hello\nworld";
let index = LineIndex::new(text);
assert_eq!(index.line_col(0.into()), LineCol { line: 0, col: 0.into() });
assert_eq!(index.line_col(1.into()), LineCol { line: 0, col: 1.into() });
assert_eq!(index.line_col(5.into()), LineCol { line: 0, col: 5.into() });
assert_eq!(index.line_col(6.into()), LineCol { line: 1, col: 0.into() });
assert_eq!(index.line_col(7.into()), LineCol { line: 1, col: 1.into() });
assert_eq!(index.line_col(8.into()), LineCol { line: 1, col: 2.into() });
assert_eq!(index.line_col(10.into()), LineCol { line: 1, col: 4.into() });
assert_eq!(index.line_col(11.into()), LineCol { line: 1, col: 5.into() });
assert_eq!(index.line_col(12.into()), LineCol { line: 1, col: 6.into() });
assert_eq!(
index.line_col(0.into()),
LineCol {
line: 0,
col: 0.into()
}
);
assert_eq!(
index.line_col(1.into()),
LineCol {
line: 0,
col: 1.into()
}
);
assert_eq!(
index.line_col(5.into()),
LineCol {
line: 0,
col: 5.into()
}
);
assert_eq!(
index.line_col(6.into()),
LineCol {
line: 1,
col: 0.into()
}
);
assert_eq!(
index.line_col(7.into()),
LineCol {
line: 1,
col: 1.into()
}
);
assert_eq!(
index.line_col(8.into()),
LineCol {
line: 1,
col: 2.into()
}
);
assert_eq!(
index.line_col(10.into()),
LineCol {
line: 1,
col: 4.into()
}
);
assert_eq!(
index.line_col(11.into()),
LineCol {
line: 1,
col: 5.into()
}
);
assert_eq!(
index.line_col(12.into()),
LineCol {
line: 1,
col: 6.into()
}
);
let text = "\nhello\nworld";
let index = LineIndex::new(text);
assert_eq!(index.line_col(0.into()), LineCol { line: 0, col: 0.into() });
assert_eq!(index.line_col(1.into()), LineCol { line: 1, col: 0.into() });
assert_eq!(index.line_col(2.into()), LineCol { line: 1, col: 1.into() });
assert_eq!(index.line_col(6.into()), LineCol { line: 1, col: 5.into() });
assert_eq!(index.line_col(7.into()), LineCol { line: 2, col: 0.into() });
assert_eq!(
index.line_col(0.into()),
LineCol {
line: 0,
col: 0.into()
}
);
assert_eq!(
index.line_col(1.into()),
LineCol {
line: 1,
col: 0.into()
}
);
assert_eq!(
index.line_col(2.into()),
LineCol {
line: 1,
col: 1.into()
}
);
assert_eq!(
index.line_col(6.into()),
LineCol {
line: 1,
col: 5.into()
}
);
assert_eq!(
index.line_col(7.into()),
LineCol {
line: 2,
col: 0.into()
}
);
}

View file

@ -1,10 +1,11 @@
use std::fmt;
use rustc_hash::FxHashMap;
use ra_syntax::{
SyntaxNodeRef, SyntaxNode, SmolStr, AstNode,
ast::{self, NameOwner, LoopBodyOwner, ArgListOwner},
algo::{generate}
algo::generate,
ast::{self, ArgListOwner, LoopBodyOwner, NameOwner},
AstNode, SmolStr, SyntaxNode, SyntaxNodeRef,
};
type ScopeId = usize;
@ -19,11 +20,12 @@ pub struct FnScopes {
impl FnScopes {
pub fn new(fn_def: ast::FnDef) -> FnScopes {
let mut scopes = FnScopes {
self_param: fn_def.param_list()
self_param: fn_def
.param_list()
.and_then(|it| it.self_param())
.map(|it| it.syntax().owned()),
scopes: Vec::new(),
scope_for: FxHashMap::default()
scope_for: FxHashMap::default(),
};
let root = scopes.root_scope();
scopes.add_params_bindings(root, fn_def.param_list());
@ -35,27 +37,38 @@ impl FnScopes {
pub fn entries(&self, scope: ScopeId) -> &[ScopeEntry] {
&self.scopes[scope].entries
}
pub fn scope_chain<'a>(&'a self, node: SyntaxNodeRef) -> impl Iterator<Item=ScopeId> + 'a {
generate(self.scope_for(node), move |&scope| self.scopes[scope].parent)
pub fn scope_chain<'a>(&'a self, node: SyntaxNodeRef) -> impl Iterator<Item = ScopeId> + 'a {
generate(self.scope_for(node), move |&scope| {
self.scopes[scope].parent
})
}
fn root_scope(&mut self) -> ScopeId {
let res = self.scopes.len();
self.scopes.push(ScopeData { parent: None, entries: vec![] });
self.scopes.push(ScopeData {
parent: None,
entries: vec![],
});
res
}
fn new_scope(&mut self, parent: ScopeId) -> ScopeId {
let res = self.scopes.len();
self.scopes.push(ScopeData { parent: Some(parent), entries: vec![] });
self.scopes.push(ScopeData {
parent: Some(parent),
entries: vec![],
});
res
}
fn add_bindings(&mut self, scope: ScopeId, pat: ast::Pat) {
let entries = pat.syntax().descendants()
let entries = pat
.syntax()
.descendants()
.filter_map(ast::BindPat::cast)
.filter_map(ScopeEntry::new);
self.scopes[scope].entries.extend(entries);
}
fn add_params_bindings(&mut self, scope: ScopeId, params: Option<ast::ParamList>) {
params.into_iter()
params
.into_iter()
.flat_map(|it| it.params())
.filter_map(|it| it.pat())
.for_each(|it| self.add_bindings(scope, it));
@ -71,34 +84,33 @@ impl FnScopes {
}
pub struct ScopeEntry {
syntax: SyntaxNode
syntax: SyntaxNode,
}
impl ScopeEntry {
fn new(pat: ast::BindPat) -> Option<ScopeEntry> {
if pat.name().is_some() {
Some(ScopeEntry { syntax: pat.syntax().owned() })
Some(ScopeEntry {
syntax: pat.syntax().owned(),
})
} else {
None
}
}
pub fn name(&self) -> SmolStr {
self.ast().name()
.unwrap()
.text()
self.ast().name().unwrap().text()
}
pub fn ast(&self) -> ast::BindPat {
ast::BindPat::cast(self.syntax.borrowed())
.unwrap()
ast::BindPat::cast(self.syntax.borrowed()).unwrap()
}
}
impl fmt::Debug for ScopeEntry {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("ScopeEntry")
.field("name", &self.name())
.field("syntax", &self.syntax)
.finish()
.field("name", &self.name())
.field("syntax", &self.syntax)
.finish()
}
}
@ -132,16 +144,16 @@ fn compute_block_scopes(block: ast::Block, scopes: &mut FnScopes, mut scope: Sco
fn compute_expr_scopes(expr: ast::Expr, scopes: &mut FnScopes, scope: ScopeId) {
match expr {
ast::Expr::IfExpr(e) => {
let cond_scope = e.condition().and_then(|cond| {
compute_cond_scopes(cond, scopes, scope)
});
let cond_scope = e
.condition()
.and_then(|cond| compute_cond_scopes(cond, scopes, scope));
if let Some(block) = e.then_branch() {
compute_block_scopes(block, scopes, cond_scope.unwrap_or(scope));
}
if let Some(block) = e.else_branch() {
compute_block_scopes(block, scopes, scope);
}
},
}
ast::Expr::BlockExpr(e) => {
if let Some(block) = e.block() {
compute_block_scopes(block, scopes, scope);
@ -153,9 +165,9 @@ fn compute_expr_scopes(expr: ast::Expr, scopes: &mut FnScopes, scope: ScopeId) {
}
}
ast::Expr::WhileExpr(e) => {
let cond_scope = e.condition().and_then(|cond| {
compute_cond_scopes(cond, scopes, scope)
});
let cond_scope = e
.condition()
.and_then(|cond| compute_cond_scopes(cond, scopes, scope));
if let Some(block) = e.loop_body() {
compute_block_scopes(block, scopes, cond_scope.unwrap_or(scope));
}
@ -201,25 +213,31 @@ fn compute_expr_scopes(expr: ast::Expr, scopes: &mut FnScopes, scope: ScopeId) {
}
}
}
_ => {
expr.syntax().children()
.filter_map(ast::Expr::cast)
.for_each(|expr| compute_expr_scopes(expr, scopes, scope))
}
_ => expr
.syntax()
.children()
.filter_map(ast::Expr::cast)
.for_each(|expr| compute_expr_scopes(expr, scopes, scope)),
};
fn compute_call_scopes(
receiver: Option<ast::Expr>,
arg_list: Option<ast::ArgList>,
scopes: &mut FnScopes, scope: ScopeId,
scopes: &mut FnScopes,
scope: ScopeId,
) {
arg_list.into_iter()
arg_list
.into_iter()
.flat_map(|it| it.args())
.chain(receiver)
.for_each(|expr| compute_expr_scopes(expr, scopes, scope));
}
fn compute_cond_scopes(cond: ast::Condition, scopes: &mut FnScopes, scope: ScopeId) -> Option<ScopeId> {
fn compute_cond_scopes(
cond: ast::Condition,
scopes: &mut FnScopes,
scope: ScopeId,
) -> Option<ScopeId> {
if let Some(expr) = cond.expr() {
compute_expr_scopes(expr, scopes, scope);
}
@ -236,14 +254,18 @@ fn compute_expr_scopes(expr: ast::Expr, scopes: &mut FnScopes, scope: ScopeId) {
#[derive(Debug)]
struct ScopeData {
parent: Option<ScopeId>,
entries: Vec<ScopeEntry>
entries: Vec<ScopeEntry>,
}
pub fn resolve_local_name<'a>(name_ref: ast::NameRef, scopes: &'a FnScopes) -> Option<&'a ScopeEntry> {
pub fn resolve_local_name<'a>(
name_ref: ast::NameRef,
scopes: &'a FnScopes,
) -> Option<&'a ScopeEntry> {
use rustc_hash::FxHashSet;
let mut shadowed = FxHashSet::default();
let ret = scopes.scope_chain(name_ref.syntax())
let ret = scopes
.scope_chain(name_ref.syntax())
.flat_map(|scope| scopes.entries(scope).iter())
.filter(|entry| shadowed.insert(entry.name()))
.filter(|entry| entry.name() == name_ref.text())
@ -255,8 +277,8 @@ pub fn resolve_local_name<'a>(name_ref: ast::NameRef, scopes: &'a FnScopes) -> O
#[cfg(test)]
mod tests {
use super::*;
use ra_syntax::File;
use crate::{find_node_at_offset, test_utils::extract_offset};
use ra_syntax::File;
fn do_check(code: &str, expected: &[&str]) {
let (off, code) = extract_offset(code);
@ -272,7 +294,8 @@ mod tests {
let marker: ast::PathExpr = find_node_at_offset(file.syntax(), off).unwrap();
let fn_def: ast::FnDef = find_node_at_offset(file.syntax(), off).unwrap();
let scopes = FnScopes::new(fn_def);
let actual = scopes.scope_chain(marker.syntax())
let actual = scopes
.scope_chain(marker.syntax())
.flat_map(|scope| scopes.entries(scope))
.map(|it| it.name())
.collect::<Vec<_>>();
@ -281,7 +304,8 @@ mod tests {
#[test]
fn test_lambda_scope() {
do_check(r"
do_check(
r"
fn quux(foo: i32) {
let f = |bar, baz: i32| {
<|>
@ -293,7 +317,8 @@ mod tests {
#[test]
fn test_call_scope() {
do_check(r"
do_check(
r"
fn quux() {
f(|x| <|> );
}",
@ -303,7 +328,8 @@ mod tests {
#[test]
fn test_metod_call_scope() {
do_check(r"
do_check(
r"
fn quux() {
z.f(|x| <|> );
}",
@ -313,7 +339,8 @@ mod tests {
#[test]
fn test_loop_scope() {
do_check(r"
do_check(
r"
fn quux() {
loop {
let x = ();
@ -326,7 +353,8 @@ mod tests {
#[test]
fn test_match() {
do_check(r"
do_check(
r"
fn quux() {
match () {
Some(x) => {
@ -340,7 +368,8 @@ mod tests {
#[test]
fn test_shadow_variable() {
do_check(r"
do_check(
r"
fn foo(x: String) {
let x : &str = &x<|>;
}",
@ -356,14 +385,20 @@ mod tests {
let scopes = FnScopes::new(fn_def);
let local_name = resolve_local_name(name_ref, &scopes).unwrap().ast().name().unwrap();
let expected_name = find_node_at_offset::<ast::Name>(file.syntax(), expected_offset.into()).unwrap();
let local_name = resolve_local_name(name_ref, &scopes)
.unwrap()
.ast()
.name()
.unwrap();
let expected_name =
find_node_at_offset::<ast::Name>(file.syntax(), expected_offset.into()).unwrap();
assert_eq!(local_name.syntax().range(), expected_name.syntax().range());
}
#[test]
fn test_resolve_local_name() {
do_check_local_name(r#"
do_check_local_name(
r#"
fn foo(x: i32, y: u32) {
{
let z = x * 2;
@ -372,25 +407,30 @@ mod tests {
let t = x<|> * 3;
}
}"#,
21);
21,
);
}
#[test]
fn test_resolve_local_name_declaration() {
do_check_local_name(r#"
do_check_local_name(
r#"
fn foo(x: String) {
let x : &str = &x<|>;
}"#,
21);
21,
);
}
#[test]
fn test_resolve_local_name_shadow() {
do_check_local_name(r"
do_check_local_name(
r"
fn foo(x: String) {
let x : &str = &x;
x<|>
}",
46);
46,
);
}
}

View file

@ -2,7 +2,6 @@ mod fn_scope;
mod mod_scope;
pub use self::{
fn_scope::{FnScopes, resolve_local_name},
fn_scope::{resolve_local_name, FnScopes},
mod_scope::ModuleScope,
};

View file

@ -1,6 +1,6 @@
use ra_syntax::{
AstNode, SyntaxNode, SyntaxNodeRef, SmolStr,
ast::{self, AstChildren},
AstNode, SmolStr, SyntaxNode, SyntaxNodeRef,
};
pub struct ModuleScope {
@ -13,7 +13,8 @@ pub struct Entry {
}
enum EntryKind {
Item, Import,
Item,
Import,
}
impl ModuleScope {
@ -34,9 +35,8 @@ impl ModuleScope {
collect_imports(tree, &mut entries);
}
continue;
},
ast::ModuleItem::ExternCrateItem(_) |
ast::ModuleItem::ImplItem(_) => continue,
}
ast::ModuleItem::ExternCrateItem(_) | ast::ModuleItem::ImplItem(_) => continue,
};
entries.extend(entry)
}
@ -52,20 +52,22 @@ impl ModuleScope {
impl Entry {
fn new<'a>(item: impl ast::NameOwner<'a>) -> Option<Entry> {
let name = item.name()?;
Some(Entry { node: name.syntax().owned(), kind: EntryKind::Item })
Some(Entry {
node: name.syntax().owned(),
kind: EntryKind::Item,
})
}
fn new_import(path: ast::Path) -> Option<Entry> {
let name_ref = path.segment()?.name_ref()?;
Some(Entry { node: name_ref.syntax().owned(), kind: EntryKind::Import })
Some(Entry {
node: name_ref.syntax().owned(),
kind: EntryKind::Import,
})
}
pub fn name(&self) -> SmolStr {
match self.kind {
EntryKind::Item =>
ast::Name::cast(self.node.borrowed()).unwrap()
.text(),
EntryKind::Import =>
ast::NameRef::cast(self.node.borrowed()).unwrap()
.text(),
EntryKind::Item => ast::Name::cast(self.node.borrowed()).unwrap().text(),
EntryKind::Import => ast::NameRef::cast(self.node.borrowed()).unwrap().text(),
}
}
pub fn syntax(&self) -> SyntaxNodeRef {
@ -75,32 +77,31 @@ impl Entry {
fn collect_imports(tree: ast::UseTree, acc: &mut Vec<Entry>) {
if let Some(use_tree_list) = tree.use_tree_list() {
return use_tree_list.use_trees().for_each(|it| collect_imports(it, acc));
return use_tree_list
.use_trees()
.for_each(|it| collect_imports(it, acc));
}
if let Some(path) = tree.path() {
acc.extend(Entry::new_import(path));
}
}
#[cfg(test)]
mod tests {
use super::*;
use ra_syntax::{File, ast::ModuleItemOwner};
use ra_syntax::{ast::ModuleItemOwner, File};
fn do_check(code: &str, expected: &[&str]) {
let file = File::parse(&code);
let scope = ModuleScope::new(file.ast().items());
let actual = scope.entries
.iter()
.map(|it| it.name())
.collect::<Vec<_>>();
let actual = scope.entries.iter().map(|it| it.name()).collect::<Vec<_>>();
assert_eq!(expected, actual.as_slice());
}
#[test]
fn test_module_scope() {
do_check("
do_check(
"
struct Foo;
enum Bar {}
mod baz {}
@ -110,6 +111,8 @@ mod tests {
t,
};
type T = ();
", &["Foo", "Bar", "baz", "quux", "z", "t", "T"])
",
&["Foo", "Bar", "baz", "quux", "z", "t", "T"],
)
}
}

View file

@ -1,12 +1,13 @@
use crate::TextRange;
use ra_syntax::{
SyntaxKind, SyntaxNodeRef, AstNode, File, SmolStr,
ast::{self, NameOwner},
algo::{
visit::{visitor, Visitor},
walk::{walk, WalkEvent},
},
ast::{self, NameOwner},
AstNode, File, SmolStr, SyntaxKind, SyntaxNodeRef,
};
use crate::TextRange;
#[derive(Debug, Clone)]
pub struct StructureNode {
@ -25,9 +26,7 @@ pub struct FileSymbol {
}
pub fn file_symbols(file: &File) -> Vec<FileSymbol> {
file.syntax().descendants()
.filter_map(to_symbol)
.collect()
file.syntax().descendants().filter_map(to_symbol).collect()
}
fn to_symbol(node: SyntaxNodeRef) -> Option<FileSymbol> {
@ -51,23 +50,20 @@ fn to_symbol(node: SyntaxNodeRef) -> Option<FileSymbol> {
.accept(node)?
}
pub fn file_structure(file: &File) -> Vec<StructureNode> {
let mut res = Vec::new();
let mut stack = Vec::new();
for event in walk(file.syntax()) {
match event {
WalkEvent::Enter(node) => {
match structure_node(node) {
Some(mut symbol) => {
symbol.parent = stack.last().map(|&n| n);
stack.push(res.len());
res.push(symbol);
}
None => (),
WalkEvent::Enter(node) => match structure_node(node) {
Some(mut symbol) => {
symbol.parent = stack.last().map(|&n| n);
stack.push(res.len());
res.push(symbol);
}
}
None => (),
},
WalkEvent::Exit(node) => {
if structure_node(node).is_some() {
stack.pop().unwrap();
@ -131,7 +127,8 @@ mod tests {
#[test]
fn test_file_structure() {
let file = File::parse(r#"
let file = File::parse(
r#"
struct Foo {
x: i32
}
@ -148,7 +145,8 @@ const C: i32 = 92;
impl E {}
impl fmt::Debug for E {}
"#);
"#,
);
let symbols = file_structure(&file);
assert_eq_dbg(
r#"[StructureNode { parent: None, label: "Foo", navigation_range: [8; 11), node_range: [1; 26), kind: STRUCT_DEF },

View file

@ -1,12 +1,8 @@
use ra_syntax::{File, TextUnit, TextRange};
pub use crate::_test_utils::*;
use crate::LocalEdit;
pub use crate::_test_utils::*;
use ra_syntax::{File, TextRange, TextUnit};
pub fn check_action<F: Fn(&File, TextUnit) -> Option<LocalEdit>> (
before: &str,
after: &str,
f: F,
) {
pub fn check_action<F: Fn(&File, TextUnit) -> Option<LocalEdit>>(before: &str, after: &str, f: F) {
let (before_cursor_pos, before) = extract_offset(before);
let file = File::parse(&before);
let result = f(&file, before_cursor_pos).expect("code action is not applicable");
@ -19,7 +15,7 @@ pub fn check_action<F: Fn(&File, TextUnit) -> Option<LocalEdit>> (
assert_eq_text!(after, &actual);
}
pub fn check_action_range<F: Fn(&File, TextRange) -> Option<LocalEdit>> (
pub fn check_action_range<F: Fn(&File, TextRange) -> Option<LocalEdit>>(
before: &str,
after: &str,
f: F,

View file

@ -1,32 +1,30 @@
use std::mem;
use ra_syntax::{
TextUnit, TextRange, SyntaxNodeRef, File, AstNode, SyntaxKind,
algo::{find_covering_node, find_leaf_at_offset, LeafAtOffset},
ast,
algo::{
find_covering_node, find_leaf_at_offset, LeafAtOffset,
},
text_utils::{intersect, contains_offset_nonstrict},
text_utils::{contains_offset_nonstrict, intersect},
AstNode, File, SyntaxKind,
SyntaxKind::*,
SyntaxNodeRef, TextRange, TextUnit,
};
use crate::{LocalEdit, EditBuilder, find_node_at_offset};
use crate::{find_node_at_offset, EditBuilder, LocalEdit};
pub fn join_lines(file: &File, range: TextRange) -> LocalEdit {
let range = if range.is_empty() {
let syntax = file.syntax();
let text = syntax.text().slice(range.start()..);
let pos = match text.find('\n') {
None => return LocalEdit {
edit: EditBuilder::new().finish(),
cursor_position: None
},
Some(pos) => pos
None => {
return LocalEdit {
edit: EditBuilder::new().finish(),
cursor_position: None,
}
}
Some(pos) => pos,
};
TextRange::offset_len(
range.start() + pos,
TextUnit::of_char('\n'),
)
TextRange::offset_len(range.start() + pos, TextUnit::of_char('\n'))
} else {
range
};
@ -58,7 +56,9 @@ pub fn join_lines(file: &File, range: TextRange) -> LocalEdit {
}
pub fn on_enter(file: &File, offset: TextUnit) -> Option<LocalEdit> {
let comment = find_leaf_at_offset(file.syntax(), offset).left_biased().and_then(|it| ast::Comment::cast(it))?;
let comment = find_leaf_at_offset(file.syntax(), offset)
.left_biased()
.and_then(|it| ast::Comment::cast(it))?;
if let ast::CommentFlavor::Multiline = comment.flavor() {
return None;
@ -88,7 +88,7 @@ fn node_indent<'a>(file: &'a File, node: SyntaxNodeRef) -> Option<&'a str> {
}
LeafAtOffset::Single(n) => {
assert!(n == node);
return Some("")
return Some("");
}
LeafAtOffset::None => unreachable!(),
};
@ -110,7 +110,12 @@ pub fn on_eq_typed(file: &File, offset: TextUnit) -> Option<LocalEdit> {
if contains_offset_nonstrict(expr_range, offset) && offset != expr_range.start() {
return None;
}
if file.syntax().text().slice(offset..expr_range.start()).contains('\n') {
if file
.syntax()
.text()
.slice(offset..expr_range.start())
.contains('\n')
{
return None;
}
} else {
@ -125,12 +130,7 @@ pub fn on_eq_typed(file: &File, offset: TextUnit) -> Option<LocalEdit> {
})
}
fn remove_newline(
edit: &mut EditBuilder,
node: SyntaxNodeRef,
node_text: &str,
offset: TextUnit,
) {
fn remove_newline(edit: &mut EditBuilder, node: SyntaxNodeRef, node_text: &str, offset: TextUnit) {
if node.kind() != WHITESPACE || node_text.bytes().filter(|&b| b == b'\n').count() != 1 {
// The node is either the first or the last in the file
let suff = &node_text[TextRange::from_to(
@ -156,7 +156,7 @@ fn remove_newline(
//
// into `my_function(<some-expr>)`
if join_single_expr_block(edit, node).is_some() {
return
return;
}
// The node is between two other nodes
@ -170,34 +170,28 @@ fn remove_newline(
// Adds: a single whitespace
edit.replace(
TextRange::from_to(prev.range().start(), node.range().end()),
" ".to_string()
" ".to_string(),
);
} else if let (Some(_), Some(next)) = (ast::Comment::cast(prev), ast::Comment::cast(next)) {
// Removes: newline (incl. surrounding whitespace), start of the next comment
edit.delete(TextRange::from_to(
node.range().start(),
next.syntax().range().start() + TextUnit::of_str(next.prefix())
next.syntax().range().start() + TextUnit::of_str(next.prefix()),
));
} else {
// Remove newline but add a computed amount of whitespace characters
edit.replace(
node.range(),
compute_ws(prev, next).to_string(),
);
edit.replace(node.range(), compute_ws(prev, next).to_string());
}
}
fn is_trailing_comma(left: SyntaxKind, right: SyntaxKind) -> bool {
match (left, right) {
(COMMA, R_PAREN) | (COMMA, R_BRACK) => true,
_ => false
(COMMA, R_PAREN) | (COMMA, R_BRACK) => true,
_ => false,
}
}
fn join_single_expr_block(
edit: &mut EditBuilder,
node: SyntaxNodeRef,
) -> Option<()> {
fn join_single_expr_block(edit: &mut EditBuilder, node: SyntaxNodeRef) -> Option<()> {
let block = ast::Block::cast(node.parent()?)?;
let block_expr = ast::BlockExpr::cast(block.syntax().parent()?)?;
let expr = single_expr(block)?;
@ -244,7 +238,7 @@ fn compute_ws(left: SyntaxNodeRef, right: SyntaxNodeRef) -> &'static str {
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{check_action, extract_range, extract_offset, add_cursor};
use crate::test_utils::{add_cursor, check_action, extract_offset, extract_range};
fn check_join_lines(before: &str, after: &str) {
check_action(before, after, |file, offset| {
@ -256,118 +250,142 @@ mod tests {
#[test]
fn test_join_lines_comma() {
check_join_lines(r"
check_join_lines(
r"
fn foo() {
<|>foo(1,
)
}
", r"
",
r"
fn foo() {
<|>foo(1)
}
");
",
);
}
#[test]
fn test_join_lines_lambda_block() {
check_join_lines(r"
check_join_lines(
r"
pub fn reparse(&self, edit: &AtomEdit) -> File {
<|>self.incremental_reparse(edit).unwrap_or_else(|| {
self.full_reparse(edit)
})
}
", r"
",
r"
pub fn reparse(&self, edit: &AtomEdit) -> File {
<|>self.incremental_reparse(edit).unwrap_or_else(|| self.full_reparse(edit))
}
");
",
);
}
#[test]
fn test_join_lines_block() {
check_join_lines(r"
check_join_lines(
r"
fn foo() {
foo(<|>{
92
})
}", r"
}",
r"
fn foo() {
foo(<|>92)
}");
}",
);
}
#[test]
fn test_join_lines_normal_comments() {
check_join_lines(r"
check_join_lines(
r"
fn foo() {
// Hello<|>
// world!
}
", r"
",
r"
fn foo() {
// Hello<|> world!
}
");
",
);
}
#[test]
fn test_join_lines_doc_comments() {
check_join_lines(r"
check_join_lines(
r"
fn foo() {
/// Hello<|>
/// world!
}
", r"
",
r"
fn foo() {
/// Hello<|> world!
}
");
",
);
}
#[test]
fn test_join_lines_mod_comments() {
check_join_lines(r"
check_join_lines(
r"
fn foo() {
//! Hello<|>
//! world!
}
", r"
",
r"
fn foo() {
//! Hello<|> world!
}
");
",
);
}
#[test]
fn test_join_lines_multiline_comments_1() {
check_join_lines(r"
check_join_lines(
r"
fn foo() {
// Hello<|>
/* world! */
}
", r"
",
r"
fn foo() {
// Hello<|> world! */
}
");
",
);
}
#[test]
fn test_join_lines_multiline_comments_2() {
check_join_lines(r"
check_join_lines(
r"
fn foo() {
// The<|>
/* quick
brown
fox! */
}
", r"
",
r"
fn foo() {
// The<|> quick
brown
fox! */
}
");
",
);
}
fn check_join_lines_sel(before: &str, after: &str) {
@ -380,59 +398,71 @@ fn foo() {
#[test]
fn test_join_lines_selection_fn_args() {
check_join_lines_sel(r"
check_join_lines_sel(
r"
fn foo() {
<|>foo(1,
2,
3,
<|>)
}
", r"
",
r"
fn foo() {
foo(1, 2, 3)
}
");
",
);
}
#[test]
fn test_join_lines_selection_struct() {
check_join_lines_sel(r"
check_join_lines_sel(
r"
struct Foo <|>{
f: u32,
}<|>
", r"
",
r"
struct Foo { f: u32 }
");
",
);
}
#[test]
fn test_join_lines_selection_dot_chain() {
check_join_lines_sel(r"
check_join_lines_sel(
r"
fn foo() {
join(<|>type_params.type_params()
.filter_map(|it| it.name())
.map(|it| it.text())<|>)
}", r"
}",
r"
fn foo() {
join(type_params.type_params().filter_map(|it| it.name()).map(|it| it.text()))
}");
}",
);
}
#[test]
fn test_join_lines_selection_lambda_block_body() {
check_join_lines_sel(r"
check_join_lines_sel(
r"
pub fn handle_find_matching_brace() {
params.offsets
.map(|offset| <|>{
world.analysis().matching_brace(&file, offset).unwrap_or(offset)
}<|>)
.collect();
}", r"
}",
r"
pub fn handle_find_matching_brace() {
params.offsets
.map(|offset| world.analysis().matching_brace(&file, offset).unwrap_or(offset))
.collect();
}");
}",
);
}
#[test]
@ -454,15 +484,18 @@ pub fn handle_find_matching_brace() {
// let foo =;
// }
// ");
do_check(r"
do_check(
r"
fn foo() {
let foo =<|> 1 + 1
}
", r"
",
r"
fn foo() {
let foo = 1 + 1;
}
");
",
);
// do_check(r"
// fn foo() {
// let foo =<|>
@ -496,28 +529,34 @@ fn foo() {
assert!(apply_on_enter(text).is_none())
}
do_check(r"
do_check(
r"
/// Some docs<|>
fn foo() {
}
", r"
",
r"
/// Some docs
/// <|>
fn foo() {
}
");
do_check(r"
",
);
do_check(
r"
impl S {
/// Some<|> docs.
fn foo() {}
}
", r"
",
r"
impl S {
/// Some
/// <|> docs.
fn foo() {}
}
");
",
);
do_check_noop(r"<|>//! docz");
}
}