vibefpmgui.problems_panel

The Problems tab: everything flagged in the file in the editor.

The last build's compiler messages, fortitude's and fortls's issues, misspelled words, gremlins (non-ASCII and invisible characters) and the AI review's findings, in line order, drawn like the Search tab's results (vibefpmgui.result_tree). A button per tool (with its count) shows or hides its problems. Click a problem to go there.

  1"""The *Problems* tab: everything flagged in the file in the editor.
  2
  3The last build's compiler messages, fortitude's and fortls's issues,
  4misspelled words, gremlins (non-ASCII and invisible characters) and the AI
  5review's findings, in line order, drawn like
  6the Search tab's results (`vibefpmgui.result_tree`). A button per tool
  7(with its count) shows or hides its problems. Click a problem to go there.
  8"""
  9
 10from __future__ import annotations
 11
 12from pathlib import Path
 13
 14from qtpy.QtCore import Qt, Signal
 15from qtpy.QtWidgets import (
 16    QApplication,
 17    QHBoxLayout,
 18    QMenu,
 19    QToolButton,
 20    QVBoxLayout,
 21    QWidget,
 22)
 23
 24from .result_tree import RESULT_ROLE, Problem, ResultTree
 25from .widgets import StatusLabel
 26
 27# (source, button label), in the order the buttons are shown.
 28SOURCES = [("build", "Build"), ("fortitude", "fortitude"), ("fortls", "fortls"),
 29           ("spelling", "Spelling"), ("gremlins", "Gremlins"), ("review", "AI review")]
 30HINT = "Open a file in the editor to see its problems here."
 31EXPLAINABLE = ("build", "fortitude", "fortls")  # (sources Explain with AI is offered for)
 32
 33
 34class ProblemsPanel(QWidget):
 35    open_requested = Signal(str, int, int, int, int)  # file, line, column, end line, end column
 36    explain_requested = Signal(object)  # a Problem (right-click → Explain with AI)
 37    fix_requested = Signal(object, object)  # its file's path and a Problem (right-click → Fix with AI…)
 38    apply_fix_requested = Signal(object, object)  # its file's path and a Problem (right-click → Apply Fix)
 39
 40    def __init__(self, settings=None, parent=None):
 41        super().__init__(parent)
 42        self.settings = settings
 43        self.path: Path | None = None
 44        self.problems: list[Problem] = []
 45        self.labels: tuple[str, str] = ("", "")  # (folder, package) shown after the file's name
 46        self.title = StatusLabel(HINT)
 47        self.title.setContentsMargins(8, 4, 8, 2)
 48        self.buttons: dict[str, QToolButton] = {}
 49        header = QHBoxLayout()
 50        header.setContentsMargins(0, 0, 6, 0)
 51        header.setSpacing(4)
 52        header.addWidget(self.title, 1)
 53        for source, label in SOURCES:
 54            button = QToolButton()
 55            button.setCheckable(True)
 56            button.setAutoRaise(True)
 57            shown = settings.value(f"problems/{source}", True, type=bool) if settings is not None else True
 58            button.setChecked(shown)
 59            button.setToolTip(f"Show or hide {label}'s problems")
 60            button.toggled.connect(self._filter_changed)
 61            button.setProperty("label", label)
 62            self.buttons[source] = button
 63            header.addWidget(button)
 64        self.tree = ResultTree()
 65        self.tree.set_wrapping(True)  # (long messages wrap: an AI review's can be a few sentences)
 66        self.tree.open_requested.connect(self.open_requested)
 67        self.tree.setContextMenuPolicy(Qt.CustomContextMenu)
 68        self.tree.customContextMenuRequested.connect(self._context_menu)
 69        layout = QVBoxLayout(self)
 70        layout.setContentsMargins(0, 0, 0, 0)
 71        layout.setSpacing(2)
 72        layout.addLayout(header)
 73        layout.addWidget(self.tree, 1)
 74        self.show_problems(None, [])
 75
 76    def _context_menu(self, pos):
 77        menu = self.context_menu(pos)
 78        if menu is not None:
 79            menu.exec(self.tree.viewport().mapToGlobal(pos))
 80            menu.deleteLater()
 81
 82    def context_menu(self, pos) -> QMenu | None:
 83        """A problem's right-click menu (None: not on a problem)."""
 84        item = self.tree.itemAt(pos)
 85        data = item.data(0, RESULT_ROLE) if item is not None else None
 86        path, problem = data if data else (None, None)
 87        if not isinstance(problem, Problem):
 88            return None
 89        menu = QMenu(self)
 90        issue = problem.issue
 91        if issue is not None and issue.fixable:  # (fortitude's own fix, as its hover card's Apply fix)
 92            label = f"Apply Fix: {issue.fix or issue.message}" + ("" if issue.fix_safe else "  (unsafe — check it)")
 93            apply = menu.addAction(label, lambda: self.apply_fix_requested.emit(path, problem))
 94            apply.setToolTip("Change the code as fortitude suggests (one Undo step)")
 95            menu.addSeparator()
 96        explain = menu.addAction("Explain with AI", lambda: self.explain_requested.emit(problem))
 97        explain.setEnabled(problem.source in EXPLAINABLE)
 98        explain.setToolTip("Ask the local AI model what this means here, and how to fix it")
 99        fix = menu.addAction("Fix with AI…", lambda: self.fix_requested.emit(path, problem))
100        fix.setEnabled(problem.source in (*EXPLAINABLE, "review"))
101        fix.setToolTip("Ask the local AI model for an edit that addresses this (you accept or reject "
102                       "each change)")
103        menu.addAction("Copy Message", lambda: QApplication.clipboard().setText(problem.message))
104        menu.setToolTipsVisible(True)
105        return menu
106
107    def _filter_changed(self):
108        if self.settings is not None:
109            for source, button in self.buttons.items():
110                self.settings.setValue(f"problems/{source}", button.isChecked())
111        self._refresh()
112
113    def show_problems(self, path: Path | None, problems: list[Problem], folder: str = "", package: str = ""):
114        """The file in the editor and its problems (``path`` None: no file)."""
115        self.path, self.problems, self.labels = path, list(problems), (folder, package)
116        self._refresh()
117
118    def shown(self) -> list[Problem]:
119        """The problems whose tool's button is on."""
120        return [p for p in self.problems if self.buttons[p.source].isChecked()]
121
122    def _refresh(self):
123        counts = {source: 0 for source in self.buttons}
124        for p in self.problems:
125            counts[p.source] += 1
126        for source, button in self.buttons.items():
127            button.setText(f"{button.property('label')} {counts[source]}")
128            button.setVisible(counts[source] > 0 or not button.isChecked())  # (a hidden kind stays findable)
129        if self.path is None:
130            self.tree.clear_results()
131            self.title.setText(HINT)
132            return
133        shown = self.shown()
134        self.tree.set_problems(self.path, shown, *self.labels)
135        total = len(self.problems)
136        if not total:
137            self.title.setText(f"No problems in <b>{self.path.name}</b>")
138        else:
139            hidden = total - len(shown)
140            self.title.setText(f"<b>{total}</b> problem{'s' * (total != 1)}"
141                               + (f" ({hidden} hidden)" if hidden else ""))
SOURCES = [('build', 'Build'), ('fortitude', 'fortitude'), ('fortls', 'fortls'), ('spelling', 'Spelling'), ('gremlins', 'Gremlins'), ('review', 'AI review')]
HINT = 'Open a file in the editor to see its problems here.'
EXPLAINABLE = ('build', 'fortitude', 'fortls')
class ProblemsPanel(PySide6.QtWidgets.QWidget):
 35class ProblemsPanel(QWidget):
 36    open_requested = Signal(str, int, int, int, int)  # file, line, column, end line, end column
 37    explain_requested = Signal(object)  # a Problem (right-click → Explain with AI)
 38    fix_requested = Signal(object, object)  # its file's path and a Problem (right-click → Fix with AI…)
 39    apply_fix_requested = Signal(object, object)  # its file's path and a Problem (right-click → Apply Fix)
 40
 41    def __init__(self, settings=None, parent=None):
 42        super().__init__(parent)
 43        self.settings = settings
 44        self.path: Path | None = None
 45        self.problems: list[Problem] = []
 46        self.labels: tuple[str, str] = ("", "")  # (folder, package) shown after the file's name
 47        self.title = StatusLabel(HINT)
 48        self.title.setContentsMargins(8, 4, 8, 2)
 49        self.buttons: dict[str, QToolButton] = {}
 50        header = QHBoxLayout()
 51        header.setContentsMargins(0, 0, 6, 0)
 52        header.setSpacing(4)
 53        header.addWidget(self.title, 1)
 54        for source, label in SOURCES:
 55            button = QToolButton()
 56            button.setCheckable(True)
 57            button.setAutoRaise(True)
 58            shown = settings.value(f"problems/{source}", True, type=bool) if settings is not None else True
 59            button.setChecked(shown)
 60            button.setToolTip(f"Show or hide {label}'s problems")
 61            button.toggled.connect(self._filter_changed)
 62            button.setProperty("label", label)
 63            self.buttons[source] = button
 64            header.addWidget(button)
 65        self.tree = ResultTree()
 66        self.tree.set_wrapping(True)  # (long messages wrap: an AI review's can be a few sentences)
 67        self.tree.open_requested.connect(self.open_requested)
 68        self.tree.setContextMenuPolicy(Qt.CustomContextMenu)
 69        self.tree.customContextMenuRequested.connect(self._context_menu)
 70        layout = QVBoxLayout(self)
 71        layout.setContentsMargins(0, 0, 0, 0)
 72        layout.setSpacing(2)
 73        layout.addLayout(header)
 74        layout.addWidget(self.tree, 1)
 75        self.show_problems(None, [])
 76
 77    def _context_menu(self, pos):
 78        menu = self.context_menu(pos)
 79        if menu is not None:
 80            menu.exec(self.tree.viewport().mapToGlobal(pos))
 81            menu.deleteLater()
 82
 83    def context_menu(self, pos) -> QMenu | None:
 84        """A problem's right-click menu (None: not on a problem)."""
 85        item = self.tree.itemAt(pos)
 86        data = item.data(0, RESULT_ROLE) if item is not None else None
 87        path, problem = data if data else (None, None)
 88        if not isinstance(problem, Problem):
 89            return None
 90        menu = QMenu(self)
 91        issue = problem.issue
 92        if issue is not None and issue.fixable:  # (fortitude's own fix, as its hover card's Apply fix)
 93            label = f"Apply Fix: {issue.fix or issue.message}" + ("" if issue.fix_safe else "  (unsafe — check it)")
 94            apply = menu.addAction(label, lambda: self.apply_fix_requested.emit(path, problem))
 95            apply.setToolTip("Change the code as fortitude suggests (one Undo step)")
 96            menu.addSeparator()
 97        explain = menu.addAction("Explain with AI", lambda: self.explain_requested.emit(problem))
 98        explain.setEnabled(problem.source in EXPLAINABLE)
 99        explain.setToolTip("Ask the local AI model what this means here, and how to fix it")
100        fix = menu.addAction("Fix with AI…", lambda: self.fix_requested.emit(path, problem))
101        fix.setEnabled(problem.source in (*EXPLAINABLE, "review"))
102        fix.setToolTip("Ask the local AI model for an edit that addresses this (you accept or reject "
103                       "each change)")
104        menu.addAction("Copy Message", lambda: QApplication.clipboard().setText(problem.message))
105        menu.setToolTipsVisible(True)
106        return menu
107
108    def _filter_changed(self):
109        if self.settings is not None:
110            for source, button in self.buttons.items():
111                self.settings.setValue(f"problems/{source}", button.isChecked())
112        self._refresh()
113
114    def show_problems(self, path: Path | None, problems: list[Problem], folder: str = "", package: str = ""):
115        """The file in the editor and its problems (``path`` None: no file)."""
116        self.path, self.problems, self.labels = path, list(problems), (folder, package)
117        self._refresh()
118
119    def shown(self) -> list[Problem]:
120        """The problems whose tool's button is on."""
121        return [p for p in self.problems if self.buttons[p.source].isChecked()]
122
123    def _refresh(self):
124        counts = {source: 0 for source in self.buttons}
125        for p in self.problems:
126            counts[p.source] += 1
127        for source, button in self.buttons.items():
128            button.setText(f"{button.property('label')} {counts[source]}")
129            button.setVisible(counts[source] > 0 or not button.isChecked())  # (a hidden kind stays findable)
130        if self.path is None:
131            self.tree.clear_results()
132            self.title.setText(HINT)
133            return
134        shown = self.shown()
135        self.tree.set_problems(self.path, shown, *self.labels)
136        total = len(self.problems)
137        if not total:
138            self.title.setText(f"No problems in <b>{self.path.name}</b>")
139        else:
140            hidden = total - len(shown)
141            self.title.setText(f"<b>{total}</b> problem{'s' * (total != 1)}"
142                               + (f" ({hidden} hidden)" if hidden else ""))

QWidget(self, /, parent: PySide6.QtWidgets.QWidget | None = None, f: PySide6.QtCore.Qt.WindowType = Default(Qt.WindowFlags), *, modal: bool | None = None, windowModality: PySide6.QtCore.Qt.WindowModality | None = None, enabled: bool | None = None, geometry: PySide6.QtCore.QRect | None = None, frameGeometry: PySide6.QtCore.QRect | None = None, normalGeometry: PySide6.QtCore.QRect | None = None, x: int | None = None, y: int | None = None, pos: PySide6.QtCore.QPoint | None = None, frameSize: PySide6.QtCore.QSize | None = None, size: PySide6.QtCore.QSize | None = None, width: int | None = None, height: int | None = None, rect: PySide6.QtCore.QRect | None = None, childrenRect: PySide6.QtCore.QRect | None = None, childrenRegion: PySide6.QtGui.QRegion | None = None, sizePolicy: PySide6.QtWidgets.QSizePolicy | None = None, minimumSize: PySide6.QtCore.QSize | None = None, maximumSize: PySide6.QtCore.QSize | None = None, minimumWidth: int | None = None, minimumHeight: int | None = None, maximumWidth: int | None = None, maximumHeight: int | None = None, sizeIncrement: PySide6.QtCore.QSize | None = None, baseSize: PySide6.QtCore.QSize | None = None, palette: PySide6.QtGui.QPalette | None = None, font: PySide6.QtGui.QFont | None = None, cursor: PySide6.QtGui.QCursor | None = None, mouseTracking: bool | None = None, tabletTracking: bool | None = None, isActiveWindow: bool | None = None, focusPolicy: PySide6.QtCore.Qt.FocusPolicy | None = None, focus: bool | None = None, contextMenuPolicy: PySide6.QtCore.Qt.ContextMenuPolicy | None = None, updatesEnabled: bool | None = None, visible: bool | None = None, minimized: bool | None = None, maximized: bool | None = None, fullScreen: bool | None = None, sizeHint: PySide6.QtCore.QSize | None = None, minimumSizeHint: PySide6.QtCore.QSize | None = None, acceptDrops: bool | None = None, windowTitle: str | None = None, windowIcon: PySide6.QtGui.QIcon | None = None, windowIconText: str | None = None, windowOpacity: float | None = None, windowModified: bool | None = None, toolTip: str | None = None, toolTipDuration: int | None = None, statusTip: str | None = None, whatsThis: str | None = None, accessibleName: str | None = None, accessibleDescription: str | None = None, accessibleIdentifier: str | None = None, layoutDirection: PySide6.QtCore.Qt.LayoutDirection | None = None, autoFillBackground: bool | None = None, styleSheet: str | None = None, locale: PySide6.QtCore.QLocale | None = None, windowFilePath: str | None = None, inputMethodHints: PySide6.QtCore.Qt.InputMethodHint | None = None) -> None

ProblemsPanel(settings=None, parent=None)
41    def __init__(self, settings=None, parent=None):
42        super().__init__(parent)
43        self.settings = settings
44        self.path: Path | None = None
45        self.problems: list[Problem] = []
46        self.labels: tuple[str, str] = ("", "")  # (folder, package) shown after the file's name
47        self.title = StatusLabel(HINT)
48        self.title.setContentsMargins(8, 4, 8, 2)
49        self.buttons: dict[str, QToolButton] = {}
50        header = QHBoxLayout()
51        header.setContentsMargins(0, 0, 6, 0)
52        header.setSpacing(4)
53        header.addWidget(self.title, 1)
54        for source, label in SOURCES:
55            button = QToolButton()
56            button.setCheckable(True)
57            button.setAutoRaise(True)
58            shown = settings.value(f"problems/{source}", True, type=bool) if settings is not None else True
59            button.setChecked(shown)
60            button.setToolTip(f"Show or hide {label}'s problems")
61            button.toggled.connect(self._filter_changed)
62            button.setProperty("label", label)
63            self.buttons[source] = button
64            header.addWidget(button)
65        self.tree = ResultTree()
66        self.tree.set_wrapping(True)  # (long messages wrap: an AI review's can be a few sentences)
67        self.tree.open_requested.connect(self.open_requested)
68        self.tree.setContextMenuPolicy(Qt.CustomContextMenu)
69        self.tree.customContextMenuRequested.connect(self._context_menu)
70        layout = QVBoxLayout(self)
71        layout.setContentsMargins(0, 0, 0, 0)
72        layout.setSpacing(2)
73        layout.addLayout(header)
74        layout.addWidget(self.tree, 1)
75        self.show_problems(None, [])

__init__(self, /, parent: PySide6.QtWidgets.QWidget | None = None, f: PySide6.QtCore.Qt.WindowType = Default(Qt.WindowFlags), *, modal: bool | None = None, windowModality: PySide6.QtCore.Qt.WindowModality | None = None, enabled: bool | None = None, geometry: PySide6.QtCore.QRect | None = None, frameGeometry: PySide6.QtCore.QRect | None = None, normalGeometry: PySide6.QtCore.QRect | None = None, x: int | None = None, y: int | None = None, pos: PySide6.QtCore.QPoint | None = None, frameSize: PySide6.QtCore.QSize | None = None, size: PySide6.QtCore.QSize | None = None, width: int | None = None, height: int | None = None, rect: PySide6.QtCore.QRect | None = None, childrenRect: PySide6.QtCore.QRect | None = None, childrenRegion: PySide6.QtGui.QRegion | None = None, sizePolicy: PySide6.QtWidgets.QSizePolicy | None = None, minimumSize: PySide6.QtCore.QSize | None = None, maximumSize: PySide6.QtCore.QSize | None = None, minimumWidth: int | None = None, minimumHeight: int | None = None, maximumWidth: int | None = None, maximumHeight: int | None = None, sizeIncrement: PySide6.QtCore.QSize | None = None, baseSize: PySide6.QtCore.QSize | None = None, palette: PySide6.QtGui.QPalette | None = None, font: PySide6.QtGui.QFont | None = None, cursor: PySide6.QtGui.QCursor | None = None, mouseTracking: bool | None = None, tabletTracking: bool | None = None, isActiveWindow: bool | None = None, focusPolicy: PySide6.QtCore.Qt.FocusPolicy | None = None, focus: bool | None = None, contextMenuPolicy: PySide6.QtCore.Qt.ContextMenuPolicy | None = None, updatesEnabled: bool | None = None, visible: bool | None = None, minimized: bool | None = None, maximized: bool | None = None, fullScreen: bool | None = None, sizeHint: PySide6.QtCore.QSize | None = None, minimumSizeHint: PySide6.QtCore.QSize | None = None, acceptDrops: bool | None = None, windowTitle: str | None = None, windowIcon: PySide6.QtGui.QIcon | None = None, windowIconText: str | None = None, windowOpacity: float | None = None, windowModified: bool | None = None, toolTip: str | None = None, toolTipDuration: int | None = None, statusTip: str | None = None, whatsThis: str | None = None, accessibleName: str | None = None, accessibleDescription: str | None = None, accessibleIdentifier: str | None = None, layoutDirection: PySide6.QtCore.Qt.LayoutDirection | None = None, autoFillBackground: bool | None = None, styleSheet: str | None = None, locale: PySide6.QtCore.QLocale | None = None, windowFilePath: str | None = None, inputMethodHints: PySide6.QtCore.Qt.InputMethodHint | None = None) -> None

Initialize self. See help(type(self)) for accurate signature.

def open_requested(unknown):

The type of the None singleton.

def explain_requested(unknown):

The type of the None singleton.

def fix_requested(unknown):

The type of the None singleton.

def apply_fix_requested(unknown):

The type of the None singleton.

settings
path: pathlib.Path | None
labels: tuple[str, str]
title
buttons: dict[str, PySide6.QtWidgets.QToolButton]
tree
def context_menu(self, pos) -> PySide6.QtWidgets.QMenu | None:
 83    def context_menu(self, pos) -> QMenu | None:
 84        """A problem's right-click menu (None: not on a problem)."""
 85        item = self.tree.itemAt(pos)
 86        data = item.data(0, RESULT_ROLE) if item is not None else None
 87        path, problem = data if data else (None, None)
 88        if not isinstance(problem, Problem):
 89            return None
 90        menu = QMenu(self)
 91        issue = problem.issue
 92        if issue is not None and issue.fixable:  # (fortitude's own fix, as its hover card's Apply fix)
 93            label = f"Apply Fix: {issue.fix or issue.message}" + ("" if issue.fix_safe else "  (unsafe — check it)")
 94            apply = menu.addAction(label, lambda: self.apply_fix_requested.emit(path, problem))
 95            apply.setToolTip("Change the code as fortitude suggests (one Undo step)")
 96            menu.addSeparator()
 97        explain = menu.addAction("Explain with AI", lambda: self.explain_requested.emit(problem))
 98        explain.setEnabled(problem.source in EXPLAINABLE)
 99        explain.setToolTip("Ask the local AI model what this means here, and how to fix it")
100        fix = menu.addAction("Fix with AI…", lambda: self.fix_requested.emit(path, problem))
101        fix.setEnabled(problem.source in (*EXPLAINABLE, "review"))
102        fix.setToolTip("Ask the local AI model for an edit that addresses this (you accept or reject "
103                       "each change)")
104        menu.addAction("Copy Message", lambda: QApplication.clipboard().setText(problem.message))
105        menu.setToolTipsVisible(True)
106        return menu

A problem's right-click menu (None: not on a problem).

def show_problems( self, path: pathlib.Path | None, problems: list[vibefpmgui.result_tree.Problem], folder: str = '', package: str = ''):
114    def show_problems(self, path: Path | None, problems: list[Problem], folder: str = "", package: str = ""):
115        """The file in the editor and its problems (``path`` None: no file)."""
116        self.path, self.problems, self.labels = path, list(problems), (folder, package)
117        self._refresh()

The file in the editor and its problems (path None: no file).

def shown(self) -> list[vibefpmgui.result_tree.Problem]:
119    def shown(self) -> list[Problem]:
120        """The problems whose tool's button is on."""
121        return [p for p in self.problems if self.buttons[p.source].isChecked()]

The problems whose tool's button is on.

staticMetaObject = PySide6.QtCore.QMetaObject("ProblemsPanel" inherits "QWidget": Methods: #33 type=Signal, signature=open_requested(QString,int,int,int,int), parameters=QString, int, int, int, int #34 type=Signal, signature=explain_requested(PyObject), parameters=PyObject #35 type=Signal, signature=fix_requested(PyObject,PyObject), parameters=PyObject, PyObject #36 type=Signal, signature=apply_fix_requested(PyObject,PyObject), parameters=PyObject, PyObject )