From ea0c124219da33462b9d0be93f7abe0478cc7af2 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 5 Mar 2020 19:03:14 +0100 Subject: [PATCH] Rerail split_import API onto AST The code is more verbose and less efficient now, but should be reusable in add_import context as well --- .../ra_assists/src/handlers/split_import.rs | 52 ++++++++++++------- crates/ra_syntax/src/ast/edit.rs | 18 +++++++ crates/ra_syntax/src/ast/make.rs | 21 ++++++++ 3 files changed, 72 insertions(+), 19 deletions(-) diff --git a/crates/ra_assists/src/handlers/split_import.rs b/crates/ra_assists/src/handlers/split_import.rs index 2c3f07a79d..292c39f59b 100644 --- a/crates/ra_assists/src/handlers/split_import.rs +++ b/crates/ra_assists/src/handlers/split_import.rs @@ -1,6 +1,9 @@ -use std::iter::successors; +use std::iter::{once, successors}; -use ra_syntax::{ast, AstNode, TextUnit, T}; +use ra_syntax::{ + ast::{self, make}, + AstNode, T, +}; use crate::{Assist, AssistCtx, AssistId}; @@ -17,39 +20,50 @@ use crate::{Assist, AssistCtx, AssistId}; // ``` pub(crate) fn split_import(ctx: AssistCtx) -> Option { let colon_colon = ctx.find_token_at_offset(T![::])?; - let path = ast::Path::cast(colon_colon.parent())?; - let top_path = successors(Some(path), |it| it.parent_path()).last()?; + let path = ast::Path::cast(colon_colon.parent())?.qualifier()?; + let top_path = successors(Some(path.clone()), |it| it.parent_path()).last()?; - let use_tree = top_path.syntax().ancestors().find_map(ast::UseTree::cast); - if use_tree.is_none() { - return None; - } + let use_tree = top_path.syntax().ancestors().find_map(ast::UseTree::cast)?; - let l_curly = colon_colon.text_range().end(); - let r_curly = match top_path.syntax().parent().and_then(ast::UseTree::cast) { - Some(tree) => tree.syntax().text_range().end(), - None => top_path.syntax().text_range().end(), - }; + let new_tree = split_use_tree_prefix(&use_tree, &path)?; + let cursor = ctx.frange.range.start(); ctx.add_assist(AssistId("split_import"), "Split import", |edit| { edit.target(colon_colon.text_range()); - edit.insert(l_curly, "{"); - edit.insert(r_curly, "}"); - edit.set_cursor(l_curly + TextUnit::of_str("{")); + edit.replace_ast(use_tree, new_tree); + edit.set_cursor(cursor); }) } +fn split_use_tree_prefix(use_tree: &ast::UseTree, prefix: &ast::Path) -> Option { + let suffix = split_path_prefix(&prefix)?; + let use_tree = make::use_tree(suffix.clone(), use_tree.use_tree_list(), use_tree.alias()); + let nested = make::use_tree_list(once(use_tree)); + let res = make::use_tree(prefix.clone(), Some(nested), None); + Some(res) +} + +fn split_path_prefix(prefix: &ast::Path) -> Option { + let parent = prefix.parent_path()?; + let mut res = make::path_unqualified(parent.segment()?); + for p in successors(parent.parent_path(), |it| it.parent_path()) { + res = make::path_qualified(res, p.segment()?); + } + Some(res) +} + #[cfg(test)] mod tests { - use super::*; use crate::helpers::{check_assist, check_assist_target}; + use super::*; + #[test] fn test_split_import() { check_assist( split_import, "use crate::<|>db::RootDatabase;", - "use crate::{<|>db::RootDatabase};", + "use crate::<|>{db::RootDatabase};", ) } @@ -58,7 +72,7 @@ mod tests { check_assist( split_import, "use crate:<|>:db::{RootDatabase, FileSymbol}", - "use crate::{<|>db::{RootDatabase, FileSymbol}}", + "use crate:<|>:{db::{RootDatabase, FileSymbol}}", ) } diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs index 1858e2b6c1..e4cdccdb43 100644 --- a/crates/ra_syntax/src/ast/edit.rs +++ b/crates/ra_syntax/src/ast/edit.rs @@ -259,6 +259,24 @@ impl ast::UseItem { } } +impl ast::UseTree { + #[must_use] + pub fn with_path(&self, path: ast::Path) -> ast::UseTree { + if let Some(old) = self.path() { + return replace_descendants(self, iter::once((old, path))); + } + self.clone() + } + + #[must_use] + pub fn with_use_tree_list(&self, use_tree_list: ast::UseTreeList) -> ast::UseTree { + if let Some(old) = self.use_tree_list() { + return replace_descendants(self, iter::once((old, use_tree_list))); + } + self.clone() + } +} + #[must_use] pub fn strip_attrs_and_docs(node: &N) -> N { N::cast(strip_attrs_and_docs_inner(node.syntax().clone())).unwrap() diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs index 0da24560ea..22c54f363e 100644 --- a/crates/ra_syntax/src/ast/make.rs +++ b/crates/ra_syntax/src/ast/make.rs @@ -25,6 +25,27 @@ fn path_from_text(text: &str) -> ast::Path { ast_from_text(text) } +pub fn use_tree( + path: ast::Path, + use_tree_list: Option, + alias: Option, +) -> ast::UseTree { + let mut buf = "use ".to_string(); + buf += &path.syntax().to_string(); + if let Some(use_tree_list) = use_tree_list { + buf += &format!("::{}", use_tree_list.syntax()); + } + if let Some(alias) = alias { + buf += &format!(" {}", alias.syntax()); + } + ast_from_text(&buf) +} + +pub fn use_tree_list(use_trees: impl IntoIterator) -> ast::UseTreeList { + let use_trees = use_trees.into_iter().map(|it| it.syntax().clone()).join(", "); + ast_from_text(&format!("use {{{}}};", use_trees)) +} + pub fn record_field(name: ast::NameRef, expr: Option) -> ast::RecordField { return match expr { Some(expr) => from_text(&format!("{}: {}", name.syntax(), expr.syntax())),