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,923 @@
1
+ """Trace view functions and supporting definitions."""
2
+ from meltygui.core.melty import Melty
3
+ from meltygui.model.trace_model import CrashReportStore
4
+ from meltygui.model.trace_model import SavedTrace
5
+ from meltygui.core.core_render import render_func
6
+ from meltygui.core.rendering.render_funcs import RenderFuncs
7
+ from meltygui.state.trace_state import CrashReportsPanelState
8
+ from meltygui.state.trace_state import StackTraceState
9
+ from meltygui.core.runtime.toggles import Tint
10
+ from meltygui.core.runtime.toggles import Toggles
11
+ import meltygui_imgui as imgui
12
+ import os
13
+ import time
14
+ import types
15
+ from meltygui.hdr_color import pack_color
16
+ from pathlib import Path
17
+ import colorsys
18
+
19
+
20
+ @render_func(is_default_for=(types.TracebackType, BaseException, SavedTrace),use_cache=True, tint=(0.9, 0.35, 0.28))
21
+ def draw_stack_trace(input_value: types.TracebackType | BaseException | SavedTrace,
22
+ draw_state=None, trace_state: StackTraceState = None,
23
+ max_span_lines=80, context_lines=8, freeze_resize=True,
24
+ project_only=True, max_frames=40, indent_views=True,
25
+ hide_dispatch=False, placeholder_lines=None, file_headers=False,
26
+ file_header_indent=18, cull_offscreen=True):
27
+ """One draw_text per frame, outermost (main) first — each pane is a
28
+ LineRange over the frame's file (project_code — pending truth, editable)
29
+ from the def line to the call into the next frame, with the frame's
30
+ locals drawn over it as live-value markers from a pane-local store.
31
+ Knobs: `max_span_lines` folds the middle of a giant span; `project_only`
32
+ hides stdlib / site-packages frames (turned off automatically when it
33
+ would hide everything); `max_frames` caps very deep / recursive stacks,
34
+ keeping the ends and eliding the middle; `indent_views=False` (the
35
+ context menu's Code tab) keeps every pane flush left — buffers are
36
+ dedented, so panes read like stacked defs — instead of the inlined
37
+ call-chain slide. An exception input, or a SavedTrace with an `error`
38
+ message, marks the RAISING line — the last frame's line — with
39
+ draw_text's own error marker (`error=`: the red gutter chip, click for
40
+ the line wash and message); a bare captured call stack (the context
41
+ menu's Code tab, whose last frame is the view being drawn) marks
42
+ nothing. `file_headers=True` reads like a printed trace: each pane sits
43
+ under a `File "path", line N, in func` line (the path in the file's
44
+ painted FileMeta tint — the editor tabs' colour) and is indented by
45
+ `file_header_indent` px. `cull_offscreen=False` turns the viewport
46
+ culling OFF: every pane resolves its bounds synchronously (the file's
47
+ span index inline — the one-time parse the background path defers)
48
+ and lays out at its real height whether or not it is in view, so the
49
+ view's height is right from its first frame and never moves as the
50
+ user scrolls. For a trace nested in a scrolling LIST (the Crash
51
+ Reports window): a parent that scrolls by the child's height can't
52
+ have that height change with the scroll — placeholders that resolved
53
+ to different heights as panes scrolled in made the list jitter.
54
+ `placeholder_lines` is the height, in lines, an UNRESOLVED pane
55
+ below the viewport holds before its bounds are known (default
56
+ max_span_lines + 2, sized for a self-scrolling view); a view nested in
57
+ a scrolling LIST (the Crash Reports window) passes a small number, or
58
+ every collapsed-away trace inflates the list by ~1900 px of empty
59
+ scroll that snaps shorter as panes resolve.
60
+
61
+ Panes fully outside the view's clip skip their draw_text call entirely:
62
+ the cursor advances by the pane's last measured height, so the scroll
63
+ geometry holds while only visible panes pay a render."""
64
+ from meltygui.code.fileref import Address
65
+ from meltygui.code.fileref import _PROJECT_ROOT
66
+ from meltygui.code.project_code import project_code
67
+ from meltygui.core.styling.fonts import Font
68
+ from meltygui.core.diagnostics.trace_core import _Pane
69
+ from meltygui.core.diagnostics.trace_core import _RaisingLine
70
+ from meltygui.core.diagnostics.trace_core import _crop_folds
71
+ from meltygui.core.diagnostics.trace_core import _ensure_store
72
+ from meltygui.core.diagnostics.trace_core import _indent_of
73
+ from meltygui.core.diagnostics.trace_core import _pane_parse
74
+ from meltygui.core.diagnostics.trace_core import _pane_view
75
+ from meltygui.core.diagnostics.trace_core import _reindent_rows
76
+ from meltygui.core.diagnostics.trace_core import _resolve_pane
77
+ from meltygui.core.diagnostics.trace_core import _shift_panes
78
+ from meltygui.core.diagnostics.trace_core import chain_shift
79
+ from meltygui.core.diagnostics.trace_core import stack_frames
80
+
81
+ from meltygui.core.melty import Melty
82
+ # [tint=(0.9, 0.35, 0.28)]
83
+ project_prefix = str(_PROJECT_ROOT)
84
+ # File header line: `File "path", line N, in func` - the path in the
85
+ # file's painted tint, the rest in the subtle-text colour.
86
+ header_height = imgui.get_text_line_height() + Melty.px(6) if file_headers else 0.0
87
+ header_indent = Melty.px(file_header_indent) if file_headers else 0.0
88
+ card_pad_bottom = Melty.px(6) if file_headers else 0.0 # the card drops this far past the pane
89
+ # The raising line's message - the marker's text; "" = no marker.
90
+ if isinstance(input_value, SavedTrace):
91
+ error_message = input_value.error
92
+ elif isinstance(input_value, BaseException):
93
+ error_message = f"{type(input_value).__name__}: {input_value}"
94
+ else:
95
+ error_message = ""
96
+
97
+ # (Re)build the render plan only when the INPUT changes — steady-state
98
+ # frames never re-walk the trace or re-copy files. The knobs join the
99
+ # sig so a live toggle flip rebuilds.
100
+ sig = (project_only, max_frames, hide_dispatch)
101
+ if trace_state.trace_obj is not input_value or trace_state.trace_sig != sig:
102
+ frames = [f for f in stack_frames(input_value)
103
+ if f[0] and not f[0].startswith("<")]
104
+ if hide_dispatch:
105
+ # Renderfunc dispatch machinery (the render_func wrapper /
106
+ # draw_inner_main, draw_any re-dispatch) - the same filter the
107
+ # func-stack labels apply per frame (_is_dispatch_frame).
108
+ from meltygui.code.chain_converters import _is_dispatch_frame
109
+ frames = [f for f in frames
110
+ if not _is_dispatch_frame(f[0], f[2])]
111
+ if project_only:
112
+ kept = [f for f in frames if f[0].startswith(project_prefix)]
113
+ if kept:
114
+ frames = kept
115
+ if len(frames) > max_frames:
116
+ half = max_frames // 2
117
+ frames = frames[:half] + [None] + frames[-half:]
118
+ trace_state.panes = [
119
+ _Pane(*f) if f is not None else None for f in frames]
120
+ trace_state.trace_obj = input_value
121
+ trace_state.trace_sig = sig
122
+ trace_state.scroll_bottom_pending = True
123
+
124
+ panes = trace_state.panes or []
125
+ if not panes:
126
+ RenderFuncs.draw_text("(no readable frames in this stack trace)",
127
+ name="stack trace empty", show_bg=False,
128
+ editable=False, tint=Tint.subtle_text())
129
+ return False, input_value
130
+
131
+ # Each callee's whole VIEW (gutter and all) slides right so its body
132
+ # lines up with the call line it replaces; the shift is columns of the
133
+ # editor's monospace font, converted to pixels here. Buffers are
134
+ # dedented, so the slide carries the def's own indentation too.
135
+ char_width = 0.0
136
+ if indent_views:
137
+ editor_font_handle = Melty.font_mgr.get(Font.FONTAWESOME_MONO_19) \
138
+ if Melty.font_mgr is not None else None
139
+ if editor_font_handle is not None:
140
+ imgui.push_font(editor_font_handle)
141
+ char_width = imgui.calc_text_size("0").x
142
+ imgui.pop_font()
143
+ else:
144
+ char_width = imgui.calc_text_size("0").x
145
+ available_width = draw_state.content_width
146
+ view_clip = draw_state.abs_clip_rect
147
+
148
+ parent_call_col = None
149
+ index_polled = set() # one span-index runner poll per FILE per frame
150
+ # file_headers: every header (and its pane) sits this far in from the
151
+ # card's left edge, and every pane stops this far short of the card's
152
+ # right edge — the card is the full width, the text views narrower.
153
+ # [tint=(0.9, 0.35, 0.28)]
154
+ file_inset = Melty.px(2) if file_headers else 0.0
155
+ # The cards span the view's FULL rect (abs_left / width - not the
156
+ # relative content rect the cursor starts in), edge to edge.
157
+ trace_left = draw_state.abs_left if file_headers else imgui.get_cursor_screen_pos()[0]
158
+ card_width = draw_state.width or available_width
159
+ error_pane = next((p for p in reversed(panes) if p is not None), None) \
160
+ if error_message else None
161
+ for index, pane in enumerate(panes):
162
+ if pane is None:
163
+ RenderFuncs.draw_text("… frames elided …",
164
+ name="stack trace elision", single_line=True,
165
+ show_bg=False, editable=False, freeze_resize=True,
166
+ syntax_highlight=False,
167
+ tint=Tint.subtle_text())
168
+ continue
169
+ # [tint=(0.9, 0.35, 0.28)]
170
+ offscreen_margin = 300.0
171
+ estimated_line_px = 23.0
172
+ cursor_x, cursor_y = imgui.get_cursor_screen_pos()
173
+ if not pane.resolved and not cull_offscreen:
174
+ # No culling: bounds resolve, inline (span_index parses the file
175
+ # synchronously when its memo holds it) - the height is real
176
+ # from this frame on.
177
+ if pane.file_code is None:
178
+ pane.file_code = project_code[pane.path]
179
+ _resolve_pane(pane, context_lines)
180
+ if not pane.resolved:
181
+ placeholder_height = pane.last_height \
182
+ if pane.last_height is not None \
183
+ else ((placeholder_lines if placeholder_lines is not None
184
+ else max_span_lines + 2) * estimated_line_px)
185
+ # Below-viewport pre-skip, BEFORE resolving: bounds need the
186
+ # FILE's ast (span_index - ~100 ms for the biggest files), so a
187
+ # frame-1 pane far below the clip holds its place with a FIXED
188
+ # estimate instead; it renders the frame the scroll brings it
189
+ # near. (The indent chain doesn't advance for this - the Code tab
190
+ # runs indent_views=False anyway, and the playground's chain
191
+ # corrects when the pane really renders.)
192
+ if (view_clip is not None
193
+ and cursor_y > view_clip[3] + offscreen_margin):
194
+ imgui.set_cursor_screen_pos(
195
+ (cursor_x, cursor_y + header_height + placeholder_height))
196
+ imgui.dummy(0, 0)
197
+ continue
198
+ # VISIBLE but unresolved: build the file's span index in the
199
+ # background (its ast parse is the tab-open stall) and hold the
200
+ # pane's place until the worker's result lands; then resolve is a
201
+ # memo lookup. One runner poll per FILE per frame (a second
202
+ # same-name call trips the duplicate-unique guard); an
203
+ # unparseable file completes with a None index - span_index_ready
204
+ # stays False, but the runner's completed edge resolves through
205
+ # the context-window logic.
206
+ if pane.file_code is None:
207
+ pane.file_code = project_code[pane.path]
208
+ if pane.file_code.span_index_ready():
209
+ _resolve_pane(pane, context_lines)
210
+ else:
211
+ from meltygui.code.new_converters import LOADING
212
+ from meltygui.code.new_converters import UNSET
213
+ from meltygui.code.new_converters import run_in_background
214
+ if trace_state.index_armed is None:
215
+ trace_state.index_armed = set()
216
+ arm = pane.path not in trace_state.index_armed
217
+ if arm:
218
+ trace_state.index_armed.add(pane.path)
219
+ result = LOADING
220
+ if pane.path not in index_polled:
221
+ index_polled.add(pane.path)
222
+ _changed, result = run_in_background(
223
+ pane.file_code.span_index, child_kwargs={},
224
+ name=f"stack span index {pane.path}", start=arm)
225
+ if result is LOADING or result is UNSET:
226
+ imgui.set_cursor_screen_pos(
227
+ (cursor_x, cursor_y + header_height + placeholder_height))
228
+ imgui.dummy(0, 0)
229
+ continue
230
+ _resolve_pane(pane, context_lines)
231
+ if pane.file_code is None:
232
+ pane.file_code = project_code[pane.path]
233
+ file_code = pane.file_code
234
+ file_lines = file_code.lines()
235
+ if not file_lines:
236
+ RenderFuncs.draw_text(f"(unreadable: {pane.path})",
237
+ name=f"stack frame unreadable##{index}",
238
+ single_line=True, show_bg=False, freeze_resize=True,
239
+ editable=False, syntax_highlight=False,
240
+ tint=Tint.subtle_text())
241
+ continue
242
+ def_indent = _indent_of(file_lines[pane.first - 1]) \
243
+ if pane.first <= len(file_lines) else 0
244
+ call_row = pane.lineno - pane.first
245
+
246
+ # ── Off-screen skip - BEFORE the heavy lazy builds (store, libcst
247
+ # span parse, dedent): a pane fully outside the view's clip pays
248
+ # nothing but the bounds lookups above. A never-measured pane skips
249
+ # on an ESTIMATED height (visible rows × a nominal line height) so
250
+ # the first frame already renders only what's in view - no tab-open
251
+ # lag of every pane parsing at once; the estimate corrects to the
252
+ # measured height when the pane scrolls in. The body re-runs on
253
+ # scroll, so panes crossing the edge render the frame they matter.
254
+ span_rows = pane.last - pane.first + 1
255
+ skip_height = pane.last_height
256
+ if skip_height is None:
257
+ visible_rows = min(span_rows, placeholder_lines if placeholder_lines is not None
258
+ else max_span_lines + 2)
259
+ skip_height = visible_rows * estimated_line_px + 12.0
260
+ skip_height += header_height
261
+ cursor_x, cursor_y = imgui.get_cursor_screen_pos()
262
+ if (cull_offscreen and view_clip is not None
263
+ and (cursor_y > view_clip[3] + offscreen_margin
264
+ or cursor_y + skip_height
265
+ < view_clip[1] - offscreen_margin)):
266
+ if indent_views:
267
+ _, parent_call_col = chain_shift(
268
+ def_indent, _indent_of(file_lines[pane.lineno - 1])
269
+ if pane.lineno <= len(file_lines) else 0,
270
+ parent_call_col)
271
+ # A skipped pane is DISCARDED, not closed - neither bvh_sync
272
+ # (render-only) nor bvh_query's lazy evict would ever drop its
273
+ # hit boxes, which remain at the on-screen positions in the
274
+ # input of whatever scrolled in under them. Evict once on the
275
+ # rendered→skipped transition; the next real render re-indexes.
276
+ if pane.ds is not None:
277
+ Melty.bvh_evict_window(pane.ds)
278
+ pane.ds = None
279
+ imgui.set_cursor_screen_pos(
280
+ (cursor_x, cursor_y + skip_height))
281
+ imgui.dummy(0, 0)
282
+ continue
283
+
284
+ # Lazy heavy halves, first visible draw only: the live store (a
285
+ # file parse for anchors), then the span parse (libcst of the def).
286
+ _ensure_store(pane)
287
+ view = _pane_view(pane, file_code)
288
+ if view is None:
289
+ RenderFuncs.draw_text(f"(unreadable: {pane.path})",
290
+ name=f"stack frame unreadable##{index}",
291
+ single_line=True, show_bg=False, freeze_resize=True,
292
+ editable=False, syntax_highlight=False,
293
+ tint=Tint.subtle_text())
294
+ continue
295
+ buffer, span_text, def_indent = view
296
+ parse = _pane_parse(pane, span_text, index)
297
+
298
+ span_kwargs = {}
299
+ # This pane's header start: the card's left edge plus file_inset -
300
+ # the same for every pane; the pane's own indent never carries over.
301
+ base_x = trace_left + file_inset if file_headers else cursor_x
302
+ cursor_x = base_x
303
+ if file_headers:
304
+ # The card (full trace width, from the card's left edge) is
305
+ # header + the pane's last measured advance tall (a fresh pane
306
+ # has no draw_state yet and paints no card).
307
+ pane_advance = pane.seen_height if pane.seen_height is not None else \
308
+ (pane.last_height - header_height if pane.last_height else 0.0)
309
+ _draw_file_header(pane, trace_left, cursor_y, card_width,
310
+ header_height + pane_advance + card_pad_bottom,
311
+ text_x=base_x, draw_state=draw_state, index=index)
312
+ cursor_y += header_height
313
+ cursor_x += header_indent
314
+ imgui.set_cursor_screen_pos((cursor_x, cursor_y))
315
+ # Even: the pane stops as far short of the card's right edge as
316
+ # it starts in from the card's left.
317
+ pane_width = card_width - 2 * (cursor_x - trace_left)
318
+ if pane_width > 200:
319
+ span_kwargs["width"] = pane_width
320
+ if indent_views:
321
+ shift_columns, parent_call_col = chain_shift(
322
+ def_indent, _indent_of(file_lines[pane.lineno - 1])
323
+ if pane.lineno <= len(file_lines) else 0, parent_call_col)
324
+ slide_columns = def_indent + shift_columns
325
+ if slide_columns:
326
+ slide_px = slide_columns * char_width
327
+ imgui.set_cursor_screen_pos((cursor_x + slide_px, cursor_y))
328
+ # Keep the slid view's right edge inside the client rect.
329
+ if available_width > slide_px + 200:
330
+ span_kwargs["width"] = available_width - slide_px
331
+ marks_error = pane is error_pane and not (call_row <= 0 and pane.has_def)
332
+ if pane.has_def and not marks_error:
333
+ # Panes open COMPACT: the root def (buffer row 0 - spans are
334
+ # dedented def→call slices) starts collapsed on the pane's first
335
+ # sight; expanding sets the badge, and the user's fold state owns
336
+ # it from then on. Module-frame panes have no def at row 0. The
337
+ # RAISING pane opens expanded: collapsed, the raising line and
338
+ # its marker sat hidden inside the fold.
339
+ span_kwargs["default_collapsed_lines"] = (0,)
340
+ # jump_to (the buffer's file-line offset) rides on every draw, not
341
+ # only once the span parse lands: the editor's floating error box
342
+ # - the raising line's message - is gated on it, and a pane with
343
+ # no value store never parses at all.
344
+ bounds = pane.bounds()
345
+ if pane.address is None or pane.address[0] != bounds:
346
+ pane.address = (bounds, Address(pane.path, pane.first - 1, pane.last))
347
+ span_kwargs["jump_to"] = pane.address[1]
348
+ if parse is not None:
349
+ span_kwargs["code_dict"] = parse
350
+ span_kwargs["live_store"] = pane.store
351
+ # Stable-identity render kwargs, rebuilt only when the span moves
352
+ # (see the render_memo comment on _Pane).
353
+ memo = pane.render_memo
354
+ if memo is None or memo[0] != pane.bounds() \
355
+ or memo[1] != max_span_lines:
356
+ memo = pane.render_memo = (
357
+ pane.bounds(), max_span_lines,
358
+ list(range(pane.first, pane.last + 1)),
359
+ _crop_folds(pane.last - pane.first + 1, call_row,
360
+ max_span_lines))
361
+ folds = memo[3]
362
+ if folds is not None:
363
+ span_kwargs["diff_fold_ranges"] = folds
364
+ span_kwargs["expand_diff"] = pane.expand_diff
365
+ # The raising line's marker (a terminal whole-function pane -
366
+ # call_row 0 with a def - has no raising line). Memoized beside the
367
+ # render kwargs: draw_text compares its kwargs by identity.
368
+ if marks_error:
369
+ marker = pane.error_marker
370
+ if marker is None or marker.lineno != call_row + 1 \
371
+ or marker.msg != error_message:
372
+ marker = pane.error_marker = _RaisingLine(call_row + 1, error_message)
373
+ span_kwargs["error"] = marker
374
+
375
+ edited, new_text, pane_ds = RenderFuncs.draw_text(
376
+ buffer, name=f"stack frame {pane.qualname}##{index}",
377
+ file_key=pane.path,
378
+ line_numbers=memo[2], freeze_resize=True,
379
+ show_header=False, show_file_header=False, show_jump_bar=False,
380
+ gutter_indent=True, shadow=False, bg_offset=-1,
381
+ # Cached: all panes are siblings in one window tile, so with
382
+ # use_cache=False a selection drag in one pane re-ran every
383
+ # span on every frame (10 draw_text bodies, 15ms - 09-01).
384
+ use_cache=True, return_extras=True, **span_kwargs)
385
+ # Measured layout advance (this item + its spacing) — what the
386
+ # off-screen skip reproduces with a cursor move. The wrapper sizes
387
+ # the pane by its whole imgui GROUP, and a pane lying entirely
388
+ # in the fold has an item in draw_text's tail land at the clip
389
+ # edge, so the group (and the cursor the wrapper advances) runs
390
+ # from the pane's top down to the clip bottom - thousands of px
391
+ # for a collapsed def, and different on every scroll. The
392
+ # wrapper's `observed_content_height` is the honest cursor advance
393
+ # of the body itself; when the advance overshoots it by more than
394
+ # a line, lay out by the honest height and put the cursor there.
395
+ advance = max(0.0, imgui.get_cursor_screen_pos()[1] - cursor_y)
396
+ honest = getattr(pane_ds, "observed_content_height", 0) if pane_ds is not None else 0
397
+ if honest > 0 and advance > honest + estimated_line_px:
398
+ # Off-screen: the in-view measurement if there was one (the
399
+ # off-screen honest delta itself wobbles by a px), else the
400
+ # honest delta.
401
+ advance = pane.seen_height if pane.seen_height is not None else float(honest)
402
+ imgui.set_cursor_screen_pos((base_x, cursor_y + advance))
403
+ else:
404
+ pane.seen_height = advance
405
+ pane.last_height = advance + header_height + card_pad_bottom
406
+ if file_headers:
407
+ imgui.set_cursor_screen_pos((base_x, cursor_y + advance + card_pad_bottom))
408
+ if file_headers and pane.ds is None and pane_ds is not None:
409
+ # First sight: the card (drawn from pane.ds's captured bg
410
+ # recipe) could not paint this frame - repaint next frame.
411
+ draw_state.invalidate()
412
+ pane.ds = pane_ds
413
+ # A manual fold-badge toggle hands this pane's middle back to
414
+ # automatic tracking (expand_diff=False would re-collapse it).
415
+ manual_gen = getattr(pane_ds, "_diff_manual_gen", 0)
416
+ if manual_gen != pane.fold_gen_seen:
417
+ pane.fold_gen_seen = manual_gen
418
+ pane.expand_diff = None
419
+ # ── Edit write-through: through the range proxy into the file's PENDING
420
+ # truth (the same queue the editor's keystrokes land in), then shift
421
+ # this pane's call line and every affected pane's span by the delta.
422
+ if edited and isinstance(new_text, str) and new_text != buffer:
423
+ old_rows = buffer.split("\n")
424
+ new_rows = new_text.split("\n")
425
+ edit_row = 0 # first differing 0-based row
426
+ for row_index in range(min(len(old_rows), len(new_rows))):
427
+ if old_rows[row_index] != new_rows[row_index]:
428
+ edit_row = row_index
429
+ break
430
+ else:
431
+ edit_row = min(len(old_rows), len(new_rows))
432
+ span_range = file_code.get_lines(pane.first - 1, pane.last)
433
+ edited_span = (pane.first, pane.last)
434
+ span_range["value"] = "\n".join(
435
+ _reindent_rows(new_rows, def_indent))
436
+ delta = (span_range.end + 1) - (pane.last + 1)
437
+ new_last = span_range.end # 0-based end == 1-based last
438
+ if edit_row <= call_row:
439
+ pane.lineno += delta
440
+ if pane.def_last is not None:
441
+ pane.def_last += delta
442
+ pane.last = new_last
443
+ _shift_panes(panes, pane, pane.path,
444
+ pane.first + edit_row, delta,
445
+ edited_span=edited_span, file_code=file_code)
446
+
447
+ # ── Open at the BOTTOM: the trace bottoms out at the view's, so
448
+ # a new capture lands there. Write past the end and let the wrapper's
449
+ # scroll clamp (max_scroll_y, next render) settle it; keep pinning
450
+ # while the async resolves are still reshaping the content height, and
451
+ # hand the scroll back to the user once the bottom pane has actually
452
+ # rendered and been measured.
453
+ # An auto-resizing view (nested in a scrolling parent) has no scroll of
454
+ # its own to clamp - writing 10**9 there just leaves a junk offset.
455
+ if trace_state.scroll_bottom_pending and draw_state.auto_resize:
456
+ trace_state.scroll_bottom_pending = False
457
+ if trace_state.scroll_bottom_pending:
458
+ draw_state.scroll_offset = (draw_state.scroll_offset[0], 10 ** 9)
459
+ bottom_pane = next((p for p in reversed(panes) if p is not None), None)
460
+ if bottom_pane is None or bottom_pane.last_height is not None:
461
+ trace_state.scroll_bottom_pending = False
462
+ return False, input_value
463
+
464
+
465
+ @render_func(use_cache=True, selectable=False, show_add_delete=False,
466
+ is_tree=False, show_name=True, shadow=True, bg_offset=-2,
467
+ is_default_for="CrashReportStore", tint=(0.86, 0.24, 0.2))
468
+ def draw_crash_reports(
469
+ # [tint=(0.85, 0.75, 0.05)]
470
+ input_value: CrashReportStore,
471
+ draw_state, panel_state: CrashReportsPanelState = None, style_manager=None,
472
+ left_mouse_down=False, **kwargs):
473
+ from meltygui.editor.source_ui import _file_meta_tint
474
+ from meltygui.editor.source_ui import _tab_text_color
475
+ from meltygui.editor.text_editor import COLORS
476
+ from meltygui.core.windowing.glfw_utils import request_render
477
+ from meltygui.view.header_view import flat_button
478
+ from meltygui.core.cache.tile_cache import add_shadow
479
+ from meltygui.core.layout.header_runtime import _brightness_clamp_fn
480
+ from meltygui.model.trace_report_model import _color_u32
481
+ from meltygui.model.trace_report_model import _ellipsize
482
+ from meltygui.model.trace_report_model import _mix
483
+ from meltygui.model.trace_report_model import date_bucket
484
+
485
+ store = input_value
486
+ store.refresh_if_stale()
487
+ open_rows = panel_state.open if panel_state is not None else {}
488
+
489
+ # ---- styling (fast_dock colours) ----
490
+ # Rows take the editor tabs' colour knobs (toggles/EditorColor_*);
491
+ # these mix the section headers and the toolbar text.
492
+ factor = 0.90
493
+ hover_bg_boost = 0.05
494
+ text_saturation = 0.8
495
+ section_text_value = 0.75 # the Today / Yesterday / date headers
496
+ # Fixed design colours (rule 18): the error text, the dim time/thread/commit
497
+ # label, and the delete buttons (the editor's error-chip red).
498
+ error_color = (0.95, 0.45, 0.4)
499
+ meta_color = (0.62, 0.65, 0.72)
500
+ delete_color = (0.85, 0.12, 0.14)
501
+
502
+ # ---- icons (glyph literals - the editor renders them as a picker) ----
503
+ trash_icon = f"" # delete this report / clear all
504
+ section_icon = f"" # calendar, on the date section headers
505
+ open_icon = f"" # chevron on an expanded row
506
+ closed_icon = f"" # chevron on a collapsed row
507
+
508
+ # ---- geometry, authored at ui_scale 1.0 and evaluated once per frame ----
509
+ px = Melty.px
510
+ # [tint=(0.939, 0.453, 0.245)]
511
+ row_height = px(30.0)
512
+ row_gap = px(4.0)
513
+ pad_x = px(10.0)
514
+ corner = px(6.0)
515
+ toolbar_height = px(30.0)
516
+ section_height = px(24.0) # a date section header row
517
+ section_gap = px(6.0) # gap after a section header
518
+ button_height = px(24.0)
519
+ button_pad_x = px(10.0)
520
+ trace_gap = px(4.0) # gap between a row and its stack trace view
521
+ card_pad_bottom = px(6.0) # the row's card runs this far past its trace
522
+ footer_height = px(22.0) # thread · commit line under an expanded trace
523
+ # [tint=(0.35, 0.85, 0.94)]
524
+ chevron_inset = px(10.0) # chevron x inside the row
525
+ text_inset = px(28.0) # error text x inset (after the chevron)
526
+ # The right-aligned "5:02PM + 20s · thread · commit" column takes what it
527
+ # needs (a thread name is never clipped for a fixed width); the ERROR
528
+ # text is what gives way, down to this much room.
529
+ error_min_width = px(90.0)
530
+ func_pad_x = px(6.0) # the function pill's inset
531
+ text_nudge_y = px(-1.0)
532
+
533
+ if style_manager is None:
534
+ style_manager = Melty.style_manager
535
+ draw_list = imgui.get_window_draw_list()
536
+ origin_x, origin_y = imgui.get_cursor_screen_pos()
537
+ content_width = draw_state.content_width or (draw_state.width or 300)
538
+ mouse_x, mouse_y = imgui.get_mouse_pos()
539
+ hover_ok = draw_state._bounding_hovered
540
+ press = left_mouse_down
541
+ # [tint=(0.62, 0.47, 0.95)]
542
+ click = (press.x, press.y) if (press and hasattr(press, "x")) else None
543
+ clip = getattr(draw_state, "abs_clip_rect", None)
544
+ line_height = imgui.get_text_line_height()
545
+ row_left, row_right = origin_x + pad_x, origin_x + content_width - pad_x
546
+ tint = draw_state.locate_tint
547
+ # Flipped whenever this frame mutated the store (a delete) or the panel
548
+ # state — it is the view's `changed` return (style guide rule 10).
549
+ changed = False
550
+
551
+ def visible(top, bottom):
552
+ return clip is None or not (bottom < clip[1] or top > clip[3])
553
+
554
+ def hovered(left, top, right, bottom):
555
+ return hover_ok and left <= mouse_x <= right and top <= mouse_y <= bottom
556
+
557
+ def clicked(left, top, right, bottom):
558
+ return click is not None and left <= click[0] <= right and top <= click[1] <= bottom
559
+
560
+ # ---- toolbar: count + directory, Clear all ----
561
+ toolbar_top = origin_y
562
+ entries = list(store.items())
563
+ # flat_button, placed by the imgui cursor with layout=True so the click
564
+ # is claimed through this view's on_action rect (layout=False is
565
+ # draw-only - no subscription at all). event="left_mouse_down": the
566
+ # body's own view-wide left_mouse_down param would otherwise take the
567
+ # click; the button's registration sits 4 above it.
568
+ clear_left, clear_pressed = row_right, False
569
+ if entries:
570
+ clear_label = f"{trash_icon} Clear all"
571
+ clear_width = imgui.calc_text_size(clear_label)[0] + 2 * button_pad_x
572
+ clear_left = row_right - clear_width
573
+ imgui.set_cursor_screen_pos((clear_left, toolbar_top + (toolbar_height - button_height) / 2.0))
574
+ clear_pressed = bool(flat_button(
575
+ clear_label, draw_state, "crash_clear_all", width=clear_width, height=button_height,
576
+ event="left_mouse_down", color=delete_color, tint_value=0.45, max_bg_brightness=0.6,
577
+ text_color=(1.0, 0.80, 0.78, 1.0), corner_radius=corner))
578
+ if clear_pressed:
579
+ store.remove_all()
580
+ entries = []
581
+ changed = True
582
+ count_note = (f"{len(entries)} report{'s' if len(entries) != 1 else ''} · "
583
+ f"{store.directory()}")
584
+ if store.error:
585
+ count_note += f" · {store.error}"
586
+ draw_list.add_text(row_left, toolbar_top + (toolbar_height - line_height) / 2.0,
587
+ _color_u32(meta_color, 0.8), _ellipsize(count_note, clear_left - px(10) - row_left))
588
+
589
+ # ---- rows, under Today / Yesterday / date section headers ----
590
+ row_top = toolbar_top + toolbar_height + row_gap
591
+ section_color = _mix(style_manager, tint, section_text_value, factor, text_saturation)
592
+ now = time.time()
593
+ current_section = None
594
+ for path, entry in entries:
595
+ section = date_bucket(entry["mtime"], now)
596
+ if section != current_section:
597
+ if current_section is not None:
598
+ row_top += section_gap # breathing room between sections
599
+ current_section = section
600
+ if visible(row_top, row_top + section_height):
601
+ section_y = row_top + (section_height - line_height) / 2.0 + text_nudge_y
602
+ draw_list.add_text(row_left, section_y, _color_u32(section_color), section_icon)
603
+ draw_list.add_text(row_left + px(20), section_y, _color_u32(section_color), section)
604
+ row_top += section_height + row_gap
605
+ row_bottom = row_top + row_height
606
+ is_open = bool(open_rows.get(entry["name"]))
607
+ row_hovered = hovered(row_left, row_top, row_right, row_bottom)
608
+ # Trash button lives on the row's right while the pointer is on the
609
+ # row (the cached tile re-renders while hovered, like the fast
610
+ # toggle's summon button). Its LEFT edge is reserved here so the meta
611
+ # text stops short of it; the button itself paints on the row's
612
+ # background below - drawn first, the row rect masks it.
613
+ trash_left = row_right
614
+ trash_pressed = False
615
+ trash_width = imgui.calc_text_size(trash_icon)[0] + 2 * button_pad_x
616
+ if row_hovered:
617
+ trash_left = row_right - px(4) - trash_width - px(6)
618
+
619
+ # The row wears the colour of the file that RAISED (the trace's last
620
+ # frame; a painted FileMeta tint - the editor tabs' source), and
621
+ # names that file after the error. An unpainted file keeps the
622
+ # window's tint.
623
+ saved_trace = store.trace(path)
624
+ raising_frame = saved_trace.frames[-1] if saved_trace.frames else None
625
+ file_tint = _file_meta_tint(raising_frame[0]) if raising_frame else None
626
+ row_tint = file_tint or tint
627
+ file_label = ""
628
+ if raising_frame:
629
+ file_label = f"{os.path.basename(raising_frame[0])}:{raising_frame[1]}"
630
+
631
+ # An expanded row's card WRAPS its whole trace: the rect runs from
632
+ # the row's top past the trace's bottom (the trace's height as last
633
+ # measured - its first frame draws a row-sized card and the
634
+ # re-measure repaints). The trace draws over it. The card is
635
+ # visibility-tested on ITS OWN rect, not the row's: with the row
636
+ # scrolled off the top the card must still paint behind the trace.
637
+ card_bottom = row_bottom
638
+ if is_open and saved_trace.seen_height is not None:
639
+ card_bottom = (row_bottom + trace_gap + saved_trace.seen_height
640
+ + footer_height + card_pad_bottom)
641
+ if visible(row_top, card_bottom):
642
+ # The editor tabs' colour pipeline (open_files' tab flat_button):
643
+ # make_color_rgb at factor 0.1 - the raw file tint with a sliver
644
+ # of theme - at the tab_*_bg knobs, brightness-clamped, and the
645
+ # tabs' text colour. Expanded = active tab, collapsed = inactive.
646
+ # tabs' text colour (below). Collapsed or expanded, the same styling.
647
+ bg = style_manager.make_color_rgb(
648
+ row_tint[0], row_tint[1], row_tint[2],
649
+ value=Toggles.CodeEditor.tab_active_bg_brightness
650
+ + (hover_bg_boost if row_hovered else 0.0),
651
+ factor=0.1, saturation_scale=Toggles.CodeEditor.tab_active_bg_saturation, alpha=1.0)
652
+ bg = _brightness_clamp_fn()(bg[0], bg[1], bg[2], 0.0,
653
+ Toggles.CodeEditor.tab_active_bg_max_brightness)
654
+ add_shadow((row_left, row_top, row_right - row_left, card_bottom - row_top),
655
+ offset=11, corner_radius=corner, clip=clip)
656
+ # One channel DOWN for the card: the trace's panes render on this
657
+ # body's channel, so a card on this paints over their text
658
+ # (columns.py's cell-bg pattern).
659
+ if Melty.channels_split:
660
+ draw_list.channels_set_current(max(0, Melty.get_channel() - 1))
661
+ # Expanded, only the top corners round - the file cards inside
662
+ # are square and flush, when poked out of a rounded bottom.
663
+ draw_list.add_rect_filled(row_left, row_top, row_right, card_bottom,
664
+ _color_u32(bg), rounding=corner,
665
+ flags=(imgui.DRAW_ROUND_CORNERS_TOP if is_open
666
+ else imgui.DRAW_ROUND_CORNERS_ALL))
667
+ if Melty.channels_split:
668
+ draw_list.channels_set_current(Melty.get_channel())
669
+ if visible(row_top, row_bottom):
670
+ fg = _tab_text_color(row_tint, Toggles.CodeEditor.tab_active_text_brightness,
671
+ Toggles.CodeEditor.tab_active_text_saturation,
672
+ Toggles.CodeEditor.tab_active_text_min_brightness)
673
+ text_y = row_top + (row_height - line_height) / 2.0 + text_nudge_y
674
+ draw_list.add_text(row_left + chevron_inset, text_y, _color_u32(fg),
675
+ open_icon if is_open else closed_icon)
676
+ # ── right: time of day (the section header carries the date),
677
+ # thread, commit - right-aligned, never clipped by the thread ──
678
+ when = time.localtime(entry["mtime"])
679
+ meta = (f"{when.tm_hour % 12 or 12}:{when.tm_min:02d}"
680
+ f"{'AM' if when.tm_hour < 12 else 'PM'} + {when.tm_sec}s")
681
+ meta_right = trash_left - px(6)
682
+ # ── left: the raising FILE first, then its FUNCTION on a pill in
683
+ # the function's own definition tint (or roster's, where the def
684
+ # carries one), then the error ──
685
+ x = row_left + text_inset
686
+ func_rect = None
687
+ if file_label:
688
+ draw_list.add_text(x, text_y, _color_u32(fg), file_label)
689
+ x += imgui.calc_text_size(file_label)[0] + px(10)
690
+ func_name = (raising_frame[2] or "") if raising_frame else ""
691
+ if func_name and func_name != "<module>":
692
+ def_w = imgui.calc_text_size("def ")[0]
693
+ pill_w = def_w + imgui.calc_text_size(func_name)[0] + 2 * func_pad_x
694
+ # The editor's own syntax colours: `def` in the keyword
695
+ # orange, the name in the def-name blue (code_editor.COLORS).
696
+ draw_list.add_text(x + func_pad_x, text_y, COLORS["def"], "def ")
697
+ draw_list.add_text(x + func_pad_x + def_w, text_y, COLORS["def_name"], func_name)
698
+ func_rect = (x, row_top, x + pill_w, row_bottom)
699
+ x += pill_w + px(10)
700
+ # Ctrl+B on the row jumps to the code editor at the raising
701
+ # file's line; on the function name it also lands the caret on
702
+ # the name (its rect registers one level above the row's) -
703
+ # the same targets as the trace's file headers.
704
+ if raising_frame:
705
+ fired = None
706
+ if func_rect is not None and draw_state.on_action(
707
+ "ctrl_b_down", view_id=f"crash_jump_func_{entry['name']}",
708
+ rect=func_rect, priority_delta=5) is not None:
709
+ fired = "func"
710
+ elif draw_state.on_action("ctrl_b_down", view_id=f"crash_jump_file_{entry['name']}",
711
+ rect=(row_left, row_top, row_right, row_bottom),
712
+ priority_delta=4) is not None:
713
+ fired = "file"
714
+ if fired:
715
+ from meltygui.core.runtime.extensions import open_source as open_in_editor
716
+ open_in_editor(raising_frame[0], raising_frame[1],
717
+ token=(func_name.rsplit(".", 1)[-1] if fired == "func" else None))
718
+ # the meta takes what it needs, the error gets the rest (floored)
719
+ meta_room = max(px(40), meta_right - x - error_min_width - px(12))
720
+ meta_fit = _ellipsize(meta, meta_room)
721
+ meta_size = imgui.calc_text_size(meta_fit)
722
+ draw_list.add_text(meta_right - meta_size[0], text_y, _color_u32(meta_color, 0.9), meta_fit)
723
+ error_fit = _ellipsize(entry["error"] or entry["name"], meta_right - meta_size[0] - px(12) - x)
724
+ if error_fit:
725
+ draw_list.add_text(x, text_y, _color_u32(error_color), error_fit)
726
+
727
+ if row_hovered:
728
+ imgui.set_cursor_screen_pos((row_right - px(4) - trash_width,
729
+ row_top + (row_height - button_height) / 2.0))
730
+ trash_pressed = bool(flat_button(
731
+ trash_icon, draw_state, f"crash_delete_{entry['name']}",
732
+ width=trash_width, height=button_height, event="left_mouse_down",
733
+ color=delete_color, tint_value=0.45, max_bg_brightness=0.6,
734
+ text_color=(1.0, 0.80, 0.78, 1.0), corner_radius=corner))
735
+
736
+ # ---- clicks ----
737
+ if trash_pressed:
738
+ store.remove(path)
739
+ open_rows.pop(entry["name"], None)
740
+ changed = True
741
+ elif clicked(row_left, row_top, trash_left, row_bottom):
742
+ if is_open:
743
+ open_rows.pop(entry["name"], None)
744
+ else:
745
+ open_rows[entry["name"]] = True
746
+ is_open = not is_open
747
+ if panel_state is not None:
748
+ panel_state.open = dict(open_rows) # @live setattr: repaints the tile
749
+ changed = True
750
+ request_render()
751
+
752
+ row_top = row_bottom + row_gap
753
+ if trash_pressed or not is_open:
754
+ continue
755
+
756
+ # ---- stack trace under each expanded row ----
757
+ # The stack trace VIEW (the code behind each frame, editable,
758
+ # pending truth) - a nested render_func in the manual row flow:
759
+ # park the cursor where the row ends, let the wrapper lay it out,
760
+ # pull the cursor back for the next row. Locals aren't saved, so
761
+ # the panes inherit no live values.
762
+ trace_top = row_top - row_gap + trace_gap
763
+ imgui.set_cursor_screen_pos((row_left, trace_top))
764
+ # cull_offscreen=False: every pane lays out at its real height, in
765
+ # view or not - this window scrolls by the trace's height, so that
766
+ # height must not change under the scroll.
767
+ _changed, _trace, trace_ds = draw_stack_trace(
768
+ store.trace(path), name=f"crash_report_{entry['name']}",
769
+ indent_views=False, file_headers=True, cull_offscreen=False, return_extras=True,
770
+ width=row_right - row_left, corner_radius=0,
771
+ # The row's card is the background and the shadow caster; the
772
+ # trace's own bg / shadow mark are rounded (radius 5) and showed
773
+ # as a corner rect where its square file headers poked over them.
774
+ show_bg=False, shadow=False)
775
+ # MANUAL height: the nested view's draw_state.height, not the cursor
776
+ # it left behind - the wrapper advances the cursor by the live
777
+ # layout on the first run and by the tile on a cache hit, and the two
778
+ # differ by a few px, so a cursor-read total jittered every time a
779
+ # pane re-rendered under the pointer. `height` is stamped from the
780
+ # measured content and only moves when the content really does.
781
+ trace_height = (trace_ds.height if trace_ds is not None and trace_ds.height
782
+ else imgui.get_cursor_screen_pos()[1] - trace_top)
783
+ # ... and the wrapper's `height` is the whole imgui GROUP in which a
784
+ # pane lying above the viewport stretches to the clip edge (see
785
+ # draw_stack_trace's advance fix); `observed_content_height` is
786
+ # the honest cursor delta of the trace body. Prefer it whenever
787
+ # the group overshoots it by more than a row.
788
+ honest = getattr(trace_ds, "observed_content_height", 0) if trace_ds is not None else 0
789
+ if honest > 0 and trace_height > honest + line_height:
790
+ trace_height = (saved_trace.seen_height if saved_trace.seen_height is not None
791
+ else float(honest))
792
+ else:
793
+ if saved_trace.seen_height != trace_height:
794
+ request_render() # the card bg was sized off the old value
795
+ changed = True
796
+ saved_trace.seen_height = trace_height
797
+ # ---- footer: thread - commit, right-aligned inside the card ----
798
+ footer_top = trace_top + trace_height
799
+ footer = entry["thread"] or ""
800
+ if entry["commit"]:
801
+ sha, _, branch = entry["commit"].partition(" ")
802
+ footer += f"{' · ' if footer else ''}{sha[:8]}{(' ' + branch) if branch else ''}"
803
+ if footer and visible(footer_top, footer_top + footer_height):
804
+ footer_fit = _ellipsize(footer, row_right - row_left - 2 * px(8))
805
+ footer_size = imgui.calc_text_size(footer_fit)
806
+ draw_list.add_text(row_right - px(8) - footer_size[0],
807
+ footer_top + (footer_height - line_height) / 2.0 + text_nudge_y,
808
+ _color_u32(meta_color, 0.9), footer_fit)
809
+ # The card runs card_pad_bottom past the footer; the next row follows it.
810
+ row_top = footer_top + footer_height + card_pad_bottom + row_gap
811
+ imgui.set_cursor_screen_pos((origin_x, row_top))
812
+
813
+ if not entries and visible(row_top, row_top + row_height):
814
+ draw_list.add_text(row_left, row_top + (row_height - line_height) / 2.0,
815
+ _color_u32(meta_color, 0.6), "No crash reports.")
816
+ row_top += row_height
817
+
818
+ # MANUAL content height: the wrapper's content() reads the cursor the
819
+ # body leaves behind (observed content_height), so the body pins it at
820
+ # its own total - rows + sections + each expanded trace's draw_state
821
+ # height (above) - instead of whatever the nested view's layout left.
822
+ # The dummy claims the width for the content rect; the setCursor after
823
+ # it fixes the exact bottom (a dummy alone adds item spacing). The
824
+ # top_inset term is fast_dock's: the clip covers the whole window,
825
+ # header included, while rows start below the header.
826
+ top_inset = (origin_y + draw_state.scroll_offset[1]) - draw_state.abs_top
827
+ total_height = max(1.0, (row_top - origin_y) + max(0.0, top_inset))
828
+ imgui.set_cursor_screen_pos((origin_x, origin_y))
829
+ imgui.dummy(content_width, total_height)
830
+ imgui.set_cursor_screen_pos((origin_x, origin_y + total_height))
831
+
832
+ return changed, input_value
833
+
834
+
835
+
836
+ def _draw_file_header(pane, x, y, width, height, text_x=None, draw_state=None, index=0):
837
+ """One printed-trace line above a pane: `File "<path>", line N, in
838
+ <func>`, on a card that WRAPS the pane — a rect from the header's top
839
+ down past the pane's bottom, painted through the pane's OWN background
840
+ recipe (`BlitCache.draw_freeze_bg(pane.ds, …, live=False)` replays the
841
+ depth / bg-stack / style-tint the pane's wrapper captured), so card and
842
+ pane are the same colour by construction; the pane then draws over it.
843
+ Nothing is painted until the pane has a draw_state (its first frame).
844
+ Text: the editor tabs' colour — the file's painted FileMeta tint (the
845
+ tabs' default tint when unpainted) scaled by the active-tab knobs, so
846
+ it is tinted toward the card, never grey."""
847
+ from meltygui.code.fileref import _PROJECT_ROOT
848
+
849
+ from meltygui.core.melty import Melty
850
+ from meltygui.models.file_meta import FileMeta
851
+ from meltygui.core.runtime.toggles import Toggles
852
+ from meltygui.editor.source_ui import _file_meta_tint
853
+ from meltygui.editor.source_ui import _tab_text_color
854
+ text_pad_x = Melty.px(8)
855
+ text_pad_y = Melty.px(3)
856
+ # The card's colour is the pane's own bg (below) pushed darker and more
857
+ # saturated — change the header/pane contrast here.
858
+ # [tint=(0.9, 0.35, 0.28)]
859
+ card_saturation = 1.6
860
+ card_value = 0.75
861
+ draw_list = imgui.get_window_draw_list()
862
+ # The card: a plain draw-list rect from the pane's OWN background
863
+ # colour — `bg_color`, stamped on its draw_state by the wrapper
864
+ # (compute_bg_color with the file tint pushed) — so card and pane share
865
+ # a hue by construction. Square, straight to the list.
866
+ # Painted two channels BELOW the body's: the panes' tint washes and
867
+ # live-value pills sit a channel under their text, and a card on the
868
+ # body's own channel fought them.
869
+ # [tint=(0.9, 0.35, 0.28)]
870
+ card_channel_drop = 2
871
+ bg_color = getattr(pane.ds, "bg_color", None) if pane.ds is not None else None
872
+ if bg_color is not None:
873
+ hue, sat, val = colorsys.rgb_to_hsv(*bg_color[:3])
874
+ card = colorsys.hsv_to_rgb(hue, min(1.0, sat * card_saturation), val * card_value)
875
+ if Melty.channels_split:
876
+ draw_list.channels_set_current(max(0, Melty.get_channel() - card_channel_drop))
877
+ draw_list.add_rect_filled(x, y, x + width, y + height,
878
+ pack_color(card[0], card[1], card[2], 1.0))
879
+ if Melty.channels_split:
880
+ draw_list.channels_set_current(Melty.get_channel())
881
+ tint = _file_meta_tint(pane.path) or FileMeta.tint
882
+ rgb = _tab_text_color(tint, Toggles.CodeEditor.tab_active_text_brightness,
883
+ Toggles.CodeEditor.tab_active_text_saturation,
884
+ Toggles.CodeEditor.tab_active_text_min_brightness)
885
+ name_u32 = pack_color(rgb[0], rgb[1], rgb[2], 1.0)
886
+ rest_u32 = pack_color(rgb[0], rgb[1], rgb[2], 0.7)
887
+ try:
888
+ shown = str(Path(pane.path).resolve().relative_to(_PROJECT_ROOT))
889
+ except (OSError, ValueError):
890
+ shown = pane.path
891
+ pen = (text_x if text_x is not None else x) + text_pad_x
892
+ text_y = y + text_pad_y
893
+ line_height = imgui.get_text_line_height()
894
+ jump_rects = {}
895
+ for key, text, color in (("", 'File "', rest_u32), ("file", shown, name_u32),
896
+ ("", f'", line {pane.lineno}, in ', rest_u32),
897
+ ("func", pane.qualname, name_u32)):
898
+ draw_list.add_text(pen, text_y, color, text)
899
+ text_w = imgui.calc_text_size(text)[0]
900
+ if key:
901
+ jump_rects[key] = (pen, text_y, pen + text_w, text_y + line_height)
902
+ pen += text_w
903
+ # Ctrl+B anywhere on the header line jumps to the code editor at that
904
+ # frame's line; on the FUNCTION name it also lands the caret on the
905
+ # def (its rect registers one level above the name's). on_action
906
+ # rects on the trace's draw_state, replayed on cache hits like every
907
+ # body action. The whole line, not the name's glyph box: the 18 px
908
+ # target a few px above a pane's def row was too easy to miss.
909
+ if draw_state is not None:
910
+ from meltygui.core.runtime.extensions import open_source as open_in_editor
911
+ # At least the text's extent - a view whose width is not measured
912
+ # yet (first frames, the render harness) would give a zero rect.
913
+ line_rect = (x, y, max(x + width, pen), y + text_pad_y + line_height + text_pad_y)
914
+ fired = None
915
+ if draw_state.on_action("ctrl_b_down", view_id=f"trace_jump_func_{index}",
916
+ rect=jump_rects["func"], priority_delta=5) is not None:
917
+ fired = "func"
918
+ elif draw_state.on_action("ctrl_b_down", view_id=f"trace_jump_file_{index}",
919
+ rect=line_rect, priority_delta=4) is not None:
920
+ fired = "file"
921
+ if fired:
922
+ token = pane.qualname.rsplit(".", 1)[-1] if fired == "func" else None
923
+ open_in_editor(pane.path, pane.lineno, token=token)