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,98 @@
1
+ """Shared studio session-status file, written by the studio and read by the MCP server.
2
+
3
+ The launcher's ``status`` tool can only tell whether a studio *thread* is alive
4
+ (running vs idle). It can't tell *why* a session ended: a clean user quit looks
5
+ the same as a crash. That ambiguity means a transient "studio idle" reads like a
6
+ bug to chase when it's usually the user closing or restarting the window.
7
+
8
+ This module is the bridge. The studio stamps a small JSON file on startup
9
+ (``state: "running"``) and again on shutdown (``state: "stopped"`` + a ``reason``
10
+ classifying the exit). The MCP server reads that file in its ``status`` tool, so
11
+ a caller can distinguish "the user quit" / "the user restarted" from "it crashed".
12
+
13
+ Reasons:
14
+ user_quit — the window close button (clean, expected)
15
+ restart — interrupt propagated into the render loop (KeyboardInterrupt/SystemExit)
16
+ stopped — programmatic close: Ctrl+Enter re-run (via cleanup_all) or launcher
17
+ shutdown — not a crash
18
+ crash — an exception propagated out of the render loop (a bug worth chasing)
19
+
20
+ Only `crash` indicates a bug; the rest are expected, environmental exits.
21
+ """
22
+
23
+ import json
24
+ import os
25
+ import time
26
+ from pathlib import Path
27
+
28
+ # session_status.py -> src/lsd/gl_gui/session_status.py -> parents[3] -> latent-descent/
29
+ # Same depth/root as mcp_server.py so both agree on the .melty location.
30
+ from meltygui.core.runtime.paths import cache_root
31
+ STATE_DIR = cache_root()
32
+ STATUS_PATH = STATE_DIR / "session_status.json"
33
+
34
+
35
+ def _write(payload):
36
+ try:
37
+ STATE_DIR.mkdir(parents=True, exist_ok=True)
38
+ # Stamp atomically so a concurrent reader never sees a half-written file.
39
+ tmp = STATUS_PATH.with_suffix(".json.tmp")
40
+ tmp.write_text(json.dumps(payload, indent=2))
41
+ os.replace(tmp, STATUS_PATH)
42
+ except Exception as e:
43
+ # Status bookkeeping must never take down the studio.
44
+ print(f"session_status: failed to write {STATUS_PATH}: {e}")
45
+
46
+
47
+ def mark_running(pid=None):
48
+ """Record that a studio session has started its render loop."""
49
+ _write({
50
+ "state": "running",
51
+ "pid": pid if pid is not None else os.getpid(),
52
+ "started_at": time.time(),
53
+ })
54
+
55
+
56
+ def mark_stopped(reason, error=None):
57
+ """Record that the studio render loop has exited, and why.
58
+
59
+ `reason` is one of user_quit / restart / crash / unknown. `error` is an
60
+ optional short string (e.g. the exception message) for crashes.
61
+ """
62
+ payload = read() or {}
63
+ payload.update({
64
+ "state": "stopped",
65
+ "reason": reason,
66
+ "stopped_at": time.time(),
67
+ "pid": os.getpid(),
68
+ })
69
+ if error is not None:
70
+ payload["error"] = str(error)[:1000]
71
+ _write(payload)
72
+
73
+
74
+ def read():
75
+ """Return the parsed status dict, or None if missing/unreadable."""
76
+ try:
77
+ if not STATUS_PATH.exists():
78
+ return None
79
+ return json.loads(STATUS_PATH.read_text())
80
+ except Exception:
81
+ return None
82
+
83
+
84
+ def summary():
85
+ """One-line human/agent-readable summary of the last recorded session state."""
86
+ data = read()
87
+ if not data:
88
+ return "no session status recorded yet"
89
+ state = data.get("state", "unknown")
90
+ if state == "running":
91
+ started = data.get("started_at")
92
+ ago = f" ({int(time.time() - started)}s ago)" if started else ""
93
+ return f"studio session running (pid {data.get('pid')}, started{ago})"
94
+ reason = data.get("reason", "unknown")
95
+ msg = f"studio session stopped — reason: {reason}"
96
+ if data.get("error"):
97
+ msg += f" — {data['error']}"
98
+ return msg
@@ -0,0 +1,445 @@
1
+ """Stack trace view — the CODE behind a stack trace, not the usual file:line
2
+ text dump. `draw_any(exception)` / `draw_any(traceback)` routes here: every
3
+ frame draws just its span — the function's def line down to the call into the
4
+ next frame — as a small draw_text, stacked outermost-first, so the whole
5
+ trace reads top-to-bottom like one continuous file that starts at main and
6
+ ends on the line that raised.
7
+
8
+ Data access goes through `project_code` (core_conversion/project_code.py):
9
+ each pane's text is a LineRange over the file's PENDING truth (same caches
10
+ and queue as code_file_io — disk is never read when a cache can answer), so
11
+ panes are editable and stay in sync with the code editor; an edit writes back
12
+ through the range proxy (one queued whole-file entry, the editor-keystroke
13
+ shape) and _shift_panes moves the other panes' spans by the line delta.
14
+
15
+ Everything renders from LOCAL state — nothing is published anywhere global:
16
+ frame locals become per-pane `LocalValueStore`s (live_view.frame_value_store)
17
+ handed to draw_text as `live_store=`, and each pane's `code_dict` is its own
18
+ span parse (cached on the pane, keyed by the file text's identity), so the
19
+ live-value pills/markers/windows draw over the code exactly as in the editor
20
+ while two traces of the same function coexist untouched. Span bounds resolve
21
+ once per capture from the pending-text ast (live_view._ast_for — mtime +
22
+ pending-generation cached).
23
+ """
24
+
25
+ import colorsys
26
+ import types
27
+ from pathlib import Path
28
+
29
+ import meltygui_imgui as imgui
30
+ from meltygui.hdr_color import pack_color
31
+
32
+ from meltygui.core.styling.fonts import Font
33
+ from meltygui.core.conversion.dict_conversion import DictConversion
34
+ from meltygui.core.rendering.render_funcs import RenderFuncs
35
+ from meltygui.core.runtime.toggles import Tint
36
+ from meltygui.code.fileref import Address
37
+ from meltygui.code.fileref import _PROJECT_ROOT
38
+ from meltygui.code.project_code import project_code
39
+ from meltygui.core.core_render import render_func
40
+ from meltygui.core.rendering.core_decoration import no_save
41
+
42
+
43
+ from meltygui.model.trace_model import SavedTrace
44
+
45
+
46
+ class _RaisingLine(Exception):
47
+ """The raising line as draw_text's `error=` marker (see
48
+ `_exception_errors`: `.lineno` = 1-based BUFFER line, `.msg` = the
49
+ message) — the editor's own red gutter chip, click for the wash."""
50
+
51
+ def __init__(self, lineno, msg):
52
+ super().__init__(msg)
53
+ self.lineno = lineno
54
+ self.msg = msg
55
+
56
+
57
+ def stack_frames(value):
58
+ """Normalize `value` into [(path, lineno, function_name, scope), ...]
59
+ with the OUTERMOST frame (main) first and the raising frame last. `scope`
60
+ is a shallow copy of the frame's f_locals when a live frame is available
61
+ (traceback / frame input), else None (FrameSummary / bare tuples).
62
+
63
+ Accepts a live traceback, an exception (its __traceback__), a frame
64
+ object, or an iterable of traceback.FrameSummary / (path, lineno[, name])
65
+ tuples. A traceback only starts at the frame that RAISED under the catch
66
+ site, so the frames above it (catch site up to main) are recovered from
67
+ the outermost traceback frame's f_back chain."""
68
+ if isinstance(value, SavedTrace):
69
+ value = value.frames
70
+ elif isinstance(value, BaseException):
71
+ value = value.__traceback__
72
+ frames = []
73
+ if isinstance(value, types.TracebackType):
74
+ above = []
75
+ outer = value.tb_frame.f_back
76
+ while outer is not None:
77
+ above.append((outer.f_code.co_filename, outer.f_lineno,
78
+ outer.f_code.co_qualname, dict(outer.f_locals)))
79
+ outer = outer.f_back
80
+ frames.extend(reversed(above))
81
+ current = value
82
+ while current is not None:
83
+ frame = current.tb_frame
84
+ frames.append((frame.f_code.co_filename, current.tb_lineno,
85
+ frame.f_code.co_qualname, dict(frame.f_locals)))
86
+ current = current.tb_next
87
+ elif isinstance(value, types.FrameType):
88
+ walker = value
89
+ while walker is not None:
90
+ frames.append((walker.f_code.co_filename, walker.f_lineno,
91
+ walker.f_code.co_qualname, dict(walker.f_locals)))
92
+ walker = walker.f_back
93
+ frames.reverse()
94
+ elif value is not None:
95
+ for entry in value:
96
+ if hasattr(entry, "filename"): # traceback.FrameSummary
97
+ frames.append((entry.filename, entry.lineno,
98
+ getattr(entry, "name", "") or "", None))
99
+ else: # (path, lineno[, name[, scope]])
100
+ path, lineno = entry[0], entry[1]
101
+ scope = entry[3] if len(entry) > 3 \
102
+ and isinstance(entry[3], dict) else None
103
+ frames.append((str(path), int(lineno),
104
+ entry[2] if len(entry) > 2 else "", scope))
105
+ return frames
106
+
107
+
108
+ class _Pane:
109
+ """One frame's resolved render plan. All line fields are 1-based
110
+ PENDING-truth file lines: `first` = def line (or context-window start),
111
+ `last` = end of the call statement, `def_last` = end of the whole def
112
+ (the parse extends to it so a mid-block cut can't break the parse),
113
+ `lineno` = the call line itself."""
114
+ __slots__ = ("path", "lineno", "qualname", "scope", "first", "last",
115
+ "def_last", "store", "resolved", "expand_diff",
116
+ "fold_gen_seen", "view", "address", "file_code",
117
+ "render_memo", "last_height", "ds", "has_def",
118
+ "store_tried", "parse_memo", "parse_armed", "error_marker",
119
+ "error_opened", "seen_height")
120
+
121
+ def __init__(self, path, lineno, qualname, scope):
122
+ self.path = path
123
+ self.lineno = lineno
124
+ self.qualname = qualname
125
+ self.scope = scope
126
+ self.first = None
127
+ self.last = None
128
+ self.def_last = None
129
+ self.store = None
130
+ self.resolved = False
131
+ # Middle-fold switch: starts open; the first manual badge toggle
132
+ # hands the state to individual tracking (None) - the owner-switch
133
+ # dance draw_code_editor's diff folds use.
134
+ self.expand_diff = False
135
+ self.fold_gen_seen = 0
136
+ self.view = None # (file_text_obj, bounds, buffer, parse, indent)
137
+ self.address = None # (bounds, Address) = per only, rebuilt on shift
138
+ self.file_code = None # project_code FileCode (memoized: the lookup
139
+ # resolves the path - syscalls - per call)
140
+ # (bounds, max_span_lines, line_numbers, folds): draw_text kwargs
141
+ # must keep a STABLE IDENTITY frame to frame - two of its
142
+ # staleness checks compare by identity (the merge window memoizes
143
+ # its line-number lists for the same reason), so not one list per
144
+ # frame re-rendered every pane every frame: constant GC, and the
145
+ # baked marker band flickering with every scroll.
146
+ self.render_memo = None
147
+ # Measured layout advance of the last real draw - what the
148
+ # off-screen skip claims with a cursor move instead of rendering.
149
+ self.last_height = None
150
+ self.ds = None # this pane's draw_state, for the skip's evs
151
+ self.has_def = False # bounds found an enclosing def
152
+ self.store_tried = False # lazy store: one build attempt per capture
153
+ self.parse_memo = None # (span_text_obj, cst dict | None)
154
+ self.parse_armed = None # span_text the background parse ran for
155
+ self.error_marker = None # the raising pane's _RaisingMarker (stable identity)
156
+ self.error_opened = False # its context box opened once on first sight
157
+ self.seen_height = None # advance measured while the pane was in view
158
+
159
+ def bounds(self):
160
+ return (self.first, self.last, self.def_last)
161
+
162
+
163
+ def _span_bounds(span_index, lineno):
164
+ """(def_first, def_last, stmt_last) in 1-based lines from a FileCode
165
+ span_index (one ast walk per text version — never a walk per query):
166
+ the innermost def containing `lineno`, and the end of the statement
167
+ STARTING at `lineno` (the call into the next frame — a multi-line call
168
+ spans to its closing paren). Containment includes the def's DECORATOR
169
+ lines (a decorated function's co_firstlineno — the terminal
170
+ render-function frame — points at the first decorator, above the def
171
+ itself); `def_first` is always the def line. def bounds are None for a
172
+ module-level frame."""
173
+ defs, stmt_ends = span_index
174
+ best = None
175
+ for span_start, def_line, end in defs:
176
+ if span_start <= lineno <= end and (best is None
177
+ or def_line > best[0]):
178
+ best = (def_line, end)
179
+ stmt_last = max(lineno, stmt_ends.get(lineno, lineno))
180
+ if best is None:
181
+ return None, None, stmt_last
182
+ return best[0], best[1], stmt_last
183
+
184
+
185
+ def chain_shift(def_indent, call_indent, parent_call_col, indent_step=4):
186
+ """How many COLUMNS to slide a frame's whole view right so the callee's
187
+ BODY lines up with the call line it replaces: the call `self.render()`
188
+ and the lines inside `def render` are the same thing, so they share an
189
+ indentation level — which puts the def line one level ABOVE the call,
190
+ aligned with the call's enclosing block header. Returns
191
+ (shift_columns, call_col): this frame's slide beyond its own def indent,
192
+ and its own call line's DISPLAYED column for the next frame.
193
+
194
+ `parent_call_col` is None for the first frame (no shift). The shift
195
+ never goes negative (clamped at 0): a def already deeper than its target
196
+ — a closure called from a shallow frame — keeps its own indentation."""
197
+ shift = 0
198
+ if parent_call_col is not None:
199
+ shift = max(0, parent_call_col - indent_step - def_indent)
200
+ return shift, shift + call_indent
201
+
202
+
203
+ def _indent_of(row):
204
+ return len(row) - len(row.lstrip())
205
+
206
+
207
+ def _dedent_rows(rows, margin):
208
+ """Strip exactly `margin` leading spaces from every row that carries
209
+ them (blank/shallower rows pass through) — the buffer draw_text sees;
210
+ the view-slide adds the indentation back visually."""
211
+ if margin <= 0:
212
+ return list(rows)
213
+ prefix = " " * margin
214
+ return [row[margin:] if row.startswith(prefix) else row for row in rows]
215
+
216
+
217
+ def _reindent_rows(rows, margin):
218
+ """The inverse of _dedent_rows for writing an edited buffer back into
219
+ file columns: non-blank rows regain the margin."""
220
+ if margin <= 0:
221
+ return list(rows)
222
+ prefix = " " * margin
223
+ return [prefix + row if row.strip() else row for row in rows]
224
+
225
+
226
+ def _span_parse(text):
227
+ """cst dict of a (dedented, syntactically whole) span text, through the
228
+ same converter the code hosts run. None when it doesn't parse — the pane
229
+ then renders without live-value anchors until the next clean text."""
230
+ try:
231
+ from meltygui.core.runtime.toggles import Toggles
232
+ from meltygui.code.libcst_conversion import cst_module_to_dict
233
+ if Toggles.TextEditor.melty_syntax:
234
+ return cst_module_to_dict(text)
235
+ import libcst as cst
236
+ return cst_module_to_dict(cst.parse_module(text))
237
+ except Exception:
238
+ return None
239
+
240
+
241
+ def _resolve_pane(pane, context_lines):
242
+ """Fill a pane's span bounds — CHEAP: the file's span index (one ast
243
+ walk per text version, shared by every pane of the file) plus lookups.
244
+ Runs once per capture. The heavy halves — the value store (a file parse
245
+ for anchors) and the span parse (libcst of the whole def) — build
246
+ lazily at the pane's first VISIBLE draw (_ensure_store / _pane_view),
247
+ so off-screen panes cost nothing at load. Bounds resolve against the
248
+ SAME text the pane renders — the project_code proxy (studio truth:
249
+ sync frame + pending), never a different cache's view."""
250
+ if pane.file_code is None:
251
+ pane.file_code = project_code[pane.path]
252
+ span_index = pane.file_code.span_index()
253
+ if span_index is None:
254
+ pane.resolved = True # unreadable/unparseable: context window
255
+ pane.first = max(1, pane.lineno - context_lines)
256
+ pane.last = pane.def_last = pane.lineno
257
+ return
258
+ def_first, def_last, stmt_last = _span_bounds(span_index, pane.lineno)
259
+ if def_first is None: # module-level frame
260
+ pane.first = max(1, pane.lineno - context_lines)
261
+ pane.def_last = stmt_last
262
+ elif pane.lineno <= def_first:
263
+ # TERMINAL pane: whose lineno IS the def region (co_firstlineno - the
264
+ # render function the trace bottoms out in, whose body wasn't on the
265
+ # stack). No call to cut at - render the whole function.
266
+ pane.first = def_first
267
+ pane.lineno = def_first
268
+ pane.def_last = def_last
269
+ stmt_last = def_last
270
+ else:
271
+ pane.first = def_first
272
+ pane.def_last = def_last
273
+ pane.last = max(stmt_last, pane.first)
274
+ pane.def_last = max(pane.def_last, pane.last)
275
+ pane.has_def = def_first is not None
276
+ pane.resolved = True
277
+
278
+
279
+ def _ensure_store(pane):
280
+ """Build the pane's LocalValueStore on FIRST NEED (its first visible
281
+ draw): the anchor computation resolves the live function and parses the
282
+ file (live_view._ast_for) — too heavy to pay per pane at tab open, and
283
+ an off-screen pane may never need it. One attempt per capture."""
284
+ if (pane.store is not None or pane.store_tried or not pane.scope
285
+ or not pane.has_def):
286
+ return
287
+ pane.store_tried = True
288
+ from meltygui.code.chain_converters import _enclosing_function
289
+ from meltygui.code.live_view import frame_value_store
290
+ fn = _enclosing_function(pane.path, pane.lineno)
291
+ if fn is not None:
292
+ # Reuse the proxy's ast - the very tree the bounds resolved on -
293
+ # instead of _ast_for's second whole-file parse; anchors land in
294
+ # the rendered text's own coordinates.
295
+ pane.store = frame_value_store(
296
+ fn, pane.scope,
297
+ tree=pane.file_code.ast_tree() if pane.file_code else None)
298
+
299
+
300
+ # Defs up to this many lines parse INLINE at their FIRST visible draw (a few
301
+ # ms; the pane shows its live-value anchors on the frame it appears); longer
302
+ # ones parse in the BACKGROUND and the pane renders without anchors until the
303
+ # parse lands — a 6.5k-line def's parse was the old tab-open stall. Every
304
+ # RE-parse (the span's text changed: a keystroke here, an editor typing in
305
+ # the same file) is debounced + backgrounded regardless of size, exactly as
306
+ # code_file_io's chain_in — a keystroke used to re-parse inline, per pane of
307
+ # the file, per frame it landed on (09-01).
308
+ # [tint=(0.9, 0.35, 0.28)]
309
+ PARSE_INLINE_LINES = 300
310
+
311
+
312
+ def _pane_parse(pane, span_text, index):
313
+ """The pane's span cst dict, memoized on the span text's identity.
314
+
315
+ Parses run through run_in_background — the same runner and typing
316
+ debounce (`_chain_in_debounce_ms`, Toggles.TextEditor.parse_debounce_ms /
317
+ small_file_debounce_ms) as the code editor's chain_in, so a burst of
318
+ keystrokes coalesces into ONE parse when the input goes quiet. The first
319
+ parse of a small def is `inline_first` (runs synchronously, anchors on
320
+ the pane's first frame); a giant def's first parse and every re-parse go
321
+ to the worker. While a re-parse is pending the pane keeps its previous
322
+ parse (stale anchors label-snap) or renders plain code. Only panes with
323
+ a value store parse at all.
324
+
325
+ The runner idle-returns its LAST result — inside the debounce window
326
+ that is the previous text's parse — so a result is adopted only when it
327
+ is a NEW object (identity against the memo) or arrives on the report
328
+ edge; stamping the old parse against the new text would have memoized
329
+ it for good and the landed re-parse would never have been read."""
330
+ if pane.store is None:
331
+ return None
332
+ memo = pane.parse_memo
333
+ if memo is not None and memo[0] is span_text:
334
+ return memo[1]
335
+ from meltygui.code.new_converters import run_in_background
336
+ from meltygui.code.new_converters import _chain_in_debounce_ms
337
+ arm = pane.parse_armed is not span_text
338
+ if arm:
339
+ pane.parse_armed = span_text
340
+ small = span_text.count("\n") + 1 <= PARSE_INLINE_LINES
341
+ landed, result = run_in_background(
342
+ _span_parse, child_kwargs={"text": span_text},
343
+ name=f"stack span parse {pane.qualname}##{index}", start=arm,
344
+ inline_first=small, debounce_ms=_chain_in_debounce_ms(span_text))
345
+ previous = memo[1] if memo is not None else None
346
+ if landed or (isinstance(result, dict) and result is not previous):
347
+ pane.parse_memo = (span_text, result if isinstance(result, dict)
348
+ else None)
349
+ return pane.parse_memo[1]
350
+ return previous
351
+
352
+
353
+ def _pane_view(pane, file_code):
354
+ """(buffer, span_text, def_indent) for a pane, memoized on the file
355
+ text's identity + the pane's bounds. `buffer` is the DEDENTED def→call
356
+ slice (draw_text sees dedented text, like every span editor; the
357
+ view-slide re-adds the indentation); `span_text` the dedented WHOLE def
358
+ (always valid Python — the def→call cut can end mid-block), the parse
359
+ input: against the buffer it is one trailing deletion, which the
360
+ overlay's parse→buffer line bridge maps exactly. Parsing itself is the
361
+ CALLER's job (inline for small defs, background for giant ones)."""
362
+ whole = file_code.text()
363
+ if whole is None:
364
+ return None
365
+ bounds = pane.bounds()
366
+ view = pane.view
367
+ if view is not None and view[0] is whole and view[1] == bounds:
368
+ return view[2], view[3], view[4]
369
+ lines = file_code.lines()
370
+ first, last, def_last = bounds
371
+ last = min(last, len(lines))
372
+ def_last = min(def_last, len(lines))
373
+ def_indent = _indent_of(lines[first - 1]) if first <= len(lines) else 0
374
+ span_rows = _dedent_rows(lines[first - 1:def_last], def_indent)
375
+ buffer = "\n".join(span_rows[:last - first + 1])
376
+ span_text = "\n".join(span_rows)
377
+ pane.view = (whole, bounds, buffer, span_text, def_indent)
378
+ return buffer, span_text, def_indent
379
+
380
+
381
+ def _crop_folds(span_rows, call_row, max_span_lines):
382
+ """The fold for a span over `max_span_lines`, in 0-based BUFFER rows
383
+ ((start, end): row `start` stays visible as the fold header, rows
384
+ start+1..end hide). A call mid-span folds the MIDDLE (head below the
385
+ def, tail above the call); a terminal pane (call_row 0 — the whole
386
+ render function) folds the TAIL, keeping the first max_span_lines rows.
387
+ None when the span fits."""
388
+ if span_rows <= max_span_lines:
389
+ return None
390
+ if call_row <= 0:
391
+ return [(max_span_lines - 1, span_rows - 1)]
392
+ keep = max(2, max_span_lines // 2)
393
+ fold_start = keep - 1
394
+ fold_end = call_row - keep
395
+ if fold_end <= fold_start:
396
+ return None
397
+ return [(fold_start, fold_end)]
398
+
399
+
400
+ def _shift_panes(panes, edited_pane, path, edit_line, delta,
401
+ edited_span=None, file_code=None):
402
+ """After an edit in `edited_pane` changed its file's line count by
403
+ `delta` at 1-based `edit_line`, move every OTHER pane of that file so
404
+ the spans keep addressing the same code.
405
+
406
+ With `edited_span` (the edited pane's PRE-edit (first, last)) and the
407
+ file's `file_code`, a sibling whose def the edit did not touch also
408
+ CARRIES its view memo onto the new file text: its dedented rows are the
409
+ same rows, so re-keying the memo (new text object, shifted bounds) keeps
410
+ its `span_text` identity and the parse memo behind it — nothing to
411
+ re-dedent, nothing to re-parse. Content-free: the edit's own span
412
+ decides; a sibling whose def overlaps it (recursion, a closure frame
413
+ inside the edited def) drops its view and rebuilds. Without this every
414
+ pane of the file re-parsed on every keystroke in any one of them."""
415
+ if delta or edited_span is not None:
416
+ new_whole = file_code.text() if file_code is not None else None
417
+ edit_first, edit_last = edited_span or (edit_line, edit_line)
418
+ for pane in panes or ():
419
+ if pane is None or pane is edited_pane or pane.path != path \
420
+ or not pane.resolved:
421
+ continue
422
+ untouched = (edited_span is not None and new_whole is not None
423
+ and pane.first is not None and pane.def_last is not None
424
+ and (pane.def_last < edit_first or pane.first > edit_last))
425
+ if delta:
426
+ if pane.first is not None and pane.first > edit_line:
427
+ pane.first += delta
428
+ if pane.last is not None and pane.last >= edit_line:
429
+ pane.last += delta
430
+ if pane.def_last is not None and pane.def_last >= edit_line:
431
+ pane.def_last += delta
432
+ if pane.lineno >= edit_line:
433
+ pane.lineno += delta
434
+ store = pane.store
435
+ if store is not None:
436
+ def_line = getattr(store, "__def_line__", None)
437
+ if def_line is not None and def_line > edit_line:
438
+ store.__def_line__ = def_line + delta
439
+ if edited_span is None:
440
+ continue
441
+ view = pane.view
442
+ if untouched and view is not None:
443
+ pane.view = (new_whole, pane.bounds()) + view[2:]
444
+ else:
445
+ pane.view = None
@@ -0,0 +1 @@
1
+ """Shared framework files machinery."""