mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-11-25 05:23:12 +00:00
move field list to ast/edit.rs
This commit is contained in:
parent
0840ec038b
commit
e010b144d5
5 changed files with 106 additions and 108 deletions
|
|
@ -178,9 +178,7 @@ impl AssistBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
|
pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
|
||||||
for (from, to) in algo::diff(old.syntax(), new.syntax()) {
|
algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
|
||||||
self.edit.replace(from.text_range(), to.to_string())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build(self) -> AssistAction {
|
fn build(self) -> AssistAction {
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,12 @@
|
||||||
use std::{iter, ops::RangeInclusive};
|
use std::{iter, ops::RangeInclusive};
|
||||||
|
|
||||||
use arrayvec::ArrayVec;
|
|
||||||
use rustc_hash::FxHashMap;
|
|
||||||
|
|
||||||
use ra_fmt::leading_indent;
|
|
||||||
use ra_syntax::{
|
use ra_syntax::{
|
||||||
algo,
|
algo,
|
||||||
ast::{self, make::tokens, TypeBoundsOwner},
|
ast::{self, TypeBoundsOwner},
|
||||||
AstNode, Direction, InsertPosition, SyntaxElement, T,
|
AstNode, SyntaxElement,
|
||||||
};
|
};
|
||||||
use ra_text_edit::TextEditBuilder;
|
use ra_text_edit::TextEditBuilder;
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
pub struct AstEditor<N: AstNode> {
|
pub struct AstEditor<N: AstNode> {
|
||||||
original_ast: N,
|
original_ast: N,
|
||||||
|
|
@ -25,9 +22,7 @@ impl<N: AstNode> AstEditor<N> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_text_edit(self, builder: &mut TextEditBuilder) {
|
pub fn into_text_edit(self, builder: &mut TextEditBuilder) {
|
||||||
for (from, to) in algo::diff(&self.original_ast.syntax(), self.ast().syntax()) {
|
algo::diff(&self.original_ast.syntax(), self.ast().syntax()).into_text_edit(builder)
|
||||||
builder.replace(from.text_range(), to.to_string())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ast(&self) -> &N {
|
pub fn ast(&self) -> &N {
|
||||||
|
|
@ -46,16 +41,6 @@ impl<N: AstNode> AstEditor<N> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
fn insert_children(
|
|
||||||
&self,
|
|
||||||
position: InsertPosition<SyntaxElement>,
|
|
||||||
mut to_insert: impl Iterator<Item = SyntaxElement>,
|
|
||||||
) -> N {
|
|
||||||
let new_syntax = algo::insert_children(self.ast().syntax(), position, &mut to_insert);
|
|
||||||
N::cast(new_syntax).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn replace_children(
|
fn replace_children(
|
||||||
&self,
|
&self,
|
||||||
|
|
@ -67,84 +52,6 @@ impl<N: AstNode> AstEditor<N> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AstEditor<ast::RecordFieldList> {
|
|
||||||
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<SyntaxElement> {
|
|
||||||
self.ast().syntax().children_with_tokens().find(|it| it.kind() == T!['{'])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AstEditor<ast::TypeParam> {
|
impl AstEditor<ast::TypeParam> {
|
||||||
pub fn remove_bounds(&mut self) -> &mut Self {
|
pub fn remove_bounds(&mut self) -> &mut Self {
|
||||||
let colon = match self.ast.colon_token() {
|
let colon = match self.ast.colon_token() {
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@ use std::cell::RefCell;
|
||||||
|
|
||||||
use hir::diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink};
|
use hir::diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ra_assists::ast_editor::AstEditor;
|
|
||||||
use ra_db::SourceDatabase;
|
use ra_db::SourceDatabase;
|
||||||
use ra_prof::profile;
|
use ra_prof::profile;
|
||||||
use ra_syntax::{
|
use ra_syntax::{
|
||||||
|
algo,
|
||||||
ast::{self, make, AstNode},
|
ast::{self, make, AstNode},
|
||||||
Location, SyntaxNode, TextRange, T,
|
Location, SyntaxNode, TextRange, T,
|
||||||
};
|
};
|
||||||
|
|
@ -56,15 +56,15 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.on::<hir::diagnostics::MissingFields, _>(|d| {
|
.on::<hir::diagnostics::MissingFields, _>(|d| {
|
||||||
let node = d.ast(db);
|
let mut field_list = d.ast(db);
|
||||||
let mut ast_editor = AstEditor::new(node);
|
|
||||||
for f in d.missed_fields.iter() {
|
for f in d.missed_fields.iter() {
|
||||||
let field = make::record_field(make::name_ref(&f.to_string()), Some(make::expr_unit()));
|
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();
|
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 =
|
let fix =
|
||||||
SourceChange::source_file_edit_from("fill struct fields", file_id, builder.finish());
|
SourceChange::source_file_edit_from("fill struct fields", file_id, builder.finish());
|
||||||
res.borrow_mut().push(Diagnostic {
|
res.borrow_mut().push(Diagnostic {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ pub mod visit;
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use ra_text_edit::TextEditBuilder;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -63,6 +64,18 @@ pub enum InsertPosition<T> {
|
||||||
After(T),
|
After(T),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct TreeDiff {
|
||||||
|
replacements: FxHashMap<SyntaxElement, SyntaxElement>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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`.
|
/// Finds minimal the diff, which, applied to `from`, will result in `to`.
|
||||||
///
|
///
|
||||||
/// Specifically, returns a map whose keys are descendants of `from` and values
|
/// Specifically, returns a map whose keys are descendants of `from` and values
|
||||||
|
|
@ -70,12 +83,12 @@ pub enum InsertPosition<T> {
|
||||||
///
|
///
|
||||||
/// A trivial solution is a singletom map `{ from: to }`, but this function
|
/// A trivial solution is a singletom map `{ from: to }`, but this function
|
||||||
/// tries to find a more fine-grained diff.
|
/// tries to find a more fine-grained diff.
|
||||||
pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> FxHashMap<SyntaxElement, SyntaxElement> {
|
pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff {
|
||||||
let mut buf = FxHashMap::default();
|
let mut buf = FxHashMap::default();
|
||||||
// FIXME: this is both horrible inefficient and gives larger than
|
// FIXME: this is both horrible inefficient and gives larger than
|
||||||
// necessary diff. I bet there's a cool algorithm to diff trees properly.
|
// necessary diff. I bet there's a cool algorithm to diff trees properly.
|
||||||
go(&mut buf, from.clone().into(), to.clone().into());
|
go(&mut buf, from.clone().into(), to.clone().into());
|
||||||
return buf;
|
return TreeDiff { replacements: buf };
|
||||||
|
|
||||||
fn go(
|
fn go(
|
||||||
buf: &mut FxHashMap<SyntaxElement, SyntaxElement>,
|
buf: &mut FxHashMap<SyntaxElement, SyntaxElement>,
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ use crate::{
|
||||||
make::{self, tokens},
|
make::{self, tokens},
|
||||||
AstNode,
|
AstNode,
|
||||||
},
|
},
|
||||||
AstToken, InsertPosition, SmolStr, SyntaxElement,
|
AstToken, Direction, InsertPosition, SmolStr, SyntaxElement,
|
||||||
SyntaxKind::{ATTR, COMMENT, WHITESPACE},
|
SyntaxKind::{ATTR, COMMENT, WHITESPACE},
|
||||||
SyntaxNode, T,
|
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<SyntaxElement> {
|
||||||
|
self.syntax().children_with_tokens().find(|it| it.kind() == T!['{'])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn strip_attrs_and_docs<N: ast::AttrsOwner>(node: N) -> N {
|
pub fn strip_attrs_and_docs<N: ast::AttrsOwner>(node: N) -> N {
|
||||||
N::cast(strip_attrs_and_docs_inner(node.syntax().clone())).unwrap()
|
N::cast(strip_attrs_and_docs_inner(node.syntax().clone())).unwrap()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue