simvx.core.scene_io.edits

Prefix-preserving editing primitives for parso trees.

parso stores leading whitespace and comments on each leaf’s prefix string; the prefix of a non-leaf node is the prefix of its first leaf. Edits that splice nodes in or out of the tree must transfer prefixes carefully so that trailing comments stay glued to the right line, blank-line spacing does not drift, and indent depth is preserved.

The primitives here operate directly on the parso tree (mutating parent.children lists). They are deliberately small: composition lives in higher tiers (scene_file, scene_module).

Module Contents

Functions

replace_node

Replace old with new in their shared parent’s children list.

insert_after

Insert new_node immediately after sibling in their shared parent.

insert_before

Insert new_node immediately before sibling in their shared parent.

remove_node

Remove node from its parent’s children list.

remove_statement

Remove one statement, which a semicolon may have joined to others on its line.

line_statements

The statements written on one line, which a semicolon can join several of.

get_call_kwarg

Return the value subtree for kwarg name in a call, or None.

set_call_kwarg

Set kwarg name on a call expression.

set_call_positional

Replace the index-th argument a call passes by position.

iter_call_items

Yield everything a call passes, named or not, in the order it is written.

is_named_argument

Is this call item written as name=value?

relayout_statement

Lay the statement containing node out to fit limit columns.

enclosing_statement

The simple_stmt node sits in, or None when it sits in none.

API

simvx.core.scene_io.edits.replace_node(old: parso.tree.NodeOrLeaf, new: parso.tree.NodeOrLeaf, *, preserve_prefix: bool = True) None[source]

Replace old with new in their shared parent’s children list.

With preserve_prefix=True (default), the leading whitespace + comments of old’s first leaf are transferred onto new’s first leaf so that same-line trailing comments on the previous statement, leading blank lines, and decorators above remain attached.

simvx.core.scene_io.edits.insert_after(sibling: parso.tree.NodeOrLeaf, new_node: parso.tree.NodeOrLeaf, *, copy_indent: bool = True) None[source]

Insert new_node immediately after sibling in their shared parent.

With copy_indent=True (default), the indent run of sibling’s first leaf prefix is copied onto new_node so the new statement sits at the same column. new_node’s prefix is overwritten with "\n<indent>": callers wanting custom prefixes should pass copy_indent=False and populate the prefix themselves.

simvx.core.scene_io.edits.insert_before(sibling: parso.tree.NodeOrLeaf, new_node: parso.tree.NodeOrLeaf, *, copy_indent: bool = True) None[source]

Insert new_node immediately before sibling in their shared parent.

With copy_indent=True (default), new_node inherits sibling’s full prefix (so any leading comments/blank lines stay above the inserted node) and sibling’s prefix is reset to "\n<indent>" so it sits at the same column it did originally.

Note: this transfers comments above sibling to the inserted node. To keep them attached to sibling, pass copy_indent=False and manage prefixes manually.

simvx.core.scene_io.edits.remove_node(node: parso.tree.NodeOrLeaf, *, collapse_blank_lines: bool = True) None[source]

Remove node from its parent’s children list.

With collapse_blank_lines=True (default), surplus blank lines in node’s prefix are collapsed onto the next sibling so deleting statements in sequence does not balloon vertical spacing. The collapse rule is: keep at most one blank line of separation; the indent run on the final line is preserved verbatim.

Same-line trailing comments stored in node’s prefix (which originate on the previous sibling: see module docstring) are re-attached to the next sibling so they stay on their original line.

simvx.core.scene_io.edits.remove_statement(stmt: parso.tree.NodeOrLeaf, *, collapse_blank_lines: bool = True) None[source]

Remove one statement, which a semicolon may have joined to others on its line.

stmt is a statement as parso hands one out: the sole statement of an ordinary line, or one of the several a semicolon-joined line holds. A line down to its last statement goes whole, newline and all (:func:remove_node, whose prefix handling this then inherits); otherwise only the statement goes, together with one of the semicolons beside it, and the line keeps its indent whichever end it lost.

simvx.core.scene_io.edits.line_statements(line: parso.tree.NodeOrLeaf) list[parso.tree.NodeOrLeaf][source]

The statements written on one line, which a semicolon can join several of.

parso folds self.name = "Root"; self.add_child(Hero()) into a single simple_stmt whose children are the two statements, the semicolon between them and the newline, so a reader that looks only at the first child sees half the line. Anything that is not a simple_stmt is already a single statement and comes back on its own.

simvx.core.scene_io.edits.get_call_kwarg(call_node: parso.tree.NodeOrLeaf, name: str) parso.tree.NodeOrLeaf | None[source]

Return the value subtree for kwarg name in a call, or None.

call_node may be either the trailer (the (...) after a name) or the enclosing atom_expr: both forms are accepted.

simvx.core.scene_io.edits.set_call_kwarg(call_node: parso.tree.NodeOrLeaf, name: str, value_expr: str) None[source]

Set kwarg name on a call expression.

Overwrites if name already exists (preserves the order of other args); appends if not. value_expr is parsed with :func:parse_snippet, so callers pass real Python source (e.g. "Vec2(0, 0)" or '"hello"').

A trailing comma in the original arglist is preserved when appending.

simvx.core.scene_io.edits.set_call_positional(call_node: parso.tree.NodeOrLeaf, index: int, value_expr: str) None[source]

Replace the index-th argument a call passes by position.

The counterpart of :func:set_call_kwarg for the arguments a call does not name. Which parameter a position fills is a fact about the callable rather than about the text, so establishing that index is the caller’s business; all that happens here is that the expression standing there is swapped for value_expr and the statement laid out again.

Raises :class:ValueError when the call passes no such position.

simvx.core.scene_io.edits.iter_call_items(trailer: parso.tree.BaseNode) collections.abc.Iterator[parso.tree.NodeOrLeaf][source]

Yield everything a call passes, named or not, in the order it is written.

Func:

_iter_arguments yields the argument nodes, which is to say the named ones and the unpackings; this yields those and the bare expressions a call passes by position, so a caller counting positions counts them against the same list the interpreter would.

simvx.core.scene_io.edits.is_named_argument(item: parso.tree.NodeOrLeaf) bool[source]

Is this call item written as name=value?

False for everything else :func:iter_call_items yields: an expression passed by position, and an unpacking (*args, **kwargs), which parso also builds an argument node for but which names no parameter.

simvx.core.scene_io.edits.relayout_statement(node: parso.tree.NodeOrLeaf, *, limit: int = LINE_LIMIT) None[source]

Lay the statement containing node out to fit limit columns.

Called after a call in an existing file has been edited, so the statement the save just rewrote is left in the shape a formatter would leave it: on one line while it fits, and one keyword argument per line once it does not (:mod:~simvx.core.scene_io.layout decides, from the same rule the emitter writes new files by). A statement nothing wrote to is never reached, so a save with no edits in it changes no line’s shape.

The author’s own expressions survive it: nothing is reparsed, reprinted or rewritten, and every value is the node it always was, down to its quotes and its digits. What moves is the whitespace the layout owns and only that – after an opening bracket, around a comma, before a closing bracket – and it moves at every depth, so a value the author spaced out inside its own brackets comes back spaced the way a formatter spaces it.

Three statements are left exactly as found, because relaying them out would destroy something this layer cannot put back: one carrying a comment, which belongs to the line it was written on; one holding a value spanning lines of its own (a triple-quoted string, a continuation); and one sharing its line with other statements behind semicolons, whose width is not one statement’s to measure.

simvx.core.scene_io.edits.enclosing_statement(node: parso.tree.NodeOrLeaf) parso.tree.BaseNode | None[source]

The simple_stmt node sits in, or None when it sits in none.

Worth asking for before an edit that detaches node from the tree: the answer is what :func:relayout_statement then has to be given, since the detached node no longer leads anywhere.