Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #18421 from Veykril/push-uxxwvwnqvomr
Move text-edit into ide-db
Lukas Wirth 2024-10-28
parent 09547e9 · parent 27306c5 · commit 8672eb8
-rw-r--r--Cargo.lock15
-rw-r--r--Cargo.toml1
-rw-r--r--crates/ide-assists/Cargo.toml1
-rw-r--r--crates/ide-assists/src/handlers/bool_to_enum.rs2
-rw-r--r--crates/ide-assists/src/handlers/destructure_struct_binding.rs2
-rw-r--r--crates/ide-assists/src/handlers/destructure_tuple_binding.rs2
-rw-r--r--crates/ide-assists/src/handlers/remove_unused_imports.rs2
-rw-r--r--crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs2
-rw-r--r--crates/ide-completion/Cargo.toml1
-rw-r--r--crates/ide-completion/src/completions/item_list/trait_impl.rs2
-rw-r--r--crates/ide-completion/src/completions/postfix.rs2
-rw-r--r--crates/ide-completion/src/context.rs4
-rw-r--r--crates/ide-completion/src/item.rs9
-rw-r--r--crates/ide-completion/src/lib.rs9
-rw-r--r--crates/ide-completion/src/render.rs2
-rw-r--r--crates/ide-db/Cargo.toml1
-rw-r--r--crates/ide-db/src/documentation.rs2
-rw-r--r--crates/ide-db/src/lib.rs2
-rw-r--r--crates/ide-db/src/rename.rs2
-rw-r--r--crates/ide-db/src/source_change.rs11
-rw-r--r--crates/ide-db/src/syntax_helpers/tree_diff.rs559
-rw-r--r--crates/ide-db/src/text_edit.rs (renamed from crates/text-edit/src/lib.rs)2
-rw-r--r--crates/ide-diagnostics/Cargo.toml1
-rw-r--r--crates/ide-diagnostics/src/handlers/field_shorthand.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/json_is_not_rust.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/missing_fields.rs7
-rw-r--r--crates/ide-diagnostics/src/handlers/missing_unsafe.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/mutability_errors.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/no_such_field.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/remove_trailing_return.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/remove_unnecessary_else.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/trait_impl_redundant_assoc_item.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/type_mismatch.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/typed_hole.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/unlinked_file.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/unresolved_field.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/unresolved_method.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/unused_variables.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/useless_braces.rs2
-rw-r--r--crates/ide-ssr/Cargo.toml3
-rw-r--r--crates/ide-ssr/src/lib.rs2
-rw-r--r--crates/ide-ssr/src/replacing.rs2
-rw-r--r--crates/ide/Cargo.toml1
-rw-r--r--crates/ide/src/inlay_hints.rs2
-rw-r--r--crates/ide/src/inlay_hints/adjustment.rs2
-rw-r--r--crates/ide/src/inlay_hints/binding_mode.rs2
-rw-r--r--crates/ide/src/inlay_hints/chaining.rs2
-rw-r--r--crates/ide/src/inlay_hints/closure_captures.rs2
-rw-r--r--crates/ide/src/inlay_hints/discriminant.rs2
-rw-r--r--crates/ide/src/inlay_hints/implicit_static.rs2
-rw-r--r--crates/ide/src/join_lines.rs2
-rw-r--r--crates/ide/src/lib.rs2
-rw-r--r--crates/ide/src/move_item.rs7
-rw-r--r--crates/ide/src/rename.rs4
-rw-r--r--crates/ide/src/typing.rs4
-rw-r--r--crates/ide/src/typing/on_enter.rs2
-rw-r--r--crates/syntax/Cargo.toml1
-rw-r--r--crates/syntax/src/algo.rs561
-rw-r--r--crates/syntax/src/fuzz.rs9
-rw-r--r--crates/syntax/src/lib.rs23
-rw-r--r--crates/syntax/src/parsing/reparsing.rs59
-rw-r--r--crates/text-edit/Cargo.toml20
63 files changed, 684 insertions, 707 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 8bbccc82ae..877c77a5aa 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -671,7 +671,6 @@ dependencies = [
"syntax",
"test-fixture",
"test-utils",
- "text-edit",
"toolchain",
"tracing",
"triomphe",
@@ -693,7 +692,6 @@ dependencies = [
"syntax",
"test-fixture",
"test-utils",
- "text-edit",
"tracing",
]
@@ -712,7 +710,6 @@ dependencies = [
"syntax",
"test-fixture",
"test-utils",
- "text-edit",
"tracing",
]
@@ -744,7 +741,6 @@ dependencies = [
"syntax",
"test-fixture",
"test-utils",
- "text-edit",
"tracing",
"triomphe",
]
@@ -766,7 +762,6 @@ dependencies = [
"syntax",
"test-fixture",
"test-utils",
- "text-edit",
"tracing",
]
@@ -785,7 +780,6 @@ dependencies = [
"syntax",
"test-fixture",
"test-utils",
- "text-edit",
"triomphe",
]
@@ -1979,7 +1973,6 @@ dependencies = [
"smol_str",
"stdx",
"test-utils",
- "text-edit",
"tracing",
"triomphe",
]
@@ -2028,14 +2021,6 @@ dependencies = [
]
[[package]]
-name = "text-edit"
-version = "0.0.0"
-dependencies = [
- "itertools",
- "text-size",
-]
-
-[[package]]
name = "text-size"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 4e6861d351..f729aae4ca 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -79,7 +79,6 @@ span = { path = "./crates/span", version = "0.0.0" }
stdx = { path = "./crates/stdx", version = "0.0.0" }
syntax = { path = "./crates/syntax", version = "0.0.0" }
syntax-bridge = { path = "./crates/syntax-bridge", version = "0.0.0" }
-text-edit = { path = "./crates/text-edit", version = "0.0.0" }
toolchain = { path = "./crates/toolchain", version = "0.0.0" }
tt = { path = "./crates/tt", version = "0.0.0" }
vfs-notify = { path = "./crates/vfs-notify", version = "0.0.0" }
diff --git a/crates/ide-assists/Cargo.toml b/crates/ide-assists/Cargo.toml
index 2a14fbe1e0..ba21586871 100644
--- a/crates/ide-assists/Cargo.toml
+++ b/crates/ide-assists/Cargo.toml
@@ -23,7 +23,6 @@ tracing.workspace = true
# local deps
stdx.workspace = true
syntax.workspace = true
-text-edit.workspace = true
ide-db.workspace = true
hir.workspace = true
diff --git a/crates/ide-assists/src/handlers/bool_to_enum.rs b/crates/ide-assists/src/handlers/bool_to_enum.rs
index c035c59ffc..605fd14052 100644
--- a/crates/ide-assists/src/handlers/bool_to_enum.rs
+++ b/crates/ide-assists/src/handlers/bool_to_enum.rs
@@ -1,5 +1,6 @@
use either::Either;
use hir::ModuleDef;
+use ide_db::text_edit::TextRange;
use ide_db::{
assists::{AssistId, AssistKind},
defs::Definition,
@@ -19,7 +20,6 @@ use syntax::{
},
AstNode, NodeOrToken, SyntaxKind, SyntaxNode, T,
};
-use text_edit::TextRange;
use crate::{
assist_context::{AssistContext, Assists},
diff --git a/crates/ide-assists/src/handlers/destructure_struct_binding.rs b/crates/ide-assists/src/handlers/destructure_struct_binding.rs
index b229b750e8..22a1efdbea 100644
--- a/crates/ide-assists/src/handlers/destructure_struct_binding.rs
+++ b/crates/ide-assists/src/handlers/destructure_struct_binding.rs
@@ -1,4 +1,5 @@
use hir::{sym, HasVisibility};
+use ide_db::text_edit::TextRange;
use ide_db::{
assists::{AssistId, AssistKind},
defs::Definition,
@@ -8,7 +9,6 @@ use ide_db::{
};
use itertools::Itertools;
use syntax::{ast, ted, AstNode, Edition, SmolStr, SyntaxNode, ToSmolStr};
-use text_edit::TextRange;
use crate::{
assist_context::{AssistContext, Assists, SourceChangeBuilder},
diff --git a/crates/ide-assists/src/handlers/destructure_tuple_binding.rs b/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
index 9ecfb83ed5..3f0d5cf152 100644
--- a/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
+++ b/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
@@ -1,3 +1,4 @@
+use ide_db::text_edit::TextRange;
use ide_db::{
assists::{AssistId, AssistKind},
defs::Definition,
@@ -8,7 +9,6 @@ use syntax::{
ast::{self, make, AstNode, FieldExpr, HasName, IdentPat},
ted,
};
-use text_edit::TextRange;
use crate::{
assist_context::{AssistContext, Assists, SourceChangeBuilder},
diff --git a/crates/ide-assists/src/handlers/remove_unused_imports.rs b/crates/ide-assists/src/handlers/remove_unused_imports.rs
index c6f99d6874..0570b44778 100644
--- a/crates/ide-assists/src/handlers/remove_unused_imports.rs
+++ b/crates/ide-assists/src/handlers/remove_unused_imports.rs
@@ -1,6 +1,7 @@
use std::collections::hash_map::Entry;
use hir::{FileRange, HirFileIdExt, InFile, InRealFile, Module, ModuleSource};
+use ide_db::text_edit::TextRange;
use ide_db::{
defs::Definition,
search::{FileReference, ReferenceCategory, SearchScope},
@@ -10,7 +11,6 @@ use syntax::{
ast::{self, Rename},
AstNode,
};
-use text_edit::TextRange;
use crate::{AssistContext, AssistId, AssistKind, Assists};
diff --git a/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs b/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs
index 8a6c2937d9..26fd887cc9 100644
--- a/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs
+++ b/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs
@@ -1,4 +1,5 @@
use hir::{FileRange, Semantics};
+use ide_db::text_edit::TextRange;
use ide_db::{
defs::Definition,
search::{SearchScope, UsageSearchResult},
@@ -11,7 +12,6 @@ use syntax::{
},
match_ast, ted, AstNode,
};
-use text_edit::TextRange;
use crate::{AssistContext, AssistId, AssistKind, Assists};
diff --git a/crates/ide-completion/Cargo.toml b/crates/ide-completion/Cargo.toml
index 614465b4d0..1bef82af5a 100644
--- a/crates/ide-completion/Cargo.toml
+++ b/crates/ide-completion/Cargo.toml
@@ -25,7 +25,6 @@ base-db.workspace = true
ide-db.workspace = true
stdx.workspace = true
syntax.workspace = true
-text-edit.workspace = true
# completions crate should depend only on the top-level `hir` package. if you need
# something from some `hir-xxx` subpackage, reexport the API via `hir`.
hir.workspace = true
diff --git a/crates/ide-completion/src/completions/item_list/trait_impl.rs b/crates/ide-completion/src/completions/item_list/trait_impl.rs
index 672e1796d1..c38a8ef29b 100644
--- a/crates/ide-completion/src/completions/item_list/trait_impl.rs
+++ b/crates/ide-completion/src/completions/item_list/trait_impl.rs
@@ -32,6 +32,7 @@
//! ```
use hir::{db::ExpandDatabase, HasAttrs, MacroFileId, Name};
+use ide_db::text_edit::TextEdit;
use ide_db::{
documentation::HasDocs, path_transform::PathTransform,
syntax_helpers::prettify_macro_expansion, traits::get_missing_assoc_items, SymbolKind,
@@ -40,7 +41,6 @@ use syntax::{
ast::{self, edit_in_place::AttrsOwnerEdit, make, HasGenericArgs, HasTypeBounds},
format_smolstr, ted, AstNode, SmolStr, SyntaxElement, SyntaxKind, TextRange, ToSmolStr, T,
};
-use text_edit::TextEdit;
use crate::{
context::PathCompletionCtx, CompletionContext, CompletionItem, CompletionItemKind,
diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs
index d3579fd8cc..495f82da86 100644
--- a/crates/ide-completion/src/completions/postfix.rs
+++ b/crates/ide-completion/src/completions/postfix.rs
@@ -3,6 +3,7 @@
mod format_like;
use hir::ItemInNs;
+use ide_db::text_edit::TextEdit;
use ide_db::{
documentation::{Documentation, HasDocs},
imports::insert_use::ImportScope,
@@ -15,7 +16,6 @@ use syntax::{
SyntaxKind::{BLOCK_EXPR, EXPR_STMT, FOR_EXPR, IF_EXPR, LOOP_EXPR, STMT_LIST, WHILE_EXPR},
TextRange, TextSize,
};
-use text_edit::TextEdit;
use crate::{
completions::postfix::format_like::add_format_like_completions,
diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs
index 0e1302ff2e..efbee39a2d 100644
--- a/crates/ide-completion/src/context.rs
+++ b/crates/ide-completion/src/context.rs
@@ -20,7 +20,6 @@ use syntax::{
SyntaxKind::{self, *},
SyntaxToken, TextRange, TextSize, T,
};
-use text_edit::Indel;
use crate::{
context::analysis::{expand_and_analyze, AnalysisResult},
@@ -684,8 +683,7 @@ impl<'a> CompletionContext<'a> {
// actual completion.
let file_with_fake_ident = {
let parse = db.parse(file_id);
- let edit = Indel::insert(offset, COMPLETION_MARKER.to_owned());
- parse.reparse(&edit, file_id.edition()).tree()
+ parse.reparse(TextRange::empty(offset), COMPLETION_MARKER, file_id.edition()).tree()
};
// always pick the token to the immediate left of the cursor, as that is what we are actually
diff --git a/crates/ide-completion/src/item.rs b/crates/ide-completion/src/item.rs
index 8c97ebd550..52f6bedaaa 100644
--- a/crates/ide-completion/src/item.rs
+++ b/crates/ide-completion/src/item.rs
@@ -3,6 +3,7 @@
use std::{fmt, mem};
use hir::Mutability;
+use ide_db::text_edit::TextEdit;
use ide_db::{
documentation::Documentation, imports::import_assets::LocatedImport, RootDatabase, SnippetCap,
SymbolKind,
@@ -11,7 +12,6 @@ use itertools::Itertools;
use smallvec::SmallVec;
use stdx::{impl_from, never};
use syntax::{format_smolstr, Edition, SmolStr, TextRange, TextSize};
-use text_edit::TextEdit;
use crate::{
context::{CompletionContext, PathCompletionCtx},
@@ -426,7 +426,7 @@ impl CompletionItem {
self.lookup.as_str()
}
- pub fn ref_match(&self) -> Option<(String, text_edit::Indel, CompletionRelevance)> {
+ pub fn ref_match(&self) -> Option<(String, ide_db::text_edit::Indel, CompletionRelevance)> {
// Relevance of the ref match should be the same as the original
// match, but with exact type match set because self.ref_match
// is only set if there is an exact type match.
@@ -436,7 +436,10 @@ impl CompletionItem {
self.ref_match.map(|(mutability, offset)| {
(
format!("&{}{}", mutability.as_keyword_for_ref(), self.label),
- text_edit::Indel::insert(offset, format!("&{}", mutability.as_keyword_for_ref())),
+ ide_db::text_edit::Indel::insert(
+ offset,
+ format!("&{}", mutability.as_keyword_for_ref()),
+ ),
relevance,
)
})
diff --git a/crates/ide-completion/src/lib.rs b/crates/ide-completion/src/lib.rs
index a78976d3fd..dfee01b187 100644
--- a/crates/ide-completion/src/lib.rs
+++ b/crates/ide-completion/src/lib.rs
@@ -10,16 +10,17 @@ mod snippet;
#[cfg(test)]
mod tests;
+use ide_db::text_edit::TextEdit;
use ide_db::{
helpers::mod_path_to_ast,
imports::{
import_assets::NameToImport,
insert_use::{self, ImportScope},
},
- items_locator, FilePosition, RootDatabase,
+ items_locator,
+ syntax_helpers::tree_diff::diff,
+ FilePosition, RootDatabase,
};
-use syntax::algo;
-use text_edit::TextEdit;
use crate::{
completions::Completions,
@@ -297,6 +298,6 @@ pub fn resolve_completion_edits(
}
});
- algo::diff(scope.as_syntax_node(), new_ast.as_syntax_node()).into_text_edit(&mut import_insert);
+ diff(scope.as_syntax_node(), new_ast.as_syntax_node()).into_text_edit(&mut import_insert);
Some(vec![import_insert.finish()])
}
diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs
index 4dd171142f..ec3c2fe355 100644
--- a/crates/ide-completion/src/render.rs
+++ b/crates/ide-completion/src/render.rs
@@ -11,6 +11,7 @@ pub(crate) mod union_literal;
pub(crate) mod variant;
use hir::{sym, AsAssocItem, HasAttrs, HirDisplay, ModuleDef, ScopeDef, Type};
+use ide_db::text_edit::TextEdit;
use ide_db::{
documentation::{Documentation, HasDocs},
helpers::item_name,
@@ -18,7 +19,6 @@ use ide_db::{
RootDatabase, SnippetCap, SymbolKind,
};
use syntax::{ast, format_smolstr, AstNode, Edition, SmolStr, SyntaxKind, TextRange, ToSmolStr};
-use text_edit::TextEdit;
use crate::{
context::{DotAccess, DotAccessKind, PathCompletionCtx, PathKind, PatternContext},
diff --git a/crates/ide-db/Cargo.toml b/crates/ide-db/Cargo.toml
index c078188d6d..17f0e69bde 100644
--- a/crates/ide-db/Cargo.toml
+++ b/crates/ide-db/Cargo.toml
@@ -35,7 +35,6 @@ parser.workspace = true
profile.workspace = true
stdx.workspace = true
syntax.workspace = true
-text-edit.workspace = true
span.workspace = true
# ide should depend only on the top-level `hir` package. if you need
# something from some `hir-xxx` subpackage, reexport the API via `hir`.
diff --git a/crates/ide-db/src/documentation.rs b/crates/ide-db/src/documentation.rs
index 5e443badf9..b52a325790 100644
--- a/crates/ide-db/src/documentation.rs
+++ b/crates/ide-db/src/documentation.rs
@@ -5,11 +5,11 @@ use hir::{
resolve_doc_path_on, sym, AttrId, AttrSourceMap, AttrsWithOwner, HasAttrs, InFile,
};
use itertools::Itertools;
+use span::{TextRange, TextSize};
use syntax::{
ast::{self, IsString},
AstToken,
};
-use text_edit::{TextRange, TextSize};
/// Holds documentation
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
diff --git a/crates/ide-db/src/lib.rs b/crates/ide-db/src/lib.rs
index b764f852f0..81260c3e08 100644
--- a/crates/ide-db/src/lib.rs
+++ b/crates/ide-db/src/lib.rs
@@ -19,6 +19,7 @@ pub mod rust_doc;
pub mod search;
pub mod source_change;
pub mod symbol_index;
+pub mod text_edit;
pub mod traits;
pub mod ty_filter;
pub mod use_trivial_constructor;
@@ -36,6 +37,7 @@ pub mod generated {
pub mod syntax_helpers {
pub mod format_string;
pub mod format_string_exprs;
+ pub mod tree_diff;
pub use hir::prettify_macro_expansion;
pub mod node_ext;
pub mod suggest_name;
diff --git a/crates/ide-db/src/rename.rs b/crates/ide-db/src/rename.rs
index f1404ed9f2..1d1679c3ff 100644
--- a/crates/ide-db/src/rename.rs
+++ b/crates/ide-db/src/rename.rs
@@ -22,6 +22,7 @@
//! Our current behavior is ¯\_(ツ)_/¯.
use std::fmt;
+use crate::text_edit::{TextEdit, TextEditBuilder};
use base_db::AnchoredPathBuf;
use either::Either;
use hir::{FieldSource, FileRange, HirFileIdExt, InFile, ModuleSource, Semantics};
@@ -32,7 +33,6 @@ use syntax::{
utils::is_raw_identifier,
AstNode, SyntaxKind, TextRange, T,
};
-use text_edit::{TextEdit, TextEditBuilder};
use crate::{
defs::Definition,
diff --git a/crates/ide-db/src/source_change.rs b/crates/ide-db/src/source_change.rs
index 73073e92f7..27ff91dc19 100644
--- a/crates/ide-db/src/source_change.rs
+++ b/crates/ide-db/src/source_change.rs
@@ -5,7 +5,8 @@
use std::{collections::hash_map::Entry, iter, mem};
-use crate::{assists::Command, SnippetCap};
+use crate::text_edit::{TextEdit, TextEditBuilder};
+use crate::{assists::Command, syntax_helpers::tree_diff::diff, SnippetCap};
use base_db::AnchoredPathBuf;
use itertools::Itertools;
use nohash_hasher::IntMap;
@@ -13,11 +14,9 @@ use rustc_hash::FxHashMap;
use span::FileId;
use stdx::never;
use syntax::{
- algo,
syntax_editor::{SyntaxAnnotation, SyntaxEditor},
AstNode, SyntaxElement, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange, TextSize,
};
-use text_edit::{TextEdit, TextEditBuilder};
#[derive(Default, Debug, Clone)]
pub struct SourceChange {
@@ -315,7 +314,7 @@ impl SourceChangeBuilder {
}
let mut edit = TextEdit::builder();
- algo::diff(edit_result.old_root(), edit_result.new_root()).into_text_edit(&mut edit);
+ diff(edit_result.old_root(), edit_result.new_root()).into_text_edit(&mut edit);
let edit = edit.finish();
let snippet_edit =
@@ -334,7 +333,7 @@ impl SourceChangeBuilder {
});
if let Some(tm) = self.mutated_tree.take() {
- algo::diff(&tm.immutable, &tm.mutable_clone).into_text_edit(&mut self.edit);
+ diff(&tm.immutable, &tm.mutable_clone).into_text_edit(&mut self.edit);
}
let edit = mem::take(&mut self.edit).finish();
@@ -373,7 +372,7 @@ impl SourceChangeBuilder {
self.edit.replace(range, replace_with.into())
}
pub fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
- algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
+ diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
}
pub fn create_file(&mut self, dst: AnchoredPathBuf, content: impl Into<String>) {
let file_system_edit = FileSystemEdit::CreateFile { dst, initial_contents: content.into() };
diff --git a/crates/ide-db/src/syntax_helpers/tree_diff.rs b/crates/ide-db/src/syntax_helpers/tree_diff.rs
new file mode 100644
index 0000000000..02e24c4776
--- /dev/null
+++ b/crates/ide-db/src/syntax_helpers/tree_diff.rs
@@ -0,0 +1,559 @@
+//! Basic tree diffing functionality.
+use rustc_hash::FxHashMap;
+use syntax::{NodeOrToken, SyntaxElement, SyntaxNode};
+
+use crate::{text_edit::TextEditBuilder, FxIndexMap};
+
+#[derive(Debug, Hash, PartialEq, Eq)]
+enum TreeDiffInsertPos {
+ After(SyntaxElement),
+ AsFirstChild(SyntaxElement),
+}
+
+#[derive(Debug)]
+pub struct TreeDiff {
+ replacements: FxHashMap<SyntaxElement, SyntaxElement>,
+ deletions: Vec<SyntaxElement>,
+ // the vec as well as the indexmap are both here to preserve order
+ insertions: FxIndexMap<TreeDiffInsertPos, Vec<SyntaxElement>>,
+}
+
+impl TreeDiff {
+ pub fn into_text_edit(&self, builder: &mut TextEditBuilder) {
+ let _p = tracing::info_span!("into_text_edit").entered();
+
+ for (anchor, to) in &self.insertions {
+ let offset = match anchor {
+ TreeDiffInsertPos::After(it) => it.text_range().end(),
+ TreeDiffInsertPos::AsFirstChild(it) => it.text_range().start(),
+ };
+ to.iter().for_each(|to| builder.insert(offset, to.to_string()));
+ }
+ for (from, to) in &self.replacements {
+ builder.replace(from.text_range(), to.to_string());
+ }
+ for text_range in self.deletions.iter().map(SyntaxElement::text_range) {
+ builder.delete(text_range);
+ }
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.replacements.is_empty() && self.deletions.is_empty() && self.insertions.is_empty()
+ }
+}
+
+/// Finds a (potentially minimal) diff, which, applied to `from`, will result in `to`.
+///
+/// Specifically, returns a structure that consists of a replacements, insertions and deletions
+/// such that applying this map on `from` will result in `to`.
+///
+/// This function tries to find a fine-grained diff.
+pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff {
+ let _p = tracing::info_span!("diff").entered();
+
+ let mut diff = TreeDiff {
+ replacements: FxHashMap::default(),
+ insertions: FxIndexMap::default(),
+ deletions: Vec::new(),
+ };
+ let (from, to) = (from.clone().into(), to.clone().into());
+
+ if !syntax_element_eq(&from, &to) {
+ go(&mut diff, from, to);
+ }
+ return diff;
+
+ fn syntax_element_eq(lhs: &SyntaxElement, rhs: &SyntaxElement) -> bool {
+ lhs.kind() == rhs.kind()
+ && lhs.text_range().len() == rhs.text_range().len()
+ && match (&lhs, &rhs) {
+ (NodeOrToken::Node(lhs), NodeOrToken::Node(rhs)) => {
+ lhs == rhs || lhs.text() == rhs.text()
+ }
+ (NodeOrToken::Token(lhs), NodeOrToken::Token(rhs)) => lhs.text() == rhs.text(),
+ _ => false,
+ }
+ }
+
+ // FIXME: this is horribly inefficient. I bet there's a cool algorithm to diff trees properly.
+ fn go(diff: &mut TreeDiff, lhs: SyntaxElement, rhs: SyntaxElement) {
+ let (lhs, rhs) = match lhs.as_node().zip(rhs.as_node()) {
+ Some((lhs, rhs)) => (lhs, rhs),
+ _ => {
+ cov_mark::hit!(diff_node_token_replace);
+ diff.replacements.insert(lhs, rhs);
+ return;
+ }
+ };
+
+ let mut look_ahead_scratch = Vec::default();
+
+ let mut rhs_children = rhs.children_with_tokens();
+ let mut lhs_children = lhs.children_with_tokens();
+ let mut last_lhs = None;
+ loop {
+ let lhs_child = lhs_children.next();
+ match (lhs_child.clone(), rhs_children.next()) {
+ (None, None) => break,
+ (None, Some(element)) => {
+ let insert_pos = match last_lhs.clone() {
+ Some(prev) => {
+ cov_mark::hit!(diff_insert);
+ TreeDiffInsertPos::After(prev)
+ }
+ // first iteration, insert into out parent as the first child
+ None => {
+ cov_mark::hit!(diff_insert_as_first_child);
+ TreeDiffInsertPos::AsFirstChild(lhs.clone().into())
+ }
+ };
+ diff.insertions.entry(insert_pos).or_default().push(element);
+ }
+ (Some(element), None) => {
+ cov_mark::hit!(diff_delete);
+ diff.deletions.push(element);
+ }
+ (Some(ref lhs_ele), Some(ref rhs_ele)) if syntax_element_eq(lhs_ele, rhs_ele) => {}
+ (Some(lhs_ele), Some(rhs_ele)) => {
+ // nodes differ, look for lhs_ele in rhs, if its found we can mark everything up
+ // until that element as insertions. This is important to keep the diff minimal
+ // in regards to insertions that have been actually done, this is important for
+ // use insertions as we do not want to replace the entire module node.
+ look_ahead_scratch.push(rhs_ele.clone());
+ let mut rhs_children_clone = rhs_children.clone();
+ let mut insert = false;
+ for rhs_child in &mut rhs_children_clone {
+ if syntax_element_eq(&lhs_ele, &rhs_child) {
+ cov_mark::hit!(diff_insertions);
+ insert = true;
+ break;
+ }
+ look_ahead_scratch.push(rhs_child);
+ }
+ let drain = look_ahead_scratch.drain(..);
+ if insert {
+ let insert_pos = if let Some(prev) = last_lhs.clone().filter(|_| insert) {
+ TreeDiffInsertPos::After(prev)
+ } else {
+ cov_mark::hit!(insert_first_child);
+ TreeDiffInsertPos::AsFirstChild(lhs.clone().into())
+ };
+
+ diff.insertions.entry(insert_pos).or_default().extend(drain);
+ rhs_children = rhs_children_clone;
+ } else {
+ go(diff, lhs_ele, rhs_ele);
+ }
+ }
+ }
+ last_lhs = lhs_child.or(last_lhs);
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::{expect, Expect};
+ use itertools::Itertools;
+ use parser::{Edition, SyntaxKind};
+ use syntax::{AstNode, SourceFile, SyntaxElement};
+
+ use crate::text_edit::TextEdit;
+
+ #[test]
+ fn replace_node_token() {
+ cov_mark::check!(diff_node_token_replace);
+ check_diff(
+ r#"use node;"#,
+ r#"ident"#,
+ expect![[r#"
+ insertions:
+
+
+
+ replacements:
+
+ Line 0: Token([email protected] "use") -> ident
+
+ deletions:
+
+ Line 1: " "
+ Line 1: node
+ Line 1: ;
+ "#]],
+ );
+ }
+
+ #[test]
+ fn replace_parent() {
+ cov_mark::check!(diff_insert_as_first_child);
+ check_diff(
+ r#""#,
+ r#"use foo::bar;"#,
+ expect![[r#"
+ insertions:
+
+ Line 0: AsFirstChild(Node([email protected]))
+ -> use foo::bar;
+
+ replacements:
+
+
+
+ deletions:
+
+
+ "#]],
+ );
+ }
+
+ #[test]
+ fn insert_last() {
+ cov_mark::check!(diff_insert);
+ check_diff(
+ r#"
+use foo;
+use bar;"#,
+ r#"
+use foo;
+use bar;
+use baz;"#,
+ expect![[r#"
+ insertions:
+
+ Line 2: After(Node([email protected]))
+ -> "\n"
+ -> use baz;
+
+ replacements:
+
+
+
+ deletions:
+
+
+ "#]],
+ );
+ }
+
+ #[test]
+ fn insert_middle() {
+ check_diff(
+ r#"
+use foo;
+use baz;"#,
+ r#"
+use foo;
+use bar;
+use baz;"#,
+ expect![[r#"
+ insertions:
+
+ Line 2: After(Token([email protected] "\n"))
+ -> use bar;
+ -> "\n"
+
+ replacements:
+
+
+
+ deletions:
+
+
+ "#]],
+ )
+ }
+
+ #[test]
+ fn insert_first() {
+ check_diff(
+ r#"
+use bar;
+use baz;"#,
+ r#"
+use foo;
+use bar;
+use baz;"#,
+ expect![[r#"
+ insertions:
+
+ Line 0: After(Token([email protected] "\n"))
+ -> use foo;
+ -> "\n"
+
+ replacements:
+
+
+
+ deletions:
+
+
+ "#]],
+ )
+ }
+
+ #[test]
+ fn first_child_insertion() {
+ cov_mark::check!(insert_first_child);
+ check_diff(
+ r#"fn main() {
+ stdi
+ }"#,
+ r#"use foo::bar;
+
+ fn main() {
+ stdi
+ }"#,
+ expect![[r#"
+ insertions:
+
+ Line 0: AsFirstChild(Node([email protected]))
+ -> use foo::bar;
+ -> "\n\n "
+
+ replacements:
+
+
+
+ deletions:
+
+
+ "#]],
+ );
+ }
+
+ #[test]
+ fn delete_last() {
+ cov_mark::check!(diff_delete);
+ check_diff(
+ r#"use foo;
+ use bar;"#,
+ r#"use foo;"#,
+ expect![[r#"
+ insertions:
+
+
+
+ replacements:
+
+
+
+ deletions:
+
+ Line 1: "\n "
+ Line 2: use bar;
+ "#]],
+ );
+ }
+
+ #[test]
+ fn delete_middle() {
+ cov_mark::check!(diff_insertions);
+ check_diff(
+ r#"
+use expect_test::{expect, Expect};
+use text_edit::TextEdit;
+
+use crate::AstNode;
+"#,
+ r#"
+use expect_test::{expect, Expect};
+
+use crate::AstNode;
+"#,
+ expect![[r#"
+ insertions:
+
+ Line 1: After(Node([email protected]))
+ -> "\n\n"
+ -> use crate::AstNode;
+
+ replacements:
+
+
+
+ deletions:
+
+ Line 2: use text_edit::TextEdit;
+ Line 3: "\n\n"
+ Line 4: use crate::AstNode;
+ Line 5: "\n"
+ "#]],
+ )
+ }
+
+ #[test]
+ fn delete_first() {
+ check_diff(
+ r#"
+use text_edit::TextEdit;
+
+use crate::AstNode;
+"#,
+ r#"
+use crate::AstNode;
+"#,
+ expect![[r#"
+ insertions:
+
+
+
+ replacements:
+
+ Line 2: Token([email protected] "text_edit") -> crate
+ Line 2: Token([email protected] "TextEdit") -> AstNode
+ Line 2: Token([email protected] "\n\n") -> "\n"
+
+ deletions:
+
+ Line 3: use crate::AstNode;
+ Line 4: "\n"
+ "#]],
+ )
+ }
+
+ #[test]
+ fn merge_use() {
+ check_diff(
+ r#"
+use std::{
+ fmt,
+ hash::BuildHasherDefault,
+ ops::{self, RangeInclusive},
+};
+"#,
+ r#"
+use std::fmt;
+use std::hash::BuildHasherDefault;
+use std::ops::{self, RangeInclusive};
+"#,
+ expect![[r#"
+ insertions:
+
+ Line 2: After(Node([email protected]))
+ -> ::
+ -> fmt
+ Line 6: After(Token([email protected] "\n"))
+ -> use std::hash::BuildHasherDefault;
+ -> "\n"
+ -> use std::ops::{self, RangeInclusive};
+ -> "\n"
+
+ replacements:
+
+ Line 2: Token([email protected] "std") -> std
+
+ deletions:
+
+ Line 2: ::
+ Line 2: {
+ fmt,
+ hash::BuildHasherDefault,
+ ops::{self, RangeInclusive},
+ }
+ "#]],
+ )
+ }
+
+ #[test]
+ fn early_return_assist() {
+ check_diff(
+ r#"
+fn main() {
+ if let Ok(x) = Err(92) {
+ foo(x);
+ }
+}
+ "#,
+ r#"
+fn main() {
+ let x = match Err(92) {
+ Ok(it) => it,
+ _ => return,
+ };
+ foo(x);
+}
+ "#,
+ expect![[r#"
+ insertions:
+
+ Line 3: After(Node([email protected]))
+ -> " "
+ -> match Err(92) {
+ Ok(it) => it,
+ _ => return,
+ }
+ -> ;
+ Line 3: After(Node([email protected]))
+ -> "\n "
+ -> foo(x);
+
+ replacements:
+
+ Line 3: Token([email protected] "if") -> let
+ Line 3: Token([email protected] "let") -> x
+ Line 3: Node([email protected]) -> =
+
+ deletions:
+
+ Line 3: " "
+ Line 3: Ok(x)
+ Line 3: " "
+ Line 3: =
+ Line 3: " "
+ Line 3: Err(92)
+ "#]],
+ )
+ }
+
+ fn check_diff(from: &str, to: &str, expected_diff: Expect) {
+ let from_node = SourceFile::parse(from, Edition::CURRENT).tree().syntax().clone();
+ let to_node = SourceFile::parse(to, Edition::CURRENT).tree().syntax().clone();
+ let diff = super::diff(&from_node, &to_node);
+
+ let line_number =
+ |syn: &SyntaxElement| from[..syn.text_range().start().into()].lines().count();
+
+ let fmt_syntax = |syn: &SyntaxElement| match syn.kind() {
+ SyntaxKind::WHITESPACE => format!("{:?}", syn.to_string()),
+ _ => format!("{syn}"),
+ };
+
+ let insertions =
+ diff.insertions.iter().format_with("\n", |(k, v), f| -> Result<(), std::fmt::Error> {
+ f(&format!(
+ "Line {}: {:?}\n-> {}",
+ line_number(match k {
+ super::TreeDiffInsertPos::After(syn) => syn,
+ super::TreeDiffInsertPos::AsFirstChild(syn) => syn,
+ }),
+ k,
+ v.iter().format_with("\n-> ", |v, f| f(&fmt_syntax(v)))
+ ))
+ });
+
+ let replacements = diff
+ .replacements
+ .iter()
+ .sorted_by_key(|(syntax, _)| syntax.text_range().start())
+ .format_with("\n", |(k, v), f| {
+ f(&format!("Line {}: {k:?} -> {}", line_number(k), fmt_syntax(v)))
+ });
+
+ let deletions = diff
+ .deletions
+ .iter()
+ .format_with("\n", |v, f| f(&format!("Line {}: {}", line_number(v), fmt_syntax(v))));
+
+ let actual = format!(
+ "insertions:\n\n{insertions}\n\nreplacements:\n\n{replacements}\n\ndeletions:\n\n{deletions}\n"
+ );
+ expected_diff.assert_eq(&actual);
+
+ let mut from = from.to_owned();
+ let mut text_edit = TextEdit::builder();
+ diff.into_text_edit(&mut text_edit);
+ text_edit.finish().apply(&mut from);
+ assert_eq!(&*from, to, "diff did not turn `from` to `to`");
+ }
+}
diff --git a/crates/text-edit/src/lib.rs b/crates/ide-db/src/text_edit.rs
index 3efe0850d8..0c675f0619 100644
--- a/crates/text-edit/src/lib.rs
+++ b/crates/ide-db/src/text_edit.rs
@@ -5,8 +5,8 @@
//! rust-analyzer.
use itertools::Itertools;
+pub use span::{TextRange, TextSize};
use std::cmp::max;
-pub use text_size::{TextRange, TextSize};
/// `InsertDelete` -- a single "atomic" change to text
///
diff --git a/crates/ide-diagnostics/Cargo.toml b/crates/ide-diagnostics/Cargo.toml
index bf54f4ab32..281a08e542 100644
--- a/crates/ide-diagnostics/Cargo.toml
+++ b/crates/ide-diagnostics/Cargo.toml
@@ -22,7 +22,6 @@ tracing.workspace = true
# local deps
stdx.workspace = true
syntax.workspace = true
-text-edit.workspace = true
cfg.workspace = true
hir.workspace = true
ide-db.workspace = true
diff --git a/crates/ide-diagnostics/src/handlers/field_shorthand.rs b/crates/ide-diagnostics/src/handlers/field_shorthand.rs
index c7071d1ce4..876c2ccd49 100644
--- a/crates/ide-diagnostics/src/handlers/field_shorthand.rs
+++ b/crates/ide-diagnostics/src/handlers/field_shorthand.rs
@@ -1,9 +1,9 @@
//! Suggests shortening `Foo { field: field }` to `Foo { field }` in both
//! expressions and patterns.
+use ide_db::text_edit::TextEdit;
use ide_db::{source_change::SourceChange, EditionedFileId, FileRange};
use syntax::{ast, match_ast, AstNode, SyntaxNode};
-use text_edit::TextEdit;
use crate::{fix, Diagnostic, DiagnosticCode};
diff --git a/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs b/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs
index ccb33fed10..dca889d1a8 100644
--- a/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs
+++ b/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs
@@ -2,6 +2,7 @@
//! example.
use hir::{ImportPathConfig, PathResolution, Semantics};
+use ide_db::text_edit::TextEdit;
use ide_db::{
helpers::mod_path_to_ast,
imports::insert_use::{insert_use, ImportScope},
@@ -14,7 +15,6 @@ use syntax::{
ast::{self, make},
Edition, SyntaxKind, SyntaxNode,
};
-use text_edit::TextEdit;
use crate::{fix, Diagnostic, DiagnosticCode, DiagnosticsConfig, Severity};
diff --git a/crates/ide-diagnostics/src/handlers/missing_fields.rs b/crates/ide-diagnostics/src/handlers/missing_fields.rs
index 3a622c6968..fd1044e51b 100644
--- a/crates/ide-diagnostics/src/handlers/missing_fields.rs
+++ b/crates/ide-diagnostics/src/handlers/missing_fields.rs
@@ -5,15 +5,14 @@ use hir::{
};
use ide_db::{
assists::Assist, famous_defs::FamousDefs, imports::import_assets::item_for_path_search,
- source_change::SourceChange, use_trivial_constructor::use_trivial_constructor, FxHashMap,
+ source_change::SourceChange, syntax_helpers::tree_diff::diff, text_edit::TextEdit,
+ use_trivial_constructor::use_trivial_constructor, FxHashMap,
};
use stdx::format_to;
use syntax::{
- algo,
ast::{self, make},
AstNode, Edition, SyntaxNode, SyntaxNodePtr, ToSmolStr,
};
-use text_edit::TextEdit;
use crate::{fix, Diagnostic, DiagnosticCode, DiagnosticsContext};
@@ -77,7 +76,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
// FIXME: this also currently discards a lot of whitespace in the input... we really need a formatter here
builder.replace(old_range.range, new_syntax.to_string());
} else {
- algo::diff(old_syntax, new_syntax).into_text_edit(&mut builder);
+ diff(old_syntax, new_syntax).into_text_edit(&mut builder);
}
builder.finish()
};
diff --git a/crates/ide-diagnostics/src/handlers/missing_unsafe.rs b/crates/ide-diagnostics/src/handlers/missing_unsafe.rs
index b18219c30f..a630d3c7c3 100644
--- a/crates/ide-diagnostics/src/handlers/missing_unsafe.rs
+++ b/crates/ide-diagnostics/src/handlers/missing_unsafe.rs
@@ -1,9 +1,9 @@
use hir::db::ExpandDatabase;
use hir::HirFileIdExt;
+use ide_db::text_edit::TextEdit;
use ide_db::{assists::Assist, source_change::SourceChange};
use syntax::{ast, SyntaxNode};
use syntax::{match_ast, AstNode};
-use text_edit::TextEdit;
use crate::{fix, Diagnostic, DiagnosticCode, DiagnosticsContext};
diff --git a/crates/ide-diagnostics/src/handlers/mutability_errors.rs b/crates/ide-diagnostics/src/handlers/mutability_errors.rs
index acf1cf1d38..1397979144 100644
--- a/crates/ide-diagnostics/src/handlers/mutability_errors.rs
+++ b/crates/ide-diagnostics/src/handlers/mutability_errors.rs
@@ -1,7 +1,7 @@
use hir::db::ExpandDatabase;
use ide_db::source_change::SourceChange;
+use ide_db::text_edit::TextEdit;
use syntax::{ast, AstNode, SyntaxKind, SyntaxNode, SyntaxNodePtr, SyntaxToken, T};
-use text_edit::TextEdit;
use crate::{fix, Diagnostic, DiagnosticCode, DiagnosticsContext};
diff --git a/crates/ide-diagnostics/src/handlers/no_such_field.rs b/crates/ide-diagnostics/src/handlers/no_such_field.rs
index dfadef11fd..e5d871975b 100644
--- a/crates/ide-diagnostics/src/handlers/no_such_field.rs
+++ b/crates/ide-diagnostics/src/handlers/no_such_field.rs
@@ -1,11 +1,11 @@
use either::Either;
use hir::{db::ExpandDatabase, HasSource, HirDisplay, HirFileIdExt, Semantics, VariantId};
+use ide_db::text_edit::TextEdit;
use ide_db::{source_change::SourceChange, EditionedFileId, RootDatabase};
use syntax::{
ast::{self, edit::IndentLevel, make},
AstNode,
};
-use text_edit::TextEdit;
use crate::{fix, Assist, Diagnostic, DiagnosticCode, DiagnosticsContext};
diff --git a/crates/ide-diagnostics/src/handlers/remove_trailing_return.rs b/crates/ide-diagnostics/src/handlers/remove_trailing_return.rs
index 62bc1f3d06..c8e3cff364 100644
--- a/crates/ide-diagnostics/src/handlers/remove_trailing_return.rs
+++ b/crates/ide-diagnostics/src/handlers/remove_trailing_return.rs
@@ -1,7 +1,7 @@
use hir::{db::ExpandDatabase, diagnostics::RemoveTrailingReturn, FileRange};
+use ide_db::text_edit::TextEdit;
use ide_db::{assists::Assist, source_change::SourceChange};
use syntax::{ast, AstNode};
-use text_edit::TextEdit;
use crate::{adjusted_display_range, fix, Diagnostic, DiagnosticCode, DiagnosticsContext};
diff --git a/crates/ide-diagnostics/src/handlers/remove_unnecessary_else.rs b/crates/ide-diagnostics/src/handlers/remove_unnecessary_else.rs
index 448df1ca16..a46c48608f 100644
--- a/crates/ide-diagnostics/src/handlers/remove_unnecessary_else.rs
+++ b/crates/ide-diagnostics/src/handlers/remove_unnecessary_else.rs
@@ -1,4 +1,5 @@
use hir::{db::ExpandDatabase, diagnostics::RemoveUnnecessaryElse, HirFileIdExt};
+use ide_db::text_edit::TextEdit;
use ide_db::{assists::Assist, source_change::SourceChange};
use itertools::Itertools;
use syntax::{
@@ -8,7 +9,6 @@ use syntax::{
},
AstNode, SyntaxToken, TextRange,
};
-use text_edit::TextEdit;
use crate::{
adjusted_display_range, fix, Diagnostic, DiagnosticCode, DiagnosticsContext, Severity,
diff --git a/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs b/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs
index 1864720623..f481365f2a 100644
--- a/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs
+++ b/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs
@@ -1,10 +1,10 @@
use hir::{db::ExpandDatabase, HirFileIdExt, InFile};
use ide_db::source_change::SourceChange;
+use ide_db::text_edit::TextEdit;
use syntax::{
ast::{self, HasArgList},
AstNode, TextRange,
};
-use text_edit::TextEdit;
use crate::{fix, Assist, Diagnostic, DiagnosticCode, DiagnosticsContext};
diff --git a/crates/ide-diagnostics/src/handlers/trait_impl_redundant_assoc_item.rs b/crates/ide-diagnostics/src/handlers/trait_impl_redundant_assoc_item.rs
index 3de51ca4a3..1363a8ff0d 100644
--- a/crates/ide-diagnostics/src/handlers/trait_impl_redundant_assoc_item.rs
+++ b/crates/ide-diagnostics/src/handlers/trait_impl_redundant_assoc_item.rs
@@ -1,11 +1,11 @@
use hir::{db::ExpandDatabase, HasSource, HirDisplay};
+use ide_db::text_edit::TextRange;
use ide_db::{
assists::{Assist, AssistId, AssistKind},
label::Label,
source_change::SourceChangeBuilder,
};
use syntax::ToSmolStr;
-use text_edit::TextRange;
use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
diff --git a/crates/ide-diagnostics/src/handlers/type_mismatch.rs b/crates/ide-diagnostics/src/handlers/type_mismatch.rs
index 90f88d6705..93fe9374a3 100644
--- a/crates/ide-diagnostics/src/handlers/type_mismatch.rs
+++ b/crates/ide-diagnostics/src/handlers/type_mismatch.rs
@@ -1,5 +1,6 @@
use either::Either;
use hir::{db::ExpandDatabase, ClosureStyle, HirDisplay, HirFileIdExt, InFile, Type};
+use ide_db::text_edit::TextEdit;
use ide_db::{famous_defs::FamousDefs, source_change::SourceChange};
use syntax::{
ast::{
@@ -9,7 +10,6 @@ use syntax::{
},
AstNode, AstPtr, TextSize,
};
-use text_edit::TextEdit;
use crate::{adjusted_display_range, fix, Assist, Diagnostic, DiagnosticCode, DiagnosticsContext};
diff --git a/crates/ide-diagnostics/src/handlers/typed_hole.rs b/crates/ide-diagnostics/src/handlers/typed_hole.rs
index 6994a7ed14..3ad84f7bda 100644
--- a/crates/ide-diagnostics/src/handlers/typed_hole.rs
+++ b/crates/ide-diagnostics/src/handlers/typed_hole.rs
@@ -3,13 +3,13 @@ use hir::{
term_search::{term_search, TermSearchConfig, TermSearchCtx},
ClosureStyle, HirDisplay, ImportPathConfig,
};
+use ide_db::text_edit::TextEdit;
use ide_db::{
assists::{Assist, AssistId, AssistKind, GroupLabel},
label::Label,
source_change::SourceChange,
};
use itertools::Itertools;
-use text_edit::TextEdit;
use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
diff --git a/crates/ide-diagnostics/src/handlers/unlinked_file.rs b/crates/ide-diagnostics/src/handlers/unlinked_file.rs
index e0822fc5b3..13591dfb2e 100644
--- a/crates/ide-diagnostics/src/handlers/unlinked_file.rs
+++ b/crates/ide-diagnostics/src/handlers/unlinked_file.rs
@@ -3,6 +3,7 @@
use std::iter;
use hir::{db::DefDatabase, DefMap, InFile, ModuleSource};
+use ide_db::text_edit::TextEdit;
use ide_db::{
base_db::{FileLoader, SourceDatabase, SourceRootDatabase},
source_change::SourceChange,
@@ -13,7 +14,6 @@ use syntax::{
ast::{self, edit::IndentLevel, HasModuleItem, HasName},
AstNode, TextRange,
};
-use text_edit::TextEdit;
use crate::{fix, Assist, Diagnostic, DiagnosticCode, DiagnosticsContext, Severity};
diff --git a/crates/ide-diagnostics/src/handlers/unresolved_field.rs b/crates/ide-diagnostics/src/handlers/unresolved_field.rs
index 76d624c47a..656bedff1a 100644
--- a/crates/ide-diagnostics/src/handlers/unresolved_field.rs
+++ b/crates/ide-diagnostics/src/handlers/unresolved_field.rs
@@ -1,6 +1,7 @@
use std::iter;
use hir::{db::ExpandDatabase, Adt, FileRange, HasSource, HirDisplay, InFile, Struct, Union};
+use ide_db::text_edit::TextEdit;
use ide_db::{
assists::{Assist, AssistId, AssistKind},
helpers::is_editable_crate,
@@ -16,7 +17,6 @@ use syntax::{
ast::{edit::AstNodeEdit, Type},
SyntaxNode,
};
-use text_edit::TextEdit;
use crate::{adjusted_display_range, Diagnostic, DiagnosticCode, DiagnosticsContext};
diff --git a/crates/ide-diagnostics/src/handlers/unresolved_method.rs b/crates/ide-diagnostics/src/handlers/unresolved_method.rs
index c0d038a238..81cb452121 100644
--- a/crates/ide-diagnostics/src/handlers/unresolved_method.rs
+++ b/crates/ide-diagnostics/src/handlers/unresolved_method.rs
@@ -1,4 +1,5 @@
use hir::{db::ExpandDatabase, AssocItem, FileRange, HirDisplay, InFile};
+use ide_db::text_edit::TextEdit;
use ide_db::{
assists::{Assist, AssistId, AssistKind},
label::Label,
@@ -8,7 +9,6 @@ use syntax::{
ast::{self, make, HasArgList},
format_smolstr, AstNode, SmolStr, TextRange, ToSmolStr,
};
-use text_edit::TextEdit;
use crate::{adjusted_display_range, Diagnostic, DiagnosticCode, DiagnosticsContext};
diff --git a/crates/ide-diagnostics/src/handlers/unused_variables.rs b/crates/ide-diagnostics/src/handlers/unused_variables.rs
index 84007b16aa..67ece56694 100644
--- a/crates/ide-diagnostics/src/handlers/unused_variables.rs
+++ b/crates/ide-diagnostics/src/handlers/unused_variables.rs
@@ -1,4 +1,5 @@
use hir::Name;
+use ide_db::text_edit::TextEdit;
use ide_db::{
assists::{Assist, AssistId, AssistKind},
label::Label,
@@ -6,7 +7,6 @@ use ide_db::{
FileRange, RootDatabase,
};
use syntax::{Edition, TextRange};
-use text_edit::TextEdit;
use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
diff --git a/crates/ide-diagnostics/src/handlers/useless_braces.rs b/crates/ide-diagnostics/src/handlers/useless_braces.rs
index 2d380ae045..e5c2eca171 100644
--- a/crates/ide-diagnostics/src/handlers/useless_braces.rs
+++ b/crates/ide-diagnostics/src/handlers/useless_braces.rs
@@ -1,8 +1,8 @@
use hir::InFile;
+use ide_db::text_edit::TextEdit;
use ide_db::{source_change::SourceChange, EditionedFileId, FileRange};
use itertools::Itertools;
use syntax::{ast, AstNode, SyntaxNode, SyntaxNodePtr};
-use text_edit::TextEdit;
use crate::{fix, Diagnostic, DiagnosticCode};
diff --git a/crates/ide-ssr/Cargo.toml b/crates/ide-ssr/Cargo.toml
index fad62fa3b9..2561467628 100644
--- a/crates/ide-ssr/Cargo.toml
+++ b/crates/ide-ssr/Cargo.toml
@@ -24,7 +24,6 @@ ide-db.workspace = true
parser.workspace = true
stdx.workspace = true
syntax.workspace = true
-text-edit.workspace = true
[dev-dependencies]
expect-test = "1.4.0"
@@ -34,4 +33,4 @@ test-utils.workspace = true
test-fixture.workspace = true
[lints]
-workspace = true \ No newline at end of file
+workspace = true
diff --git a/crates/ide-ssr/src/lib.rs b/crates/ide-ssr/src/lib.rs
index 54236ea8bc..eaca95d98c 100644
--- a/crates/ide-ssr/src/lib.rs
+++ b/crates/ide-ssr/src/lib.rs
@@ -84,10 +84,10 @@ pub use crate::{errors::SsrError, from_comment::ssr_from_comment, matching::Matc
use crate::{errors::bail, matching::MatchFailureReason};
use hir::{FileRange, Semantics};
+use ide_db::text_edit::TextEdit;
use ide_db::{base_db::SourceDatabase, EditionedFileId, FileId, FxHashMap, RootDatabase};
use resolving::ResolvedRule;
use syntax::{ast, AstNode, SyntaxNode, TextRange};
-use text_edit::TextEdit;
// A structured search replace rule. Create by calling `parse` on a str.
#[derive(Debug)]
diff --git a/crates/ide-ssr/src/replacing.rs b/crates/ide-ssr/src/replacing.rs
index 65756601f6..11c1615a56 100644
--- a/crates/ide-ssr/src/replacing.rs
+++ b/crates/ide-ssr/src/replacing.rs
@@ -1,5 +1,6 @@
//! Code for applying replacement templates for matches that have previously been found.
+use ide_db::text_edit::TextEdit;
use ide_db::{FxHashMap, FxHashSet};
use itertools::Itertools;
use parser::Edition;
@@ -7,7 +8,6 @@ use syntax::{
ast::{self, AstNode, AstToken},
SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize,
};
-use text_edit::TextEdit;
use crate::{fragments, resolving::ResolvedRule, Match, SsrMatches};
diff --git a/crates/ide/Cargo.toml b/crates/ide/Cargo.toml
index d976d604f1..7c66b36dc8 100644
--- a/crates/ide/Cargo.toml
+++ b/crates/ide/Cargo.toml
@@ -39,7 +39,6 @@ profile.workspace = true
stdx.workspace = true
syntax.workspace = true
span.workspace = true
-text-edit.workspace = true
# ide should depend only on the top-level `hir` package. if you need
# something from some `hir-xxx` subpackage, reexport the API via `hir`.
hir.workspace = true
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index b6e46c3202..c58ca0f01c 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -8,6 +8,7 @@ use hir::{
sym, ClosureStyle, HasVisibility, HirDisplay, HirDisplayError, HirWrite, ModuleDef,
ModuleDefId, Semantics,
};
+use ide_db::text_edit::TextEdit;
use ide_db::{famous_defs::FamousDefs, FileRange, RootDatabase};
use itertools::Itertools;
use smallvec::{smallvec, SmallVec};
@@ -17,7 +18,6 @@ use syntax::{
ast::{self, AstNode, HasGenericParams},
format_smolstr, match_ast, SmolStr, SyntaxNode, TextRange, TextSize, WalkEvent,
};
-use text_edit::TextEdit;
use crate::{navigation_target::TryToNav, FileId};
diff --git a/crates/ide/src/inlay_hints/adjustment.rs b/crates/ide/src/inlay_hints/adjustment.rs
index 0e0d50b1f3..4d7d6e270e 100644
--- a/crates/ide/src/inlay_hints/adjustment.rs
+++ b/crates/ide/src/inlay_hints/adjustment.rs
@@ -11,13 +11,13 @@ use hir::{
};
use ide_db::famous_defs::FamousDefs;
+use ide_db::text_edit::TextEditBuilder;
use span::EditionedFileId;
use stdx::never;
use syntax::{
ast::{self, make, AstNode},
ted,
};
-use text_edit::TextEditBuilder;
use crate::{
AdjustmentHints, AdjustmentHintsMode, InlayHint, InlayHintLabel, InlayHintLabelPart,
diff --git a/crates/ide/src/inlay_hints/binding_mode.rs b/crates/ide/src/inlay_hints/binding_mode.rs
index e38450b73f..cfe8657fd0 100644
--- a/crates/ide/src/inlay_hints/binding_mode.rs
+++ b/crates/ide/src/inlay_hints/binding_mode.rs
@@ -7,9 +7,9 @@ use std::mem;
use hir::Mutability;
use ide_db::famous_defs::FamousDefs;
+use ide_db::text_edit::TextEditBuilder;
use span::EditionedFileId;
use syntax::ast::{self, AstNode};
-use text_edit::TextEditBuilder;
use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, InlayKind};
diff --git a/crates/ide/src/inlay_hints/chaining.rs b/crates/ide/src/inlay_hints/chaining.rs
index 58d8f97a8c..028ed1650f 100644
--- a/crates/ide/src/inlay_hints/chaining.rs
+++ b/crates/ide/src/inlay_hints/chaining.rs
@@ -77,7 +77,7 @@ pub(super) fn hints(
#[cfg(test)]
mod tests {
use expect_test::{expect, Expect};
- use text_edit::{TextRange, TextSize};
+ use ide_db::text_edit::{TextRange, TextSize};
use crate::{
fixture,
diff --git a/crates/ide/src/inlay_hints/closure_captures.rs b/crates/ide/src/inlay_hints/closure_captures.rs
index 628ddc6154..906f2acf0c 100644
--- a/crates/ide/src/inlay_hints/closure_captures.rs
+++ b/crates/ide/src/inlay_hints/closure_captures.rs
@@ -2,10 +2,10 @@
//!
//! Tests live in [`bind_pat`][super::bind_pat] module.
use ide_db::famous_defs::FamousDefs;
+use ide_db::text_edit::{TextRange, TextSize};
use span::EditionedFileId;
use stdx::{never, TupleExt};
use syntax::ast::{self, AstNode};
-use text_edit::{TextRange, TextSize};
use crate::{
InlayHint, InlayHintLabel, InlayHintLabelPart, InlayHintPosition, InlayHintsConfig, InlayKind,
diff --git a/crates/ide/src/inlay_hints/discriminant.rs b/crates/ide/src/inlay_hints/discriminant.rs
index 40d7367dba..cd77c3ec3e 100644
--- a/crates/ide/src/inlay_hints/discriminant.rs
+++ b/crates/ide/src/inlay_hints/discriminant.rs
@@ -5,10 +5,10 @@
//! }
//! ```
use hir::Semantics;
+use ide_db::text_edit::TextEdit;
use ide_db::{famous_defs::FamousDefs, RootDatabase};
use span::EditionedFileId;
use syntax::ast::{self, AstNode, HasName};
-use text_edit::TextEdit;
use crate::{
DiscriminantHints, InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, InlayKind,
diff --git a/crates/ide/src/inlay_hints/implicit_static.rs b/crates/ide/src/inlay_hints/implicit_static.rs
index f15d19047c..1560df37d0 100644
--- a/crates/ide/src/inlay_hints/implicit_static.rs
+++ b/crates/ide/src/inlay_hints/implicit_static.rs
@@ -4,12 +4,12 @@
//! ```
use either::Either;
use ide_db::famous_defs::FamousDefs;
+use ide_db::text_edit::TextEdit;
use span::EditionedFileId;
use syntax::{
ast::{self, AstNode},
SyntaxKind,
};
-use text_edit::TextEdit;
use crate::{InlayHint, InlayHintPosition, InlayHintsConfig, InlayKind, LifetimeElisionHints};
diff --git a/crates/ide/src/join_lines.rs b/crates/ide/src/join_lines.rs
index 9d8ba90b2f..5192f91a4a 100644
--- a/crates/ide/src/join_lines.rs
+++ b/crates/ide/src/join_lines.rs
@@ -8,7 +8,7 @@ use syntax::{
SyntaxToken, TextRange, TextSize, T,
};
-use text_edit::{TextEdit, TextEditBuilder};
+use ide_db::text_edit::{TextEdit, TextEditBuilder};
pub struct JoinLinesConfig {
pub join_else_if: bool,
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index d7163d57d2..d053c4b3c9 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -122,6 +122,7 @@ pub use ide_completion::{
CallableSnippets, CompletionConfig, CompletionFieldsToResolve, CompletionItem,
CompletionItemKind, CompletionRelevance, Snippet, SnippetScope,
};
+pub use ide_db::text_edit::{Indel, TextEdit};
pub use ide_db::{
base_db::{Cancelled, CrateGraph, CrateId, FileChange, SourceRoot, SourceRootId},
documentation::Documentation,
@@ -139,7 +140,6 @@ pub use ide_diagnostics::{
pub use ide_ssr::SsrError;
pub use span::Edition;
pub use syntax::{TextRange, TextSize};
-pub use text_edit::{Indel, TextEdit};
pub type Cancellable<T> = Result<T, Cancelled>;
diff --git a/crates/ide/src/move_item.rs b/crates/ide/src/move_item.rs
index ea6cc9d6de..a232df2b82 100644
--- a/crates/ide/src/move_item.rs
+++ b/crates/ide/src/move_item.rs
@@ -1,10 +1,11 @@
use std::{iter::once, mem};
use hir::Semantics;
+use ide_db::syntax_helpers::tree_diff::diff;
+use ide_db::text_edit::{TextEdit, TextEditBuilder};
use ide_db::{helpers::pick_best_token, FileRange, RootDatabase};
use itertools::Itertools;
-use syntax::{algo, ast, match_ast, AstNode, SyntaxElement, SyntaxKind, SyntaxNode, TextRange};
-use text_edit::{TextEdit, TextEditBuilder};
+use syntax::{ast, match_ast, AstNode, SyntaxElement, SyntaxKind, SyntaxNode, TextRange};
#[derive(Copy, Clone, Debug)]
pub enum Direction {
@@ -166,7 +167,7 @@ fn replace_nodes<'a>(
let mut edit = TextEditBuilder::default();
- algo::diff(first, second).into_text_edit(&mut edit);
+ diff(first, second).into_text_edit(&mut edit);
edit.replace(second.text_range(), first_with_cursor);
edit.finish()
diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs
index f17c1fa5c6..665fc954d2 100644
--- a/crates/ide/src/rename.rs
+++ b/crates/ide/src/rename.rs
@@ -15,7 +15,7 @@ use itertools::Itertools;
use stdx::{always, never};
use syntax::{ast, AstNode, SyntaxKind, SyntaxNode, TextRange, TextSize};
-use text_edit::TextEdit;
+use ide_db::text_edit::TextEdit;
use crate::{FilePosition, RangeInfo, SourceChange};
@@ -449,9 +449,9 @@ fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Opt
mod tests {
use expect_test::{expect, Expect};
use ide_db::source_change::SourceChange;
+ use ide_db::text_edit::TextEdit;
use stdx::trim_indent;
use test_utils::assert_eq_text;
- use text_edit::TextEdit;
use crate::fixture;
diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs
index a09e1e85ae..9bb5de9f2e 100644
--- a/crates/ide/src/typing.rs
+++ b/crates/ide/src/typing.rs
@@ -23,7 +23,7 @@ use syntax::{
AstNode, Parse, SourceFile, SyntaxKind, TextRange, TextSize, T,
};
-use text_edit::{Indel, TextEdit};
+use ide_db::text_edit::TextEdit;
use crate::SourceChange;
@@ -126,7 +126,7 @@ fn on_opening_bracket_typed(
return None;
}
// FIXME: Edition
- let file = file.reparse(&Indel::delete(range), span::Edition::CURRENT_FIXME);
+ let file = file.reparse(range, "", span::Edition::CURRENT_FIXME);
if let Some(edit) = bracket_expr(&file.tree(), offset, opening_bracket, closing_bracket) {
return Some(edit);
diff --git a/crates/ide/src/typing/on_enter.rs b/crates/ide/src/typing/on_enter.rs
index 6e56bd6185..773e352220 100644
--- a/crates/ide/src/typing/on_enter.rs
+++ b/crates/ide/src/typing/on_enter.rs
@@ -12,7 +12,7 @@ use syntax::{
SyntaxNode, SyntaxToken, TextRange, TextSize, TokenAtOffset,
};
-use text_edit::TextEdit;
+use ide_db::text_edit::TextEdit;
// Feature: On Enter
//
diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml
index fcb9b0ea35..51eaea5434 100644
--- a/crates/syntax/Cargo.toml
+++ b/crates/syntax/Cargo.toml
@@ -27,7 +27,6 @@ ra-ap-rustc_lexer.workspace = true
parser.workspace = true
stdx.workspace = true
-text-edit.workspace = true
[dev-dependencies]
rayon.workspace = true
diff --git a/crates/syntax/src/algo.rs b/crates/syntax/src/algo.rs
index 8dc6d36a7e..2acb215831 100644
--- a/crates/syntax/src/algo.rs
+++ b/crates/syntax/src/algo.rs
@@ -1,11 +1,6 @@
//! Collection of assorted algorithms for syntax trees.
-use std::hash::BuildHasherDefault;
-
-use indexmap::IndexMap;
use itertools::Itertools;
-use rustc_hash::FxHashMap;
-use text_edit::TextEditBuilder;
use crate::{
AstNode, Direction, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange,
@@ -101,559 +96,3 @@ pub fn neighbor<T: AstNode>(me: &T, direction: Direction) -> Option<T> {
pub fn has_errors(node: &SyntaxNode) -> bool {
node.children().any(|it| it.kind() == SyntaxKind::ERROR)
}
-
-type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<rustc_hash::FxHasher>>;
-
-#[derive(Debug, Hash, PartialEq, Eq)]
-enum TreeDiffInsertPos {
- After(SyntaxElement),
- AsFirstChild(SyntaxElement),
-}
-
-#[derive(Debug)]
-pub struct TreeDiff {
- replacements: FxHashMap<SyntaxElement, SyntaxElement>,
- deletions: Vec<SyntaxElement>,
- // the vec as well as the indexmap are both here to preserve order
- insertions: FxIndexMap<TreeDiffInsertPos, Vec<SyntaxElement>>,
-}
-
-impl TreeDiff {
- pub fn into_text_edit(&self, builder: &mut TextEditBuilder) {
- let _p = tracing::info_span!("into_text_edit").entered();
-
- for (anchor, to) in &self.insertions {
- let offset = match anchor {
- TreeDiffInsertPos::After(it) => it.text_range().end(),
- TreeDiffInsertPos::AsFirstChild(it) => it.text_range().start(),
- };
- to.iter().for_each(|to| builder.insert(offset, to.to_string()));
- }
- for (from, to) in &self.replacements {
- builder.replace(from.text_range(), to.to_string());
- }
- for text_range in self.deletions.iter().map(SyntaxElement::text_range) {
- builder.delete(text_range);
- }
- }
-
- pub fn is_empty(&self) -> bool {
- self.replacements.is_empty() && self.deletions.is_empty() && self.insertions.is_empty()
- }
-}
-
-/// Finds a (potentially minimal) diff, which, applied to `from`, will result in `to`.
-///
-/// Specifically, returns a structure that consists of a replacements, insertions and deletions
-/// such that applying this map on `from` will result in `to`.
-///
-/// This function tries to find a fine-grained diff.
-pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff {
- let _p = tracing::info_span!("diff").entered();
-
- let mut diff = TreeDiff {
- replacements: FxHashMap::default(),
- insertions: FxIndexMap::default(),
- deletions: Vec::new(),
- };
- let (from, to) = (from.clone().into(), to.clone().into());
-
- if !syntax_element_eq(&from, &to) {
- go(&mut diff, from, to);
- }
- return diff;
-
- fn syntax_element_eq(lhs: &SyntaxElement, rhs: &SyntaxElement) -> bool {
- lhs.kind() == rhs.kind()
- && lhs.text_range().len() == rhs.text_range().len()
- && match (&lhs, &rhs) {
- (NodeOrToken::Node(lhs), NodeOrToken::Node(rhs)) => {
- lhs == rhs || lhs.text() == rhs.text()
- }
- (NodeOrToken::Token(lhs), NodeOrToken::Token(rhs)) => lhs.text() == rhs.text(),
- _ => false,
- }
- }
-
- // FIXME: this is horribly inefficient. I bet there's a cool algorithm to diff trees properly.
- fn go(diff: &mut TreeDiff, lhs: SyntaxElement, rhs: SyntaxElement) {
- let (lhs, rhs) = match lhs.as_node().zip(rhs.as_node()) {
- Some((lhs, rhs)) => (lhs, rhs),
- _ => {
- cov_mark::hit!(diff_node_token_replace);
- diff.replacements.insert(lhs, rhs);
- return;
- }
- };
-
- let mut look_ahead_scratch = Vec::default();
-
- let mut rhs_children = rhs.children_with_tokens();
- let mut lhs_children = lhs.children_with_tokens();
- let mut last_lhs = None;
- loop {
- let lhs_child = lhs_children.next();
- match (lhs_child.clone(), rhs_children.next()) {
- (None, None) => break,
- (None, Some(element)) => {
- let insert_pos = match last_lhs.clone() {
- Some(prev) => {
- cov_mark::hit!(diff_insert);
- TreeDiffInsertPos::After(prev)
- }
- // first iteration, insert into out parent as the first child
- None => {
- cov_mark::hit!(diff_insert_as_first_child);
- TreeDiffInsertPos::AsFirstChild(lhs.clone().into())
- }
- };
- diff.insertions.entry(insert_pos).or_default().push(element);
- }
- (Some(element), None) => {
- cov_mark::hit!(diff_delete);
- diff.deletions.push(element);
- }
- (Some(ref lhs_ele), Some(ref rhs_ele)) if syntax_element_eq(lhs_ele, rhs_ele) => {}
- (Some(lhs_ele), Some(rhs_ele)) => {
- // nodes differ, look for lhs_ele in rhs, if its found we can mark everything up
- // until that element as insertions. This is important to keep the diff minimal
- // in regards to insertions that have been actually done, this is important for
- // use insertions as we do not want to replace the entire module node.
- look_ahead_scratch.push(rhs_ele.clone());
- let mut rhs_children_clone = rhs_children.clone();
- let mut insert = false;
- for rhs_child in &mut rhs_children_clone {
- if syntax_element_eq(&lhs_ele, &rhs_child) {
- cov_mark::hit!(diff_insertions);
- insert = true;
- break;
- }
- look_ahead_scratch.push(rhs_child);
- }
- let drain = look_ahead_scratch.drain(..);
- if insert {
- let insert_pos = if let Some(prev) = last_lhs.clone().filter(|_| insert) {
- TreeDiffInsertPos::After(prev)
- } else {
- cov_mark::hit!(insert_first_child);
- TreeDiffInsertPos::AsFirstChild(lhs.clone().into())
- };
-
- diff.insertions.entry(insert_pos).or_default().extend(drain);
- rhs_children = rhs_children_clone;
- } else {
- go(diff, lhs_ele, rhs_ele);
- }
- }
- }
- last_lhs = lhs_child.or(last_lhs);
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use expect_test::{expect, Expect};
- use itertools::Itertools;
- use parser::{Edition, SyntaxKind};
- use text_edit::TextEdit;
-
- use crate::{AstNode, SyntaxElement};
-
- #[test]
- fn replace_node_token() {
- cov_mark::check!(diff_node_token_replace);
- check_diff(
- r#"use node;"#,
- r#"ident"#,
- expect![[r#"
- insertions:
-
-
-
- replacements:
-
- Line 0: Token([email protected] "use") -> ident
-
- deletions:
-
- Line 1: " "
- Line 1: node
- Line 1: ;
- "#]],
- );
- }
-
- #[test]
- fn replace_parent() {
- cov_mark::check!(diff_insert_as_first_child);
- check_diff(
- r#""#,
- r#"use foo::bar;"#,
- expect![[r#"
- insertions:
-
- Line 0: AsFirstChild(Node([email protected]))
- -> use foo::bar;
-
- replacements:
-
-
-
- deletions:
-
-
- "#]],
- );
- }
-
- #[test]
- fn insert_last() {
- cov_mark::check!(diff_insert);
- check_diff(
- r#"
-use foo;
-use bar;"#,
- r#"
-use foo;
-use bar;
-use baz;"#,
- expect![[r#"
- insertions:
-
- Line 2: After(Node([email protected]))
- -> "\n"
- -> use baz;
-
- replacements:
-
-
-
- deletions:
-
-
- "#]],
- );
- }
-
- #[test]
- fn insert_middle() {
- check_diff(
- r#"
-use foo;
-use baz;"#,
- r#"
-use foo;
-use bar;
-use baz;"#,
- expect![[r#"
- insertions:
-
- Line 2: After(Token([email protected] "\n"))
- -> use bar;
- -> "\n"
-
- replacements:
-
-
-
- deletions:
-
-
- "#]],
- )
- }
-
- #[test]
- fn insert_first() {
- check_diff(
- r#"
-use bar;
-use baz;"#,
- r#"
-use foo;
-use bar;
-use baz;"#,
- expect![[r#"
- insertions:
-
- Line 0: After(Token([email protected] "\n"))
- -> use foo;
- -> "\n"
-
- replacements:
-
-
-
- deletions:
-
-
- "#]],
- )
- }
-
- #[test]
- fn first_child_insertion() {
- cov_mark::check!(insert_first_child);
- check_diff(
- r#"fn main() {
- stdi
- }"#,
- r#"use foo::bar;
-
- fn main() {
- stdi
- }"#,
- expect![[r#"
- insertions:
-
- Line 0: AsFirstChild(Node([email protected]))
- -> use foo::bar;
- -> "\n\n "
-
- replacements:
-
-
-
- deletions:
-
-
- "#]],
- );
- }
-
- #[test]
- fn delete_last() {
- cov_mark::check!(diff_delete);
- check_diff(
- r#"use foo;
- use bar;"#,
- r#"use foo;"#,
- expect![[r#"
- insertions:
-
-
-
- replacements:
-
-
-
- deletions:
-
- Line 1: "\n "
- Line 2: use bar;
- "#]],
- );
- }
-
- #[test]
- fn delete_middle() {
- cov_mark::check!(diff_insertions);
- check_diff(
- r#"
-use expect_test::{expect, Expect};
-use text_edit::TextEdit;
-
-use crate::AstNode;
-"#,
- r#"
-use expect_test::{expect, Expect};
-
-use crate::AstNode;
-"#,
- expect![[r#"
- insertions:
-
- Line 1: After(Node([email protected]))
- -> "\n\n"
- -> use crate::AstNode;
-
- replacements:
-
-
-
- deletions:
-
- Line 2: use text_edit::TextEdit;
- Line 3: "\n\n"
- Line 4: use crate::AstNode;
- Line 5: "\n"
- "#]],
- )
- }
-
- #[test]
- fn delete_first() {
- check_diff(
- r#"
-use text_edit::TextEdit;
-
-use crate::AstNode;
-"#,
- r#"
-use crate::AstNode;
-"#,
- expect![[r#"
- insertions:
-
-
-
- replacements:
-
- Line 2: Token([email protected] "text_edit") -> crate
- Line 2: Token([email protected] "TextEdit") -> AstNode
- Line 2: Token([email protected] "\n\n") -> "\n"
-
- deletions:
-
- Line 3: use crate::AstNode;
- Line 4: "\n"
- "#]],
- )
- }
-
- #[test]
- fn merge_use() {
- check_diff(
- r#"
-use std::{
- fmt,
- hash::BuildHasherDefault,
- ops::{self, RangeInclusive},
-};
-"#,
- r#"
-use std::fmt;
-use std::hash::BuildHasherDefault;
-use std::ops::{self, RangeInclusive};
-"#,
- expect![[r#"
- insertions:
-
- Line 2: After(Node([email protected]))
- -> ::
- -> fmt
- Line 6: After(Token([email protected] "\n"))
- -> use std::hash::BuildHasherDefault;
- -> "\n"
- -> use std::ops::{self, RangeInclusive};
- -> "\n"
-
- replacements:
-
- Line 2: Token([email protected] "std") -> std
-
- deletions:
-
- Line 2: ::
- Line 2: {
- fmt,
- hash::BuildHasherDefault,
- ops::{self, RangeInclusive},
- }
- "#]],
- )
- }
-
- #[test]
- fn early_return_assist() {
- check_diff(
- r#"
-fn main() {
- if let Ok(x) = Err(92) {
- foo(x);
- }
-}
- "#,
- r#"
-fn main() {
- let x = match Err(92) {
- Ok(it) => it,
- _ => return,
- };
- foo(x);
-}
- "#,
- expect![[r#"
- insertions:
-
- Line 3: After(Node([email protected]))
- -> " "
- -> match Err(92) {
- Ok(it) => it,
- _ => return,
- }
- -> ;
- Line 3: After(Node([email protected]))
- -> "\n "
- -> foo(x);
-
- replacements:
-
- Line 3: Token([email protected] "if") -> let
- Line 3: Token([email protected] "let") -> x
- Line 3: Node([email protected]) -> =
-
- deletions:
-
- Line 3: " "
- Line 3: Ok(x)
- Line 3: " "
- Line 3: =
- Line 3: " "
- Line 3: Err(92)
- "#]],
- )
- }
-
- fn check_diff(from: &str, to: &str, expected_diff: Expect) {
- let from_node = crate::SourceFile::parse(from, Edition::CURRENT).tree().syntax().clone();
- let to_node = crate::SourceFile::parse(to, Edition::CURRENT).tree().syntax().clone();
- let diff = super::diff(&from_node, &to_node);
-
- let line_number =
- |syn: &SyntaxElement| from[..syn.text_range().start().into()].lines().count();
-
- let fmt_syntax = |syn: &SyntaxElement| match syn.kind() {
- SyntaxKind::WHITESPACE => format!("{:?}", syn.to_string()),
- _ => format!("{syn}"),
- };
-
- let insertions =
- diff.insertions.iter().format_with("\n", |(k, v), f| -> Result<(), std::fmt::Error> {
- f(&format!(
- "Line {}: {:?}\n-> {}",
- line_number(match k {
- super::TreeDiffInsertPos::After(syn) => syn,
- super::TreeDiffInsertPos::AsFirstChild(syn) => syn,
- }),
- k,
- v.iter().format_with("\n-> ", |v, f| f(&fmt_syntax(v)))
- ))
- });
-
- let replacements = diff
- .replacements
- .iter()
- .sorted_by_key(|(syntax, _)| syntax.text_range().start())
- .format_with("\n", |(k, v), f| {
- f(&format!("Line {}: {k:?} -> {}", line_number(k), fmt_syntax(v)))
- });
-
- let deletions = diff
- .deletions
- .iter()
- .format_with("\n", |v, f| f(&format!("Line {}: {}", line_number(v), fmt_syntax(v))));
-
- let actual = format!(
- "insertions:\n\n{insertions}\n\nreplacements:\n\n{replacements}\n\ndeletions:\n\n{deletions}\n"
- );
- expected_diff.assert_eq(&actual);
-
- let mut from = from.to_owned();
- let mut text_edit = TextEdit::builder();
- diff.into_text_edit(&mut text_edit);
- text_edit.finish().apply(&mut from);
- assert_eq!(&*from, to, "diff did not turn `from` to `to`");
- }
-}
diff --git a/crates/syntax/src/fuzz.rs b/crates/syntax/src/fuzz.rs
index 682dcd7cc4..fd20e603ed 100644
--- a/crates/syntax/src/fuzz.rs
+++ b/crates/syntax/src/fuzz.rs
@@ -5,7 +5,6 @@
use std::str::{self, FromStr};
use parser::Edition;
-use text_edit::Indel;
use crate::{validation, AstNode, SourceFile, TextRange};
@@ -22,7 +21,8 @@ pub fn check_parser(text: &str) {
#[derive(Debug, Clone)]
pub struct CheckReparse {
text: String,
- edit: Indel,
+ delete: TextRange,
+ insert: String,
edited_text: String,
}
@@ -43,14 +43,13 @@ impl CheckReparse {
TextRange::at(delete_start.try_into().unwrap(), delete_len.try_into().unwrap());
let edited_text =
format!("{}{}{}", &text[..delete_start], &insert, &text[delete_start + delete_len..]);
- let edit = Indel { insert, delete };
- Some(CheckReparse { text, edit, edited_text })
+ Some(CheckReparse { text, insert, delete, edited_text })
}
#[allow(clippy::print_stderr)]
pub fn run(&self) {
let parse = SourceFile::parse(&self.text, Edition::CURRENT);
- let new_parse = parse.reparse(&self.edit, Edition::CURRENT);
+ let new_parse = parse.reparse(self.delete, &self.insert, Edition::CURRENT);
check_file_invariants(&new_parse.tree());
assert_eq!(&new_parse.tree().syntax().text().to_string(), &self.edited_text);
let full_reparse = SourceFile::parse(&self.edited_text, Edition::CURRENT);
diff --git a/crates/syntax/src/lib.rs b/crates/syntax/src/lib.rs
index c1554c4b29..c9e9f468dc 100644
--- a/crates/syntax/src/lib.rs
+++ b/crates/syntax/src/lib.rs
@@ -44,10 +44,9 @@ pub mod syntax_editor;
pub mod ted;
pub mod utils;
-use std::marker::PhantomData;
+use std::{marker::PhantomData, ops::Range};
use stdx::format_to;
-use text_edit::Indel;
use triomphe::Arc;
pub use crate::{
@@ -150,16 +149,22 @@ impl Parse<SourceFile> {
buf
}
- pub fn reparse(&self, indel: &Indel, edition: Edition) -> Parse<SourceFile> {
- self.incremental_reparse(indel, edition)
- .unwrap_or_else(|| self.full_reparse(indel, edition))
+ pub fn reparse(&self, delete: TextRange, insert: &str, edition: Edition) -> Parse<SourceFile> {
+ self.incremental_reparse(delete, insert, edition)
+ .unwrap_or_else(|| self.full_reparse(delete, insert, edition))
}
- fn incremental_reparse(&self, indel: &Indel, edition: Edition) -> Option<Parse<SourceFile>> {
+ fn incremental_reparse(
+ &self,
+ delete: TextRange,
+ insert: &str,
+ edition: Edition,
+ ) -> Option<Parse<SourceFile>> {
// FIXME: validation errors are not handled here
parsing::incremental_reparse(
self.tree().syntax(),
- indel,
+ delete,
+ insert,
self.errors.as_deref().unwrap_or_default().iter().cloned(),
edition,
)
@@ -170,9 +175,9 @@ impl Parse<SourceFile> {
})
}
- fn full_reparse(&self, indel: &Indel, edition: Edition) -> Parse<SourceFile> {
+ fn full_reparse(&self, delete: TextRange, insert: &str, edition: Edition) -> Parse<SourceFile> {
let mut text = self.tree().syntax().text().to_string();
- indel.apply(&mut text);
+ text.replace_range(Range::<usize>::from(delete), insert);
SourceFile::parse(&text, edition)
}
}
diff --git a/crates/syntax/src/parsing/reparsing.rs b/crates/syntax/src/parsing/reparsing.rs
index a5cc4e90df..f2eab18c27 100644
--- a/crates/syntax/src/parsing/reparsing.rs
+++ b/crates/syntax/src/parsing/reparsing.rs
@@ -6,8 +6,9 @@
//! - otherwise, we search for the nearest `{}` block which contains the edit
//! and try to parse only this block.
+use std::ops::Range;
+
use parser::{Edition, Reparser};
-use text_edit::Indel;
use crate::{
parsing::build_tree,
@@ -19,38 +20,48 @@ use crate::{
pub(crate) fn incremental_reparse(
node: &SyntaxNode,
- edit: &Indel,
+ delete: TextRange,
+ insert: &str,
errors: impl IntoIterator<Item = SyntaxError>,
edition: Edition,
) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> {
- if let Some((green, new_errors, old_range)) = reparse_token(node, edit, edition) {
- return Some((green, merge_errors(errors, new_errors, old_range, edit), old_range));
+ if let Some((green, new_errors, old_range)) = reparse_token(node, delete, insert, edition) {
+ return Some((
+ green,
+ merge_errors(errors, new_errors, old_range, delete, insert),
+ old_range,
+ ));
}
- if let Some((green, new_errors, old_range)) = reparse_block(node, edit, edition) {
- return Some((green, merge_errors(errors, new_errors, old_range, edit), old_range));
+ if let Some((green, new_errors, old_range)) = reparse_block(node, delete, insert, edition) {
+ return Some((
+ green,
+ merge_errors(errors, new_errors, old_range, delete, insert),
+ old_range,
+ ));
}
None
}
fn reparse_token(
root: &SyntaxNode,
- edit: &Indel,
+ delete: TextRange,
+ insert: &str,
edition: Edition,
) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> {
- let prev_token = root.covering_element(edit.delete).as_token()?.clone();
+ let prev_token = root.covering_element(delete).as_token()?.clone();
let prev_token_kind = prev_token.kind();
match prev_token_kind {
WHITESPACE | COMMENT | IDENT | STRING | BYTE_STRING | C_STRING => {
if prev_token_kind == WHITESPACE || prev_token_kind == COMMENT {
// removing a new line may extends previous token
- let deleted_range = edit.delete - prev_token.text_range().start();
+ let deleted_range = delete - prev_token.text_range().start();
if prev_token.text()[deleted_range].contains('\n') {
return None;
}
}
- let mut new_text = get_text_after_edit(prev_token.clone().into(), edit);
+ let mut new_text = get_text_after_edit(prev_token.clone().into(), delete, insert);
let (new_token_kind, new_err) = parser::LexedStr::single_token(edition, &new_text)?;
if new_token_kind != prev_token_kind
@@ -85,11 +96,12 @@ fn reparse_token(
fn reparse_block(
root: &SyntaxNode,
- edit: &Indel,
+ delete: TextRange,
+ insert: &str,
edition: parser::Edition,
) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> {
- let (node, reparser) = find_reparsable_node(root, edit.delete)?;
- let text = get_text_after_edit(node.clone().into(), edit);
+ let (node, reparser) = find_reparsable_node(root, delete)?;
+ let text = get_text_after_edit(node.clone().into(), delete, insert);
let lexed = parser::LexedStr::new(edition, text.as_str());
let parser_input = lexed.to_input(edition);
@@ -104,14 +116,14 @@ fn reparse_block(
Some((node.replace_with(green), new_parser_errors, node.text_range()))
}
-fn get_text_after_edit(element: SyntaxElement, edit: &Indel) -> String {
- let edit = Indel::replace(edit.delete - element.text_range().start(), edit.insert.clone());
+fn get_text_after_edit(element: SyntaxElement, mut delete: TextRange, insert: &str) -> String {
+ delete -= element.text_range().start();
let mut text = match element {
NodeOrToken::Token(token) => token.text().to_owned(),
NodeOrToken::Node(node) => node.text().to_string(),
};
- edit.apply(&mut text);
+ text.replace_range(Range::<usize>::from(delete), insert);
text
}
@@ -153,7 +165,8 @@ fn merge_errors(
old_errors: impl IntoIterator<Item = SyntaxError>,
new_errors: Vec<SyntaxError>,
range_before_reparse: TextRange,
- edit: &Indel,
+ delete: TextRange,
+ insert: &str,
) -> Vec<SyntaxError> {
let mut res = Vec::new();
@@ -162,8 +175,8 @@ fn merge_errors(
if old_err_range.end() <= range_before_reparse.start() {
res.push(old_err);
} else if old_err_range.start() >= range_before_reparse.end() {
- let inserted_len = TextSize::of(&edit.insert);
- res.push(old_err.with_range((old_err_range + inserted_len) - edit.delete.len()));
+ let inserted_len = TextSize::of(insert);
+ res.push(old_err.with_range((old_err_range + inserted_len) - delete.len()));
// Note: extra parens are intentional to prevent uint underflow, HWAB (here was a bug)
}
}
@@ -177,6 +190,8 @@ fn merge_errors(
#[cfg(test)]
mod tests {
+ use std::ops::Range;
+
use parser::Edition;
use test_utils::{assert_eq_text, extract_range};
@@ -185,10 +200,9 @@ mod tests {
fn do_check(before: &str, replace_with: &str, reparsed_len: u32) {
let (range, before) = extract_range(before);
- let edit = Indel::replace(range, replace_with.to_owned());
let after = {
let mut after = before.clone();
- edit.apply(&mut after);
+ after.replace_range(Range::<usize>::from(range), replace_with);
after
};
@@ -197,7 +211,8 @@ mod tests {
let before = SourceFile::parse(&before, Edition::CURRENT);
let (green, new_errors, range) = incremental_reparse(
before.tree().syntax(),
- &edit,
+ range,
+ replace_with,
before.errors.as_deref().unwrap_or_default().iter().cloned(),
Edition::CURRENT,
)
diff --git a/crates/text-edit/Cargo.toml b/crates/text-edit/Cargo.toml
deleted file mode 100644
index dc6b3d31a0..0000000000
--- a/crates/text-edit/Cargo.toml
+++ /dev/null
@@ -1,20 +0,0 @@
-[package]
-name = "text-edit"
-version = "0.0.0"
-repository.workspace = true
-description = "Representation of a `TextEdit` for rust-analyzer."
-
-authors.workspace = true
-edition.workspace = true
-license.workspace = true
-rust-version.workspace = true
-
-[lib]
-doctest = false
-
-[dependencies]
-itertools.workspace = true
-text-size.workspace = true
-
-[lints]
-workspace = true \ No newline at end of file