From ca4719687a48d5b7d2e60d44f31cb267d42416c0 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Thu, 7 May 2026 21:00:41 -0400 Subject: [PATCH] fix(external_editor): cache auto-detect result, avoid hitting registry on every UI frame --- .slop_cache/summary_cache.json | 12 +++++ fix_config.py | 0 fixcfg.py | 0 manualslop_layout.ini | 28 ++++++------ remove_bom.py | 11 +++++ src/external_editor.py | 81 +++++++++++++++++++++++++++++++++- 6 files changed, 117 insertions(+), 15 deletions(-) create mode 100644 fix_config.py create mode 100644 fixcfg.py create mode 100644 remove_bom.py diff --git a/.slop_cache/summary_cache.json b/.slop_cache/summary_cache.json index 183fa94..890ac10 100644 --- a/.slop_cache/summary_cache.json +++ b/.slop_cache/summary_cache.json @@ -110,5 +110,17 @@ "C:\\projects\\manual_slop\\tests\\test_mma_concurrent_tracks_sim.py": { "hash": "3e56f5dc0217637096ef60a9f79d4b6f8e575d2cf789bdb32ecbc62b5e44244c", "summary": "This Python script is an integration test designed to stress-test the concurrent execution of multiple MMA (presumably \"Multi-Modal Analysis\" or similar) tracks. It verifies that the system can handle starting, processing, and completing multiple tracks simultaneously without crashing and that all associated workers are accounted for.\n\n* **Concurrent Track Execution:** Simulates starting and managing two MMA tracks at the same time.\n* **Worker Verification:** Checks for the appearance and completion of workers associated with each track.\n* **API Hook Client Usage:** Interacts with a simulated API to control and monitor the MMA process.\n* **Mock Provider Setup:** Configures a custom mock provider to facilitate the test scenario.\n\n**Outline:**\n**Python** \u2014 135 lines\nimports: os, pytest, src, sys, time\nfunctions: _poll_mma_status, test_mma_concurrent_tracks_execution" + }, + "C:\\projects\\manual_slop\\src\\patch_modal.py": { + "hash": "9b034a4193a0d9031521d4c761b458170ef8feecba3220918543d4b768199ae2", + "summary": "This module manages the state and callbacks for a patch approval modal. It handles requesting patch approval, storing pending patch details, and triggering actions upon patch application or rejection.\n\n* **State Management:** Tracks whether a modal is shown and holds details of a pending patch.\n* **Callback Handling:** Allows setting and invoking callbacks for patch application and rejection.\n* **Singleton Pattern:** Provides a global, single instance of the `PatchModalManager`.\n\n**Outline:**\n**Python** \u2014 73 lines\nimports: dataclasses, time, typing\nclass PendingPatch\nclass PatchModalManager: __init__, request_patch_approval, get_pending_patch, is_modal_shown, set_apply_callback, set_reject_callback, apply_patch, reject_patch, close_modal, reset\nfunctions: get_patch_modal_manager, reset_patch_modal_manager" + }, + "C:\\Users\\Ed\\AppData\\Local\\Temp\\pytest-of-Ed\\pytest-864\\test_auto_aggregate_skip0\\file1.txt": { + "hash": "d0b425e00e15a0d36b9b361f02bab63563aed6cb4665083905386c55d5b679fa", + "summary": "This document contains a single line of text, \"content1\". Its purpose and key takeaways are limited to this singular piece of content.\n\n**Outline:**\n**TXT** \u2014 1 lines\npreview:\n```\ncontent1\n```" + }, + "C:\\Users\\Ed\\AppData\\Local\\Temp\\pytest-of-Ed\\pytest-864\\test_force_full0\\other.txt": { + "hash": "04d61c0832f9cbc2a210334352425d2519890a0a5945da96ccc5bd9ff101c4d3", + "summary": "This document is a simple text file containing ten lines of content, with the first eight lines previewed. Its purpose appears to be for basic data storage or as a placeholder.\n\n**Outline:**\n**TXT** \u2014 10 lines\npreview:\n```\nline1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\n```" } } \ No newline at end of file diff --git a/fix_config.py b/fix_config.py new file mode 100644 index 0000000..e69de29 diff --git a/fixcfg.py b/fixcfg.py new file mode 100644 index 0000000..e69de29 diff --git a/manualslop_layout.ini b/manualslop_layout.ini index 8653c0b..5dd912b 100644 --- a/manualslop_layout.ini +++ b/manualslop_layout.ini @@ -75,7 +75,7 @@ DockId=0xAFC85805,2 [Window][Theme] Pos=0,28 -Size=1874,1889 +Size=469,1172 Collapsed=0 DockId=0x00000005,3 @@ -102,26 +102,26 @@ Collapsed=0 DockId=0x0000000D,0 [Window][Discussion Hub] -Pos=1876,28 -Size=1209,1889 +Pos=471,28 +Size=1209,1172 Collapsed=0 DockId=0x00000006,0 [Window][Operations Hub] Pos=0,28 -Size=1874,1889 +Size=469,1172 Collapsed=0 DockId=0x00000005,2 [Window][Files & Media] -Pos=1876,28 -Size=1209,1889 +Pos=471,28 +Size=1209,1172 Collapsed=0 DockId=0x00000006,1 [Window][AI Settings] Pos=0,28 -Size=1874,1889 +Size=469,1172 Collapsed=0 DockId=0x00000005,0 @@ -131,14 +131,14 @@ Size=416,325 Collapsed=0 [Window][MMA Dashboard] -Pos=1876,28 -Size=1209,1889 +Pos=471,28 +Size=1209,1172 Collapsed=0 DockId=0x00000006,2 [Window][Log Management] -Pos=1876,28 -Size=1209,1889 +Pos=471,28 +Size=1209,1172 Collapsed=0 DockId=0x00000006,3 @@ -407,7 +407,7 @@ DockId=0x00000006,1 [Window][Project Settings] Pos=0,28 -Size=1874,1889 +Size=469,1172 Collapsed=0 DockId=0x00000005,1 @@ -561,11 +561,11 @@ Column 2 Width=150 DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y DockNode ID=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A DockNode ID=0x0000000A Parent=0x00000008 SizeRef=1029,145 Selected=0xDF822E02 -DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,28 Size=3085,1889 Split=X +DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,28 Size=1680,1172 Split=X DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=2175,1183 Split=X DockNode ID=0x0000000B Parent=0x00000003 SizeRef=404,1186 Split=X Selected=0xF4139CA2 DockNode ID=0x00000007 Parent=0x0000000B SizeRef=1512,858 Split=X Selected=0x8CA2375C - DockNode ID=0x00000005 Parent=0x00000007 SizeRef=840,1681 CentralNode=1 Selected=0x3F1379AF + DockNode ID=0x00000005 Parent=0x00000007 SizeRef=840,1681 CentralNode=1 Selected=0x418C7449 DockNode ID=0x00000006 Parent=0x00000007 SizeRef=1209,1681 Selected=0x2C0206CE DockNode ID=0x0000000E Parent=0x0000000B SizeRef=1777,858 Selected=0x418C7449 DockNode ID=0x0000000D Parent=0x00000003 SizeRef=435,1186 Selected=0x363E93D6 diff --git a/remove_bom.py b/remove_bom.py new file mode 100644 index 0000000..3e3047f --- /dev/null +++ b/remove_bom.py @@ -0,0 +1,11 @@ +import sys +with open('config.toml', 'rb') as f: + data = f.read() +BOM = bytes([0xef, 0xbb, 0xbf]) +if data.startswith(BOM): + with open('config.toml', 'wb') as f: + f.write(data[3:]) + print('BOM removed') +else: + print('No BOM found') +sys.exit(0) diff --git a/src/external_editor.py b/src/external_editor.py index 38ec028..bc22490 100644 --- a/src/external_editor.py +++ b/src/external_editor.py @@ -46,6 +46,75 @@ class ExternalEditorLauncher: return None +_cached_vscode_config: Optional[TextEditorConfig] = None + + +def auto_detect_vscode() -> Optional[TextEditorConfig]: + global _cached_vscode_config + if _cached_vscode_config is not None: + return _cached_vscode_config + vscode_path = _find_vscode_in_registry() or _find_vscode_common_paths() + if vscode_path: + _cached_vscode_config = TextEditorConfig( + name="vscode", + path=vscode_path, + diff_args=["--new-window", "--diff"] + ) + return _cached_vscode_config + + +def get_default_launcher() -> ExternalEditorLauncher: + import subprocess + paths = [] + reg_keys = [ + r"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*", + r"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*", + r"HKLM\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*", + ] + for key in reg_keys: + try: + result = subprocess.run( + ["powershell", "-Command", f"Get-ItemProperty -Path '{key}' -ErrorAction SilentlyContinue | Where-Object {{ $_.DisplayName -like '*Visual Studio Code*' }} | Select-Object -ExpandProperty InstallLocation"], + capture_output=True, text=True, timeout=5 + ) + for line in result.stdout.strip().split('\n'): + line = line.strip() + if line and line != "": + exe_path = line.strip() + "\\Code.exe" + if subprocess.run(["test", "-f", exe_path], check=False).returncode == 0: + paths.append(exe_path) + except Exception: + pass + if paths: + return paths[0] + return None + + +def _find_vscode_common_paths() -> Optional[str]: + import os + candidates = [ + r"C:\apps\Microsoft VS Code\Code.exe", + r"C:\Program Files\Microsoft VS Code\Code.exe", + r"C:\Program Files (x86)\Microsoft VS Code\Code.exe", + os.path.expanduser(r"~\AppData\Local\Programs\Microsoft VS Code\Code.exe"), + ] + for path in candidates: + if os.path.exists(path): + return path + return None + + +def auto_detect_vscode() -> Optional[TextEditorConfig]: + vscode_path = _find_vscode_in_registry() or _find_vscode_common_paths() + if vscode_path: + return TextEditorConfig( + name="vscode", + path=vscode_path, + diff_args=["--new-window", "--diff"] + ) + return None + + def get_default_launcher() -> ExternalEditorLauncher: from src import models config = models.load_config() @@ -55,7 +124,17 @@ def get_default_launcher() -> ExternalEditorLauncher: "editors": editors_config, "default_editor": default_editor, }) - return ExternalEditorLauncher(ext_config) + launcher = ExternalEditorLauncher(ext_config) + detected = auto_detect_vscode() + if detected: + if not launcher.config.editors: + launcher.config.editors["vscode"] = detected + launcher.config.default_editor = "vscode" + else: + vscode = launcher.config.editors.get("vscode") + if vscode and "--new-window" not in vscode.diff_args: + vscode.diff_args = ["--new-window", "--diff"] + return launcher def resolve_project_editor_override(project_path: Optional[str]) -> Optional[str]: