🎉 extend selection
This commit is contained in:
@@ -3,6 +3,7 @@ extern crate neon;
|
|||||||
extern crate libeditor;
|
extern crate libeditor;
|
||||||
|
|
||||||
use neon::prelude::*;
|
use neon::prelude::*;
|
||||||
|
use libeditor::TextRange;
|
||||||
|
|
||||||
pub struct Wrapper {
|
pub struct Wrapper {
|
||||||
inner: libeditor::File,
|
inner: libeditor::File,
|
||||||
@@ -19,8 +20,8 @@ declare_types! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
method syntaxTree(mut cx) {
|
method syntaxTree(mut cx) {
|
||||||
let this = cx.this();
|
|
||||||
let tree = {
|
let tree = {
|
||||||
|
let this = cx.this();
|
||||||
let guard = cx.lock();
|
let guard = cx.lock();
|
||||||
let wrapper = this.borrow(&guard);
|
let wrapper = this.borrow(&guard);
|
||||||
wrapper.inner.syntax_tree()
|
wrapper.inner.syntax_tree()
|
||||||
@@ -29,8 +30,8 @@ declare_types! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
method highlight(mut cx) {
|
method highlight(mut cx) {
|
||||||
let this = cx.this();
|
|
||||||
let highlights = {
|
let highlights = {
|
||||||
|
let this = cx.this();
|
||||||
let guard = cx.lock();
|
let guard = cx.lock();
|
||||||
let wrapper = this.borrow(&guard);
|
let wrapper = this.borrow(&guard);
|
||||||
wrapper.inner.highlight()
|
wrapper.inner.highlight()
|
||||||
@@ -51,6 +52,33 @@ declare_types! {
|
|||||||
|
|
||||||
Ok(res.upcast())
|
Ok(res.upcast())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
method extendSelection(mut cx) {
|
||||||
|
let from_offset = cx.argument::<JsNumber>(0)?.value() as u32;
|
||||||
|
let to_offset = cx.argument::<JsNumber>(1)?.value() as u32;
|
||||||
|
let text_range = TextRange::from_to(from_offset.into(), to_offset.into());
|
||||||
|
let extended_range = {
|
||||||
|
let this = cx.this();
|
||||||
|
let guard = cx.lock();
|
||||||
|
let wrapper = this.borrow(&guard);
|
||||||
|
wrapper.inner.extend_selection(text_range)
|
||||||
|
};
|
||||||
|
|
||||||
|
match extended_range {
|
||||||
|
None => Ok(cx.null().upcast()),
|
||||||
|
Some(range) => {
|
||||||
|
let start: u32 = range.start().into();
|
||||||
|
let end: u32 = range.end().into();
|
||||||
|
let start = cx.number(start);
|
||||||
|
let end = cx.number(end);
|
||||||
|
let arr = cx.empty_array();
|
||||||
|
arr.set(&mut cx, 0, start)?;
|
||||||
|
arr.set(&mut cx, 1, end)?;
|
||||||
|
Ok(arr.upcast())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,11 +36,15 @@
|
|||||||
{
|
{
|
||||||
"command": "libsyntax-rust.syntaxTree",
|
"command": "libsyntax-rust.syntaxTree",
|
||||||
"title": "Show Rust syntax tree"
|
"title": "Show Rust syntax tree"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "libsyntax-rust.extendSelection",
|
||||||
|
"title": "Rust Extend Selection"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"keybindings": [
|
"keybindings": [
|
||||||
{
|
{
|
||||||
"command": "libsyntax-rust.semanticSelection",
|
"command": "libsyntax-rust.extendSelection",
|
||||||
"key": "ctrl+w",
|
"key": "ctrl+w",
|
||||||
"when": "editorTextFocus && editorLangId == rust"
|
"when": "editorTextFocus && editorLangId == rust"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,16 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
))
|
))
|
||||||
|
|
||||||
registerCommand('libsyntax-rust.syntaxTree', () => openDoc(uris.syntaxTree))
|
registerCommand('libsyntax-rust.syntaxTree', () => openDoc(uris.syntaxTree))
|
||||||
|
registerCommand('libsyntax-rust.extendSelection', () => {
|
||||||
|
let editor = vscode.window.activeTextEditor
|
||||||
|
let file = activeSyntax()
|
||||||
|
if (editor == null || file == null) return
|
||||||
|
editor.selections = editor.selections.map((s) => {
|
||||||
|
let range = file.extendSelection(s)
|
||||||
|
if (range == null) return null
|
||||||
|
return new vscode.Selection(range.start, range.end)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deactivate() { }
|
export function deactivate() { }
|
||||||
@@ -49,6 +59,11 @@ export class Syntax {
|
|||||||
|
|
||||||
syntaxTree(): string { return this.imp.syntaxTree() }
|
syntaxTree(): string { return this.imp.syntaxTree() }
|
||||||
highlight(): Array<[number, number, string]> { return this.imp.highlight() }
|
highlight(): Array<[number, number, string]> { return this.imp.highlight() }
|
||||||
|
extendSelection(range: vscode.Range): vscode.Range {
|
||||||
|
let range_ = fromVsRange(this.doc, range);
|
||||||
|
let extRange = this.imp.extendSelection(range_[0], range_[1]);
|
||||||
|
return toVsRange(this.doc, extRange);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,4 +6,3 @@ publish = false
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libsyntax2 = { path = "../" }
|
libsyntax2 = { path = "../" }
|
||||||
text_unit = "0.1.2"
|
|
||||||
|
|||||||
36
libeditor/src/extend_selection.rs
Normal file
36
libeditor/src/extend_selection.rs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
use libsyntax2::{
|
||||||
|
TextRange, SyntaxNodeRef,
|
||||||
|
SyntaxKind::WHITESPACE,
|
||||||
|
algo::{find_leaf_at_offset, find_covering_node, ancestors},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
pub(crate) fn extend_selection(root: SyntaxNodeRef, range: TextRange) -> Option<TextRange> {
|
||||||
|
if range.is_empty() {
|
||||||
|
let offset = range.start();
|
||||||
|
let mut leaves = find_leaf_at_offset(root, offset);
|
||||||
|
if let Some(leaf) = leaves.clone().find(|node| node.kind() != WHITESPACE) {
|
||||||
|
return Some(leaf.range());
|
||||||
|
}
|
||||||
|
let ws = leaves.next()?;
|
||||||
|
// let ws_suffix = file.text().slice(
|
||||||
|
// TextRange::from_to(offset, ws.range().end())
|
||||||
|
// );
|
||||||
|
// if ws.text().contains("\n") && !ws_suffix.contains("\n") {
|
||||||
|
// if let Some(line_end) = file.text()
|
||||||
|
// .slice(TextSuffix::from(ws.range().end()))
|
||||||
|
// .find("\n")
|
||||||
|
// {
|
||||||
|
// let range = TextRange::from_len(ws.range().end(), line_end);
|
||||||
|
// return Some(find_covering_node(file.root(), range).range());
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
return Some(ws.range());
|
||||||
|
};
|
||||||
|
let node = find_covering_node(root, range);
|
||||||
|
|
||||||
|
match ancestors(node).skip_while(|n| n.range() == range).next() {
|
||||||
|
None => None,
|
||||||
|
Some(parent) => Some(parent.range()),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
extern crate libsyntax2;
|
extern crate libsyntax2;
|
||||||
extern crate text_unit;
|
|
||||||
|
mod extend_selection;
|
||||||
|
|
||||||
use libsyntax2::{
|
use libsyntax2::{
|
||||||
SyntaxNodeRef,
|
SyntaxNodeRef,
|
||||||
algo::walk,
|
algo::walk,
|
||||||
SyntaxKind::*,
|
SyntaxKind::*,
|
||||||
};
|
};
|
||||||
use text_unit::TextRange;
|
pub use libsyntax2::{TextRange, TextUnit};
|
||||||
|
|
||||||
pub struct File {
|
pub struct File {
|
||||||
inner: libsyntax2::File
|
inner: libsyntax2::File
|
||||||
@@ -71,6 +72,11 @@ impl File {
|
|||||||
.collect();
|
.collect();
|
||||||
res // NLL :-(
|
res // NLL :-(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn extend_selection(&self, range: TextRange) -> Option<TextRange> {
|
||||||
|
let syntax = self.inner.syntax();
|
||||||
|
extend_selection::extend_selection(syntax.as_ref(), range)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -96,3 +102,22 @@ impl<'f> Declaration<'f> {
|
|||||||
self.0.range()
|
self.0.range()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extend_selection() {
|
||||||
|
let text = r#"fn foo() {
|
||||||
|
1 + 1
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let file = File::new(text);
|
||||||
|
let range = TextRange::offset_len(18.into(), 0.into());
|
||||||
|
let range = file.extend_selection(range).unwrap();
|
||||||
|
assert_eq!(range, TextRange::from_to(17.into(), 18.into()));
|
||||||
|
let range = file.extend_selection(range).unwrap();
|
||||||
|
assert_eq!(range, TextRange::from_to(15.into(), 20.into()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
122
src/algo/mod.rs
122
src/algo/mod.rs
@@ -1 +1,123 @@
|
|||||||
pub mod walk;
|
pub mod walk;
|
||||||
|
|
||||||
|
use {SyntaxNodeRef, TextUnit, TextRange};
|
||||||
|
|
||||||
|
pub fn find_leaf_at_offset(node: SyntaxNodeRef, offset: TextUnit) -> LeafAtOffset {
|
||||||
|
let range = node.range();
|
||||||
|
assert!(
|
||||||
|
contains_offset_nonstrict(range, offset),
|
||||||
|
"Bad offset: range {:?} offset {:?}", range, offset
|
||||||
|
);
|
||||||
|
if range.is_empty() {
|
||||||
|
return LeafAtOffset::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.is_leaf() {
|
||||||
|
return LeafAtOffset::Single(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut children = node.children()
|
||||||
|
.filter(|child| {
|
||||||
|
let child_range = child.range();
|
||||||
|
!child_range.is_empty() && contains_offset_nonstrict(child_range, offset)
|
||||||
|
});
|
||||||
|
|
||||||
|
let left = children.next().unwrap();
|
||||||
|
let right = children.next();
|
||||||
|
assert!(children.next().is_none());
|
||||||
|
return if let Some(right) = right {
|
||||||
|
match (find_leaf_at_offset(left, offset), find_leaf_at_offset(right, offset)) {
|
||||||
|
(LeafAtOffset::Single(left), LeafAtOffset::Single(right)) =>
|
||||||
|
LeafAtOffset::Between(left, right),
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
find_leaf_at_offset(left, offset)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum LeafAtOffset<'a> {
|
||||||
|
None,
|
||||||
|
Single(SyntaxNodeRef<'a>),
|
||||||
|
Between(SyntaxNodeRef<'a>, SyntaxNodeRef<'a>)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> LeafAtOffset<'a> {
|
||||||
|
pub fn right_biased(self) -> Option<SyntaxNodeRef<'a>> {
|
||||||
|
match self {
|
||||||
|
LeafAtOffset::None => None,
|
||||||
|
LeafAtOffset::Single(node) => Some(node),
|
||||||
|
LeafAtOffset::Between(_, right) => Some(right)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn left_biased(self) -> Option<SyntaxNodeRef<'a>> {
|
||||||
|
match self {
|
||||||
|
LeafAtOffset::None => None,
|
||||||
|
LeafAtOffset::Single(node) => Some(node),
|
||||||
|
LeafAtOffset::Between(left, _) => Some(left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'f> Iterator for LeafAtOffset<'f> {
|
||||||
|
type Item = SyntaxNodeRef<'f>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<SyntaxNodeRef<'f>> {
|
||||||
|
match *self {
|
||||||
|
LeafAtOffset::None => None,
|
||||||
|
LeafAtOffset::Single(node) => { *self = LeafAtOffset::None; Some(node) }
|
||||||
|
LeafAtOffset::Between(left, right) => { *self = LeafAtOffset::Single(right); Some(left) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn find_covering_node(root: SyntaxNodeRef, range: TextRange) -> SyntaxNodeRef {
|
||||||
|
assert!(is_subrange(root.range(), range));
|
||||||
|
let (left, right) = match (
|
||||||
|
find_leaf_at_offset(root, range.start()).right_biased(),
|
||||||
|
find_leaf_at_offset(root, range.end()).left_biased()
|
||||||
|
) {
|
||||||
|
(Some(l), Some(r)) => (l, r),
|
||||||
|
_ => return root
|
||||||
|
};
|
||||||
|
|
||||||
|
common_ancestor(left, right)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn common_ancestor<'a>(n1: SyntaxNodeRef<'a>, n2: SyntaxNodeRef<'a>) -> SyntaxNodeRef<'a> {
|
||||||
|
for p in ancestors(n1) {
|
||||||
|
if ancestors(n2).any(|a| a == p) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic!("Can't find common ancestor of {:?} and {:?}", n1, n2)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ancestors<'a>(node: SyntaxNodeRef<'a>) -> impl Iterator<Item=SyntaxNodeRef<'a>> {
|
||||||
|
Ancestors(Some(node))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Ancestors<'a>(Option<SyntaxNodeRef<'a>>);
|
||||||
|
|
||||||
|
impl<'a> Iterator for Ancestors<'a> {
|
||||||
|
type Item = SyntaxNodeRef<'a>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.0.take().map(|n| {
|
||||||
|
self.0 = n.parent();
|
||||||
|
n
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains_offset_nonstrict(range: TextRange, offset: TextUnit) -> bool {
|
||||||
|
range.start() <= offset && offset <= range.end()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_subrange(range: TextRange, subrange: TextRange) -> bool {
|
||||||
|
range.start() <= subrange.start() && subrange.end() <= range.end()
|
||||||
|
}
|
||||||
|
|||||||
136
src/algo/search.rs
Normal file
136
src/algo/search.rs
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
use {Node, NodeType, TextUnit, TextRange};
|
||||||
|
use ::visitor::{visitor, process_subtree_bottom_up};
|
||||||
|
|
||||||
|
pub fn child_of_type(node: Node, ty: NodeType) -> Option<Node> {
|
||||||
|
node.children().find(|n| n.ty() == ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn children_of_type<'f>(node: Node<'f>, ty: NodeType) -> Box<Iterator<Item=Node<'f>> + 'f> {
|
||||||
|
Box::new(node.children().filter(move |n| n.ty() == ty))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn subtree<'f>(node: Node<'f>) -> Box<Iterator<Item=Node<'f>> + 'f> {
|
||||||
|
Box::new(node.children().flat_map(subtree).chain(::std::iter::once(node)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn descendants_of_type<'f>(node: Node<'f>, ty: NodeType) -> Vec<Node<'f>> {
|
||||||
|
process_subtree_bottom_up(
|
||||||
|
node,
|
||||||
|
visitor(Vec::new())
|
||||||
|
.visit_nodes(&[ty], |node, nodes| nodes.push(node))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn child_of_type_exn(node: Node, ty: NodeType) -> Node {
|
||||||
|
child_of_type(node, ty).unwrap_or_else(|| {
|
||||||
|
panic!("No child of type {:?} for {:?}\
|
||||||
|
----\
|
||||||
|
{}\
|
||||||
|
----", ty, node.ty(), node.text())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn ancestors(node: Node) -> Ancestors {
|
||||||
|
Ancestors(Some(node))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Ancestors<'f>(Option<Node<'f>>);
|
||||||
|
|
||||||
|
impl<'f> Iterator for Ancestors<'f> {
|
||||||
|
type Item = Node<'f>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let current = self.0;
|
||||||
|
self.0 = current.and_then(|n| n.parent());
|
||||||
|
current
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_leaf(node: Node) -> bool {
|
||||||
|
node.children().next().is_none() && !node.range().is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||||
|
pub enum Direction {
|
||||||
|
Left, Right
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sibling(node: Node, dir: Direction) -> Option<Node> {
|
||||||
|
let (parent, idx) = child_position(node)?;
|
||||||
|
let idx = match dir {
|
||||||
|
Direction::Left => idx.checked_sub(1)?,
|
||||||
|
Direction::Right => idx + 1,
|
||||||
|
};
|
||||||
|
parent.children().nth(idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod ast {
|
||||||
|
use {Node, AstNode, TextUnit, AstChildren};
|
||||||
|
use visitor::{visitor, process_subtree_bottom_up};
|
||||||
|
use super::{ancestors, find_leaf_at_offset, LeafAtOffset};
|
||||||
|
|
||||||
|
pub fn ancestor<'f, T: AstNode<'f>>(node: Node<'f>) -> Option<T> {
|
||||||
|
ancestors(node)
|
||||||
|
.filter_map(T::wrap)
|
||||||
|
.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ancestor_exn<'f, T: AstNode<'f>>(node: Node<'f>) -> T {
|
||||||
|
ancestor(node).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn children_of_type<'f, N: AstNode<'f>>(node: Node<'f>) -> AstChildren<N> {
|
||||||
|
AstChildren::new(node.children())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn descendants_of_type<'f, N: AstNode<'f>>(node: Node<'f>) -> Vec<N> {
|
||||||
|
process_subtree_bottom_up(
|
||||||
|
node,
|
||||||
|
visitor(Vec::new())
|
||||||
|
.visit::<N, _>(|node, acc| acc.push(node))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn node_at_offset<'f, T: AstNode<'f>>(node: Node<'f>, offset: TextUnit) -> Option<T> {
|
||||||
|
match find_leaf_at_offset(node, offset) {
|
||||||
|
LeafAtOffset::None => None,
|
||||||
|
LeafAtOffset::Single(node) => ancestor(node),
|
||||||
|
LeafAtOffset::Between(left, right) => ancestor(left).or_else(|| ancestor(right)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod traversal {
|
||||||
|
use {Node};
|
||||||
|
|
||||||
|
pub fn bottom_up<'f, F: FnMut(Node<'f>)>(node: Node<'f>, mut f: F)
|
||||||
|
{
|
||||||
|
go(node, &mut f);
|
||||||
|
|
||||||
|
fn go<'f, F: FnMut(Node<'f>)>(node: Node<'f>, f: &mut F) {
|
||||||
|
for child in node.children() {
|
||||||
|
go(child, f)
|
||||||
|
}
|
||||||
|
f(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn child_position(child: Node) -> Option<(Node, usize)> {
|
||||||
|
child.parent()
|
||||||
|
.map(|parent| {
|
||||||
|
(parent, parent.children().position(|n| n == child).unwrap())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn common_ancestor<'f>(n1: Node<'f>, n2: Node<'f>) -> Node<'f> {
|
||||||
|
for p in ancestors(n1) {
|
||||||
|
if ancestors(n2).any(|a| a == p) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic!("Can't find common ancestor of {:?} and {:?}", n1, n2)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -13,6 +13,7 @@ use {
|
|||||||
SyntaxKind::{self, TOMBSTONE},
|
SyntaxKind::{self, TOMBSTONE},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/// `Parser` produces a flat list of `Event`s.
|
/// `Parser` produces a flat list of `Event`s.
|
||||||
/// They are converted to a tree-structure in
|
/// They are converted to a tree-structure in
|
||||||
/// a separate pass, via `TreeBuilder`.
|
/// a separate pass, via `TreeBuilder`.
|
||||||
|
|||||||
@@ -115,6 +115,10 @@ impl<R: TreeRoot> SyntaxNode<R> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_leaf(&self) -> bool {
|
||||||
|
self.first_child().is_none()
|
||||||
|
}
|
||||||
|
|
||||||
fn red(&self) -> &RedNode {
|
fn red(&self) -> &RedNode {
|
||||||
unsafe { self.red.as_ref() }
|
unsafe { self.red.as_ref() }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user