Source code for simvx.ide.config

"""Persistent IDE settings stored via the unified ``AppConfig``.

``Config`` exposes flat-name accessors over ``AppConfig.general`` and
``AppConfig.ide`` sections. All colours come from the ``AppTheme`` singleton.
"""

import logging
import os
from pathlib import Path

from simvx.core.config import AppConfig

log = logging.getLogger(__name__)

[docs] class Config: """IDE preferences backed by the unified ``AppConfig``. Flat attribute accessors map to ``AppConfig.general`` and ``AppConfig.ide`` sections. Colours are provided by the ``AppTheme`` singleton (see ``simvx.core.ui.theme``). """ def __init__(self) -> None: self._config = AppConfig() # IDE uses a narrower default window than the editor. self._config.general.window_width = 1400 # -- General section properties ------------------------------------------ @property def font_size(self) -> int: return int(self._config.general.font_size)
[docs] @font_size.setter def font_size(self, value: int) -> None: self._config.general.font_size = float(value)
@property def window_width(self) -> int: return self._config.general.window_width
[docs] @window_width.setter def window_width(self, value: int) -> None: self._config.general.window_width = value
@property def window_height(self) -> int: return self._config.general.window_height
[docs] @window_height.setter def window_height(self, value: int) -> None: self._config.general.window_height = value
@property def recent_files(self) -> list[str]: return self._config.general.recent_files
[docs] @recent_files.setter def recent_files(self, value: list[str]) -> None: self._config.general.recent_files = value
@property def recent_folders(self) -> list[str]: return self._config.general.recent_folders
[docs] @recent_folders.setter def recent_folders(self, value: list[str]) -> None: self._config.general.recent_folders = value
@property def theme_preset(self) -> str: return self._config.general.theme_preset
[docs] @theme_preset.setter def theme_preset(self, value: str) -> None: self._config.general.theme_preset = value
# -- IDE section properties ---------------------------------------------- @property def tab_size(self) -> int: return self._config.ide.tab_size
[docs] @tab_size.setter def tab_size(self, value: int) -> None: self._config.ide.tab_size = value
@property def insert_spaces(self) -> bool: return self._config.ide.insert_spaces
[docs] @insert_spaces.setter def insert_spaces(self, value: bool) -> None: self._config.ide.insert_spaces = value
@property def show_line_numbers(self) -> bool: return self._config.ide.show_line_numbers
[docs] @show_line_numbers.setter def show_line_numbers(self, value: bool) -> None: self._config.ide.show_line_numbers = value
@property def show_minimap(self) -> bool: return self._config.ide.show_minimap
[docs] @show_minimap.setter def show_minimap(self, value: bool) -> None: self._config.ide.show_minimap = value
@property def show_code_folding(self) -> bool: return self._config.ide.show_code_folding
[docs] @show_code_folding.setter def show_code_folding(self, value: bool) -> None: self._config.ide.show_code_folding = value
@property def show_indent_guides(self) -> bool: return self._config.ide.show_indent_guides
[docs] @show_indent_guides.setter def show_indent_guides(self, value: bool) -> None: self._config.ide.show_indent_guides = value
@property def auto_save(self) -> bool: return self._config.ide.auto_save
[docs] @auto_save.setter def auto_save(self, value: bool) -> None: self._config.ide.auto_save = value
@property def format_on_save(self) -> bool: return self._config.ide.format_on_save
[docs] @format_on_save.setter def format_on_save(self, value: bool) -> None: self._config.ide.format_on_save = value
@property def sidebar_width(self) -> int: return self._config.ide.sidebar_width
[docs] @sidebar_width.setter def sidebar_width(self, value: int) -> None: self._config.ide.sidebar_width = value
@property def bottom_panel_height(self) -> int: return self._config.ide.bottom_panel_height
[docs] @bottom_panel_height.setter def bottom_panel_height(self, value: int) -> None: self._config.ide.bottom_panel_height = value
@property def sidebar_visible(self) -> bool: return self._config.ide.sidebar_visible
[docs] @sidebar_visible.setter def sidebar_visible(self, value: bool) -> None: self._config.ide.sidebar_visible = value
@property def bottom_panel_visible(self) -> bool: return self._config.ide.bottom_panel_visible
[docs] @bottom_panel_visible.setter def bottom_panel_visible(self, value: bool) -> None: self._config.ide.bottom_panel_visible = value
@property def lsp_enabled(self) -> bool: return self._config.ide.lsp_enabled
[docs] @lsp_enabled.setter def lsp_enabled(self, value: bool) -> None: self._config.ide.lsp_enabled = value
@property def lsp_command(self) -> str: return self._config.ide.lsp_command
[docs] @lsp_command.setter def lsp_command(self, value: str) -> None: self._config.ide.lsp_command = value
@property def lsp_args(self) -> list[str]: return self._config.ide.lsp_args
[docs] @lsp_args.setter def lsp_args(self, value: list[str]) -> None: self._config.ide.lsp_args = value
@property def lint_enabled(self) -> bool: return self._config.ide.lint_enabled
[docs] @lint_enabled.setter def lint_enabled(self, value: bool) -> None: self._config.ide.lint_enabled = value
@property def lint_on_save(self) -> bool: return self._config.ide.lint_on_save
[docs] @lint_on_save.setter def lint_on_save(self, value: bool) -> None: self._config.ide.lint_on_save = value
@property def lint_command(self) -> str: return self._config.ide.lint_command
[docs] @lint_command.setter def lint_command(self, value: str) -> None: self._config.ide.lint_command = value
@property def format_command(self) -> str: return self._config.ide.format_command
[docs] @format_command.setter def format_command(self, value: str) -> None: self._config.ide.format_command = value
@property def python_path(self) -> str: return self._config.ide.python_path
[docs] @python_path.setter def python_path(self, value: str) -> None: self._config.ide.python_path = value
@property def venv_path(self) -> str: return self._config.ide.venv_path
[docs] @venv_path.setter def venv_path(self, value: str) -> None: self._config.ide.venv_path = value
@property def auto_detect_venv(self) -> bool: return self._config.ide.auto_detect_venv
[docs] @auto_detect_venv.setter def auto_detect_venv(self, value: bool) -> None: self._config.ide.auto_detect_venv = value
@property def debug_adapter(self) -> str: return self._config.ide.debug_adapter
[docs] @debug_adapter.setter def debug_adapter(self, value: str) -> None: self._config.ide.debug_adapter = value
@property def keybindings(self) -> dict[str, str]: return self._config.ide.keybindings
[docs] @keybindings.setter def keybindings(self, value: dict[str, str]) -> None: self._config.ide.keybindings = value
# -- Persistence ---------------------------------------------------------
[docs] def load(self) -> None: self._config.load()
[docs] def save(self) -> None: self._config.save()
# -- Recent files/folders ------------------------------------------------
[docs] def add_recent_file(self, path: str) -> None: if path in self.recent_files: self.recent_files.remove(path) self.recent_files.insert(0, path) self.recent_files = self.recent_files[:20]
[docs] def add_recent_folder(self, path: str) -> None: if path in self.recent_folders: self.recent_folders.remove(path) self.recent_folders.insert(0, path) self.recent_folders = self.recent_folders[:10]
# -- Virtual environment detection ---------------------------------------
[docs] @staticmethod def detect_venv(project_root: str) -> str | None: """Detect a virtual environment in *project_root*. Checks common venv directory names (.venv, venv, .env) for a Python interpreter. Returns the absolute path to the venv directory, or None. """ if not project_root: return None root = Path(project_root) for name in (".venv", "venv", ".env"): python = root / name / "bin" / "python" if python.is_file(): return str(root / name) return None
[docs] def get_python_command(self, project_root: str) -> str: """Return the Python interpreter path to use for this project. Priority: explicit ``python_path`` setting > auto-detected venv > system ``python``. """ if self.python_path: return self.python_path venv = self._resolve_venv(project_root) if venv: return str(Path(venv) / "bin" / "python") return "python"
[docs] def get_env(self, project_root: str) -> dict[str, str]: """Build a subprocess environment dict with the detected venv activated.""" env = os.environ.copy() venv = self._resolve_venv(project_root) if venv: venv_bin = str(Path(venv) / "bin") env["VIRTUAL_ENV"] = venv env["PATH"] = venv_bin + os.pathsep + env.get("PATH", "") env.pop("PYTHONHOME", None) return env
def _resolve_venv(self, project_root: str) -> str | None: """Return the venv path from explicit config or auto-detection.""" if self.venv_path: return self.venv_path if self.auto_detect_venv: return self.detect_venv(project_root) return None # -- Theme presets ------------------------------------------------------- _THEME_FACTORIES: dict[str, str] | None = None @staticmethod def _get_factories() -> dict: from simvx.core.ui.theme import AppTheme return { "dark": AppTheme.dark, "abyss": AppTheme.abyss, "midnight": AppTheme.midnight, "light": AppTheme.light, "monokai": AppTheme.monokai, "solarised_dark": AppTheme.solarised_dark, "nord": AppTheme.nord, }
[docs] def apply_theme(self, preset: str): """Apply a theme preset and update the global singleton.""" from simvx.core.ui.theme import set_theme factories = self._get_factories() factory = factories.get(preset) if not factory: return self.theme_preset = preset set_theme(factory()) self.save()
[docs] def get_theme(self): """Return an ``AppTheme`` matching the current preset and set the global singleton.""" from simvx.core.ui.theme import set_theme factories = self._get_factories() theme = factories.get(self.theme_preset, factories["dark"])() set_theme(theme) return theme