meltygui 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (372) hide show
  1. meltygui/__init__.py +107 -0
  2. meltygui/accounts/__init__.py +0 -0
  3. meltygui/accounts/internet_accounts.py +1355 -0
  4. meltygui/chat/__init__.py +91 -0
  5. meltygui/chat/activity.py +75 -0
  6. meltygui/chat/backends.py +36 -0
  7. meltygui/chat/chat_interface.py +732 -0
  8. meltygui/chat/chat_proxy.py +352 -0
  9. meltygui/chat/codex_proxy.py +592 -0
  10. meltygui/chat/codex_settings.py +100 -0
  11. meltygui/chat/codex_transport.py +60 -0
  12. meltygui/chat/command_parser.py +204 -0
  13. meltygui/chat/images.py +227 -0
  14. meltygui/chat/messages.py +417 -0
  15. meltygui/chat/metadata.py +139 -0
  16. meltygui/chat/writer_locks.py +64 -0
  17. meltygui/code/__init__.py +0 -0
  18. meltygui/code/basic_converters.py +533 -0
  19. meltygui/code/chain_converters.py +2111 -0
  20. meltygui/code/code_checks.py +2209 -0
  21. meltygui/code/core_syntax.py +1430 -0
  22. meltygui/code/file_converters.py +1933 -0
  23. meltygui/code/fileref.py +702 -0
  24. meltygui/code/hotswap_guard.py +144 -0
  25. meltygui/code/libcst_conversion.py +9724 -0
  26. meltygui/code/live_instrument.py +392 -0
  27. meltygui/code/live_view.py +2490 -0
  28. meltygui/code/melty_scan.py +2684 -0
  29. meltygui/code/new_codecs.py +1255 -0
  30. meltygui/code/new_converters.py +3017 -0
  31. meltygui/code/project_code.py +278 -0
  32. meltygui/code/source_context.py +63 -0
  33. meltygui/code/symbol_roster.py +1588 -0
  34. meltygui/code/syntax_check.py +34 -0
  35. meltygui/code/syntax_check_worker.py +114 -0
  36. meltygui/completion/__init__.py +0 -0
  37. meltygui/completion/fim.py +1232 -0
  38. meltygui/completion/fim_context.py +481 -0
  39. meltygui/completion/providers/__init__.py +0 -0
  40. meltygui/completion/providers/anthropic_oauth.py +446 -0
  41. meltygui/completion/providers/anthropic_requests.py +69 -0
  42. meltygui/completion/providers/claude.py +203 -0
  43. meltygui/completion/providers/claude_usage.py +499 -0
  44. meltygui/completion/providers/codex_accounts.py +180 -0
  45. meltygui/completion/providers/copilot.py +617 -0
  46. meltygui/completion/providers/oauth_popup.py +220 -0
  47. meltygui/completion/providers/ollama.py +320 -0
  48. meltygui/completion/providers/profiles.py +23 -0
  49. meltygui/core/README.md +88 -0
  50. meltygui/core/__init__.py +1 -0
  51. meltygui/core/automation/__init__.py +1 -0
  52. meltygui/core/automation/action_core.py +153 -0
  53. meltygui/core/automation/collection_action.py +45 -0
  54. meltygui/core/automation/mcp_eval.py +167 -0
  55. meltygui/core/automation/mcp_hotswap.py +107 -0
  56. meltygui/core/automation/mcp_query.py +552 -0
  57. meltygui/core/automation/mcp_server.py +657 -0
  58. meltygui/core/automation/orchestration_core.py +2636 -0
  59. meltygui/core/automation/query_core.py +124 -0
  60. meltygui/core/automation/search_core.py +26 -0
  61. meltygui/core/automation/selector_core.py +340 -0
  62. meltygui/core/automation/value_core.py +1752 -0
  63. meltygui/core/cache/__init__.py +1 -0
  64. meltygui/core/cache/cache_diagnostics.py +0 -0
  65. meltygui/core/cache/invalidation_decoration.py +153 -0
  66. meltygui/core/cache/invalidation_tracker.py +43 -0
  67. meltygui/core/cache/tile_cache.py +5968 -0
  68. meltygui/core/conversion/__init__.py +1 -0
  69. meltygui/core/conversion/bubbling.py +599 -0
  70. meltygui/core/conversion/cache_tree.py +188 -0
  71. meltygui/core/conversion/chain.py +113 -0
  72. meltygui/core/conversion/converter_register.py +145 -0
  73. meltygui/core/conversion/data_decoration.py +67 -0
  74. meltygui/core/conversion/dict_conversion.py +1815 -0
  75. meltygui/core/conversion/dict_conversion_util.py +177 -0
  76. meltygui/core/conversion/dynamic_obj.py +89 -0
  77. meltygui/core/conversion/graph_compare.py +183 -0
  78. meltygui/core/conversion/load_save_v2.py +1032 -0
  79. meltygui/core/conversion/missing_saved_class.py +39 -0
  80. meltygui/core/conversion/path_finder.py +606 -0
  81. meltygui/core/conversion/render_host.py +1083 -0
  82. meltygui/core/core_render.py +6537 -0
  83. meltygui/core/definition_hotswap.py +231 -0
  84. meltygui/core/diagnostics/__init__.py +1 -0
  85. meltygui/core/diagnostics/attribute_churn.py +38 -0
  86. meltygui/core/diagnostics/fps_counter.py +39 -0
  87. meltygui/core/diagnostics/gpu_frame_timer.py +103 -0
  88. meltygui/core/diagnostics/inspection_core.py +169 -0
  89. meltygui/core/diagnostics/monitor_core.py +105 -0
  90. meltygui/core/diagnostics/notifications.py +706 -0
  91. meltygui/core/diagnostics/perf_trace.py +281 -0
  92. meltygui/core/diagnostics/profile_decoration.py +88 -0
  93. meltygui/core/diagnostics/resize_trace.py +62 -0
  94. meltygui/core/diagnostics/screenshot_core.py +247 -0
  95. meltygui/core/diagnostics/session_status.py +98 -0
  96. meltygui/core/diagnostics/trace_core.py +445 -0
  97. meltygui/core/files/__init__.py +1 -0
  98. meltygui/core/files/file_core.py +208 -0
  99. meltygui/core/files/file_explorer_core.py +104 -0
  100. meltygui/core/files/file_tree_core.py +198 -0
  101. meltygui/core/files/file_watch_core.py +43 -0
  102. meltygui/core/files/import_graph_core.py +43 -0
  103. meltygui/core/files/metadata_core.py +51 -0
  104. meltygui/core/graphics/__init__.py +1 -0
  105. meltygui/core/graphics/cuda_context_core.py +166 -0
  106. meltygui/core/graphics/cuda_interop_core.py +136 -0
  107. meltygui/core/graphics/cuda_kernel_core.py +91 -0
  108. meltygui/core/graphics/framebuffer_recorder.py +337 -0
  109. meltygui/core/graphics/gl_state.py +658 -0
  110. meltygui/core/graphics/lut_core.py +52 -0
  111. meltygui/core/graphics/overlay_renderer.py +984 -0
  112. meltygui/core/graphics/scene_target.py +180 -0
  113. meltygui/core/graphics/screenshot.py +439 -0
  114. meltygui/core/graphics/shader_func.py +478 -0
  115. meltygui/core/graphics/tensor_core.py +45 -0
  116. meltygui/core/graphics/text_texture.py +329 -0
  117. meltygui/core/graphics/wayland_color.py +635 -0
  118. meltygui/core/input/__init__.py +1 -0
  119. meltygui/core/input/collision.py +165 -0
  120. meltygui/core/input/drag_drop_core.py +1525 -0
  121. meltygui/core/input/hypr_left_drag.py +323 -0
  122. meltygui/core/input/input_core.py +245 -0
  123. meltygui/core/input/input_handler.py +1101 -0
  124. meltygui/core/input/mouse_cursor.py +355 -0
  125. meltygui/core/input/pynput_backend.py +1054 -0
  126. meltygui/core/input/space_mouse.py +338 -0
  127. meltygui/core/input/touchpad_backend.py +393 -0
  128. meltygui/core/input/view_selection.py +177 -0
  129. meltygui/core/layout/__init__.py +1 -0
  130. meltygui/core/layout/column_core.py +2153 -0
  131. meltygui/core/layout/cursor_core.py +161 -0
  132. meltygui/core/layout/dropdown_core.py +278 -0
  133. meltygui/core/layout/edge_constraints.py +155 -0
  134. meltygui/core/layout/grid_core.py +117 -0
  135. meltygui/core/layout/header_core.py +23 -0
  136. meltygui/core/layout/header_runtime.py +67 -0
  137. meltygui/core/layout/layout_core.py +87 -0
  138. meltygui/core/layout/tile_manager_core.py +591 -0
  139. meltygui/core/melty.py +6726 -0
  140. meltygui/core/module_map.json +896 -0
  141. meltygui/core/module_names.py +19 -0
  142. meltygui/core/rendering/__init__.py +1 -0
  143. meltygui/core/rendering/core_decoration.py +431 -0
  144. meltygui/core/rendering/core_render_helpers.py +328 -0
  145. meltygui/core/rendering/func_metadata.py +398 -0
  146. meltygui/core/rendering/mode.py +818 -0
  147. meltygui/core/rendering/mode_defaults.py +41 -0
  148. meltygui/core/rendering/modes.py +136 -0
  149. meltygui/core/rendering/parameter_core.py +1665 -0
  150. meltygui/core/rendering/render_dispatch.py +1891 -0
  151. meltygui/core/rendering/render_funcs.py +273 -0
  152. meltygui/core/rendering/shaped.py +312 -0
  153. meltygui/core/rendering/window_decoration.py +25 -0
  154. meltygui/core/runtime/__init__.py +1 -0
  155. meltygui/core/runtime/app.py +735 -0
  156. meltygui/core/runtime/app_session.py +140 -0
  157. meltygui/core/runtime/background.py +564 -0
  158. meltygui/core/runtime/extensions.py +53 -0
  159. meltygui/core/runtime/gc_manager.py +1163 -0
  160. meltygui/core/runtime/lifecycle.py +21 -0
  161. meltygui/core/runtime/paths.py +27 -0
  162. meltygui/core/runtime/settings.py +14 -0
  163. meltygui/core/runtime/singleton.py +16 -0
  164. meltygui/core/runtime/thread_safe_bool.py +24 -0
  165. meltygui/core/runtime/thread_signal.py +30 -0
  166. meltygui/core/runtime/toggles.py +3115 -0
  167. meltygui/core/services/__init__.py +1 -0
  168. meltygui/core/services/account_core.py +10 -0
  169. meltygui/core/services/chat_core.py +19 -0
  170. meltygui/core/services/claude_terminal_core.py +346 -0
  171. meltygui/core/services/terminal_core.py +458 -0
  172. meltygui/core/services/terminal_runtime.py +78 -0
  173. meltygui/core/styling/__init__.py +1 -0
  174. meltygui/core/styling/color_core.py +46 -0
  175. meltygui/core/styling/fonts.py +639 -0
  176. meltygui/core/styling/global_style.py +338 -0
  177. meltygui/core/styling/style.py +198 -0
  178. meltygui/core/styling/style_core.py +522 -0
  179. meltygui/core/styling/warm_start.py +149 -0
  180. meltygui/core/windowing/__init__.py +1 -0
  181. meltygui/core/windowing/backends/PYIMGUI_LICENSE +28 -0
  182. meltygui/core/windowing/backends/__init__.py +1 -0
  183. meltygui/core/windowing/backends/imgui_renderer.py +138 -0
  184. meltygui/core/windowing/backends/native_wayland.py +850 -0
  185. meltygui/core/windowing/backends/protocols/xdg-decoration-unstable-v1.xml +156 -0
  186. meltygui/core/windowing/backends/protocols/xdg-shell.xml +1420 -0
  187. meltygui/core/windowing/backends/wayland_protocol.py +101 -0
  188. meltygui/core/windowing/dock_core.py +182 -0
  189. meltygui/core/windowing/frame_geometry.py +42 -0
  190. meltygui/core/windowing/geometry_feed.py +864 -0
  191. meltygui/core/windowing/glfw_utils.py +1343 -0
  192. meltygui/core/windowing/os_frame.py +1552 -0
  193. meltygui/core/windowing/surface.py +643 -0
  194. meltygui/core/windowing/titlebar.py +1560 -0
  195. meltygui/core/windowing/titlebar_buttons.py +281 -0
  196. meltygui/core/windowing/wayland_move.py +932 -0
  197. meltygui/core/windowing/window_api.py +62 -0
  198. meltygui/core/windowing/window_constants.py +339 -0
  199. meltygui/core/windowing/window_visibility.py +162 -0
  200. meltygui/debug/__init__.py +0 -0
  201. meltygui/debug/app_view_utils.py +9 -0
  202. meltygui/editor/__init__.py +0 -0
  203. meltygui/editor/bash_syntax.py +30 -0
  204. meltygui/editor/code_line_fast.py +152 -0
  205. meltygui/editor/diff.py +139 -0
  206. meltygui/editor/external_changes.py +159 -0
  207. meltygui/editor/file_header.py +41 -0
  208. meltygui/editor/live_usage.py +169 -0
  209. meltygui/editor/live_view_views.py +1803 -0
  210. meltygui/editor/pending_save.py +1351 -0
  211. meltygui/editor/roster_tints.py +547 -0
  212. meltygui/editor/source_preview.py +16 -0
  213. meltygui/editor/source_tools.py +10 -0
  214. meltygui/editor/source_ui.py +70 -0
  215. meltygui/editor/spell_check.py +77 -0
  216. meltygui/editor/text_editor.py +9076 -0
  217. meltygui/editor/usage_picker.py +538 -0
  218. meltygui/events/__init__.py +0 -0
  219. meltygui/events/example.py +118 -0
  220. meltygui/examples/__init__.py +0 -0
  221. meltygui/examples/columns_demo.py +33 -0
  222. meltygui/examples/columns_window_demo.py +60 -0
  223. meltygui/examples/context_menu_demo.py +24 -0
  224. meltygui/examples/context_menu_window_demo.py +49 -0
  225. meltygui/examples/gui_playground.py +127 -0
  226. meltygui/examples/live_view_playground.py +256 -0
  227. meltygui/examples/lora.py +43 -0
  228. meltygui/examples/lora_data.py +59 -0
  229. meltygui/examples/lora_policies.py +67 -0
  230. meltygui/examples/mode_demo.py +49 -0
  231. meltygui/examples/modifies_demo.py +119 -0
  232. meltygui/examples/scalar_policies.py +60 -0
  233. meltygui/examples/style_layouts.py +149 -0
  234. meltygui/examples/tile_manager_demo.py +74 -0
  235. meltygui/examples/tint_demo.py +160 -0
  236. meltygui/examples/tint_functions.py +56 -0
  237. meltygui/examples/trace_demo.py +88 -0
  238. meltygui/examples/two_windows.py +36 -0
  239. meltygui/files/__init__.py +0 -0
  240. meltygui/files/fast_file_explorer.py +439 -0
  241. meltygui/gnome_extension/lsd-window-geometry@latent-descent/extension.js +237 -0
  242. meltygui/gnome_extension/lsd-window-geometry@latent-descent/lsd-window-geometry@latent-descent.iml +9 -0
  243. meltygui/gnome_extension/lsd-window-geometry@latent-descent/metadata.json +7 -0
  244. meltygui/graphics/__init__.py +6 -0
  245. meltygui/graphics/base.py +85 -0
  246. meltygui/graphics/examples.py +507 -0
  247. meltygui/graphics/executor.py +520 -0
  248. meltygui/graphics/filter.py +667 -0
  249. meltygui/graphics/filter.pyi +856 -0
  250. meltygui/graphics/generate_stubs.py +22 -0
  251. meltygui/graphics/registry.py +203 -0
  252. meltygui/graphics/shader_compiler.py +155 -0
  253. meltygui/graphics/shaders.py +1302 -0
  254. meltygui/graphics/stub_generator.py +250 -0
  255. meltygui/graphics/texture_manager.py +170 -0
  256. meltygui/graphics/texture_min_max.py +295 -0
  257. meltygui/hdr_color.py +757 -0
  258. meltygui/image_load.py +308 -0
  259. meltygui/model/__init__.py +1 -0
  260. meltygui/model/account_model.py +142 -0
  261. meltygui/model/camera_model.py +159 -0
  262. meltygui/model/chat_model.py +86 -0
  263. meltygui/model/code_model.py +62 -0
  264. meltygui/model/code_proxy_model.py +876 -0
  265. meltygui/model/collection_model.py +41 -0
  266. meltygui/model/color_model.py +127 -0
  267. meltygui/model/cuda_tensor_model.py +36 -0
  268. meltygui/model/cuda_texture_model.py +149 -0
  269. meltygui/model/dropdown_model.py +137 -0
  270. meltygui/model/file_metadata_model.py +73 -0
  271. meltygui/model/file_model.py +289 -0
  272. meltygui/model/format_model.py +390 -0
  273. meltygui/model/graph_model.py +98 -0
  274. meltygui/model/icon_model.py +1024 -0
  275. meltygui/model/import_graph_model.py +468 -0
  276. meltygui/model/layout_model.py +15 -0
  277. meltygui/model/lut_model.py +266 -0
  278. meltygui/model/search_model.py +173 -0
  279. meltygui/model/tensor_model.py +402 -0
  280. meltygui/model/terminal_model.py +329 -0
  281. meltygui/model/texture_model.py +146 -0
  282. meltygui/model/tile_model.py +24 -0
  283. meltygui/model/trace_model.py +198 -0
  284. meltygui/model/trace_report_model.py +146 -0
  285. meltygui/models/__init__.py +0 -0
  286. meltygui/models/file_meta.py +613 -0
  287. meltygui/models/function_console.py +184 -0
  288. meltygui/models/orchestration.py +48 -0
  289. meltygui/pbr.py +1576 -0
  290. meltygui/png_unfilter.c +49 -0
  291. meltygui/resources/JetBrainsMono-Regular.ttf +0 -0
  292. meltygui/resources/THIRD_PARTY_NOTICES.md +21 -0
  293. meltygui/resources/dejavu/DejaVuSans-Bold.ttf +0 -0
  294. meltygui/resources/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
  295. meltygui/resources/dejavu/DejaVuSans.ttf +0 -0
  296. meltygui/resources/dejavu/LICENSE.txt +78 -0
  297. meltygui/resources/fontawesome-LICENSE.txt +121 -0
  298. meltygui/resources/fontawesome-webfont.ttf +0 -0
  299. meltygui/resources/hdri/studio_small_09_1k.hdr +0 -0
  300. meltygui/resources/jetbrains-weights/JetBrainsMono-Bold.ttf +0 -0
  301. meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraBold.ttf +0 -0
  302. meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraLight.ttf +0 -0
  303. meltygui/resources/jetbrains-weights/JetBrainsMono-Light.ttf +0 -0
  304. meltygui/resources/jetbrains-weights/JetBrainsMono-Medium.ttf +0 -0
  305. meltygui/resources/jetbrains-weights/JetBrainsMono-SemiBold.ttf +0 -0
  306. meltygui/resources/jetbrains-weights/JetBrainsMono-Thin.ttf +0 -0
  307. meltygui/resources/jetbrains-weights/OFL.txt +93 -0
  308. meltygui/resources/jetbrains-weights/README.md +2 -0
  309. meltygui/state/__init__.py +0 -0
  310. meltygui/state/account_state.py +22 -0
  311. meltygui/state/animation_state.py +97 -0
  312. meltygui/state/annotation_state.py +22 -0
  313. meltygui/state/chat_state.py +36 -0
  314. meltygui/state/code_state.py +10 -0
  315. meltygui/state/core_enums.py +59 -0
  316. meltygui/state/core_markers.py +115 -0
  317. meltygui/state/core_undo.py +1075 -0
  318. meltygui/state/file_state.py +75 -0
  319. meltygui/state/graph_state.py +26 -0
  320. meltygui/state/inspection_state.py +47 -0
  321. meltygui/state/menu_state.py +10 -0
  322. meltygui/state/model_enums.py +11 -0
  323. meltygui/state/new_core_model.py +2394 -0
  324. meltygui/state/orchestration_state.py +14 -0
  325. meltygui/state/query_state.py +24 -0
  326. meltygui/state/tensor_state.py +10 -0
  327. meltygui/state/terminal_state.py +23 -0
  328. meltygui/state/trace_state.py +32 -0
  329. meltygui/state/voxel_state.py +12 -0
  330. meltygui/text_index.py +816 -0
  331. meltygui/utils/__init__.py +0 -0
  332. meltygui/utils/jump_to_code.py +344 -0
  333. meltygui/utils/pkl_inspect.py +90 -0
  334. meltygui/utils/render_utils.py +1419 -0
  335. meltygui/view/__init__.py +1 -0
  336. meltygui/view/account_view.py +524 -0
  337. meltygui/view/action_view.py +73 -0
  338. meltygui/view/chat_decoration_view.py +112 -0
  339. meltygui/view/chat_view.py +1703 -0
  340. meltygui/view/code_view.py +2677 -0
  341. meltygui/view/collection_view.py +1185 -0
  342. meltygui/view/color_view.py +824 -0
  343. meltygui/view/control_view.py +559 -0
  344. meltygui/view/decoration_view.py +439 -0
  345. meltygui/view/diagnostic_view.py +177 -0
  346. meltygui/view/dropdown_view.py +1082 -0
  347. meltygui/view/file_view.py +1599 -0
  348. meltygui/view/graph_cuda_view.py +105 -0
  349. meltygui/view/graph_view.py +882 -0
  350. meltygui/view/header_view.py +848 -0
  351. meltygui/view/input_view.py +238 -0
  352. meltygui/view/inspection_view.py +1783 -0
  353. meltygui/view/layout_view.py +596 -0
  354. meltygui/view/lut_view.py +45 -0
  355. meltygui/view/menu_view.py +212 -0
  356. meltygui/view/orchestration_view.py +658 -0
  357. meltygui/view/query_view.py +118 -0
  358. meltygui/view/search_view.py +342 -0
  359. meltygui/view/tab_view.py +258 -0
  360. meltygui/view/tensor_view.py +430 -0
  361. meltygui/view/terminal_view.py +355 -0
  362. meltygui/view/text_view.py +8031 -0
  363. meltygui/view/texture_view.py +480 -0
  364. meltygui/view/tile_view.py +38 -0
  365. meltygui/view/trace_view.py +923 -0
  366. meltygui/view/voxel_cuda_view.py +824 -0
  367. meltygui/view/voxel_view.py +1860 -0
  368. meltygui/view/window_view.py +111 -0
  369. meltygui-0.1.0.dist-info/METADATA +143 -0
  370. meltygui-0.1.0.dist-info/RECORD +372 -0
  371. meltygui-0.1.0.dist-info/WHEEL +4 -0
  372. meltygui-0.1.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,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))