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,547 @@
|
|
|
1
|
+
"""Editor consumers of the symbol roster (`core_conversion/symbol_roster.py`):
|
|
2
|
+
|
|
3
|
+
collect_def_tints — the definition-tint washes (blocks + per-occurrence
|
|
4
|
+
spans + line bands + name→rgb map) in EXACTLY the
|
|
5
|
+
4-tuple shape `_collect_def_tints` returns, built from
|
|
6
|
+
the live buffer + roster instead of the cst-dict +
|
|
7
|
+
usage graph. No live objects, no background pass: a
|
|
8
|
+
tinted def typed into ANY file paints its references
|
|
9
|
+
here on the next rebuild.
|
|
10
|
+
ctrl_b_lookup — Ctrl+B at a buffer index: usage → its definition
|
|
11
|
+
(roster resolve), definition → its usages (roster
|
|
12
|
+
reverse lookup over the trigram index). Pending /
|
|
13
|
+
live-buffer coordinates throughout.
|
|
14
|
+
|
|
15
|
+
Kept out of text_editor.py so the roster path is one small, hotswappable
|
|
16
|
+
unit; text_editor only gates between the two pipelines.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import bisect
|
|
21
|
+
import re
|
|
22
|
+
import time
|
|
23
|
+
|
|
24
|
+
import meltygui.code.symbol_roster as roster
|
|
25
|
+
|
|
26
|
+
_ASN_RE = re.compile(r"^(\s*)([A-Za-z_]\w*)\s*[:=](?!=)")
|
|
27
|
+
_IDENT_RE = re.compile(r"[A-Za-z_][\w.]*")
|
|
28
|
+
_DEF_LINE_RE = re.compile(r"\s*(?:async\s+)?(def|class)\s")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _decor_start(lines, def_line):
|
|
32
|
+
"""Top of a def's wash: extend upward over the decorator / comment run
|
|
33
|
+
directly above the class/def keyword (reverse paren-balanced), stopping
|
|
34
|
+
at a blank line or any other statement."""
|
|
35
|
+
start, j, depth = def_line, def_line - 1, 0
|
|
36
|
+
while j >= 0 and j > def_line - 40:
|
|
37
|
+
s = lines[j].strip()
|
|
38
|
+
if not s and depth == 0:
|
|
39
|
+
break
|
|
40
|
+
depth += s.count(")") - s.count("(")
|
|
41
|
+
if depth == 0:
|
|
42
|
+
if s.startswith("@"):
|
|
43
|
+
start = j
|
|
44
|
+
elif not s.startswith("#"):
|
|
45
|
+
break
|
|
46
|
+
elif depth < 0:
|
|
47
|
+
break
|
|
48
|
+
j -= 1
|
|
49
|
+
return start
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def collect_def_tints(text, line_offset=0, view_path=None, window=None,
|
|
53
|
+
line_open=None, hold_live=True, world=None, table=None, project=None):
|
|
54
|
+
"""(blocks, spans, line_tints, name_tints) — see module doc.
|
|
55
|
+
|
|
56
|
+
blocks: [(def_buf_line, indent_buf_index, end_buf_line, tint)] per
|
|
57
|
+
tinted class/def DEFINED in this buffer.
|
|
58
|
+
spans: [(start, end, rgb, scale)] per occurrence of a symbol whose
|
|
59
|
+
definition — here or in another file — carries a tint.
|
|
60
|
+
line_tints: [(buf_line, rgb, scale, text_start, text_end)].
|
|
61
|
+
name_tints: {name: rgb} for the completion popup.
|
|
62
|
+
|
|
63
|
+
`window` = (lo, hi) 0-based buffer lines: occurrences (spans, line
|
|
64
|
+
bands, the assignment sweep) are only computed inside it — the editor
|
|
65
|
+
passes its visible band plus margin, so the per-rebuild cost is
|
|
66
|
+
O(viewport), not O(buffer). Blocks always cover the whole buffer (they
|
|
67
|
+
come from the table, not a scan). `line_open` is the editor's per-line
|
|
68
|
+
string state (text_editor._update_line_open) so the scan can start at
|
|
69
|
+
the nearest string-clean line at/above `lo`; without it the start backs
|
|
70
|
+
up to the nearest column-0 def/class/decorator/import line.
|
|
71
|
+
|
|
72
|
+
`hold_live=False` for READ-ONLY previews of the file (global-search
|
|
73
|
+
rows: one-line span views of the pending text): the buffer is then NOT
|
|
74
|
+
installed as the file's live roster override. Holding it would let
|
|
75
|
+
every row of the same file stomp the hold in turn — each splice differs
|
|
76
|
+
(a class's 1-line span has no tint comment / extent), so the roster
|
|
77
|
+
generation bumped per row per frame, every roster consumer re-keyed and
|
|
78
|
+
re-rendered continuously, and cross-file lookups into that file saw a
|
|
79
|
+
different table each frame (washes flickering in the search list).
|
|
80
|
+
|
|
81
|
+
`world` (a symbol_roster.World) makes the buffer a view of ANOTHER
|
|
82
|
+
version of its file — the merge window's disk / sync-frame panes: its
|
|
83
|
+
own table comes from the world (built from the same text object the
|
|
84
|
+
pane shows) and every cross-file reference resolves through the
|
|
85
|
+
world's tables, so `Toggles.Foo` paints in the tint that version of
|
|
86
|
+
toggles.py carries. `table` overrides only the buffer's OWN table (a
|
|
87
|
+
detached_table of a staged result) while other files stay the
|
|
88
|
+
studio's. Neither installs anything in the roster."""
|
|
89
|
+
if view_path is None:
|
|
90
|
+
return ((), (), (), {})
|
|
91
|
+
project = roster.analysis_project(project, view_path)
|
|
92
|
+
with roster.pass_scope():
|
|
93
|
+
return _collect(text, line_offset, view_path, window, line_open,
|
|
94
|
+
hold_live=hold_live, world=world, table=table, project=project)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# Local-binding memo: (id(text), path) -> (text, {scope-qualname tuple:
|
|
98
|
+
# bindings}). A pure scroll re-runs the windowed pass on the SAME buffer; the
|
|
99
|
+
# 5000-line draw_text scope's binding cost (~10ms) must not be paid per chunk
|
|
100
|
+
# crossed. A few slots (not one): the merge window runs three panes of ONE
|
|
101
|
+
# file with three texts like this in a row. The text is held so its id
|
|
102
|
+
# stays unique while the entry lives.
|
|
103
|
+
_bind_memo = {}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _bindings_memo(text, own, line_offset, lines, scopes):
|
|
107
|
+
if not scopes:
|
|
108
|
+
return {}
|
|
109
|
+
key = (id(text), own.path)
|
|
110
|
+
held = _bind_memo.get(key)
|
|
111
|
+
if held is None or held[0] is not text:
|
|
112
|
+
if len(_bind_memo) >= 8:
|
|
113
|
+
_bind_memo.clear()
|
|
114
|
+
held = _bind_memo[key] = (text, {})
|
|
115
|
+
sk = tuple(e.qualname for e in scopes)
|
|
116
|
+
b = held[1].get(sk)
|
|
117
|
+
if b is None:
|
|
118
|
+
b = held[1][sk] = roster.local_bindings(text, own, line_offset, lines, scopes)
|
|
119
|
+
return b
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
_DEF_LIKE_COL0 = re.compile(r"(?:async\s+)?(?:def|class)\s|@|import\s|from\s")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _scan_range(lines, line_start_idx, text, window, line_open):
|
|
126
|
+
"""(scan_start_index, scan_end_index, lo_line, hi_line) for the chain
|
|
127
|
+
scan: the window clamped to the buffer, its start backed up to a line
|
|
128
|
+
that is not inside a multi-line string."""
|
|
129
|
+
n = len(lines)
|
|
130
|
+
if window is None:
|
|
131
|
+
return 0, len(text), 0, n - 1
|
|
132
|
+
lo = max(0, min(int(window[0]), n - 1))
|
|
133
|
+
hi = max(lo, min(int(window[1]), n - 1))
|
|
134
|
+
sl = lo
|
|
135
|
+
if line_open is not None and len(line_open) >= n:
|
|
136
|
+
while sl > 0 and line_open[sl] is not None:
|
|
137
|
+
sl -= 1
|
|
138
|
+
else:
|
|
139
|
+
while sl > 0 and not _DEF_LIKE_COL0.match(lines[sl]):
|
|
140
|
+
sl -= 1
|
|
141
|
+
start = line_start_idx[sl]
|
|
142
|
+
end = line_start_idx[hi + 1] - 1 if hi + 1 < len(line_start_idx) else len(text)
|
|
143
|
+
return start, min(end, len(text)), lo, hi
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _collect(text, line_offset, view_path, window=None, line_open=None,
|
|
147
|
+
hold_live=True, world=None, table=None, project=None):
|
|
148
|
+
from meltygui.core.runtime.toggles import Toggles
|
|
149
|
+
lines = text.split("\n")
|
|
150
|
+
line_start_idx = [0]
|
|
151
|
+
for l in lines:
|
|
152
|
+
line_start_idx.append(line_start_idx[-1] + len(l) + 1)
|
|
153
|
+
vpath = roster._norm(str(view_path))
|
|
154
|
+
# The buffer's own table: an explicit one (a staged result), the world's
|
|
155
|
+
# (disk / sync-frame pane - the world's text for this path IS the pane's
|
|
156
|
+
# text), else the roster's - except a preview buffer resolves against
|
|
157
|
+
# the PENDING text (or whatever live hold a real editor of this file
|
|
158
|
+
# keeps) instead of becoming the hold.
|
|
159
|
+
if table is not None:
|
|
160
|
+
own = table
|
|
161
|
+
elif world is not None:
|
|
162
|
+
own = world.table(vpath)
|
|
163
|
+
else:
|
|
164
|
+
own = roster.table_for(vpath, live_text=text if hold_live else None,
|
|
165
|
+
line_offset=line_offset)
|
|
166
|
+
scan_start, scan_end, win_lo, win_hi = _scan_range(lines, line_start_idx, text,
|
|
167
|
+
window, line_open)
|
|
168
|
+
|
|
169
|
+
# ── blocks: tinted class/def entries of this file that sit in the buffer ──
|
|
170
|
+
blocks = []
|
|
171
|
+
block_range = {} # entry qualname -> (buf_start, buf_end)
|
|
172
|
+
for e in own.entries:
|
|
173
|
+
if e.tint is None or e.kind == "var":
|
|
174
|
+
continue
|
|
175
|
+
bl = e.line - 1 - line_offset
|
|
176
|
+
if not (0 <= bl < len(lines)):
|
|
177
|
+
continue
|
|
178
|
+
lt = lines[bl]
|
|
179
|
+
indent = len(lt) - len(lt.lstrip())
|
|
180
|
+
end = min(e.end - 1 - line_offset, len(lines) - 1)
|
|
181
|
+
# Trim trailing blank lines off the block (the indent walk leaves
|
|
182
|
+
# them outside, but a span-view's pending table may see past).
|
|
183
|
+
while end > bl and not lines[end].strip():
|
|
184
|
+
end -= 1
|
|
185
|
+
start = _decor_start(lines, bl)
|
|
186
|
+
blocks.append((start, line_start_idx[start] + indent, end, tuple(e.tint[:3])))
|
|
187
|
+
block_range[e.qualname] = (start, end)
|
|
188
|
+
|
|
189
|
+
# ── locals: bindings of the function scopes touching the window ──
|
|
190
|
+
# A local is first-class: its binding(s) + every occurrence in its scope
|
|
191
|
+
# (and nested closures) are a symbol; it SHADOWS roster names; and its
|
|
192
|
+
# tint is its own `# [tint=...]` or - with propagation on - a faded
|
|
193
|
+
# blend of what its binding line reads (chained locals fade per hop).
|
|
194
|
+
from meltygui.editor.text_editor import _scan_def_tint_lines
|
|
195
|
+
fade = Toggles.TextEditor.def_propagation_fade
|
|
196
|
+
mix_on = Toggles.TextEditor.def_tint_propagation
|
|
197
|
+
win_file_lo, win_file_hi = win_lo + 1 + line_offset, win_hi + 1 + line_offset
|
|
198
|
+
scopes = [e for e in own.entries
|
|
199
|
+
if e.kind == "def" and e.line <= win_file_hi and e.end >= win_file_lo]
|
|
200
|
+
# Binding / tint SOURCE text: the buffer itself when it is the whole
|
|
201
|
+
# file; for a SPAN buffer (a function body or a one-line search), use the
|
|
202
|
+
# pending file - a local defined above the span (a param, an earlier
|
|
203
|
+
# assignment) must still colour its uses inside it.
|
|
204
|
+
# A staged / detached table was built from this very buffer - it is the
|
|
205
|
+
# whole file just like a live one.
|
|
206
|
+
whole_file = (line_offset == 0
|
|
207
|
+
and (world is not None or table is not None
|
|
208
|
+
or (isinstance(own.key, tuple) and own.key[0] == "live")))
|
|
209
|
+
if whole_file:
|
|
210
|
+
ftext, flines, foff = text, lines, 0
|
|
211
|
+
else:
|
|
212
|
+
ftext = roster.file_text(vpath)
|
|
213
|
+
flines, foff = ftext.split("\n"), 0
|
|
214
|
+
bindings = _bindings_memo(ftext, own, foff, flines, scopes)
|
|
215
|
+
local_occ = {} # (scope, name) -> [(start, end, buf_line)]
|
|
216
|
+
tint_lines = roster.tint_line_index(flines) # prefilter for explicit-tint scans
|
|
217
|
+
|
|
218
|
+
# ── spans: every identifier chain in code, resolved per occurrence ──
|
|
219
|
+
spans = []
|
|
220
|
+
seen_spans = set()
|
|
221
|
+
name_tint = {} # roster-resolved prefix / leaf name -> (rgb, scale)
|
|
222
|
+
memo = {} # (scope qualname, chain) -> [(n_parts, entry)] (tinted only)
|
|
223
|
+
for s, e_, chain in roster.iter_chains(text, scan_start, scan_end):
|
|
224
|
+
ln = bisect.bisect_right(line_start_idx, s) - 1
|
|
225
|
+
if ln < win_lo:
|
|
226
|
+
continue # lead-in from the string-clean start: not visible
|
|
227
|
+
sc = own.scope_at(ln + 1 + line_offset)
|
|
228
|
+
scq = sc.qualname if sc is not None else None
|
|
229
|
+
if bindings and sc is not None:
|
|
230
|
+
dot = chain.find(".")
|
|
231
|
+
first = chain if dot == -1 else chain[:dot]
|
|
232
|
+
lk = roster.local_key(own, sc, first, bindings)
|
|
233
|
+
if lk is not None:
|
|
234
|
+
local_occ.setdefault(lk, []).append((s, s + len(first), ln))
|
|
235
|
+
continue # a local shadows every roster name
|
|
236
|
+
mkey = (scq, chain)
|
|
237
|
+
got = memo.get(mkey)
|
|
238
|
+
if got is None:
|
|
239
|
+
got = []
|
|
240
|
+
for ent, n in roster.resolve_prefixes(vpath, chain, own, scope=sc,
|
|
241
|
+
world=world, project=project):
|
|
242
|
+
if ent.tint is not None:
|
|
243
|
+
got.append((n, ent))
|
|
244
|
+
memo[mkey] = got
|
|
245
|
+
if not got:
|
|
246
|
+
continue
|
|
247
|
+
parts = chain.split(".")
|
|
248
|
+
for n, ent in got:
|
|
249
|
+
prefix = ".".join(parts[:n])
|
|
250
|
+
# The wash covers only the resolved SEGMENT (`TextEditor` in
|
|
251
|
+
# `Toggles.TextEditor.x`), not the whole prefix back to the
|
|
252
|
+
# chain start - the earlier parts carry their own rects.
|
|
253
|
+
pe = s + len(prefix)
|
|
254
|
+
ps = pe - len(parts[n - 1])
|
|
255
|
+
in_own = ent.path == vpath
|
|
256
|
+
rng = block_range.get(ent.qualname) if in_own else None
|
|
257
|
+
if rng is not None and rng[0] <= ln <= rng[1]:
|
|
258
|
+
continue # structurally redundant inside its own def block
|
|
259
|
+
if (ps, pe) in seen_spans:
|
|
260
|
+
continue
|
|
261
|
+
seen_spans.add((ps, pe))
|
|
262
|
+
rgb = tuple(ent.tint[:3])
|
|
263
|
+
at_own_def = in_own and (ln + 1 + line_offset) == ent.line
|
|
264
|
+
spans.append((ps, pe, rgb, 1.0, at_own_def))
|
|
265
|
+
name_tint[prefix] = (rgb, 1.0)
|
|
266
|
+
name_tint.setdefault(ent.name, (rgb, 1.0))
|
|
267
|
+
|
|
268
|
+
# ── local tints: own comment > propagation blend; then every occurrence ──
|
|
269
|
+
# local_tint[(scope, name)] = [(from_file_line, rgb, scale)] - segments sorted
|
|
270
|
+
# in order: the FIRST binding's tint owns the name, a LATER binding with
|
|
271
|
+
# its own `# [tint=...]` takes over from that line on Computed LAZILY for
|
|
272
|
+
# the locals that occur in the window (plus the locals their binding
|
|
273
|
+
# lines read, recursively - that's the propagation chain), never for
|
|
274
|
+
# every binding of a 5000-line function.
|
|
275
|
+
local_tint = {}
|
|
276
|
+
_IN_PROGRESS = ()
|
|
277
|
+
|
|
278
|
+
def _seg_at(segs, at_line):
|
|
279
|
+
cur = segs[0]
|
|
280
|
+
for sg in segs:
|
|
281
|
+
if sg[0] <= at_line:
|
|
282
|
+
cur = sg
|
|
283
|
+
return cur
|
|
284
|
+
|
|
285
|
+
def _tok_tint(tok, sc, at_line, depth):
|
|
286
|
+
"""Tint of an RHS token read in scope `sc` on file line `at_line`: a
|
|
287
|
+
visible local's tint IN EFFECT at that line first (innermost scope
|
|
288
|
+
wins; a later rebinding doesn't colour earlier reads), else the
|
|
289
|
+
roster's."""
|
|
290
|
+
dot = tok.find(".")
|
|
291
|
+
first = tok if dot == -1 else tok[:dot]
|
|
292
|
+
lk = roster.local_key(own, sc, first, bindings) if sc is not None else None
|
|
293
|
+
if lk is not None:
|
|
294
|
+
segs = _local_segments(lk, depth + 1)
|
|
295
|
+
return _seg_at(segs, at_line)[1:] if segs else None
|
|
296
|
+
t = name_tint.get(tok)
|
|
297
|
+
if t is None and dot != -1:
|
|
298
|
+
t = name_tint.get(first)
|
|
299
|
+
return t
|
|
300
|
+
|
|
301
|
+
def _rhs_of(b, lt):
|
|
302
|
+
code = roster._code_part(lt)
|
|
303
|
+
if b.kind == "assign":
|
|
304
|
+
p = code.find("=", b.col)
|
|
305
|
+
return code[p + 1:] if p != -1 else ""
|
|
306
|
+
if b.kind == "for":
|
|
307
|
+
p = code.find(" in ", b.col)
|
|
308
|
+
return code[p + 4:] if p != -1 else ""
|
|
309
|
+
if b.kind == "walrus":
|
|
310
|
+
p = code.find(":=", b.col)
|
|
311
|
+
return code[p + 2:] if p != -1 else ""
|
|
312
|
+
return ""
|
|
313
|
+
|
|
314
|
+
def _local_segments(lk, depth=0):
|
|
315
|
+
got = local_tint.get(lk)
|
|
316
|
+
if got is not None:
|
|
317
|
+
return () if got is _IN_PROGRESS else got
|
|
318
|
+
local_tint[lk] = _IN_PROGRESS # cycle guard (x = x + 1)
|
|
319
|
+
segs = []
|
|
320
|
+
sc_e = own.by_qualname.get(lk[0])
|
|
321
|
+
blist = bindings.get(lk, ())
|
|
322
|
+
for b in blist:
|
|
323
|
+
bl = b.line - 1 - foff
|
|
324
|
+
if not (0 <= bl < len(flines)):
|
|
325
|
+
continue
|
|
326
|
+
lt = flines[bl]
|
|
327
|
+
own_t = None
|
|
328
|
+
if roster._scan_tint(flines, bl + 1, None, tint_lines, None) is not None:
|
|
329
|
+
try:
|
|
330
|
+
res = _scan_def_tint_lines(flines, bl + 1, None)
|
|
331
|
+
except Exception:
|
|
332
|
+
res = None
|
|
333
|
+
if res is not None and res[1] == bl + 1:
|
|
334
|
+
own_t = (tuple(res[0][:3]), 1.0)
|
|
335
|
+
if own_t is not None:
|
|
336
|
+
segs.append((b.line, own_t[0], own_t[1]))
|
|
337
|
+
elif not segs and mix_on and b is blist[0] and depth < 6:
|
|
338
|
+
rhs = _rhs_of(b, lt)
|
|
339
|
+
if not rhs:
|
|
340
|
+
continue
|
|
341
|
+
contribs = [t for t in (_tok_tint(tok, sc_e, b.line, depth)
|
|
342
|
+
for tok in _IDENT_RE.findall(rhs))
|
|
343
|
+
if t is not None]
|
|
344
|
+
if not contribs:
|
|
345
|
+
continue
|
|
346
|
+
uniq = list(dict.fromkeys(contribs))
|
|
347
|
+
n = len(uniq)
|
|
348
|
+
rgb = tuple(sum(c[0][i] for c in uniq) / n for i in range(3))
|
|
349
|
+
scale = fade * (sum(c[1] for c in uniq) / n)
|
|
350
|
+
if scale < 0.2:
|
|
351
|
+
continue
|
|
352
|
+
segs.append((b.line, rgb, scale))
|
|
353
|
+
segs.sort()
|
|
354
|
+
local_tint[lk] = segs
|
|
355
|
+
return segs
|
|
356
|
+
|
|
357
|
+
for lk, occs in local_occ.items():
|
|
358
|
+
segs = _local_segments(lk)
|
|
359
|
+
if not segs:
|
|
360
|
+
continue
|
|
361
|
+
for (s, e, ln) in occs:
|
|
362
|
+
fl = ln + 1 + line_offset
|
|
363
|
+
seg = _seg_at(segs, fl)
|
|
364
|
+
if (s, e) in seen_spans:
|
|
365
|
+
continue
|
|
366
|
+
seen_spans.add((s, e))
|
|
367
|
+
spans.append((s, e, seg[1], seg[2], fl == seg[0]))
|
|
368
|
+
name_tint.setdefault(lk[1], (segs[-1][1], segs[-1][2]))
|
|
369
|
+
|
|
370
|
+
blocks.sort()
|
|
371
|
+
# Misresolution filter (ported): an assignment-target span inside a
|
|
372
|
+
# tinted block that isn't its own original def line is a same-name
|
|
373
|
+
# field of THIS class coloured by another definition; drop it.
|
|
374
|
+
if blocks and spans:
|
|
375
|
+
kept = []
|
|
376
|
+
for sp in spans:
|
|
377
|
+
scale, at_own_def = sp[3], sp[4]
|
|
378
|
+
if scale >= 1.0 and not at_own_def:
|
|
379
|
+
ln = bisect.bisect_right(line_start_idx, sp[0]) - 1
|
|
380
|
+
if any(b[0] <= ln <= b[2] for b in blocks):
|
|
381
|
+
ls = line_start_idx[ln]
|
|
382
|
+
le = (line_start_idx[ln + 1] - 1
|
|
383
|
+
if ln + 1 < len(line_start_idx) else len(text))
|
|
384
|
+
if (text[ls:sp[0]].strip() == ""
|
|
385
|
+
and re.match(r"\s*(?::[^=\n]+)?=[^=]",
|
|
386
|
+
text[sp[1]:le]) is not None):
|
|
387
|
+
continue
|
|
388
|
+
kept.append(sp)
|
|
389
|
+
spans = kept
|
|
390
|
+
line_mix = {}
|
|
391
|
+
for sp in spans:
|
|
392
|
+
ln = bisect.bisect_right(line_start_idx, sp[0]) - 1
|
|
393
|
+
line_mix.setdefault(ln, []).append(sp)
|
|
394
|
+
line_tints = []
|
|
395
|
+
for ln, entries in line_mix.items():
|
|
396
|
+
own_e = [e for e in entries if e[4] and e[3] >= 1.0]
|
|
397
|
+
pick = own_e if own_e else entries
|
|
398
|
+
uniq = list(dict.fromkeys((e[2], e[3]) for e in pick))
|
|
399
|
+
n = len(uniq)
|
|
400
|
+
rgb = tuple(sum(c[0][i] for c in uniq) / n for i in range(3))
|
|
401
|
+
lt = lines[ln] if 0 <= ln < len(lines) else ""
|
|
402
|
+
ind = len(lt) - len(lt.lstrip())
|
|
403
|
+
line_tints.append((ln, rgb, sum(c[1] for c in uniq) / n,
|
|
404
|
+
line_start_idx[ln] + ind,
|
|
405
|
+
line_start_idx[ln] + max(len(lt.rstrip()), ind + 1)))
|
|
406
|
+
line_tints.sort()
|
|
407
|
+
spans = [sp[:4] for sp in spans]
|
|
408
|
+
spans.sort(key=lambda s: (s[0], -(s[1] - s[0])))
|
|
409
|
+
name_tints = {}
|
|
410
|
+
for nm, t in name_tint.items():
|
|
411
|
+
if t is not None:
|
|
412
|
+
name_tints[nm] = tuple(t[0][:3])
|
|
413
|
+
for nm, t in name_tint.items():
|
|
414
|
+
if t is not None and "." in nm:
|
|
415
|
+
name_tints.setdefault(nm.rsplit(".", 1)[-1], tuple(t[0][:3]))
|
|
416
|
+
return (tuple(blocks), tuple(spans), tuple(line_tints), name_tints)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
# ── Ctrl+B ────────────────────────────────────────────────────────────────────
|
|
420
|
+
|
|
421
|
+
class _Sym:
|
|
422
|
+
"""The tiny `su`-shaped object _present_usage_targets reads `.name` off."""
|
|
423
|
+
__slots__ = ("name", "entry")
|
|
424
|
+
|
|
425
|
+
def __init__(self, name, entry):
|
|
426
|
+
self.name = name
|
|
427
|
+
self.entry = entry
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def ctrl_b_lookup(full_text, pos, view_path, line_offset=0, project=None):
|
|
431
|
+
"""Resolve Ctrl+B at full-buffer index `pos`. Returns None when the
|
|
432
|
+
caret isn't on a resolvable symbol, else
|
|
433
|
+
(start, end, sym, at_def, targets)
|
|
434
|
+
with start/end the caret part's span in `full_text` coordinates, `sym`
|
|
435
|
+
carrying `.name`, and `targets` a list of UsageRef: [definition] at a
|
|
436
|
+
usage, or the usages at the definition. Coordinates are pending/live
|
|
437
|
+
throughout — no disk bridge."""
|
|
438
|
+
from meltygui.code.libcst_conversion import UsageRef
|
|
439
|
+
from pathlib import Path
|
|
440
|
+
if view_path is None:
|
|
441
|
+
return None
|
|
442
|
+
hit = roster.chain_at(full_text, pos)
|
|
443
|
+
if hit is None:
|
|
444
|
+
return None
|
|
445
|
+
cs, ce, chain, part_ix = hit
|
|
446
|
+
parts = chain.split(".")
|
|
447
|
+
vpath = roster._norm(str(view_path))
|
|
448
|
+
t0 = time.perf_counter()
|
|
449
|
+
project = roster.analysis_project(project, view_path)
|
|
450
|
+
roster.sweep(force=True, project=project) # Ctrl+B is live and wants fresh tables
|
|
451
|
+
with roster.pass_scope():
|
|
452
|
+
return _lookup(full_text, pos, vpath, line_offset, cs, ce, chain, parts,
|
|
453
|
+
part_ix, t0, project)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def _lookup(full_text, pos, vpath, line_offset, cs, ce, chain, parts, part_ix, t0, project=None):
|
|
457
|
+
from meltygui.code.libcst_conversion import UsageRef
|
|
458
|
+
from pathlib import Path
|
|
459
|
+
own = roster.table_for(vpath, live_text=full_text, line_offset=line_offset)
|
|
460
|
+
caret_line = full_text.count("\n", 0, cs) + 1 + line_offset
|
|
461
|
+
sc = own.scope_at(caret_line)
|
|
462
|
+
# Locals first: a bare name bound in the caret's function (or an
|
|
463
|
+
# enclosing one) is that local - binding <-> every occurrence in scope.
|
|
464
|
+
if sc is not None and part_ix == 0:
|
|
465
|
+
loc = _local_ctrl_b(full_text, line_offset, own, sc, parts[0], cs, caret_line)
|
|
466
|
+
if loc is not None:
|
|
467
|
+
return loc
|
|
468
|
+
res = roster.resolve_prefixes(vpath, parts[:part_ix + 1], own, scope=sc, project=project)
|
|
469
|
+
entry = None
|
|
470
|
+
for ent, n in res:
|
|
471
|
+
if n == part_ix + 1:
|
|
472
|
+
entry = ent
|
|
473
|
+
if entry is None:
|
|
474
|
+
return None
|
|
475
|
+
# The caret part's own span.
|
|
476
|
+
off = cs
|
|
477
|
+
for i, p in enumerate(parts):
|
|
478
|
+
if i == part_ix:
|
|
479
|
+
ps, pe = off, off + len(p)
|
|
480
|
+
break
|
|
481
|
+
off += len(p) + 1
|
|
482
|
+
else:
|
|
483
|
+
ps, pe = cs, ce
|
|
484
|
+
file_line = full_text.count("\n", 0, ps) + 1 + line_offset
|
|
485
|
+
at_def = (entry.path == vpath and entry.line == file_line)
|
|
486
|
+
sym = _Sym(entry.name, entry)
|
|
487
|
+
if not at_def:
|
|
488
|
+
d = UsageRef(Path(entry.path), entry.line, entry.col,
|
|
489
|
+
scope=entry.parent or "<module>", module_name="")
|
|
490
|
+
return ps, pe, sym, False, [d]
|
|
491
|
+
uses = roster.usages_of(entry, live={vpath: (full_text, line_offset)}, project=project)
|
|
492
|
+
targets = [UsageRef(Path(u.path), u.line, u.col, scope=u.scope,
|
|
493
|
+
module_name="") for u in uses]
|
|
494
|
+
ms = (time.perf_counter() - t0) * 1000.0
|
|
495
|
+
print(f"[roster ctrl+b] {entry.qualname} ({entry.kind}) -> {len(targets)} usages "
|
|
496
|
+
f"in {ms:.0f}ms")
|
|
497
|
+
return ps, pe, sym, True, targets
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def _local_ctrl_b(full_text, line_offset, own, sc, name, cs, caret_line):
|
|
501
|
+
"""Ctrl+B on a function local. Returns (start, end, sym, at_def, targets)
|
|
502
|
+
or None when `name` isn't a local visible from `sc`."""
|
|
503
|
+
from meltygui.code.libcst_conversion import UsageRef
|
|
504
|
+
from pathlib import Path
|
|
505
|
+
# Outermost enclosing def: its scope covers every nested scope.
|
|
506
|
+
outer = sc
|
|
507
|
+
while outer is not None and outer.parent:
|
|
508
|
+
pe = own.by_qualname.get(outer.parent)
|
|
509
|
+
if pe is None or pe.kind != "def":
|
|
510
|
+
break
|
|
511
|
+
outer = pe
|
|
512
|
+
lines = full_text.split("\n")
|
|
513
|
+
bindings = roster.local_bindings(full_text, own, line_offset, lines, [outer])
|
|
514
|
+
lk = roster.local_key(own, sc, name, bindings)
|
|
515
|
+
if lk is None:
|
|
516
|
+
return None
|
|
517
|
+
scope_e = own.by_qualname.get(lk[0])
|
|
518
|
+
if scope_e is None:
|
|
519
|
+
return None
|
|
520
|
+
starts = roster._line_starts(full_text)
|
|
521
|
+
b0 = max(0, scope_e.line - 1 - line_offset)
|
|
522
|
+
b1 = min(len(lines) - 1, scope_e.end - 1 - line_offset)
|
|
523
|
+
lo_idx = starts[b0]
|
|
524
|
+
hi_idx = starts[b1 + 1] - 1 if b1 + 1 < len(starts) else len(full_text)
|
|
525
|
+
occ = []
|
|
526
|
+
for s, e, chain in roster.iter_chains(full_text, lo_idx, hi_idx):
|
|
527
|
+
dot = chain.find(".")
|
|
528
|
+
first = chain if dot == -1 else chain[:dot]
|
|
529
|
+
if first != name:
|
|
530
|
+
continue
|
|
531
|
+
ln = full_text.count("\n", 0, s) # cheap enough for one scope
|
|
532
|
+
fl = ln + 1 + line_offset
|
|
533
|
+
osc = own.scope_at(fl)
|
|
534
|
+
if roster.local_key(own, osc, name, bindings) != lk:
|
|
535
|
+
continue
|
|
536
|
+
occ.append((s, s + len(first), fl, s - starts[ln]))
|
|
537
|
+
blines = {b.line for b in bindings[lk]}
|
|
538
|
+
sym = _Sym(name, None)
|
|
539
|
+
at_def = caret_line in blines
|
|
540
|
+
if at_def:
|
|
541
|
+
targets = [UsageRef(Path(own.path), fl, col, scope=lk[0], module_name="")
|
|
542
|
+
for (s, e, fl, col) in occ if not (s <= cs < e)]
|
|
543
|
+
else:
|
|
544
|
+
cands = [b for b in bindings[lk] if b.line <= caret_line] or bindings[lk][:1]
|
|
545
|
+
b = cands[-1]
|
|
546
|
+
targets = [UsageRef(Path(own.path), b.line, b.col, scope=lk[0], module_name="")]
|
|
547
|
+
return cs, cs + len(name), sym, at_def, targets
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""A single source preview for inspection and symbol navigation."""
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from meltygui.core.core_render import render_func
|
|
4
|
+
from meltygui.core.conversion.dict_conversion import DictConversion
|
|
5
|
+
|
|
6
|
+
_pending = globals().get('_pending')
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def open_source_preview(path, line=None, token=None):
|
|
10
|
+
global _pending
|
|
11
|
+
_pending = (str(path), line, token)
|
|
12
|
+
from meltygui.core.windowing.glfw_utils import request_render
|
|
13
|
+
request_render()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
from meltygui.state.code_state import SourcePreviewState
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""State owned by optional source-view integrations."""
|
|
2
|
+
from meltygui.core.conversion.dict_conversion import DictConversion
|
|
3
|
+
from meltygui.core.rendering.core_decoration import no_save
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@no_save('tools')
|
|
7
|
+
class SourceToolsState(DictConversion):
|
|
8
|
+
def __init__(self):
|
|
9
|
+
super().__init__()
|
|
10
|
+
self.tools = None
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Shared source and file presentation helpers."""
|
|
2
|
+
import colorsys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from collections import namedtuple
|
|
5
|
+
|
|
6
|
+
def _tab_text_color(tint, brightness, saturation, min_brightness=0.0):
|
|
7
|
+
"""Tab-label color: the tab's tint with its hsv saturation and value
|
|
8
|
+
scaled by the Toggles.CodeEditor tab text knobs. Computed directly and
|
|
9
|
+
handed to flat_button as text_color — its own text pipeline mixes only
|
|
10
|
+
`factor` worth of theme color into the raw tint, which compressed these
|
|
11
|
+
knobs to a ~10% effect. min_brightness floors the scaled value so dark
|
|
12
|
+
tints stay legible."""
|
|
13
|
+
h, s, v = colorsys.rgb_to_hsv(*tint[:3])
|
|
14
|
+
return colorsys.hsv_to_rgb(h, min(max(s * saturation, 0.0), 1.0),
|
|
15
|
+
min(max(v * brightness, min_brightness), 1.0))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _file_meta_tint(path):
|
|
20
|
+
"""The tint the user painted on this file (FileMeta — same store the
|
|
21
|
+
editor tabs and folder tree read), or None."""
|
|
22
|
+
from meltygui.models.file_meta import FileMeta
|
|
23
|
+
from meltygui.models.file_meta import file_meta_store
|
|
24
|
+
tint = FileMeta.painted_tint(file_meta_store().get(str(path)))
|
|
25
|
+
return tuple(tint[:3]) if tint else None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class _RowSpan:
|
|
29
|
+
"""jump_to shim for a one-line search-row buffer: draw_text reads `.start`
|
|
30
|
+
(0-based file line of the buffer's first line) to offset every tree-derived
|
|
31
|
+
wash into buffer space. `path` is the row's file so the ROSTER tint path
|
|
32
|
+
(text_editor._def_tints in roster mode needs a view_path to resolve the
|
|
33
|
+
row's symbols — calls like `draw_any(...)` wash in their definition's
|
|
34
|
+
tint); `pending_coords=True` tells the editor our line numbers are
|
|
35
|
+
already PENDING coordinates so it must NOT run the disk→pending delta
|
|
36
|
+
bridge a second time. `source` mirrors Address's slot (None) for any
|
|
37
|
+
duck-typed reader; the jump-to BAR itself is off (show_jump_bar=False)."""
|
|
38
|
+
__slots__ = ("start", "path", "end", "source", "pending_coords")
|
|
39
|
+
|
|
40
|
+
def __init__(self, start, path=None):
|
|
41
|
+
self.start = start
|
|
42
|
+
self.end = start + 1
|
|
43
|
+
self.path = path
|
|
44
|
+
self.pending_coords = True
|
|
45
|
+
self.source = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _row_code_hosts(path):
|
|
49
|
+
"""(code_dict, dict_host) for a search row's file from the SHARED
|
|
50
|
+
code-host cache — the same parse the editor renders with. Creating a host
|
|
51
|
+
is cheap; its whole-file parse runs in the background and rows repaint
|
|
52
|
+
when it lands (notify_on_change on the row's ds). Only called for rows
|
|
53
|
+
actually drawn (≤ max_visible), and hosts are cached across queries.
|
|
54
|
+
Non-Python files get (None, None) — plain syntax colors."""
|
|
55
|
+
if not str(path).endswith(".py"):
|
|
56
|
+
return None, None
|
|
57
|
+
try:
|
|
58
|
+
from meltygui.code.new_converters import code_hosts_for
|
|
59
|
+
_str_host, dict_host = code_hosts_for(Path(path))
|
|
60
|
+
code_dict = dict_host._held()
|
|
61
|
+
return (code_dict if isinstance(code_dict, dict) else None), dict_host
|
|
62
|
+
except Exception:
|
|
63
|
+
traceback.print_exc()
|
|
64
|
+
return None, None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
SearchHit = namedtuple("SearchHit", "label tint activate kind match state keep_open set_state icon parts goto code_row file sym terms", defaults=("", None, None, False, None, None, None, None, None, None, None, None))
|
|
68
|
+
|
|
69
|
+
def _category_tint(kind):
|
|
70
|
+
return {"Actions": (0.92, 0.58, 0.25), "Functions": (0.55, 0.72, 0.85), "Classes": (0.72, 0.62, 0.35)}.get(kind, (0.55, 0.72, 0.85))
|