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