meltygui 0.1.2__py3-none-any.whl → 0.1.8__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 +32 -51
- meltygui/accounts/internet_accounts.py +9 -9
- meltygui/chat/chat_interface.py +5 -5
- meltygui/chat/images.py +1 -1
- meltygui/chat/main.py +13 -7
- meltygui/code/chain_converters.py +1 -1
- meltygui/code/codec_registry.py +4 -2
- meltygui/code/embedded_jedi.py +47 -0
- meltygui/code/fileref.py +18 -2
- meltygui/code/libcst_conversion.py +60 -25
- meltygui/code/new_codecs.py +118 -12
- meltygui/code/new_converters.py +20 -8
- meltygui/code/project_code.py +35 -4
- meltygui/code/source_context.py +170 -8
- meltygui/code/symbol_roster.py +418 -58
- meltygui/completion/providers/anthropic_oauth.py +1 -1
- meltygui/completion/providers/anthropic_requests.py +1 -1
- meltygui/completion/providers/claude_usage.py +9 -9
- meltygui/completion/providers/codex_accounts.py +1 -1
- meltygui/core/automation/orchestration_core.py +4 -4
- meltygui/core/automation/query_core.py +2 -2
- meltygui/core/cache/deferred_invalidation.py +88 -0
- meltygui/core/cache/mask_batch.py +16 -4
- meltygui/core/cache/tile_cache.py +770 -607
- meltygui/core/conversion/dict_conversion_util.py +5 -4
- meltygui/core/conversion/render_host.py +1 -2
- meltygui/core/core_render.py +162 -191
- meltygui/core/diagnostics/edge_motion_guard.py +16 -10
- meltygui/core/diagnostics/fps_counter.py +41 -1
- meltygui/core/diagnostics/geometry_jitter.py +4 -1
- meltygui/core/diagnostics/resize_trace.py +6 -2
- meltygui/core/files/ios_file_observer.py +147 -0
- meltygui/core/files/workspace_paths.py +59 -0
- meltygui/core/graphics/desktop_gl.py +14 -0
- meltygui/core/graphics/gl_state.py +5 -3
- meltygui/core/graphics/metal_renderer.py +273 -0
- meltygui/core/graphics/metal_tiles.py +376 -0
- meltygui/core/graphics/overlay_renderer.py +36 -6
- meltygui/core/graphics/scene_target.py +1 -1
- meltygui/core/graphics/screenshot.py +3 -2
- meltygui/core/graphics/shader_func.py +39 -30
- meltygui/core/graphics/text_texture.py +1 -1
- meltygui/core/input/input_handler.py +86 -6
- meltygui/core/input/ios_input.py +476 -0
- meltygui/core/input/pynput_backend.py +5 -2
- meltygui/core/input/touch_input.py +232 -0
- meltygui/core/layout/column_core.py +48 -33
- meltygui/core/layout/tile_links.py +3 -5
- meltygui/core/layout/tile_manager_core.py +79 -17
- meltygui/core/layout/tile_resize.py +2 -1
- meltygui/core/melty.py +309 -77
- meltygui/core/module_map.json +2 -1
- meltygui/core/rendering/gui_imgui_import.py +40 -0
- meltygui/core/rendering/gui_imgui_input.py +188 -0
- meltygui/core/rendering/gui_layout_prototype.py +666 -0
- meltygui/core/rendering/gui_native_collision.py +156 -0
- meltygui/core/rendering/gui_prototype.py +110 -0
- meltygui/core/rendering/gui_window_prototype.py +96 -0
- meltygui/core/rendering/mode.py +6 -6
- meltygui/core/rendering/overlay.py +130 -20
- meltygui/core/rendering/render_funcs.py +8 -0
- meltygui/core/rendering/retained_gui_prototype.py +593 -0
- meltygui/core/rendering/view_identity.py +17 -3
- meltygui/core/runtime/app.py +200 -35
- meltygui/core/runtime/app_session.py +7 -7
- meltygui/core/runtime/app_settings.py +7 -7
- meltygui/core/runtime/gc_manager.py +1 -1
- meltygui/core/runtime/launch_override.py +6 -3
- meltygui/core/runtime/native_app.py +303 -0
- meltygui/core/runtime/paths.py +65 -3
- meltygui/core/runtime/public_exports.py +79 -0
- meltygui/core/runtime/toggles.py +17 -2
- meltygui/core/services/terminal_core.py +4 -1
- meltygui/core/styling/fonts.py +31 -14
- meltygui/core/styling/style_core.py +7 -3
- meltygui/core/windowing/backends/imgui_renderer.py +5 -0
- meltygui/core/windowing/backends/native_ios.py +197 -0
- meltygui/core/windowing/geometry_feed.py +2 -2
- meltygui/core/windowing/glfw_utils.py +22 -3
- meltygui/core/windowing/melty_windows.py +358 -0
- meltygui/core/windowing/os_frame.py +70 -20
- meltygui/core/windowing/surface.py +87 -169
- meltygui/core/windowing/surface_frame.py +181 -0
- meltygui/core/windowing/titlebar.py +227 -62
- meltygui/core/windowing/titlebar_buttons.py +179 -4
- meltygui/core/windowing/window_api.py +38 -3
- meltygui/editor/pending_save.py +100 -15
- meltygui/editor/text_editor.py +58 -14
- meltygui/examples/retained_gui_demo.py +164 -0
- meltygui/examples/retained_layout_demo.py +146 -0
- meltygui/examples/rust_gui_demo.py +308 -0
- meltygui/files/fast_file_explorer.py +16 -3
- meltygui/graphics/shader_compiler.py +9 -3
- meltygui/image_load.py +5 -0
- meltygui/model/chat_folder_model.py +13 -5
- meltygui/model/chat_model.py +6 -6
- meltygui/model/file_location_model.py +151 -4
- meltygui/model/file_model.py +38 -0
- meltygui/model/folder_icon_model.py +152 -25
- meltygui/model/import_graph_model.py +6 -2
- meltygui/model/source_snapshot_model.py +10 -2
- meltygui/model/ssh_auth_model.py +148 -0
- meltygui/model/ssh_file_model.py +459 -0
- meltygui/model/texture_model.py +59 -3
- meltygui/models/file_meta.py +107 -16
- meltygui/platforms/__init__.py +1 -0
- meltygui/platforms/ios/.gitignore +2 -0
- meltygui/platforms/ios/Host/App.mm +316 -0
- meltygui/platforms/ios/Host/Info.plist +34 -0
- meltygui/platforms/ios/Host/InputQueue.hpp +66 -0
- meltygui/platforms/ios/Host/LocalNetworkAccess.h +96 -0
- meltygui/platforms/ios/Host/MeltyHost.h +25 -0
- meltygui/platforms/ios/Host/MeltyHost.mm +808 -0
- meltygui/platforms/ios/Host/MeltyMetalRenderer.mm +426 -0
- meltygui/platforms/ios/Host/MeltyRenderer.h +32 -0
- meltygui/platforms/ios/Host/SSHAuthentication.h +183 -0
- meltygui/platforms/ios/Python/melty_ios_bootstrap.py +255 -0
- meltygui/platforms/ios/README.md +440 -0
- meltygui/platforms/ios/Renderer/MetalShaders.metal +102 -0
- meltygui/platforms/ios/__init__.py +9 -0
- meltygui/platforms/ios/__main__.py +21 -0
- meltygui/platforms/ios/app_icon.py +441 -0
- meltygui/platforms/ios/application.py +20 -0
- meltygui/platforms/ios/build_crypto.py +269 -0
- meltygui/platforms/ios/build_imgui.py +236 -0
- meltygui/platforms/ios/build_platform_deps.py +278 -0
- meltygui/platforms/ios/build_rust_deps.py +257 -0
- meltygui/platforms/ios/compile_shaders.py +192 -0
- meltygui/platforms/ios/dependencies.json +308 -0
- meltygui/platforms/ios/devices.py +329 -0
- meltygui/platforms/ios/download_numeric_wheels.py +223 -0
- meltygui/platforms/ios/generate.py +216 -0
- meltygui/platforms/ios/prepare_bundle.py +237 -0
- meltygui/platforms/ios/provision.py +851 -0
- meltygui/platforms/ios/rec2020-pq.icc +0 -0
- meltygui/platforms/ios/runtime.py +77 -0
- meltygui/platforms/ios/stage_dependencies.py +409 -0
- meltygui/state/new_core_model.py +109 -24
- meltygui/text_index.py +3 -1
- meltygui/view/account_view.py +2 -2
- meltygui/view/chat_view.py +6 -6
- meltygui/view/collection_view.py +6 -3
- meltygui/view/color_view.py +61 -118
- meltygui/view/control_view.py +4 -3
- meltygui/view/dropdown_view.py +112 -61
- meltygui/view/file_view.py +27 -18
- meltygui/view/graph_view.py +2 -2
- meltygui/view/header_view.py +21 -6
- meltygui/view/menu_view.py +4 -2
- meltygui/view/orchestration_view.py +4 -4
- meltygui/view/path_icon_view.py +7 -5
- meltygui/view/text_view.py +272 -41
- meltygui/view/texture_view.py +33 -19
- meltygui/view/tile_view.py +49 -27
- meltygui/view/trace_view.py +5 -5
- meltygui/view/voxel_view.py +2 -2
- {meltygui-0.1.2.dist-info → meltygui-0.1.8.dist-info}/METADATA +40 -8
- {meltygui-0.1.2.dist-info → meltygui-0.1.8.dist-info}/RECORD +160 -102
- {meltygui-0.1.2.dist-info → meltygui-0.1.8.dist-info}/WHEEL +0 -0
- {meltygui-0.1.2.dist-info → meltygui-0.1.8.dist-info}/licenses/LICENSE +0 -0
meltygui/__init__.py
CHANGED
|
@@ -22,6 +22,7 @@ from meltygui.core.styling.style import default_tint_accumulation
|
|
|
22
22
|
from meltygui.core.styling.style import default_scalar_accumulation
|
|
23
23
|
|
|
24
24
|
from meltygui.core.runtime.app import boot
|
|
25
|
+
from meltygui.core.runtime.app import embedded_imports
|
|
25
26
|
from meltygui.core.runtime.app import glfw_window
|
|
26
27
|
from meltygui.core.runtime.app import run
|
|
27
28
|
from meltygui.core.runtime.app import pressed
|
|
@@ -29,68 +30,31 @@ from meltygui.core.runtime.app import content_size
|
|
|
29
30
|
from meltygui.core.runtime.app import mark
|
|
30
31
|
from meltygui.core.runtime.app import persisted
|
|
31
32
|
from meltygui.core.runtime.app import after_first_frame
|
|
33
|
+
from meltygui.core.runtime.app import checkpoint
|
|
32
34
|
|
|
33
35
|
if TYPE_CHECKING: # IDE / type checkers only; never executed
|
|
36
|
+
import meltygui_imgui as imgui
|
|
37
|
+
from meltygui.core.rendering.gui_prototype import gui, os_window
|
|
34
38
|
from meltygui.state.new_core_model import DrawState
|
|
35
|
-
from meltygui.view.text_view import draw_text
|
|
36
39
|
from meltygui.model.tile_model import multi_instance
|
|
37
40
|
from meltygui.view.chat_view import draw_claude_chat
|
|
38
|
-
from meltygui.view.texture_view import draw_texture
|
|
39
41
|
from meltygui.core.rendering.render_dispatch import draw_any
|
|
40
|
-
from meltygui.view.control_view import draw_button
|
|
41
|
-
from meltygui.view.control_view import draw_str
|
|
42
|
-
from meltygui.view.control_view import draw_float
|
|
43
|
-
from meltygui.view.control_view import draw_int
|
|
44
|
-
from meltygui.view.control_view import draw_int_slider
|
|
45
|
-
from meltygui.view.control_view import draw_enum
|
|
46
|
-
from meltygui.view.dropdown_view import draw_dropdown
|
|
47
|
-
from meltygui.view.inspection_view import draw_view_func_selector
|
|
48
|
-
from meltygui.view.color_view import draw_color_picker
|
|
49
|
-
from meltygui.view.collection_view import draw_collection_as_tabs
|
|
50
|
-
from meltygui.view.layout_view import draw_columns
|
|
51
|
-
from meltygui.view.layout_view import draw_rows
|
|
52
42
|
from meltygui.view.menu_view import draw_menu_bar
|
|
53
|
-
from meltygui.view.file_view import draw_file_selector
|
|
54
|
-
from meltygui.view.file_view import draw_fast_file_explorer
|
|
55
|
-
from meltygui.view.file_view import draw_shortcuts
|
|
56
43
|
from meltygui.view.file_view import draw_breadcrumbs
|
|
57
|
-
from meltygui.core.files.file_core import draw_folder_files
|
|
58
|
-
from meltygui.view.terminal_view import draw_terminal
|
|
59
44
|
|
|
60
|
-
|
|
61
|
-
|
|
45
|
+
# Non-render helpers keep explicit locations; @render_func views are discovered.
|
|
46
|
+
_LAZY_EXPORTS = {
|
|
47
|
+
'columns': ('meltygui.core.rendering.gui_layout_prototype', 'columns'),
|
|
48
|
+
'rows': ('meltygui.core.rendering.gui_layout_prototype', 'rows'),
|
|
49
|
+
'gui': ('meltygui.core.rendering.gui_prototype', 'gui'),
|
|
50
|
+
'os_window': ('meltygui.core.rendering.gui_prototype', 'os_window'),
|
|
62
51
|
'DrawState': ('meltygui.state.new_core_model', 'DrawState'),
|
|
63
|
-
'draw_voxels': ('meltygui.view.voxel_view', 'draw_voxels'),
|
|
64
|
-
'draw_voxels_opengl': ('meltygui.view.voxel_view', 'draw_voxels_opengl'),
|
|
65
|
-
'draw_voxels_cuda': ('meltygui.view.voxel_view', 'draw_voxels_cuda'),
|
|
66
|
-
'draw_tensor_slices': ('meltygui.view.tensor_view', 'draw_tensor_slices'),
|
|
67
|
-
'draw_tensor_error': ('meltygui.view.tensor_view', 'draw_tensor_error'),
|
|
68
|
-
'draw_line_graph': ('meltygui.view.graph_view', 'draw_line_graph'),
|
|
69
52
|
'render_func': ('meltygui.core.core_render', 'render_func'),
|
|
70
53
|
'multi_instance': ('meltygui.model.tile_model', 'multi_instance'),
|
|
71
54
|
'draw_claude_chat': ('meltygui.view.chat_view', 'draw_claude_chat'),
|
|
72
|
-
'
|
|
73
|
-
'draw_texture': ('meltygui.view.texture_view', 'draw_texture'),
|
|
74
|
-
'draw_any': (_NCV, 'draw_any'),
|
|
75
|
-
'draw_button': ('meltygui.view.control_view', 'draw_button'),
|
|
76
|
-
'draw_str': ('meltygui.view.control_view', 'draw_str'),
|
|
77
|
-
'draw_float': ('meltygui.view.control_view', 'draw_float'),
|
|
78
|
-
'draw_int': ('meltygui.view.control_view', 'draw_int'),
|
|
79
|
-
'draw_int_slider': ('meltygui.view.control_view', 'draw_int_slider'),
|
|
80
|
-
'draw_enum': ('meltygui.view.control_view', 'draw_enum'),
|
|
81
|
-
'draw_dropdown': ('meltygui.view.dropdown_view', 'draw_dropdown'),
|
|
82
|
-
'draw_view_func_selector': ('meltygui.view.inspection_view', 'draw_view_func_selector'),
|
|
83
|
-
'draw_color_picker': ('meltygui.view.color_view', 'draw_color_picker'),
|
|
84
|
-
'draw_collection_as_tabs': ('meltygui.view.collection_view', 'draw_collection_as_tabs'),
|
|
85
|
-
'draw_columns': ('meltygui.view.layout_view', 'draw_columns'),
|
|
86
|
-
'draw_rows': ('meltygui.view.layout_view', 'draw_rows'),
|
|
55
|
+
'draw_any': ('meltygui.core.rendering.render_dispatch', 'draw_any'),
|
|
87
56
|
'draw_menu_bar': ('meltygui.view.menu_view', 'draw_menu_bar'),
|
|
88
|
-
'draw_file_selector': ('meltygui.view.file_view', 'draw_file_selector'),
|
|
89
|
-
'draw_fast_file_explorer': ('meltygui.view.file_view', 'draw_fast_file_explorer'),
|
|
90
|
-
'draw_shortcuts': ('meltygui.view.file_view', 'draw_shortcuts'),
|
|
91
57
|
'draw_breadcrumbs': ('meltygui.view.file_view', 'draw_breadcrumbs'),
|
|
92
|
-
'draw_folder_files': ('meltygui.core.files.file_core', 'draw_folder_files'),
|
|
93
|
-
'draw_terminal': ('meltygui.view.terminal_view', 'draw_terminal'),
|
|
94
58
|
}
|
|
95
59
|
|
|
96
60
|
|
|
@@ -104,7 +68,13 @@ def __getattr__(name):
|
|
|
104
68
|
if name == 'window_api':
|
|
105
69
|
import meltygui.core.windowing.window_api as window_api
|
|
106
70
|
return window_api
|
|
107
|
-
|
|
71
|
+
if name == '__all__':
|
|
72
|
+
value = list(dict.fromkeys([*_PUBLIC_NAMES, *_LAZY_EXPORTS, *_render_exports()]))
|
|
73
|
+
globals()[name] = value
|
|
74
|
+
return value
|
|
75
|
+
spec = _LAZY_EXPORTS.get(name)
|
|
76
|
+
if spec is None:
|
|
77
|
+
spec = _render_exports().get(name)
|
|
108
78
|
if spec is None:
|
|
109
79
|
raise AttributeError(name)
|
|
110
80
|
import importlib
|
|
@@ -115,6 +85,17 @@ def __getattr__(name):
|
|
|
115
85
|
return value
|
|
116
86
|
|
|
117
87
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
88
|
+
_PUBLIC_NAMES = [
|
|
89
|
+
'Style', 'boot', 'embedded_imports', 'glfw_window', 'run', 'pressed',
|
|
90
|
+
'content_size', 'mark', 'persisted', 'after_first_frame', 'checkpoint',
|
|
91
|
+
'toggles', 'window_api', 'imgui',
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _render_exports():
|
|
96
|
+
from meltygui.core.runtime.public_exports import render_exports
|
|
97
|
+
return render_exports()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def __dir__():
|
|
101
|
+
return sorted(set(globals()) | set(_PUBLIC_NAMES) | set(_LAZY_EXPORTS) | set(_render_exports()))
|
|
@@ -353,7 +353,7 @@ class AnthropicKind(AccountKind):
|
|
|
353
353
|
source = self._source(account)
|
|
354
354
|
if source is None:
|
|
355
355
|
return ("needs_login", "not signed in")
|
|
356
|
-
return ("ready", f"{source}
|
|
356
|
+
return ("ready", f"{source} verified" if account.get("_validated") else source)
|
|
357
357
|
|
|
358
358
|
# -- Claude plan usage (through Claude Code's login) ----------------------
|
|
359
359
|
# The plan's rate-limit windows (session, weekly all-models, weekly
|
|
@@ -662,7 +662,7 @@ class AnthropicKind(AccountKind):
|
|
|
662
662
|
same_day = fetched[:3] == time.localtime()[:3]
|
|
663
663
|
stamp = "as of " + time.strftime("%H:%M:%S" if same_day else "%m-%d %H:%M", fetched)
|
|
664
664
|
if account.get("_usage_loading"):
|
|
665
|
-
stamp += "
|
|
665
|
+
stamp += " refreshing…"
|
|
666
666
|
out.append(("stamp", stamp))
|
|
667
667
|
return out
|
|
668
668
|
|
|
@@ -690,13 +690,13 @@ class AnthropicKind(AccountKind):
|
|
|
690
690
|
client.models.list(limit=1)
|
|
691
691
|
client.close()
|
|
692
692
|
account["_validated"] = True
|
|
693
|
-
account["_status"] = ("ready", f"{source}
|
|
693
|
+
account["_status"] = ("ready", f"{source} verified")
|
|
694
694
|
except ImportError:
|
|
695
695
|
account["_status"] = ("error", "anthropic package not installed")
|
|
696
696
|
except Exception as error:
|
|
697
697
|
account["_validated"] = False
|
|
698
698
|
message = getattr(error, "message", None) or str(error)
|
|
699
|
-
account["_status"] = ("error", f"{source}
|
|
699
|
+
account["_status"] = ("error", f"{source} {message[:90]}")
|
|
700
700
|
accounts_changed()
|
|
701
701
|
|
|
702
702
|
# -- browser sign-in ---------------------------------------------------
|
|
@@ -880,7 +880,7 @@ class CodexKind(AccountKind):
|
|
|
880
880
|
return ("needs_login", "not signed in")
|
|
881
881
|
if identity.get("type") != "chatgpt":
|
|
882
882
|
return ("warning", "ChatGPT sign-in required for plan usage")
|
|
883
|
-
return ("ready", "
|
|
883
|
+
return ("ready", " ".join(filter(None, (
|
|
884
884
|
identity.get("email") or "ChatGPT", identity.get("planType")))))
|
|
885
885
|
|
|
886
886
|
def probe(self, account):
|
|
@@ -1025,7 +1025,7 @@ class CodexKind(AccountKind):
|
|
|
1025
1025
|
if fetched:
|
|
1026
1026
|
stamp = "as of " + time.strftime("%m-%d %H:%M:%S", time.localtime(fetched))
|
|
1027
1027
|
if account.get("_usage_loading"):
|
|
1028
|
-
stamp += "
|
|
1028
|
+
stamp += " refreshing…"
|
|
1029
1029
|
out.append(("stamp", stamp))
|
|
1030
1030
|
out.append(("note", "Shared with Codex desktop / CLI" if account["id"] == "codex"
|
|
1031
1031
|
else "Separate Codex account"))
|
|
@@ -1136,7 +1136,7 @@ class OllamaKind(AccountKind):
|
|
|
1136
1136
|
models = ollama.list_models(client)
|
|
1137
1137
|
except Exception as error:
|
|
1138
1138
|
account["_models"] = []
|
|
1139
|
-
return ("error", f"{host}
|
|
1139
|
+
return ("error", f"{host} {str(error)[:60]}")
|
|
1140
1140
|
account["_models"] = models
|
|
1141
1141
|
try:
|
|
1142
1142
|
account["_gpus"] = ollama.gpu_inventory() # best-effort GPU names for the device menu
|
|
@@ -1148,10 +1148,10 @@ class OllamaKind(AccountKind):
|
|
|
1148
1148
|
for key in ("coder", "codellama", "starcoder", "codestral", "deepseek-coder"))]
|
|
1149
1149
|
text = f"{len(models)} models"
|
|
1150
1150
|
if loaded:
|
|
1151
|
-
text += f"
|
|
1151
|
+
text += f" {len(loaded)} loaded on " + ", ".join(
|
|
1152
1152
|
sorted({model['where'] or '?' for model in loaded}))
|
|
1153
1153
|
if not fim_like:
|
|
1154
|
-
text += "
|
|
1154
|
+
text += " no FIM model (pull qwen2.5-coder)"
|
|
1155
1155
|
return ("ready", text)
|
|
1156
1156
|
|
|
1157
1157
|
def actions(self, account):
|
meltygui/chat/chat_interface.py
CHANGED
|
@@ -387,7 +387,7 @@ def source_initials(label):
|
|
|
387
387
|
def source_label(account_id, kind, accounts):
|
|
388
388
|
"""A tab's text: the provider, plus the account when the kind has several."""
|
|
389
389
|
if len(accounts.of_kind(kind.name)) > 1:
|
|
390
|
-
return f'{kind.chat_label}
|
|
390
|
+
return f'{kind.chat_label} {accounts[account_id]["label"]}'
|
|
391
391
|
return kind.chat_label
|
|
392
392
|
|
|
393
393
|
|
|
@@ -530,10 +530,10 @@ def _draw_image(ref, x, y, max_width, box_height, caption_height, tint, *, name=
|
|
|
530
530
|
rendered_size = (image_state.width, image_state.height)
|
|
531
531
|
width, height = rendered_size
|
|
532
532
|
elif entry is not None and entry.status == "failed":
|
|
533
|
-
caption = (caption + "
|
|
533
|
+
caption = (caption + " " if caption else "") + "could not decode: " + str(entry.error)
|
|
534
534
|
else:
|
|
535
535
|
draw_list.add_rect_filled(x, y, x + width, y + height, _color(tint, 0.12), rounding=Melty.px(6))
|
|
536
|
-
caption = (caption + "
|
|
536
|
+
caption = (caption + " " if caption else "") + "decoding…"
|
|
537
537
|
_title(caption or ref.label, x, y + height, max_width, caption_height, tint, brightness=0.7, ellipsis=True)
|
|
538
538
|
return rendered_size
|
|
539
539
|
|
|
@@ -560,10 +560,10 @@ def transcript_entries(messages, state, key):
|
|
|
560
560
|
if len(entries) == 1:
|
|
561
561
|
preview = _message_preview(entries[0][1])
|
|
562
562
|
if preview:
|
|
563
|
-
summary += "
|
|
563
|
+
summary += " " + preview
|
|
564
564
|
failures = sum(_message_failed(message) for _, message in entries)
|
|
565
565
|
if failures:
|
|
566
|
-
summary += f"
|
|
566
|
+
summary += f" {failures} failed"
|
|
567
567
|
group = Message("actionGroup", details={"label": summary})
|
|
568
568
|
yield identifier, group
|
|
569
569
|
if getattr(state, "action_groups", {}).get(key + ":" + identifier, False):
|
meltygui/chat/images.py
CHANGED
meltygui/chat/main.py
CHANGED
|
@@ -30,6 +30,7 @@ The service must remain alive; rebooting or killing the service stops its work.
|
|
|
30
30
|
import os
|
|
31
31
|
import pathlib
|
|
32
32
|
import socket
|
|
33
|
+
import sys
|
|
33
34
|
import threading
|
|
34
35
|
|
|
35
36
|
import meltygui
|
|
@@ -169,12 +170,16 @@ def main(argv=None):
|
|
|
169
170
|
parser.error(f'not a directory: {project}')
|
|
170
171
|
state = dict(new_project=str(project) if project else None,
|
|
171
172
|
default_project=str(pathlib.Path.cwd()))
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if
|
|
176
|
-
|
|
177
|
-
|
|
173
|
+
close_instance = None
|
|
174
|
+
# iOS owns the app lifecycle; the desktop activation socket under /tmp
|
|
175
|
+
# is outside the app sandbox and must not be created there.
|
|
176
|
+
if sys.platform != 'ios':
|
|
177
|
+
instance = os.environ.get('MELTY_CLAUDE_INSTANCE')
|
|
178
|
+
socket_path = (pathlib.Path(os.environ.get('XDG_RUNTIME_DIR') or '/tmp')
|
|
179
|
+
/ f"{APP_ID}-{os.getuid()}{('-' + instance) if instance else ''}.sock")
|
|
180
|
+
if hand_over_to_running_instance(socket_path):
|
|
181
|
+
return 0
|
|
182
|
+
close_instance = serve_instance(socket_path)
|
|
178
183
|
try:
|
|
179
184
|
# Window registration and runtime state belong to this invocation.
|
|
180
185
|
@glfw_window(name='Claude Code', width=1180, height=820, app_id=APP_ID,
|
|
@@ -196,7 +201,8 @@ def main(argv=None):
|
|
|
196
201
|
meltygui.run()
|
|
197
202
|
finally:
|
|
198
203
|
try:
|
|
199
|
-
close_instance
|
|
204
|
+
if close_instance is not None:
|
|
205
|
+
close_instance()
|
|
200
206
|
finally:
|
|
201
207
|
disconnect_chats()
|
|
202
208
|
return 0
|
|
@@ -1320,7 +1320,7 @@ def focus(input_value, path=(), default=None, kind=None, draw_state=None, unique
|
|
|
1320
1320
|
else:
|
|
1321
1321
|
target = None
|
|
1322
1322
|
base = kind or str(draw_state.name)
|
|
1323
|
-
label = f"{base}
|
|
1323
|
+
label = f"{base} {target}" if target else base
|
|
1324
1324
|
|
|
1325
1325
|
imgui.same_line()
|
|
1326
1326
|
imgui.text(label)
|
meltygui/code/codec_registry.py
CHANGED
|
@@ -17,7 +17,8 @@ def file_icon_for_path(path):
|
|
|
17
17
|
# File browsers also work before any editor has initialized the codecs.
|
|
18
18
|
from meltygui.code import new_codecs
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
from meltygui.model.file_location_model import file_path
|
|
21
|
+
codec = extension_to_codec.get(file_path(path).suffix.lower())
|
|
21
22
|
return codec.icon_for_path(path) if codec is not None else None
|
|
22
23
|
|
|
23
24
|
|
|
@@ -36,5 +37,6 @@ def file_badge_for_path(path):
|
|
|
36
37
|
from pathlib import Path
|
|
37
38
|
from meltygui.code import new_codecs
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
from meltygui.model.file_location_model import file_path
|
|
41
|
+
codec = extension_to_codec.get(file_path(path).suffix.lower())
|
|
40
42
|
return codec.badge_for_path(path) if codec is not None else None
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Jedi introspection inside a running app, without process-global path swaps.
|
|
2
|
+
|
|
3
|
+
Jedi's usual same-process helpers temporarily replace sys.path. Its analysis
|
|
4
|
+
worker then races imports on the render thread and application workers. Source
|
|
5
|
+
lookups can pass their search path directly; compiled modules must use the
|
|
6
|
+
embedded application's actual environment.
|
|
7
|
+
"""
|
|
8
|
+
import importlib
|
|
9
|
+
import sys
|
|
10
|
+
import warnings
|
|
11
|
+
|
|
12
|
+
from jedi.api.environment import InterpreterEnvironment
|
|
13
|
+
from jedi.inference.compiled import access
|
|
14
|
+
from jedi.inference.compiled.subprocess import InferenceStateSameProcess, functions
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class EmbeddedProcess(InferenceStateSameProcess):
|
|
18
|
+
def get_module_info(self, sys_path=None, full_name=None, **kwargs):
|
|
19
|
+
if kwargs.get('path') is None and sys_path is not None:
|
|
20
|
+
kwargs['path'] = sys_path
|
|
21
|
+
# Jedi's namespace-package result normally gets this from a global
|
|
22
|
+
# lookup. Passing an explicit root path must retain that name.
|
|
23
|
+
full_name = full_name or kwargs['string']
|
|
24
|
+
try:
|
|
25
|
+
return functions._find_module(full_name=full_name, **kwargs)
|
|
26
|
+
except ImportError:
|
|
27
|
+
return None, None
|
|
28
|
+
|
|
29
|
+
def load_module(self, dotted_name, sys_path):
|
|
30
|
+
# Project-specific Python sources are resolved statically above. Native
|
|
31
|
+
# introspection uses this interpreter's modules, never another venv's
|
|
32
|
+
# binaries or a temporary replacement of the application's sys.path.
|
|
33
|
+
try:
|
|
34
|
+
module = importlib.import_module(dotted_name)
|
|
35
|
+
except Exception:
|
|
36
|
+
warnings.warn(f'Module {dotted_name} is not importable in the embedded app.',
|
|
37
|
+
stacklevel=2)
|
|
38
|
+
return None
|
|
39
|
+
return access.create_access_path(self._inference_state_weakref(), module)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class EmbeddedEnvironment(InterpreterEnvironment):
|
|
43
|
+
def get_inference_state_subprocess(self, inference_state):
|
|
44
|
+
return EmbeddedProcess(inference_state)
|
|
45
|
+
|
|
46
|
+
def get_sys_path(self):
|
|
47
|
+
return list(sys.path)
|
meltygui/code/fileref.py
CHANGED
|
@@ -66,6 +66,9 @@ def add_editable_root(path) -> Path | None:
|
|
|
66
66
|
or the PROJECT ROOT of a file `path` (see project_root_of). Idempotent;
|
|
67
67
|
a library install (site-packages, a venv) is never registered. Returns
|
|
68
68
|
the root registered, or None when refused."""
|
|
69
|
+
from meltygui.model.file_location_model import is_remote
|
|
70
|
+
if is_remote(path):
|
|
71
|
+
return None
|
|
69
72
|
try:
|
|
70
73
|
p = Path(path).expanduser().resolve()
|
|
71
74
|
except (OSError, ValueError):
|
|
@@ -122,7 +125,7 @@ def is_editable_source(source_file) -> bool:
|
|
|
122
125
|
except (OSError, ValueError):
|
|
123
126
|
return False
|
|
124
127
|
ok = True
|
|
125
|
-
if {"site-packages", "dist-packages"} & set(p.parts):
|
|
128
|
+
if _ios_bundle_source(p) or {"site-packages", "dist-packages"} & set(p.parts):
|
|
126
129
|
ok = False
|
|
127
130
|
else:
|
|
128
131
|
ok = any(p.is_relative_to(root) for root in _EDITABLE_ROOTS)
|
|
@@ -136,6 +139,12 @@ _LIBRARY_PARTS = frozenset({"site-packages", "dist-packages", "venv", ".venv",
|
|
|
136
139
|
"node_modules"})
|
|
137
140
|
|
|
138
141
|
|
|
142
|
+
def _ios_bundle_source(path: Path) -> bool:
|
|
143
|
+
# Signed iOS bundles can report writable POSIX modes/access(), while the
|
|
144
|
+
# sandbox rejects writes. This includes app source registered as a project.
|
|
145
|
+
return sys.platform == "ios" and any(part.endswith(".app") for part in path.parts)
|
|
146
|
+
|
|
147
|
+
|
|
139
148
|
def writable_file_refusal(path) -> str | None:
|
|
140
149
|
"""Why the PLAIN-FILE codecs (TextFileCodec and friends) will not edit
|
|
141
150
|
`path` — a short human reason — or None when they will.
|
|
@@ -150,10 +159,16 @@ def writable_file_refusal(path) -> str | None:
|
|
|
150
159
|
gate exists to prevent. (It used to require $HOME instead of write
|
|
151
160
|
permission, and a refused file showed as a tab that never loaded.)
|
|
152
161
|
A path that does not exist yet is judged by its directory."""
|
|
162
|
+
from meltygui.model.file_location_model import file_path, is_remote
|
|
163
|
+
if is_remote(path):
|
|
164
|
+
hit = _LIBRARY_PARTS & set(file_path(path).parts)
|
|
165
|
+
return f'inside a library install ({sorted(hit)[0]}/)' if hit else None
|
|
153
166
|
try:
|
|
154
167
|
p = Path(path).resolve()
|
|
155
168
|
except (OSError, ValueError) as e:
|
|
156
169
|
return f"path cannot be resolved ({e})"
|
|
170
|
+
if _ios_bundle_source(p):
|
|
171
|
+
return "inside a read-only iOS app bundle"
|
|
157
172
|
hit = _LIBRARY_PARTS & set(p.parts)
|
|
158
173
|
if hit:
|
|
159
174
|
return f"inside a library install ({sorted(hit)[0]}/)"
|
|
@@ -198,7 +213,8 @@ class Address:
|
|
|
198
213
|
end = None
|
|
199
214
|
|
|
200
215
|
def __init__(self, path, start=None, end=None, source=None, watcher_ds=None):
|
|
201
|
-
|
|
216
|
+
from meltygui.model.file_location_model import file_path
|
|
217
|
+
self.path = file_path(path).resolve()
|
|
202
218
|
self.start = start
|
|
203
219
|
self.end = end
|
|
204
220
|
self.source = source
|
|
@@ -558,11 +558,12 @@ _xref_cache: dict[_Path, tuple[float, dict[str, list[UsageRef]]]] = {}
|
|
|
558
558
|
|
|
559
559
|
DISABLE_JEDI = False
|
|
560
560
|
|
|
561
|
-
# ── Jedi
|
|
562
|
-
#
|
|
563
|
-
#
|
|
561
|
+
# ── Jedi workers ──────────────────────────────────────────────
|
|
562
|
+
# Desktop analysis runs in child processes to avoid the render thread's GIL.
|
|
563
|
+
# iOS uses one thread in the embedded interpreter: processes are unavailable,
|
|
564
|
+
# and concurrent Jedi jobs must not race its shared parser/inference caches.
|
|
564
565
|
|
|
565
|
-
from concurrent.futures import
|
|
566
|
+
from concurrent.futures import Executor as _Executor
|
|
566
567
|
from concurrent.futures import Future as _Future
|
|
567
568
|
|
|
568
569
|
# Two pools, same forkserver context. "index" (4 workers) runs the heavy
|
|
@@ -572,7 +573,7 @@ from concurrent.futures import Future as _Future
|
|
|
572
573
|
# every interactive job lands on the SAME worker process, so parso's per-process
|
|
573
574
|
# parse cache stays warm for the file being edited (~10-30ms per completion vs
|
|
574
575
|
# ~1s re-parsing cold on whichever index worker happened to be idle).
|
|
575
|
-
_jedi_pools: dict[str,
|
|
576
|
+
_jedi_pools: dict[str, _Executor] = {}
|
|
576
577
|
_jedi_pool_lock = threading.Lock()
|
|
577
578
|
_jedi_mp_ctx = None
|
|
578
579
|
|
|
@@ -595,6 +596,8 @@ def _get_jedi_mp_ctx():
|
|
|
595
596
|
COW-share that ~0.4 GB base instead of each re-importing it. Built once and cached:
|
|
596
597
|
the server persists across restarts, so rebuilt pools fork from the same clean base."""
|
|
597
598
|
global _jedi_mp_ctx
|
|
599
|
+
if sys.platform == 'ios':
|
|
600
|
+
raise RuntimeError('iOS Jedi analysis uses the embedded interpreter, not multiprocessing')
|
|
598
601
|
if _jedi_mp_ctx is None:
|
|
599
602
|
import multiprocessing as _mp
|
|
600
603
|
if "forkserver" in _mp.get_all_start_methods():
|
|
@@ -611,7 +614,15 @@ def _get_jedi_mp_ctx():
|
|
|
611
614
|
return _jedi_mp_ctx
|
|
612
615
|
|
|
613
616
|
|
|
614
|
-
def _get_jedi_pool(kind: str = "index") ->
|
|
617
|
+
def _get_jedi_pool(kind: str = "index") -> _Executor:
|
|
618
|
+
if sys.platform == 'ios':
|
|
619
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
620
|
+
with _jedi_pool_lock:
|
|
621
|
+
pool = _jedi_pools.get('ios')
|
|
622
|
+
if pool is None or pool._shutdown:
|
|
623
|
+
pool = ThreadPoolExecutor(max_workers=1, thread_name_prefix='melty-jedi-ios')
|
|
624
|
+
_jedi_pools['ios'] = pool
|
|
625
|
+
return pool
|
|
615
626
|
# A studio restart-in-place ends the session, which fires concurrent.futures'
|
|
616
627
|
# atexit (_python_exit) even though THIS process keeps running. That sets the
|
|
617
628
|
# module-level _global_shutdown flag and kills the worker processes, so EVERY
|
|
@@ -619,14 +630,15 @@ def _get_jedi_pool(kind: str = "index") -> _PPE:
|
|
|
619
630
|
# silently disabling jedi + every off-GIL task. Clear that stale signal (the
|
|
620
631
|
# process is not actually exiting) and rebuild BOTH pools.
|
|
621
632
|
import concurrent.futures.process as _cfp
|
|
633
|
+
from concurrent.futures import ProcessPoolExecutor
|
|
622
634
|
with _jedi_pool_lock:
|
|
623
635
|
if getattr(_cfp, "_global_shutdown", False):
|
|
624
636
|
_cfp._global_shutdown = False
|
|
625
637
|
_jedi_pools.clear()
|
|
626
638
|
pool = _jedi_pools.get(kind)
|
|
627
639
|
if pool is None or getattr(pool, "_shutdown_thread", False):
|
|
628
|
-
pool =
|
|
629
|
-
|
|
640
|
+
pool = ProcessPoolExecutor(max_workers=1 if kind == "ac" else 4,
|
|
641
|
+
mp_context=_get_jedi_mp_ctx())
|
|
630
642
|
_jedi_pools[kind] = pool
|
|
631
643
|
return pool
|
|
632
644
|
|
|
@@ -647,6 +659,10 @@ def _submit_interactive(worker, *args) -> _Future:
|
|
|
647
659
|
forkserver — which preloads this module, >1s of imports — so pool get +
|
|
648
660
|
submit happen on a short-lived daemon thread and the pool future's result is
|
|
649
661
|
mirrored into the returned Future (same done()/add_done_callback contract)."""
|
|
662
|
+
if sys.platform == 'ios':
|
|
663
|
+
# Thread startup does not import/spawn a second interpreter. Returning
|
|
664
|
+
# its own Future also preserves cancellation of queued device work.
|
|
665
|
+
return _get_jedi_pool('ac').submit(worker, *args)
|
|
650
666
|
out = _Future()
|
|
651
667
|
|
|
652
668
|
def _bg():
|
|
@@ -679,7 +695,7 @@ def _jedi_project(file_path=None):
|
|
|
679
695
|
held = _jedi_projects.get(project.key)
|
|
680
696
|
if held is None:
|
|
681
697
|
held = _jedi_projects[project.key] = jedi.Project(
|
|
682
|
-
path=project.root, environment_path=project.environment,
|
|
698
|
+
path=project.root, environment_path=None if sys.platform == 'ios' else project.environment,
|
|
683
699
|
added_sys_path=list(project.source_paths))
|
|
684
700
|
return held
|
|
685
701
|
|
|
@@ -688,7 +704,15 @@ def _jedi_script(file_path, code=None):
|
|
|
688
704
|
"""jedi.Script on the file’s owning project. `code` (in-memory source) overrides
|
|
689
705
|
the on-disk file so unsaved edits are analyzed; path still drives resolution."""
|
|
690
706
|
import jedi
|
|
691
|
-
|
|
707
|
+
kwargs = {}
|
|
708
|
+
if sys.platform == 'ios':
|
|
709
|
+
# Threading alone is insufficient: Script's default Environment probes
|
|
710
|
+
# an executable and runs compiled-module introspection in a subprocess.
|
|
711
|
+
# Keep lookups inside bundled CPython without replacing sys.path while
|
|
712
|
+
# the app's rendering/decoder threads are importing their own modules.
|
|
713
|
+
from meltygui.code.embedded_jedi import EmbeddedEnvironment
|
|
714
|
+
kwargs['environment'] = EmbeddedEnvironment()
|
|
715
|
+
return jedi.Script(code=code, path=str(file_path), project=_jedi_project(file_path), **kwargs)
|
|
692
716
|
|
|
693
717
|
|
|
694
718
|
def shutdown_jedi_pool():
|
|
@@ -696,8 +720,8 @@ def shutdown_jedi_pool():
|
|
|
696
720
|
pools = list(_jedi_pools.values())
|
|
697
721
|
_jedi_pools.clear()
|
|
698
722
|
for pool in pools:
|
|
699
|
-
#
|
|
700
|
-
#
|
|
723
|
+
# Desktop workers can be killed. An iOS job already executing in a
|
|
724
|
+
# thread must finish; shutdown cancels its queued requests only.
|
|
701
725
|
for pid, proc in list(getattr(pool, '_processes', {}).items()):
|
|
702
726
|
try:
|
|
703
727
|
proc.kill()
|
|
@@ -809,7 +833,11 @@ def _completion_namespace():
|
|
|
809
833
|
if _completion_ns_cache is None:
|
|
810
834
|
import importlib
|
|
811
835
|
ns = {}
|
|
812
|
-
|
|
836
|
+
modules = _COMPLETION_MODULES
|
|
837
|
+
if sys.platform == 'ios':
|
|
838
|
+
modules = dict(modules, imgui='meltygui_imgui',
|
|
839
|
+
glfw='meltygui.core.windowing.window_api')
|
|
840
|
+
for alias, mod in modules.items():
|
|
813
841
|
try:
|
|
814
842
|
ns[alias] = importlib.import_module(mod)
|
|
815
843
|
except Exception:
|
|
@@ -827,8 +855,7 @@ def _jedi_complete_worker(code: str, line: int, col: int, path: str = None):
|
|
|
827
855
|
module/class/function/instance/param/keyword/statement/property/path)."""
|
|
828
856
|
import jedi
|
|
829
857
|
try:
|
|
830
|
-
|
|
831
|
-
script = (jedi.Script(code, **kw) if path else
|
|
858
|
+
script = (_jedi_script(path, code) if path else
|
|
832
859
|
jedi.Interpreter(code, [_completion_namespace()]))
|
|
833
860
|
comps = script.complete(line, col)
|
|
834
861
|
except Exception:
|
|
@@ -904,8 +931,7 @@ def _jedi_signatures_worker(code: str, line: int, col: int, path: str = None):
|
|
|
904
931
|
'x', 'y=0', '*args')."""
|
|
905
932
|
import jedi
|
|
906
933
|
try:
|
|
907
|
-
|
|
908
|
-
script = (jedi.Script(code, **kw) if path else
|
|
934
|
+
script = (_jedi_script(path, code) if path else
|
|
909
935
|
jedi.Interpreter(code, [_completion_namespace()]))
|
|
910
936
|
sigs = script.get_signatures(line, col)
|
|
911
937
|
except Exception:
|
|
@@ -3704,7 +3730,7 @@ _FRAME_STAMP_STALE_S = 2.0 # a frame stamp this old = frame aborted mid-draw
|
|
|
3704
3730
|
|
|
3705
3731
|
def _park_while_frame(max_park_s=_FRAME_PARK_MAX_S):
|
|
3706
3732
|
"""Sleep a background thread while the render thread is INSIDE a frame
|
|
3707
|
-
(Melty._frame_draw_start set
|
|
3733
|
+
(Melty._frame_draw_start set by the surface, restored in its finally).
|
|
3708
3734
|
|
|
3709
3735
|
Why: post_frame's capture pass is hundreds of ctypes GL calls, each
|
|
3710
3736
|
releasing and re-acquiring the GIL. Under a CPU-bound background
|
|
@@ -3725,9 +3751,13 @@ def _park_while_frame(max_park_s=_FRAME_PARK_MAX_S):
|
|
|
3725
3751
|
cur = threading.current_thread()
|
|
3726
3752
|
if cur is threading.main_thread():
|
|
3727
3753
|
return 0.0
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3754
|
+
from meltygui.core.windowing import glfw_utils
|
|
3755
|
+
render_id = glfw_utils._render_thread_id
|
|
3756
|
+
# Native Metal hosts have a render thread but no GL-state owner. Read an
|
|
3757
|
+
# existing GL module only for the studio's older dedicated render loop.
|
|
3758
|
+
gl_state = sys.modules.get("meltygui.core.graphics.gl_state")
|
|
3759
|
+
glt = getattr(gl_state, "_gl_thread", None)
|
|
3760
|
+
if cur.ident == render_id or cur is glt or (render_id is None and glt is None):
|
|
3731
3761
|
return 0.0
|
|
3732
3762
|
t0 = time.monotonic()
|
|
3733
3763
|
if t0 - fs >= _FRAME_STAMP_STALE_S:
|
|
@@ -3783,9 +3813,14 @@ def _yield_to_ui():
|
|
|
3783
3813
|
cur = threading.current_thread()
|
|
3784
3814
|
if cur is threading.main_thread():
|
|
3785
3815
|
return
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3816
|
+
# Native Metal has a registered render thread but no OpenGL thread. Use
|
|
3817
|
+
# the same ownership check as _park_while_frame so its workers also defer
|
|
3818
|
+
# between frames while a gesture or momentum animation remains active.
|
|
3819
|
+
from meltygui.core.windowing import glfw_utils
|
|
3820
|
+
render_id = glfw_utils._render_thread_id
|
|
3821
|
+
gl_state = sys.modules.get("meltygui.core.graphics.gl_state")
|
|
3822
|
+
glt = getattr(gl_state, "_gl_thread", None)
|
|
3823
|
+
if cur.ident == render_id or cur is glt or (render_id is None and glt is None):
|
|
3789
3824
|
return
|
|
3790
3825
|
_t0 = time.monotonic()
|
|
3791
3826
|
while Toggles.yield_to_ui:
|
|
@@ -9721,4 +9756,4 @@ def shutdown_symbol_index_daemon():
|
|
|
9721
9756
|
# try:
|
|
9722
9757
|
# SymbolIndexCache.rebuild()
|
|
9723
9758
|
# except Exception:
|
|
9724
|
-
# pass
|
|
9759
|
+
# pass
|