easycoder 251215.2__py2.py3-none-any.whl → 260111.1__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- easycoder/__init__.py +4 -3
- easycoder/debugger/ec_dbg_value_display copy.py +1 -1
- easycoder/debugger/ec_dbg_value_display.py +12 -11
- easycoder/debugger/ec_dbg_watchlist.py +146 -12
- easycoder/debugger/ec_debug.py +85 -8
- easycoder/ec_classes.py +228 -25
- easycoder/ec_compiler.py +29 -8
- easycoder/ec_core.py +364 -242
- easycoder/ec_gclasses.py +20 -9
- easycoder/ec_graphics.py +42 -26
- easycoder/ec_handler.py +4 -3
- easycoder/ec_mqtt.py +248 -0
- easycoder/ec_program.py +63 -28
- easycoder/ec_psutil.py +1 -1
- easycoder/ec_value.py +57 -36
- easycoder/pre/README.md +3 -0
- easycoder/pre/__init__.py +17 -0
- easycoder/pre/debugger/__init__.py +5 -0
- easycoder/pre/debugger/ec_dbg_value_display copy.py +195 -0
- easycoder/pre/debugger/ec_dbg_value_display.py +24 -0
- easycoder/pre/debugger/ec_dbg_watch_list copy.py +219 -0
- easycoder/pre/debugger/ec_dbg_watchlist.py +293 -0
- easycoder/pre/debugger/ec_debug.py +1014 -0
- easycoder/pre/ec_border.py +67 -0
- easycoder/pre/ec_classes.py +470 -0
- easycoder/pre/ec_compiler.py +291 -0
- easycoder/pre/ec_condition.py +27 -0
- easycoder/pre/ec_core.py +2772 -0
- easycoder/pre/ec_gclasses.py +230 -0
- easycoder/pre/ec_graphics.py +1682 -0
- easycoder/pre/ec_handler.py +79 -0
- easycoder/pre/ec_keyboard.py +439 -0
- easycoder/pre/ec_program.py +557 -0
- easycoder/pre/ec_psutil.py +48 -0
- easycoder/pre/ec_timestamp.py +11 -0
- easycoder/pre/ec_value.py +124 -0
- easycoder/pre/icons/close.png +0 -0
- easycoder/pre/icons/exit.png +0 -0
- easycoder/pre/icons/run.png +0 -0
- easycoder/pre/icons/step.png +0 -0
- easycoder/pre/icons/stop.png +0 -0
- easycoder/pre/icons/tick.png +0 -0
- {easycoder-251215.2.dist-info → easycoder-260111.1.dist-info}/METADATA +1 -1
- easycoder-260111.1.dist-info/RECORD +59 -0
- easycoder-251215.2.dist-info/RECORD +0 -31
- {easycoder-251215.2.dist-info → easycoder-260111.1.dist-info}/WHEEL +0 -0
- {easycoder-251215.2.dist-info → easycoder-260111.1.dist-info}/entry_points.txt +0 -0
- {easycoder-251215.2.dist-info → easycoder-260111.1.dist-info}/licenses/LICENSE +0 -0
easycoder/__init__.py
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
'''EasyCoder for Python'''
|
|
2
2
|
|
|
3
|
+
from .ec_border import *
|
|
3
4
|
from .ec_classes import *
|
|
4
|
-
from .ec_gclasses import *
|
|
5
5
|
from .ec_compiler import *
|
|
6
6
|
from .ec_condition import *
|
|
7
7
|
from .ec_core import *
|
|
8
|
+
from .ec_gclasses import *
|
|
8
9
|
from .ec_graphics import *
|
|
9
10
|
from .ec_handler import *
|
|
10
11
|
from .ec_keyboard import *
|
|
11
|
-
from .
|
|
12
|
+
from .ec_mqtt import *
|
|
12
13
|
from .ec_program import *
|
|
13
14
|
from .ec_psutil import *
|
|
14
15
|
from .ec_timestamp import *
|
|
15
16
|
from .ec_value import *
|
|
16
17
|
|
|
17
|
-
__version__ = "
|
|
18
|
+
__version__ = "260111.1"
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
"""ValueDisplay widget for displaying variable values in the EasyCoder debugger"""
|
|
2
2
|
|
|
3
|
-
from PySide6.QtWidgets import
|
|
4
|
-
QWidget,
|
|
5
|
-
QFrame,
|
|
6
|
-
QVBoxLayout,
|
|
7
|
-
QLabel,
|
|
8
|
-
QScrollArea,
|
|
9
|
-
)
|
|
10
|
-
from PySide6.QtCore import Qt
|
|
3
|
+
from PySide6.QtWidgets import QLabel
|
|
11
4
|
|
|
12
5
|
|
|
13
6
|
class ValueDisplay(QLabel):
|
|
@@ -15,9 +8,17 @@ class ValueDisplay(QLabel):
|
|
|
15
8
|
|
|
16
9
|
def __init__(self, parent=None):
|
|
17
10
|
super().__init__(parent)
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def render_text(program, symbol_name):
|
|
20
14
|
record = program.getVariable(symbol_name)
|
|
21
15
|
value = program.textify(record)
|
|
22
|
-
|
|
16
|
+
return None if value is None else str(value)
|
|
17
|
+
|
|
18
|
+
def setValue(self, program, symbol_name):
|
|
19
|
+
try:
|
|
20
|
+
rendered = self.render_text(program, symbol_name)
|
|
21
|
+
except Exception as exc:
|
|
22
|
+
rendered = f"<error: {exc}>"
|
|
23
|
+
self.setText(rendered if rendered is not None else "")
|
|
23
24
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""WatchListWidget showing variables on a single line with scrollable values."""
|
|
2
2
|
|
|
3
3
|
import bisect
|
|
4
|
+
import json
|
|
4
5
|
|
|
5
6
|
from PySide6.QtWidgets import (
|
|
6
7
|
QWidget,
|
|
@@ -10,8 +11,12 @@ from PySide6.QtWidgets import (
|
|
|
10
11
|
QPushButton,
|
|
11
12
|
QSizePolicy,
|
|
12
13
|
QScrollArea,
|
|
14
|
+
QPlainTextEdit,
|
|
15
|
+
QFrame,
|
|
16
|
+
QSplitter,
|
|
13
17
|
)
|
|
14
18
|
from PySide6.QtCore import Qt
|
|
19
|
+
from easycoder.ec_classes import ECVariable, ECDictionary, ECList, ECValue
|
|
15
20
|
from .ec_dbg_value_display import ValueDisplay
|
|
16
21
|
|
|
17
22
|
|
|
@@ -25,9 +30,20 @@ class WatchListWidget(QWidget):
|
|
|
25
30
|
self._variable_set: set[str] = set()
|
|
26
31
|
self._order: list[str] = []
|
|
27
32
|
self._placeholder: QLabel | None = None
|
|
33
|
+
self._expanded_name: str | None = None
|
|
28
34
|
|
|
35
|
+
main_layout = QVBoxLayout(self)
|
|
36
|
+
main_layout.setContentsMargins(0, 0, 0, 0)
|
|
37
|
+
main_layout.setSpacing(6)
|
|
38
|
+
|
|
39
|
+
self.splitter = QSplitter(Qt.Orientation.Vertical, self)
|
|
40
|
+
self.splitter.setHandleWidth(6)
|
|
41
|
+
self.splitter.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
|
42
|
+
|
|
43
|
+
list_container = QWidget(self)
|
|
44
|
+
list_container.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
|
29
45
|
# Outer layout: scrollable labels (left) and fixed button column (right)
|
|
30
|
-
outer = QHBoxLayout(
|
|
46
|
+
outer = QHBoxLayout(list_container)
|
|
31
47
|
outer.setContentsMargins(0, 0, 0, 0)
|
|
32
48
|
outer.setSpacing(2)
|
|
33
49
|
|
|
@@ -54,8 +70,42 @@ class WatchListWidget(QWidget):
|
|
|
54
70
|
self.buttons_column.addStretch(1)
|
|
55
71
|
outer.addLayout(self.buttons_column)
|
|
56
72
|
|
|
73
|
+
self.splitter.addWidget(list_container)
|
|
74
|
+
|
|
75
|
+
self._build_expanded_panel()
|
|
76
|
+
|
|
77
|
+
main_layout.addWidget(self.splitter, 1)
|
|
78
|
+
# Give the list more space by default
|
|
79
|
+
try:
|
|
80
|
+
self.splitter.setStretchFactor(0, 3)
|
|
81
|
+
self.splitter.setStretchFactor(1, 2)
|
|
82
|
+
except Exception:
|
|
83
|
+
pass
|
|
84
|
+
|
|
57
85
|
self._show_placeholder()
|
|
58
86
|
|
|
87
|
+
def _build_expanded_panel(self):
|
|
88
|
+
self.expanded_frame = QFrame(self)
|
|
89
|
+
self.expanded_frame.setFrameShape(QFrame.Shape.StyledPanel)
|
|
90
|
+
self.expanded_frame.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
|
91
|
+
|
|
92
|
+
expanded_layout = QVBoxLayout(self.expanded_frame)
|
|
93
|
+
expanded_layout.setContentsMargins(6, 6, 6, 6)
|
|
94
|
+
expanded_layout.setSpacing(4)
|
|
95
|
+
|
|
96
|
+
self.expanded_title = QLabel("Expanded view")
|
|
97
|
+
self.expanded_title.setStyleSheet("font-weight: bold;")
|
|
98
|
+
expanded_layout.addWidget(self.expanded_title)
|
|
99
|
+
|
|
100
|
+
self.expanded_body = QPlainTextEdit(self.expanded_frame)
|
|
101
|
+
self.expanded_body.setReadOnly(True)
|
|
102
|
+
self.expanded_body.setLineWrapMode(QPlainTextEdit.LineWrapMode.WidgetWidth)
|
|
103
|
+
self.expanded_body.setStyleSheet("font-family: monospace; background-color: #fafafa;")
|
|
104
|
+
expanded_layout.addWidget(self.expanded_body)
|
|
105
|
+
|
|
106
|
+
self.splitter.addWidget(self.expanded_frame)
|
|
107
|
+
self._clear_expanded_panel()
|
|
108
|
+
|
|
59
109
|
def _show_placeholder(self):
|
|
60
110
|
if self._placeholder is not None:
|
|
61
111
|
return
|
|
@@ -90,9 +140,18 @@ class WatchListWidget(QWidget):
|
|
|
90
140
|
label.setWordWrap(False)
|
|
91
141
|
row_layout.addWidget(label, 1)
|
|
92
142
|
|
|
93
|
-
#
|
|
94
|
-
|
|
143
|
+
# Buttons live in a side container: remove (x) and expand (+)
|
|
144
|
+
button_container = QWidget(self)
|
|
145
|
+
button_row = QHBoxLayout(button_container)
|
|
146
|
+
button_row.setContentsMargins(0, 0, 0, 0)
|
|
147
|
+
button_row.setSpacing(4)
|
|
148
|
+
|
|
149
|
+
remove_btn = QPushButton("x")
|
|
95
150
|
remove_btn.setFixedWidth(22)
|
|
151
|
+
expand_btn = QPushButton("+")
|
|
152
|
+
expand_btn.setFixedWidth(22)
|
|
153
|
+
button_row.addWidget(remove_btn)
|
|
154
|
+
button_row.addWidget(expand_btn)
|
|
96
155
|
|
|
97
156
|
def on_remove():
|
|
98
157
|
try:
|
|
@@ -104,33 +163,43 @@ class WatchListWidget(QWidget):
|
|
|
104
163
|
self._order.remove(name)
|
|
105
164
|
self.content_layout.removeWidget(row_widget)
|
|
106
165
|
row_widget.deleteLater()
|
|
107
|
-
self.buttons_column.removeWidget(
|
|
108
|
-
|
|
166
|
+
self.buttons_column.removeWidget(button_container)
|
|
167
|
+
button_container.deleteLater()
|
|
109
168
|
self._rows.pop(name, None)
|
|
169
|
+
if self._expanded_name == name:
|
|
170
|
+
self._clear_expanded_panel()
|
|
110
171
|
if not self._rows:
|
|
172
|
+
self._clear_expanded_panel()
|
|
111
173
|
self._show_placeholder()
|
|
112
174
|
except Exception:
|
|
113
175
|
pass
|
|
114
176
|
|
|
115
177
|
remove_btn.clicked.connect(on_remove)
|
|
178
|
+
expand_btn.clicked.connect(lambda: self._on_expand(name))
|
|
116
179
|
|
|
117
180
|
insert_pos = bisect.bisect_left(self._order, name)
|
|
118
181
|
self._order.insert(insert_pos, name)
|
|
119
182
|
# Insert label row above stretch, keeping alphabetical order
|
|
120
183
|
self.content_layout.insertWidget(insert_pos, row_widget)
|
|
121
184
|
# Insert button above stretch on the right, same position
|
|
122
|
-
self.buttons_column.insertWidget(insert_pos,
|
|
185
|
+
self.buttons_column.insertWidget(insert_pos, button_container)
|
|
123
186
|
|
|
124
187
|
# Align button height to row height
|
|
125
188
|
row_widget.adjustSize()
|
|
126
189
|
btn_h = row_widget.sizeHint().height()
|
|
127
190
|
if btn_h > 0:
|
|
191
|
+
button_container.setFixedHeight(btn_h)
|
|
128
192
|
remove_btn.setFixedHeight(btn_h)
|
|
193
|
+
expand_btn.setFixedHeight(btn_h)
|
|
129
194
|
|
|
130
195
|
self._rows[name] = {
|
|
131
196
|
'widget': row_widget,
|
|
132
197
|
'label': label,
|
|
133
|
-
'
|
|
198
|
+
'buttons': button_container,
|
|
199
|
+
'remove_btn': remove_btn,
|
|
200
|
+
'expand_btn': expand_btn,
|
|
201
|
+
'summary': '',
|
|
202
|
+
'detail': '',
|
|
134
203
|
}
|
|
135
204
|
self._variable_set.add(name)
|
|
136
205
|
|
|
@@ -143,13 +212,18 @@ class WatchListWidget(QWidget):
|
|
|
143
212
|
row = self._rows.get(name)
|
|
144
213
|
if not row:
|
|
145
214
|
return
|
|
215
|
+
detail_text = ''
|
|
146
216
|
try:
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
217
|
+
summary_text, detail_text = self._get_value_texts(program, name)
|
|
218
|
+
row['summary'] = summary_text
|
|
219
|
+
row['detail'] = detail_text
|
|
150
220
|
except Exception as e:
|
|
151
|
-
|
|
152
|
-
|
|
221
|
+
summary_text = f"<error: {e}>"
|
|
222
|
+
row['summary'] = summary_text
|
|
223
|
+
row['detail'] = ''
|
|
224
|
+
row['label'].setText(f"{name} = {summary_text}")
|
|
225
|
+
if self._expanded_name == name:
|
|
226
|
+
self._apply_expanded_content(name, summary_text, detail_text)
|
|
153
227
|
|
|
154
228
|
def refreshVariables(self, program):
|
|
155
229
|
if not self._rows:
|
|
@@ -157,3 +231,63 @@ class WatchListWidget(QWidget):
|
|
|
157
231
|
return
|
|
158
232
|
for name in list(self._rows.keys()):
|
|
159
233
|
self._refresh_one(name, program)
|
|
234
|
+
|
|
235
|
+
def _get_value_texts(self, program, name: str):
|
|
236
|
+
summary = ValueDisplay.render_text(program, name)
|
|
237
|
+
if summary is None:
|
|
238
|
+
summary = ''
|
|
239
|
+
|
|
240
|
+
try:
|
|
241
|
+
record = program.getVariable(name)
|
|
242
|
+
obj = program.getObject(record)
|
|
243
|
+
except Exception:
|
|
244
|
+
return summary, ''
|
|
245
|
+
|
|
246
|
+
detail = ''
|
|
247
|
+
try:
|
|
248
|
+
if isinstance(obj, ECVariable):
|
|
249
|
+
detail = summary
|
|
250
|
+
elif isinstance(obj, ECDictionary):
|
|
251
|
+
raw = obj.getValue()
|
|
252
|
+
if isinstance(raw, ECValue):
|
|
253
|
+
raw = raw.getContent()
|
|
254
|
+
if isinstance(raw, dict):
|
|
255
|
+
detail = json.dumps(raw, indent=2)
|
|
256
|
+
elif isinstance(obj, ECList):
|
|
257
|
+
raw = obj.getValue()
|
|
258
|
+
if isinstance(raw, ECValue):
|
|
259
|
+
raw = raw.getContent()
|
|
260
|
+
if isinstance(raw, list):
|
|
261
|
+
detail = json.dumps(raw, indent=2)
|
|
262
|
+
except Exception:
|
|
263
|
+
pass
|
|
264
|
+
return summary, detail
|
|
265
|
+
|
|
266
|
+
def _on_expand(self, name: str):
|
|
267
|
+
if name not in self._rows:
|
|
268
|
+
return
|
|
269
|
+
try:
|
|
270
|
+
if hasattr(self.debugger, 'program'):
|
|
271
|
+
self._refresh_one(name, self.debugger.program)
|
|
272
|
+
except Exception:
|
|
273
|
+
pass
|
|
274
|
+
|
|
275
|
+
row = self._rows.get(name)
|
|
276
|
+
if not row:
|
|
277
|
+
return
|
|
278
|
+
self._expanded_name = name
|
|
279
|
+
self._apply_expanded_content(name, row.get('summary', ''), row.get('detail', ''))
|
|
280
|
+
|
|
281
|
+
def _apply_expanded_content(self, name: str, summary: str, detail: str):
|
|
282
|
+
body = detail if detail else summary
|
|
283
|
+
if body is None:
|
|
284
|
+
body = ''
|
|
285
|
+
if not body:
|
|
286
|
+
body = 'No content available for this variable.'
|
|
287
|
+
self.expanded_title.setText(f"Expanded view: {name}")
|
|
288
|
+
self.expanded_body.setPlainText(body)
|
|
289
|
+
|
|
290
|
+
def _clear_expanded_panel(self):
|
|
291
|
+
self._expanded_name = None
|
|
292
|
+
self.expanded_title.setText("Expanded view")
|
|
293
|
+
self.expanded_body.setPlainText("Select a watched variable and click + to view its contents.")
|
easycoder/debugger/ec_debug.py
CHANGED
|
@@ -43,6 +43,14 @@ class Debugger(QMainWindow):
|
|
|
43
43
|
if not text:
|
|
44
44
|
return
|
|
45
45
|
|
|
46
|
+
# Echo all output to original stdout with proper line breaks
|
|
47
|
+
try:
|
|
48
|
+
if self.debugger._orig_stdout:
|
|
49
|
+
self.debugger._orig_stdout.write(text)
|
|
50
|
+
self.debugger._orig_stdout.flush()
|
|
51
|
+
except Exception:
|
|
52
|
+
pass
|
|
53
|
+
|
|
46
54
|
# Check if this looks like an error message - if so, also write to original stderr
|
|
47
55
|
if any(err_marker in text for err_marker in ['Error', 'Traceback', 'Exception']):
|
|
48
56
|
try:
|
|
@@ -418,6 +426,9 @@ class Debugger(QMainWindow):
|
|
|
418
426
|
self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint)
|
|
419
427
|
self.stopped = True
|
|
420
428
|
self.skip_next_breakpoint = False # Flag to skip breakpoint check on resume
|
|
429
|
+
self.saved_queue = [] # Save queue state when stopped to preserve forked threads
|
|
430
|
+
self._highlighted: set[int] = set()
|
|
431
|
+
self.step_from_line: int | None = None # Track source line when stepping
|
|
421
432
|
|
|
422
433
|
# try to load saved geometry from ~/.ecdebug.conf
|
|
423
434
|
cfg_path = os.path.join(os.path.expanduser("~"), ".ecdebug.conf")
|
|
@@ -467,9 +478,10 @@ class Debugger(QMainWindow):
|
|
|
467
478
|
left.setFrameShape(QFrame.Shape.StyledPanel)
|
|
468
479
|
left_layout = QVBoxLayout(left)
|
|
469
480
|
left_layout.setContentsMargins(8, 8, 8, 8)
|
|
481
|
+
left_layout.setSpacing(0)
|
|
470
482
|
self.leftColumn = self.MainLeftColumn(self)
|
|
471
|
-
|
|
472
|
-
left_layout.
|
|
483
|
+
self.leftColumn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
|
484
|
+
left_layout.addWidget(self.leftColumn, 1)
|
|
473
485
|
|
|
474
486
|
# Right pane
|
|
475
487
|
right = QFrame()
|
|
@@ -794,7 +806,7 @@ class Debugger(QMainWindow):
|
|
|
794
806
|
###########################################################################
|
|
795
807
|
# Set the background color of one line of the script
|
|
796
808
|
def setBackground(self, lino, color):
|
|
797
|
-
# Set the background color of the given line
|
|
809
|
+
# Set the background color of the given line and track highlighted lines
|
|
798
810
|
if lino < 0 or lino >= len(self.scriptLines):
|
|
799
811
|
return
|
|
800
812
|
lineSpec = self.scriptLines[lino]
|
|
@@ -803,8 +815,16 @@ class Debugger(QMainWindow):
|
|
|
803
815
|
return
|
|
804
816
|
if color == 'none':
|
|
805
817
|
panel.setStyleSheet("")
|
|
818
|
+
self._highlighted.discard(lino)
|
|
806
819
|
else:
|
|
807
820
|
panel.setStyleSheet(f"background-color: {color};")
|
|
821
|
+
self._highlighted.add(lino)
|
|
822
|
+
|
|
823
|
+
def _clearHighlights(self):
|
|
824
|
+
# Remove highlighting from all previously highlighted lines
|
|
825
|
+
for lino in list(self._highlighted):
|
|
826
|
+
self.setBackground(lino, 'none')
|
|
827
|
+
self._highlighted.clear()
|
|
808
828
|
|
|
809
829
|
###########################################################################
|
|
810
830
|
# Here before each instruction is run
|
|
@@ -832,22 +852,32 @@ class Debugger(QMainWindow):
|
|
|
832
852
|
if is_first_command:
|
|
833
853
|
should_halt = True
|
|
834
854
|
self.stopped = True
|
|
855
|
+
self.step_from_line = None
|
|
835
856
|
print(f"Program ready at line {lino + 1}")
|
|
836
857
|
# If we're in stopped (step) mode, halt after each command
|
|
837
858
|
elif self.stopped:
|
|
838
|
-
|
|
859
|
+
# If stepping, only halt when we reach a different source line
|
|
860
|
+
if self.step_from_line is not None:
|
|
861
|
+
if lino != self.step_from_line:
|
|
862
|
+
should_halt = True
|
|
863
|
+
self.step_from_line = None
|
|
864
|
+
else:
|
|
865
|
+
should_halt = True
|
|
839
866
|
# If there's a breakpoint on this line, halt
|
|
840
867
|
elif bp:
|
|
841
868
|
print(f"Hit breakpoint at line {lino + 1}")
|
|
842
869
|
self.stopped = True
|
|
843
870
|
should_halt = True
|
|
844
871
|
|
|
845
|
-
# If halting, update the UI
|
|
872
|
+
# If halting, update the UI and save queue state
|
|
846
873
|
if should_halt:
|
|
847
874
|
self.scrollTo(lino)
|
|
848
|
-
self.
|
|
875
|
+
self._clearHighlights()
|
|
876
|
+
self.setBackground(lino, 'Yellow')
|
|
849
877
|
# Refresh variable values when halted
|
|
850
878
|
self.refreshVariables()
|
|
879
|
+
# Save the current queue state to preserve forked threads
|
|
880
|
+
self._saveQueueState()
|
|
851
881
|
|
|
852
882
|
return should_halt
|
|
853
883
|
|
|
@@ -859,6 +889,28 @@ class Debugger(QMainWindow):
|
|
|
859
889
|
except Exception as ex:
|
|
860
890
|
print(f"Error refreshing variables: {ex}")
|
|
861
891
|
|
|
892
|
+
def _saveQueueState(self):
|
|
893
|
+
"""Save the current global queue state (preserves forked threads)"""
|
|
894
|
+
try:
|
|
895
|
+
# Import the module to access the global queue
|
|
896
|
+
from easycoder import ec_program
|
|
897
|
+
# Save a copy of the queue
|
|
898
|
+
self.saved_queue = list(ec_program.queue)
|
|
899
|
+
except Exception as ex:
|
|
900
|
+
print(f"Error saving queue state: {ex}")
|
|
901
|
+
|
|
902
|
+
def _restoreQueueState(self):
|
|
903
|
+
"""Restore the saved queue state (resume all forked threads)"""
|
|
904
|
+
try:
|
|
905
|
+
# Import here to avoid circular dependency
|
|
906
|
+
from easycoder import ec_program
|
|
907
|
+
# Restore the queue from saved state
|
|
908
|
+
if self.saved_queue:
|
|
909
|
+
ec_program.queue.clear()
|
|
910
|
+
ec_program.queue.extend(self.saved_queue)
|
|
911
|
+
except Exception as ex:
|
|
912
|
+
print(f"Error restoring queue state: {ex}")
|
|
913
|
+
|
|
862
914
|
def doRun(self):
|
|
863
915
|
"""Resume free-running execution from current PC"""
|
|
864
916
|
command = self.program.code[self.pc]
|
|
@@ -870,12 +922,18 @@ class Debugger(QMainWindow):
|
|
|
870
922
|
|
|
871
923
|
# Switch to free-running mode
|
|
872
924
|
self.stopped = False
|
|
925
|
+
self.step_from_line = None
|
|
873
926
|
|
|
874
927
|
# Skip the breakpoint check for the current instruction (the one we're resuming from)
|
|
875
928
|
self.skip_next_breakpoint = True
|
|
876
929
|
|
|
877
|
-
#
|
|
930
|
+
# Restore the saved queue state to resume all forked threads
|
|
931
|
+
self._restoreQueueState()
|
|
932
|
+
|
|
933
|
+
# Enqueue the current thread, then flush immediately
|
|
878
934
|
self.program.run(self.pc)
|
|
935
|
+
from easycoder.ec_program import flush
|
|
936
|
+
flush()
|
|
879
937
|
|
|
880
938
|
def doStep(self):
|
|
881
939
|
"""Execute one instruction and halt again"""
|
|
@@ -887,14 +945,33 @@ class Debugger(QMainWindow):
|
|
|
887
945
|
|
|
888
946
|
# Stay in stopped mode (will halt after next instruction)
|
|
889
947
|
self.stopped = True
|
|
948
|
+
# Remember the line we're stepping from - don't halt until we reach a different line
|
|
949
|
+
self.step_from_line = lino
|
|
890
950
|
|
|
891
951
|
# Skip the breakpoint check for the current instruction (the one we're stepping from)
|
|
892
952
|
self.skip_next_breakpoint = True
|
|
893
953
|
|
|
894
|
-
#
|
|
954
|
+
# Restore the saved queue state to resume all forked threads
|
|
955
|
+
self._restoreQueueState()
|
|
956
|
+
|
|
957
|
+
# Enqueue the current thread, then flush a single cycle
|
|
895
958
|
self.program.run(self.pc)
|
|
959
|
+
from easycoder.ec_program import flush
|
|
960
|
+
flush()
|
|
896
961
|
|
|
897
962
|
def doStop(self):
|
|
963
|
+
try:
|
|
964
|
+
lino = self.program.code[self.pc].get('lino', 0) + 1
|
|
965
|
+
print(f"Stopped by user at line {lino}")
|
|
966
|
+
except Exception:
|
|
967
|
+
print("Stopped by user")
|
|
968
|
+
# Clear all previous highlights and mark the current line
|
|
969
|
+
try:
|
|
970
|
+
self._clearHighlights()
|
|
971
|
+
current_lino = self.program.code[self.pc].get('lino', 0)
|
|
972
|
+
self.setBackground(current_lino, 'LightYellow')
|
|
973
|
+
except Exception:
|
|
974
|
+
pass
|
|
898
975
|
self.stopped = True
|
|
899
976
|
|
|
900
977
|
def doClose(self):
|