curation pass on gui_2.py
This commit is contained in:
+136
-236
@@ -22,7 +22,7 @@ _thirdparty = os.path.join(_project_root, "thirdparty")
|
||||
if _thirdparty not in sys.path:
|
||||
sys.path.insert(0, _thirdparty)
|
||||
|
||||
from contextlib import ExitStack, nullcontext
|
||||
from contextlib import ExitStack, nullcontext
|
||||
from pathlib import Path
|
||||
from typing import Optional, Any
|
||||
from imgui_bundle import imgui, hello_imgui, immapp, imgui_node_editor as ed, imgui_color_text_edit as ced
|
||||
@@ -41,6 +41,7 @@ import importlib as _importlib
|
||||
from typing import Any as _Any
|
||||
from typing import Optional as _Optional
|
||||
|
||||
#TODO(Ed): Remove Excpetion based errors
|
||||
class _LazyModule:
|
||||
"""Lazy proxy that defers an import until first attribute access or call.
|
||||
|
||||
@@ -89,11 +90,10 @@ class _FiledialogStub:
|
||||
def askdirectory(self, *args: _Any, **kwargs: _Any) -> str: return ""
|
||||
def asksaveasfilename(self, *args: _Any, **kwargs: _Any) -> str: return ""
|
||||
|
||||
|
||||
# Heavy modules that were previously top-level imports (now lazy):
|
||||
np = _LazyModule("numpy") # was: import numpy as np
|
||||
np = _LazyModule("numpy") # was: import numpy as np
|
||||
filedialog = _LazyModule("tkinter", "filedialog") # was: from tkinter import filedialog
|
||||
Tk = _LazyModule("tkinter", "Tk") # was: from tkinter import Tk
|
||||
Tk = _LazyModule("tkinter", "Tk") # was: from tkinter import Tk
|
||||
|
||||
from src.diff_viewer import apply_patch_to_file
|
||||
from src import ai_client
|
||||
@@ -186,6 +186,7 @@ def _detect_refresh_rate_win32() -> float:
|
||||
shelled out to PowerShell + WMI (Get-CimInstance Win32_VideoController), which
|
||||
cost ~350ms on every startup and blocked the first frame.
|
||||
"""
|
||||
#Note(Ed): Exception(Thirdparty)
|
||||
try:
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
@@ -233,6 +234,7 @@ def _resolve_font_path(font_path: str, assets_dir: Path) -> str:
|
||||
p = Path(font_path)
|
||||
if not p.is_absolute():
|
||||
return font_path # already relative; hello_imgui searches the assets folder
|
||||
#Note(Ed): Exception(Thirdparty)
|
||||
try:
|
||||
if p.is_relative_to(assets_dir):
|
||||
return str(p.relative_to(assets_dir)).replace("\\", "/")
|
||||
@@ -285,7 +287,7 @@ def _render_v2_capability_badges(caps: "VendorCapabilities") -> None:
|
||||
]
|
||||
enabled: list[tuple[str, str]] = []
|
||||
for field_name, label in badged_fields:
|
||||
if getattr(caps, field_name, False):
|
||||
if getattr(caps, field_name, False):
|
||||
enabled.append((field_name, label))
|
||||
if not enabled: return
|
||||
imgui.text("Capabilities")
|
||||
@@ -297,16 +299,9 @@ class App:
|
||||
"""The main ImGui interface orchestrator for Manual Slop."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""
|
||||
Initializes core app dependencies (controller, history, performance monitor,
|
||||
"""Initializes core app dependencies (controller, history, performance monitor,
|
||||
command palette, workspace manager) and registers app callback handlers.
|
||||
|
||||
State Mutations:
|
||||
self.controller, self.perf_monitor, self.history,
|
||||
self.show_command_palette, self.workspace_manager.
|
||||
|
||||
SSDL Shape:
|
||||
`[I:init_controller] -> [I:init_workspace] -> [I:load_profiles]`
|
||||
SSDL Shape: `[I:init_controller] -> [I:init_workspace] -> [I:load_profiles]`
|
||||
"""
|
||||
#region: --- Core Dependencies & State ---
|
||||
from src.startup_profiler import startup_profiler
|
||||
@@ -505,22 +500,22 @@ class App:
|
||||
self.node_editor_ctx = ed.create_editor(self.node_editor_config)
|
||||
|
||||
# --- Context & AST State ---
|
||||
self.ui_selected_ticket_id: Optional[str] = None
|
||||
self.ui_selected_tickets: set[str] = set()
|
||||
self.ui_selected_context_files: set[str] = set()
|
||||
self.ui_new_ticket_priority: str = 'medium'
|
||||
self._autofocus_response_tab = False
|
||||
self.ui_selected_ticket_id: Optional[str] = None
|
||||
self.ui_selected_tickets: set[str] = set()
|
||||
self.ui_selected_context_files: set[str] = set()
|
||||
self.ui_new_ticket_priority: str = 'medium'
|
||||
self._autofocus_response_tab = False
|
||||
self._last_selected_context_index = -1
|
||||
self.ui_inspecting_ast_file = None
|
||||
self._show_ast_inspector = False
|
||||
self._cached_ast_nodes = []
|
||||
self._cached_ast_file_path = ''
|
||||
self._cached_ast_file_lines = []
|
||||
self.ui_editing_slices_file = None
|
||||
self._slice_sel_start = -1
|
||||
self._slice_sel_end = -1
|
||||
self.context_files = []
|
||||
self.ui_synthesis_prompt: str = ""
|
||||
self.ui_inspecting_ast_file = None
|
||||
self._show_ast_inspector = False
|
||||
self._cached_ast_nodes = []
|
||||
self._cached_ast_file_path = ''
|
||||
self._cached_ast_file_lines = []
|
||||
self.ui_editing_slices_file = None
|
||||
self._slice_sel_start = -1
|
||||
self._slice_sel_end = -1
|
||||
self.context_files = []
|
||||
self.ui_synthesis_prompt: str = ""
|
||||
self.ui_synthesis_selected_takes: dict[str, bool] = {}
|
||||
|
||||
# --- Rendering & Theme State ---
|
||||
@@ -564,18 +559,17 @@ class App:
|
||||
# is safe. The render_warmup_status_indicator() function reads
|
||||
# the timestamp to show a brief "ready" tag for 3 seconds.
|
||||
if hasattr(self.controller, "on_warmup_complete"):
|
||||
#Note(Ed): Exception(Thirdparty)
|
||||
try:
|
||||
self.controller.on_warmup_complete(lambda status: _on_warmup_complete_callback(self, status))
|
||||
except Exception: pass
|
||||
self._diag_layout_state()
|
||||
|
||||
def _diag_layout_state(self) -> None:
|
||||
"""
|
||||
One-shot startup diagnostic: log show_windows state and warn if the
|
||||
"""One-shot startup diagnostic: log show_windows state and warn if the
|
||||
on-disk manualslop_layout.ini references window names that no longer
|
||||
exist in the current code. Helps users and test operators detect
|
||||
stale layout state at a glance instead of debugging missing panels.
|
||||
[C: src/gui_2.py:App._post_init]
|
||||
"""
|
||||
import os as _os
|
||||
visible_by_default = [w for w, v in self.show_windows.items() if v]
|
||||
@@ -588,6 +582,7 @@ class App:
|
||||
return
|
||||
ini_size = _os.path.getsize(ini_path)
|
||||
sys.stderr.write(f"[GUI] layout file: {ini_path} ({ini_size} bytes)\n")
|
||||
#Note(Ed): Exception(Thirdparty)
|
||||
try:
|
||||
with open(ini_path, encoding="utf-8") as _f:
|
||||
_ini_text = _f.read()
|
||||
@@ -611,14 +606,9 @@ class App:
|
||||
return result
|
||||
|
||||
def run(self) -> None:
|
||||
"""
|
||||
Initializes the ImGui runner (HelloImGui) and starts the main application loop.
|
||||
"""Initializes the ImGui runner (HelloImGui) and starts the main application loop.
|
||||
Loads system themes, default styling metrics, fonts, and sets up window docking layouts.
|
||||
|
||||
SSDL Shape:
|
||||
`[I:hello_imgui] -> o-> [I:main_loop]`
|
||||
|
||||
[C: simulation/sim_base.py:run_sim, src/mcp_client.py:get_git_diff, src/project_manager.py:get_git_commit, src/rag_engine.py:RAGEngine._search_mcp, src/shell_runner.py:run_powershell, tests/conftest.py:kill_process_tree, tests/conftest.py:live_gui, tests/test_conductor_abort_event.py:test_conductor_abort_event_populated, tests/test_conductor_engine_v2.py:test_conductor_engine_dynamic_parsing_and_execution, tests/test_conductor_engine_v2.py:test_conductor_engine_run_executes_tickets_in_order, tests/test_extended_sims.py:test_ai_settings_sim_live, tests/test_extended_sims.py:test_context_sim_live, tests/test_extended_sims.py:test_execution_sim_live, tests/test_extended_sims.py:test_tools_sim_live, tests/test_external_editor_gui.py:get_vscode_processes, tests/test_external_editor_gui.py:test_vscode_launches_with_diff_view, tests/test_gui_custom_window.py:test_app_window_is_borderless, tests/test_headless_simulation.py:module, tests/test_headless_verification.py:test_headless_verification_error_and_qa_interceptor, tests/test_headless_verification.py:test_headless_verification_full_run, tests/test_mock_gemini_cli.py:run_mock, tests/test_orchestration_logic.py:test_conductor_engine_run, tests/test_parallel_execution.py:test_conductor_engine_pool_integration, tests/test_sim_ai_settings.py:test_ai_settings_simulation_run, tests/test_sim_context.py:test_context_simulation_run, tests/test_sim_execution.py:test_execution_simulation_run, tests/test_sim_tools.py:test_tools_simulation_run]
|
||||
SSDL: `[I:hello_imgui] -> o-> [I:main_loop]`
|
||||
"""
|
||||
if "--headless" in sys.argv:
|
||||
print("Headless mode active")
|
||||
@@ -642,15 +632,15 @@ class App:
|
||||
# (removed stale _t-based print; the phase() above already logs RunnerParams_init)
|
||||
|
||||
if sys.platform == "win32":
|
||||
self.runner_params.app_window_params.borderless = True
|
||||
self.runner_params.app_window_params.borderless_closable = False
|
||||
self.runner_params.app_window_params.borderless_movable = False
|
||||
self.runner_params.app_window_params.borderless = True
|
||||
self.runner_params.app_window_params.borderless_closable = False
|
||||
self.runner_params.app_window_params.borderless_movable = False
|
||||
self.runner_params.app_window_params.borderless_resizable = True
|
||||
|
||||
self.runner_params.app_window_params.window_geometry.size = (1680, 1200)
|
||||
self.runner_params.imgui_window_params.enable_viewports = getattr(self, "ui_multi_viewport", False)
|
||||
self.runner_params.imgui_window_params.remember_theme = True
|
||||
self.runner_params.imgui_window_params.tweaked_theme = theme.get_tweaked_theme()
|
||||
self.runner_params.app_window_params.window_geometry.size = (1680, 1200)
|
||||
self.runner_params.imgui_window_params.enable_viewports = getattr(self, "ui_multi_viewport", False)
|
||||
self.runner_params.imgui_window_params.remember_theme = True
|
||||
self.runner_params.imgui_window_params.tweaked_theme = theme.get_tweaked_theme()
|
||||
self.runner_params.imgui_window_params.default_imgui_window_type = hello_imgui.DefaultImGuiWindowType.provide_full_screen_dock_space
|
||||
|
||||
# Enforce DPI Awareness and User Scale
|
||||
@@ -667,25 +657,26 @@ class App:
|
||||
|
||||
# Enable idling with monitor refresh rate to effectively cap FPS
|
||||
self.runner_params.fps_idling.enable_idling = True
|
||||
self.runner_params.fps_idling.fps_idle = fps_cap
|
||||
self.runner_params.fps_idling.fps_idle = fps_cap
|
||||
|
||||
self.runner_params.imgui_window_params.show_menu_bar = True
|
||||
self.runner_params.imgui_window_params.show_menu_bar = True
|
||||
self.runner_params.imgui_window_params.show_menu_view_themes = True
|
||||
self.runner_params.ini_folder_type = hello_imgui.IniFolderType.current_folder
|
||||
self.runner_params.ini_filename = "manualslop_layout.ini"
|
||||
self.runner_params.ini_folder_type = hello_imgui.IniFolderType.current_folder
|
||||
self.runner_params.ini_filename = "manualslop_layout.ini"
|
||||
def _profiled_setup_style() -> None:
|
||||
with startup_profiler.phase("setup_imgui_style"):
|
||||
theme.apply_current()
|
||||
def _profiled_post_init() -> None:
|
||||
with startup_profiler.phase("post_init"):
|
||||
self._post_init()
|
||||
self.runner_params.callbacks.show_gui = self._gui_func
|
||||
self.runner_params.callbacks.show_menus = self._show_menus
|
||||
self.runner_params.callbacks.show_gui = self._gui_func
|
||||
self.runner_params.callbacks.show_menus = self._show_menus
|
||||
self.runner_params.callbacks.load_additional_fonts = self._load_fonts
|
||||
self.runner_params.callbacks.setup_imgui_style = _profiled_setup_style
|
||||
self.runner_params.callbacks.post_init = _profiled_post_init
|
||||
self.runner_params.callbacks.setup_imgui_style = _profiled_setup_style
|
||||
self.runner_params.callbacks.post_init = _profiled_post_init
|
||||
self._fetch_models(self.current_provider)
|
||||
md_options = markdown_helper.get_renderer().options
|
||||
#Note(Ed): Exception(Thirdparty)
|
||||
try:
|
||||
immapp.run(self.runner_params, add_ons_params=immapp.AddOnsParams(with_markdown_options=md_options))
|
||||
except RuntimeError as _immapp_exc:
|
||||
@@ -703,8 +694,8 @@ class App:
|
||||
self.controller._last_imgui_assert = traceback.format_exc()
|
||||
print(
|
||||
f"[GUI-DEGRADED] immapp.run raised: {_immapp_exc}",
|
||||
file=sys.stderr,
|
||||
flush=True,
|
||||
file = sys.stderr,
|
||||
flush = True,
|
||||
)
|
||||
print(self.controller._last_imgui_assert if hasattr(self, "controller") and self.controller else "",
|
||||
file=sys.stderr, flush=True)
|
||||
@@ -722,7 +713,7 @@ class App:
|
||||
hello_imgui.set_assets_folder(str(assets_dir.absolute()))
|
||||
|
||||
# Improved font rendering with oversampling
|
||||
config = imgui.ImFontConfig()
|
||||
config = imgui.ImFontConfig()
|
||||
config.oversample_h = 3
|
||||
config.oversample_v = 3
|
||||
|
||||
@@ -730,6 +721,7 @@ class App:
|
||||
|
||||
if font_path:
|
||||
font_path = _resolve_font_path(font_path, assets_dir)
|
||||
#Note(Ed): Exception(Thirdparty)
|
||||
# Just try loading it directly; hello_imgui will look in the assets folder
|
||||
try:
|
||||
with startup_profiler.phase("load_fonts.main_with_fontawesome"):
|
||||
@@ -739,10 +731,11 @@ class App:
|
||||
self.main_font = None
|
||||
else:
|
||||
self.main_font = None
|
||||
|
||||
|
||||
#Note(Ed): Exception(Thirdparty)
|
||||
try:
|
||||
with startup_profiler.phase("load_fonts.mono"):
|
||||
params = hello_imgui.FontLoadingParams(font_config=config)
|
||||
params = hello_imgui.FontLoadingParams(font_config=config)
|
||||
self.mono_font = hello_imgui.load_font("fonts/MapleMono-Regular.ttf", font_size, params)
|
||||
except Exception as e:
|
||||
print(f"Failed to load mono font: {e}")
|
||||
@@ -756,6 +749,7 @@ class App:
|
||||
"""UI-level wrapper for approving a pending MMA sub-agent spawn."""
|
||||
self._handle_mma_respond(approved=True)
|
||||
|
||||
#TODO(Ed): Remove Exception based errors.
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
if name == 'controller':
|
||||
raise AttributeError(name)
|
||||
@@ -772,7 +766,7 @@ class App:
|
||||
def _handle_generate_send(self) -> None:
|
||||
if not self.ui_selected_context_files and not getattr(self, "_pending_proceed_generate", False):
|
||||
self._pending_generation_action = 'generate'
|
||||
self.show_empty_context_modal = True
|
||||
self.show_empty_context_modal = True
|
||||
else:
|
||||
self._pending_proceed_generate = False
|
||||
self.controller._handle_generate_send()
|
||||
@@ -780,13 +774,13 @@ class App:
|
||||
def _handle_md_only(self) -> None:
|
||||
if not self.ui_selected_context_files and not getattr(self, "_pending_proceed_md_only", False):
|
||||
self._pending_generation_action = 'md_only'
|
||||
self.show_empty_context_modal = True
|
||||
self.show_empty_context_modal = True
|
||||
else:
|
||||
self._pending_proceed_md_only = False
|
||||
self.controller._handle_md_only()
|
||||
|
||||
@property
|
||||
def current_provider(self) -> str:
|
||||
def current_provider(self) -> str:
|
||||
return self.controller.current_provider
|
||||
|
||||
@current_provider.setter
|
||||
@@ -801,8 +795,10 @@ class App:
|
||||
def current_model(self, value: str) -> None:
|
||||
self.controller.current_model = value
|
||||
|
||||
#TODO(Ed): Remove Exception based errors.
|
||||
def _get_active_capabilities(self) -> "VendorCapabilities":
|
||||
from src.vendor_capabilities import VendorCapabilities, get_capabilities
|
||||
#TODO(Ed): Remove Exception based errors.
|
||||
try:
|
||||
caps = get_capabilities(self.current_provider, self.current_model)
|
||||
except KeyError:
|
||||
@@ -824,27 +820,21 @@ class App:
|
||||
@property
|
||||
def app_debug_info(self) -> dict:
|
||||
return {
|
||||
"context_files": [f.path for f in self.context_files],
|
||||
"missing_files": self.missing_files,
|
||||
"screenshots": self.screenshots,
|
||||
"context_files": [f.path for f in self.context_files],
|
||||
"missing_files": self.missing_files,
|
||||
"screenshots": self.screenshots,
|
||||
"ui_new_context_preset_name": self.ui_new_context_preset_name,
|
||||
"target_context_preset_name": self.target_context_preset_name,
|
||||
"show_missing_files_modal": self.show_missing_files_modal,
|
||||
"active_project_root": str(self.controller.active_project_root),
|
||||
"project_keys": list(self.controller.project.keys()),
|
||||
"presets": list(self.controller.project.get('context_presets', {}).keys())
|
||||
"show_missing_files_modal": self.show_missing_files_modal,
|
||||
"active_project_root": str(self.controller.active_project_root),
|
||||
"project_keys": list(self.controller.project.keys()),
|
||||
"presets": list(self.controller.project.get('context_presets', {}).keys())
|
||||
}
|
||||
|
||||
def _take_snapshot(self) -> history.UISnapshot:
|
||||
"""
|
||||
Captures the current state of UI input parameters, system prompts, active
|
||||
""" Captures the current state of UI input parameters, system prompts, active
|
||||
discussions, and files list, returning a UISnapshot for history management.
|
||||
|
||||
State Mutations:
|
||||
None (read-only state capture).
|
||||
|
||||
SSDL Shape:
|
||||
`[Q:ui_state] -> [I:copy] -> [T:snapshot]`
|
||||
SSDL: `[Q:ui_state] -> [I:copy] -> [T:snapshot]`
|
||||
"""
|
||||
from src import history
|
||||
import copy
|
||||
@@ -865,16 +855,9 @@ class App:
|
||||
)
|
||||
|
||||
def _apply_snapshot(self, snapshot: history.UISnapshot) -> None:
|
||||
"""
|
||||
Applies a previously captured UISnapshot back to the active UI state.
|
||||
"""Applies a previously captured UISnapshot back to the active UI state.
|
||||
Restores input fields, parameters, discussions, screenshots, and context files.
|
||||
|
||||
State Mutations:
|
||||
Modifies active UI variables (self.ui_ai_input, self.temperature, self.files, self.context_files, etc.)
|
||||
self._is_applying_snapshot (temporarily set to True)
|
||||
|
||||
SSDL Shape:
|
||||
`[I:lock_flag] -> [S:ui_state] -> [I:unlock]`
|
||||
SSDL Shape: `[I:lock_flag] -> [S:ui_state] -> [I:unlock]`
|
||||
"""
|
||||
self._is_applying_snapshot = True
|
||||
try:
|
||||
@@ -893,42 +876,31 @@ class App:
|
||||
from src import models
|
||||
self.files = []
|
||||
for f in snapshot.files:
|
||||
if isinstance(f, dict):
|
||||
self.files.append(models.FileItem.from_dict(f))
|
||||
else:
|
||||
self.files.append(models.FileItem(path=str(f)))
|
||||
if isinstance(f, dict): self.files.append(models.FileItem.from_dict(f))
|
||||
else: self.files.append(models.FileItem(path=str(f)))
|
||||
|
||||
self.context_files = []
|
||||
for f in snapshot.context_files:
|
||||
if isinstance(f, dict):
|
||||
self.context_files.append(models.FileItem.from_dict(f))
|
||||
else:
|
||||
self.context_files.append(models.FileItem(path=str(f)))
|
||||
if isinstance(f, dict): self.context_files.append(models.FileItem.from_dict(f))
|
||||
else: self.context_files.append(models.FileItem(path=str(f)))
|
||||
|
||||
self.screenshots = list(snapshot.screenshots)
|
||||
self.screenshots = list(snapshot.screenshots)
|
||||
self._last_ui_snapshot = snapshot # Update last snapshot to avoid immediate re-push
|
||||
finally:
|
||||
self._is_applying_snapshot = False
|
||||
self._is_applying_snapshot = False # ?? TODO(Ed): Whats the point of this??
|
||||
|
||||
def _capture_workspace_profile(self, name: str) -> models.WorkspaceProfile:
|
||||
"""
|
||||
Serializes the current window visibility states, popped-out panel layouts, and
|
||||
"""Serializes the current window visibility states, popped-out panel layouts, and
|
||||
ImGui INI configurations into a WorkspaceProfile object.
|
||||
|
||||
State Mutations:
|
||||
self._ini_capture_ready (set to True on first invocation to bypass initial ImGui frame bugs).
|
||||
|
||||
SSDL Shape:
|
||||
`[Q:ui_states] -> [B:ini_ready] -> [T:profile]`
|
||||
SSDL Shape: `[Q:ui_states] -> [B:ini_ready] -> [T:profile]`
|
||||
"""
|
||||
if not getattr(self, "_ini_capture_ready", False):
|
||||
self._ini_capture_ready = True
|
||||
ini = ""
|
||||
else:
|
||||
try:
|
||||
ini = str(imgui.save_ini_settings_to_memory() or "")
|
||||
except Exception:
|
||||
ini = ""
|
||||
#Note(Ed): Thirdparty Exception
|
||||
try: ini = str(imgui.save_ini_settings_to_memory() or "")
|
||||
except Exception: ini = ""
|
||||
panel_states = {
|
||||
"ui_separate_context_preview": getattr(self, "ui_separate_context_preview", False),
|
||||
"ui_separate_message_panel": getattr(self, "ui_separate_message_panel", False),
|
||||
@@ -944,73 +916,46 @@ class App:
|
||||
"ui_discussion_split_h": getattr(self, "ui_discussion_split_h", 300.0),
|
||||
}
|
||||
return models.WorkspaceProfile(
|
||||
name=name,
|
||||
ini_content=ini,
|
||||
show_windows=copy.deepcopy(self.show_windows),
|
||||
panel_states=panel_states
|
||||
name = name,
|
||||
ini_content = ini,
|
||||
show_windows = copy.deepcopy(self.show_windows),
|
||||
panel_states = panel_states
|
||||
)
|
||||
|
||||
def _apply_workspace_profile(self, profile: models.WorkspaceProfile):
|
||||
"""
|
||||
Restores the window docking layout and popped-out panel visibility states
|
||||
"""Restores the window docking layout and popped-out panel visibility states
|
||||
from a saved WorkspaceProfile.
|
||||
|
||||
State Mutations:
|
||||
Modifies window visibility and panel configuration state variables.
|
||||
|
||||
SSDL Shape:
|
||||
`[I:load_ini] -> [S:ui_states]`
|
||||
SSDL Shape: `[I:load_ini] -> [S:ui_states]`
|
||||
"""
|
||||
imgui.load_ini_settings_from_memory(profile.ini_content)
|
||||
self.show_windows.update(profile.show_windows)
|
||||
for k, v in profile.panel_states.items():
|
||||
if hasattr(self, k):
|
||||
setattr(self, k, v)
|
||||
if hasattr(self, k): setattr(self, k, v)
|
||||
|
||||
def _handle_undo(self) -> None:
|
||||
"""
|
||||
Reverts the application UI state to the previous snapshot in the history stack.
|
||||
|
||||
State Mutations:
|
||||
self.history (mutated to record index changes)
|
||||
Modifies active UI variables via _apply_snapshot()
|
||||
|
||||
"""Reverts the application UI state to the previous snapshot in the history stack.
|
||||
DAG Render Context:
|
||||
Called by: _gui_func() (via hotkey Ctrl+Z) or undo button click.
|
||||
Calls: _take_snapshot(), _apply_snapshot(), HistoryManager.undo()
|
||||
|
||||
Threading & Safety:
|
||||
Must run synchronously on the Main Thread.
|
||||
"""
|
||||
sys.stderr.write(f"[DEBUG History] _handle_undo called. can_undo={self.history.can_undo}\n")
|
||||
sys.stderr.flush()
|
||||
if not self.history.can_undo:
|
||||
return
|
||||
sys.stderr.write(f"[DEBUG History] _handle_undo called. can_undo={self.history.can_undo}\n"); sys.stderr.flush()
|
||||
if not self.history.can_undo: return
|
||||
current = self._take_snapshot()
|
||||
entry = self.history.undo(current, "Undo Action")
|
||||
entry = self.history.undo(current, "Undo Action")
|
||||
if entry:
|
||||
sys.stderr.write(f"[DEBUG History] Undoing to: {entry.description}\n")
|
||||
sys.stderr.flush()
|
||||
sys.stderr.write(f"[DEBUG History] Undoing to: {entry.description}\n"); sys.stderr.flush()
|
||||
self._apply_snapshot(entry.state)
|
||||
|
||||
def _handle_jump_to_history(self, index: int) -> None:
|
||||
sys.stderr.write(f"[DEBUG History] Jumping to index {index}\n")
|
||||
sys.stderr.flush()
|
||||
sys.stderr.write(f"[DEBUG History] Jumping to index {index}\n"); sys.stderr.flush()
|
||||
current = self._take_snapshot()
|
||||
entry = self.history.jump_to_undo(index, current, "Before Jump")
|
||||
entry = self.history.jump_to_undo(index, current, "Before Jump")
|
||||
if entry:
|
||||
self._apply_snapshot(entry.state)
|
||||
|
||||
def _handle_redo(self) -> None:
|
||||
"""
|
||||
Re-applies the next snapshot in the history stack (forward navigation).
|
||||
|
||||
State Mutations:
|
||||
self.history (mutated to record index changes)
|
||||
Modifies active UI variables via _apply_snapshot()
|
||||
|
||||
SSDL Shape:
|
||||
`[I:snapshot] -> [B:history] => [I:state]`
|
||||
"""Re-applies the next snapshot in the history stack (forward navigation).
|
||||
SSDL Shape: `[I:snapshot] -> [B:history] => [I:state]`
|
||||
"""
|
||||
sys.stderr.write(f"[DEBUG History] _handle_redo called. can_redo={self.history.can_redo}\n")
|
||||
sys.stderr.flush()
|
||||
@@ -1024,17 +969,9 @@ class App:
|
||||
self._apply_snapshot(entry.state)
|
||||
|
||||
def shutdown(self) -> None:
|
||||
"""
|
||||
Cleanly shuts down the app's background tasks, saves workspace layout configurations,
|
||||
"""Cleanly shuts down the app's background tasks, saves workspace layout configurations,
|
||||
forces a save of dirty registries/caches, and terminates the active thread pools.
|
||||
|
||||
State Mutations:
|
||||
runner_params settings are flushed to disk (imgui.save_ini_settings_to_disk).
|
||||
|
||||
SSDL Shape:
|
||||
`[I:save_ini] -> [I:controller_shutdown]`
|
||||
|
||||
[C: tests/conftest.py:app_instance, tests/conftest.py:mock_app]
|
||||
SSDL Shape: `[I:save_ini] -> [I:controller_shutdown]`
|
||||
"""
|
||||
try:
|
||||
if hasattr(self, 'runner_params') and self.runner_params.ini_filename:
|
||||
@@ -1044,29 +981,23 @@ class App:
|
||||
self.controller.shutdown()
|
||||
|
||||
def load_context_preset(self, name: str) -> None:
|
||||
"""
|
||||
[C: tests/test_context_presets.py:test_load_context_preset, tests/test_context_presets.py:test_load_nonexistent_preset]
|
||||
"""
|
||||
preset = self.controller.load_context_preset(name)
|
||||
from src import models
|
||||
import copy
|
||||
self.context_files = []
|
||||
for f in preset.files:
|
||||
fi = models.FileItem(path=f.path, view_mode=f.view_mode)
|
||||
fi.custom_slices = copy.deepcopy(f.custom_slices) if hasattr(f, 'custom_slices') else []
|
||||
fi.ast_mask = copy.deepcopy(f.ast_mask) if hasattr(f, 'ast_mask') else {}
|
||||
fi.ast_signatures = getattr(f, 'ast_signatures', False)
|
||||
fi.custom_slices = copy.deepcopy(f.custom_slices) if hasattr(f, 'custom_slices') else []
|
||||
fi.ast_mask = copy.deepcopy(f.ast_mask) if hasattr(f, 'ast_mask') else {}
|
||||
fi.ast_signatures = getattr(f, 'ast_signatures', False)
|
||||
fi.ast_definitions = getattr(f, 'ast_definitions', False)
|
||||
self.context_files.append(fi)
|
||||
self.screenshots = list(preset.screenshots)
|
||||
self.ui_file_paths = [f.path for f in preset.files]
|
||||
self.screenshots = list(preset.screenshots)
|
||||
self.ui_file_paths = [f.path for f in preset.files]
|
||||
self.ui_screenshot_paths = list(preset.screenshots)
|
||||
self._update_context_file_stats()
|
||||
|
||||
def delete_context_preset(self, name: str) -> None:
|
||||
"""
|
||||
[C: tests/test_context_presets.py:test_delete_context_preset, tests/test_context_presets.py:test_delete_nonexistent_preset_no_error]
|
||||
"""
|
||||
self.controller.delete_context_preset(name)
|
||||
if getattr(self, "ui_active_context_preset", "") == name:
|
||||
self.ui_active_context_preset = ""
|
||||
@@ -1103,12 +1034,9 @@ class App:
|
||||
since the keyboard shortcut (Ctrl+Shift+P) cannot be simulated via the hook API."""
|
||||
self.show_command_palette = not self.show_command_palette
|
||||
if self.show_command_palette:
|
||||
if hasattr(self, '_command_palette_query'):
|
||||
self._command_palette_query = ""
|
||||
if hasattr(self, '_command_palette_selected'):
|
||||
self._command_palette_selected = 0
|
||||
if hasattr(self, '_command_palette_input_focused'):
|
||||
self._command_palette_input_focused = False
|
||||
if hasattr(self, '_command_palette_query'): self._command_palette_query = ""
|
||||
if hasattr(self, '_command_palette_selected'): self._command_palette_selected = 0
|
||||
if hasattr(self, '_command_palette_input_focused'): self._command_palette_input_focused = False
|
||||
|
||||
def _test_callback_func_write_to_file(self, data: str) -> None:
|
||||
"""A dummy function that a custom_callback would execute for testing."""
|
||||
@@ -1118,17 +1046,11 @@ class App:
|
||||
f.write(data)
|
||||
|
||||
def _gui_func(self) -> None:
|
||||
"""
|
||||
Main immediate-mode render loop callback executed on every frame.
|
||||
"""Main immediate-mode render loop callback executed on every frame.
|
||||
Dispatches keyboard shortcuts, renders the background shader, custom title bar,
|
||||
main dockspace, and handles popups/modals.
|
||||
|
||||
State Mutations:
|
||||
self.show_command_palette (toggled via Ctrl+Shift+P)
|
||||
self._hot_reload_error (updated on Ctrl+Alt+R)
|
||||
|
||||
SSDL Shape:
|
||||
`o-> [I:hotkeys] -> [I:title_bar] -> [I:main_interface] -> [I:modals]`
|
||||
SSDL Shape: `o-> [I:hotkeys] -> [I:title_bar] -> [I:main_interface] -> [I:modals]`
|
||||
|
||||
ASCII Layout Map:
|
||||
+---------------------------------------------------------+
|
||||
@@ -1155,7 +1077,7 @@ class App:
|
||||
sys.stderr.write(f"[startup] first _gui_func entry at {(time.time() - init_ts) * 1000:.1f}ms after init (window/GL + font/style/post_init callbacks done)\n")
|
||||
sys.stderr.flush()
|
||||
except Exception: pass
|
||||
|
||||
|
||||
# One-shot: kick off the controller's heavy-module warmup on the shared
|
||||
# io_pool once the FIRST frame has actually been painted. Waiting one frame
|
||||
# keeps the ~2s of SDK C-extension imports from holding the GIL during
|
||||
@@ -1168,33 +1090,29 @@ class App:
|
||||
self._preload_started = True
|
||||
else:
|
||||
self._first_frame_painted = True
|
||||
|
||||
|
||||
io = imgui.get_io()
|
||||
if io.key_ctrl and io.key_alt and imgui.is_key_down(imgui.Key.r):
|
||||
self._trigger_hot_reload()
|
||||
if io.key_ctrl and io.key_alt and imgui.is_key_down(imgui.Key.r): self._trigger_hot_reload()
|
||||
if (io.key_ctrl and io.key_shift
|
||||
and not io.key_alt and not io.key_super
|
||||
and imgui.is_key_pressed(imgui.Key.p)):
|
||||
self.show_command_palette = not self.show_command_palette
|
||||
if self.show_command_palette:
|
||||
if hasattr(self, '_command_palette_query'):
|
||||
self._command_palette_query = ""
|
||||
if hasattr(self, '_command_palette_selected'):
|
||||
self._command_palette_selected = 0
|
||||
|
||||
if hasattr(self, '_command_palette_query'): self._command_palette_query = ""
|
||||
if hasattr(self, '_command_palette_selected'): self._command_palette_selected = 0
|
||||
|
||||
render_custom_title_bar(self)
|
||||
render_shader_live_editor(self)
|
||||
render_history_window(self)
|
||||
pushed_prior_tint = False
|
||||
|
||||
|
||||
# Render background shader
|
||||
bg = bg_shader.get_bg()
|
||||
ws = imgui.get_io().display_size
|
||||
if bg.enabled:
|
||||
bg.render(ws.x, ws.y)
|
||||
if bg.enabled: bg.render(ws.x, ws.y)
|
||||
|
||||
theme.render_post_fx(ws.x, ws.y, self.ai_status, self.ui_crt_filter)
|
||||
|
||||
|
||||
if self.perf_profiling_enabled: self.perf_monitor.start_component("_gui_func")
|
||||
try:
|
||||
if self.is_viewing_prior_session:
|
||||
@@ -1205,11 +1123,9 @@ class App:
|
||||
except Exception as e:
|
||||
sys.stderr.write(f"ERROR in _gui_func: {e}\n")
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
self._handle_history_logic()
|
||||
|
||||
if self.perf_profiling_enabled:
|
||||
self.perf_monitor.end_component("_gui_func")
|
||||
if self.perf_profiling_enabled: self.perf_monitor.end_component("_gui_func")
|
||||
return
|
||||
|
||||
def _render_window_if_open(self, name: str, render_func: Callable[[], None], flag_condition: bool = True) -> None:
|
||||
@@ -1220,9 +1136,6 @@ class App:
|
||||
if exp: render_func()
|
||||
|
||||
def _show_menus(self) -> None:
|
||||
"""
|
||||
[C: tests/test_gui_window_controls.py:test_gui_window_controls_minimize_maximize_close]
|
||||
"""
|
||||
global win32gui, win32con
|
||||
if win32gui is None:
|
||||
import win32con
|
||||
@@ -1231,11 +1144,10 @@ class App:
|
||||
win32gui = win32gui
|
||||
|
||||
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
|
||||
if active and imgui.menu_item("Quit", "Ctrl+Q", False)[0]: self.runner_params.app_shall_exit = True
|
||||
with imscope.menu("Windows") as (active):
|
||||
if (active):
|
||||
for w in self.show_windows.keys():
|
||||
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:
|
||||
@@ -1249,21 +1161,21 @@ class App:
|
||||
ai_client.clear_comms_log()
|
||||
self._tool_log.clear()
|
||||
self._comms_log.clear()
|
||||
self.ai_status = "session reset"
|
||||
self.ai_status = "session reset"
|
||||
self.ai_response = ""
|
||||
if imgui.menu_item("Generate MD Only", "", False)[0]:
|
||||
try:
|
||||
md, path, *_ = self._do_generate()
|
||||
self.last_md = md
|
||||
md, path, *_ = self._do_generate()
|
||||
self.last_md = md
|
||||
self.last_md_path = path
|
||||
self.ai_status = f"md written: {path.name}"
|
||||
self.ai_status = f"md written: {path.name}"
|
||||
except Exception as e:
|
||||
self.ai_status = f"error: {e}"
|
||||
with imscope.menu("Layout") as (active):
|
||||
if active:
|
||||
if imgui.menu_item("Save Current...", "", False)[0]:
|
||||
self._show_save_workspace_profile_modal = True
|
||||
self._new_workspace_profile_name = ""
|
||||
self._new_workspace_profile_name = ""
|
||||
imgui.separator()
|
||||
for profile_id, profile in self.workspace_profiles.items():
|
||||
if imgui.menu_item(profile.name, "", False)[0]:
|
||||
@@ -1306,33 +1218,22 @@ class App:
|
||||
win32gui.SendMessage(hwnd, win32con.WM_NCLBUTTONDOWN, win32con.HTCAPTION, 0)
|
||||
|
||||
imgui.push_style_color(imgui.Col_.button, imgui.ImVec4(0, 0, 0, 0))
|
||||
|
||||
try:
|
||||
is_max = win32gui.GetWindowPlacement(hwnd)[1] == win32con.SW_SHOWMAXIMIZED
|
||||
except Exception:
|
||||
is_max = False
|
||||
|
||||
#Note(Ed): Thirdparty Exception
|
||||
try: is_max = win32gui.GetWindowPlacement(hwnd)[1] == win32con.SW_SHOWMAXIMIZED
|
||||
except Exception: is_max = False
|
||||
# Explicitly set Y to 0 and match button height to bar height for perfect alignment
|
||||
imgui.set_cursor_pos((right_x, 0))
|
||||
if imgui.button("_", (btn_w, bar_h)):
|
||||
win32gui.ShowWindow(hwnd, win32con.SW_MINIMIZE)
|
||||
|
||||
if imgui.button("_", (btn_w, bar_h)): win32gui.ShowWindow(hwnd, win32con.SW_MINIMIZE)
|
||||
imgui.set_cursor_pos((right_x + btn_w, 0))
|
||||
if imgui.button("[=]" if is_max else "[]", (btn_w, bar_h)):
|
||||
win32gui.ShowWindow(hwnd, win32con.SW_RESTORE if is_max else win32con.SW_MAXIMIZE)
|
||||
|
||||
if imgui.button("[=]" if is_max else "[]", (btn_w, bar_h)): win32gui.ShowWindow(hwnd, win32con.SW_RESTORE if is_max else win32con.SW_MAXIMIZE)
|
||||
imgui.set_cursor_pos((right_x + btn_w * 2, 0))
|
||||
imgui.push_style_color(imgui.Col_.button_hovered, theme.get_color("status_error"))
|
||||
if imgui.button("X", (btn_w, bar_h)):
|
||||
win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0)
|
||||
if imgui.button("X", (btn_w, bar_h)): win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0)
|
||||
imgui.pop_style_color()
|
||||
imgui.pop_style_color()
|
||||
|
||||
def _handle_history_logic(self) -> None:
|
||||
"""
|
||||
|
||||
Logic for capturing UI state for undo/redo.
|
||||
"""
|
||||
"""Logic for capturing UI state for undo/redo."""
|
||||
if self._is_applying_snapshot:
|
||||
return
|
||||
|
||||
@@ -2495,12 +2396,10 @@ def render_paths_panel(app: App) -> None:
|
||||
#region: AI Settings
|
||||
|
||||
def render_ai_settings_hub(app: App) -> None:
|
||||
"""
|
||||
Groups and renders all AI-related configuration panels in a unified hub sidebar.
|
||||
"""Groups and renders all AI-related configuration panels in a unified hub sidebar.
|
||||
Includes persona selection, LLM provider settings, system prompts, RAG config, and tools.
|
||||
|
||||
SSDL Shape:
|
||||
`[I:persona_selector] -> [B:provider_header] -> [B:system_prompts_header] -> [B:rag_header] -> [I:agent_tools]`
|
||||
SSDL Shape: `[I:persona_selector] -> [B:provider_header] -> [B:system_prompts_header] -> [B:rag_header] -> [I:agent_tools]`
|
||||
|
||||
ASCII Layout Map:
|
||||
+---------------------------------------------------------+
|
||||
@@ -2669,6 +2568,7 @@ def render_agent_tools_panel(app: App) -> None:
|
||||
| Bias Profile: [None v] |
|
||||
+---------------------------------------------------------+
|
||||
"""
|
||||
caps = app._get_active_capabilities()
|
||||
if not caps.tool_calling:
|
||||
if imgui.collapsing_header("Active Tool Presets & Biases", imgui.TreeNodeFlags_.default_open):
|
||||
imgui.text_disabled(f"(tools not supported by {app.current_provider}/{app.current_model})")
|
||||
|
||||
Reference in New Issue
Block a user