from __future__ import annotations from collections import deque from typing import Iterable, List from PySide6.QtGui import QColor, QFont, QTextCharFormat, QTextCursor, QTextOption from PySide6.QtWidgets import QTextEdit from ameba_control_panel.services.log_buffer import LogLine class LogView(QTextEdit): """Fast-ish append-only log with selectable text and match highlighting.""" def __init__(self, max_items: int, parent=None) -> None: super().__init__(parent) self.setReadOnly(True) self.setWordWrapMode(QTextOption.NoWrap) self.setLineWrapMode(QTextEdit.NoWrap) self.setHorizontalScrollBarPolicy(self.horizontalScrollBarPolicy()) font = QFont("JetBrains Mono") font.setStyleHint(QFont.Monospace) font.setPointSize(10) self.setFont(font) self._max_items = max_items self._lines = deque() self._colors = { "rx": QColor("#1b5e20"), "tx": QColor("#0d47a1"), "info": QColor("#424242"), } self._match_rows: List[int] = [] def set_colors(self, rx: str, tx: str, info: str) -> None: self._colors = {"rx": QColor(rx), "tx": QColor(tx), "info": QColor(info)} def append_lines(self, lines: Iterable[LogLine]) -> None: cursor = self.textCursor() cursor.movePosition(QTextCursor.End) doc = self.document() for line in lines: self._lines.append(line) fmt = QTextCharFormat() fmt.setForeground(self._colors.get(line.direction, self._colors["info"])) cursor.insertText(line.as_display(), fmt) cursor.insertBlock() self.setTextCursor(cursor) # Trim overflow blocks and mirror deque while len(self._lines) > self._max_items and doc.blockCount() > 0: self._lines.popleft() block = doc.firstBlock() cur = QTextCursor(block) cur.select(QTextCursor.BlockUnderCursor) cur.removeSelectedText() cur.deleteChar() self._apply_matches() self.verticalScrollBar().setValue(self.verticalScrollBar().maximum()) def clear_log(self) -> None: self._lines.clear() self.clear() self._match_rows = [] def set_matches(self, rows: List[int]) -> None: self._match_rows = rows self._apply_matches() def _apply_matches(self) -> None: extra = [] doc = self.document() for row in self._match_rows: block = doc.findBlockByNumber(row) if not block.isValid(): continue cursor = QTextCursor(block) sel = QTextEdit.ExtraSelection() sel.cursor = cursor sel.format.setBackground(QColor("#fff59d")) extra.append(sel) self.setExtraSelections(extra) def copy_selected(self) -> None: self.copy()