Skip to main content

Editor

Struct Editor 

Source
pub struct Editor { /* private fields */ }
Expand description

A span-splice editor over a document: applies lossless, in-place edits and reparses after each one, so node addressing stays valid as the document evolves. Every op is addressed by a locator — a dot-separated index path ("0.3.1") or a selector that must match exactly one node (heading("Status")). A failed edit leaves the document unchanged.

Implementations§

Source§

impl Editor

Source

pub fn new(input: &[u8], format: Format) -> Result<Self, Error>

Create an editor over a private copy of input, parsed as format with default options.

Source

pub fn new_str(input: &str, format: Format) -> Result<Self, Error>

Source

pub fn new_ext( input: &[u8], format: Format, extensions: MarkdownExtensions, ) -> Result<Self, Error>

Like Editor::new, plus Markdown extensions to enable (ignored for other formats). The editor reparses with these after every edit, so a directive-bearing document stays parseable — needed before Editor::filter can match directive[...] selectors.

Source

pub fn replace(&mut self, locator: &str, text: &str) -> Result<(), Error>

Replace the whole source of the located node with text.

Source

pub fn replace_content( &mut self, locator: &str, text: &str, ) -> Result<(), Error>

Replace the interior (between-delimiters content) of the located container.

Source

pub fn insert_before(&mut self, locator: &str, text: &str) -> Result<(), Error>

Insert text immediately before the located node.

Source

pub fn insert_after(&mut self, locator: &str, text: &str) -> Result<(), Error>

Insert text immediately after the located node.

Source

pub fn insert_child( &mut self, locator: &str, index: usize, text: &str, ) -> Result<(), Error>

Insert text as the index-th child of the located container (an index at or past the child count appends).

Source

pub fn delete(&mut self, locator: &str) -> Result<(), Error>

Delete the located node (removes exactly its span; no whitespace cleanup).

Source

pub fn delete_smart(&mut self, locator: &str) -> Result<(), Error>

Delete the located node, tidying surrounding blank lines for a whole-line (block) node; an inline node degrades to the exact delete.

Source

pub fn unwrap_node(&mut self, locator: &str) -> Result<(), Error>

Unwrap the located node: replace it with its interior (drop the wrapper, keep the children) — e.g. peel a :::vis{...} container. A node with no interior (a leaf, or an empty container) is removed.

Source

pub fn filter( &mut self, drop: &str, keep: Option<&str>, unwrap_kept: bool, ) -> Result<(), Error>

Prune the document in place: remove every node matching the drop selector except those also matching keep (None spares nothing), then — if unwrap_kept — unwrap the survivors. Read the result with Editor::source.

Source

pub fn source(&mut self) -> Result<Vec<u8>, Error>

The editor’s current (edited) source bytes.

Source

pub fn source_str(&mut self) -> Result<String, Error>

The editor’s current source bytes as a UTF-8 string.

Source

pub fn ast_json(&mut self) -> Result<Vec<u8>, Error>

Encode the editor’s current tree as pretty-printed JSON — the live counterpart of Document::ast_json, for inspecting between edits.

Source

pub fn query(&mut self, selector: &str) -> Result<Vec<QueryMatch>, Error>

Resolve a selector against the editor’s current tree — the live counterpart of Document::query.

Source

pub fn edit_range( &mut self, start: usize, end: usize, text: &str, ) -> Result<Change, Error>

Splice [start, end) of the current source with text, reparse, and return the Change the edit produced — the offset-addressed primitive a caret editor is built on: a keystroke is edit_range(c, c, "x"), backspace edit_range(c - 1, c, ""), a selection replace edit_range(a, b, s). start <= end <= source length, else Error::InvalidArgument. A reparse-breaking edit is rolled back and returns Error::EditConflict, leaving the document untouched.

Source

pub fn last_change(&mut self) -> Option<Change>

The byte effect of the last successful edit — including the locator ops (Editor::replace, Editor::delete_smart, …), so any edit can re-anchor a caret without re-diffing. None before the first successful edit. (A multi-splice op such as Editor::filter reports only its final splice.)

Source

pub fn undo(&mut self) -> Result<Option<Change>, Error>

Undo the last edit step, restoring the previous source and reparsing. Returns the Change the undo produced (current → restored) so a caret can re-anchor, or None when there’s nothing to undo. History accrues across every successful edit that funnels through the splice primitive.

Source

pub fn redo(&mut self) -> Result<Option<Change>, Error>

Redo the most recently undone edit step; the inverse of Editor::undo. Returns None when the redo stack is empty (nothing undone, or a fresh edit has invalidated it).

Source

pub fn coalesce_last_undo(&mut self) -> Result<(), Error>

Fold the most recent edit into the undo step before it, so a caret editor can coalesce a run of keystrokes into a single undo. Call right after an edit_range that continues a run (same kind, no intervening caret move); a no-op unless there are at least two steps to merge.

Source

pub fn nodes(&mut self) -> Result<Vec<FlatNode>, Error>

Snapshot the current tree as a flat FlatNode array (the JSON-free read path for a renderer), indexed so nodes[i].id == NodeId(i). Walk it via the parent/first_child/next_sibling links; the root is the node whose parent is None.

Source

pub fn node_at(&mut self, offset: usize) -> Result<Option<QueryMatch>, Error>

The deepest node whose span contains byte offset (with offset equal to the source length treated as inside the root) — mouse hit-testing and cursor context. Ok(None) if no node covers the offset; Error::InvalidArgument if offset exceeds the source length.

Source

pub fn ancestors_at(&mut self, offset: usize) -> Result<Vec<QueryMatch>, Error>

The chain of nodes containing byte offset, root-first down to the deepest (the node Editor::node_at returns) — the ancestor path for a breadcrumb or context-scoped edit. Empty if no node covers the offset.

Source

pub fn wrap_range( &mut self, start: usize, end: usize, kind: InlineKind, ) -> Result<Change, Error>

Wrap [start, end) with kind’s delimiters — the unconditional half of the inline toolbar (always adds a mark; *word***word** stacks). Error::UnsupportedFormat if the document’s format can’t spell kind (e.g. a Markdown InlineKind::Mark); Error::InvalidArgument for a bad range; Error::EditConflict if the result doesn’t reparse.

Source

pub fn toggle_inline( &mut self, start: usize, end: usize, kind: InlineKind, ) -> Result<Change, Error>

Toggle kind over [start, end): remove the mark if the range already is a node of kind (its whole span or its rendered interior), else wrap it — a rich editor’s Cmd-B. Same error rules as Editor::wrap_range.

Source

pub fn set_block( &mut self, offset: usize, kind: BlockKind, ) -> Result<Change, Error>

Convert the innermost heading/paragraph covering byte offset to kind, rewriting its leading marker while keeping its inline content (the toolbar’s H1…H6 / Body switch). Djot and Markdown only, else Error::UnsupportedFormat; Error::NotFound if no heading/paragraph covers offset; Error::InvalidArgument for a heading level outside 1–6.

Source

pub fn toggle_block_container( &mut self, start: usize, end: usize, kind: BlockContainerKind, ) -> Result<Change, Error>

Toggle a block container over the blocks [start, end) covers — the toolbar’s Quote / Bulleted list / Numbered list buttons. Djot and Markdown only, else Error::UnsupportedFormat; Error::NotFound if the range covers no block; Error::InvalidArgument for a bad range.

The range widens to whole lines of the blocks it touches (you cannot quote half a paragraph), and the prefix lands at column 0, so a container wraps the outermost structure on those lines.

Whether this adds or removes is decided from the AST — the ancestors of start — not by looking for a > in the source. It removes the container only when the range covers every block that container holds, and then only one level (> > a> a). A partly covered container nests instead, since removing it would drag its uncovered siblings out with it: selecting the first paragraph of > a\n>\n> b\n gives > > a\n>\n> b\n. Toggling one list kind while inside the other converts in place (- a1. a) rather than nesting.

Each covered block becomes one item, so an ordered list numbers a multi-block range 1., 2., 3.… Removing a list inserts a blank line between items that lacked one, keeping them separate blocks (a tight - a\n- b\n stripped bare would be a single two-line paragraph).

Link [start, end) to destination[text](destination). Djot and Markdown only, else Error::UnsupportedFormat; Error::InvalidArgument for a bad range or a destination containing a newline (neither format can carry one, and quietly rewriting the URL would be worse than refusing).

An existing link covering the range has its destination replaced and its text kept, so re-linking fixes a URL instead of nesting [[t](a)](b); to unlink, use Editor::unwrap_node.

A caret in an existing autolink (<https://x.dev>) re-points it the same way, but there is no text to keep — an autolink’s text is its destination — so the node is replaced whole, respelled canonically for the new destination. Only a caret: a selection carries text of its own to link, and one straddling the autolink’s edges has no re-point to mean, so it wraps as usual. A caret inside both an autolink and a link ([<https://x.dev>](d)) re-points the link, whose text is separable from its destination and so survives.

A link with no text — an empty range, or re-pointing an existing [](old) — is spelled canonically for the destination given, never as [](destination): a childless link has nothing to render, so consumers fall back to showing the destination and a caret has nowhere to sit. A destination the format can autolink (an absolute URL or an email, by that format’s own rules) yields <destination>; anything else yields [destination](destination), the destination doubling as the text so it stays visible and editable. Which destinations autolink is not the caller’s to guess — <foo> is raw HTML in Markdown, a relative path goes literal in both, and the formats disagree (<mailto:a@b.dev> is a url in Markdown, an email in Djot), so each is asked its own parser.

The destination is escaped for the format, so a ) or a space in it cannot break the markup — and the two formats genuinely differ: Markdown ends a destination at the first space ([t](a b) is not a link at all) so whitespace moves it into the <…> form, while Djot takes spaces literally and would read <a b> as the URL itself.

Trait Implementations§

Source§

impl Debug for Editor

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Drop for Editor

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.