From 7e74af32268f9b0783ca94107b0b10d52e4ebe5e Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 28 Aug 2018 14:06:30 +0300 Subject: Avoid materializing strings --- crates/libeditor/src/code_actions.rs | 4 +- crates/libeditor/src/typing.rs | 17 +++-- crates/libsyntax2/src/lib.rs | 4 +- crates/libsyntax2/src/yellow/green.rs | 16 ++--- crates/libsyntax2/src/yellow/mod.rs | 10 ++- crates/libsyntax2/src/yellow/syntax.rs | 19 ++++-- crates/libsyntax2/src/yellow/syntax_text.rs | 101 ++++++++++++++++++++++++++++ 7 files changed, 139 insertions(+), 32 deletions(-) create mode 100644 crates/libsyntax2/src/yellow/syntax_text.rs (limited to 'crates') diff --git a/crates/libeditor/src/code_actions.rs b/crates/libeditor/src/code_actions.rs index e6ba83d2e..cd5146d87 100644 --- a/crates/libeditor/src/code_actions.rs +++ b/crates/libeditor/src/code_actions.rs @@ -29,8 +29,8 @@ pub fn flip_comma<'a>(file: &'a File, offset: TextUnit) -> Option ActionResult { let range = if range.is_empty() { - let text = file.syntax().text(); - let text = &text[TextRange::from_to(range.start(), TextUnit::of_str(&text))]; - let pos = text.bytes().take_while(|&b| b != b'\n').count(); - if pos == text.len() { - return ActionResult { + let syntax = file.syntax(); + let text = syntax.text().slice(range.start()..); + let pos = match text.find('\n') { + None => return ActionResult { edit: EditBuilder::new().finish(), cursor_position: None - }; - } - let pos: TextUnit = (pos as u32).into(); + }, + Some(pos) => pos + }; TextRange::offset_len( range.start() + pos, TextUnit::of_char('\n'), @@ -129,7 +128,7 @@ fn join_lambda_body( let expr = single_expr(block)?; edit.replace( block_expr.syntax().range(), - expr.syntax().text(), + expr.syntax().text().to_string(), ); Some(()) } diff --git a/crates/libsyntax2/src/lib.rs b/crates/libsyntax2/src/lib.rs index 93057dd6a..ccf13e4ab 100644 --- a/crates/libsyntax2/src/lib.rs +++ b/crates/libsyntax2/src/lib.rs @@ -81,7 +81,7 @@ impl File { pub fn incremental_reparse(&self, edit: &AtomEdit) -> Option { let (node, reparser) = find_reparsable_node(self.syntax(), edit.delete)?; let text = replace_range( - node.text(), + node.text().to_string(), edit.delete - node.range().start(), &edit.insert, ); @@ -97,7 +97,7 @@ impl File { Some(File::new(green_root, errors)) } fn full_reparse(&self, edit: &AtomEdit) -> File { - let text = replace_range(self.syntax().text(), edit.delete, &edit.insert); + let text = replace_range(self.syntax().text().to_string(), edit.delete, &edit.insert); File::parse(&text) } pub fn ast(&self) -> ast::Root { diff --git a/crates/libsyntax2/src/yellow/green.rs b/crates/libsyntax2/src/yellow/green.rs index 700f2704f..59aefb0de 100644 --- a/crates/libsyntax2/src/yellow/green.rs +++ b/crates/libsyntax2/src/yellow/green.rs @@ -43,21 +43,13 @@ impl GreenNode { } } - pub fn text(&self) -> String { - let mut buff = String::new(); - go(self, &mut buff); - return buff; - fn go(node: &GreenNode, buff: &mut String) { - match node { - GreenNode::Leaf { text, .. } => buff.push_str(text.as_str()), - GreenNode::Branch(b) => b.children().iter().for_each(|child| go(child, buff)), - } - } + pub fn leaf_text(&self) -> Option { + self.leaf_text_ref().map(Clone::clone) } - pub fn leaf_text(&self) -> Option { + pub fn leaf_text_ref(&self) -> Option<&SmolStr> { match self { - GreenNode::Leaf { text, .. } => Some(text.clone()), + GreenNode::Leaf { text, .. } => Some(text), GreenNode::Branch(_) => None, } } diff --git a/crates/libsyntax2/src/yellow/mod.rs b/crates/libsyntax2/src/yellow/mod.rs index b94c794fe..82eda79d6 100644 --- a/crates/libsyntax2/src/yellow/mod.rs +++ b/crates/libsyntax2/src/yellow/mod.rs @@ -2,6 +2,7 @@ mod builder; mod green; mod red; mod syntax; +mod syntax_text; use std::{ sync::Arc, @@ -12,6 +13,7 @@ pub(crate) use self::{ builder::GreenBuilder, green::GreenNode, red::RedNode, + syntax_text::SyntaxText, }; #[derive(Debug)] @@ -32,6 +34,12 @@ pub struct OwnedRoot(Arc); #[derive(Clone, Copy, Debug)] pub struct RefRoot<'a>(&'a OwnedRoot); // TODO: shared_from_this instead of double indirection +impl<'a> RefRoot<'a> { + fn syntax_root(&self) -> &'a SyntaxRoot { + self.0.syntax_root() + } +} + impl TreeRoot for OwnedRoot { fn borrowed(&self) -> RefRoot { RefRoot(&self) @@ -78,7 +86,7 @@ impl RedPtr { RedPtr(red.into()) } - unsafe fn get<'a>(self, _root: &'a impl TreeRoot) -> &'a RedNode { + unsafe fn get<'a>(self, _root: &'a SyntaxRoot) -> &'a RedNode { &*self.0.as_ptr() } } diff --git a/crates/libsyntax2/src/yellow/syntax.rs b/crates/libsyntax2/src/yellow/syntax.rs index 75b6cb7dc..ede9ad3c8 100644 --- a/crates/libsyntax2/src/yellow/syntax.rs +++ b/crates/libsyntax2/src/yellow/syntax.rs @@ -6,7 +6,7 @@ use std::{ use smol_str::SmolStr; use { - yellow::{GreenNode, RedNode, TreeRoot, SyntaxRoot, RedPtr, RefRoot, OwnedRoot}, + yellow::{GreenNode, RedNode, TreeRoot, SyntaxRoot, RedPtr, RefRoot, OwnedRoot, SyntaxText}, SyntaxKind::{self, *}, TextRange, TextUnit, }; @@ -58,6 +58,13 @@ impl SyntaxNode { } } +impl<'a> SyntaxNode> { + pub(crate) fn leaf_text_ref(self) -> Option<&'a SmolStr> { + let red = unsafe { self.red.get(self.root.syntax_root()) }; + red.green().leaf_text_ref() + } +} + impl SyntaxNode { pub fn borrowed<'a>(&'a self) -> SyntaxNodeRef<'a> { SyntaxNode { @@ -66,7 +73,7 @@ impl SyntaxNode { } } - pub fn owned<'a>(&'a self) -> SyntaxNode { + pub fn owned(&self) -> SyntaxNode { SyntaxNode { root: self.root.owned(), red: self.red, @@ -82,8 +89,8 @@ impl SyntaxNode { TextRange::offset_len(red.start_offset(), red.green().text_len()) } - pub fn text(&self) -> String { - self.red().green().text() + pub fn text(&self) -> SyntaxText { + SyntaxText::new(self.borrowed()) } pub fn children(&self) -> impl Iterator> { @@ -91,7 +98,7 @@ impl SyntaxNode { let n_children = self.red().n_children(); let root = self.root.clone(); (0..n_children).map(move |i| { - let red = unsafe { red.get(&root) }; + let red = unsafe { red.get(root.syntax_root()) }; SyntaxNode { root: root.clone(), red: red.get_child(i).unwrap(), @@ -171,7 +178,7 @@ impl SyntaxNode { } fn red(&self) -> &RedNode { - unsafe { self.red.get(&self.root) } + unsafe { self.red.get(self.root.syntax_root()) } } } diff --git a/crates/libsyntax2/src/yellow/syntax_text.rs b/crates/libsyntax2/src/yellow/syntax_text.rs new file mode 100644 index 000000000..268547470 --- /dev/null +++ b/crates/libsyntax2/src/yellow/syntax_text.rs @@ -0,0 +1,101 @@ +use std::{ + fmt, ops, +}; + +use { + SyntaxNodeRef, TextRange, TextUnit, + algo::walk::preorder, + text_utils::{intersect, contains_offset_nonstrict}, +}; + +#[derive(Clone)] +pub struct SyntaxText<'a> { + node: SyntaxNodeRef<'a>, + range: TextRange, +} + +impl<'a> SyntaxText<'a> { + pub(crate) fn new(node: SyntaxNodeRef<'a>) -> SyntaxText<'a> { + SyntaxText { + node, + range: node.range() + } + } + pub fn chunks(&self) -> impl Iterator { + let range = self.range; + preorder(self.node) + .filter_map(move |node| { + let text = node.leaf_text_ref()?; + let range = intersect(range, node.range())?; + let range = range - node.range().start(); + Some(&text[range]) + }) + } + pub fn to_string(&self) -> String { + self.chunks().collect() + } + pub fn contains(&self, c: char) -> bool { + self.chunks().any(|it| it.contains(c)) + } + pub fn find(&self, c: char) -> Option { + let mut acc: TextUnit = 0.into(); + for chunk in self.chunks() { + if let Some(pos) = chunk.find(c) { + let pos: TextUnit = (pos as u32).into(); + return Some(acc + pos); + } + acc += TextUnit::of_str(chunk); + } + None + } + pub fn len(&self) -> TextUnit { + self.range.len() + } + pub fn slice(&self, range: impl SyntaxTextSlice) -> SyntaxText<'a> { + let range = range.restrict(self.range) + .unwrap_or_else(|| { + panic!("invalid slice, range: {:?}, slice: {:?}", self.range, range) + }); + SyntaxText { node: self.node, range } + } +} + +impl<'a> fmt::Debug for SyntaxText<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&self.to_string(), f) + } +} + +impl<'a> fmt::Display for SyntaxText<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.to_string(), f) + } +} + +pub trait SyntaxTextSlice: fmt::Debug { + fn restrict(&self, range: TextRange) -> Option; +} + +impl SyntaxTextSlice for TextRange { + fn restrict(&self, range: TextRange) -> Option { + intersect(*self, range) + } +} + +impl SyntaxTextSlice for ops::RangeTo { + fn restrict(&self, range: TextRange) -> Option { + if !contains_offset_nonstrict(range, self.end) { + return None; + } + Some(TextRange::from_to(range.start(), self.end)) + } +} + +impl SyntaxTextSlice for ops::RangeFrom { + fn restrict(&self, range: TextRange) -> Option { + if !contains_offset_nonstrict(range, self.start) { + return None; + } + Some(TextRange::from_to(self.start, range.end())) + } +} -- cgit v1.2.3