meltygui 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- meltygui/__init__.py +107 -0
- meltygui/accounts/__init__.py +0 -0
- meltygui/accounts/internet_accounts.py +1355 -0
- meltygui/chat/__init__.py +91 -0
- meltygui/chat/activity.py +75 -0
- meltygui/chat/backends.py +36 -0
- meltygui/chat/chat_interface.py +732 -0
- meltygui/chat/chat_proxy.py +352 -0
- meltygui/chat/codex_proxy.py +592 -0
- meltygui/chat/codex_settings.py +100 -0
- meltygui/chat/codex_transport.py +60 -0
- meltygui/chat/command_parser.py +204 -0
- meltygui/chat/images.py +227 -0
- meltygui/chat/messages.py +417 -0
- meltygui/chat/metadata.py +139 -0
- meltygui/chat/writer_locks.py +64 -0
- meltygui/code/__init__.py +0 -0
- meltygui/code/basic_converters.py +533 -0
- meltygui/code/chain_converters.py +2111 -0
- meltygui/code/code_checks.py +2209 -0
- meltygui/code/core_syntax.py +1430 -0
- meltygui/code/file_converters.py +1933 -0
- meltygui/code/fileref.py +702 -0
- meltygui/code/hotswap_guard.py +144 -0
- meltygui/code/libcst_conversion.py +9724 -0
- meltygui/code/live_instrument.py +392 -0
- meltygui/code/live_view.py +2490 -0
- meltygui/code/melty_scan.py +2684 -0
- meltygui/code/new_codecs.py +1255 -0
- meltygui/code/new_converters.py +3017 -0
- meltygui/code/project_code.py +278 -0
- meltygui/code/source_context.py +63 -0
- meltygui/code/symbol_roster.py +1588 -0
- meltygui/code/syntax_check.py +34 -0
- meltygui/code/syntax_check_worker.py +114 -0
- meltygui/completion/__init__.py +0 -0
- meltygui/completion/fim.py +1232 -0
- meltygui/completion/fim_context.py +481 -0
- meltygui/completion/providers/__init__.py +0 -0
- meltygui/completion/providers/anthropic_oauth.py +446 -0
- meltygui/completion/providers/anthropic_requests.py +69 -0
- meltygui/completion/providers/claude.py +203 -0
- meltygui/completion/providers/claude_usage.py +499 -0
- meltygui/completion/providers/codex_accounts.py +180 -0
- meltygui/completion/providers/copilot.py +617 -0
- meltygui/completion/providers/oauth_popup.py +220 -0
- meltygui/completion/providers/ollama.py +320 -0
- meltygui/completion/providers/profiles.py +23 -0
- meltygui/core/README.md +88 -0
- meltygui/core/__init__.py +1 -0
- meltygui/core/automation/__init__.py +1 -0
- meltygui/core/automation/action_core.py +153 -0
- meltygui/core/automation/collection_action.py +45 -0
- meltygui/core/automation/mcp_eval.py +167 -0
- meltygui/core/automation/mcp_hotswap.py +107 -0
- meltygui/core/automation/mcp_query.py +552 -0
- meltygui/core/automation/mcp_server.py +657 -0
- meltygui/core/automation/orchestration_core.py +2636 -0
- meltygui/core/automation/query_core.py +124 -0
- meltygui/core/automation/search_core.py +26 -0
- meltygui/core/automation/selector_core.py +340 -0
- meltygui/core/automation/value_core.py +1752 -0
- meltygui/core/cache/__init__.py +1 -0
- meltygui/core/cache/cache_diagnostics.py +0 -0
- meltygui/core/cache/invalidation_decoration.py +153 -0
- meltygui/core/cache/invalidation_tracker.py +43 -0
- meltygui/core/cache/tile_cache.py +5968 -0
- meltygui/core/conversion/__init__.py +1 -0
- meltygui/core/conversion/bubbling.py +599 -0
- meltygui/core/conversion/cache_tree.py +188 -0
- meltygui/core/conversion/chain.py +113 -0
- meltygui/core/conversion/converter_register.py +145 -0
- meltygui/core/conversion/data_decoration.py +67 -0
- meltygui/core/conversion/dict_conversion.py +1815 -0
- meltygui/core/conversion/dict_conversion_util.py +177 -0
- meltygui/core/conversion/dynamic_obj.py +89 -0
- meltygui/core/conversion/graph_compare.py +183 -0
- meltygui/core/conversion/load_save_v2.py +1032 -0
- meltygui/core/conversion/missing_saved_class.py +39 -0
- meltygui/core/conversion/path_finder.py +606 -0
- meltygui/core/conversion/render_host.py +1083 -0
- meltygui/core/core_render.py +6537 -0
- meltygui/core/definition_hotswap.py +231 -0
- meltygui/core/diagnostics/__init__.py +1 -0
- meltygui/core/diagnostics/attribute_churn.py +38 -0
- meltygui/core/diagnostics/fps_counter.py +39 -0
- meltygui/core/diagnostics/gpu_frame_timer.py +103 -0
- meltygui/core/diagnostics/inspection_core.py +169 -0
- meltygui/core/diagnostics/monitor_core.py +105 -0
- meltygui/core/diagnostics/notifications.py +706 -0
- meltygui/core/diagnostics/perf_trace.py +281 -0
- meltygui/core/diagnostics/profile_decoration.py +88 -0
- meltygui/core/diagnostics/resize_trace.py +62 -0
- meltygui/core/diagnostics/screenshot_core.py +247 -0
- meltygui/core/diagnostics/session_status.py +98 -0
- meltygui/core/diagnostics/trace_core.py +445 -0
- meltygui/core/files/__init__.py +1 -0
- meltygui/core/files/file_core.py +208 -0
- meltygui/core/files/file_explorer_core.py +104 -0
- meltygui/core/files/file_tree_core.py +198 -0
- meltygui/core/files/file_watch_core.py +43 -0
- meltygui/core/files/import_graph_core.py +43 -0
- meltygui/core/files/metadata_core.py +51 -0
- meltygui/core/graphics/__init__.py +1 -0
- meltygui/core/graphics/cuda_context_core.py +166 -0
- meltygui/core/graphics/cuda_interop_core.py +136 -0
- meltygui/core/graphics/cuda_kernel_core.py +91 -0
- meltygui/core/graphics/framebuffer_recorder.py +337 -0
- meltygui/core/graphics/gl_state.py +658 -0
- meltygui/core/graphics/lut_core.py +52 -0
- meltygui/core/graphics/overlay_renderer.py +984 -0
- meltygui/core/graphics/scene_target.py +180 -0
- meltygui/core/graphics/screenshot.py +439 -0
- meltygui/core/graphics/shader_func.py +478 -0
- meltygui/core/graphics/tensor_core.py +45 -0
- meltygui/core/graphics/text_texture.py +329 -0
- meltygui/core/graphics/wayland_color.py +635 -0
- meltygui/core/input/__init__.py +1 -0
- meltygui/core/input/collision.py +165 -0
- meltygui/core/input/drag_drop_core.py +1525 -0
- meltygui/core/input/hypr_left_drag.py +323 -0
- meltygui/core/input/input_core.py +245 -0
- meltygui/core/input/input_handler.py +1101 -0
- meltygui/core/input/mouse_cursor.py +355 -0
- meltygui/core/input/pynput_backend.py +1054 -0
- meltygui/core/input/space_mouse.py +338 -0
- meltygui/core/input/touchpad_backend.py +393 -0
- meltygui/core/input/view_selection.py +177 -0
- meltygui/core/layout/__init__.py +1 -0
- meltygui/core/layout/column_core.py +2153 -0
- meltygui/core/layout/cursor_core.py +161 -0
- meltygui/core/layout/dropdown_core.py +278 -0
- meltygui/core/layout/edge_constraints.py +155 -0
- meltygui/core/layout/grid_core.py +117 -0
- meltygui/core/layout/header_core.py +23 -0
- meltygui/core/layout/header_runtime.py +67 -0
- meltygui/core/layout/layout_core.py +87 -0
- meltygui/core/layout/tile_manager_core.py +591 -0
- meltygui/core/melty.py +6726 -0
- meltygui/core/module_map.json +896 -0
- meltygui/core/module_names.py +19 -0
- meltygui/core/rendering/__init__.py +1 -0
- meltygui/core/rendering/core_decoration.py +431 -0
- meltygui/core/rendering/core_render_helpers.py +328 -0
- meltygui/core/rendering/func_metadata.py +398 -0
- meltygui/core/rendering/mode.py +818 -0
- meltygui/core/rendering/mode_defaults.py +41 -0
- meltygui/core/rendering/modes.py +136 -0
- meltygui/core/rendering/parameter_core.py +1665 -0
- meltygui/core/rendering/render_dispatch.py +1891 -0
- meltygui/core/rendering/render_funcs.py +273 -0
- meltygui/core/rendering/shaped.py +312 -0
- meltygui/core/rendering/window_decoration.py +25 -0
- meltygui/core/runtime/__init__.py +1 -0
- meltygui/core/runtime/app.py +735 -0
- meltygui/core/runtime/app_session.py +140 -0
- meltygui/core/runtime/background.py +564 -0
- meltygui/core/runtime/extensions.py +53 -0
- meltygui/core/runtime/gc_manager.py +1163 -0
- meltygui/core/runtime/lifecycle.py +21 -0
- meltygui/core/runtime/paths.py +27 -0
- meltygui/core/runtime/settings.py +14 -0
- meltygui/core/runtime/singleton.py +16 -0
- meltygui/core/runtime/thread_safe_bool.py +24 -0
- meltygui/core/runtime/thread_signal.py +30 -0
- meltygui/core/runtime/toggles.py +3115 -0
- meltygui/core/services/__init__.py +1 -0
- meltygui/core/services/account_core.py +10 -0
- meltygui/core/services/chat_core.py +19 -0
- meltygui/core/services/claude_terminal_core.py +346 -0
- meltygui/core/services/terminal_core.py +458 -0
- meltygui/core/services/terminal_runtime.py +78 -0
- meltygui/core/styling/__init__.py +1 -0
- meltygui/core/styling/color_core.py +46 -0
- meltygui/core/styling/fonts.py +639 -0
- meltygui/core/styling/global_style.py +338 -0
- meltygui/core/styling/style.py +198 -0
- meltygui/core/styling/style_core.py +522 -0
- meltygui/core/styling/warm_start.py +149 -0
- meltygui/core/windowing/__init__.py +1 -0
- meltygui/core/windowing/backends/PYIMGUI_LICENSE +28 -0
- meltygui/core/windowing/backends/__init__.py +1 -0
- meltygui/core/windowing/backends/imgui_renderer.py +138 -0
- meltygui/core/windowing/backends/native_wayland.py +850 -0
- meltygui/core/windowing/backends/protocols/xdg-decoration-unstable-v1.xml +156 -0
- meltygui/core/windowing/backends/protocols/xdg-shell.xml +1420 -0
- meltygui/core/windowing/backends/wayland_protocol.py +101 -0
- meltygui/core/windowing/dock_core.py +182 -0
- meltygui/core/windowing/frame_geometry.py +42 -0
- meltygui/core/windowing/geometry_feed.py +864 -0
- meltygui/core/windowing/glfw_utils.py +1343 -0
- meltygui/core/windowing/os_frame.py +1552 -0
- meltygui/core/windowing/surface.py +643 -0
- meltygui/core/windowing/titlebar.py +1560 -0
- meltygui/core/windowing/titlebar_buttons.py +281 -0
- meltygui/core/windowing/wayland_move.py +932 -0
- meltygui/core/windowing/window_api.py +62 -0
- meltygui/core/windowing/window_constants.py +339 -0
- meltygui/core/windowing/window_visibility.py +162 -0
- meltygui/debug/__init__.py +0 -0
- meltygui/debug/app_view_utils.py +9 -0
- meltygui/editor/__init__.py +0 -0
- meltygui/editor/bash_syntax.py +30 -0
- meltygui/editor/code_line_fast.py +152 -0
- meltygui/editor/diff.py +139 -0
- meltygui/editor/external_changes.py +159 -0
- meltygui/editor/file_header.py +41 -0
- meltygui/editor/live_usage.py +169 -0
- meltygui/editor/live_view_views.py +1803 -0
- meltygui/editor/pending_save.py +1351 -0
- meltygui/editor/roster_tints.py +547 -0
- meltygui/editor/source_preview.py +16 -0
- meltygui/editor/source_tools.py +10 -0
- meltygui/editor/source_ui.py +70 -0
- meltygui/editor/spell_check.py +77 -0
- meltygui/editor/text_editor.py +9076 -0
- meltygui/editor/usage_picker.py +538 -0
- meltygui/events/__init__.py +0 -0
- meltygui/events/example.py +118 -0
- meltygui/examples/__init__.py +0 -0
- meltygui/examples/columns_demo.py +33 -0
- meltygui/examples/columns_window_demo.py +60 -0
- meltygui/examples/context_menu_demo.py +24 -0
- meltygui/examples/context_menu_window_demo.py +49 -0
- meltygui/examples/gui_playground.py +127 -0
- meltygui/examples/live_view_playground.py +256 -0
- meltygui/examples/lora.py +43 -0
- meltygui/examples/lora_data.py +59 -0
- meltygui/examples/lora_policies.py +67 -0
- meltygui/examples/mode_demo.py +49 -0
- meltygui/examples/modifies_demo.py +119 -0
- meltygui/examples/scalar_policies.py +60 -0
- meltygui/examples/style_layouts.py +149 -0
- meltygui/examples/tile_manager_demo.py +74 -0
- meltygui/examples/tint_demo.py +160 -0
- meltygui/examples/tint_functions.py +56 -0
- meltygui/examples/trace_demo.py +88 -0
- meltygui/examples/two_windows.py +36 -0
- meltygui/files/__init__.py +0 -0
- meltygui/files/fast_file_explorer.py +439 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/extension.js +237 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/lsd-window-geometry@latent-descent.iml +9 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/metadata.json +7 -0
- meltygui/graphics/__init__.py +6 -0
- meltygui/graphics/base.py +85 -0
- meltygui/graphics/examples.py +507 -0
- meltygui/graphics/executor.py +520 -0
- meltygui/graphics/filter.py +667 -0
- meltygui/graphics/filter.pyi +856 -0
- meltygui/graphics/generate_stubs.py +22 -0
- meltygui/graphics/registry.py +203 -0
- meltygui/graphics/shader_compiler.py +155 -0
- meltygui/graphics/shaders.py +1302 -0
- meltygui/graphics/stub_generator.py +250 -0
- meltygui/graphics/texture_manager.py +170 -0
- meltygui/graphics/texture_min_max.py +295 -0
- meltygui/hdr_color.py +757 -0
- meltygui/image_load.py +308 -0
- meltygui/model/__init__.py +1 -0
- meltygui/model/account_model.py +142 -0
- meltygui/model/camera_model.py +159 -0
- meltygui/model/chat_model.py +86 -0
- meltygui/model/code_model.py +62 -0
- meltygui/model/code_proxy_model.py +876 -0
- meltygui/model/collection_model.py +41 -0
- meltygui/model/color_model.py +127 -0
- meltygui/model/cuda_tensor_model.py +36 -0
- meltygui/model/cuda_texture_model.py +149 -0
- meltygui/model/dropdown_model.py +137 -0
- meltygui/model/file_metadata_model.py +73 -0
- meltygui/model/file_model.py +289 -0
- meltygui/model/format_model.py +390 -0
- meltygui/model/graph_model.py +98 -0
- meltygui/model/icon_model.py +1024 -0
- meltygui/model/import_graph_model.py +468 -0
- meltygui/model/layout_model.py +15 -0
- meltygui/model/lut_model.py +266 -0
- meltygui/model/search_model.py +173 -0
- meltygui/model/tensor_model.py +402 -0
- meltygui/model/terminal_model.py +329 -0
- meltygui/model/texture_model.py +146 -0
- meltygui/model/tile_model.py +24 -0
- meltygui/model/trace_model.py +198 -0
- meltygui/model/trace_report_model.py +146 -0
- meltygui/models/__init__.py +0 -0
- meltygui/models/file_meta.py +613 -0
- meltygui/models/function_console.py +184 -0
- meltygui/models/orchestration.py +48 -0
- meltygui/pbr.py +1576 -0
- meltygui/png_unfilter.c +49 -0
- meltygui/resources/JetBrainsMono-Regular.ttf +0 -0
- meltygui/resources/THIRD_PARTY_NOTICES.md +21 -0
- meltygui/resources/dejavu/DejaVuSans-Bold.ttf +0 -0
- meltygui/resources/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
- meltygui/resources/dejavu/DejaVuSans.ttf +0 -0
- meltygui/resources/dejavu/LICENSE.txt +78 -0
- meltygui/resources/fontawesome-LICENSE.txt +121 -0
- meltygui/resources/fontawesome-webfont.ttf +0 -0
- meltygui/resources/hdri/studio_small_09_1k.hdr +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Bold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraBold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraLight.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Light.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Medium.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-SemiBold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Thin.ttf +0 -0
- meltygui/resources/jetbrains-weights/OFL.txt +93 -0
- meltygui/resources/jetbrains-weights/README.md +2 -0
- meltygui/state/__init__.py +0 -0
- meltygui/state/account_state.py +22 -0
- meltygui/state/animation_state.py +97 -0
- meltygui/state/annotation_state.py +22 -0
- meltygui/state/chat_state.py +36 -0
- meltygui/state/code_state.py +10 -0
- meltygui/state/core_enums.py +59 -0
- meltygui/state/core_markers.py +115 -0
- meltygui/state/core_undo.py +1075 -0
- meltygui/state/file_state.py +75 -0
- meltygui/state/graph_state.py +26 -0
- meltygui/state/inspection_state.py +47 -0
- meltygui/state/menu_state.py +10 -0
- meltygui/state/model_enums.py +11 -0
- meltygui/state/new_core_model.py +2394 -0
- meltygui/state/orchestration_state.py +14 -0
- meltygui/state/query_state.py +24 -0
- meltygui/state/tensor_state.py +10 -0
- meltygui/state/terminal_state.py +23 -0
- meltygui/state/trace_state.py +32 -0
- meltygui/state/voxel_state.py +12 -0
- meltygui/text_index.py +816 -0
- meltygui/utils/__init__.py +0 -0
- meltygui/utils/jump_to_code.py +344 -0
- meltygui/utils/pkl_inspect.py +90 -0
- meltygui/utils/render_utils.py +1419 -0
- meltygui/view/__init__.py +1 -0
- meltygui/view/account_view.py +524 -0
- meltygui/view/action_view.py +73 -0
- meltygui/view/chat_decoration_view.py +112 -0
- meltygui/view/chat_view.py +1703 -0
- meltygui/view/code_view.py +2677 -0
- meltygui/view/collection_view.py +1185 -0
- meltygui/view/color_view.py +824 -0
- meltygui/view/control_view.py +559 -0
- meltygui/view/decoration_view.py +439 -0
- meltygui/view/diagnostic_view.py +177 -0
- meltygui/view/dropdown_view.py +1082 -0
- meltygui/view/file_view.py +1599 -0
- meltygui/view/graph_cuda_view.py +105 -0
- meltygui/view/graph_view.py +882 -0
- meltygui/view/header_view.py +848 -0
- meltygui/view/input_view.py +238 -0
- meltygui/view/inspection_view.py +1783 -0
- meltygui/view/layout_view.py +596 -0
- meltygui/view/lut_view.py +45 -0
- meltygui/view/menu_view.py +212 -0
- meltygui/view/orchestration_view.py +658 -0
- meltygui/view/query_view.py +118 -0
- meltygui/view/search_view.py +342 -0
- meltygui/view/tab_view.py +258 -0
- meltygui/view/tensor_view.py +430 -0
- meltygui/view/terminal_view.py +355 -0
- meltygui/view/text_view.py +8031 -0
- meltygui/view/texture_view.py +480 -0
- meltygui/view/tile_view.py +38 -0
- meltygui/view/trace_view.py +923 -0
- meltygui/view/voxel_cuda_view.py +824 -0
- meltygui/view/voxel_view.py +1860 -0
- meltygui/view/window_view.py +111 -0
- meltygui-0.1.0.dist-info/METADATA +143 -0
- meltygui-0.1.0.dist-info/RECORD +372 -0
- meltygui-0.1.0.dist-info/WHEEL +4 -0
- meltygui-0.1.0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,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)
|