-
Notifications
You must be signed in to change notification settings - Fork 9
Add Turtle support #998
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
TomNaessens
wants to merge
13
commits into
main
Choose a base branch
from
poc-turtle
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Add Turtle support #998
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
53f3241
Turtle POC
TomNaessens 273f7a8
Add debugging functionality for Turtle
TomNaessens c0ccbe5
Claude PR review
TomNaessens e964bad
Emit incremental turtle snapshots on sleep and fix stacking
TomNaessens cf0a53d
Add Turtle tests
TomNaessens 8704719
Tag turtle snapshots with an explicit OutputType
TomNaessens 4505030
Scale turtle SVG to fit its tab with a framing border
TomNaessens 279d18c
Give output panel a surface background and frame turtle canvas
TomNaessens dfbc507
Align output tabs with editor tabs vertically
TomNaessens e00a021
Show blank canvas in Turtle tab before first snapshot
TomNaessens b78f1de
Simplify turtle plumbing
TomNaessens a36b7d1
Let svg-turtle ImportError surface
TomNaessens 683ef1f
Locate turtle.py via sysconfig to avoid importing tkinter
TomNaessens File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| # Astroid brain plugin for Python's turtle module. | ||
| # turtle.py creates module-level functions dynamically via exec() in | ||
| # _make_global_funcs(), so astroid can't see them statically. | ||
| # This plugin injects stubs for those functions so pylint can check | ||
| # valid vs invalid member access (e.g. turtle.forward is OK, | ||
| # turtle.test is not). | ||
|
|
||
| import astroid | ||
| from astroid import MANAGER | ||
|
|
||
| # These lists mirror CPython 3.13's turtle._tg_turtle_functions and | ||
| # turtle._tg_screen_functions — the functions generated at module level. | ||
| _TG_TURTLE_FUNCTIONS = [ | ||
| 'back', 'backward', 'begin_fill', 'begin_poly', 'bk', | ||
| 'circle', 'clear', 'clearstamp', 'clearstamps', 'clone', 'color', | ||
| 'degrees', 'distance', 'dot', 'down', 'end_fill', 'end_poly', 'fd', | ||
| 'fillcolor', 'filling', 'forward', 'get_poly', 'getpen', 'getscreen', | ||
| 'getturtle', 'goto', 'heading', 'hideturtle', 'home', 'ht', 'isdown', | ||
| 'isvisible', 'left', 'lt', 'onclick', 'ondrag', 'onrelease', 'pd', | ||
| 'pen', 'pencolor', 'pendown', 'pensize', 'penup', 'pos', 'position', | ||
| 'pu', 'radians', 'right', 'reset', 'resizemode', 'rt', 'seth', | ||
| 'setheading', 'setpos', 'setposition', 'settiltangle', | ||
| 'setundobuffersize', 'setx', 'sety', 'shape', 'shapesize', | ||
| 'shapetransform', 'shearfactor', 'showturtle', 'speed', 'st', 'stamp', | ||
| 'teleport', 'tilt', 'tiltangle', 'towards', 'turtlesize', 'undo', | ||
| 'undobuffercount', 'up', 'width', 'write', 'xcor', 'ycor', | ||
| ] | ||
|
|
||
| _TG_SCREEN_FUNCTIONS = [ | ||
| 'addshape', 'bgcolor', 'bgpic', 'bye', 'clearscreen', 'colormode', | ||
| 'delay', 'exitonclick', 'getcanvas', 'getshapes', 'listen', | ||
| 'mainloop', 'mode', 'numinput', 'onkey', 'onkeypress', 'onkeyrelease', | ||
| 'onscreenclick', 'ontimer', 'register_shape', 'resetscreen', | ||
| 'screensize', 'setup', 'setworldcoordinates', 'textinput', 'title', | ||
| 'tracer', 'turtles', 'update', 'window_height', 'window_width', 'done', | ||
| ] | ||
|
|
||
| _ALL_FUNCTIONS = _TG_TURTLE_FUNCTIONS + _TG_SCREEN_FUNCTIONS | ||
|
|
||
|
|
||
| def _turtle_transform(module): | ||
| """Add stub definitions for turtle's dynamically generated functions.""" | ||
| code = "\n".join(f"def {name}(*args, **kwargs): ..." for name in _ALL_FUNCTIONS) | ||
| fake = astroid.parse(code) | ||
| for node in fake.body: | ||
| module.body.append(node) | ||
| module.locals[node.name] = [node] | ||
|
|
||
|
|
||
| MANAGER.register_transform( | ||
| astroid.Module, | ||
| _turtle_transform, | ||
| lambda node: node.name == 'turtle', | ||
| ) | ||
|
|
||
|
|
||
| def register(linter): | ||
| """Required by pylint plugin interface (no checkers to register).""" | ||
| pass | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| import sys | ||
|
|
||
|
|
||
| class TurtleImportHook: | ||
| """Import hook that lazily sets up SVG-based turtle graphics. | ||
|
|
||
| Installed in sys.meta_path. When user code does `import turtle`, | ||
| this hook intercepts it, imports svg-turtle, creates a shared | ||
| canvas/screen, and patches the turtle module to render SVG. | ||
|
|
||
| If user code never imports turtle, no setup occurs. | ||
| """ | ||
|
|
||
| def __init__(self): | ||
| self.papyros = None | ||
| self.render = None | ||
| self._loading = False | ||
| self._turtle_module = None | ||
|
|
||
| def find_spec(self, name, path, target=None): | ||
| if name == 'turtle' and not self._loading: | ||
| import importlib.util | ||
| return importlib.util.spec_from_loader(name, self) | ||
| return None | ||
|
|
||
| def create_module(self, spec): | ||
| return self._setup_turtle() | ||
|
|
||
| def exec_module(self, module): | ||
| pass | ||
|
|
||
| def _setup_turtle(self): | ||
| self._loading = True | ||
| try: | ||
| from svg_turtle import SvgTurtle | ||
| from svg_turtle.canvas import Canvas | ||
|
|
||
| if self._turtle_module is None: | ||
| # svg_turtle stubs tkinter as a side effect of import; must precede `import turtle` | ||
| import turtle | ||
| self._turtle_module = sys.modules['turtle'] | ||
|
|
||
| turtle_mod = self._turtle_module | ||
| sys.modules['turtle'] = turtle_mod | ||
|
|
||
| canvas = Canvas(400, 400) | ||
| screen = SvgTurtle._Screen(canvas) | ||
| screen.cv.config(bg="") | ||
|
|
||
| class PapyrosTurtle(SvgTurtle): | ||
| def __init__(self): | ||
| super().__init__(screen=screen) | ||
|
|
||
| SvgTurtle._screen = screen | ||
| SvgTurtle._pen = PapyrosTurtle() | ||
|
|
||
| def render(): | ||
| self.papyros._emit_turtle_snapshot() | ||
|
|
||
| turtle_mod.Turtle = PapyrosTurtle | ||
| turtle_mod.done = render | ||
| turtle_mod.mainloop = render | ||
| turtle_mod.exitonclick = render | ||
| turtle_mod.bye = render | ||
|
|
||
| self.render = render | ||
| return turtle_mod | ||
| finally: | ||
| self._loading = False |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,7 @@ export enum BackendEventType { | |
| FrameChange = "frame-change", | ||
| Stop = "stop", | ||
| Files = "files", | ||
| Turtle = "turtle", | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note: added this type so we can act on turtle images vs. regular images. Previously in this branch, we did comparison based on the content type (img -> regular image, svg -> turtle images), but I found that too brittle. |
||
| } | ||
|
|
||
| /** | ||
|
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: note sure if this is the cleanest thing to teach pylint about the Turtle methods 🤔