Source code for simvx.ide.widgets.status_bar

"""StatusBar -- bottom status bar showing file, cursor, diagnostics, debug state."""


from __future__ import annotations

import logging
from typing import TYPE_CHECKING

from simvx.core import Vec2
from simvx.core.ui.core import Control
from simvx.core.ui.theme import get_theme

if TYPE_CHECKING:
    from ..config import IDEConfig
    from ..state import IDEState

log = logging.getLogger(__name__)

_FONT_SIZE = 13.0
_PAD = 8.0


[docs] class StatusBar(Control): """28px status bar at the bottom of the IDE window. Displays: file path | Ln X, Col Y | language | encoding | diagnostics | LSP | lint | debug """ def __init__(self, state: IDEState, config: IDEConfig, **kwargs): super().__init__(**kwargs) self._state = state self._config = config self.size = Vec2(config.window_width, 28) # Cached display values self._file_path: str = "" self._cursor_text: str = "Ln 1, Col 1" self._language: str = "" self._encoding: str = "UTF-8" self._error_count: int = 0 self._warning_count: int = 0 self._debug_state: str = "" self._status_msg: str = "" self._status_timer: float = 0.0 self._lsp_status: str = "off" # off, starting, ready, error self._bookmark_count: int = 0 # Connect signals state.active_file_changed.connect(self._on_file_changed) state.cursor_moved.connect(self._on_cursor_moved) state.diagnostics_updated.connect(self._on_diagnostics_updated) state.debug_state_changed.connect(self._on_debug_state_changed) state.status_message.connect(self._on_status_message) state.bookmark_toggled.connect(self._on_bookmark_toggled) def _on_file_changed(self, path: str): self._file_path = self._state.relative_path(path) if path else "" # Detect language from file extension if path: from pathlib import Path as P ext = P(path).suffix.lower() lang_map = {".py": "Python", ".js": "JavaScript", ".ts": "TypeScript", ".json": "JSON", ".toml": "TOML", ".yaml": "YAML", ".yml": "YAML", ".md": "Markdown", ".html": "HTML", ".css": "CSS", ".glsl": "GLSL", ".rs": "Rust", ".go": "Go", ".c": "C", ".cpp": "C++", ".h": "C", ".sh": "Shell", ".txt": "Text", ".xml": "XML", ".ini": "INI", ".cfg": "Config"} self._language = lang_map.get(ext, ext.lstrip(".").upper() if ext else "") else: self._language = "" self._encoding = "UTF-8" def _on_cursor_moved(self, line: int, col: int): self._cursor_text = f"Ln {line}, Col {col}" def _on_diagnostics_updated(self, path: str, diagnostics: list): # Aggregate from all files all_diags = self._state.get_all_diagnostics() self._error_count = sum(sum(1 for d in diags if d.severity == 1) for diags in all_diags.values()) self._warning_count = sum(sum(1 for d in diags if d.severity == 2) for diags in all_diags.values()) def _on_debug_state_changed(self, state_name: str, data=None): self._debug_state = state_name def _on_status_message(self, message: str): self._status_msg = message self._status_timer = 5.0 def _on_bookmark_toggled(self, path: str, line: int): """Update bookmark count from all files.""" total = sum(len(bm) for bm in self._state.get_all_bookmarks().values()) self._bookmark_count = total
[docs] def set_lsp_status(self, status: str): """Update LSP status: 'off', 'starting', 'ready', 'error'.""" self._lsp_status = status
[docs] def process(self, dt: float): if self._status_timer > 0: self._status_timer -= dt if self._status_timer <= 0: self._status_msg = ""
[docs] def draw(self, renderer): theme = get_theme() x, y, w, h = self.get_global_rect() scale = _FONT_SIZE / 14.0 # Background renderer.draw_filled_rect(x, y, w, h, theme.status_bar_bg) # Top border line renderer.draw_line_coloured(x, y, x + w, y, theme.border) cx = x + _PAD # Status message (temporary, takes priority over file path) if self._status_msg: renderer.draw_text_coloured(self._status_msg, cx, y + (h - _FONT_SIZE) / 2, scale, theme.accent) return # File path if self._file_path: renderer.draw_text_coloured(self._file_path, cx, y + (h - _FONT_SIZE) / 2, scale, theme.text) cx += renderer.text_width(self._file_path, scale) + _PAD * 2 # Separator if self._file_path: renderer.draw_line_coloured(cx, y + 4, cx, y + h - 4, theme.border_light) cx += _PAD # Right-aligned items (build from right edge) rx = x + w - _PAD ty = y + (h - _FONT_SIZE) / 2 # Debug state (rightmost) if self._debug_state: dw = renderer.text_width(self._debug_state, scale) rx -= dw renderer.draw_text_coloured(self._debug_state, rx, ty, scale, theme.accent) rx -= _PAD * 2 # LSP status lsp_colours = {"off": theme.text_dim, "starting": theme.warning, "ready": theme.success, "error": theme.error} lsp_label = f"LSP: {self._lsp_status}" lsp_colour = lsp_colours.get(self._lsp_status, theme.text_dim) lsp_w = renderer.text_width(lsp_label, scale) rx -= lsp_w renderer.draw_text_coloured(lsp_label, rx, ty, scale, lsp_colour) rx -= _PAD * 2 # Lint status lint_label = "Lint: on" if self._config.lint_enabled else "Lint: off" lint_colour = theme.success if self._config.lint_enabled else theme.text_dim lint_w = renderer.text_width(lint_label, scale) rx -= lint_w renderer.draw_text_coloured(lint_label, rx, ty, scale, lint_colour) rx -= _PAD * 2 # Diagnostics diag_parts = [] if self._error_count > 0: diag_parts.append((f"E:{self._error_count}", theme.error)) if self._warning_count > 0: diag_parts.append((f"W:{self._warning_count}", theme.warning)) for text, colour in reversed(diag_parts): tw = renderer.text_width(text, scale) rx -= tw renderer.draw_text_coloured(text, rx, ty, scale, colour) rx -= _PAD # Bookmarks if self._bookmark_count > 0: bm_text = f"BM: {self._bookmark_count}" bm_w = renderer.text_width(bm_text, scale) rx -= bm_w renderer.draw_text_coloured(bm_text, rx, ty, scale, theme.accent) rx -= _PAD * 2 # Encoding if self._encoding: ew = renderer.text_width(self._encoding, scale) rx -= ew renderer.draw_text_coloured(self._encoding, rx, ty, scale, theme.text) rx -= _PAD * 2 # Language if self._language: lw = renderer.text_width(self._language, scale) rx -= lw renderer.draw_text_coloured(self._language, rx, ty, scale, theme.text) rx -= _PAD * 2 # Cursor position cw = renderer.text_width(self._cursor_text, scale) rx -= cw renderer.draw_text_coloured(self._cursor_text, rx, ty, scale, theme.text)