aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_syntax
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
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')
-rw-r--r--crates/ra_syntax/src/ast/extensions.rs9
-rw-r--r--crates/ra_syntax/src/lib.rs2
-rw-r--r--crates/ra_syntax/src/ptr.rs2
-rw-r--r--crates/ra_syntax/src/syntax_node.rs109
4 files changed, 119 insertions, 3 deletions
diff --git a/crates/ra_syntax/src/ast/extensions.rs b/crates/ra_syntax/src/ast/extensions.rs
index 5c4c0ffc1..9cbd2c6b8 100644
--- a/crates/ra_syntax/src/ast/extensions.rs
+++ b/crates/ra_syntax/src/ast/extensions.rs
@@ -210,6 +210,15 @@ impl ast::EnumVariant {
210 } 210 }
211} 211}
212 212
213impl ast::FnDef {
214 pub fn semicolon_token(&self) -> Option<SyntaxToken<'_>> {
215 self.syntax()
216 .last_child_or_token()
217 .and_then(|it| it.as_token())
218 .filter(|it| it.kind() == SEMI)
219 }
220}
221
213impl ast::LetStmt { 222impl ast::LetStmt {
214 pub fn has_semi(&self) -> bool { 223 pub fn has_semi(&self) -> bool {
215 match self.syntax().last_child_or_token() { 224 match self.syntax().last_child_or_token() {
diff --git a/crates/ra_syntax/src/lib.rs b/crates/ra_syntax/src/lib.rs
index a6ce14f06..9cb66b76b 100644
--- a/crates/ra_syntax/src/lib.rs
+++ b/crates/ra_syntax/src/lib.rs
@@ -38,7 +38,7 @@ pub use crate::{
38 ast::AstNode, 38 ast::AstNode,
39 syntax_error::{SyntaxError, SyntaxErrorKind, Location}, 39 syntax_error::{SyntaxError, SyntaxErrorKind, Location},
40 syntax_text::SyntaxText, 40 syntax_text::SyntaxText,
41 syntax_node::{Direction, SyntaxNode, WalkEvent, TreeArc, SyntaxTreeBuilder, SyntaxElement, SyntaxToken}, 41 syntax_node::{Direction, SyntaxNode, WalkEvent, TreeArc, SyntaxTreeBuilder, SyntaxElement, SyntaxToken, InsertPosition},
42 ptr::{SyntaxNodePtr, AstPtr}, 42 ptr::{SyntaxNodePtr, AstPtr},
43 parsing::{tokenize, classify_literal, Token}, 43 parsing::{tokenize, classify_literal, Token},
44}; 44};
diff --git a/crates/ra_syntax/src/ptr.rs b/crates/ra_syntax/src/ptr.rs
index 15a8b94cd..b0816b135 100644
--- a/crates/ra_syntax/src/ptr.rs
+++ b/crates/ra_syntax/src/ptr.rs
@@ -10,7 +10,7 @@ use crate::{
10/// specific node across reparses of the same file. 10/// specific node across reparses of the same file.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub struct SyntaxNodePtr { 12pub struct SyntaxNodePtr {
13 range: TextRange, 13 pub(crate) range: TextRange,
14 kind: SyntaxKind, 14 kind: SyntaxKind,
15} 15}
16 16
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> {