From e010b144d5abcbd0947d0490123ef693a6a17c78 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 30 Sep 2019 09:27:26 +0300 Subject: move field list to ast/edit.rs --- crates/ra_assists/src/assist_ctx.rs | 4 +- crates/ra_assists/src/ast_editor.rs | 101 ++--------------------------------- crates/ra_ide_api/src/diagnostics.rs | 10 ++-- crates/ra_syntax/src/algo.rs | 17 +++++- crates/ra_syntax/src/ast/edit.rs | 82 +++++++++++++++++++++++++++- 5 files changed, 106 insertions(+), 108 deletions(-) diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs index cbe12e908..5f564be0b 100644 --- a/crates/ra_assists/src/assist_ctx.rs +++ b/crates/ra_assists/src/assist_ctx.rs @@ -178,9 +178,7 @@ impl AssistBuilder { } pub(crate) fn replace_ast(&mut self, old: N, new: N) { - for (from, to) in algo::diff(old.syntax(), new.syntax()) { - self.edit.replace(from.text_range(), to.to_string()) - } + algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit) } fn build(self) -> AssistAction { diff --git a/crates/ra_assists/src/ast_editor.rs b/crates/ra_assists/src/ast_editor.rs index 262e2fcf4..54849b7b0 100644 --- a/crates/ra_assists/src/ast_editor.rs +++ b/crates/ra_assists/src/ast_editor.rs @@ -1,15 +1,12 @@ use std::{iter, ops::RangeInclusive}; -use arrayvec::ArrayVec; -use rustc_hash::FxHashMap; - -use ra_fmt::leading_indent; use ra_syntax::{ algo, - ast::{self, make::tokens, TypeBoundsOwner}, - AstNode, Direction, InsertPosition, SyntaxElement, T, + ast::{self, TypeBoundsOwner}, + AstNode, SyntaxElement, }; use ra_text_edit::TextEditBuilder; +use rustc_hash::FxHashMap; pub struct AstEditor { original_ast: N, @@ -25,9 +22,7 @@ impl AstEditor { } pub fn into_text_edit(self, builder: &mut TextEditBuilder) { - for (from, to) in algo::diff(&self.original_ast.syntax(), self.ast().syntax()) { - builder.replace(from.text_range(), to.to_string()) - } + algo::diff(&self.original_ast.syntax(), self.ast().syntax()).into_text_edit(builder) } pub fn ast(&self) -> &N { @@ -46,16 +41,6 @@ impl AstEditor { self } - #[must_use] - fn insert_children( - &self, - position: InsertPosition, - mut to_insert: impl Iterator, - ) -> N { - let new_syntax = algo::insert_children(self.ast().syntax(), position, &mut to_insert); - N::cast(new_syntax).unwrap() - } - #[must_use] fn replace_children( &self, @@ -67,84 +52,6 @@ impl AstEditor { } } -impl AstEditor { - pub fn append_field(&mut self, field: &ast::RecordField) { - self.insert_field(InsertPosition::Last, field) - } - - pub fn insert_field( - &mut self, - position: InsertPosition<&'_ ast::RecordField>, - field: &ast::RecordField, - ) { - let is_multiline = self.ast().syntax().text().contains_char('\n'); - let ws; - let space = if is_multiline { - ws = tokens::WsBuilder::new(&format!( - "\n{} ", - leading_indent(self.ast().syntax()).unwrap_or("".into()) - )); - ws.ws() - } else { - tokens::single_space() - }; - - let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new(); - to_insert.push(space.into()); - to_insert.push(field.syntax().clone().into()); - to_insert.push(tokens::comma().into()); - - macro_rules! after_l_curly { - () => {{ - let anchor = match self.l_curly() { - Some(it) => it, - None => return, - }; - InsertPosition::After(anchor) - }}; - } - - macro_rules! after_field { - ($anchor:expr) => { - if let Some(comma) = $anchor - .syntax() - .siblings_with_tokens(Direction::Next) - .find(|it| it.kind() == T![,]) - { - InsertPosition::After(comma) - } else { - to_insert.insert(0, tokens::comma().into()); - InsertPosition::After($anchor.syntax().clone().into()) - } - }; - }; - - let position = match position { - InsertPosition::First => after_l_curly!(), - InsertPosition::Last => { - if !is_multiline { - // don't insert comma before curly - to_insert.pop(); - } - match self.ast().fields().last() { - Some(it) => after_field!(it), - None => after_l_curly!(), - } - } - InsertPosition::Before(anchor) => { - InsertPosition::Before(anchor.syntax().clone().into()) - } - InsertPosition::After(anchor) => after_field!(anchor), - }; - - self.ast = self.insert_children(position, to_insert.iter().cloned()); - } - - fn l_curly(&self) -> Option { - self.ast().syntax().children_with_tokens().find(|it| it.kind() == T!['{']) - } -} - impl AstEditor { pub fn remove_bounds(&mut self) -> &mut Self { let colon = match self.ast.colon_token() { diff --git a/crates/ra_ide_api/src/diagnostics.rs b/crates/ra_ide_api/src/diagnostics.rs index 144bc0a70..4fa07e3dc 100644 --- a/crates/ra_ide_api/src/diagnostics.rs +++ b/crates/ra_ide_api/src/diagnostics.rs @@ -2,10 +2,10 @@ use std::cell::RefCell; use hir::diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink}; use itertools::Itertools; -use ra_assists::ast_editor::AstEditor; use ra_db::SourceDatabase; use ra_prof::profile; use ra_syntax::{ + algo, ast::{self, make, AstNode}, Location, SyntaxNode, TextRange, T, }; @@ -56,15 +56,15 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec }) }) .on::(|d| { - let node = d.ast(db); - let mut ast_editor = AstEditor::new(node); + let mut field_list = d.ast(db); for f in d.missed_fields.iter() { let field = make::record_field(make::name_ref(&f.to_string()), Some(make::expr_unit())); - ast_editor.append_field(&field); + field_list = field_list.append_field(&field); } let mut builder = TextEditBuilder::default(); - ast_editor.into_text_edit(&mut builder); + algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder); + let fix = SourceChange::source_file_edit_from("fill struct fields", file_id, builder.finish()); res.borrow_mut().push(Diagnostic { diff --git a/crates/ra_syntax/src/algo.rs b/crates/ra_syntax/src/algo.rs index 46680a08f..f33d2ad4e 100644 --- a/crates/ra_syntax/src/algo.rs +++ b/crates/ra_syntax/src/algo.rs @@ -3,6 +3,7 @@ pub mod visit; use std::ops::RangeInclusive; use itertools::Itertools; +use ra_text_edit::TextEditBuilder; use rustc_hash::FxHashMap; use crate::{ @@ -63,6 +64,18 @@ pub enum InsertPosition { After(T), } +pub struct TreeDiff { + replacements: FxHashMap, +} + +impl TreeDiff { + pub fn into_text_edit(&self, builder: &mut TextEditBuilder) { + for (from, to) in self.replacements.iter() { + builder.replace(from.text_range(), to.to_string()) + } + } +} + /// Finds minimal the diff, which, applied to `from`, will result in `to`. /// /// Specifically, returns a map whose keys are descendants of `from` and values @@ -70,12 +83,12 @@ pub enum InsertPosition { /// /// A trivial solution is a singletom map `{ from: to }`, but this function /// tries to find a more fine-grained diff. -pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> FxHashMap { +pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff { let mut buf = FxHashMap::default(); // FIXME: this is both horrible inefficient and gives larger than // necessary diff. I bet there's a cool algorithm to diff trees properly. go(&mut buf, from.clone().into(), to.clone().into()); - return buf; + return TreeDiff { replacements: buf }; fn go( buf: &mut FxHashMap, diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs index 2af6f573e..6e64c0675 100644 --- a/crates/ra_syntax/src/ast/edit.rs +++ b/crates/ra_syntax/src/ast/edit.rs @@ -12,7 +12,7 @@ use crate::{ make::{self, tokens}, AstNode, }, - AstToken, InsertPosition, SmolStr, SyntaxElement, + AstToken, Direction, InsertPosition, SmolStr, SyntaxElement, SyntaxKind::{ATTR, COMMENT, WHITESPACE}, SyntaxNode, T, }; @@ -105,6 +105,86 @@ impl ast::ItemList { } } +impl ast::RecordFieldList { + #[must_use] + pub fn append_field(&self, field: &ast::RecordField) -> ast::RecordFieldList { + self.insert_field(InsertPosition::Last, field) + } + + #[must_use] + pub fn insert_field( + &self, + position: InsertPosition<&'_ ast::RecordField>, + field: &ast::RecordField, + ) -> ast::RecordFieldList { + let is_multiline = self.syntax().text().contains_char('\n'); + let ws; + let space = if is_multiline { + ws = tokens::WsBuilder::new(&format!( + "\n{} ", + leading_indent(self.syntax()).unwrap_or("".into()) + )); + ws.ws() + } else { + tokens::single_space() + }; + + let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new(); + to_insert.push(space.into()); + to_insert.push(field.syntax().clone().into()); + to_insert.push(tokens::comma().into()); + + macro_rules! after_l_curly { + () => {{ + let anchor = match self.l_curly() { + Some(it) => it, + None => return self.clone(), + }; + InsertPosition::After(anchor) + }}; + } + + macro_rules! after_field { + ($anchor:expr) => { + if let Some(comma) = $anchor + .syntax() + .siblings_with_tokens(Direction::Next) + .find(|it| it.kind() == T![,]) + { + InsertPosition::After(comma) + } else { + to_insert.insert(0, tokens::comma().into()); + InsertPosition::After($anchor.syntax().clone().into()) + } + }; + }; + + let position = match position { + InsertPosition::First => after_l_curly!(), + InsertPosition::Last => { + if !is_multiline { + // don't insert comma before curly + to_insert.pop(); + } + match self.fields().last() { + Some(it) => after_field!(it), + None => after_l_curly!(), + } + } + InsertPosition::Before(anchor) => { + InsertPosition::Before(anchor.syntax().clone().into()) + } + InsertPosition::After(anchor) => after_field!(anchor), + }; + + insert_children(self, position, to_insert.iter().cloned()) + } + + fn l_curly(&self) -> Option { + self.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) + } +} + pub fn strip_attrs_and_docs(node: N) -> N { N::cast(strip_attrs_and_docs_inner(node.syntax().clone())).unwrap() } -- cgit v1.2.3