fix: edge cases for parsing pasted notebooks#10033
Conversation
Pasting a marimo app into a cell used line-based heuristics to strip the
file boilerplate, which mangled several valid constructs. This PR
replaces the line-based heuristics with lezer's Python parser, adding
support for setup cells and app.function's in the process.
Fixes the following, each covered by a test:
- Decorators inside a cell (`@functools.cache`, `@property`, ...) were
dropped and could split the cell in two.
- `async def` cells with multi-line args leaked the argument list into
the body.
- Multi-line returns using `[` or `{` (rather than `(`) leaked their
contents into the cell.
- Cross-cell parser state could corrupt a following cell.
- `with app.setup(...)` blocks and top-level `@app.function`
definitions were not extracted at all.
Because the parser models block structure directly, nested-function
returns, decorators, `async def`, and multi-line returns of any bracket
type are all handled structurally rather than by ad-hoc string matching.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
for more information, see https://pre-commit.ci
There was a problem hiding this comment.
Pull request overview
This PR improves the “paste a marimo app into a cell” workflow by replacing fragile line-based parsing with Lezer’s Python parser, enabling structurally-correct extraction of pasted marimo source (including setup blocks and @app.function).
Changes:
- Replace heuristic cell extraction with syntax-tree-based parsing via
@lezer/python, adding support forwith app.setup...and@app.function. - Route extracted setup code into the notebook’s dedicated setup cell (create it or append to it).
- Expand test coverage for previously-mangled edge cases (nested decorators, async defs, multiline returns, setup/function extraction).
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
frontend/src/core/codemirror/misc/paste.ts |
Switches pasted-app parsing to Lezer AST; extracts setup blocks, cells, and @app.function definitions. |
frontend/src/core/codemirror/misc/__tests__/paste.test.ts |
Adds regression tests for parsing edge cases and setup-cell routing behavior. |
frontend/src/core/codemirror/cells/state.ts |
Extends CodemirrorCellActions with addOrAppendSetupCell. |
frontend/src/components/editor/cell/code/cell-editor.tsx |
Implements addOrAppendSetupCell by creating/appending to the setup cell and syncing editor/state. |
There was a problem hiding this comment.
1 issue found and verified against the latest diff
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="frontend/src/core/codemirror/misc/paste.ts">
<violation number="1" location="frontend/src/core/codemirror/misc/paste.ts:123">
P2: `return false` is unconditional here, so *every* `WithStatement` (not just `app.setup`) stops tree descent into its children. If a decorated `@app.cell` or `@app.function` is nested inside a non-setup `with` block, it will be silently missed. Consider only returning `false` when the node is actually an `app.setup` block, and continuing traversal otherwise. Also, the `.includes("app.setup")` check could match unrelated identifiers (e.g. `my_app.setup_data`) — use a word-boundary–aware check consistent with `looksLikeMarimoApp`'s `\bapp\.setup\b` pattern.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| setupBlocks.push(setup); | ||
| } | ||
| } | ||
| return false; |
There was a problem hiding this comment.
P2: return false is unconditional here, so every WithStatement (not just app.setup) stops tree descent into its children. If a decorated @app.cell or @app.function is nested inside a non-setup with block, it will be silently missed. Consider only returning false when the node is actually an app.setup block, and continuing traversal otherwise. Also, the .includes("app.setup") check could match unrelated identifiers (e.g. my_app.setup_data) — use a word-boundary–aware check consistent with looksLikeMarimoApp's \bapp\.setup\b pattern.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/core/codemirror/misc/paste.ts, line 123:
<comment>`return false` is unconditional here, so *every* `WithStatement` (not just `app.setup`) stops tree descent into its children. If a decorated `@app.cell` or `@app.function` is nested inside a non-setup `with` block, it will be silently missed. Consider only returning `false` when the node is actually an `app.setup` block, and continuing traversal otherwise. Also, the `.includes("app.setup")` check could match unrelated identifiers (e.g. `my_app.setup_data`) — use a word-boundary–aware check consistent with `looksLikeMarimoApp`'s `\bapp\.setup\b` pattern.</comment>
<file context>
@@ -1,159 +1,175 @@
+ setupBlocks.push(setup);
+ }
+ }
+ return false;
}
- skipLines--;
</file context>
|
🚀 Development release published. You may be able to view the changes at https://marimo.app?v=0.23.13-dev1 |
This PR replaces the line-based heuristics with lezer's Python parser, adding support for setup cells and app.function's in the process.
Previously, pasting a marimo notebook into a cell used line-based heuristics to strip the file boilerplate, which mangled several valid constructs. Because the parser models block structure directly, nested-function returns, decorators,
async def, and multi-line returns of any bracket type are all handled structurally rather than by ad-hoc string matching.This feature remains not very discoverable, but I do really like it ...