Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/syntax/src/syntax_editor.rs')
-rw-r--r--crates/syntax/src/syntax_editor.rs113
1 files changed, 112 insertions, 1 deletions
diff --git a/crates/syntax/src/syntax_editor.rs b/crates/syntax/src/syntax_editor.rs
index dbb9f15e17..8e4dc75d22 100644
--- a/crates/syntax/src/syntax_editor.rs
+++ b/crates/syntax/src/syntax_editor.rs
@@ -14,7 +14,10 @@ use std::{
use rowan::TextRange;
use rustc_hash::FxHashMap;
-use crate::{AstNode, SyntaxElement, SyntaxNode, SyntaxToken};
+use crate::{
+ AstNode, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, T,
+ ast::{self, edit::IndentLevel, syntax_factory::SyntaxFactory},
+};
mod edit_algo;
mod edits;
@@ -101,6 +104,34 @@ impl SyntaxEditor {
self.changes.push(Change::InsertAll(position, elements))
}
+ pub fn insert_with_whitespace(
+ &mut self,
+ position: Position,
+ element: impl Element,
+ factory: &SyntaxFactory,
+ ) {
+ self.insert_all_with_whitespace(position, vec![element.syntax_element()], factory)
+ }
+
+ pub fn insert_all_with_whitespace(
+ &mut self,
+ position: Position,
+ mut elements: Vec<SyntaxElement>,
+ factory: &SyntaxFactory,
+ ) {
+ if let Some(first) = elements.first()
+ && let Some(ws) = ws_before(&position, first, factory)
+ {
+ elements.insert(0, ws.into());
+ }
+ if let Some(last) = elements.last()
+ && let Some(ws) = ws_after(&position, last, factory)
+ {
+ elements.push(ws.into());
+ }
+ self.insert_all(position, elements)
+ }
+
pub fn delete(&mut self, element: impl Element) {
let element = element.syntax_element();
debug_assert!(is_ancestor_or_self_of_element(&element, &self.root));
@@ -412,6 +443,86 @@ impl Element for SyntaxToken {
}
}
+fn ws_before(
+ position: &Position,
+ new: &SyntaxElement,
+ factory: &SyntaxFactory,
+) -> Option<SyntaxToken> {
+ let prev = match &position.repr {
+ PositionRepr::FirstChild(_) => return None,
+ PositionRepr::After(it) => it,
+ };
+
+ if prev.kind() == T!['{']
+ && new.kind() == SyntaxKind::USE
+ && let Some(item_list) = prev.parent().and_then(ast::ItemList::cast)
+ {
+ let mut indent = IndentLevel::from_element(&item_list.syntax().clone().into());
+ indent.0 += 1;
+ return Some(factory.whitespace(&format!("\n{indent}")));
+ }
+
+ if prev.kind() == T!['{']
+ && ast::Stmt::can_cast(new.kind())
+ && let Some(stmt_list) = prev.parent().and_then(ast::StmtList::cast)
+ {
+ let mut indent = IndentLevel::from_element(&stmt_list.syntax().clone().into());
+ indent.0 += 1;
+ return Some(factory.whitespace(&format!("\n{indent}")));
+ }
+
+ ws_between(prev, new, factory)
+}
+
+fn ws_after(
+ position: &Position,
+ new: &SyntaxElement,
+ factory: &SyntaxFactory,
+) -> Option<SyntaxToken> {
+ let next = match &position.repr {
+ PositionRepr::FirstChild(parent) => parent.first_child_or_token()?,
+ PositionRepr::After(sibling) => sibling.next_sibling_or_token()?,
+ };
+ ws_between(new, &next, factory)
+}
+
+fn ws_between(
+ left: &SyntaxElement,
+ right: &SyntaxElement,
+ factory: &SyntaxFactory,
+) -> Option<SyntaxToken> {
+ if left.kind() == SyntaxKind::WHITESPACE || right.kind() == SyntaxKind::WHITESPACE {
+ return None;
+ }
+ if right.kind() == T![;] || right.kind() == T![,] {
+ return None;
+ }
+ if left.kind() == T![<] || right.kind() == T![>] {
+ return None;
+ }
+ if left.kind() == T![&] && right.kind() == SyntaxKind::LIFETIME {
+ return None;
+ }
+ if right.kind() == SyntaxKind::GENERIC_ARG_LIST {
+ return None;
+ }
+ if right.kind() == SyntaxKind::USE {
+ let mut indent = IndentLevel::from_element(left);
+ if left.kind() == SyntaxKind::USE {
+ indent.0 = IndentLevel::from_element(right).0.max(indent.0);
+ }
+ return Some(factory.whitespace(&format!("\n{indent}")));
+ }
+ if left.kind() == SyntaxKind::ATTR {
+ let mut indent = IndentLevel::from_element(right);
+ if right.kind() == SyntaxKind::ATTR {
+ indent.0 = IndentLevel::from_element(left).0.max(indent.0);
+ }
+ return Some(factory.whitespace(&format!("\n{indent}")));
+ }
+ Some(factory.whitespace(" "))
+}
+
fn is_ancestor_or_self(node: &SyntaxNode, ancestor: &SyntaxNode) -> bool {
node == ancestor || node.ancestors().any(|it| &it == ancestor)
}