From b5021411a84822cb3f1e3aeffad9550dd15bdeb6 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 16 Sep 2018 12:54:24 +0300 Subject: rename all things --- crates/libeditor/Cargo.toml | 15 - crates/libeditor/scope.rs | 0 crates/libeditor/src/code_actions.rs | 218 -------------- crates/libeditor/src/completion.rs | 480 ------------------------------- crates/libeditor/src/edit.rs | 84 ------ crates/libeditor/src/extend_selection.rs | 167 ----------- crates/libeditor/src/lib.rs | 228 --------------- crates/libeditor/src/line_index.rs | 62 ---- crates/libeditor/src/scope/fn_scope.rs | 329 --------------------- crates/libeditor/src/scope/mod.rs | 8 - crates/libeditor/src/scope/mod_scope.rs | 115 -------- crates/libeditor/src/symbols.rs | 167 ----------- crates/libeditor/src/test_utils.rs | 37 --- crates/libeditor/src/typing.rs | 348 ---------------------- 14 files changed, 2258 deletions(-) delete mode 100644 crates/libeditor/Cargo.toml delete mode 100644 crates/libeditor/scope.rs delete mode 100644 crates/libeditor/src/code_actions.rs delete mode 100644 crates/libeditor/src/completion.rs delete mode 100644 crates/libeditor/src/edit.rs delete mode 100644 crates/libeditor/src/extend_selection.rs delete mode 100644 crates/libeditor/src/lib.rs delete mode 100644 crates/libeditor/src/line_index.rs delete mode 100644 crates/libeditor/src/scope/fn_scope.rs delete mode 100644 crates/libeditor/src/scope/mod.rs delete mode 100644 crates/libeditor/src/scope/mod_scope.rs delete mode 100644 crates/libeditor/src/symbols.rs delete mode 100644 crates/libeditor/src/test_utils.rs delete mode 100644 crates/libeditor/src/typing.rs (limited to 'crates/libeditor') diff --git a/crates/libeditor/Cargo.toml b/crates/libeditor/Cargo.toml deleted file mode 100644 index b04da1abe..000000000 --- a/crates/libeditor/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "libeditor" -version = "0.1.0" -authors = ["Aleksey Kladov "] -publish = false - -[dependencies] -itertools = "0.7.8" -superslice = "0.1.0" -join_to_string = "0.1.1" - -libsyntax2 = { path = "../libsyntax2" } - -[dev-dependencies] -test_utils = { path = "../test_utils" } diff --git a/crates/libeditor/scope.rs b/crates/libeditor/scope.rs deleted file mode 100644 index e69de29bb..000000000 diff --git a/crates/libeditor/src/code_actions.rs b/crates/libeditor/src/code_actions.rs deleted file mode 100644 index 4a07d1bc9..000000000 --- a/crates/libeditor/src/code_actions.rs +++ /dev/null @@ -1,218 +0,0 @@ -use join_to_string::join; - -use libsyntax2::{ - File, TextUnit, TextRange, - ast::{self, AstNode, AttrsOwner, TypeParamsOwner, NameOwner}, - SyntaxKind::{COMMA, WHITESPACE}, - SyntaxNodeRef, - algo::{ - Direction, siblings, - find_leaf_at_offset, - find_covering_node, - ancestors, - }, -}; - -use {EditBuilder, Edit, find_node_at_offset}; - -#[derive(Debug)] -pub struct LocalEdit { - pub edit: Edit, - pub cursor_position: Option, -} - -pub fn flip_comma<'a>(file: &'a File, offset: TextUnit) -> Option LocalEdit + 'a> { - let syntax = file.syntax(); - - let comma = find_leaf_at_offset(syntax, offset).find(|leaf| leaf.kind() == COMMA)?; - let left = non_trivia_sibling(comma, Direction::Backward)?; - let right = non_trivia_sibling(comma, Direction::Forward)?; - Some(move || { - let mut edit = EditBuilder::new(); - edit.replace(left.range(), right.text().to_string()); - edit.replace(right.range(), left.text().to_string()); - LocalEdit { - edit: edit.finish(), - cursor_position: None, - } - }) -} - -pub fn add_derive<'a>(file: &'a File, offset: TextUnit) -> Option LocalEdit + 'a> { - let nominal = find_node_at_offset::(file.syntax(), offset)?; - Some(move || { - let derive_attr = nominal - .attrs() - .filter_map(|x| x.as_call()) - .filter(|(name, _arg)| name == "derive") - .map(|(_name, arg)| arg) - .next(); - let mut edit = EditBuilder::new(); - let offset = match derive_attr { - None => { - let node_start = nominal.syntax().range().start(); - edit.insert(node_start, "#[derive()]\n".to_string()); - node_start + TextUnit::of_str("#[derive(") - } - Some(tt) => { - tt.syntax().range().end() - TextUnit::of_char(')') - } - }; - LocalEdit { - edit: edit.finish(), - cursor_position: Some(offset), - } - }) -} - -pub fn add_impl<'a>(file: &'a File, offset: TextUnit) -> Option LocalEdit + 'a> { - let nominal = find_node_at_offset::(file.syntax(), offset)?; - let name = nominal.name()?; - - Some(move || { - let type_params = nominal.type_param_list(); - let mut edit = EditBuilder::new(); - let start_offset = nominal.syntax().range().end(); - 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); - } - 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()); - join(lifetime_params.chain(type_params)) - .surround_with("<", ">") - .to_buf(&mut buf); - } - buf.push_str(" {\n"); - let offset = start_offset + TextUnit::of_str(&buf); - buf.push_str("\n}"); - edit.insert(start_offset, buf); - LocalEdit { - edit: edit.finish(), - cursor_position: Some(offset), - } - }) -} - -pub fn introduce_variable<'a>(file: &'a File, range: TextRange) -> Option LocalEdit + 'a> { - let node = find_covering_node(file.syntax(), range); - let expr = ancestors(node).filter_map(ast::Expr::cast).next()?; - let anchor_stmt = ancestors(expr.syntax()).filter_map(ast::Stmt::cast).next()?; - let indent = anchor_stmt.syntax().prev_sibling()?; - if indent.kind() != WHITESPACE { - return None; - } - Some(move || { - let mut buf = String::new(); - let mut edit = EditBuilder::new(); - - buf.push_str("let var_name = "); - expr.syntax().text().push_to(&mut buf); - if expr.syntax().range().start() == anchor_stmt.syntax().range().start() { - edit.replace(expr.syntax().range(), buf); - } else { - buf.push_str(";"); - indent.text().push_to(&mut buf); - edit.replace(expr.syntax().range(), "var_name".to_string()); - edit.insert(anchor_stmt.syntax().range().start(), buf); - } - let cursor_position = anchor_stmt.syntax().range().start() + TextUnit::of_str("let "); - LocalEdit { - edit: edit.finish(), - cursor_position: Some(cursor_position), - } - }) -} - -fn non_trivia_sibling(node: SyntaxNodeRef, direction: Direction) -> Option { - siblings(node, direction) - .skip(1) - .find(|node| !node.kind().is_trivia()) -} - -#[cfg(test)] -mod tests { - use super::*; - use test_utils::{check_action, check_action_range}; - - #[test] - fn test_swap_comma() { - check_action( - "fn foo(x: i32,<|> y: Result<(), ()>) {}", - "fn foo(y: Result<(), ()>,<|> x: i32) {}", - |file, off| flip_comma(file, off).map(|f| f()), - ) - } - - #[test] - fn test_add_derive() { - check_action( - "struct Foo { a: i32, <|>}", - "#[derive(<|>)]\nstruct Foo { a: i32, }", - |file, off| add_derive(file, off).map(|f| f()), - ); - check_action( - "struct Foo { <|> a: i32, }", - "#[derive(<|>)]\nstruct Foo { a: i32, }", - |file, off| add_derive(file, off).map(|f| f()), - ); - check_action( - "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", - "#[derive(Clone<|>)]\nstruct Foo { a: i32, }", - |file, off| add_derive(file, off).map(|f| f()), - ); - } - - #[test] - fn test_add_impl() { - check_action( - "struct Foo {<|>}\n", - "struct Foo {}\n\nimpl Foo {\n<|>\n}\n", - |file, off| add_impl(file, off).map(|f| f()), - ); - check_action( - "struct Foo {<|>}", - "struct Foo {}\n\nimpl Foo {\n<|>\n}", - |file, off| add_impl(file, off).map(|f| f()), - ); - check_action( - "struct Foo<'a, T: Foo<'a>> {<|>}", - "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}", - |file, off| add_impl(file, off).map(|f| f()), - ); - } - - #[test] - fn test_intrdoduce_var_simple() { - check_action_range( - " -fn foo() { - foo(<|>1 + 1<|>); -}", " -fn foo() { - let <|>var_name = 1 + 1; - foo(var_name); -}", - |file, range| introduce_variable(file, range).map(|f| f()), - ); - } - #[test] - fn test_intrdoduce_var_expr_stmt() { -check_action_range( - " -fn foo() { - <|>1 + 1<|>; -}", " -fn foo() { - let <|>var_name = 1 + 1; -}", - |file, range| introduce_variable(file, range).map(|f| f()), - ); - } - -} diff --git a/crates/libeditor/src/completion.rs b/crates/libeditor/src/completion.rs deleted file mode 100644 index 52df6fd10..000000000 --- a/crates/libeditor/src/completion.rs +++ /dev/null @@ -1,480 +0,0 @@ -use std::collections::{HashSet, HashMap}; - -use libsyntax2::{ - File, TextUnit, AstNode, SyntaxNodeRef, SyntaxKind::*, - ast::{self, LoopBodyOwner, ModuleItemOwner}, - algo::{ - ancestors, - visit::{visitor, Visitor, visitor_ctx, VisitorCtx}, - }, - text_utils::is_subrange, -}; - -use { - AtomEdit, find_node_at_offset, - scope::{FnScopes, ModuleScope}, -}; - -#[derive(Debug)] -pub struct CompletionItem { - /// What user sees in pop-up - pub label: String, - /// What string is used for filtering, defaults to label - pub lookup: Option, - /// What is inserted, defaults to label - pub snippet: Option -} - -pub fn scope_completion(file: &File, offset: TextUnit) -> Option> { - // Insert a fake ident to get a valid parse tree - let file = { - let edit = AtomEdit::insert(offset, "intellijRulezz".to_string()); - file.reparse(&edit) - }; - let mut has_completions = false; - let mut res = Vec::new(); - if let Some(name_ref) = find_node_at_offset::(file.syntax(), offset) { - has_completions = true; - complete_name_ref(&file, name_ref, &mut res); - // special case, `trait T { fn foo(i_am_a_name_ref) {} }` - if is_node::(name_ref.syntax()) { - param_completions(name_ref.syntax(), &mut res); - } - } - if let Some(name) = find_node_at_offset::(file.syntax(), offset) { - if is_node::(name.syntax()) { - has_completions = true; - param_completions(name.syntax(), &mut res); - } - } - if has_completions { - Some(res) - } else { - None - } -} - -fn complete_name_ref(file: &File, name_ref: ast::NameRef, acc: &mut Vec) { - if !is_node::(name_ref.syntax()) { - return; - } - let mut visited_fn = false; - for node in ancestors(name_ref.syntax()) { - if let Some(items) = visitor() - .visit::(|it| Some(it.items())) - .visit::(|it| Some(it.item_list()?.items())) - .accept(node) { - if let Some(items) = items { - let scope = ModuleScope::new(items); - acc.extend( - 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; - complete_expr_keywords(&file, fn_def, name_ref, acc); - let scopes = FnScopes::new(fn_def); - complete_fn(name_ref, &scopes, acc); - } - } - } -} - -fn param_completions(ctx: SyntaxNodeRef, acc: &mut Vec) { - let mut params = HashMap::new(); - for node in ancestors(ctx) { - let _ = visitor_ctx(&mut params) - .visit::(process) - .visit::(process) - .accept(node); - } - params.into_iter() - .filter_map(|(label, (count, param))| { - let lookup = param.pat()?.syntax().text().to_string(); - if count < 2 { None } else { Some((label, lookup)) } - }) - .for_each(|(label, lookup)| { - acc.push(CompletionItem { - label, lookup: Some(lookup), snippet: None - }) - }); - - fn process<'a, N: ast::FnDefOwner<'a>>(node: N, params: &mut HashMap)>) { - 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; - }) - } -} - -fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool { - match ancestors(node).filter_map(N::cast).next() { - None => false, - Some(n) => n.syntax().range() == node.range(), - } -} - - -fn complete_expr_keywords(file: &File, fn_def: ast::FnDef, name_ref: ast::NameRef, acc: &mut Vec) { - acc.push(keyword("if", "if $0 {}")); - acc.push(keyword("match", "match $0 {}")); - acc.push(keyword("while", "while $0 {}")); - acc.push(keyword("loop", "loop {$0}")); - - if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) { - if let Some(if_expr) = find_node_at_offset::(file.syntax(), off) { - if if_expr.syntax().range().end() < name_ref.syntax().range().start() { - acc.push(keyword("else", "else {$0}")); - acc.push(keyword("else if", "else if $0 {}")); - } - } - } - if is_in_loop_body(name_ref) { - acc.push(keyword("continue", "continue")); - acc.push(keyword("break", "break")); - } - acc.extend(complete_return(fn_def, name_ref)); -} - -fn is_in_loop_body(name_ref: ast::NameRef) -> bool { - for node in ancestors(name_ref.syntax()) { - if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR { - break; - } - let loop_body = visitor() - .visit::(LoopBodyOwner::loop_body) - .visit::(LoopBodyOwner::loop_body) - .visit::(LoopBodyOwner::loop_body) - .accept(node); - if let Some(Some(body)) = loop_body { - if is_subrange(body.syntax().range(), name_ref.syntax().range()) { - return true; - } - } - } - false -} - -fn complete_return(fn_def: ast::FnDef, name_ref: ast::NameRef) -> Option { - // let is_last_in_block = ancestors(name_ref.syntax()).filter_map(ast::Expr::cast) - // .next() - // .and_then(|it| it.syntax().parent()) - // .and_then(ast::Block::cast) - // .is_some(); - - // if is_last_in_block { - // return None; - // } - - let is_stmt = match ancestors(name_ref.syntax()).filter_map(ast::ExprStmt::cast).next() { - None => false, - 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;", - (true, false) => "return;", - (false, true) => "return $0", - (false, false) => "return", - }; - Some(keyword("return", snip)) -} - -fn keyword(kw: &str, snip: &str) -> CompletionItem { - CompletionItem { - label: kw.to_string(), - lookup: None, - snippet: Some(snip.to_string()), - } -} - -fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Vec) { - let mut shadowed = HashSet::new(); - acc.extend( - 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 { - label: "self".to_string(), - lookup: None, - snippet: None, - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use test_utils::{assert_eq_dbg, extract_offset}; - - fn check_scope_completion(code: &str, expected_completions: &str) { - let (off, code) = extract_offset(&code); - let file = File::parse(&code); - let completions = scope_completion(&file, off) - .unwrap() - .into_iter() - .filter(|c| c.snippet.is_none()) - .collect::>(); - assert_eq_dbg(expected_completions, &completions); - } - - fn check_snippet_completion(code: &str, expected_completions: &str) { - let (off, code) = extract_offset(&code); - let file = File::parse(&code); - let completions = scope_completion(&file, off) - .unwrap() - .into_iter() - .filter(|c| c.snippet.is_some()) - .collect::>(); - assert_eq_dbg(expected_completions, &completions); - } - - #[test] - fn test_completion_let_scope() { - check_scope_completion(r" - fn quux(x: i32) { - let y = 92; - 1 + <|>; - let z = (); - } - ", r#"[CompletionItem { label: "y", lookup: None, snippet: None }, - CompletionItem { label: "x", lookup: None, snippet: None }, - CompletionItem { label: "quux", lookup: None, snippet: None }]"#); - } - - #[test] - fn test_completion_if_let_scope() { - check_scope_completion(r" - fn quux() { - if let Some(x) = foo() { - let y = 92; - }; - if let Some(a) = bar() { - let b = 62; - 1 + <|> - } - } - ", r#"[CompletionItem { label: "b", lookup: None, snippet: None }, - CompletionItem { label: "a", lookup: None, snippet: None }, - CompletionItem { label: "quux", lookup: None, snippet: None }]"#); - } - - #[test] - fn test_completion_for_scope() { - 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 }]"#); - } - - #[test] - fn test_completion_mod_scope() { - check_scope_completion(r" - struct Foo; - enum Baz {} - fn quux() { - <|> - } - ", r#"[CompletionItem { label: "Foo", lookup: None, snippet: None }, - CompletionItem { label: "Baz", lookup: None, snippet: None }, - CompletionItem { label: "quux", lookup: None, snippet: None }]"#); - } - - #[test] - fn test_completion_mod_scope_no_self_use() { - check_scope_completion(r" - use foo<|>; - ", r#"[]"#); - } - - #[test] - fn test_completion_mod_scope_nested() { - 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 }]"#); - } - - #[test] - fn test_complete_type() { - check_scope_completion(r" - struct Foo; - fn x() -> <|> - ", r#"[CompletionItem { label: "Foo", lookup: None, snippet: None }, - CompletionItem { label: "x", lookup: None, snippet: None }]"#) - } - - #[test] - fn test_complete_shadowing() { - check_scope_completion(r" - fn foo() -> { - let bar = 92; - { - let bar = 62; - <|> - } - } - ", r#"[CompletionItem { label: "bar", lookup: None, snippet: None }, - CompletionItem { label: "foo", lookup: None, snippet: None }]"#) - } - - #[test] - fn test_complete_self() { - check_scope_completion(r" - impl S { fn foo(&self) { <|> } } - ", r#"[CompletionItem { label: "self", lookup: None, snippet: None }]"#) - } - - #[test] - fn test_completion_kewords() { - check_snippet_completion(r" - fn quux() { - <|> - } - ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, - CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, - CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, - CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, - CompletionItem { label: "return", lookup: None, snippet: Some("return") }]"#); - } - - #[test] - fn test_completion_else() { - check_snippet_completion(r" - fn quux() { - if true { - () - } <|> - } - ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, - CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, - CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, - CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, - CompletionItem { label: "else", lookup: None, snippet: Some("else {$0}") }, - CompletionItem { label: "else if", lookup: None, snippet: Some("else if $0 {}") }, - CompletionItem { label: "return", lookup: None, snippet: Some("return") }]"#); - } - - #[test] - fn test_completion_return_value() { - check_snippet_completion(r" - fn quux() -> i32 { - <|> - 92 - } - ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, - CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, - CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, - CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, - CompletionItem { label: "return", lookup: None, snippet: Some("return $0;") }]"#); - check_snippet_completion(r" - fn quux() { - <|> - 92 - } - ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, - CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, - CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, - CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, - CompletionItem { label: "return", lookup: None, snippet: Some("return;") }]"#); - } - - #[test] - fn test_completion_return_no_stmt() { - check_snippet_completion(r" - fn quux() -> i32 { - match () { - () => <|> - } - } - ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, - CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, - CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, - CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, - CompletionItem { label: "return", lookup: None, snippet: Some("return $0") }]"#); - } - - #[test] - fn test_continue_break_completion() { - check_snippet_completion(r" - fn quux() -> i32 { - loop { <|> } - } - ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, - CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, - CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, - CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, - CompletionItem { label: "continue", lookup: None, snippet: Some("continue") }, - CompletionItem { label: "break", lookup: None, snippet: Some("break") }, - CompletionItem { label: "return", lookup: None, snippet: Some("return $0") }]"#); - check_snippet_completion(r" - fn quux() -> i32 { - loop { || { <|> } } - } - ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, - CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, - CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, - CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, - CompletionItem { label: "return", lookup: None, snippet: Some("return $0") }]"#); - } - - #[test] - fn test_param_completion_last_param() { - check_scope_completion(r" - fn foo(file_id: FileId) {} - fn bar(file_id: FileId) {} - fn baz(file<|>) {} - ", r#"[CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#); - } - - #[test] - fn test_param_completion_nth_param() { - check_scope_completion(r" - fn foo(file_id: FileId) {} - fn bar(file_id: FileId) {} - fn baz(file<|>, x: i32) {} - ", r#"[CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#); - } - - #[test] - fn test_param_completion_trait_param() { - check_scope_completion(r" - pub(crate) trait SourceRoot { - pub fn contains(&self, file_id: FileId) -> bool; - pub fn module_map(&self) -> &ModuleMap; - pub fn lines(&self, file_id: FileId) -> &LineIndex; - pub fn syntax(&self, file<|>) - } - ", r#"[CompletionItem { label: "self", lookup: None, snippet: None }, - CompletionItem { label: "SourceRoot", lookup: None, snippet: None }, - CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#); - } -} diff --git a/crates/libeditor/src/edit.rs b/crates/libeditor/src/edit.rs deleted file mode 100644 index 09cf2bd00..000000000 --- a/crates/libeditor/src/edit.rs +++ /dev/null @@ -1,84 +0,0 @@ -use {TextRange, TextUnit}; -use libsyntax2::{ - AtomEdit, - text_utils::contains_offset_nonstrict, -}; - -#[derive(Debug, Clone)] -pub struct Edit { - atoms: Vec, -} - -#[derive(Debug)] -pub struct EditBuilder { - atoms: Vec -} - -impl EditBuilder { - pub fn new() -> EditBuilder { - EditBuilder { atoms: Vec::new() } - } - pub fn replace(&mut self, range: TextRange, replace_with: String) { - self.atoms.push(AtomEdit::replace(range, replace_with)) - } - pub fn delete(&mut self, range: TextRange) { - self.atoms.push(AtomEdit::delete(range)) - } - pub fn insert(&mut self, offset: TextUnit, text: String) { - self.atoms.push(AtomEdit::insert(offset, text)) - } - pub fn finish(self) -> Edit { - let mut atoms = self.atoms; - atoms.sort_by_key(|a| a.delete.start()); - for (a1, a2) in atoms.iter().zip(atoms.iter().skip(1)) { - assert!(a1.delete.end() <= a2.delete.start()) - } - Edit { atoms } - } - pub fn invalidates_offset(&self, offset: TextUnit) -> bool { - self.atoms.iter().any(|atom| contains_offset_nonstrict(atom.delete, offset)) - } -} - -impl Edit { - pub fn into_atoms(self) -> Vec { - self.atoms - } - - pub fn apply(&self, text: &str) -> String { - let mut total_len = text.len(); - for atom in self.atoms.iter() { - total_len += atom.insert.len(); - total_len -= u32::from(atom.delete.end() - atom.delete.start()) as usize; - } - let mut buf = String::with_capacity(total_len); - let mut prev = 0; - for atom in self.atoms.iter() { - let start = u32::from(atom.delete.start()) as usize; - let end = u32::from(atom.delete.end()) as usize; - if start > prev { - buf.push_str(&text[prev..start]); - } - buf.push_str(&atom.insert); - prev = end; - } - buf.push_str(&text[prev..text.len()]); - assert_eq!(buf.len(), total_len); - buf - } - - pub fn apply_to_offset(&self, offset: TextUnit) -> Option { - let mut res = offset; - for atom in self.atoms.iter() { - if atom.delete.start() >= offset { - break; - } - if offset < atom.delete.end() { - return None - } - res += TextUnit::of_str(&atom.insert); - res -= atom.delete.len(); - } - Some(res) - } -} diff --git a/crates/libeditor/src/extend_selection.rs b/crates/libeditor/src/extend_selection.rs deleted file mode 100644 index 9dc59e254..000000000 --- a/crates/libeditor/src/extend_selection.rs +++ /dev/null @@ -1,167 +0,0 @@ -use libsyntax2::{ - File, TextRange, SyntaxNodeRef, TextUnit, - SyntaxKind::*, - algo::{find_leaf_at_offset, LeafAtOffset, find_covering_node, ancestors, Direction, siblings}, -}; - -pub fn extend_selection(file: &File, range: TextRange) -> Option { - let syntax = file.syntax(); - extend(syntax.borrowed(), range) -} - -pub(crate) fn extend(root: SyntaxNodeRef, range: TextRange) -> Option { - if range.is_empty() { - let offset = range.start(); - let mut leaves = find_leaf_at_offset(root, offset); - if leaves.clone().all(|it| it.kind() == WHITESPACE) { - return Some(extend_ws(root, leaves.next()?, offset)); - } - let leaf = match leaves { - LeafAtOffset::None => return None, - LeafAtOffset::Single(l) => l, - LeafAtOffset::Between(l, r) => pick_best(l, r), - }; - return Some(leaf.range()); - }; - let node = find_covering_node(root, range); - if node.kind() == COMMENT && range == node.range() { - if let Some(range) = extend_comments(node) { - return Some(range); - } - } - - match ancestors(node).skip_while(|n| n.range() == range).next() { - None => None, - Some(parent) => Some(parent.range()), - } -} - -fn extend_ws(root: SyntaxNodeRef, ws: SyntaxNodeRef, offset: TextUnit) -> TextRange { - let ws_text = ws.leaf_text().unwrap(); - let suffix = TextRange::from_to(offset, ws.range().end()) - ws.range().start(); - let prefix = TextRange::from_to(ws.range().start(), offset) - ws.range().start(); - let ws_suffix = &ws_text.as_str()[suffix]; - let ws_prefix = &ws_text.as_str()[prefix]; - if ws_text.contains("\n") && !ws_suffix.contains("\n") { - 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() - }; - let end = if root.text().char_at(node.range().end()) == Some('\n') { - node.range().end() + TextUnit::of_char('\n') - } else { - node.range().end() - }; - return TextRange::from_to(start, end); - } - } - ws.range() -} - -fn pick_best<'a>(l: SyntaxNodeRef<'a>, r: SyntaxNodeRef<'a>) -> SyntaxNodeRef<'a> { - return if priority(r) > priority(l) { r } else { l }; - fn priority(n: SyntaxNodeRef) -> usize { - match n.kind() { - WHITESPACE => 0, - IDENT | SELF_KW | SUPER_KW | CRATE_KW => 2, - _ => 1, - } - } -} - -fn extend_comments(node: SyntaxNodeRef) -> Option { - let left = adj_comments(node, Direction::Backward); - let right = adj_comments(node, Direction::Forward); - if left != right { - Some(TextRange::from_to( - left.range().start(), - right.range().end(), - )) - } else { - None - } -} - -fn adj_comments(node: SyntaxNodeRef, dir: Direction) -> SyntaxNodeRef { - let mut res = node; - for node in siblings(node, dir) { - match node.kind() { - COMMENT => res = node, - WHITESPACE if !node.leaf_text().unwrap().as_str().contains("\n\n") => (), - _ => break - } - } - res -} - -#[cfg(test)] -mod tests { - use super::*; - use test_utils::extract_offset; - - fn do_check(before: &str, afters: &[&str]) { - let (cursor, before) = extract_offset(before); - let file = File::parse(&before); - let mut range = TextRange::offset_len(cursor, 0.into()); - for &after in afters { - range = extend_selection(&file, range) - .unwrap(); - let actual = &before[range]; - assert_eq!(after, actual); - } - } - - #[test] - fn test_extend_selection_arith() { - do_check( - r#"fn foo() { <|>1 + 1 }"#, - &["1", "1 + 1", "{ 1 + 1 }"], - ); - } - - #[test] - fn test_extend_selection_start_of_the_lind() { - do_check( - r#" -impl S { -<|> fn foo() { - - } -}"#, - &[" fn foo() {\n\n }\n"] - ); - } - - #[test] - fn test_extend_selection_comments() { - do_check( - r#" -fn bar(){} - -// fn foo() { -// 1 + <|>1 -// } - -// fn foo(){} - "#, - &["// 1 + 1", "// fn foo() {\n// 1 + 1\n// }"] - ); - } - - #[test] - fn test_extend_selection_prefer_idents() { - do_check( - r#" -fn main() { foo<|>+bar;} - "#, - &["foo", "foo+bar"] - ); - do_check( - r#" -fn main() { foo+<|>bar;} - "#, - &["bar", "foo+bar"] - ); - } -} diff --git a/crates/libeditor/src/lib.rs b/crates/libeditor/src/lib.rs deleted file mode 100644 index b3cf2ef55..000000000 --- a/crates/libeditor/src/lib.rs +++ /dev/null @@ -1,228 +0,0 @@ -extern crate libsyntax2; -extern crate superslice; -extern crate itertools; -extern crate join_to_string; -#[cfg(test)] -#[macro_use] -extern crate test_utils as _test_utils; - -mod extend_selection; -mod symbols; -mod line_index; -mod edit; -mod code_actions; -mod typing; -mod completion; -mod scope; -#[cfg(test)] -mod test_utils; - -use libsyntax2::{ - File, TextUnit, TextRange, SyntaxNodeRef, - ast::{self, AstNode, NameOwner}, - algo::{walk, find_leaf_at_offset, ancestors}, - SyntaxKind::{self, *}, -}; -pub use libsyntax2::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}, - completion::{scope_completion, CompletionItem}, -}; - -#[derive(Debug)] -pub struct HighlightedRange { - pub range: TextRange, - pub tag: &'static str, -} - -#[derive(Debug)] -pub struct Diagnostic { - pub range: TextRange, - pub msg: String, -} - -#[derive(Debug)] -pub struct Runnable { - pub range: TextRange, - pub kind: RunnableKind, -} - -#[derive(Debug)] -pub enum RunnableKind { - Test { name: String }, - Bin, -} - -pub fn matching_brace(file: &File, offset: TextUnit) -> Option { - const BRACES: &[SyntaxKind] = &[ - 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| { - let idx = BRACES.iter().position(|&brace| brace == node.kind())?; - Some((node, idx)) - }) - .next()?; - let parent = brace_node.parent()?; - let matching_kind = BRACES[brace_idx ^ 1]; - let matching_node = parent.children() - .find(|node| node.kind() == matching_kind)?; - Some(matching_node.range().start()) -} - -pub fn highlight(file: &File) -> Vec { - let mut res = Vec::new(); - for node in walk::preorder(file.syntax()) { - let tag = match node.kind() { - ERROR => "error", - COMMENT | DOC_COMMENT => "comment", - STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => "string", - ATTR => "attribute", - NAME_REF => "text", - NAME => "function", - INT_NUMBER | FLOAT_NUMBER | CHAR | BYTE => "literal", - LIFETIME => "parameter", - k if k.is_keyword() => "keyword", - _ => continue, - }; - res.push(HighlightedRange { - range: node.range(), - tag, - }) - } - res -} - -pub fn diagnostics(file: &File) -> Vec { - let mut res = Vec::new(); - - for node in walk::preorder(file.syntax()) { - if node.kind() == ERROR { - res.push(Diagnostic { - range: node.range(), - msg: "Syntax Error".to_string(), - }); - } - } - res.extend(file.errors().into_iter().map(|err| Diagnostic { - range: TextRange::offset_len(err.offset, 1.into()), - msg: err.msg, - })); - res -} - -pub fn syntax_tree(file: &File) -> String { - ::libsyntax2::utils::dump_tree(file.syntax()) -} - -pub fn runnables(file: &File) -> Vec { - walk::preorder(file.syntax()) - .filter_map(ast::FnDef::cast) - .filter_map(|f| { - let name = f.name()?.text(); - let kind = if name == "main" { - RunnableKind::Bin - } else if f.has_atom_attr("test") { - RunnableKind::Test { - name: name.to_string() - } - } else { - return None; - }; - Some(Runnable { - range: f.syntax().range(), - kind, - }) - }) - .collect() -} - -pub fn find_node_at_offset<'a, N: AstNode<'a>>( - syntax: SyntaxNodeRef<'a>, - offset: TextUnit, -) -> Option { - let leaves = find_leaf_at_offset(syntax, offset); - let leaf = leaves.clone() - .find(|leaf| !leaf.kind().is_trivia()) - .or_else(|| leaves.right_biased())?; - ancestors(leaf) - .filter_map(N::cast) - .next() -} - -#[cfg(test)] -mod tests { - use super::*; - use test_utils::{assert_eq_dbg, extract_offset, add_cursor}; - - #[test] - fn test_highlighting() { - 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" }, - HighlightedRange { range: [12; 14), tag: "keyword" }, - HighlightedRange { range: [15; 19), tag: "function" }, - HighlightedRange { range: [29; 36), tag: "text" }, - HighlightedRange { range: [38; 50), tag: "string" }, - HighlightedRange { range: [52; 54), tag: "literal" }]"#, - &hls, - ); - } - - #[test] - fn test_runnables() { - let file = File::parse(r#" -fn main() {} - -#[test] -fn test_foo() {} - -#[test] -#[ignore] -fn test_foo() {} -"#); - let runnables = runnables(&file); - assert_eq_dbg( - r#"[Runnable { range: [1; 13), kind: Bin }, - Runnable { range: [15; 39), kind: Test { name: "test_foo" } }, - Runnable { range: [41; 75), kind: Test { name: "test_foo" } }]"#, - &runnables, - ) - } - - #[test] - fn test_matching_brace() { - fn do_check(before: &str, after: &str) { - let (pos, before) = extract_offset(before); - let file = File::parse(&before); - let new_pos = match matching_brace(&file, pos) { - None => pos, - Some(pos) => pos, - }; - let actual = add_cursor(&before, new_pos); - assert_eq_text!(after, &actual); - } - - do_check( - "struct Foo { a: i32, }<|>", - "struct Foo <|>{ a: i32, }", - ); - } -} diff --git a/crates/libeditor/src/line_index.rs b/crates/libeditor/src/line_index.rs deleted file mode 100644 index 9cd8da3a8..000000000 --- a/crates/libeditor/src/line_index.rs +++ /dev/null @@ -1,62 +0,0 @@ -use superslice::Ext; -use ::TextUnit; - -#[derive(Clone, Debug, Hash)] -pub struct LineIndex { - newlines: Vec, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct LineCol { - pub line: u32, - pub col: TextUnit, -} - -impl LineIndex { - pub fn new(text: &str) -> LineIndex { - let mut newlines = vec![0.into()]; - let mut curr = 0.into(); - for c in text.chars() { - curr += TextUnit::of_char(c); - if c == '\n' { - newlines.push(curr); - } - } - LineIndex { newlines } - } - - pub fn line_col(&self, offset: TextUnit) -> LineCol { - 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 }; - } - - pub fn offset(&self, line_col: LineCol) -> TextUnit { - //TODO: return Result - self.newlines[line_col.line as usize] + line_col.col - } -} - -#[test] -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() }); - - 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() }); -} diff --git a/crates/libeditor/src/scope/fn_scope.rs b/crates/libeditor/src/scope/fn_scope.rs deleted file mode 100644 index 60b8ce919..000000000 --- a/crates/libeditor/src/scope/fn_scope.rs +++ /dev/null @@ -1,329 +0,0 @@ -use std::{ - fmt, - collections::HashMap, -}; - -use libsyntax2::{ - SyntaxNodeRef, SyntaxNode, SmolStr, AstNode, - ast::{self, NameOwner, LoopBodyOwner, ArgListOwner}, - algo::{ancestors, generate, walk::preorder} -}; - -type ScopeId = usize; - -#[derive(Debug)] -pub struct FnScopes { - pub self_param: Option, - scopes: Vec, - scope_for: HashMap, -} - -impl FnScopes { - pub fn new(fn_def: ast::FnDef) -> FnScopes { - let mut scopes = FnScopes { - self_param: fn_def.param_list() - .and_then(|it| it.self_param()) - .map(|it| it.syntax().owned()), - scopes: Vec::new(), - scope_for: HashMap::new() - }; - let root = scopes.root_scope(); - scopes.add_params_bindings(root, fn_def.param_list()); - if let Some(body) = fn_def.body() { - compute_block_scopes(body, &mut scopes, root) - } - scopes - } - pub fn entries(&self, scope: ScopeId) -> &[ScopeEntry] { - &self.scopes[scope].entries - } - pub fn scope_chain<'a>(&'a self, node: SyntaxNodeRef) -> impl Iterator + '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![] }); - res - } - fn new_scope(&mut self, parent: ScopeId) -> ScopeId { - let res = self.scopes.len(); - self.scopes.push(ScopeData { parent: Some(parent), entries: vec![] }); - res - } - fn add_bindings(&mut self, scope: ScopeId, pat: ast::Pat) { - let entries = preorder(pat.syntax()) - .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) { - params.into_iter() - .flat_map(|it| it.params()) - .filter_map(|it| it.pat()) - .for_each(|it| self.add_bindings(scope, it)); - } - fn set_scope(&mut self, node: SyntaxNodeRef, scope: ScopeId) { - self.scope_for.insert(node.owned(), scope); - } - fn scope_for(&self, node: SyntaxNodeRef) -> Option { - ancestors(node) - .filter_map(|it| self.scope_for.get(&it.owned()).map(|&scope| scope)) - .next() - } -} - -pub struct ScopeEntry { - syntax: SyntaxNode -} - -impl ScopeEntry { - fn new(pat: ast::BindPat) -> Option { - if pat.name().is_some() { - Some(ScopeEntry { syntax: pat.syntax().owned() }) - } else { - None - } - } - pub fn name(&self) -> SmolStr { - self.ast().name() - .unwrap() - .text() - } - fn ast(&self) -> ast::BindPat { - 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() - } -} - -fn compute_block_scopes(block: ast::Block, scopes: &mut FnScopes, mut scope: ScopeId) { - for stmt in block.statements() { - match stmt { - ast::Stmt::LetStmt(stmt) => { - if let Some(expr) = stmt.initializer() { - scopes.set_scope(expr.syntax(), scope); - compute_expr_scopes(expr, scopes, scope); - } - scope = scopes.new_scope(scope); - if let Some(pat) = stmt.pat() { - scopes.add_bindings(scope, pat); - } - } - ast::Stmt::ExprStmt(expr_stmt) => { - if let Some(expr) = expr_stmt.expr() { - scopes.set_scope(expr.syntax(), scope); - compute_expr_scopes(expr, scopes, scope); - } - } - } - } - if let Some(expr) = block.expr() { - scopes.set_scope(expr.syntax(), scope); - compute_expr_scopes(expr, scopes, scope); - } -} - -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) - }); - 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); - } - } - ast::Expr::LoopExpr(e) => { - if let Some(block) = e.loop_body() { - compute_block_scopes(block, scopes, scope); - } - } - ast::Expr::WhileExpr(e) => { - 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)); - } - } - ast::Expr::ForExpr(e) => { - if let Some(expr) = e.iterable() { - compute_expr_scopes(expr, scopes, scope); - } - let mut scope = scope; - if let Some(pat) = e.pat() { - scope = scopes.new_scope(scope); - scopes.add_bindings(scope, pat); - } - if let Some(block) = e.loop_body() { - compute_block_scopes(block, scopes, scope); - } - } - ast::Expr::LambdaExpr(e) => { - let mut scope = scopes.new_scope(scope); - scopes.add_params_bindings(scope, e.param_list()); - if let Some(body) = e.body() { - scopes.set_scope(body.syntax(), scope); - compute_expr_scopes(body, scopes, scope); - } - } - ast::Expr::CallExpr(e) => { - compute_call_scopes(e.expr(), e.arg_list(), scopes, scope); - } - ast::Expr::MethodCallExpr(e) => { - compute_call_scopes(e.expr(), e.arg_list(), scopes, scope); - } - ast::Expr::MatchExpr(e) => { - if let Some(expr) = e.expr() { - compute_expr_scopes(expr, scopes, scope); - } - for arm in e.match_arm_list().into_iter().flat_map(|it| it.arms()) { - let scope = scopes.new_scope(scope); - for pat in arm.pats() { - scopes.add_bindings(scope, pat); - } - if let Some(expr) = arm.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, - arg_list: Option, - scopes: &mut FnScopes, scope: ScopeId, - ) { - 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 { - if let Some(expr) = cond.expr() { - compute_expr_scopes(expr, scopes, scope); - } - if let Some(pat) = cond.pat() { - let s = scopes.new_scope(scope); - scopes.add_bindings(s, pat); - Some(s) - } else { - None - } - } -} - -#[derive(Debug)] -struct ScopeData { - parent: Option, - entries: Vec -} - -#[cfg(test)] -mod tests { - use super::*; - use libsyntax2::File; - use {find_node_at_offset, test_utils::extract_offset}; - - fn do_check(code: &str, expected: &[&str]) { - let (off, code) = extract_offset(code); - let code = { - let mut buf = String::new(); - let off = u32::from(off) as usize; - buf.push_str(&code[..off]); - buf.push_str("marker"); - buf.push_str(&code[off..]); - buf - }; - let file = File::parse(&code); - 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()) - .flat_map(|scope| scopes.entries(scope)) - .map(|it| it.name()) - .collect::>(); - assert_eq!(expected, actual.as_slice()); - } - - #[test] - fn test_lambda_scope() { - do_check(r" - fn quux(foo: i32) { - let f = |bar, baz: i32| { - <|> - }; - }", - &["bar", "baz", "foo"], - ); - } - - #[test] - fn test_call_scope() { - do_check(r" - fn quux() { - f(|x| <|> ); - }", - &["x"], - ); - } - - #[test] - fn test_metod_call_scope() { - do_check(r" - fn quux() { - z.f(|x| <|> ); - }", - &["x"], - ); - } - - #[test] - fn test_loop_scope() { - do_check(r" - fn quux() { - loop { - let x = (); - <|> - }; - }", - &["x"], - ); - } - - #[test] - fn test_match() { - do_check(r" - fn quux() { - match () { - Some(x) => { - <|> - } - }; - }", - &["x"], - ); - } -} diff --git a/crates/libeditor/src/scope/mod.rs b/crates/libeditor/src/scope/mod.rs deleted file mode 100644 index 2f25230f8..000000000 --- a/crates/libeditor/src/scope/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -mod fn_scope; -mod mod_scope; - -pub use self::{ - fn_scope::FnScopes, - mod_scope::ModuleScope, -}; - diff --git a/crates/libeditor/src/scope/mod_scope.rs b/crates/libeditor/src/scope/mod_scope.rs deleted file mode 100644 index 0ec56a206..000000000 --- a/crates/libeditor/src/scope/mod_scope.rs +++ /dev/null @@ -1,115 +0,0 @@ -use libsyntax2::{ - AstNode, SyntaxNode, SyntaxNodeRef, SmolStr, - ast::{self, AstChildren}, -}; - -pub struct ModuleScope { - entries: Vec, -} - -pub struct Entry { - node: SyntaxNode, - kind: EntryKind, -} - -enum EntryKind { - Item, Import, -} - -impl ModuleScope { - pub fn new(items: AstChildren) -> ModuleScope { - let mut entries = Vec::new(); - for item in items { - let entry = match item { - ast::ModuleItem::StructDef(item) => Entry::new(item), - ast::ModuleItem::EnumDef(item) => Entry::new(item), - ast::ModuleItem::FnDef(item) => Entry::new(item), - ast::ModuleItem::ConstDef(item) => Entry::new(item), - ast::ModuleItem::StaticDef(item) => Entry::new(item), - ast::ModuleItem::TraitDef(item) => Entry::new(item), - ast::ModuleItem::TypeDef(item) => Entry::new(item), - ast::ModuleItem::Module(item) => Entry::new(item), - ast::ModuleItem::UseItem(item) => { - if let Some(tree) = item.use_tree() { - collect_imports(tree, &mut entries); - } - continue; - }, - ast::ModuleItem::ExternCrateItem(_) | - ast::ModuleItem::ImplItem(_) => continue, - }; - entries.extend(entry) - } - - ModuleScope { entries } - } - - pub fn entries(&self) -> &[Entry] { - self.entries.as_slice() - } -} - -impl Entry { - fn new<'a>(item: impl ast::NameOwner<'a>) -> Option { - let name = item.name()?; - Some(Entry { node: name.syntax().owned(), kind: EntryKind::Item }) - } - fn new_import(path: ast::Path) -> Option { - let name_ref = path.segment()?.name_ref()?; - 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(), - } - } - pub fn syntax(&self) -> SyntaxNodeRef { - self.node.borrowed() - } -} - -fn collect_imports(tree: ast::UseTree, acc: &mut Vec) { - if let Some(use_tree_list) = tree.use_tree_list() { - 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 libsyntax2::{File, ast::ModuleItemOwner}; - - 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::>(); - assert_eq!(expected, actual.as_slice()); - } - - #[test] - fn test_module_scope() { - do_check(" - struct Foo; - enum Bar {} - mod baz {} - fn quux() {} - use x::{ - y::z, - t, - }; - type T = (); - ", &["Foo", "Bar", "baz", "quux", "z", "t", "T"]) - } -} diff --git a/crates/libeditor/src/symbols.rs b/crates/libeditor/src/symbols.rs deleted file mode 100644 index 2f9cc9233..000000000 --- a/crates/libeditor/src/symbols.rs +++ /dev/null @@ -1,167 +0,0 @@ -use libsyntax2::{ - SyntaxKind, SyntaxNodeRef, AstNode, File, SmolStr, - ast::{self, NameOwner}, - algo::{ - visit::{visitor, Visitor}, - walk::{walk, WalkEvent, preorder}, - }, -}; -use TextRange; - -#[derive(Debug, Clone)] -pub struct StructureNode { - pub parent: Option, - pub label: String, - pub navigation_range: TextRange, - pub node_range: TextRange, - pub kind: SyntaxKind, -} - -#[derive(Debug, Clone, Hash)] -pub struct FileSymbol { - pub name: SmolStr, - pub node_range: TextRange, - pub kind: SyntaxKind, -} - -pub fn file_symbols(file: &File) -> Vec { - preorder(file.syntax()) - .filter_map(to_symbol) - .collect() -} - -fn to_symbol(node: SyntaxNodeRef) -> Option { - fn decl<'a, N: NameOwner<'a>>(node: N) -> Option { - let name = node.name()?; - Some(FileSymbol { - name: name.text(), - node_range: node.syntax().range(), - kind: node.syntax().kind(), - }) - } - visitor() - .visit(decl::) - .visit(decl::) - .visit(decl::) - .visit(decl::) - .visit(decl::) - .visit(decl::) - .visit(decl::) - .visit(decl::) - .accept(node)? -} - - -pub fn file_structure(file: &File) -> Vec { - 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::Exit(node) => { - if structure_node(node).is_some() { - stack.pop().unwrap(); - } - } - } - } - res -} - -fn structure_node(node: SyntaxNodeRef) -> Option { - fn decl<'a, N: NameOwner<'a>>(node: N) -> Option { - let name = node.name()?; - Some(StructureNode { - parent: None, - label: name.text().to_string(), - navigation_range: name.syntax().range(), - node_range: node.syntax().range(), - kind: node.syntax().kind(), - }) - } - - visitor() - .visit(decl::) - .visit(decl::) - .visit(decl::) - .visit(decl::) - .visit(decl::) - .visit(decl::) - .visit(decl::) - .visit(decl::) - .visit(decl::) - .visit(|im: ast::ImplItem| { - let target_type = im.target_type()?; - let target_trait = im.target_trait(); - let label = match target_trait { - None => format!("impl {}", target_type.syntax().text()), - Some(t) => format!( - "impl {} for {}", - t.syntax().text(), - target_type.syntax().text(), - ), - }; - - let node = StructureNode { - parent: None, - label, - navigation_range: target_type.syntax().range(), - node_range: im.syntax().range(), - kind: im.syntax().kind(), - }; - Some(node) - }) - .accept(node)? -} - -#[cfg(test)] -mod tests { - use super::*; - use test_utils::assert_eq_dbg; - - #[test] - fn test_file_structure() { - let file = File::parse(r#" -struct Foo { - x: i32 -} - -mod m { - fn bar() {} -} - -enum E { X, Y(i32) } -type T = (); -static S: i32 = 92; -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 }, - StructureNode { parent: Some(0), label: "x", navigation_range: [18; 19), node_range: [18; 24), kind: NAMED_FIELD_DEF }, - StructureNode { parent: None, label: "m", navigation_range: [32; 33), node_range: [28; 53), kind: MODULE }, - StructureNode { parent: Some(2), label: "bar", navigation_range: [43; 46), node_range: [40; 51), kind: FN_DEF }, - StructureNode { parent: None, label: "E", navigation_range: [60; 61), node_range: [55; 75), kind: ENUM_DEF }, - StructureNode { parent: None, label: "T", navigation_range: [81; 82), node_range: [76; 88), kind: TYPE_DEF }, - StructureNode { parent: None, label: "S", navigation_range: [96; 97), node_range: [89; 108), kind: STATIC_DEF }, - StructureNode { parent: None, label: "C", navigation_range: [115; 116), node_range: [109; 127), kind: CONST_DEF }, - StructureNode { parent: None, label: "impl E", navigation_range: [134; 135), node_range: [129; 138), kind: IMPL_ITEM }, - StructureNode { parent: None, label: "impl fmt::Debug for E", navigation_range: [160; 161), node_range: [140; 164), kind: IMPL_ITEM }]"#, - &symbols, - ) - } -} diff --git a/crates/libeditor/src/test_utils.rs b/crates/libeditor/src/test_utils.rs deleted file mode 100644 index 9c1279991..000000000 --- a/crates/libeditor/src/test_utils.rs +++ /dev/null @@ -1,37 +0,0 @@ -use libsyntax2::{File, TextUnit, TextRange}; -pub use _test_utils::*; -use LocalEdit; - -pub fn check_action Option> ( - 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"); - let actual = result.edit.apply(&before); - let actual_cursor_pos = match result.cursor_position { - None => result.edit.apply_to_offset(before_cursor_pos).unwrap(), - Some(off) => off, - }; - let actual = add_cursor(&actual, actual_cursor_pos); - assert_eq_text!(after, &actual); -} - -pub fn check_action_range Option> ( - before: &str, - after: &str, - f: F, -) { - let (range, before) = extract_range(before); - let file = File::parse(&before); - let result = f(&file, range).expect("code action is not applicable"); - let actual = result.edit.apply(&before); - let actual_cursor_pos = match result.cursor_position { - None => result.edit.apply_to_offset(range.start()).unwrap(), - Some(off) => off, - }; - let actual = add_cursor(&actual, actual_cursor_pos); - assert_eq_text!(after, &actual); -} diff --git a/crates/libeditor/src/typing.rs b/crates/libeditor/src/typing.rs deleted file mode 100644 index 826b16181..000000000 --- a/crates/libeditor/src/typing.rs +++ /dev/null @@ -1,348 +0,0 @@ -use std::mem; - -use libsyntax2::{ - TextUnit, TextRange, SyntaxNodeRef, File, AstNode, SyntaxKind, - ast, - algo::{ - walk::preorder, - find_covering_node, - }, - text_utils::{intersect, contains_offset_nonstrict}, - SyntaxKind::*, -}; - -use {LocalEdit, EditBuilder, find_node_at_offset}; - -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 - }; - TextRange::offset_len( - range.start() + pos, - TextUnit::of_char('\n'), - ) - } else { - range - }; - let node = find_covering_node(file.syntax(), range); - let mut edit = EditBuilder::new(); - for node in preorder(node) { - let text = match node.leaf_text() { - Some(text) => text, - None => continue, - }; - let range = match intersect(range, node.range()) { - Some(range) => range, - None => continue, - } - node.range().start(); - for (pos, _) in text[range].bytes().enumerate().filter(|&(_, b)| b == b'\n') { - let pos: TextUnit = (pos as u32).into(); - let off = node.range().start() + range.start() + pos; - if !edit.invalidates_offset(off) { - remove_newline(&mut edit, node, text.as_str(), off); - } - } - } - - LocalEdit { - edit: edit.finish(), - cursor_position: None, - } -} - -pub fn on_eq_typed(file: &File, offset: TextUnit) -> Option { - let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?; - if let_stmt.has_semi() { - return None; - } - if let Some(expr) = let_stmt.initializer() { - let expr_range = expr.syntax().range(); - if contains_offset_nonstrict(expr_range, offset) && offset != expr_range.start() { - return None; - } - if file.syntax().text().slice(offset..expr_range.start()).contains('\n') { - return None; - } - } else { - return None; - } - let offset = let_stmt.syntax().range().end(); - let mut edit = EditBuilder::new(); - edit.insert(offset, ";".to_string()); - Some(LocalEdit { - edit: edit.finish(), - cursor_position: None, - }) -} - -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 { - if join_single_expr_block(edit, node).is_some() { - return - } - match (node.prev_sibling(), node.next_sibling()) { - (Some(prev), Some(next)) => { - let range = TextRange::from_to(prev.range().start(), node.range().end()); - if is_trailing_comma(prev.kind(), next.kind()) { - edit.delete(range); - } else if no_space_required(prev.kind(), next.kind()) { - edit.delete(node.range()); - } else if prev.kind() == COMMA && next.kind() == R_CURLY { - edit.replace(range, " ".to_string()); - } else { - edit.replace( - node.range(), - compute_ws(prev, next).to_string(), - ); - } - return; - } - _ => (), - } - } - - let suff = &node_text[TextRange::from_to( - offset - node.range().start() + TextUnit::of_char('\n'), - TextUnit::of_str(node_text), - )]; - let spaces = suff.bytes().take_while(|&b| b == b' ').count(); - - edit.replace( - TextRange::offset_len(offset, ((spaces + 1) as u32).into()), - " ".to_string(), - ); -} - -fn is_trailing_comma(left: SyntaxKind, right: SyntaxKind) -> bool { - match (left, right) { - (COMMA, R_PAREN) | (COMMA, R_BRACK) => true, - _ => false - } -} - -fn no_space_required(left: SyntaxKind, right: SyntaxKind) -> bool { - match (left, right) { - (_, DOT) => true, - _ => false - } -} - -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)?; - edit.replace( - block_expr.syntax().range(), - expr.syntax().text().to_string(), - ); - Some(()) -} - -fn single_expr(block: ast::Block) -> Option { - let mut res = None; - for child in block.syntax().children() { - if let Some(expr) = ast::Expr::cast(child) { - if expr.syntax().text().contains('\n') { - return None; - } - if mem::replace(&mut res, Some(expr)).is_some() { - return None; - } - } else { - match child.kind() { - WHITESPACE | L_CURLY | R_CURLY => (), - _ => return None, - } - } - } - res -} - -fn compute_ws(left: SyntaxNodeRef, right: SyntaxNodeRef) -> &'static str { - match left.kind() { - L_PAREN | L_BRACK => return "", - _ => (), - } - match right.kind() { - R_PAREN | R_BRACK => return "", - _ => (), - } - " " -} - -#[cfg(test)] -mod tests { - use super::*; - use test_utils::{check_action, extract_range, extract_offset}; - - fn check_join_lines(before: &str, after: &str) { - check_action(before, after, |file, offset| { - let range = TextRange::offset_len(offset, 0.into()); - let res = join_lines(file, range); - Some(res) - }) - } - - #[test] - fn test_join_lines_comma() { - check_join_lines(r" -fn foo() { - <|>foo(1, - ) -} -", r" -fn foo() { - <|>foo(1) -} -"); - } - - #[test] - fn test_join_lines_lambda_block() { - check_join_lines(r" -pub fn reparse(&self, edit: &AtomEdit) -> File { - <|>self.incremental_reparse(edit).unwrap_or_else(|| { - self.full_reparse(edit) - }) -} -", 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" -fn foo() { - foo(<|>{ - 92 - }) -}", r" -fn foo() { - foo(<|>92) -}"); - } - - fn check_join_lines_sel(before: &str, after: &str) { - let (sel, before) = extract_range(before); - let file = File::parse(&before); - let result = join_lines(&file, sel); - let actual = result.edit.apply(&before); - assert_eq_text!(after, &actual); - } - - #[test] - fn test_join_lines_selection_fn_args() { - check_join_lines_sel(r" -fn foo() { - <|>foo(1, - 2, - 3, - <|>) -} - ", r" -fn foo() { - foo(1, 2, 3) -} - "); - } - - #[test] - fn test_join_lines_selection_struct() { - check_join_lines_sel(r" -struct Foo <|>{ - f: u32, -}<|> - ", r" -struct Foo { f: u32 } - "); - } - - #[test] - fn test_join_lines_selection_dot_chain() { - check_join_lines_sel(r" -fn foo() { - join(<|>type_params.type_params() - .filter_map(|it| it.name()) - .map(|it| it.text())<|>) -}", 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" -pub fn handle_find_matching_brace() { - params.offsets - .map(|offset| <|>{ - world.analysis().matching_brace(&file, offset).unwrap_or(offset) - }<|>) - .collect(); -}", r" -pub fn handle_find_matching_brace() { - params.offsets - .map(|offset| world.analysis().matching_brace(&file, offset).unwrap_or(offset)) - .collect(); -}"); - } - - #[test] - fn test_on_eq_typed() { - fn do_check(before: &str, after: &str) { - let (offset, before) = extract_offset(before); - let file = File::parse(&before); - let result = on_eq_typed(&file, offset).unwrap(); - let actual = result.edit.apply(&before); - assert_eq_text!(after, &actual); - } - - // do_check(r" - // fn foo() { - // let foo =<|> - // } - // ", r" - // fn foo() { - // let foo =; - // } - // "); - do_check(r" -fn foo() { - let foo =<|> 1 + 1 -} -", r" -fn foo() { - let foo = 1 + 1; -} -"); - // do_check(r" - // fn foo() { - // let foo =<|> - // let bar = 1; - // } - // ", r" - // fn foo() { - // let foo =; - // let bar = 1; - // } - // "); - } -} -- cgit v1.2.3