meltygui 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- meltygui/__init__.py +107 -0
- meltygui/accounts/__init__.py +0 -0
- meltygui/accounts/internet_accounts.py +1355 -0
- meltygui/chat/__init__.py +91 -0
- meltygui/chat/activity.py +75 -0
- meltygui/chat/backends.py +36 -0
- meltygui/chat/chat_interface.py +732 -0
- meltygui/chat/chat_proxy.py +352 -0
- meltygui/chat/codex_proxy.py +592 -0
- meltygui/chat/codex_settings.py +100 -0
- meltygui/chat/codex_transport.py +60 -0
- meltygui/chat/command_parser.py +204 -0
- meltygui/chat/images.py +227 -0
- meltygui/chat/messages.py +417 -0
- meltygui/chat/metadata.py +139 -0
- meltygui/chat/writer_locks.py +64 -0
- meltygui/code/__init__.py +0 -0
- meltygui/code/basic_converters.py +533 -0
- meltygui/code/chain_converters.py +2111 -0
- meltygui/code/code_checks.py +2209 -0
- meltygui/code/core_syntax.py +1430 -0
- meltygui/code/file_converters.py +1933 -0
- meltygui/code/fileref.py +702 -0
- meltygui/code/hotswap_guard.py +144 -0
- meltygui/code/libcst_conversion.py +9724 -0
- meltygui/code/live_instrument.py +392 -0
- meltygui/code/live_view.py +2490 -0
- meltygui/code/melty_scan.py +2684 -0
- meltygui/code/new_codecs.py +1255 -0
- meltygui/code/new_converters.py +3017 -0
- meltygui/code/project_code.py +278 -0
- meltygui/code/source_context.py +63 -0
- meltygui/code/symbol_roster.py +1588 -0
- meltygui/code/syntax_check.py +34 -0
- meltygui/code/syntax_check_worker.py +114 -0
- meltygui/completion/__init__.py +0 -0
- meltygui/completion/fim.py +1232 -0
- meltygui/completion/fim_context.py +481 -0
- meltygui/completion/providers/__init__.py +0 -0
- meltygui/completion/providers/anthropic_oauth.py +446 -0
- meltygui/completion/providers/anthropic_requests.py +69 -0
- meltygui/completion/providers/claude.py +203 -0
- meltygui/completion/providers/claude_usage.py +499 -0
- meltygui/completion/providers/codex_accounts.py +180 -0
- meltygui/completion/providers/copilot.py +617 -0
- meltygui/completion/providers/oauth_popup.py +220 -0
- meltygui/completion/providers/ollama.py +320 -0
- meltygui/completion/providers/profiles.py +23 -0
- meltygui/core/README.md +88 -0
- meltygui/core/__init__.py +1 -0
- meltygui/core/automation/__init__.py +1 -0
- meltygui/core/automation/action_core.py +153 -0
- meltygui/core/automation/collection_action.py +45 -0
- meltygui/core/automation/mcp_eval.py +167 -0
- meltygui/core/automation/mcp_hotswap.py +107 -0
- meltygui/core/automation/mcp_query.py +552 -0
- meltygui/core/automation/mcp_server.py +657 -0
- meltygui/core/automation/orchestration_core.py +2636 -0
- meltygui/core/automation/query_core.py +124 -0
- meltygui/core/automation/search_core.py +26 -0
- meltygui/core/automation/selector_core.py +340 -0
- meltygui/core/automation/value_core.py +1752 -0
- meltygui/core/cache/__init__.py +1 -0
- meltygui/core/cache/cache_diagnostics.py +0 -0
- meltygui/core/cache/invalidation_decoration.py +153 -0
- meltygui/core/cache/invalidation_tracker.py +43 -0
- meltygui/core/cache/tile_cache.py +5968 -0
- meltygui/core/conversion/__init__.py +1 -0
- meltygui/core/conversion/bubbling.py +599 -0
- meltygui/core/conversion/cache_tree.py +188 -0
- meltygui/core/conversion/chain.py +113 -0
- meltygui/core/conversion/converter_register.py +145 -0
- meltygui/core/conversion/data_decoration.py +67 -0
- meltygui/core/conversion/dict_conversion.py +1815 -0
- meltygui/core/conversion/dict_conversion_util.py +177 -0
- meltygui/core/conversion/dynamic_obj.py +89 -0
- meltygui/core/conversion/graph_compare.py +183 -0
- meltygui/core/conversion/load_save_v2.py +1032 -0
- meltygui/core/conversion/missing_saved_class.py +39 -0
- meltygui/core/conversion/path_finder.py +606 -0
- meltygui/core/conversion/render_host.py +1083 -0
- meltygui/core/core_render.py +6537 -0
- meltygui/core/definition_hotswap.py +231 -0
- meltygui/core/diagnostics/__init__.py +1 -0
- meltygui/core/diagnostics/attribute_churn.py +38 -0
- meltygui/core/diagnostics/fps_counter.py +39 -0
- meltygui/core/diagnostics/gpu_frame_timer.py +103 -0
- meltygui/core/diagnostics/inspection_core.py +169 -0
- meltygui/core/diagnostics/monitor_core.py +105 -0
- meltygui/core/diagnostics/notifications.py +706 -0
- meltygui/core/diagnostics/perf_trace.py +281 -0
- meltygui/core/diagnostics/profile_decoration.py +88 -0
- meltygui/core/diagnostics/resize_trace.py +62 -0
- meltygui/core/diagnostics/screenshot_core.py +247 -0
- meltygui/core/diagnostics/session_status.py +98 -0
- meltygui/core/diagnostics/trace_core.py +445 -0
- meltygui/core/files/__init__.py +1 -0
- meltygui/core/files/file_core.py +208 -0
- meltygui/core/files/file_explorer_core.py +104 -0
- meltygui/core/files/file_tree_core.py +198 -0
- meltygui/core/files/file_watch_core.py +43 -0
- meltygui/core/files/import_graph_core.py +43 -0
- meltygui/core/files/metadata_core.py +51 -0
- meltygui/core/graphics/__init__.py +1 -0
- meltygui/core/graphics/cuda_context_core.py +166 -0
- meltygui/core/graphics/cuda_interop_core.py +136 -0
- meltygui/core/graphics/cuda_kernel_core.py +91 -0
- meltygui/core/graphics/framebuffer_recorder.py +337 -0
- meltygui/core/graphics/gl_state.py +658 -0
- meltygui/core/graphics/lut_core.py +52 -0
- meltygui/core/graphics/overlay_renderer.py +984 -0
- meltygui/core/graphics/scene_target.py +180 -0
- meltygui/core/graphics/screenshot.py +439 -0
- meltygui/core/graphics/shader_func.py +478 -0
- meltygui/core/graphics/tensor_core.py +45 -0
- meltygui/core/graphics/text_texture.py +329 -0
- meltygui/core/graphics/wayland_color.py +635 -0
- meltygui/core/input/__init__.py +1 -0
- meltygui/core/input/collision.py +165 -0
- meltygui/core/input/drag_drop_core.py +1525 -0
- meltygui/core/input/hypr_left_drag.py +323 -0
- meltygui/core/input/input_core.py +245 -0
- meltygui/core/input/input_handler.py +1101 -0
- meltygui/core/input/mouse_cursor.py +355 -0
- meltygui/core/input/pynput_backend.py +1054 -0
- meltygui/core/input/space_mouse.py +338 -0
- meltygui/core/input/touchpad_backend.py +393 -0
- meltygui/core/input/view_selection.py +177 -0
- meltygui/core/layout/__init__.py +1 -0
- meltygui/core/layout/column_core.py +2153 -0
- meltygui/core/layout/cursor_core.py +161 -0
- meltygui/core/layout/dropdown_core.py +278 -0
- meltygui/core/layout/edge_constraints.py +155 -0
- meltygui/core/layout/grid_core.py +117 -0
- meltygui/core/layout/header_core.py +23 -0
- meltygui/core/layout/header_runtime.py +67 -0
- meltygui/core/layout/layout_core.py +87 -0
- meltygui/core/layout/tile_manager_core.py +591 -0
- meltygui/core/melty.py +6726 -0
- meltygui/core/module_map.json +896 -0
- meltygui/core/module_names.py +19 -0
- meltygui/core/rendering/__init__.py +1 -0
- meltygui/core/rendering/core_decoration.py +431 -0
- meltygui/core/rendering/core_render_helpers.py +328 -0
- meltygui/core/rendering/func_metadata.py +398 -0
- meltygui/core/rendering/mode.py +818 -0
- meltygui/core/rendering/mode_defaults.py +41 -0
- meltygui/core/rendering/modes.py +136 -0
- meltygui/core/rendering/parameter_core.py +1665 -0
- meltygui/core/rendering/render_dispatch.py +1891 -0
- meltygui/core/rendering/render_funcs.py +273 -0
- meltygui/core/rendering/shaped.py +312 -0
- meltygui/core/rendering/window_decoration.py +25 -0
- meltygui/core/runtime/__init__.py +1 -0
- meltygui/core/runtime/app.py +735 -0
- meltygui/core/runtime/app_session.py +140 -0
- meltygui/core/runtime/background.py +564 -0
- meltygui/core/runtime/extensions.py +53 -0
- meltygui/core/runtime/gc_manager.py +1163 -0
- meltygui/core/runtime/lifecycle.py +21 -0
- meltygui/core/runtime/paths.py +27 -0
- meltygui/core/runtime/settings.py +14 -0
- meltygui/core/runtime/singleton.py +16 -0
- meltygui/core/runtime/thread_safe_bool.py +24 -0
- meltygui/core/runtime/thread_signal.py +30 -0
- meltygui/core/runtime/toggles.py +3115 -0
- meltygui/core/services/__init__.py +1 -0
- meltygui/core/services/account_core.py +10 -0
- meltygui/core/services/chat_core.py +19 -0
- meltygui/core/services/claude_terminal_core.py +346 -0
- meltygui/core/services/terminal_core.py +458 -0
- meltygui/core/services/terminal_runtime.py +78 -0
- meltygui/core/styling/__init__.py +1 -0
- meltygui/core/styling/color_core.py +46 -0
- meltygui/core/styling/fonts.py +639 -0
- meltygui/core/styling/global_style.py +338 -0
- meltygui/core/styling/style.py +198 -0
- meltygui/core/styling/style_core.py +522 -0
- meltygui/core/styling/warm_start.py +149 -0
- meltygui/core/windowing/__init__.py +1 -0
- meltygui/core/windowing/backends/PYIMGUI_LICENSE +28 -0
- meltygui/core/windowing/backends/__init__.py +1 -0
- meltygui/core/windowing/backends/imgui_renderer.py +138 -0
- meltygui/core/windowing/backends/native_wayland.py +850 -0
- meltygui/core/windowing/backends/protocols/xdg-decoration-unstable-v1.xml +156 -0
- meltygui/core/windowing/backends/protocols/xdg-shell.xml +1420 -0
- meltygui/core/windowing/backends/wayland_protocol.py +101 -0
- meltygui/core/windowing/dock_core.py +182 -0
- meltygui/core/windowing/frame_geometry.py +42 -0
- meltygui/core/windowing/geometry_feed.py +864 -0
- meltygui/core/windowing/glfw_utils.py +1343 -0
- meltygui/core/windowing/os_frame.py +1552 -0
- meltygui/core/windowing/surface.py +643 -0
- meltygui/core/windowing/titlebar.py +1560 -0
- meltygui/core/windowing/titlebar_buttons.py +281 -0
- meltygui/core/windowing/wayland_move.py +932 -0
- meltygui/core/windowing/window_api.py +62 -0
- meltygui/core/windowing/window_constants.py +339 -0
- meltygui/core/windowing/window_visibility.py +162 -0
- meltygui/debug/__init__.py +0 -0
- meltygui/debug/app_view_utils.py +9 -0
- meltygui/editor/__init__.py +0 -0
- meltygui/editor/bash_syntax.py +30 -0
- meltygui/editor/code_line_fast.py +152 -0
- meltygui/editor/diff.py +139 -0
- meltygui/editor/external_changes.py +159 -0
- meltygui/editor/file_header.py +41 -0
- meltygui/editor/live_usage.py +169 -0
- meltygui/editor/live_view_views.py +1803 -0
- meltygui/editor/pending_save.py +1351 -0
- meltygui/editor/roster_tints.py +547 -0
- meltygui/editor/source_preview.py +16 -0
- meltygui/editor/source_tools.py +10 -0
- meltygui/editor/source_ui.py +70 -0
- meltygui/editor/spell_check.py +77 -0
- meltygui/editor/text_editor.py +9076 -0
- meltygui/editor/usage_picker.py +538 -0
- meltygui/events/__init__.py +0 -0
- meltygui/events/example.py +118 -0
- meltygui/examples/__init__.py +0 -0
- meltygui/examples/columns_demo.py +33 -0
- meltygui/examples/columns_window_demo.py +60 -0
- meltygui/examples/context_menu_demo.py +24 -0
- meltygui/examples/context_menu_window_demo.py +49 -0
- meltygui/examples/gui_playground.py +127 -0
- meltygui/examples/live_view_playground.py +256 -0
- meltygui/examples/lora.py +43 -0
- meltygui/examples/lora_data.py +59 -0
- meltygui/examples/lora_policies.py +67 -0
- meltygui/examples/mode_demo.py +49 -0
- meltygui/examples/modifies_demo.py +119 -0
- meltygui/examples/scalar_policies.py +60 -0
- meltygui/examples/style_layouts.py +149 -0
- meltygui/examples/tile_manager_demo.py +74 -0
- meltygui/examples/tint_demo.py +160 -0
- meltygui/examples/tint_functions.py +56 -0
- meltygui/examples/trace_demo.py +88 -0
- meltygui/examples/two_windows.py +36 -0
- meltygui/files/__init__.py +0 -0
- meltygui/files/fast_file_explorer.py +439 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/extension.js +237 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/lsd-window-geometry@latent-descent.iml +9 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/metadata.json +7 -0
- meltygui/graphics/__init__.py +6 -0
- meltygui/graphics/base.py +85 -0
- meltygui/graphics/examples.py +507 -0
- meltygui/graphics/executor.py +520 -0
- meltygui/graphics/filter.py +667 -0
- meltygui/graphics/filter.pyi +856 -0
- meltygui/graphics/generate_stubs.py +22 -0
- meltygui/graphics/registry.py +203 -0
- meltygui/graphics/shader_compiler.py +155 -0
- meltygui/graphics/shaders.py +1302 -0
- meltygui/graphics/stub_generator.py +250 -0
- meltygui/graphics/texture_manager.py +170 -0
- meltygui/graphics/texture_min_max.py +295 -0
- meltygui/hdr_color.py +757 -0
- meltygui/image_load.py +308 -0
- meltygui/model/__init__.py +1 -0
- meltygui/model/account_model.py +142 -0
- meltygui/model/camera_model.py +159 -0
- meltygui/model/chat_model.py +86 -0
- meltygui/model/code_model.py +62 -0
- meltygui/model/code_proxy_model.py +876 -0
- meltygui/model/collection_model.py +41 -0
- meltygui/model/color_model.py +127 -0
- meltygui/model/cuda_tensor_model.py +36 -0
- meltygui/model/cuda_texture_model.py +149 -0
- meltygui/model/dropdown_model.py +137 -0
- meltygui/model/file_metadata_model.py +73 -0
- meltygui/model/file_model.py +289 -0
- meltygui/model/format_model.py +390 -0
- meltygui/model/graph_model.py +98 -0
- meltygui/model/icon_model.py +1024 -0
- meltygui/model/import_graph_model.py +468 -0
- meltygui/model/layout_model.py +15 -0
- meltygui/model/lut_model.py +266 -0
- meltygui/model/search_model.py +173 -0
- meltygui/model/tensor_model.py +402 -0
- meltygui/model/terminal_model.py +329 -0
- meltygui/model/texture_model.py +146 -0
- meltygui/model/tile_model.py +24 -0
- meltygui/model/trace_model.py +198 -0
- meltygui/model/trace_report_model.py +146 -0
- meltygui/models/__init__.py +0 -0
- meltygui/models/file_meta.py +613 -0
- meltygui/models/function_console.py +184 -0
- meltygui/models/orchestration.py +48 -0
- meltygui/pbr.py +1576 -0
- meltygui/png_unfilter.c +49 -0
- meltygui/resources/JetBrainsMono-Regular.ttf +0 -0
- meltygui/resources/THIRD_PARTY_NOTICES.md +21 -0
- meltygui/resources/dejavu/DejaVuSans-Bold.ttf +0 -0
- meltygui/resources/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
- meltygui/resources/dejavu/DejaVuSans.ttf +0 -0
- meltygui/resources/dejavu/LICENSE.txt +78 -0
- meltygui/resources/fontawesome-LICENSE.txt +121 -0
- meltygui/resources/fontawesome-webfont.ttf +0 -0
- meltygui/resources/hdri/studio_small_09_1k.hdr +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Bold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraBold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraLight.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Light.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Medium.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-SemiBold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Thin.ttf +0 -0
- meltygui/resources/jetbrains-weights/OFL.txt +93 -0
- meltygui/resources/jetbrains-weights/README.md +2 -0
- meltygui/state/__init__.py +0 -0
- meltygui/state/account_state.py +22 -0
- meltygui/state/animation_state.py +97 -0
- meltygui/state/annotation_state.py +22 -0
- meltygui/state/chat_state.py +36 -0
- meltygui/state/code_state.py +10 -0
- meltygui/state/core_enums.py +59 -0
- meltygui/state/core_markers.py +115 -0
- meltygui/state/core_undo.py +1075 -0
- meltygui/state/file_state.py +75 -0
- meltygui/state/graph_state.py +26 -0
- meltygui/state/inspection_state.py +47 -0
- meltygui/state/menu_state.py +10 -0
- meltygui/state/model_enums.py +11 -0
- meltygui/state/new_core_model.py +2394 -0
- meltygui/state/orchestration_state.py +14 -0
- meltygui/state/query_state.py +24 -0
- meltygui/state/tensor_state.py +10 -0
- meltygui/state/terminal_state.py +23 -0
- meltygui/state/trace_state.py +32 -0
- meltygui/state/voxel_state.py +12 -0
- meltygui/text_index.py +816 -0
- meltygui/utils/__init__.py +0 -0
- meltygui/utils/jump_to_code.py +344 -0
- meltygui/utils/pkl_inspect.py +90 -0
- meltygui/utils/render_utils.py +1419 -0
- meltygui/view/__init__.py +1 -0
- meltygui/view/account_view.py +524 -0
- meltygui/view/action_view.py +73 -0
- meltygui/view/chat_decoration_view.py +112 -0
- meltygui/view/chat_view.py +1703 -0
- meltygui/view/code_view.py +2677 -0
- meltygui/view/collection_view.py +1185 -0
- meltygui/view/color_view.py +824 -0
- meltygui/view/control_view.py +559 -0
- meltygui/view/decoration_view.py +439 -0
- meltygui/view/diagnostic_view.py +177 -0
- meltygui/view/dropdown_view.py +1082 -0
- meltygui/view/file_view.py +1599 -0
- meltygui/view/graph_cuda_view.py +105 -0
- meltygui/view/graph_view.py +882 -0
- meltygui/view/header_view.py +848 -0
- meltygui/view/input_view.py +238 -0
- meltygui/view/inspection_view.py +1783 -0
- meltygui/view/layout_view.py +596 -0
- meltygui/view/lut_view.py +45 -0
- meltygui/view/menu_view.py +212 -0
- meltygui/view/orchestration_view.py +658 -0
- meltygui/view/query_view.py +118 -0
- meltygui/view/search_view.py +342 -0
- meltygui/view/tab_view.py +258 -0
- meltygui/view/tensor_view.py +430 -0
- meltygui/view/terminal_view.py +355 -0
- meltygui/view/text_view.py +8031 -0
- meltygui/view/texture_view.py +480 -0
- meltygui/view/tile_view.py +38 -0
- meltygui/view/trace_view.py +923 -0
- meltygui/view/voxel_cuda_view.py +824 -0
- meltygui/view/voxel_view.py +1860 -0
- meltygui/view/window_view.py +111 -0
- meltygui-0.1.0.dist-info/METADATA +143 -0
- meltygui-0.1.0.dist-info/RECORD +372 -0
- meltygui-0.1.0.dist-info/WHEEL +4 -0
- meltygui-0.1.0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
from meltygui.code.fileref import Address
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class _Unset:
|
|
5
|
+
"""Sentinel indicating a step hasn't produced a real value yet."""
|
|
6
|
+
def __repr__(self):
|
|
7
|
+
return "UNSET_VALUE"
|
|
8
|
+
|
|
9
|
+
UNSET_VALUE = _Unset()
|
|
10
|
+
|
|
11
|
+
# Types safe for early cutoff equality comparison
|
|
12
|
+
_CUTOFF_TYPES = {Address, str, int, float, bool, tuple, bytes, frozenset}
|
|
13
|
+
|
|
14
|
+
def register_cutoff_type(*types):
|
|
15
|
+
"""Register types as safe for early cutoff equality comparison."""
|
|
16
|
+
_CUTOFF_TYPES.update(types)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CacheNode:
|
|
20
|
+
def __init__(self, type_key):
|
|
21
|
+
self.type_key = type_key
|
|
22
|
+
self.value = UNSET_VALUE
|
|
23
|
+
self.children = []
|
|
24
|
+
self._child_cursor = 0
|
|
25
|
+
|
|
26
|
+
def __repr__(self):
|
|
27
|
+
kids = [c.type_key for c in self.children]
|
|
28
|
+
return f"CacheNode({self.type_key}, value={self.value!r}, children={kids})"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CacheTree:
|
|
32
|
+
"""
|
|
33
|
+
Tree-shaped cache that auto-shares nodes for symmetric type sequences.
|
|
34
|
+
Types are inferred from output values.
|
|
35
|
+
|
|
36
|
+
Builds progressively — UNSET positions are filled in on later walks.
|
|
37
|
+
Handles type changes by replacing nodes and invalidating subtrees.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self):
|
|
41
|
+
self._root = CacheNode("__root__")
|
|
42
|
+
self._mapping = []
|
|
43
|
+
self._stack = None
|
|
44
|
+
self._cursor = 0
|
|
45
|
+
|
|
46
|
+
def begin(self):
|
|
47
|
+
"""Reset cursors for a new walk."""
|
|
48
|
+
self._cursor = 0
|
|
49
|
+
self._stack = [self._root]
|
|
50
|
+
self._reset_child_cursors(self._root)
|
|
51
|
+
|
|
52
|
+
def step(self, changed, value):
|
|
53
|
+
"""
|
|
54
|
+
Advance one position. Returns (changed, cached_value).
|
|
55
|
+
|
|
56
|
+
Early cutoff: if a step re-executes but produces the same result
|
|
57
|
+
as what's cached, changed is flipped to False — stopping propagation.
|
|
58
|
+
"""
|
|
59
|
+
if value is UNSET_VALUE or value is None:
|
|
60
|
+
return self._step_unset()
|
|
61
|
+
|
|
62
|
+
type_key = type(value).__name__
|
|
63
|
+
node = self._resolve(type_key)
|
|
64
|
+
|
|
65
|
+
# Check if mapping changed at this position
|
|
66
|
+
if self._cursor < len(self._mapping):
|
|
67
|
+
old_node = self._mapping[self._cursor]
|
|
68
|
+
if old_node is not node:
|
|
69
|
+
if old_node is not None:
|
|
70
|
+
self._clear(old_node)
|
|
71
|
+
self._mapping[self._cursor] = node
|
|
72
|
+
else:
|
|
73
|
+
self._mapping.append(node)
|
|
74
|
+
|
|
75
|
+
if changed or node.value is UNSET_VALUE:
|
|
76
|
+
node.value = value
|
|
77
|
+
|
|
78
|
+
self._cursor += 1
|
|
79
|
+
return node.value
|
|
80
|
+
|
|
81
|
+
def _step_unset(self):
|
|
82
|
+
"""Handle an UNSET_VALUE step."""
|
|
83
|
+
if self._cursor < len(self._mapping) and self._mapping[self._cursor] is not None:
|
|
84
|
+
# Replay old type through stack to keep it consistent
|
|
85
|
+
old_node = self._mapping[self._cursor]
|
|
86
|
+
self._resolve(old_node.type_key)
|
|
87
|
+
self._cursor += 1
|
|
88
|
+
return old_node.value
|
|
89
|
+
else:
|
|
90
|
+
# No prior mapping - can't resolve, leave placeholder
|
|
91
|
+
if self._cursor >= len(self._mapping):
|
|
92
|
+
self._mapping.append(None)
|
|
93
|
+
self._cursor += 1
|
|
94
|
+
return UNSET_VALUE
|
|
95
|
+
|
|
96
|
+
def peek(self):
|
|
97
|
+
"""Return the cached value at the current cursor without advancing."""
|
|
98
|
+
if self._cursor < len(self._mapping):
|
|
99
|
+
node = self._mapping[self._cursor]
|
|
100
|
+
if node is not None:
|
|
101
|
+
return node.value
|
|
102
|
+
return UNSET_VALUE
|
|
103
|
+
|
|
104
|
+
def end(self):
|
|
105
|
+
"""End of walk. Stack is rebuilt each begin(), nothing to finalize."""
|
|
106
|
+
self._stack = None
|
|
107
|
+
|
|
108
|
+
def _resolve(self, type_key):
|
|
109
|
+
current = self._stack[-1]
|
|
110
|
+
|
|
111
|
+
# Same as current → stay at this level
|
|
112
|
+
if current.type_key == type_key:
|
|
113
|
+
return current
|
|
114
|
+
|
|
115
|
+
# Parent matches → pop back up (reuse node)
|
|
116
|
+
if len(self._stack) > 1 and self._stack[-2].type_key == type_key:
|
|
117
|
+
self._stack.pop()
|
|
118
|
+
return self._stack[-1]
|
|
119
|
+
|
|
120
|
+
# Push - check existing children first
|
|
121
|
+
child_idx = current._child_cursor
|
|
122
|
+
if child_idx < len(current.children):
|
|
123
|
+
existing = current.children[child_idx]
|
|
124
|
+
if existing.type_key == type_key:
|
|
125
|
+
# Reuse existing child
|
|
126
|
+
current._child_cursor += 1
|
|
127
|
+
self._stack.append(existing)
|
|
128
|
+
return existing
|
|
129
|
+
else:
|
|
130
|
+
# Type changed - replace child, invalidate its subtree
|
|
131
|
+
self._clear(existing)
|
|
132
|
+
new_child = CacheNode(type_key)
|
|
133
|
+
current.children[child_idx] = new_child
|
|
134
|
+
current._child_cursor += 1
|
|
135
|
+
self._stack.append(new_child)
|
|
136
|
+
return new_child
|
|
137
|
+
else:
|
|
138
|
+
# New child
|
|
139
|
+
child = CacheNode(type_key)
|
|
140
|
+
current.children.append(child)
|
|
141
|
+
current._child_cursor += 1
|
|
142
|
+
self._stack.append(child)
|
|
143
|
+
return child
|
|
144
|
+
|
|
145
|
+
def _reset_child_cursors(self, node):
|
|
146
|
+
node._child_cursor = 0
|
|
147
|
+
for child in node.children:
|
|
148
|
+
self._reset_child_cursors(child)
|
|
149
|
+
|
|
150
|
+
def node_at(self, index):
|
|
151
|
+
return self._mapping[index]
|
|
152
|
+
|
|
153
|
+
def invalidate(self):
|
|
154
|
+
self._clear(self._root)
|
|
155
|
+
|
|
156
|
+
def _clear(self, node):
|
|
157
|
+
node.value = UNSET_VALUE
|
|
158
|
+
for child in node.children:
|
|
159
|
+
self._clear(child)
|
|
160
|
+
|
|
161
|
+
def print_tree(self, node=None, indent=0):
|
|
162
|
+
if node is None:
|
|
163
|
+
node = self._root
|
|
164
|
+
prefix = " " * indent
|
|
165
|
+
print(f"{prefix}{node.type_key}: {node.value!r}")
|
|
166
|
+
for child in node.children:
|
|
167
|
+
self.print_tree(child, indent + 1)
|
|
168
|
+
|
|
169
|
+
def print_mapping(self, labels=None):
|
|
170
|
+
for i, node in enumerate(self._mapping):
|
|
171
|
+
label = labels[i] if labels else str(i)
|
|
172
|
+
if node is None:
|
|
173
|
+
print(f" [{i}] {label:>15} → (unset)")
|
|
174
|
+
else:
|
|
175
|
+
print(f" [{i}] {label:>15} → {node.type_key} (id={id(node):#x})")
|
|
176
|
+
|
|
177
|
+
def get_mapping_as_str(self, labels=None):
|
|
178
|
+
lines = []
|
|
179
|
+
for i, node in enumerate(self._mapping):
|
|
180
|
+
label = labels[i] if labels else str(i)
|
|
181
|
+
if node is None:
|
|
182
|
+
lines.append(f" [{i}] {label:>15} → (unset)")
|
|
183
|
+
else:
|
|
184
|
+
lines.append(f" [{i}] {label:>15} → {node.type_key} (id={id(node):#x})")
|
|
185
|
+
return "\n".join(lines)
|
|
186
|
+
|
|
187
|
+
def __len__(self):
|
|
188
|
+
return len(self._mapping)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Chain executor for the unified render pipeline.
|
|
3
|
+
|
|
4
|
+
A chain is a flat list of @render_func nodes. Data flows left to right:
|
|
5
|
+
|
|
6
|
+
[cls_to_cst, cst_to_dict, draw_collection, dict_to_cst, cst_to_cls]
|
|
7
|
+
load side ──────────────> render <────────────── save side
|
|
8
|
+
|
|
9
|
+
Every node has the same calling convention:
|
|
10
|
+
changed, value = func(input_value, **kwargs)
|
|
11
|
+
|
|
12
|
+
The executor walks the chain, passing the output of each node as the
|
|
13
|
+
input to the next. Nodes that haven't changed return cached results
|
|
14
|
+
instantly. The chain stops propagating when a node returns changed=False
|
|
15
|
+
(no downstream work needed), UNLESS the node has never run (first frame).
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
draw_any(my_class, chain=[cls_to_cst, cst_to_dict, draw_collection,
|
|
19
|
+
dict_to_cst, cst_to_cls])
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from meltygui.core.conversion.path_finder import Pending
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _step(func, value, **kwargs):
|
|
26
|
+
"""Execute one chain node. Returns (changed, value, func_name)."""
|
|
27
|
+
result = func(value, **kwargs)
|
|
28
|
+
|
|
29
|
+
if isinstance(result, tuple) and len(result) == 2:
|
|
30
|
+
changed, value = result
|
|
31
|
+
else:
|
|
32
|
+
changed = False
|
|
33
|
+
value = result
|
|
34
|
+
|
|
35
|
+
name = getattr(func, '__name__', None) or getattr(func, '__qualname__', repr(func))
|
|
36
|
+
return changed, value, name
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Nodes a real user edit flows through. If a chain ends changed=True and NONE
|
|
40
|
+
# of these returned True, the change came from converters alone - exactly the
|
|
41
|
+
# "round-trip mutated state on its own" case we want to surface. Loose by name
|
|
42
|
+
# (matches whatever render_func wrappers expose) and overinclusive on purpose:
|
|
43
|
+
# false-positets are worse than the occasional missed case.
|
|
44
|
+
_INTERACTION_NODES = frozenset({
|
|
45
|
+
"focus", "draw_collection", "draw_tuple", "draw_text", "button",
|
|
46
|
+
"draw_int", "draw_str", "draw_float", "draw_bool", "draw_enum_tabs",
|
|
47
|
+
"draw_dropdown", "draw_tuple_float",
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def run_chain(chain, input_value, **kwargs):
|
|
52
|
+
"""Execute a chain of render_func nodes left to right.
|
|
53
|
+
|
|
54
|
+
Each node is called as: changed, value = node(value, **kwargs)
|
|
55
|
+
|
|
56
|
+
Returns (changed, value) — the output of the last node that ran.
|
|
57
|
+
|
|
58
|
+
Propagation rules:
|
|
59
|
+
- A node always runs if it has never produced a result (first frame).
|
|
60
|
+
- After first frame, a node runs only if upstream returned changed=True.
|
|
61
|
+
- If a node returns a Pending, the chain stops and returns the Pending.
|
|
62
|
+
"""
|
|
63
|
+
value = input_value
|
|
64
|
+
changed = True # first frame always runs
|
|
65
|
+
|
|
66
|
+
trace = [] # (name, changed_returned, value_type) - for round-trip diagnostics
|
|
67
|
+
|
|
68
|
+
for func in chain:
|
|
69
|
+
if not changed:
|
|
70
|
+
break
|
|
71
|
+
|
|
72
|
+
changed, value, name = _step(func, value, **kwargs)
|
|
73
|
+
trace.append((name, changed, type(value).__name__))
|
|
74
|
+
|
|
75
|
+
# Pending stops the chain: downstream can't run without data
|
|
76
|
+
if isinstance(value, Pending):
|
|
77
|
+
return False, value
|
|
78
|
+
|
|
79
|
+
# ── Round-trip change detection ──────────────────────────────────────────
|
|
80
|
+
# The chain ends changed=True iff a save-side node will (or might, via the
|
|
81
|
+
# save_pending latch) write back. A real user edit always flows through an
|
|
82
|
+
# interaction node (focus, the picker, etc.). If we get here with changed=True
|
|
83
|
+
# but no interaction node returned True, only converters did - meaning a
|
|
84
|
+
# round-trip mutated the value on its own. Logs only, behavior unchanged.
|
|
85
|
+
if changed:
|
|
86
|
+
interaction_fired = any(
|
|
87
|
+
ch and n in _INTERACTION_NODES for n, ch, _ in trace)
|
|
88
|
+
if not interaction_fired:
|
|
89
|
+
converter_changes = [(n, vt) for n, ch, vt in trace if ch]
|
|
90
|
+
if converter_changes:
|
|
91
|
+
_log_round_trip_change(trace, converter_changes, kwargs)
|
|
92
|
+
|
|
93
|
+
return changed, value
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _log_round_trip_change(trace, converter_changes, kwargs):
|
|
97
|
+
"""Dump a prominent record of a converter-only changed=True flow."""
|
|
98
|
+
import sys
|
|
99
|
+
ds = kwargs.get("draw_state")
|
|
100
|
+
ds_id = getattr(ds, "id", None) or getattr(ds, "_tile_id", None)
|
|
101
|
+
ds_name = getattr(ds, "name", None)
|
|
102
|
+
marked = " → ".join(
|
|
103
|
+
(f"\033[91m{n}*\033[0m" if ch else n) for n, ch, _ in trace)
|
|
104
|
+
print(
|
|
105
|
+
f"\n\033[93;1m[CHAIN ROUND-TRIP CHANGE — no user input]\033[0m "
|
|
106
|
+
f"chain returned changed=True with no interaction node firing.",
|
|
107
|
+
file=sys.stderr)
|
|
108
|
+
print(f" draw_state: name={ds_name!r} id={ds_id}", file=sys.stderr)
|
|
109
|
+
print(f" trace (* = node returned changed=True):", file=sys.stderr)
|
|
110
|
+
print(f" {marked}", file=sys.stderr)
|
|
111
|
+
print(f" suspect converters: "
|
|
112
|
+
f"{[n for n, _ in converter_changes]}", file=sys.stderr)
|
|
113
|
+
print(f" → a downstream save may fire without user input", file=sys.stderr)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Converter decorator. Threads apply and cache_id through all converters.
|
|
3
|
+
|
|
4
|
+
@converter(registry=Melty)
|
|
5
|
+
def str_to_module(value: str) -> cst.Module:
|
|
6
|
+
return cst.parse_module(value)
|
|
7
|
+
|
|
8
|
+
All registered converters accept (value, apply=False, cache_id=None)
|
|
9
|
+
regardless of original signature. The decorator absorbs extra kwargs
|
|
10
|
+
for simple converters and passes them through for load/save wrappers.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import functools
|
|
16
|
+
import inspect
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _fn_accepts(fn, param_name: str) -> bool:
|
|
21
|
+
"""Check if fn accepts a given parameter."""
|
|
22
|
+
if getattr(fn, f"_accepts_{param_name}", False):
|
|
23
|
+
return True
|
|
24
|
+
try:
|
|
25
|
+
return param_name in inspect.signature(fn).parameters
|
|
26
|
+
except (ValueError, TypeError):
|
|
27
|
+
return False
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def converter(converter_fn=None, registry: Any = None, *,
|
|
31
|
+
from_type: type | None = None,
|
|
32
|
+
to_type: type | None = None,
|
|
33
|
+
load_data=None,
|
|
34
|
+
save_data=None,
|
|
35
|
+
**kwargs):
|
|
36
|
+
if converter_fn is None:
|
|
37
|
+
return lambda fn: converter(
|
|
38
|
+
fn, registry,
|
|
39
|
+
from_type=from_type, to_type=to_type,
|
|
40
|
+
load_data=load_data, save_data=save_data,
|
|
41
|
+
**kwargs
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# ── Infer types from signature ──────────────────────────────────
|
|
45
|
+
|
|
46
|
+
func_signature = inspect.signature(converter_fn)
|
|
47
|
+
fn_params = func_signature.parameters
|
|
48
|
+
|
|
49
|
+
inferred_to = func_signature.return_annotation
|
|
50
|
+
if inferred_to is inspect.Signature.empty:
|
|
51
|
+
inferred_to = None
|
|
52
|
+
if inferred_to is not None and hasattr(inferred_to, "__args__"):
|
|
53
|
+
inferred_to = inferred_to.__args__[0]
|
|
54
|
+
|
|
55
|
+
inferred_from = None
|
|
56
|
+
for param_name in ("value", "data"):
|
|
57
|
+
param = fn_params.get(param_name)
|
|
58
|
+
if param is not None and param.annotation is not inspect.Parameter.empty:
|
|
59
|
+
inferred_from = param.annotation
|
|
60
|
+
break
|
|
61
|
+
|
|
62
|
+
actual_from = from_type or inferred_from
|
|
63
|
+
actual_to = to_type or inferred_to
|
|
64
|
+
|
|
65
|
+
# ── Wrap with load/save if needed ───────────────────────────────
|
|
66
|
+
|
|
67
|
+
wrapped_fn = converter_fn
|
|
68
|
+
|
|
69
|
+
if load_data is not None:
|
|
70
|
+
if actual_from is None:
|
|
71
|
+
raise TypeError(
|
|
72
|
+
f"Converter {converter_fn.__name__} with load_data requires from_type=")
|
|
73
|
+
from meltygui.code.fileref import make_load_wrapper
|
|
74
|
+
wrapped_fn = make_load_wrapper(converter_fn, load_data, actual_from)
|
|
75
|
+
|
|
76
|
+
if save_data is not None:
|
|
77
|
+
if actual_to is None:
|
|
78
|
+
raise TypeError(
|
|
79
|
+
f"Converter {converter_fn.__name__} with save_data requires to_type=")
|
|
80
|
+
from meltygui.code.fileref import make_save_wrapper
|
|
81
|
+
load_cache = None
|
|
82
|
+
inverse_fn = kwargs.get("inverse_of")
|
|
83
|
+
if inverse_fn is not None and hasattr(inverse_fn, "_local_cache"):
|
|
84
|
+
load_cache = inverse_fn._local_cache
|
|
85
|
+
wrapped_fn = make_save_wrapper(converter_fn, save_data, actual_to,
|
|
86
|
+
load_cache=load_cache)
|
|
87
|
+
|
|
88
|
+
# ── Normalize args: all converters accept (value, apply, cache_id) ──
|
|
89
|
+
|
|
90
|
+
has_apply = _fn_accepts(wrapped_fn, "apply")
|
|
91
|
+
has_cache_id = _fn_accepts(wrapped_fn, "cache_id")
|
|
92
|
+
|
|
93
|
+
if not has_apply or not has_cache_id:
|
|
94
|
+
inner = wrapped_fn
|
|
95
|
+
|
|
96
|
+
@functools.wraps(inner)
|
|
97
|
+
def _normalized(value, apply=False, cache_id=None):
|
|
98
|
+
kw = {}
|
|
99
|
+
if has_apply:
|
|
100
|
+
kw["apply"] = apply
|
|
101
|
+
if has_cache_id:
|
|
102
|
+
kw["cache_id"] = cache_id
|
|
103
|
+
return inner(value, **kw)
|
|
104
|
+
|
|
105
|
+
_normalized._accepts_apply = True
|
|
106
|
+
_normalized._accepts_cache_id = True
|
|
107
|
+
# Preserve wrapper attributes
|
|
108
|
+
for attr in ("_watched_from_type", "_watched_to_type", "_local_cache"):
|
|
109
|
+
if hasattr(inner, attr):
|
|
110
|
+
setattr(_normalized, attr, getattr(inner, attr))
|
|
111
|
+
wrapped_fn = _normalized
|
|
112
|
+
|
|
113
|
+
# ── Register ────────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
if not hasattr(registry, '_converters') or not isinstance(registry._converters, dict):
|
|
116
|
+
setattr(registry, '_converters', {})
|
|
117
|
+
|
|
118
|
+
if actual_from is not None and actual_to is not None:
|
|
119
|
+
registry._converters[(actual_from, actual_to)] = wrapped_fn
|
|
120
|
+
|
|
121
|
+
if hasattr(registry, "_converter_to_type") and isinstance(registry._converter_to_type, dict):
|
|
122
|
+
registry._converter_to_type[wrapped_fn] = (actual_from, actual_to)
|
|
123
|
+
|
|
124
|
+
# ── Store flags ─────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
if hasattr(registry, 'converter_flags_by_type') and isinstance(registry.converter_flags_by_type, dict):
|
|
127
|
+
if kwargs:
|
|
128
|
+
registry.converter_flags_by_type[(actual_from, actual_to)] = kwargs
|
|
129
|
+
|
|
130
|
+
if hasattr(registry, 'converter_flags') and isinstance(registry.converter_flags, dict):
|
|
131
|
+
if kwargs:
|
|
132
|
+
registry.converter_flags[wrapped_fn] = kwargs
|
|
133
|
+
if "inverse_of" in kwargs:
|
|
134
|
+
inverse_fn = kwargs["inverse_of"]
|
|
135
|
+
if inverse_fn not in registry.converter_flags:
|
|
136
|
+
registry.converter_flags[inverse_fn] = {}
|
|
137
|
+
registry.converter_flags[inverse_fn]["inverse_of"] = wrapped_fn
|
|
138
|
+
|
|
139
|
+
if load_data is not None:
|
|
140
|
+
if hasattr(registry, 'converter_flags') and isinstance(registry.converter_flags, dict):
|
|
141
|
+
if wrapped_fn not in registry.converter_flags:
|
|
142
|
+
registry.converter_flags[wrapped_fn] = {}
|
|
143
|
+
registry.converter_flags[wrapped_fn]["stateful"] = True
|
|
144
|
+
|
|
145
|
+
return wrapped_fn
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from dataclasses import dataclass, field, MISSING, make_dataclass
|
|
2
|
+
import types
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
# Types considered safe to use as constants (not recreated per instance)
|
|
6
|
+
IMMUTABLES = (int, float, str, bool, type(None), bytes, tuple, frozenset)
|
|
7
|
+
|
|
8
|
+
def data(_cls=None, **dc_kwargs):
|
|
9
|
+
"""
|
|
10
|
+
A decorator that works like @dataclass, but ensures that
|
|
11
|
+
all defaults are re-evaluated for every new instance.
|
|
12
|
+
|
|
13
|
+
Behavior:
|
|
14
|
+
- Immutable defaults (int, str, bool, None, tuple, etc.) are kept as-is.
|
|
15
|
+
- Mutable defaults ([], {}, set()) are turned into default_factory calls.
|
|
16
|
+
- Callable defaults (like list, dict, or a user function) are used as factories.
|
|
17
|
+
- Function call results (e.g. id = generate_id()) are re-executed for each instance.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def wrap(cls):
|
|
21
|
+
annotations = getattr(cls, "__annotations__", {})
|
|
22
|
+
new_fields = []
|
|
23
|
+
|
|
24
|
+
for name, typ in annotations.items():
|
|
25
|
+
default = getattr(cls, name, MISSING)
|
|
26
|
+
|
|
27
|
+
if default is MISSING:
|
|
28
|
+
# Field without a default
|
|
29
|
+
new_fields.append((name, typ))
|
|
30
|
+
else:
|
|
31
|
+
if isinstance(default, IMMUTABLES):
|
|
32
|
+
# Safe to use as a constant default
|
|
33
|
+
new_fields.append((name, typ, default))
|
|
34
|
+
|
|
35
|
+
elif isinstance(default, (list, dict, set)):
|
|
36
|
+
# Dangerous mutable literal: replace with factory
|
|
37
|
+
new_fields.append(
|
|
38
|
+
(name, typ, field(default_factory=default.__class__))
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
elif isinstance(default, types.FunctionType):
|
|
42
|
+
# A function type was provided: use it as default_factory
|
|
43
|
+
new_fields.append((name, typ, field(default_factory=default)))
|
|
44
|
+
|
|
45
|
+
else:
|
|
46
|
+
# A computed value (like generate_id())
|
|
47
|
+
# We capture the original function by closure
|
|
48
|
+
def factory(val=default):
|
|
49
|
+
# If value looks callable (like generate_id()), re-call
|
|
50
|
+
if callable(val):
|
|
51
|
+
return val()
|
|
52
|
+
# Else try to copy/recreate it
|
|
53
|
+
return type(val)(val)
|
|
54
|
+
|
|
55
|
+
new_fields.append((name, typ, field(default_factory=factory)))
|
|
56
|
+
|
|
57
|
+
# Rebuild dataclass with new defaults
|
|
58
|
+
new_cls = make_dataclass(
|
|
59
|
+
cls.__name__,
|
|
60
|
+
new_fields,
|
|
61
|
+
bases=cls.__bases__,
|
|
62
|
+
namespace=dict(cls.__dict__),
|
|
63
|
+
**dc_kwargs
|
|
64
|
+
)
|
|
65
|
+
return new_cls
|
|
66
|
+
|
|
67
|
+
return wrap if _cls is None else wrap(_cls)
|