Tiptap Studio

Querying

Walk the tree and find nodes with predicate closures and selector helpers.

Every query runs depth-first in pre-order: a node is visited before its children, and children in document order. Every query includes the node you call it on, so doc.find(..) can return the root itself.

The predicate is a plain closure over &Node, so it can test anything: type, text, attributes, marks, unknown fields. For string queries typed by a user, see the query language.

Find

use tiptap_rusty_parser::{Document, Node};

let doc = Document::from_json_str(r#"{"type":"doc","content":[
    {"type":"paragraph","content":[
        {"type":"text","text":"a","marks":[{"type":"bold"}]},
        {"type":"text","text":"b"}
    ]}
]}"#)?;

// First match, or None.
let bold: Option<&Node> = doc.find(|n| n.has_mark("bold"));
assert_eq!(bold.unwrap().get_text(), Some("a"));

// Every match.
let texts: Vec<&Node> = doc.find_all(|n| n.node_type.as_deref() == Some("text"));
assert_eq!(texts.len(), 2);

// The root is included.
assert_eq!(doc.find(|_| true).unwrap().node_type.as_deref(), Some("doc"));

Walk and iterate

walk calls a closure for every node. descendants returns a lazy iterator, so you can chain iterator adapters and stop early:

use tiptap_rusty_parser::Document;

let doc = Document::from_json_str(r#"{"type":"doc","content":[
    {"type":"paragraph","content":[{"type":"text","text":"a"}]},
    {"type":"paragraph"}
]}"#)?;

let mut count = 0;
doc.walk(&mut |_| count += 1);
assert_eq!(count, 4);

let types: Vec<&str> = doc.descendants().filter_map(|n| n.node_type.as_deref()).collect();
assert_eq!(types, ["doc", "paragraph", "text", "paragraph"]);

descendants keeps its own stack instead of recursing, and find and find_all are built on it, so deep documents do not grow the call stack.

Mutable queries

find_all_mut and walk_mut give &mut Node access. The predicate still takes &Node. To edit only the first match, find its path with path_to and borrow it with node_at_mut.

use tiptap_rusty_parser::{Document, Mark, Node};

let mut doc = Document::from_json_str(r#"{"type":"doc","content":[
    {"type":"paragraph","content":[{"type":"text","text":"x"},{"type":"text","text":"y"}]}
]}"#)?;

if let Some(path) = doc.path_to(|n| n.get_text() == Some("x")) {
    doc.node_at_mut(&path).unwrap().set_text("changed");
}

let mut is_text = |n: &Node| n.node_type.as_deref() == Some("text");
for node in doc.find_all_mut(&mut is_text) {
    node.add_mark(Mark::new("italic"));
}

doc.walk_mut(&mut |n| {
    n.remove_attr("id");
});

assert_eq!(doc.by_mark("italic").len(), 2);
assert_eq!(doc.text_content(), "changedy");

To change every match in one call, use replace_all, covered in Mutating.

MethodPredicateReturns
find(pred)FnMut(&Node) -> boolOption<&Node>
find_all(pred)FnMut(&Node) -> boolVec<&Node>
find_all_mut(&mut pred)&mut impl FnMut(&Node) -> boolVec<&mut Node>
walk(&mut f)&mut impl FnMut(&Node)()
walk_mut(&mut f)&mut impl FnMut(&mut Node)()
descendants()noneDescendants<'_>, an Iterator<Item = &Node>

Selectors

Selectors are shortcuts for the most common predicates. They are thin wrappers over find and find_all.

use tiptap_rusty_parser::Document;

let mut doc = Document::from_json_str(r#"{"type":"doc","content":[
    {"type":"heading","attrs":{"level":1},"content":[{"type":"text","text":"Title"}]},
    {"type":"paragraph","content":[{"type":"text","text":"a","marks":[{"type":"bold"}]}]}
]}"#)?;

assert_eq!(doc.by_type("paragraph").len(), 1);
assert_eq!(doc.first_by_type("text").unwrap().get_text(), Some("Title"));
assert_eq!(doc.by_mark("bold").len(), 1);
assert_eq!(doc.by_attr("level", 1).len(), 1);
assert!(doc.by_attr("level", "1").is_empty()); // the value must match exactly

for p in doc.by_type_mut("paragraph") {
    p.set_attr("touched", true);
}
assert_eq!(doc.by_attr("touched", true).len(), 1);
MethodMatchesReturns
by_type(t)Nodes whose type equals tVec<&Node>
first_by_type(t)The first such nodeOption<&Node>
by_type_mut(t)Nodes whose type equals tVec<&mut Node>
by_mark(mark_type)Nodes that carry a mark of that typeVec<&Node>
by_attr(key, value)Nodes whose attribute key equals valueVec<&Node>

by_attr takes any impl Into<serde_json::Value>, and compares with JSON equality: the number 1 and the string "1" are different values.

Paths instead of references

The methods above return references. When you need to come back to a node later, or edit it while holding other borrows, get its index path with path_to or paths_to, or run a query language query with find_paths. See Paths and positions.

On this page