diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-04-22 11:08:18 +0100 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-04-22 11:08:18 +0100 |
commit | 38c0a1e3331f4b2b9efc7caa20b5927874024686 (patch) | |
tree | 321dd22a931c2ca11d2e207d734f283844af656d /crates/ra_syntax | |
parent | 76e0129a21661029dc6cdbea2412ab53efe33aa1 (diff) | |
parent | b73a978b95810b188090a37e002a22403a9067bd (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.rs | 9 | ||||
-rw-r--r-- | crates/ra_syntax/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ra_syntax/src/ptr.rs | 2 | ||||
-rw-r--r-- | crates/ra_syntax/src/syntax_node.rs | 109 |
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 | ||
213 | impl 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 | |||
213 | impl ast::LetStmt { | 222 | impl 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)] |
12 | pub struct SyntaxNodePtr { | 12 | pub 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 | ||
9 | use std::{ | 9 | use 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; | |||
17 | use rowan::{TransparentNewType, GreenNodeBuilder}; | 18 | use rowan::{TransparentNewType, GreenNodeBuilder}; |
18 | 19 | ||
19 | use crate::{ | 20 | use 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 | ||
24 | pub use rowan::WalkEvent; | 25 | pub use rowan::WalkEvent; |
25 | pub(crate) use rowan::{GreenNode, GreenToken}; | 26 | pub(crate) use rowan::{GreenNode, GreenToken}; |
26 | 27 | ||
28 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
29 | pub 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 |
28 | pub trait SyntaxNodeWrapper: TransparentNewType<Repr = rowan::SyntaxNode> {} | 37 | pub trait SyntaxNodeWrapper: TransparentNewType<Repr = rowan::SyntaxNode> {} |
29 | impl<T: TransparentNewType<Repr = rowan::SyntaxNode>> SyntaxNodeWrapper for T {} | 38 | impl<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 | |||
405 | fn 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 | ||
456 | impl<'a> From<rowan::SyntaxElement<'a>> for SyntaxElement<'a> { | 563 | impl<'a> From<rowan::SyntaxElement<'a>> for SyntaxElement<'a> { |