Managing thirdparty package: defer.

This commit is contained in:
2026-05-13 05:09:23 -04:00
parent 8d6c91d306
commit 9266add6a1
10 changed files with 388 additions and 112 deletions
+26 -34
View File
@@ -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
+79 -78
View File
@@ -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
+68
View File
@@ -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,
"<frozen ",
"<string>",
"<pytest ",
}
):
return self
if event != "line":
return self
exc = Source.executing(frame)
if not (stmt := next(iter(exc.statements), None)):
return self
if isinstance(stmt, (AsyncFunctionDef, FunctionDef)):
self.pending.append(exc)
return self
if not self.pending or frame.f_back is not self.pending[-1].frame.f_back:
return self
stmts = self.pending.pop().statements
node = cast(FunctionDef | AsyncFunctionDef, next(iter(stmts)))
fn = frame.f_locals[node.name]
if fn.__module__ in sys.stdlib_module_names:
return self
if fn.__code__.co_freevars:
raise FreeVarsError(fn)
if not (ast := RewriteDefer.transform(node)):
return self
locals = frame.f_locals.copy()
del locals[node.name]
exec(compile(ast, frame.f_code.co_filename, "exec"), frame.f_globals, locals)
frame.f_locals[node.name].__code__ = locals[node.name].__code__
return self
+1
View File
@@ -0,0 +1 @@
from .sugar import defer as defer
+32
View File
@@ -0,0 +1,32 @@
from contextlib import ExitStack
from types import FrameType
from typing import Any, Callable, Optional, ParamSpec, TypeVar
P = ParamSpec("P")
T = TypeVar("T")
class _Defer:
def __init__(self, tracefn: Optional[Callable]) -> 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
+23
View File
@@ -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)
+13
View File
@@ -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)))
+13
View File
@@ -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()
+67
View File
@@ -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,
"<frozen ",
"<string>",
"<pytest ",
}
):
return self
if event != "line":
return self
if not (stmt := next(iter(exc.statements), None)):
return self
if isinstance(stmt, (AsyncFunctionDef, FunctionDef)):
if stmt.name.startswith("__") and stmt.name.endswith("__"):
return self
self.pending.append(exc)
return self
return self
stmts = self.pending.pop().statements
node = cast(FunctionDef | AsyncFunctionDef, next(iter(stmts)))
fn = frame.f_locals[node.name]
if fn.__module__ in sys.stdlib_module_names:
return self
if fn.__code__.co_freevars:
raise FreeVarsError(fn)
if not (ast := RewriteDefer.transform(node)):
return self
locals = frame.f_locals.copy()
del locals[node.name]
exec(compile(ast, frame.f_code.co_filename, "exec"), frame.f_globals, locals)
frame.f_locals[node.name].__code__ = locals[node.name].__code__
return self
+66
View File
@@ -0,0 +1,66 @@
from ast import (
AST,
AsyncFunctionDef,
Call,
Compare,
FunctionDef,
In,
Lambda,
Load,
Module,
Name,
NodeTransformer,
arg,
arguments,
copy_location,
fix_missing_locations,
)
from ast import (
walk as ast_walk,
)
from typing import Any, Optional
class RewriteDefer(NodeTransformer):
def __init__(self, root: AST) -> 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