# incremental

URL: /patitas/api/incremental/
Section: api
Description: Incremental re-parsing for Patitas ASTs.

When a user edits one paragraph, only that paragraph's region needs
re-parsing.  This module accepts a previous Document AST plus the new
source text and an edit range, then:

1. Identifies which top-level blocks overlap the edit range.
2. Determines the minimal region of source that must be re-parsed.
3. Re-parses only that region.
4. Splices the new blocks into the existing AST.

The result is a new Document that is structurally and offset-wise
equivalent to a full re-parse, computed in O(change) rather than
O(document).  Re-parsed blocks have their full node tree (the top-level
block ``location`` *and* every descendant inline/nested ``location``,
plus ``FencedCode`` ``source_start``/``source_end``) shifted to absolute
offsets, and blocks after the edit are shifted by the edit delta.  This
keeps source maps, LSP positions, and diagnostics accurate without a
full re-parse.

Custom registries:
    ``parse_incremental`` accepts ``directive_registry`` so the re-parsed
    region honors custom directives exactly like ``parse()``.  Inline
    roles are resolved at *render* time, not parse time, so there is no
    parse-time role registry -- this mirrors ``parse()``'s own signature.

Fallback:
    If the edit cannot be handled incrementally (e.g., the region
    detection fails or the partial re-parse raises), falls back to a
    full re-parse.  This guarantees correctness at all times.  If the
    fallback full re-parse *also* fails, the error is propagated (with
    any original partial-parse error chained as ``__cause__``) rather
    than silently swallowed.

Thread Safety:
    ``parse_incremental`` is a pure function -- safe to call from any thread.

---

> For a complete page index, fetch /patitas/llms.txt.

Open LLM text
(/patitas/api/incremental/index.txt)

Share with AI

Ask Claude
(https://claude.ai/new?q=Please%20help%20me%20understand%20this%20documentation%3A%20%2Fpatitas%2Fapi%2Fincremental%2Findex.txt)

Ask ChatGPT
(https://chatgpt.com/?q=Please%20help%20me%20understand%20this%20documentation%3A%20%2Fpatitas%2Fapi%2Fincremental%2Findex.txt)

Ask Gemini
(https://gemini.google.com/app?q=Please%20help%20me%20understand%20this%20documentation%3A%20%2Fpatitas%2Fapi%2Fincremental%2Findex.txt)

Ask Copilot
(https://copilot.microsoft.com/?q=Please%20help%20me%20understand%20this%20documentation%3A%20%2Fpatitas%2Fapi%2Fincremental%2Findex.txt)

Module

#
`incremental`

Incremental re-parsing for Patitas ASTs.

When a user edits one paragraph, only that paragraph's region needs
re-parsing. This module accepts a previous Document AST plus the new
source text and an edit range, then:

- Identifies which top-level blocks overlap the edit range.

- Determines the minimal region of source that must be re-parsed.

- Re-parses only that region.

- Splices the new blocks into the existing AST.

The result is a new Document that is structurally and offset-wise
equivalent to a full re-parse, computed in O(change) rather than
O(document). Re-parsed blocks have their full node tree (the top-level
block`location` and every descendant inline/nested `location`,
plus`FencedCode` `source_start`/`source_end`) shifted to absolute
offsets, and blocks after the edit are shifted by the edit delta. This
keeps source maps, LSP positions, and diagnostics accurate without a
full re-parse.

Custom registries:

```
``parse_incremental`` accepts ``directive_registry`` so the re-parsed
region honors custom directives exactly like ``parse()``.  Inline
roles are resolved at *render* time, not parse time, so there is no
parse-time role registry -- this mirrors ``parse()``'s own signature.
```

Fallback:

```
If the edit cannot be handled incrementally (e.g., the region
detection fails or the partial re-parse raises), falls back to a
full re-parse.  This guarantees correctness at all times.  If the
fallback full re-parse *also* fails, the error is propagated (with
any original partial-parse error chained as ``__cause__``) rather
than silently swallowed.
```

Thread Safety:

```
``parse_incremental`` is a pure function -- safe to call from any thread.
```

8Functions

## Functions

`parse_incremental`

7

`Document (/patitas/api/nodes/#Document)`

▼

Parse only the edited region and splice into the existing AST.

`def parse_incremental(new_source: str, previous: Document, edit_start: int, edit_end: int, new_length: int, *, source_file: str | None = None, directive_registry: DirectiveRegistry | None = None) -> Document`

##### Parameters

Name
Type
Description

`new_source`
`str`

The complete new source text (after the edit).

`previous`
`Document (/patitas/api/nodes/#Document)`

The Document AST from before the edit.

`edit_start`
`int`

Offset in the OLD source where the edit begins.

`edit_end`
`int`

Offset in the OLD source where the edit ends (i.e., the old text from edit_start..edit_end was replaced).

`new_length`
`int`

Length of the replacement text in the new source. The replaced region in new_source is`new_source[edit_start : edit_start + new_length]`.

`source_file`
`str | None`

Optional source file path for location tracking.

Default:`None`

`directive_registry`
`DirectiveRegistry (/patitas/api/directives/registry/#DirectiveRegistry) | None`

Custom directive registry (uses defaults if None), matching :func:`patitas.parse`. The re-parsed region honors it exactly like a full parse.

Default:`None`

##### Returns

`Document (/patitas/api/nodes/#Document)`

`_find_affected_range`

3

`tuple[int | None, int | …`

▼

Find the range of block indices affected by an edit.

A block is affected if it…

`def _find_affected_range(blocks: Sequence[Block], edit_start: int, edit_end: int) -> tuple[int | None, int | None]`

Find the range of block indices affected by an edit.

A block is affected if its source range overlaps [edit_start, edit_end).
If no blocks overlap, expands to the surrounding blocks to handle
boundary changes (e.g., merging two paragraphs).

Returns (first_affected, last_affected) or (None, None).

##### Parameters

Name
Type
Description

`blocks`
`Sequence[Block]`

`edit_start`
`int`

`edit_end`
`int`

##### Returns

`tuple[int | None, int | None]`

`_parse_region`

4

`tuple[tuple[Block, ...] …`

▼

Parse a source region and adjust locations to absolute offsets.

Returns ``(blo…

`def _parse_region(source: str, offset: int, source_file: str | None, directive_registry: DirectiveRegistry | None) -> tuple[tuple[Block, ...] | None, Exception | None]`

Parse a source region and adjust locations to absolute offsets.

Returns`(blocks, None)` on success or `(None, error)`when the
partial parse raises. Returning the captured exception (instead of
swallowing it) lets the caller chain it onto a fallback failure so
errors are never fully opaque.

##### Parameters

Name
Type
Description

`source`
`str`

`offset`
`int`

`source_file`
`str | None`

`directive_registry`
`DirectiveRegistry (/patitas/api/directives/registry/#DirectiveRegistry) | None`

##### Returns

`tuple[tuple[Block, ...] | None, Exception | None]`

`_shift_node`

2

`N`

▼

Return a copy of ``node`` with its entire subtree shifted by ``delta``.

Shifts…

`def _shift_node(node: N, delta: int) -> N`

Return a copy of `node` (/patitas/api/parsing/inline/tokens/#NodeToken) with its entire subtree shifted by `delta`.

Shifts `location` (/patitas/api/location/) (offset/end_offset) on every node, `FencedCode` (/patitas/api/nodes/#FencedCode)
`source_start` (/patitas/api/nodes/#FencedCode)/`source_end` (/patitas/api/nodes/#FencedCode) (which index into source text), and
recurses into every child node held in dataclass fields (single nodes
and tuples of nodes, including tuples-of-tuples such as `Table` (/patitas/api/nodes/#Table) rows).

Uses`dataclasses.replace`for clean frozen-dataclass copying.

##### Parameters

Name
Type
Description

`node`
`N`

`delta`
`int`

##### Returns

`N`

`_shift_field_value`

2

`object`

▼

Recursively shift any nodes reachable through a dataclass field value.

Handles…

`def _shift_field_value(value: object, delta: int) -> object`

Recursively shift any nodes reachable through a dataclass field value.

Handles single nodes and (possibly nested) tuples of nodes. Non-node
values (strings, ints, options objects, ...) are returned unchanged so
the caller can skip them via identity comparison.

##### Parameters

Name
Type
Description

`value`
`object`

`delta`
`int`

##### Returns

`object`

`_shift_block_offset`

2

`Block`

▼

Shift a block and its entire subtree by ``delta``.

Thin wrapper over `_shift_n…

`def _shift_block_offset(block: Block, delta: int) -> Block`

Shift a block and its entire subtree by`delta`.

Thin wrapper over `_shift_node` (/patitas/api/incremental/#_shift_node)() kept for the call-site/test
surface. Unlike earlier versions, this adjusts all descendant
locations (inline and nested), not just the top-level block location,
so incremental results match a full parse.

##### Parameters

Name
Type
Description

`block`
`Block`

`delta`
`int`

##### Returns

`Block`

`_adjust_offsets`

2

`tuple[Block, ...]`

▼

Shift location offsets of all blocks (and their subtrees) by delta.

`def _adjust_offsets(blocks: Sequence[Block], delta: int) -> tuple[Block, ...]`

##### Parameters

Name
Type
Description

`blocks`
`Sequence[Block]`

`delta`
`int`

##### Returns

`tuple[Block, ...]`

`_full_parse`

4

`Document (/patitas/api/nodes/#Document)`

▼

Fall back to a full re-parse.

If the full re-parse itself fails, the error is …

`def _full_parse(source: str, source_file: str | None, directive_registry: DirectiveRegistry | None = None, cause: Exception | None = None) -> Document`

Fall back to a full re-parse.

If the full re-parse itself fails, the error is not swallowed: it
propagates, with any earlier partial-parse error chained as
`__cause__`so the recoverable-vs-fatal distinction stays visible.

##### Parameters

Name
Type
Description

`source`
`str`

`source_file`
`str | None`

`directive_registry`
`DirectiveRegistry (/patitas/api/directives/registry/#DirectiveRegistry) | None`

Default:`None`

`cause`
`Exception | None`

Default:`None`

##### Returns

`Document (/patitas/api/nodes/#Document)`
