aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_syntax/src/syntax_node.rs
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2019-04-22 11:08:18 +0100
committerbors[bot] <bors[bot]@users.noreply.github.com>2019-04-22 11:08:18 +0100
commit38c0a1e3331f4b2b9efc7caa20b5927874024686 (patch)
tree321dd22a931c2ca11d2e207d734f283844af656d /crates/ra_syntax/src/syntax_node.rs
parent76e0129a21661029dc6cdbea2412ab53efe33aa1 (diff)
parentb73a978b95810b188090a37e002a22403a9067bd (diff)
Merge #1184
1184: Start structured editing API r=matklad a=matklad I think I finally understand how to provide nice, mutable structured editing API on top of red-green trees. The problem I am trying to solve is that any modification to a particular `SyntaxNode` returns an independent new file. So, if you are editing a struct literal, and add a field, you get back a SourceFile, and you have to find the struct literal inside it yourself! This happens because our trees are immutable, but have parent pointers. The main idea here is to introduce `AstEditor<T>` type, which abstracts away that API. So, you create an `AstEditor` for node you want to edit and call various `&mut` taking methods on it. Internally, `AstEditor` stores both the original node and the current node. All edits are applied to the current node, which is replaced by the corresponding node in the new file. In the end, `AstEditor` computes a text edit between old and new nodes. Note that this also should sole a problem when you create an anchor pointing to a subnode and mutate the parent node, invalidating anchor. Because mutation needs `&mut`, all anchors must be killed before modification. Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates/ra_syntax/src/syntax_node.rs')
-rw-r--r--crates/ra_syntax/src/syntax_node.rs109
1 files changed, 108 insertions, 1 deletions
diff --git a/crates/ra_syntax/src/syntax_node.rs b/crates/ra_syntax/src/syntax_node.rs
index dc2352c76..92c15234e 100644
--- a/crates/ra_syntax/src/syntax_node.rs
+++ b/crates/ra_syntax/src/syntax_node.rs
@@ -7,6 +7,7 @@
7//! modules just wraps its API. 7//! modules just wraps its API.
8 8
9use std::{ 9use std::{
10 ops::RangeInclusive,
10 fmt::{self, Write}, 11 fmt::{self, Write},
11 any::Any, 12 any::Any,
12 borrow::Borrow, 13 borrow::Borrow,
@@ -17,13 +18,21 @@ use ra_parser::ParseError;
17use rowan::{TransparentNewType, GreenNodeBuilder}; 18use rowan::{TransparentNewType, GreenNodeBuilder};
18 19
19use crate::{ 20use crate::{
20 SmolStr, SyntaxKind, TextUnit, TextRange, SyntaxText, SourceFile, AstNode, 21 SmolStr, SyntaxKind, TextUnit, TextRange, SyntaxText, SourceFile, AstNode, SyntaxNodePtr,
21 syntax_error::{SyntaxError, SyntaxErrorKind}, 22 syntax_error::{SyntaxError, SyntaxErrorKind},
22}; 23};
23 24
24pub use rowan::WalkEvent; 25pub use rowan::WalkEvent;
25pub(crate) use rowan::{GreenNode, GreenToken}; 26pub(crate) use rowan::{GreenNode, GreenToken};
26 27
28#[derive(Debug, PartialEq, Eq, Clone, Copy)]
29pub enum InsertPosition<T> {
30 First,
31 Last,
32 Before(T),
33 After(T),
34}
35
27/// Marker trait for CST and AST nodes 36/// Marker trait for CST and AST nodes
28pub trait SyntaxNodeWrapper: TransparentNewType<Repr = rowan::SyntaxNode> {} 37pub trait SyntaxNodeWrapper: TransparentNewType<Repr = rowan::SyntaxNode> {}
29impl<T: TransparentNewType<Repr = rowan::SyntaxNode>> SyntaxNodeWrapper for T {} 38impl<T: TransparentNewType<Repr = rowan::SyntaxNode>> SyntaxNodeWrapper for T {}
@@ -309,6 +318,97 @@ impl SyntaxNode {
309 pub(crate) fn replace_with(&self, replacement: GreenNode) -> GreenNode { 318 pub(crate) fn replace_with(&self, replacement: GreenNode) -> GreenNode {
310 self.0.replace_with(replacement) 319 self.0.replace_with(replacement)
311 } 320 }
321
322 /// Adds specified children (tokens or nodes) to the current node at the
323 /// specific position.
324 ///
325 /// This is a type-unsafe low-level editing API, if you need to use it,
326 /// prefer to create a type-safe abstraction on top of it instead.
327 pub fn insert_children<'a>(
328 &self,
329 position: InsertPosition<SyntaxElement<'_>>,
330 to_insert: impl Iterator<Item = SyntaxElement<'a>>,
331 ) -> TreeArc<SyntaxNode> {
332 let mut delta = TextUnit::default();
333 let to_insert = to_insert.map(|element| {
334 delta += element.text_len();
335 to_green_element(element)
336 });
337
338 let old_children = self.0.green().children();
339
340 let new_children = match position {
341 InsertPosition::First => {
342 to_insert.chain(old_children.iter().cloned()).collect::<Box<[_]>>()
343 }
344 InsertPosition::Last => {
345 old_children.iter().cloned().chain(to_insert).collect::<Box<[_]>>()
346 }
347 InsertPosition::Before(anchor) | InsertPosition::After(anchor) => {
348 let take_anchor = if let InsertPosition::After(_) = position { 1 } else { 0 };
349 let split_at = self.position_of_child(anchor) + take_anchor;
350 let (before, after) = old_children.split_at(split_at);
351 before
352 .iter()
353 .cloned()
354 .chain(to_insert)
355 .chain(after.iter().cloned())
356 .collect::<Box<[_]>>()
357 }
358 };
359
360 self.with_children(new_children)
361 }
362
363 /// Replaces all nodes in `to_delete` with nodes from `to_insert`
364 ///
365 /// This is a type-unsafe low-level editing API, if you need to use it,
366 /// prefer to create a type-safe abstraction on top of it instead.
367 pub fn replace_children<'a>(
368 &self,
369 to_delete: RangeInclusive<SyntaxElement<'_>>,
370 to_insert: impl Iterator<Item = SyntaxElement<'a>>,
371 ) -> TreeArc<SyntaxNode> {
372 let start = self.position_of_child(*to_delete.start());
373 let end = self.position_of_child(*to_delete.end());
374 let old_children = self.0.green().children();
375
376 let new_children = old_children[..start]
377 .iter()
378 .cloned()
379 .chain(to_insert.map(to_green_element))
380 .chain(old_children[end + 1..].iter().cloned())
381 .collect::<Box<[_]>>();
382 self.with_children(new_children)
383 }
384
385 fn with_children(&self, new_children: Box<[rowan::GreenElement]>) -> TreeArc<SyntaxNode> {
386 let len = new_children.iter().map(|it| it.text_len()).sum::<TextUnit>();
387 let new_node = GreenNode::new(rowan::SyntaxKind(self.kind() as u16), new_children);
388 let new_file_node = self.replace_with(new_node);
389 let file = SourceFile::new(new_file_node, Vec::new());
390
391 // FIXME: use a more elegant way to re-fetch the node (#1185), make
392 // `range` private afterwards
393 let mut ptr = SyntaxNodePtr::new(self);
394 ptr.range = TextRange::offset_len(ptr.range().start(), len);
395 return ptr.to_node(&file).to_owned();
396 }
397
398 fn position_of_child(&self, child: SyntaxElement) -> usize {
399 self.children_with_tokens()
400 .position(|it| it == child)
401 .expect("elemetn is not a child of current element")
402 }
403}
404
405fn to_green_element(element: SyntaxElement) -> rowan::GreenElement {
406 match element {
407 SyntaxElement::Node(node) => node.0.green().clone().into(),
408 SyntaxElement::Token(tok) => {
409 GreenToken::new(rowan::SyntaxKind(tok.kind() as u16), tok.text().clone()).into()
410 }
411 }
312} 412}
313 413
314#[derive(Clone, Copy, PartialEq, Eq, Hash)] 414#[derive(Clone, Copy, PartialEq, Eq, Hash)]
@@ -451,6 +551,13 @@ impl<'a> SyntaxElement<'a> {
451 } 551 }
452 .ancestors() 552 .ancestors()
453 } 553 }
554
555 fn text_len(&self) -> TextUnit {
556 match self {
557 SyntaxElement::Node(node) => node.0.green().text_len(),
558 SyntaxElement::Token(token) => TextUnit::of_str(token.0.text()),
559 }
560 }
454} 561}
455 562
456impl<'a> From<rowan::SyntaxElement<'a>> for SyntaxElement<'a> { 563impl<'a> From<rowan::SyntaxElement<'a>> for SyntaxElement<'a> {