From 9266add6a17c231bff45844722f0b84419d443ab Mon Sep 17 00:00:00 2001 From: Ed_ Date: Wed, 13 May 2026 05:09:23 -0400 Subject: [PATCH] Managing thirdparty package: defer. --- src/gui_2.py | 60 +++++----- src/imgui_scopes.py | 157 +++++++++++++------------- src/vendor/defer/sugar/_parse.py | 68 +++++++++++ thirdparty/defer/__init__.py | 1 + thirdparty/defer/_defer.py | 32 ++++++ thirdparty/defer/defer.py | 23 ++++ thirdparty/defer/errors.py | 13 +++ thirdparty/defer/sugar/__init__.py | 13 +++ thirdparty/defer/sugar/_parse.py | 67 +++++++++++ thirdparty/defer/sugar/transformer.py | 66 +++++++++++ 10 files changed, 388 insertions(+), 112 deletions(-) create mode 100644 src/vendor/defer/sugar/_parse.py create mode 100644 thirdparty/defer/__init__.py create mode 100644 thirdparty/defer/_defer.py create mode 100644 thirdparty/defer/defer.py create mode 100644 thirdparty/defer/errors.py create mode 100644 thirdparty/defer/sugar/__init__.py create mode 100644 thirdparty/defer/sugar/_parse.py create mode 100644 thirdparty/defer/sugar/transformer.py diff --git a/src/gui_2.py b/src/gui_2.py index 1492e0a..912f52a 100644 --- a/src/gui_2.py +++ b/src/gui_2.py @@ -13,7 +13,7 @@ import subprocess import sys import threading import time -# from defer import defer +from defer import defer import tomli_w # from contextlib import ExitStack, nullcontext import traceback @@ -657,16 +657,15 @@ class App: """ [C: tests/test_gui_window_controls.py:test_gui_window_controls_minimize_maximize_close] """ - if imgui.begin_menu("manual slop"): - if imgui.menu_item("Quit", "Ctrl+Q", False)[0]: + with imscope.menu("manual slop") as (active): + if active and imgui.menu_item("Quit", "Ctrl+Q", False)[0]: self.runner_params.app_shall_exit = True - imgui.end_menu() - if imgui.begin_menu("Windows"): - for w in self.show_windows.keys(): - _, self.show_windows[w] = imgui.menu_item(w, "", self.show_windows[w]) - imgui.end_menu() - if imgui.begin_menu("Project"): - if imgui.menu_item("Save All", "", False)[0]: + with imscope.menu("Windows") as (active): + if (active): + for w in self.show_windows.keys(): + _, self.show_windows[w] = imgui.menu_item(w, "", self.show_windows[w]) + with imscope.menu("Project") as (active): + if active and imgui.menu_item("Save All", "", False)[0]: self._flush_to_project() self._flush_to_config() models.save_config(self.config) @@ -686,9 +685,8 @@ class App: self.ai_status = f"md written: {path.name}" except Exception as e: self.ai_status = f"error: {e}" - imgui.end_menu() - if imgui.begin_menu("Layout"): - if imgui.menu_item("Save Current...", "", False)[0]: + with imscope.menu("Layout") as (active): + if active and imgui.menu_item("Save Current...", "", False)[0]: self._show_save_workspace_profile_modal = True self._new_workspace_profile_name = "" imgui.separator() @@ -696,37 +694,32 @@ class App: if imgui.menu_item(profile.name, "", False)[0]: self.controller._cb_load_workspace_profile(profile_id) imgui.separator() - if imgui.begin_menu("Delete Profile"): - for profile_id, profile in self.workspace_profiles.items(): - if imgui.menu_item(profile.name, "", False)[0]: - self.controller._cb_delete_workspace_profile(profile_id, self._new_workspace_profile_scope) - imgui.end_menu() - imgui.end_menu() + with imscope.menu("Delete Profile") as (active): + if active: + for profile_id, profile in self.workspace_profiles.items(): + if imgui.menu_item(profile.name, "", False)[0]: + self.controller._cb_delete_workspace_profile(profile_id, self._new_workspace_profile_scope) # RAG status indicator if self.controller.rag_config and self.controller.rag_config.enabled: imgui.same_line() status = self.controller.rag_status - if status == "indexing...": - color = vec4(100, 255, 100) - elif status == "error": - color = vec4(255, 100, 100) - else: - color = vec4(180, 180, 180) + if status == "indexing...": color = vec4(100, 255, 100) + elif status == "error": color = vec4(255, 100, 100) + else: color = vec4(180, 180, 180) + imgui.text_colored(color, f"[RAG: {status}]") - if imgui.is_item_hovered(): - imgui.set_tooltip(f"RAG is enabled. Status: {status}. Click to rebuild index.") - if imgui.is_item_clicked(): - self.controller.event_queue.put('click', 'btn_rebuild_rag_index') + if imgui.is_item_hovered(): imgui.set_tooltip(f"RAG is enabled. Status: {status}. Click to rebuild index.") + if imgui.is_item_clicked(): self.controller.event_queue.put('click', 'btn_rebuild_rag_index') # Draw right-aligned window controls directly in the menu bar (Win32 only) if sys.platform == "win32": try: import ctypes - ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p + ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p ctypes.pythonapi.PyCapsule_GetPointer.argtypes = [ctypes.py_object, ctypes.c_char_p] hwnd_capsule = imgui.get_main_viewport().platform_handle_raw - hwnd = ctypes.pythonapi.PyCapsule_GetPointer(hwnd_capsule, b"nb_handle") + hwnd = ctypes.pythonapi.PyCapsule_GetPointer(hwnd_capsule, b"nb_handle") except Exception: hwnd = 0 @@ -734,9 +727,8 @@ class App: btn_w = 40 # Use window width (points) instead of display_size (pixels) for correct scaling window_w = imgui.get_window_width() - bar_h = imgui.get_window_height() - right_x = window_w - (btn_w * 3) - + bar_h = imgui.get_window_height() + right_x = window_w - (btn_w * 3) # Drag area check using an explicit invisible button spanning the empty space curr_x = imgui.get_cursor_pos_x() drag_w = right_x - curr_x diff --git a/src/imgui_scopes.py b/src/imgui_scopes.py index f46194d..48bac03 100644 --- a/src/imgui_scopes.py +++ b/src/imgui_scopes.py @@ -2,20 +2,6 @@ from __future__ import annotations from imgui_bundle import imgui from imgui_bundle import imgui_node_editor -def window(name: str, visible: bool = True, flags: int = 0): return _ScopeWindow(name, visible, flags) -class _ScopeWindow: - def __init__(self, name: str, visible: bool, flags: int): - self._name = name - self._visible = visible - self._flags = flags - self._result = None - def __enter__(self): - self._result = imgui.begin(self._name, self._visible, self._flags) - return self._result - def __exit__(self, *args): - imgui.end() - return False - def child(id_str: str, size_x: float = 0, size_y: float = 0, flags: int = 0): return _ScopeChild(id_str, size_x, size_y, flags) class _ScopeChild: def __init__(self, id_str: str, size_x: float | imgui.ImVec2, size_y: float, flags: int): @@ -33,19 +19,35 @@ class _ScopeChild: imgui.end_child() return False -def table(name: str, columns: int, flags: int = 0): return _ScopeTable(name, columns, flags) -class _ScopeTable: - def __init__(self, name: str, columns: int, flags: int): - self._name = name - self._columns = columns - self._flags = flags +def group(): return _ScopeGroup() +class _ScopeGroup: + def __enter__(self): + return imgui.begin_group() + def __exit__(self, *args): + imgui.end_group() + return False + +def id(str_id: str): return _ScopeId(str_id) +class _ScopeId: + def __init__(self, str_id: str): + self._id = str_id + def __enter__(self): + imgui.push_id(self._id) + def __exit__(self, *args): + imgui.pop_id() + return False + +def menu(label: str): return _ScopeMenu(label) +class _ScopeMenu: + def __init__(self, label: str): + self._label = label self._active = False def __enter__(self): - self._active = imgui.begin_table(self._name, self._columns, self._flags) + self._active = imgui.begin_menu(self._label) return self._active def __exit__(self, *args): if self._active: - imgui.end_table() + imgui.end_menu() return False def menu_bar(): return _ScopeMenuBar() @@ -60,17 +62,14 @@ class _ScopeMenuBar: imgui.end_menu_bar() return False -def menu(label: str): return _ScopeMenu(label) -class _ScopeMenu: - def __init__(self, label: str): - self._label = label - self._active = False +def node(name: str): return _ScopeNode(name) +class _ScopeNode: + def __init__(self, name: str): + self._name = name def __enter__(self): - self._active = imgui.begin_menu(self._label) - return self._active + return imgui_node_editor.begin(self._name) def __exit__(self, *args): - if self._active: - imgui.end_menu() + imgui_node_editor.end() return False def popup(id_str: str): return _ScopePopup(id_str) @@ -101,50 +100,42 @@ class _ScopePopupModal: imgui.end_popup() return False -def tooltip(): return _ScopeTooltip() -class _ScopeTooltip: + +def style_color(col: int, val: Any): return _ScopeStyleColor(col, val) +class _ScopeStyleColor: + def __init__(self, col: int, val: Any): + self._col = col + self._val = val def __enter__(self): - return imgui.begin_tooltip() + imgui.push_style_color(self._col, self._val) def __exit__(self, *args): - imgui.end_tooltip() + imgui.pop_style_color() return False -def group(): return _ScopeGroup() -class _ScopeGroup: +def style_var(var: int, val: Any): return _ScopeStyleVar(var, val) +class _ScopeStyleVar: + def __init__(self, var: int, val: Any): + self._var = var + self._val = val def __enter__(self): - return imgui.begin_group() + imgui.push_style_var(self._var, self._val) def __exit__(self, *args): - imgui.end_group() + imgui.pop_style_var() return False -def node(name: str): return _ScopeNode(name) -class _ScopeNode: - def __init__(self, name: str): +def table(name: str, columns: int, flags: int = 0): return _ScopeTable(name, columns, flags) +class _ScopeTable: + def __init__(self, name: str, columns: int, flags: int): self._name = name + self._columns = columns + self._flags = flags + self._active = False def __enter__(self): - return imgui_node_editor.begin(self._name) + self._active = imgui.begin_table(self._name, self._columns, self._flags) + return self._active def __exit__(self, *args): - imgui_node_editor.end() - return False - -def text_wrap(wrap_pos: float = 0.0): return _ScopeTextWrap(wrap_pos) -class _ScopeTextWrap: - def __init__(self, wrap_pos: float): - self._wrap_pos = wrap_pos - def __enter__(self): - imgui.push_text_wrap_pos(self._wrap_pos) - def __exit__(self, *args): - imgui.pop_text_wrap_pos() - return False - -def id(str_id: str): return _ScopeId(str_id) -class _ScopeId: - def __init__(self, str_id: str): - self._id = str_id - def __enter__(self): - imgui.push_id(self._id) - def __exit__(self, *args): - imgui.pop_id() + if self._active: + imgui.end_table() return False def tab_bar(id_str: str, flags: int = 0): return _ScopeTabBar(id_str, flags) @@ -176,26 +167,22 @@ class _ScopeTabItem: imgui.end_tab_item() return False -def style_color(col: int, val: Any): return _ScopeStyleColor(col, val) -class _ScopeStyleColor: - def __init__(self, col: int, val: Any): - self._col = col - self._val = val +def text_wrap(wrap_pos: float = 0.0): return _ScopeTextWrap(wrap_pos) +class _ScopeTextWrap: + def __init__(self, wrap_pos: float): + self._wrap_pos = wrap_pos def __enter__(self): - imgui.push_style_color(self._col, self._val) + imgui.push_text_wrap_pos(self._wrap_pos) def __exit__(self, *args): - imgui.pop_style_color() + imgui.pop_text_wrap_pos() return False -def style_var(var: int, val: Any): return _ScopeStyleVar(var, val) -class _ScopeStyleVar: - def __init__(self, var: int, val: Any): - self._var = var - self._val = val +def tooltip(): return _ScopeTooltip() +class _ScopeTooltip: def __enter__(self): - imgui.push_style_var(self._var, self._val) + return imgui.begin_tooltip() def __exit__(self, *args): - imgui.pop_style_var() + imgui.end_tooltip() return False def tree_node_ex(label: str, flags: int = 0): return _ScopeTreeNodeEx(label, flags) @@ -211,3 +198,17 @@ class _ScopeTreeNodeEx: if self._opened: imgui.tree_pop() return False + +def window(name: str, visible: bool = True, flags: int = 0): return _ScopeWindow(name, visible, flags) +class _ScopeWindow: + def __init__(self, name: str, visible: bool, flags: int): + self._name = name + self._visible = visible + self._flags = flags + self._result = None + def __enter__(self): + self._result = imgui.begin(self._name, self._visible, self._flags) + return self._result + def __exit__(self, *args): + imgui.end() + return False diff --git a/src/vendor/defer/sugar/_parse.py b/src/vendor/defer/sugar/_parse.py new file mode 100644 index 0000000..5edad3c --- /dev/null +++ b/src/vendor/defer/sugar/_parse.py @@ -0,0 +1,68 @@ +import sys +from ast import ( + AsyncFunctionDef, + FunctionDef, +) +from collections import deque +from types import FrameType +from typing import Any, Callable, Optional, cast + +from executing.executing import Executing, Source + +from defer.errors import FreeVarsError +from defer.sugar.transformer import RewriteDefer + + +class _ParseDefer: + IDENTITY = lambda *_: None # noqa: E731 + + def __init__(self, tracefn: Optional[Callable]) -> None: + self.tracefn = tracefn or self.IDENTITY + self.pending: deque[Executing] = deque() + + def __call__(self, frame: FrameType, event: str, arg: Any): + self.tracefn(frame, event, arg) + + if any( + frame.f_code.co_filename.startswith(path) + for path in { + sys.base_exec_prefix, + sys.base_prefix, + sys.exec_prefix, + sys.prefix, + "", + " None: + self.tracefn = tracefn or (lambda *_: None) + + self._stack = ExitStack() + self._stack.__enter__() + + def push(self, fn: Callable[P, T], *args: P.args, **kwargs: P.kwargs): + self._stack.callback(fn, *args, **kwargs) + + def __call__(self, frame: FrameType, event: str, arg: Any): + self.tracefn(frame, event, arg) + + match event: + case "call": + self._stack.__enter__() + case "return": + self._stack.__exit__(None, None, None) + self._stack = ExitStack() + case "exception": + self._stack.__exit__(*arg) + self._stack = ExitStack() + return self diff --git a/thirdparty/defer/defer.py b/thirdparty/defer/defer.py new file mode 100644 index 0000000..44b8eb3 --- /dev/null +++ b/thirdparty/defer/defer.py @@ -0,0 +1,23 @@ +import sys +from typing import Any, Callable, ParamSpec, TypeVar + +from defer._defer import _Defer +from defer.sugar._parse import _ParseDefer + +P = ParamSpec("P") +T = TypeVar("T") + + +class Defer: + def __call__(self, fn: Callable[P, T], *args: P.args, **kwargs: P.kwargs): + if sys.gettrace() is None: + sys.settrace(_ParseDefer.IDENTITY) + frame = sys._getframe(1) + if not isinstance(frame.f_trace, _Defer): + frame.f_trace = _Defer(frame.f_trace) + frame.f_trace.push(fn, *args, **kwargs) + frame.f_trace_lines = False + + def __contains__(self, fn: Any): + breakpoint() + self(fn) diff --git a/thirdparty/defer/errors.py b/thirdparty/defer/errors.py new file mode 100644 index 0000000..fcfecba --- /dev/null +++ b/thirdparty/defer/errors.py @@ -0,0 +1,13 @@ +from types import FunctionType + + +class DeferErrror(RuntimeError): + pass + + +class FreeVarsError(DeferErrror): + def __init__(self, fn: FunctionType) -> None: + super().__init__( + "deferred function must not have free variables", + ) + self.add_note("free vars: " + str(list(fn.__code__.co_freevars))) diff --git a/thirdparty/defer/sugar/__init__.py b/thirdparty/defer/sugar/__init__.py new file mode 100644 index 0000000..cec03f7 --- /dev/null +++ b/thirdparty/defer/sugar/__init__.py @@ -0,0 +1,13 @@ +import sys + +from defer.defer import Defer +from defer.sugar._parse import _ParseDefer + + +def install(): + if not isinstance(sys.gettrace(), _ParseDefer): + sys.settrace(_ParseDefer(sys.gettrace())) + + +defer = Defer() +install() diff --git a/thirdparty/defer/sugar/_parse.py b/thirdparty/defer/sugar/_parse.py new file mode 100644 index 0000000..254ae5e --- /dev/null +++ b/thirdparty/defer/sugar/_parse.py @@ -0,0 +1,67 @@ +import sys +from ast import ( + AsyncFunctionDef, + FunctionDef, +) +from collections import deque +from types import FrameType +from typing import Any, Callable, Optional, cast + +from executing.executing import Executing, Source + +from defer.errors import FreeVarsError +from defer.sugar.transformer import RewriteDefer + + +class _ParseDefer: + IDENTITY = lambda *_: None # noqa: E731 + + def __init__(self, tracefn: Optional[Callable]) -> None: + self.tracefn = tracefn or self.IDENTITY + self.pending: deque[Executing] = deque() + + def __call__(self, frame: FrameType, event: str, arg: Any): + self.tracefn(frame, event, arg) + + if any( + frame.f_code.co_filename.startswith(path) + for path in { + sys.base_exec_prefix, + sys.base_prefix, + sys.exec_prefix, + sys.prefix, + "", + " None: + super().__init__() + self._dirty = False + self._root = root + + @classmethod + def transform(cls, node: FunctionDef | AsyncFunctionDef) -> Optional[Module]: + instance = cls(node) + node = instance.visit(node) + if not instance._dirty: + return None + return fix_missing_locations(Module(body=[node], type_ignores=[])) + + def visit_Compare(self, node: Compare): + match node: + case Compare(ops=[In()], comparators=[Name(id="defer", ctx=Load())]): + names = [n for n in ast_walk(node.left) if isinstance(n, Name)] + fn = Lambda( + args=arguments( + args=[arg(arg=n.id, annotation=None) for n in names], + kwonlyargs=[], + kw_defaults=[], + defaults=[], + posonlyargs=[], + ), + body=node.left, + ) + call = Call( + func=Name(id="defer", ctx=Load()), args=[fn, *names], keywords=[] + ) + copy_location(call, node) + self._dirty = True + return call + case _: + return node + + def visit_FunctionDef(self, node: FunctionDef | AsyncFunctionDef) -> Any: + if node is self._root: + return self.generic_visit(node) + return node + + visit_AsyncFunctionDef = visit_FunctionDef