vibefpmgui.debug_panel

The Debug panel: the session's state and controls (the Debug menu's actions), the call stack, the variables (expanded as asked for: an array's elements, a type's components) and the console: the program's output and the debugger's messages, with a field to evaluate an expression in the frame shown. It shows what vibefpmgui.debug_session.DebugSession says; the window (vibefpmgui.window.debugging) connects them.

  1"""The Debug panel: the session's state and controls (the Debug menu's
  2actions), the call stack, the variables (expanded as asked for: an array's
  3elements, a type's components) and the console: the program's output and the
  4debugger's messages, with a field to evaluate an expression in the frame
  5shown. It shows what `vibefpmgui.debug_session.DebugSession` says; the window
  6(`vibefpmgui.window.debugging`) connects them.
  7"""
  8
  9from __future__ import annotations
 10
 11from qtpy.QtCore import QSize, Qt, Signal
 12from qtpy.QtGui import QAction, QTextCursor
 13from qtpy.QtWidgets import (
 14    QHBoxLayout,
 15    QLabel,
 16    QListWidget,
 17    QListWidgetItem,
 18    QPlainTextEdit,
 19    QSplitter,
 20    QToolButton,
 21    QTreeWidget,
 22    QTreeWidgetItem,
 23    QVBoxLayout,
 24    QWidget,
 25)
 26
 27from .breakpoints_list import BreakpointsList
 28from .build_monitor import PALETTES, TitleIcon, app_is_light
 29from .debug_session import (
 30    IDLE,
 31    RUNNING,
 32    STARTING,
 33    STOPPED,
 34    DebugSession,
 35    Frame,
 36    Scope,
 37    Variable,
 38)
 39from .widgets import HistoryLineEdit
 40
 41REFERENCE_ROLE = Qt.UserRole + 1  # a variable's item: its variablesReference (0: no parts)
 42LOADED_ROLE = Qt.UserRole + 2  # its parts asked for already
 43PLACEHOLDER = "…"  # (an item's child until its parts are asked for)
 44
 45STATE_TEXT = {IDLE: "Not debugging", STARTING: "Starting…", RUNNING: "Running", STOPPED: "Paused"}
 46
 47
 48class DebugPanel(QWidget):
 49    frame_chosen = Signal(int)  # a call stack row clicked (its index)
 50
 51    def __init__(self, session: DebugSession, actions: list[QAction], breakpoint_actions: list[QAction] = (),
 52                 parent=None):
 53        """``actions``: the buttons (the Debug menu's); ``breakpoint_actions``: in
 54        the breakpoints list's right-click menu (Disable All, Remove All)."""
 55        super().__init__(parent)
 56        self.session = session
 57        self.title_icon = TitleIcon("bug")  # (as the other panels': see refresh_palette)
 58        self.title = QLabel()
 59        self.title.setTextInteractionFlags(Qt.TextSelectableByMouse)
 60        header = QHBoxLayout()
 61        header.setContentsMargins(8, 4, 6, 2)
 62        header.addWidget(self.title_icon)
 63        header.addSpacing(2)
 64        header.addWidget(self.title, 1)
 65        for action in actions:  # (the Debug menu's: Continue, Step Over…; their icons, tooltips saying which)
 66            button = QToolButton()
 67            button.setDefaultAction(action)
 68            button.setToolButtonStyle(Qt.ToolButtonIconOnly)
 69            button.setIconSize(QSize(18, 18))
 70            button.setAutoRaise(True)
 71            header.addWidget(button)
 72
 73        self.stack = QListWidget()
 74        self.stack.currentRowChanged.connect(self._frame_clicked)
 75        self.variables = QTreeWidget()
 76        self.variables.setHeaderLabels(["Name", "Value"])
 77        self.variables.setColumnWidth(0, 160)
 78        self.variables.itemExpanded.connect(self._expanded)
 79        self.console = QPlainTextEdit()
 80        self.console.setReadOnly(True)
 81        self.console.setMaximumBlockCount(5000)
 82        self.evaluate_edit = HistoryLineEdit()  # (Up, Down: the expressions evaluated before)
 83        self.evaluate_edit.setPlaceholderText("Evaluate in the frame shown (e.g. a(2:3), p%x), Enter; ↑↓ earlier ones")
 84        self.evaluate_edit.submitted.connect(self._evaluate)
 85
 86        self.breakpoints = BreakpointsList(breakpoint_actions)  # (filled by the window: see window.debugging)
 87        left = QSplitter(Qt.Vertical)
 88        left.addWidget(_titled("Call Stack", self.stack))
 89        left.addWidget(_titled("Breakpoints", self.breakpoints))
 90        middle = _titled("Variables", self.variables)
 91        console_box = QWidget()
 92        console_layout = QVBoxLayout(console_box)
 93        console_layout.setContentsMargins(0, 0, 0, 0)
 94        console_layout.setSpacing(2)
 95        console_layout.addWidget(_titled("Console", self.console), 1)
 96        console_layout.addWidget(self.evaluate_edit)
 97        split = QSplitter(Qt.Horizontal)
 98        split.addWidget(left)
 99        split.addWidget(middle)
100        split.addWidget(console_box)
101        split.setStretchFactor(0, 2)
102        split.setStretchFactor(1, 3)
103        split.setStretchFactor(2, 3)
104        layout = QVBoxLayout(self)
105        layout.setContentsMargins(0, 0, 0, 0)
106        layout.setSpacing(2)
107        layout.addLayout(header)
108        layout.addWidget(split, 1)
109
110        session.state_changed.connect(self._state_changed)
111        session.stopped_at.connect(self._stopped)
112        session.scopes_ready.connect(self._scopes)
113        session.output.connect(self.append)
114        session.ended.connect(lambda why: self.append("console", f"[{why}]\n"))
115        self._state_changed(session.state)
116        self.refresh_palette()
117
118    def refresh_palette(self):
119        """The icon in the theme's text color (again when the theme changes)."""
120        self.title_icon.draw(PALETTES["light" if app_is_light() else "dark"]["text"], self.title)
121
122    # ---------------------------------------------------------- state
123    def _state_changed(self, state: str):
124        adapter = self.session.adapter
125        debugger = f" · {adapter.title}" if adapter is not None and state != IDLE else ""
126        self.title.setText(f"<b>{STATE_TEXT.get(state, state)}</b>{debugger}")
127        self.evaluate_edit.setEnabled(state == STOPPED)
128        if state != STOPPED:
129            self.stack.clear()
130            self.variables.clear()
131
132    def begin(self, what: str):
133        """A new session (``what``: the program): the console cleared."""
134        self.console.clear()
135        self.append("console", f"[debugging {what}]\n")
136
137    def append(self, category: str, text: str):
138        cursor = self.console.textCursor()
139        cursor.movePosition(QTextCursor.End)
140        cursor.insertText(text)
141        self.console.setTextCursor(cursor)
142        self.console.ensureCursorVisible()
143
144    # ---------------------------------------------------------- the stack
145    def _stopped(self, frames: list[Frame], _why: str):
146        self.stack.blockSignals(True)
147        self.stack.clear()
148        for frame in frames:
149            where = f"{frame.path.rsplit('/', 1)[-1]}:{frame.line}" if frame.path else "(no source)"
150            item = QListWidgetItem(f"{frame_name(frame.name)}  ·  {where}")
151            item.setToolTip(f"{frame.path}:{frame.line}" if frame.path else frame.name)
152            if not frame.path:
153                item.setForeground(self.palette().placeholderText())
154            self.stack.addItem(item)
155        self.stack.setCurrentRow(self.session.frame_index)
156        self.stack.blockSignals(False)
157
158    def _frame_clicked(self, row: int):
159        if row >= 0:
160            self.frame_chosen.emit(row)
161
162    # ---------------------------------------------------------- the variables
163    def _scopes(self, _index: int, scopes: list[Scope]):
164        self.variables.clear()
165        for number, scope in enumerate(scopes):
166            item = QTreeWidgetItem([scope.name, ""])
167            item.setData(0, REFERENCE_ROLE, scope.reference)
168            font = item.font(0)
169            font.setBold(True)
170            item.setFont(0, font)
171            self.variables.addTopLevelItem(item)
172            item.addChild(QTreeWidgetItem([PLACEHOLDER, ""]))
173            if number == 0 or not scope.expensive and scope.name.lower() in ("locals", "arguments"):
174                item.setExpanded(True)
175
176    def _expanded(self, item: QTreeWidgetItem):
177        if item.data(0, LOADED_ROLE):
178            return
179        item.setData(0, LOADED_ROLE, True)
180        reference = int(item.data(0, REFERENCE_ROLE) or 0)
181        self.session.variables(reference, lambda variables, more, it=item: self._fill(it, variables, more))
182
183    def _fill(self, item: QTreeWidgetItem, variables: list[Variable], more: bool):
184        try:
185            item.takeChildren()
186        except RuntimeError:
187            return  # (cleared meanwhile: the program moved on)
188        for variable in variables:
189            child = QTreeWidgetItem([variable.name, variable.value])
190            child.setToolTip(1, variable.value)
191            if variable.type:
192                child.setToolTip(0, variable.type)
193            child.setData(0, REFERENCE_ROLE, variable.reference)
194            if variable.reference:
195                child.addChild(QTreeWidgetItem([PLACEHOLDER, ""]))
196            item.addChild(child)
197        if more:
198            item.addChild(QTreeWidgetItem(["…", "more: evaluate a section, e.g. a(1001:1100)"]))
199        if not variables:
200            item.addChild(QTreeWidgetItem(["(none)", ""]))
201
202    def variable_rows(self) -> list[tuple[str, str]]:
203        """(name, value) of the variables shown, depth first (for the tests)."""
204        rows = []
205
206        def visit(item: QTreeWidgetItem):
207            for i in range(item.childCount()):
208                child = item.child(i)
209                if child.text(0) != PLACEHOLDER:
210                    rows.append((child.text(0), child.text(1)))
211                    visit(child)
212
213        for i in range(self.variables.topLevelItemCount()):
214            visit(self.variables.topLevelItem(i))
215        return rows
216
217    # ---------------------------------------------------------- evaluate
218    def _evaluate(self, expression: str):
219        self.append("console", f"> {expression}\n")
220        self.session.evaluate(expression, lambda ok, text, _ref: self.append("console", f"{text}\n"))
221
222
223def frame_name(name: str) -> str:
224    """A frame's procedure as a Fortran programmer knows it (gfortran calls
225    the main program MAIN__)."""
226    return "main program" if name == "MAIN__" else name
227
228
229def _titled(title: str, widget: QWidget) -> QWidget:
230    box = QWidget()
231    layout = QVBoxLayout(box)
232    layout.setContentsMargins(0, 0, 0, 0)
233    layout.setSpacing(0)
234    label = QLabel(title)
235    label.setContentsMargins(6, 2, 6, 2)
236    font = label.font()
237    font.setBold(True)
238    label.setFont(font)
239    layout.addWidget(label)
240    layout.addWidget(widget, 1)
241    return box
REFERENCE_ROLE = 257
LOADED_ROLE = 258
PLACEHOLDER = '…'
STATE_TEXT = {'idle': 'Not debugging', 'starting': 'Starting…', 'running': 'Running', 'stopped': 'Paused'}
class DebugPanel(PySide6.QtWidgets.QWidget):
 49class DebugPanel(QWidget):
 50    frame_chosen = Signal(int)  # a call stack row clicked (its index)
 51
 52    def __init__(self, session: DebugSession, actions: list[QAction], breakpoint_actions: list[QAction] = (),
 53                 parent=None):
 54        """``actions``: the buttons (the Debug menu's); ``breakpoint_actions``: in
 55        the breakpoints list's right-click menu (Disable All, Remove All)."""
 56        super().__init__(parent)
 57        self.session = session
 58        self.title_icon = TitleIcon("bug")  # (as the other panels': see refresh_palette)
 59        self.title = QLabel()
 60        self.title.setTextInteractionFlags(Qt.TextSelectableByMouse)
 61        header = QHBoxLayout()
 62        header.setContentsMargins(8, 4, 6, 2)
 63        header.addWidget(self.title_icon)
 64        header.addSpacing(2)
 65        header.addWidget(self.title, 1)
 66        for action in actions:  # (the Debug menu's: Continue, Step Over…; their icons, tooltips saying which)
 67            button = QToolButton()
 68            button.setDefaultAction(action)
 69            button.setToolButtonStyle(Qt.ToolButtonIconOnly)
 70            button.setIconSize(QSize(18, 18))
 71            button.setAutoRaise(True)
 72            header.addWidget(button)
 73
 74        self.stack = QListWidget()
 75        self.stack.currentRowChanged.connect(self._frame_clicked)
 76        self.variables = QTreeWidget()
 77        self.variables.setHeaderLabels(["Name", "Value"])
 78        self.variables.setColumnWidth(0, 160)
 79        self.variables.itemExpanded.connect(self._expanded)
 80        self.console = QPlainTextEdit()
 81        self.console.setReadOnly(True)
 82        self.console.setMaximumBlockCount(5000)
 83        self.evaluate_edit = HistoryLineEdit()  # (Up, Down: the expressions evaluated before)
 84        self.evaluate_edit.setPlaceholderText("Evaluate in the frame shown (e.g. a(2:3), p%x), Enter; ↑↓ earlier ones")
 85        self.evaluate_edit.submitted.connect(self._evaluate)
 86
 87        self.breakpoints = BreakpointsList(breakpoint_actions)  # (filled by the window: see window.debugging)
 88        left = QSplitter(Qt.Vertical)
 89        left.addWidget(_titled("Call Stack", self.stack))
 90        left.addWidget(_titled("Breakpoints", self.breakpoints))
 91        middle = _titled("Variables", self.variables)
 92        console_box = QWidget()
 93        console_layout = QVBoxLayout(console_box)
 94        console_layout.setContentsMargins(0, 0, 0, 0)
 95        console_layout.setSpacing(2)
 96        console_layout.addWidget(_titled("Console", self.console), 1)
 97        console_layout.addWidget(self.evaluate_edit)
 98        split = QSplitter(Qt.Horizontal)
 99        split.addWidget(left)
100        split.addWidget(middle)
101        split.addWidget(console_box)
102        split.setStretchFactor(0, 2)
103        split.setStretchFactor(1, 3)
104        split.setStretchFactor(2, 3)
105        layout = QVBoxLayout(self)
106        layout.setContentsMargins(0, 0, 0, 0)
107        layout.setSpacing(2)
108        layout.addLayout(header)
109        layout.addWidget(split, 1)
110
111        session.state_changed.connect(self._state_changed)
112        session.stopped_at.connect(self._stopped)
113        session.scopes_ready.connect(self._scopes)
114        session.output.connect(self.append)
115        session.ended.connect(lambda why: self.append("console", f"[{why}]\n"))
116        self._state_changed(session.state)
117        self.refresh_palette()
118
119    def refresh_palette(self):
120        """The icon in the theme's text color (again when the theme changes)."""
121        self.title_icon.draw(PALETTES["light" if app_is_light() else "dark"]["text"], self.title)
122
123    # ---------------------------------------------------------- state
124    def _state_changed(self, state: str):
125        adapter = self.session.adapter
126        debugger = f" · {adapter.title}" if adapter is not None and state != IDLE else ""
127        self.title.setText(f"<b>{STATE_TEXT.get(state, state)}</b>{debugger}")
128        self.evaluate_edit.setEnabled(state == STOPPED)
129        if state != STOPPED:
130            self.stack.clear()
131            self.variables.clear()
132
133    def begin(self, what: str):
134        """A new session (``what``: the program): the console cleared."""
135        self.console.clear()
136        self.append("console", f"[debugging {what}]\n")
137
138    def append(self, category: str, text: str):
139        cursor = self.console.textCursor()
140        cursor.movePosition(QTextCursor.End)
141        cursor.insertText(text)
142        self.console.setTextCursor(cursor)
143        self.console.ensureCursorVisible()
144
145    # ---------------------------------------------------------- the stack
146    def _stopped(self, frames: list[Frame], _why: str):
147        self.stack.blockSignals(True)
148        self.stack.clear()
149        for frame in frames:
150            where = f"{frame.path.rsplit('/', 1)[-1]}:{frame.line}" if frame.path else "(no source)"
151            item = QListWidgetItem(f"{frame_name(frame.name)}  ·  {where}")
152            item.setToolTip(f"{frame.path}:{frame.line}" if frame.path else frame.name)
153            if not frame.path:
154                item.setForeground(self.palette().placeholderText())
155            self.stack.addItem(item)
156        self.stack.setCurrentRow(self.session.frame_index)
157        self.stack.blockSignals(False)
158
159    def _frame_clicked(self, row: int):
160        if row >= 0:
161            self.frame_chosen.emit(row)
162
163    # ---------------------------------------------------------- the variables
164    def _scopes(self, _index: int, scopes: list[Scope]):
165        self.variables.clear()
166        for number, scope in enumerate(scopes):
167            item = QTreeWidgetItem([scope.name, ""])
168            item.setData(0, REFERENCE_ROLE, scope.reference)
169            font = item.font(0)
170            font.setBold(True)
171            item.setFont(0, font)
172            self.variables.addTopLevelItem(item)
173            item.addChild(QTreeWidgetItem([PLACEHOLDER, ""]))
174            if number == 0 or not scope.expensive and scope.name.lower() in ("locals", "arguments"):
175                item.setExpanded(True)
176
177    def _expanded(self, item: QTreeWidgetItem):
178        if item.data(0, LOADED_ROLE):
179            return
180        item.setData(0, LOADED_ROLE, True)
181        reference = int(item.data(0, REFERENCE_ROLE) or 0)
182        self.session.variables(reference, lambda variables, more, it=item: self._fill(it, variables, more))
183
184    def _fill(self, item: QTreeWidgetItem, variables: list[Variable], more: bool):
185        try:
186            item.takeChildren()
187        except RuntimeError:
188            return  # (cleared meanwhile: the program moved on)
189        for variable in variables:
190            child = QTreeWidgetItem([variable.name, variable.value])
191            child.setToolTip(1, variable.value)
192            if variable.type:
193                child.setToolTip(0, variable.type)
194            child.setData(0, REFERENCE_ROLE, variable.reference)
195            if variable.reference:
196                child.addChild(QTreeWidgetItem([PLACEHOLDER, ""]))
197            item.addChild(child)
198        if more:
199            item.addChild(QTreeWidgetItem(["…", "more: evaluate a section, e.g. a(1001:1100)"]))
200        if not variables:
201            item.addChild(QTreeWidgetItem(["(none)", ""]))
202
203    def variable_rows(self) -> list[tuple[str, str]]:
204        """(name, value) of the variables shown, depth first (for the tests)."""
205        rows = []
206
207        def visit(item: QTreeWidgetItem):
208            for i in range(item.childCount()):
209                child = item.child(i)
210                if child.text(0) != PLACEHOLDER:
211                    rows.append((child.text(0), child.text(1)))
212                    visit(child)
213
214        for i in range(self.variables.topLevelItemCount()):
215            visit(self.variables.topLevelItem(i))
216        return rows
217
218    # ---------------------------------------------------------- evaluate
219    def _evaluate(self, expression: str):
220        self.append("console", f"> {expression}\n")
221        self.session.evaluate(expression, lambda ok, text, _ref: self.append("console", f"{text}\n"))

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

DebugPanel( session: vibefpmgui.debug_session.DebugSession, actions: list[PySide6.QtGui.QAction], breakpoint_actions: list[PySide6.QtGui.QAction] = (), parent=None)
 52    def __init__(self, session: DebugSession, actions: list[QAction], breakpoint_actions: list[QAction] = (),
 53                 parent=None):
 54        """``actions``: the buttons (the Debug menu's); ``breakpoint_actions``: in
 55        the breakpoints list's right-click menu (Disable All, Remove All)."""
 56        super().__init__(parent)
 57        self.session = session
 58        self.title_icon = TitleIcon("bug")  # (as the other panels': see refresh_palette)
 59        self.title = QLabel()
 60        self.title.setTextInteractionFlags(Qt.TextSelectableByMouse)
 61        header = QHBoxLayout()
 62        header.setContentsMargins(8, 4, 6, 2)
 63        header.addWidget(self.title_icon)
 64        header.addSpacing(2)
 65        header.addWidget(self.title, 1)
 66        for action in actions:  # (the Debug menu's: Continue, Step Over…; their icons, tooltips saying which)
 67            button = QToolButton()
 68            button.setDefaultAction(action)
 69            button.setToolButtonStyle(Qt.ToolButtonIconOnly)
 70            button.setIconSize(QSize(18, 18))
 71            button.setAutoRaise(True)
 72            header.addWidget(button)
 73
 74        self.stack = QListWidget()
 75        self.stack.currentRowChanged.connect(self._frame_clicked)
 76        self.variables = QTreeWidget()
 77        self.variables.setHeaderLabels(["Name", "Value"])
 78        self.variables.setColumnWidth(0, 160)
 79        self.variables.itemExpanded.connect(self._expanded)
 80        self.console = QPlainTextEdit()
 81        self.console.setReadOnly(True)
 82        self.console.setMaximumBlockCount(5000)
 83        self.evaluate_edit = HistoryLineEdit()  # (Up, Down: the expressions evaluated before)
 84        self.evaluate_edit.setPlaceholderText("Evaluate in the frame shown (e.g. a(2:3), p%x), Enter; ↑↓ earlier ones")
 85        self.evaluate_edit.submitted.connect(self._evaluate)
 86
 87        self.breakpoints = BreakpointsList(breakpoint_actions)  # (filled by the window: see window.debugging)
 88        left = QSplitter(Qt.Vertical)
 89        left.addWidget(_titled("Call Stack", self.stack))
 90        left.addWidget(_titled("Breakpoints", self.breakpoints))
 91        middle = _titled("Variables", self.variables)
 92        console_box = QWidget()
 93        console_layout = QVBoxLayout(console_box)
 94        console_layout.setContentsMargins(0, 0, 0, 0)
 95        console_layout.setSpacing(2)
 96        console_layout.addWidget(_titled("Console", self.console), 1)
 97        console_layout.addWidget(self.evaluate_edit)
 98        split = QSplitter(Qt.Horizontal)
 99        split.addWidget(left)
100        split.addWidget(middle)
101        split.addWidget(console_box)
102        split.setStretchFactor(0, 2)
103        split.setStretchFactor(1, 3)
104        split.setStretchFactor(2, 3)
105        layout = QVBoxLayout(self)
106        layout.setContentsMargins(0, 0, 0, 0)
107        layout.setSpacing(2)
108        layout.addLayout(header)
109        layout.addWidget(split, 1)
110
111        session.state_changed.connect(self._state_changed)
112        session.stopped_at.connect(self._stopped)
113        session.scopes_ready.connect(self._scopes)
114        session.output.connect(self.append)
115        session.ended.connect(lambda why: self.append("console", f"[{why}]\n"))
116        self._state_changed(session.state)
117        self.refresh_palette()

actions: the buttons (the Debug menu's); breakpoint_actions: in the breakpoints list's right-click menu (Disable All, Remove All).

def frame_chosen(unknown):

The type of the None singleton.

session
title_icon
title
stack
variables
console
evaluate_edit
breakpoints
def refresh_palette(self):
119    def refresh_palette(self):
120        """The icon in the theme's text color (again when the theme changes)."""
121        self.title_icon.draw(PALETTES["light" if app_is_light() else "dark"]["text"], self.title)

The icon in the theme's text color (again when the theme changes).

def begin(self, what: str):
133    def begin(self, what: str):
134        """A new session (``what``: the program): the console cleared."""
135        self.console.clear()
136        self.append("console", f"[debugging {what}]\n")

A new session (what: the program): the console cleared.

def append(self, category: str, text: str):
138    def append(self, category: str, text: str):
139        cursor = self.console.textCursor()
140        cursor.movePosition(QTextCursor.End)
141        cursor.insertText(text)
142        self.console.setTextCursor(cursor)
143        self.console.ensureCursorVisible()
def variable_rows(self) -> list[tuple[str, str]]:
203    def variable_rows(self) -> list[tuple[str, str]]:
204        """(name, value) of the variables shown, depth first (for the tests)."""
205        rows = []
206
207        def visit(item: QTreeWidgetItem):
208            for i in range(item.childCount()):
209                child = item.child(i)
210                if child.text(0) != PLACEHOLDER:
211                    rows.append((child.text(0), child.text(1)))
212                    visit(child)
213
214        for i in range(self.variables.topLevelItemCount()):
215            visit(self.variables.topLevelItem(i))
216        return rows

(name, value) of the variables shown, depth first (for the tests).

staticMetaObject = PySide6.QtCore.QMetaObject("DebugPanel" inherits "QWidget": Methods: #33 type=Signal, signature=frame_chosen(int), parameters=int )
def frame_name(name: str) -> str:
224def frame_name(name: str) -> str:
225    """A frame's procedure as a Fortran programmer knows it (gfortran calls
226    the main program MAIN__)."""
227    return "main program" if name == "MAIN__" else name

A frame's procedure as a Fortran programmer knows it (gfortran calls the main program MAIN__).