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.
Files changed (372) hide show
  1. meltygui/__init__.py +107 -0
  2. meltygui/accounts/__init__.py +0 -0
  3. meltygui/accounts/internet_accounts.py +1355 -0
  4. meltygui/chat/__init__.py +91 -0
  5. meltygui/chat/activity.py +75 -0
  6. meltygui/chat/backends.py +36 -0
  7. meltygui/chat/chat_interface.py +732 -0
  8. meltygui/chat/chat_proxy.py +352 -0
  9. meltygui/chat/codex_proxy.py +592 -0
  10. meltygui/chat/codex_settings.py +100 -0
  11. meltygui/chat/codex_transport.py +60 -0
  12. meltygui/chat/command_parser.py +204 -0
  13. meltygui/chat/images.py +227 -0
  14. meltygui/chat/messages.py +417 -0
  15. meltygui/chat/metadata.py +139 -0
  16. meltygui/chat/writer_locks.py +64 -0
  17. meltygui/code/__init__.py +0 -0
  18. meltygui/code/basic_converters.py +533 -0
  19. meltygui/code/chain_converters.py +2111 -0
  20. meltygui/code/code_checks.py +2209 -0
  21. meltygui/code/core_syntax.py +1430 -0
  22. meltygui/code/file_converters.py +1933 -0
  23. meltygui/code/fileref.py +702 -0
  24. meltygui/code/hotswap_guard.py +144 -0
  25. meltygui/code/libcst_conversion.py +9724 -0
  26. meltygui/code/live_instrument.py +392 -0
  27. meltygui/code/live_view.py +2490 -0
  28. meltygui/code/melty_scan.py +2684 -0
  29. meltygui/code/new_codecs.py +1255 -0
  30. meltygui/code/new_converters.py +3017 -0
  31. meltygui/code/project_code.py +278 -0
  32. meltygui/code/source_context.py +63 -0
  33. meltygui/code/symbol_roster.py +1588 -0
  34. meltygui/code/syntax_check.py +34 -0
  35. meltygui/code/syntax_check_worker.py +114 -0
  36. meltygui/completion/__init__.py +0 -0
  37. meltygui/completion/fim.py +1232 -0
  38. meltygui/completion/fim_context.py +481 -0
  39. meltygui/completion/providers/__init__.py +0 -0
  40. meltygui/completion/providers/anthropic_oauth.py +446 -0
  41. meltygui/completion/providers/anthropic_requests.py +69 -0
  42. meltygui/completion/providers/claude.py +203 -0
  43. meltygui/completion/providers/claude_usage.py +499 -0
  44. meltygui/completion/providers/codex_accounts.py +180 -0
  45. meltygui/completion/providers/copilot.py +617 -0
  46. meltygui/completion/providers/oauth_popup.py +220 -0
  47. meltygui/completion/providers/ollama.py +320 -0
  48. meltygui/completion/providers/profiles.py +23 -0
  49. meltygui/core/README.md +88 -0
  50. meltygui/core/__init__.py +1 -0
  51. meltygui/core/automation/__init__.py +1 -0
  52. meltygui/core/automation/action_core.py +153 -0
  53. meltygui/core/automation/collection_action.py +45 -0
  54. meltygui/core/automation/mcp_eval.py +167 -0
  55. meltygui/core/automation/mcp_hotswap.py +107 -0
  56. meltygui/core/automation/mcp_query.py +552 -0
  57. meltygui/core/automation/mcp_server.py +657 -0
  58. meltygui/core/automation/orchestration_core.py +2636 -0
  59. meltygui/core/automation/query_core.py +124 -0
  60. meltygui/core/automation/search_core.py +26 -0
  61. meltygui/core/automation/selector_core.py +340 -0
  62. meltygui/core/automation/value_core.py +1752 -0
  63. meltygui/core/cache/__init__.py +1 -0
  64. meltygui/core/cache/cache_diagnostics.py +0 -0
  65. meltygui/core/cache/invalidation_decoration.py +153 -0
  66. meltygui/core/cache/invalidation_tracker.py +43 -0
  67. meltygui/core/cache/tile_cache.py +5968 -0
  68. meltygui/core/conversion/__init__.py +1 -0
  69. meltygui/core/conversion/bubbling.py +599 -0
  70. meltygui/core/conversion/cache_tree.py +188 -0
  71. meltygui/core/conversion/chain.py +113 -0
  72. meltygui/core/conversion/converter_register.py +145 -0
  73. meltygui/core/conversion/data_decoration.py +67 -0
  74. meltygui/core/conversion/dict_conversion.py +1815 -0
  75. meltygui/core/conversion/dict_conversion_util.py +177 -0
  76. meltygui/core/conversion/dynamic_obj.py +89 -0
  77. meltygui/core/conversion/graph_compare.py +183 -0
  78. meltygui/core/conversion/load_save_v2.py +1032 -0
  79. meltygui/core/conversion/missing_saved_class.py +39 -0
  80. meltygui/core/conversion/path_finder.py +606 -0
  81. meltygui/core/conversion/render_host.py +1083 -0
  82. meltygui/core/core_render.py +6537 -0
  83. meltygui/core/definition_hotswap.py +231 -0
  84. meltygui/core/diagnostics/__init__.py +1 -0
  85. meltygui/core/diagnostics/attribute_churn.py +38 -0
  86. meltygui/core/diagnostics/fps_counter.py +39 -0
  87. meltygui/core/diagnostics/gpu_frame_timer.py +103 -0
  88. meltygui/core/diagnostics/inspection_core.py +169 -0
  89. meltygui/core/diagnostics/monitor_core.py +105 -0
  90. meltygui/core/diagnostics/notifications.py +706 -0
  91. meltygui/core/diagnostics/perf_trace.py +281 -0
  92. meltygui/core/diagnostics/profile_decoration.py +88 -0
  93. meltygui/core/diagnostics/resize_trace.py +62 -0
  94. meltygui/core/diagnostics/screenshot_core.py +247 -0
  95. meltygui/core/diagnostics/session_status.py +98 -0
  96. meltygui/core/diagnostics/trace_core.py +445 -0
  97. meltygui/core/files/__init__.py +1 -0
  98. meltygui/core/files/file_core.py +208 -0
  99. meltygui/core/files/file_explorer_core.py +104 -0
  100. meltygui/core/files/file_tree_core.py +198 -0
  101. meltygui/core/files/file_watch_core.py +43 -0
  102. meltygui/core/files/import_graph_core.py +43 -0
  103. meltygui/core/files/metadata_core.py +51 -0
  104. meltygui/core/graphics/__init__.py +1 -0
  105. meltygui/core/graphics/cuda_context_core.py +166 -0
  106. meltygui/core/graphics/cuda_interop_core.py +136 -0
  107. meltygui/core/graphics/cuda_kernel_core.py +91 -0
  108. meltygui/core/graphics/framebuffer_recorder.py +337 -0
  109. meltygui/core/graphics/gl_state.py +658 -0
  110. meltygui/core/graphics/lut_core.py +52 -0
  111. meltygui/core/graphics/overlay_renderer.py +984 -0
  112. meltygui/core/graphics/scene_target.py +180 -0
  113. meltygui/core/graphics/screenshot.py +439 -0
  114. meltygui/core/graphics/shader_func.py +478 -0
  115. meltygui/core/graphics/tensor_core.py +45 -0
  116. meltygui/core/graphics/text_texture.py +329 -0
  117. meltygui/core/graphics/wayland_color.py +635 -0
  118. meltygui/core/input/__init__.py +1 -0
  119. meltygui/core/input/collision.py +165 -0
  120. meltygui/core/input/drag_drop_core.py +1525 -0
  121. meltygui/core/input/hypr_left_drag.py +323 -0
  122. meltygui/core/input/input_core.py +245 -0
  123. meltygui/core/input/input_handler.py +1101 -0
  124. meltygui/core/input/mouse_cursor.py +355 -0
  125. meltygui/core/input/pynput_backend.py +1054 -0
  126. meltygui/core/input/space_mouse.py +338 -0
  127. meltygui/core/input/touchpad_backend.py +393 -0
  128. meltygui/core/input/view_selection.py +177 -0
  129. meltygui/core/layout/__init__.py +1 -0
  130. meltygui/core/layout/column_core.py +2153 -0
  131. meltygui/core/layout/cursor_core.py +161 -0
  132. meltygui/core/layout/dropdown_core.py +278 -0
  133. meltygui/core/layout/edge_constraints.py +155 -0
  134. meltygui/core/layout/grid_core.py +117 -0
  135. meltygui/core/layout/header_core.py +23 -0
  136. meltygui/core/layout/header_runtime.py +67 -0
  137. meltygui/core/layout/layout_core.py +87 -0
  138. meltygui/core/layout/tile_manager_core.py +591 -0
  139. meltygui/core/melty.py +6726 -0
  140. meltygui/core/module_map.json +896 -0
  141. meltygui/core/module_names.py +19 -0
  142. meltygui/core/rendering/__init__.py +1 -0
  143. meltygui/core/rendering/core_decoration.py +431 -0
  144. meltygui/core/rendering/core_render_helpers.py +328 -0
  145. meltygui/core/rendering/func_metadata.py +398 -0
  146. meltygui/core/rendering/mode.py +818 -0
  147. meltygui/core/rendering/mode_defaults.py +41 -0
  148. meltygui/core/rendering/modes.py +136 -0
  149. meltygui/core/rendering/parameter_core.py +1665 -0
  150. meltygui/core/rendering/render_dispatch.py +1891 -0
  151. meltygui/core/rendering/render_funcs.py +273 -0
  152. meltygui/core/rendering/shaped.py +312 -0
  153. meltygui/core/rendering/window_decoration.py +25 -0
  154. meltygui/core/runtime/__init__.py +1 -0
  155. meltygui/core/runtime/app.py +735 -0
  156. meltygui/core/runtime/app_session.py +140 -0
  157. meltygui/core/runtime/background.py +564 -0
  158. meltygui/core/runtime/extensions.py +53 -0
  159. meltygui/core/runtime/gc_manager.py +1163 -0
  160. meltygui/core/runtime/lifecycle.py +21 -0
  161. meltygui/core/runtime/paths.py +27 -0
  162. meltygui/core/runtime/settings.py +14 -0
  163. meltygui/core/runtime/singleton.py +16 -0
  164. meltygui/core/runtime/thread_safe_bool.py +24 -0
  165. meltygui/core/runtime/thread_signal.py +30 -0
  166. meltygui/core/runtime/toggles.py +3115 -0
  167. meltygui/core/services/__init__.py +1 -0
  168. meltygui/core/services/account_core.py +10 -0
  169. meltygui/core/services/chat_core.py +19 -0
  170. meltygui/core/services/claude_terminal_core.py +346 -0
  171. meltygui/core/services/terminal_core.py +458 -0
  172. meltygui/core/services/terminal_runtime.py +78 -0
  173. meltygui/core/styling/__init__.py +1 -0
  174. meltygui/core/styling/color_core.py +46 -0
  175. meltygui/core/styling/fonts.py +639 -0
  176. meltygui/core/styling/global_style.py +338 -0
  177. meltygui/core/styling/style.py +198 -0
  178. meltygui/core/styling/style_core.py +522 -0
  179. meltygui/core/styling/warm_start.py +149 -0
  180. meltygui/core/windowing/__init__.py +1 -0
  181. meltygui/core/windowing/backends/PYIMGUI_LICENSE +28 -0
  182. meltygui/core/windowing/backends/__init__.py +1 -0
  183. meltygui/core/windowing/backends/imgui_renderer.py +138 -0
  184. meltygui/core/windowing/backends/native_wayland.py +850 -0
  185. meltygui/core/windowing/backends/protocols/xdg-decoration-unstable-v1.xml +156 -0
  186. meltygui/core/windowing/backends/protocols/xdg-shell.xml +1420 -0
  187. meltygui/core/windowing/backends/wayland_protocol.py +101 -0
  188. meltygui/core/windowing/dock_core.py +182 -0
  189. meltygui/core/windowing/frame_geometry.py +42 -0
  190. meltygui/core/windowing/geometry_feed.py +864 -0
  191. meltygui/core/windowing/glfw_utils.py +1343 -0
  192. meltygui/core/windowing/os_frame.py +1552 -0
  193. meltygui/core/windowing/surface.py +643 -0
  194. meltygui/core/windowing/titlebar.py +1560 -0
  195. meltygui/core/windowing/titlebar_buttons.py +281 -0
  196. meltygui/core/windowing/wayland_move.py +932 -0
  197. meltygui/core/windowing/window_api.py +62 -0
  198. meltygui/core/windowing/window_constants.py +339 -0
  199. meltygui/core/windowing/window_visibility.py +162 -0
  200. meltygui/debug/__init__.py +0 -0
  201. meltygui/debug/app_view_utils.py +9 -0
  202. meltygui/editor/__init__.py +0 -0
  203. meltygui/editor/bash_syntax.py +30 -0
  204. meltygui/editor/code_line_fast.py +152 -0
  205. meltygui/editor/diff.py +139 -0
  206. meltygui/editor/external_changes.py +159 -0
  207. meltygui/editor/file_header.py +41 -0
  208. meltygui/editor/live_usage.py +169 -0
  209. meltygui/editor/live_view_views.py +1803 -0
  210. meltygui/editor/pending_save.py +1351 -0
  211. meltygui/editor/roster_tints.py +547 -0
  212. meltygui/editor/source_preview.py +16 -0
  213. meltygui/editor/source_tools.py +10 -0
  214. meltygui/editor/source_ui.py +70 -0
  215. meltygui/editor/spell_check.py +77 -0
  216. meltygui/editor/text_editor.py +9076 -0
  217. meltygui/editor/usage_picker.py +538 -0
  218. meltygui/events/__init__.py +0 -0
  219. meltygui/events/example.py +118 -0
  220. meltygui/examples/__init__.py +0 -0
  221. meltygui/examples/columns_demo.py +33 -0
  222. meltygui/examples/columns_window_demo.py +60 -0
  223. meltygui/examples/context_menu_demo.py +24 -0
  224. meltygui/examples/context_menu_window_demo.py +49 -0
  225. meltygui/examples/gui_playground.py +127 -0
  226. meltygui/examples/live_view_playground.py +256 -0
  227. meltygui/examples/lora.py +43 -0
  228. meltygui/examples/lora_data.py +59 -0
  229. meltygui/examples/lora_policies.py +67 -0
  230. meltygui/examples/mode_demo.py +49 -0
  231. meltygui/examples/modifies_demo.py +119 -0
  232. meltygui/examples/scalar_policies.py +60 -0
  233. meltygui/examples/style_layouts.py +149 -0
  234. meltygui/examples/tile_manager_demo.py +74 -0
  235. meltygui/examples/tint_demo.py +160 -0
  236. meltygui/examples/tint_functions.py +56 -0
  237. meltygui/examples/trace_demo.py +88 -0
  238. meltygui/examples/two_windows.py +36 -0
  239. meltygui/files/__init__.py +0 -0
  240. meltygui/files/fast_file_explorer.py +439 -0
  241. meltygui/gnome_extension/lsd-window-geometry@latent-descent/extension.js +237 -0
  242. meltygui/gnome_extension/lsd-window-geometry@latent-descent/lsd-window-geometry@latent-descent.iml +9 -0
  243. meltygui/gnome_extension/lsd-window-geometry@latent-descent/metadata.json +7 -0
  244. meltygui/graphics/__init__.py +6 -0
  245. meltygui/graphics/base.py +85 -0
  246. meltygui/graphics/examples.py +507 -0
  247. meltygui/graphics/executor.py +520 -0
  248. meltygui/graphics/filter.py +667 -0
  249. meltygui/graphics/filter.pyi +856 -0
  250. meltygui/graphics/generate_stubs.py +22 -0
  251. meltygui/graphics/registry.py +203 -0
  252. meltygui/graphics/shader_compiler.py +155 -0
  253. meltygui/graphics/shaders.py +1302 -0
  254. meltygui/graphics/stub_generator.py +250 -0
  255. meltygui/graphics/texture_manager.py +170 -0
  256. meltygui/graphics/texture_min_max.py +295 -0
  257. meltygui/hdr_color.py +757 -0
  258. meltygui/image_load.py +308 -0
  259. meltygui/model/__init__.py +1 -0
  260. meltygui/model/account_model.py +142 -0
  261. meltygui/model/camera_model.py +159 -0
  262. meltygui/model/chat_model.py +86 -0
  263. meltygui/model/code_model.py +62 -0
  264. meltygui/model/code_proxy_model.py +876 -0
  265. meltygui/model/collection_model.py +41 -0
  266. meltygui/model/color_model.py +127 -0
  267. meltygui/model/cuda_tensor_model.py +36 -0
  268. meltygui/model/cuda_texture_model.py +149 -0
  269. meltygui/model/dropdown_model.py +137 -0
  270. meltygui/model/file_metadata_model.py +73 -0
  271. meltygui/model/file_model.py +289 -0
  272. meltygui/model/format_model.py +390 -0
  273. meltygui/model/graph_model.py +98 -0
  274. meltygui/model/icon_model.py +1024 -0
  275. meltygui/model/import_graph_model.py +468 -0
  276. meltygui/model/layout_model.py +15 -0
  277. meltygui/model/lut_model.py +266 -0
  278. meltygui/model/search_model.py +173 -0
  279. meltygui/model/tensor_model.py +402 -0
  280. meltygui/model/terminal_model.py +329 -0
  281. meltygui/model/texture_model.py +146 -0
  282. meltygui/model/tile_model.py +24 -0
  283. meltygui/model/trace_model.py +198 -0
  284. meltygui/model/trace_report_model.py +146 -0
  285. meltygui/models/__init__.py +0 -0
  286. meltygui/models/file_meta.py +613 -0
  287. meltygui/models/function_console.py +184 -0
  288. meltygui/models/orchestration.py +48 -0
  289. meltygui/pbr.py +1576 -0
  290. meltygui/png_unfilter.c +49 -0
  291. meltygui/resources/JetBrainsMono-Regular.ttf +0 -0
  292. meltygui/resources/THIRD_PARTY_NOTICES.md +21 -0
  293. meltygui/resources/dejavu/DejaVuSans-Bold.ttf +0 -0
  294. meltygui/resources/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
  295. meltygui/resources/dejavu/DejaVuSans.ttf +0 -0
  296. meltygui/resources/dejavu/LICENSE.txt +78 -0
  297. meltygui/resources/fontawesome-LICENSE.txt +121 -0
  298. meltygui/resources/fontawesome-webfont.ttf +0 -0
  299. meltygui/resources/hdri/studio_small_09_1k.hdr +0 -0
  300. meltygui/resources/jetbrains-weights/JetBrainsMono-Bold.ttf +0 -0
  301. meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraBold.ttf +0 -0
  302. meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraLight.ttf +0 -0
  303. meltygui/resources/jetbrains-weights/JetBrainsMono-Light.ttf +0 -0
  304. meltygui/resources/jetbrains-weights/JetBrainsMono-Medium.ttf +0 -0
  305. meltygui/resources/jetbrains-weights/JetBrainsMono-SemiBold.ttf +0 -0
  306. meltygui/resources/jetbrains-weights/JetBrainsMono-Thin.ttf +0 -0
  307. meltygui/resources/jetbrains-weights/OFL.txt +93 -0
  308. meltygui/resources/jetbrains-weights/README.md +2 -0
  309. meltygui/state/__init__.py +0 -0
  310. meltygui/state/account_state.py +22 -0
  311. meltygui/state/animation_state.py +97 -0
  312. meltygui/state/annotation_state.py +22 -0
  313. meltygui/state/chat_state.py +36 -0
  314. meltygui/state/code_state.py +10 -0
  315. meltygui/state/core_enums.py +59 -0
  316. meltygui/state/core_markers.py +115 -0
  317. meltygui/state/core_undo.py +1075 -0
  318. meltygui/state/file_state.py +75 -0
  319. meltygui/state/graph_state.py +26 -0
  320. meltygui/state/inspection_state.py +47 -0
  321. meltygui/state/menu_state.py +10 -0
  322. meltygui/state/model_enums.py +11 -0
  323. meltygui/state/new_core_model.py +2394 -0
  324. meltygui/state/orchestration_state.py +14 -0
  325. meltygui/state/query_state.py +24 -0
  326. meltygui/state/tensor_state.py +10 -0
  327. meltygui/state/terminal_state.py +23 -0
  328. meltygui/state/trace_state.py +32 -0
  329. meltygui/state/voxel_state.py +12 -0
  330. meltygui/text_index.py +816 -0
  331. meltygui/utils/__init__.py +0 -0
  332. meltygui/utils/jump_to_code.py +344 -0
  333. meltygui/utils/pkl_inspect.py +90 -0
  334. meltygui/utils/render_utils.py +1419 -0
  335. meltygui/view/__init__.py +1 -0
  336. meltygui/view/account_view.py +524 -0
  337. meltygui/view/action_view.py +73 -0
  338. meltygui/view/chat_decoration_view.py +112 -0
  339. meltygui/view/chat_view.py +1703 -0
  340. meltygui/view/code_view.py +2677 -0
  341. meltygui/view/collection_view.py +1185 -0
  342. meltygui/view/color_view.py +824 -0
  343. meltygui/view/control_view.py +559 -0
  344. meltygui/view/decoration_view.py +439 -0
  345. meltygui/view/diagnostic_view.py +177 -0
  346. meltygui/view/dropdown_view.py +1082 -0
  347. meltygui/view/file_view.py +1599 -0
  348. meltygui/view/graph_cuda_view.py +105 -0
  349. meltygui/view/graph_view.py +882 -0
  350. meltygui/view/header_view.py +848 -0
  351. meltygui/view/input_view.py +238 -0
  352. meltygui/view/inspection_view.py +1783 -0
  353. meltygui/view/layout_view.py +596 -0
  354. meltygui/view/lut_view.py +45 -0
  355. meltygui/view/menu_view.py +212 -0
  356. meltygui/view/orchestration_view.py +658 -0
  357. meltygui/view/query_view.py +118 -0
  358. meltygui/view/search_view.py +342 -0
  359. meltygui/view/tab_view.py +258 -0
  360. meltygui/view/tensor_view.py +430 -0
  361. meltygui/view/terminal_view.py +355 -0
  362. meltygui/view/text_view.py +8031 -0
  363. meltygui/view/texture_view.py +480 -0
  364. meltygui/view/tile_view.py +38 -0
  365. meltygui/view/trace_view.py +923 -0
  366. meltygui/view/voxel_cuda_view.py +824 -0
  367. meltygui/view/voxel_view.py +1860 -0
  368. meltygui/view/window_view.py +111 -0
  369. meltygui-0.1.0.dist-info/METADATA +143 -0
  370. meltygui-0.1.0.dist-info/RECORD +372 -0
  371. meltygui-0.1.0.dist-info/WHEEL +4 -0
  372. meltygui-0.1.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,392 @@
1
+ """Transparent function instrumentation: run an in-memory TWIN of a function
2
+ whose every assignment also publishes its value through live_view. The source
3
+ file never changes — the editor keeps showing the original code — and the
4
+ captured values key and attach exactly as hand-written live_view() calls
5
+ would, so the whole marker/window/watcher stack works unchanged.
6
+
7
+ How the rendezvous holds with NO live_view tokens in the file: every injected
8
+ call is stamped with the ORIGINAL line number of the assignment it follows
9
+ (ast.copy_location), and the twin compiles against the real co_filename. At
10
+ publish time, capture-side site resolution for a line that has no live_view
11
+ call falls back to the statement's own CST key — ('x',), ('if##0', 'z'), or a
12
+ ('line:N',) key for statements the dict conversion doesn't surface (aug-
13
+ assigns, while/with bodies — which therefore get coverage here that manual
14
+ live_view() calls can't anchor). The store object resolves by co_filename +
15
+ lineno to the ORIGINAL function, not the twin.
16
+
17
+ The twin is rebuilt per code version: the cache keys on the original
18
+ function's CURRENT __code__ identity, and hotswap replaces __code__ in place,
19
+ so an edit + recompile transparently re-instruments from the new source.
20
+
21
+ Out of scope (the twin falls back to running the ORIGINAL uninstrumented
22
+ function): closures (free variables can't be rebuilt standalone), generators
23
+ and async functions. Nested defs/classes/lambdas inside an instrumented
24
+ function run uninstrumented, and recursive calls re-enter the ORIGINAL
25
+ function — only the top frame snapshots.
26
+ """
27
+
28
+ import ast
29
+ import inspect
30
+ import linecache
31
+ import sys
32
+ import textwrap
33
+ import weakref
34
+ from pathlib import Path
35
+
36
+ from meltygui.code.live_view import live_view
37
+ from meltygui.code.live_view import twin_snap
38
+ from meltygui.code.live_view import twin_ret
39
+ from meltygui.code.live_view import _loop_name
40
+
41
+ # id(original __code__) -> ((source mtime, pending gen), twin function |
42
+ # original on fallback). Identity-keyed for the same reason as
43
+ # live_view._sites: code objects compare equal across files; weakref.finalize
44
+ # evicts when hotswap drops the lastref. The signature is the SEAMLESS-EDIT
45
+ # half: the twin builds from the file's IN-MEMORY text (disk + PendingSave
46
+ # splices - deferred saves never reach disk before shutdown), so both a plain
47
+ # disk save (mtime) and a queued editor edit (pending gen) rebuild the twin
48
+ # even though fn.__code__ hasn't been hotswapped - otherwise Run keeps
49
+ # replaying the pre-edit code and "nothing updates".
50
+ _twins = {}
51
+
52
+ _SNAP_NAME = "__lv_view__"
53
+ _RET_NAME = "__lv_ret__"
54
+
55
+
56
+ def run_instrumented(fn, *args, **kwargs):
57
+ """Call `fn` with assignment snapshots publishing to its live_view store.
58
+ Equivalent to fn(*args, **kwargs) — same return value, same exceptions —
59
+ with the original function running un-instrumented when the source can't
60
+ be transformed (closure/generator/async/unparseable).
61
+
62
+ A successful instrumented run also PRUNES the store (run_capture): the
63
+ twin republishes every assignment the function still contains, so keys
64
+ it didn't touch belong to removed lines and are dropped — values,
65
+ markers, and their orphaned value windows. The uninstrumented fallback
66
+ republishes nothing and must not prune."""
67
+ try:
68
+ target = inspect.unwrap(fn)
69
+ except Exception:
70
+ target = fn
71
+ twin = instrumented_twin(fn)
72
+ try:
73
+ if twin is target:
74
+ return fn(*args, **kwargs)
75
+ from meltygui.code.live_view import run_capture
76
+ # Fresh run, fresh exit line: a raise (or an edit that removed the
77
+ # return the last run took) must not leave a stale green line, and a
78
+ # now-passing run must not keep the previous failure's red one.
79
+ try:
80
+ vars(target).pop("__live_return_line__", None)
81
+ vars(target).pop("__live_error_line__", None)
82
+ except TypeError:
83
+ pass
84
+ with run_capture(target):
85
+ try:
86
+ return twin(*args, **kwargs)
87
+ except BaseException as e:
88
+ _stamp_error_line(target, twin, e)
89
+ # An out-of-memory run leaves the live set holding onto VRAM;
90
+ # arm the deferred sweep (runs after these frames unwind).
91
+ try:
92
+ from meltygui.core.runtime.gc_manager import respond_to_cuda_oom
93
+ respond_to_cuda_oom(e, where=getattr(target, "__name__", "run"))
94
+ except Exception:
95
+ pass
96
+ raise
97
+ finally:
98
+ pass
99
+ # # Retire the PREVIOUS run's garbage: the GUI's gc_manager kee
100
+ # gen2 out of auto-reach, so the cycle-trapped graphs each run
101
+ # replaces (deepcopied components, the old ForwardPassResult) pin
102
+ # their CUDA tensors until an explicit collect. This runs on the
103
+ # run's worker thread, win or lose - the garbage exists either way.ps
104
+ # try:
105
+ # from meltygui.core.runtime.gc_manager import collect_after_run
106
+ # collect_after_run(getattr(target, "__name__", "run"))
107
+ # except Exception:
108
+ # pass
109
+
110
+
111
+ def _stamp_error_line(target, twin, exc):
112
+ """Stamp `__live_error_line__` = (absolute file line, message, run-time
113
+ line text) on the
114
+ store function for an exception raised during an instrumented run: the
115
+ DEEPEST traceback frame that is the twin's own code — the twin compiles
116
+ against the real co_filename with original linenos, so tb_lineno is the
117
+ file line the editor should wash red (the live-run twin of the routed
118
+ error markers). An error that never entered the twin (bad args, etc.)
119
+ stamps nothing."""
120
+ try:
121
+ from meltygui.code.live_view import stamp_run_marker
122
+ from meltygui.code.live_view import _line_text_at
123
+ code = getattr(twin, "__code__", None)
124
+ lineno = None
125
+ tb = exc.__traceback__
126
+ while tb is not None:
127
+ if tb.tb_frame.f_code is code:
128
+ lineno = tb.tb_lineno
129
+ tb = tb.tb_next
130
+ if lineno is not None:
131
+ stamp_run_marker(target, "__live_error_line__",
132
+ (lineno, f"{type(exc).__name__}: {exc}",
133
+ _line_text_at(target, lineno)))
134
+ except Exception:
135
+ pass
136
+
137
+
138
+ def instrumented_twin(fn):
139
+ """The instrumented twin of `fn` for its CURRENT SOURCE — cached, rebuilt
140
+ after a hotswap (new __code__), a plain disk save (new mtime), OR a
141
+ deferred in-editor edit (new PendingSave gen: the twin compiles from the
142
+ file's in-memory text, the same text Ctrl+Enter's hotswap compiles, so
143
+ Run executes the latest code even though deferred saves never reach
144
+ disk). Returns `fn` itself when instrumentation isn't possible, so
145
+ callers never need a fallback."""
146
+ try:
147
+ fn = inspect.unwrap(fn)
148
+ except Exception:
149
+ return fn
150
+ code = getattr(fn, "__code__", None)
151
+ if code is None:
152
+ return fn
153
+ try:
154
+ mtime = Path(code.co_filename).stat().st_mtime
155
+ except OSError:
156
+ mtime = None
157
+ sig = (mtime, _pending_gen(code.co_filename))
158
+ cached = _twins.get(id(code))
159
+ if cached is not None and cached[0] == sig:
160
+ return cached[1]
161
+ pending = _pending_state(code.co_filename)[1] # O(file) - miss only
162
+ try:
163
+ linecache.checkcache(code.co_filename) # getsourcelines must see the save
164
+ twin = _build_twin(fn, pending)
165
+ except Exception as e:
166
+ print(f"live_instrument: falling back to uninstrumented "
167
+ f"{getattr(fn, '__qualname__', fn)}: {e!r}", file=sys.stderr)
168
+ twin = fn
169
+ if cached is None:
170
+ weakref.finalize(code, _twins.pop, id(code), None)
171
+ _twins[id(code)] = (sig, twin)
172
+ return twin
173
+
174
+
175
+ def _pending_gen(path):
176
+ """Combined pending-edit generation for `path` — the CHEAP half of the
177
+ cache signature (dict lookups only; never builds text). Sums the raw and
178
+ resolved Path keys: queue_save keys the counter on the address's own
179
+ path value (new_converters does the same dual lookup); only monotonicity
180
+ matters."""
181
+ try:
182
+ from meltygui.editor.pending_save import PendingSave
183
+ p = Path(path)
184
+ gen = PendingSave.pending_gen_for(p)
185
+ try:
186
+ rp = p.resolve()
187
+ if rp != p:
188
+ gen += PendingSave.pending_gen_for(rp)
189
+ except OSError:
190
+ pass
191
+ return gen
192
+ except Exception:
193
+ return 0
194
+
195
+
196
+ def _pending_state(path):
197
+ """(pending edit generation, in-memory file text | None) for a
198
+ co_filename. The text build is O(file) (current_file_text splices) — a
199
+ MISS-only cost: callers compare a (mtime, _pending_gen) signature first
200
+ and only then call this. gen 0 → nothing queued → (0, None) and the twin
201
+ builds straight from disk via getsourcelines."""
202
+ gen = _pending_gen(path)
203
+ if not gen:
204
+ return 0, None
205
+ try:
206
+ from meltygui.editor.pending_save import PendingSave
207
+ return gen, PendingSave.current_file_text(Path(path))
208
+ except Exception:
209
+ return gen, None
210
+
211
+
212
+ def _delta_above(path, lineno):
213
+ """Net line-count change of queued span edits fully ABOVE 1-indexed disk
214
+ line `lineno` — bridges the live code's DISK coordinates (the
215
+ co_firstlineno invariant) to positions in the pending text; the same sum
216
+ text_editor's _pending_line_delta computes, without its buffer-cache
217
+ plumbing."""
218
+ from meltygui.editor.pending_save import PendingSave
219
+ try:
220
+ rp = Path(path).resolve()
221
+ except OSError:
222
+ return 0
223
+ delta = 0
224
+ for addr, (codec, kwargs) in list(PendingSave.pending_saves.items()):
225
+ data = kwargs.get("data")
226
+ start, end = getattr(addr, "start", None), getattr(addr, "end", None)
227
+ if (not isinstance(data, str) or start is None or end is None
228
+ or end > lineno - 1):
229
+ continue
230
+ try:
231
+ if Path(addr.path).resolve() != rp:
232
+ continue
233
+ except OSError:
234
+ continue
235
+ d = data[:-1] if data.endswith("\n") else data
236
+ delta += (d.count("\n") + 1) - (end - start)
237
+ return delta
238
+
239
+
240
+ def _fn_source(fn, pending):
241
+ """(source text incl. decorators, 1-indexed anchor line in DISK coords)
242
+ of the function's CURRENT code. With nothing queued (`pending` is None)
243
+ this is exactly inspect.getsourcelines. Otherwise the def is located in
244
+ the pending text — by name, nearest its expected (delta-shifted) line —
245
+ and the anchor maps BACK to disk coordinates so the injected snapshot
246
+ linenos keep matching the hotswapped code's disk-coord stamps and the
247
+ editor overlay's line math (disk span start + pending-relative offset)."""
248
+ if pending is None:
249
+ src_lines, start = inspect.getsourcelines(fn)
250
+ return "".join(src_lines), start
251
+ code = fn.__code__
252
+ delta = _delta_above(code.co_filename, code.co_firstlineno)
253
+ want = code.co_firstlineno + delta
254
+ best = None
255
+ for node in ast.walk(ast.parse(pending)):
256
+ if (isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
257
+ and node.name == code.co_name):
258
+ score = abs(node.lineno - want)
259
+ if best is None or score < best[0]:
260
+ best = (score, node)
261
+ if best is None: # def renamed/deleted in the pending text
262
+ src_lines, start = inspect.getsourcelines(fn)
263
+ return "".join(src_lines), start
264
+ node = best[1]
265
+ start = min([node.lineno] + [d.lineno for d in node.decorator_list])
266
+ lines = pending.split("\n")
267
+ return ("\n".join(lines[start - 1:node.end_lineno]) + "\n",
268
+ start - delta)
269
+
270
+
271
+ def _build_twin(fn, pending=None):
272
+ code = fn.__code__
273
+ if code.co_freevars:
274
+ return fn # a standalone def can't rebind another frame's cells
275
+ if code.co_flags & (inspect.CO_GENERATOR | inspect.CO_COROUTINE
276
+ | inspect.CO_ASYNC_GENERATOR):
277
+ return fn
278
+
279
+ src, start = _fn_source(fn, pending)
280
+ tree = ast.parse(textwrap.dedent(src))
281
+ ast.increment_lineno(tree, start - 1)
282
+ fdef = tree.body[0]
283
+ if not isinstance(fdef, (ast.FunctionDef, ast.AsyncFunctionDef)):
284
+ return fn
285
+ # The twin is a plain function: re-running decorators would re-register
286
+ # wrappers (render_funcs, etc.) against the twin.
287
+ fdef.decorator_list = []
288
+
289
+ fdef.body = _inject_snaps(fdef.body)
290
+ ast.fix_missing_locations(tree)
291
+ code_obj = compile(tree, code.co_filename, "exec")
292
+
293
+ # A COPY of the function's globals: the twin's def lands here instead of
294
+ # the real module (no name pollution). The snap name is twin_snap - the
295
+ # fast line-keyed publish path (no per-span libcst site resolution; see
296
+ # live_view.twin_snap) - bound directly and crucially with NO wrapper
297
+ # call in between: it reads sys._getframe(1), so the injected call must
298
+ # be the twin's own frame for the stamp to carry the original
299
+ # assignment's file + line. Manual live_view() calls in the body still
300
+ # resolve through the real/builtin live_view binding.
301
+ namespace = dict(fn.__globals__)
302
+ namespace[_SNAP_NAME] = twin_snap
303
+ namespace[_RET_NAME] = twin_ret
304
+ exec(code_obj, namespace)
305
+ twin = namespace[fdef.name]
306
+ twin.__qualname__ = fn.__qualname__ + ".<instrumented>"
307
+ return twin
308
+
309
+
310
+ _BLOCK_FIELDS = ("body", "orelse", "finalbody")
311
+
312
+
313
+ def _inject_snaps(body, loops=()):
314
+ """Insert `__lv_view__(<name>, name='<name>')` after every single-Name
315
+ assignment in `body`, recursing into control-flow blocks but NOT into
316
+ nested defs/classes (their locals live in other frames). Each injected
317
+ call copies the assignment's location, which IS the key rendezvous.
318
+
319
+ `loops` is the static chain of enclosing-loop dim names (outermost
320
+ first): recursing into a For/While BODY extends it with the loop's auto
321
+ name (live_view._loop_name — the loop variable, 'iter' for while), and a
322
+ snap under a non-empty chain gains `dims=(<names>…)`, which switches its
323
+ publish from overwrite to accumulation (live_view._accumulate). A loop's
324
+ `else:` runs once and keeps the outer chain."""
325
+ out = []
326
+ for stmt in body:
327
+ if isinstance(stmt, ast.Return):
328
+ # `return X` → `return __lv_ret__(X)` (bare return passes None):
329
+ # the hook stamps the store module's __live_return_line__ with
330
+ # this statement's disk line - see live_view.twin_ret.
331
+ _rcall = ast.Call(
332
+ func=ast.Name(id=_RET_NAME, ctx=ast.Load()),
333
+ args=[stmt.value if stmt.value is not None
334
+ else ast.Constant(value=None)],
335
+ keywords=[])
336
+ ast.copy_location(_rcall, stmt)
337
+ ast.copy_location(_rcall.func, stmt)
338
+ stmt.value = _rcall
339
+ out.append(stmt)
340
+ name = _snap_target(stmt)
341
+ if name is not None:
342
+ keywords = [ast.keyword(arg="name",
343
+ value=ast.Constant(value=name))]
344
+ if loops:
345
+ keywords.append(ast.keyword(
346
+ arg="dims",
347
+ value=ast.Tuple(elts=[ast.Constant(value=d)
348
+ for d in loops],
349
+ ctx=ast.Load())))
350
+ call = ast.Expr(value=ast.Call(
351
+ func=ast.Name(id=_SNAP_NAME, ctx=ast.Load()),
352
+ args=[ast.Name(id=name, ctx=ast.Load())],
353
+ keywords=keywords))
354
+ ast.copy_location(call, stmt)
355
+ for child in ast.walk(call):
356
+ ast.copy_location(child, stmt)
357
+ out.append(call)
358
+ continue
359
+ if isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef,
360
+ ast.ClassDef)):
361
+ continue
362
+ if isinstance(stmt, (ast.For, ast.AsyncFor, ast.While)):
363
+ stmt.body = _inject_snaps(stmt.body, loops + (_loop_name(stmt),))
364
+ stmt.orelse = _inject_snaps(stmt.orelse, loops)
365
+ else:
366
+ for field in _BLOCK_FIELDS:
367
+ sub = getattr(stmt, field, None)
368
+ if isinstance(sub, list) and sub:
369
+ setattr(stmt, field, _inject_snaps(sub, loops))
370
+ for handler in getattr(stmt, "handlers", None) or []:
371
+ handler.body = _inject_snaps(handler.body, loops)
372
+ for case in getattr(stmt, "cases", None) or []:
373
+ case.body = _inject_snaps(case.body, loops)
374
+ return out
375
+
376
+
377
+ def _snap_target(stmt):
378
+ """The name to snapshot after `stmt`: single-Name (or first-Name chained)
379
+ assignment targets only — tuple unpacks and attribute/subscript targets
380
+ have no single frame-local to read."""
381
+ if isinstance(stmt, ast.Assign):
382
+ for target in stmt.targets:
383
+ if isinstance(target, ast.Name):
384
+ return target.id
385
+ return None
386
+ if isinstance(stmt, (ast.AugAssign, ast.AnnAssign)):
387
+ if isinstance(stmt.target, ast.Name):
388
+ # An AnnAssign without a value (`x: int`) binds rather to read.
389
+ if isinstance(stmt, ast.AnnAssign) and stmt.value is None:
390
+ return None
391
+ return stmt.target.id
392
+ return None