mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-03 07:04:49 +00:00
Cargo Format
Run `cargo fmt` and ignore generated files
This commit is contained in:
parent
39cb6c6d3f
commit
61f3a438d3
76 changed files with 1936 additions and 1530 deletions
|
@ -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;
|
||||
}",
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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"],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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, }");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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"],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue