pub fn format_node_range(
node: &SyntaxNode,
range: TextRange,
) -> Option<(TextRange, String)>Expand description
Format the subset of node’s top-level children that intersect
range, returning the snapped byte range and the canonical-form
replacement text.
This is the building block for the LSP textDocument/rangeFormatting
provider: the client sends a Range, the server snaps it up to
the smallest set of top-level structural nodes (directives or
standalone comments) that intersect the selection, formats those
nodes the same way format_node formats the whole file, and
returns a single TextEdit replacing the snapped range. The
alternative — formatting a substring of the source — would have
to either invent a partial canonical form (creating a second
truth alongside the whole-file canonical form, the failure mode
that bit #1252) or refuse to format anything that crosses a
structural boundary. Snapping up to top-level boundaries is the
only choice that lets the same canonical-form rules apply.
Frame. range is in the CST byte frame — the same frame
the syntax node’s TextRanges use. The LSP handler is
responsible for shifting bom_offset at the input/output
boundary (mirrors the super::super::SyntaxNode /
selection_range handler convention; see
ParseResult::syntax_root rustdoc for the rationale).
Behavior.
- If
rangeintersects no top-level Directive or standalone COMMENT/SHEBANG/EMACS token, returnsNone. The LSP handler surfacesNonedirectly (serialized asnullper LSP, not as[]); the client treats it as “nothing to format”. - If the computed snap range would cover any top-level
ERROR_NODEbyte, returnsNone. Range formatting refuses to delete user content the parser couldn’t classify. This diverges fromformat_node, which silently dropsERROR_NODEchildren on the whole-file path; the rationale is the per-handler asymmetry the LSP exposes — the user pressing “Format Selection” expects either a clean reformat or a no-op, never a silent partial delete of an in-progress directive. Tooling that genuinely wants to drop broken regions can still callformat_nodeon the same node. - Otherwise returns
Some((snap, text))wheresnapis the union of the included children’s text ranges (so it begins at the first included child’s start and ends at the last included child’s end, including each child’s leading-trivia prefix per the phase-2.0 Directive-Terminator Rule) andtextis the canonical-form replacement. - Cursor-only selection (
range.is_empty()): the child at the cursor is included if the cursor is strictly inside it OR is exactly at the child’s start. Boundary at the child’s end belongs to the next child, not the previous one — matches the standard “end-of-line cursor is start-of-next-line” convention.
Posting alignment. The pre-pass uses the FULL SourceFile, not
the selected subset. A selection that formats one transaction
in a file with many other transactions inherits the file’s
alignment columns, so the formatted output stays visually
aligned with un-formatted postings elsewhere. The opposite
policy (per-selection alignment) would create a jarring
visual jump every time the user re-formats a sub-range.
Round-trip invariant. For any range that contains every
top-level child, the returned text equals the result of
format_node on the same node. Pinned by
format_node_range_full_range_matches_format_node in this
file’s test module.
§Panics
Panics if node’s kind is not SOURCE_FILE — same precondition
as format_node.