easycoder 251104.1__py2.py3-none-any.whl → 260108.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.
Potentially problematic release.
This version of easycoder might be problematic. Click here for more details.
- easycoder/__init__.py +6 -3
- easycoder/debugger/__init__.py +5 -0
- easycoder/debugger/ec_dbg_value_display copy.py +195 -0
- easycoder/debugger/ec_dbg_value_display.py +24 -0
- easycoder/debugger/ec_dbg_watch_list copy.py +219 -0
- easycoder/debugger/ec_dbg_watchlist.py +293 -0
- easycoder/debugger/ec_debug.py +1025 -0
- easycoder/ec_border.py +15 -11
- easycoder/ec_classes.py +494 -7
- easycoder/ec_compiler.py +82 -44
- easycoder/ec_condition.py +1 -1
- easycoder/ec_core.py +1043 -1089
- easycoder/ec_gclasses.py +236 -0
- easycoder/ec_graphics.py +1683 -0
- easycoder/ec_handler.py +18 -13
- easycoder/ec_keyboard.py +50 -50
- easycoder/ec_mqtt.py +249 -0
- easycoder/ec_program.py +313 -152
- easycoder/ec_psutil.py +48 -0
- easycoder/ec_timestamp.py +2 -1
- easycoder/ec_value.py +65 -47
- easycoder/icons/exit.png +0 -0
- easycoder/icons/run.png +0 -0
- easycoder/icons/step.png +0 -0
- easycoder/icons/stop.png +0 -0
- 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/{ec_pyside.py → pre/ec_graphics.py} +631 -494
- 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-251104.1.dist-info → easycoder-260108.1.dist-info}/METADATA +11 -1
- easycoder-260108.1.dist-info/RECORD +59 -0
- easycoder-251104.1.dist-info/RECORD +0 -19
- /easycoder/{close.png → icons/close.png} +0 -0
- /easycoder/{tick.png → icons/tick.png} +0 -0
- {easycoder-251104.1.dist-info → easycoder-260108.1.dist-info}/WHEEL +0 -0
- {easycoder-251104.1.dist-info → easycoder-260108.1.dist-info}/entry_points.txt +0 -0
- {easycoder-251104.1.dist-info → easycoder-260108.1.dist-info}/licenses/LICENSE +0 -0
easycoder/ec_value.py
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
from
|
|
1
|
+
from typing import Optional, List
|
|
2
|
+
from .ec_classes import ECObject, FatalError, ECValue
|
|
2
3
|
|
|
3
4
|
# Create a constant
|
|
4
5
|
def getConstant(str):
|
|
5
|
-
|
|
6
|
-
value['type'] = 'text'
|
|
7
|
-
value['content'] = str
|
|
8
|
-
return value
|
|
6
|
+
return ECValue(type=str, content=str)
|
|
9
7
|
|
|
10
8
|
class Value:
|
|
11
9
|
|
|
@@ -14,30 +12,28 @@ class Value:
|
|
|
14
12
|
self.getToken = compiler.getToken
|
|
15
13
|
self.nextToken = compiler.nextToken
|
|
16
14
|
self.peek = compiler.peek
|
|
15
|
+
self.skip = compiler.skip
|
|
17
16
|
self.tokenIs = compiler.tokenIs
|
|
18
17
|
|
|
19
|
-
def getItem(self):
|
|
18
|
+
def getItem(self) -> Optional[ECValue]:
|
|
20
19
|
token = self.getToken()
|
|
21
20
|
if not token:
|
|
22
21
|
return None
|
|
23
22
|
|
|
24
|
-
value =
|
|
23
|
+
value = ECValue()
|
|
25
24
|
|
|
26
25
|
if token == 'true':
|
|
27
|
-
value
|
|
28
|
-
value['content'] = True
|
|
26
|
+
value.setValue(bool, True)
|
|
29
27
|
return value
|
|
30
28
|
|
|
31
29
|
if token == 'false':
|
|
32
|
-
value
|
|
33
|
-
value['content'] = False
|
|
30
|
+
value.setValue(bool, False)
|
|
34
31
|
return value
|
|
35
32
|
|
|
36
33
|
# Check for a string constant
|
|
37
34
|
if token[0] == '`':
|
|
38
35
|
if token[len(token) - 1] == '`':
|
|
39
|
-
value
|
|
40
|
-
value['content'] = token[1 : len(token) - 1]
|
|
36
|
+
value.setValue(type=str, content=token[1 : len(token) - 1])
|
|
41
37
|
return value
|
|
42
38
|
FatalError(self.compiler, f'Unterminated string "{token}"')
|
|
43
39
|
return None
|
|
@@ -46,8 +42,7 @@ class Value:
|
|
|
46
42
|
if token.isnumeric() or (token[0] == '-' and token[1:].isnumeric):
|
|
47
43
|
val = eval(token)
|
|
48
44
|
if isinstance(val, int):
|
|
49
|
-
value
|
|
50
|
-
value['content'] = val
|
|
45
|
+
value.setValue(int, val)
|
|
51
46
|
return value
|
|
52
47
|
FatalError(self.compiler, f'{token} is not an integer')
|
|
53
48
|
|
|
@@ -55,51 +50,74 @@ class Value:
|
|
|
55
50
|
mark = self.compiler.getIndex()
|
|
56
51
|
for domain in self.compiler.program.getDomains():
|
|
57
52
|
item = domain.compileValue()
|
|
58
|
-
if item != None:
|
|
59
|
-
return item
|
|
53
|
+
if item != None: return item
|
|
60
54
|
self.compiler.rewindTo(mark)
|
|
61
55
|
# self.compiler.warning(f'I don\'t understand \'{token}\'')
|
|
62
56
|
return None
|
|
63
57
|
|
|
64
|
-
|
|
65
|
-
|
|
58
|
+
# Get a list of items following 'the cat of ...'
|
|
59
|
+
def getCatItems(self) -> Optional[List[ECValue]]:
|
|
60
|
+
items: List[ECValue] = []
|
|
66
61
|
item = self.getItem()
|
|
62
|
+
if item == None: return None
|
|
63
|
+
items.append(item)
|
|
64
|
+
while self.peek() in ['cat', 'and']:
|
|
65
|
+
self.nextToken()
|
|
66
|
+
self.nextToken()
|
|
67
|
+
element = self.getItem()
|
|
68
|
+
if element != None:
|
|
69
|
+
items.append(element) # pyright: ignore[reportOptionalMemberAccess]
|
|
70
|
+
return items
|
|
71
|
+
|
|
72
|
+
# Check if any domain has something to add to the value
|
|
73
|
+
def checkDomainAdditions(self, value):
|
|
74
|
+
for domain in self.compiler.program.getDomains():
|
|
75
|
+
value = domain.modifyValue(value)
|
|
76
|
+
return value
|
|
77
|
+
|
|
78
|
+
# Compile a value
|
|
79
|
+
def compileValue(self) -> Optional[ECValue]:
|
|
80
|
+
token = self.getToken()
|
|
81
|
+
# Special-case the plugin-safe full form: "the cat of ..."
|
|
82
|
+
if token == 'the' and self.peek() == 'cat':
|
|
83
|
+
self.nextToken() # move to 'cat'
|
|
84
|
+
self.skip('of')
|
|
85
|
+
self.nextToken()
|
|
86
|
+
items = self.getCatItems()
|
|
87
|
+
value = ECValue(type='cat', content=items)
|
|
88
|
+
return self.checkDomainAdditions(value)
|
|
89
|
+
|
|
90
|
+
# Otherwise, consume any leading articles before normal parsing
|
|
91
|
+
self.compiler.skipArticles()
|
|
92
|
+
token = self.getToken()
|
|
93
|
+
|
|
94
|
+
item: ECValue|None = self.getItem()
|
|
67
95
|
if item == None:
|
|
68
96
|
self.compiler.warning(f'ec_value.compileValue: Cannot get the value of "{token}"')
|
|
69
97
|
return None
|
|
98
|
+
if item.getType() == 'symbol':
|
|
99
|
+
object = self.compiler.getSymbolRecord(item.getContent())['object']
|
|
100
|
+
if not object.hasRuntimeValue(): return None
|
|
70
101
|
|
|
71
|
-
value = {}
|
|
72
102
|
if self.peek() == 'cat':
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
self.nextToken()
|
|
79
|
-
item = self.getItem()
|
|
80
|
-
if item != None:
|
|
81
|
-
value['value'].append(item)
|
|
103
|
+
self.nextToken() # consume 'cat'
|
|
104
|
+
self.nextToken()
|
|
105
|
+
items = self.getCatItems()
|
|
106
|
+
if items != None: items.insert(0, item)
|
|
107
|
+
value = ECValue(type='cat', content=items)
|
|
82
108
|
else:
|
|
83
109
|
value = item
|
|
84
110
|
|
|
85
|
-
|
|
86
|
-
for domain in self.compiler.program.getDomains():
|
|
87
|
-
value = domain.modifyValue(value)
|
|
88
|
-
|
|
89
|
-
return value
|
|
111
|
+
return self.checkDomainAdditions(value)
|
|
90
112
|
|
|
91
113
|
def compileConstant(self, token):
|
|
92
|
-
value =
|
|
93
|
-
if
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
value
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
value
|
|
101
|
-
value['content'] = token
|
|
102
|
-
return value
|
|
103
|
-
value['type'] = 'text'
|
|
104
|
-
value['content'] = token
|
|
114
|
+
value = ECValue()
|
|
115
|
+
if isinstance(token, str):
|
|
116
|
+
value.setValue(type=int, content=token)
|
|
117
|
+
elif isinstance(token, int):
|
|
118
|
+
value.setValue(type=int, content=token)
|
|
119
|
+
elif isinstance(token, float):
|
|
120
|
+
value.setValue(type=float, content=token)
|
|
121
|
+
else:
|
|
122
|
+
value.setValue(type=str, content=str(token))
|
|
105
123
|
return value
|
easycoder/icons/exit.png
ADDED
|
Binary file
|
easycoder/icons/run.png
ADDED
|
Binary file
|
easycoder/icons/step.png
ADDED
|
Binary file
|
easycoder/icons/stop.png
ADDED
|
Binary file
|
easycoder/pre/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'''EasyCoder for Python'''
|
|
2
|
+
|
|
3
|
+
from .ec_classes import *
|
|
4
|
+
from .ec_gclasses import *
|
|
5
|
+
from .ec_compiler import *
|
|
6
|
+
from .ec_condition import *
|
|
7
|
+
from .ec_core import *
|
|
8
|
+
from .ec_graphics import *
|
|
9
|
+
from .ec_handler import *
|
|
10
|
+
from .ec_keyboard import *
|
|
11
|
+
from .ec_border import *
|
|
12
|
+
from .ec_program import *
|
|
13
|
+
from .ec_psutil import *
|
|
14
|
+
from .ec_timestamp import *
|
|
15
|
+
from .ec_value import *
|
|
16
|
+
|
|
17
|
+
__version__ = "251227.1"
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""ValueDisplay widget for displaying variable values in the EasyCoder debugger"""
|
|
2
|
+
|
|
3
|
+
from PySide6.QtWidgets import (
|
|
4
|
+
QWidget,
|
|
5
|
+
QFrame,
|
|
6
|
+
QVBoxLayout,
|
|
7
|
+
QLabel,
|
|
8
|
+
QScrollArea,
|
|
9
|
+
)
|
|
10
|
+
from PySide6.QtCore import Qt
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ValueDisplay(QWidget):
|
|
14
|
+
"""Widget to display a variable value with type-appropriate formatting"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, parent=None):
|
|
17
|
+
super().__init__(parent)
|
|
18
|
+
vlayout = QVBoxLayout(self)
|
|
19
|
+
vlayout.setContentsMargins(0, 0, 0, 0)
|
|
20
|
+
vlayout.setSpacing(2)
|
|
21
|
+
|
|
22
|
+
# Main value label (always visible)
|
|
23
|
+
self.value_label = QLabel()
|
|
24
|
+
self.value_label.setStyleSheet("font-family: mono; padding: 1px 2px;")
|
|
25
|
+
self.value_label.setWordWrap(False)
|
|
26
|
+
vlayout.addWidget(self.value_label)
|
|
27
|
+
|
|
28
|
+
# Expanded details inside a scroll area (initially hidden)
|
|
29
|
+
self.details_scroll = QScrollArea()
|
|
30
|
+
self.details_scroll.setFrameShape(QFrame.Shape.NoFrame)
|
|
31
|
+
self.details_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
|
32
|
+
self.details_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
|
33
|
+
self.details_scroll.setWidgetResizable(True)
|
|
34
|
+
self.details_content = QWidget()
|
|
35
|
+
self.details_layout = QVBoxLayout(self.details_content)
|
|
36
|
+
self.details_layout.setContentsMargins(10, 0, 0, 0)
|
|
37
|
+
self.details_layout.setSpacing(1)
|
|
38
|
+
self.details_scroll.setWidget(self.details_content)
|
|
39
|
+
self.details_scroll.hide()
|
|
40
|
+
vlayout.addWidget(self.details_scroll)
|
|
41
|
+
|
|
42
|
+
self.is_expanded = False
|
|
43
|
+
self.current_value = None
|
|
44
|
+
self.max_detail_rows = 10
|
|
45
|
+
|
|
46
|
+
def setValue(self, symbol_record, program):
|
|
47
|
+
"""Update display with current value from symbol record"""
|
|
48
|
+
if not symbol_record:
|
|
49
|
+
self.value_label.setText("<not found>")
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
# Check if variable has value capability
|
|
53
|
+
symbol_object = program.getObject(symbol_record)
|
|
54
|
+
if not symbol_object.hasRuntimeValue():
|
|
55
|
+
self.value_label.setText(f"<{symbol_record.get('type', 'no-value')}>")
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
# Get the value array
|
|
59
|
+
value_array = symbol_object.getValues()
|
|
60
|
+
|
|
61
|
+
if not value_array or len(value_array) == 0:
|
|
62
|
+
self.value_label.setText("<empty>")
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
# For arrays, show summary
|
|
66
|
+
if len(value_array) > 1:
|
|
67
|
+
index = symbol_record.get('index', 0)
|
|
68
|
+
self.value_label.setText(f"[{len(value_array)} elements] @{index}")
|
|
69
|
+
|
|
70
|
+
# If expanded, show individual elements
|
|
71
|
+
if self.is_expanded:
|
|
72
|
+
self._show_array_elements(value_array, index)
|
|
73
|
+
else:
|
|
74
|
+
self._hide_details()
|
|
75
|
+
else:
|
|
76
|
+
# Single value - show it directly
|
|
77
|
+
val = program.textify(symbol_object)
|
|
78
|
+
self._show_single_value(val)
|
|
79
|
+
|
|
80
|
+
def _show_single_value(self, content):
|
|
81
|
+
"""Display a single value element"""
|
|
82
|
+
if content is None or content == {}:
|
|
83
|
+
self.value_label.setText("<none>")
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
if isinstance(content, bool):
|
|
87
|
+
self.value_label.setText(str(content))
|
|
88
|
+
elif isinstance(content, int):
|
|
89
|
+
self.value_label.setText(str(content))
|
|
90
|
+
elif isinstance(content, str):
|
|
91
|
+
# Check if it's JSON
|
|
92
|
+
if isinstance(content, str) and content.strip().startswith(('{', '[')):
|
|
93
|
+
# Likely JSON - show truncated with expand option
|
|
94
|
+
self._set_elided_text(str(content), multiplier=2.0)
|
|
95
|
+
if self.is_expanded and len(content) > 50:
|
|
96
|
+
self._show_text_details(content)
|
|
97
|
+
else:
|
|
98
|
+
# Regular string
|
|
99
|
+
text_s = str(content)
|
|
100
|
+
self._set_elided_text(text_s, multiplier=2.0)
|
|
101
|
+
if self.is_expanded:
|
|
102
|
+
self._show_text_details(text_s)
|
|
103
|
+
else:
|
|
104
|
+
self.value_label.setText(str(content))
|
|
105
|
+
|
|
106
|
+
def _show_array_elements(self, value_array, current_index):
|
|
107
|
+
"""Show expanded array elements"""
|
|
108
|
+
self.details_scroll.show()
|
|
109
|
+
# Clear existing
|
|
110
|
+
while self.details_layout.count():
|
|
111
|
+
item = self.details_layout.takeAt(0)
|
|
112
|
+
if item.widget():
|
|
113
|
+
item.widget().deleteLater()
|
|
114
|
+
|
|
115
|
+
# Show all elements with internal vertical scrolling capped to N lines
|
|
116
|
+
for i in range(len(value_array)):
|
|
117
|
+
val = value_array[i]
|
|
118
|
+
marker = '→ ' if i == current_index else ' '
|
|
119
|
+
|
|
120
|
+
if val is None or val == {}:
|
|
121
|
+
text = f"{marker}[{i}]: <none>"
|
|
122
|
+
else:
|
|
123
|
+
val_type = val.get('type', '?')
|
|
124
|
+
content = val.get('content', '')
|
|
125
|
+
if val_type == str:
|
|
126
|
+
# keep each element concise
|
|
127
|
+
s = str(content)
|
|
128
|
+
if len(s) > 120:
|
|
129
|
+
content = s[:120] + '...'
|
|
130
|
+
text = f'{marker}[{i}]: {content}'
|
|
131
|
+
|
|
132
|
+
lbl = QLabel(text)
|
|
133
|
+
lbl.setStyleSheet("font-family: mono; font-size: 9pt;")
|
|
134
|
+
self.details_layout.addWidget(lbl)
|
|
135
|
+
# Cap scroll area height to max_detail_rows
|
|
136
|
+
fm = self.fontMetrics()
|
|
137
|
+
max_h = int(self.max_detail_rows * fm.height() * 1.2)
|
|
138
|
+
self.details_scroll.setMaximumHeight(max_h)
|
|
139
|
+
|
|
140
|
+
def _show_text_details(self, text):
|
|
141
|
+
"""Show full text in details area"""
|
|
142
|
+
self.details_scroll.show()
|
|
143
|
+
# Clear existing
|
|
144
|
+
while self.details_layout.count():
|
|
145
|
+
item = self.details_layout.takeAt(0)
|
|
146
|
+
if item.widget():
|
|
147
|
+
item.widget().deleteLater()
|
|
148
|
+
|
|
149
|
+
lbl = QLabel(text)
|
|
150
|
+
lbl.setStyleSheet("font-family: mono; font-size: 9pt;")
|
|
151
|
+
lbl.setWordWrap(True)
|
|
152
|
+
self.details_layout.addWidget(lbl)
|
|
153
|
+
# Cap to max_detail_rows
|
|
154
|
+
fm = self.fontMetrics()
|
|
155
|
+
max_h = int(self.max_detail_rows * fm.height() * 1.2)
|
|
156
|
+
self.details_scroll.setMaximumHeight(max_h)
|
|
157
|
+
|
|
158
|
+
def _hide_details(self):
|
|
159
|
+
"""Hide expanded details"""
|
|
160
|
+
self.details_scroll.hide()
|
|
161
|
+
|
|
162
|
+
def toggleExpand(self):
|
|
163
|
+
"""Toggle between expanded and compact view"""
|
|
164
|
+
self.is_expanded = not self.is_expanded
|
|
165
|
+
# Caller should call setValue again to refresh display
|
|
166
|
+
|
|
167
|
+
def _approx_available_width(self) -> int:
|
|
168
|
+
try:
|
|
169
|
+
# Try to find nearest scroll area's viewport width
|
|
170
|
+
w = self
|
|
171
|
+
depth = 0
|
|
172
|
+
while w and depth < 6:
|
|
173
|
+
if isinstance(w, QScrollArea):
|
|
174
|
+
return max(200, w.viewport().width())
|
|
175
|
+
w = w.parentWidget()
|
|
176
|
+
depth += 1
|
|
177
|
+
# Fallback to our own width or a safe default
|
|
178
|
+
return max(240, self.width())
|
|
179
|
+
except Exception:
|
|
180
|
+
return 320
|
|
181
|
+
|
|
182
|
+
def _set_elided_text(self, text: str, multiplier: float = 2.0):
|
|
183
|
+
try:
|
|
184
|
+
fm = self.value_label.fontMetrics()
|
|
185
|
+
avail = int(self._approx_available_width() * multiplier)
|
|
186
|
+
# Apply quotes for display
|
|
187
|
+
quoted = f'"{text}"'
|
|
188
|
+
elided = fm.elidedText(quoted, Qt.TextElideMode.ElideRight, max(80, avail))
|
|
189
|
+
self.value_label.setText(elided)
|
|
190
|
+
except Exception:
|
|
191
|
+
# Fallback simple trim
|
|
192
|
+
s = text
|
|
193
|
+
if len(s) > 160:
|
|
194
|
+
s = s[:160] + '...'
|
|
195
|
+
self.value_label.setText(f'"{s}"')
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""ValueDisplay widget for displaying variable values in the EasyCoder debugger"""
|
|
2
|
+
|
|
3
|
+
from PySide6.QtWidgets import QLabel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ValueDisplay(QLabel):
|
|
7
|
+
"""Widget to display a variable value with type-appropriate formatting"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, parent=None):
|
|
10
|
+
super().__init__(parent)
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def render_text(program, symbol_name):
|
|
14
|
+
record = program.getVariable(symbol_name)
|
|
15
|
+
value = program.textify(record)
|
|
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 "")
|
|
24
|
+
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""WatchListWidget for managing the variable watch list in the EasyCoder debugger"""
|
|
2
|
+
|
|
3
|
+
from PySide6.QtWidgets import (
|
|
4
|
+
QWidget,
|
|
5
|
+
QFrame,
|
|
6
|
+
QHBoxLayout,
|
|
7
|
+
QVBoxLayout,
|
|
8
|
+
QGridLayout,
|
|
9
|
+
QLabel,
|
|
10
|
+
"""WatchListWidget for managing the variable watch list in the EasyCoder debugger
|
|
11
|
+
|
|
12
|
+
Simplified to show variables in single-line form: `Name = value`.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from PySide6.QtWidgets import (
|
|
16
|
+
QWidget,
|
|
17
|
+
QHBoxLayout,
|
|
18
|
+
QVBoxLayout,
|
|
19
|
+
QLabel,
|
|
20
|
+
QPushButton,
|
|
21
|
+
QSizePolicy,
|
|
22
|
+
)
|
|
23
|
+
from .ec_dbg_value_display import ValueDisplay
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class WatchListWidget(QWidget):
|
|
27
|
+
"""Simple watch list that renders each variable on a single line."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, debugger):
|
|
30
|
+
super().__init__(debugger)
|
|
31
|
+
self.debugger = debugger
|
|
32
|
+
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
|
33
|
+
|
|
34
|
+
self._rows: dict[str, dict] = {}
|
|
35
|
+
self._variable_set: set[str] = set()
|
|
36
|
+
self._placeholder: QLabel | None = None
|
|
37
|
+
|
|
38
|
+
self.layout = QVBoxLayout(self)
|
|
39
|
+
self.layout.setContentsMargins(0, 0, 0, 0)
|
|
40
|
+
self.layout.setSpacing(2)
|
|
41
|
+
|
|
42
|
+
self._show_placeholder()
|
|
43
|
+
|
|
44
|
+
# ------------------------------------------------------------------
|
|
45
|
+
def _show_placeholder(self):
|
|
46
|
+
if self._placeholder is not None:
|
|
47
|
+
return
|
|
48
|
+
self._placeholder = QLabel("No variables watched. Click + to add.")
|
|
49
|
+
self._placeholder.setStyleSheet("color: #666; font-style: italic; padding: 4px 2px;")
|
|
50
|
+
self.layout.addWidget(self._placeholder)
|
|
51
|
+
|
|
52
|
+
def _hide_placeholder(self):
|
|
53
|
+
if self._placeholder is None:
|
|
54
|
+
return
|
|
55
|
+
self.layout.removeWidget(self._placeholder)
|
|
56
|
+
self._placeholder.deleteLater()
|
|
57
|
+
self._placeholder = None
|
|
58
|
+
|
|
59
|
+
# ------------------------------------------------------------------
|
|
60
|
+
def addVariable(self, name: str):
|
|
61
|
+
if not name or name in self._variable_set:
|
|
62
|
+
return
|
|
63
|
+
if not hasattr(self.debugger, 'watched'):
|
|
64
|
+
self.debugger.watched = [] # type: ignore[attr-defined]
|
|
65
|
+
if name not in self.debugger.watched: # type: ignore[attr-defined]
|
|
66
|
+
self.debugger.watched.append(name) # type: ignore[attr-defined]
|
|
67
|
+
|
|
68
|
+
# Ensure placeholder hidden
|
|
69
|
+
self._hide_placeholder()
|
|
70
|
+
|
|
71
|
+
# Build a simple row: [ QLabel("Name = value") | remove_btn ]
|
|
72
|
+
row_widget = QWidget(self)
|
|
73
|
+
row_layout = QHBoxLayout(row_widget)
|
|
74
|
+
row_layout.setContentsMargins(4, 0, 4, 0)
|
|
75
|
+
row_layout.setSpacing(6)
|
|
76
|
+
|
|
77
|
+
label = QLabel("")
|
|
78
|
+
label.setWordWrap(False)
|
|
79
|
+
row_layout.addWidget(label, 1)
|
|
80
|
+
|
|
81
|
+
remove_btn = QPushButton("–")
|
|
82
|
+
remove_btn.setFixedSize(22, 22)
|
|
83
|
+
|
|
84
|
+
def on_remove():
|
|
85
|
+
try:
|
|
86
|
+
if hasattr(self.debugger, 'watched') and name in self.debugger.watched: # type: ignore[attr-defined]
|
|
87
|
+
self.debugger.watched.remove(name) # type: ignore[attr-defined]
|
|
88
|
+
if name in self._variable_set:
|
|
89
|
+
self._variable_set.remove(name)
|
|
90
|
+
self.layout.removeWidget(row_widget)
|
|
91
|
+
row_widget.deleteLater()
|
|
92
|
+
self._rows.pop(name, None)
|
|
93
|
+
if not self._rows:
|
|
94
|
+
"""WatchListWidget for managing the variable watch list in the EasyCoder debugger
|
|
95
|
+
|
|
96
|
+
Simplified to show variables in single-line form: `Name = value`.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
from PySide6.QtWidgets import (
|
|
100
|
+
QWidget,
|
|
101
|
+
QHBoxLayout,
|
|
102
|
+
QVBoxLayout,
|
|
103
|
+
QLabel,
|
|
104
|
+
QPushButton,
|
|
105
|
+
QSizePolicy,
|
|
106
|
+
)
|
|
107
|
+
from .ec_dbg_value_display import ValueDisplay
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class WatchListWidget(QWidget):
|
|
111
|
+
"""Simple watch list that renders each variable on a single line."""
|
|
112
|
+
|
|
113
|
+
def __init__(self, debugger):
|
|
114
|
+
super().__init__(debugger)
|
|
115
|
+
self.debugger = debugger
|
|
116
|
+
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
|
117
|
+
|
|
118
|
+
self._rows: dict[str, dict] = {}
|
|
119
|
+
self._variable_set: set[str] = set()
|
|
120
|
+
self._placeholder: QLabel | None = None
|
|
121
|
+
|
|
122
|
+
self.layout = QVBoxLayout(self)
|
|
123
|
+
self.layout.setContentsMargins(0, 0, 0, 0)
|
|
124
|
+
self.layout.setSpacing(2)
|
|
125
|
+
|
|
126
|
+
self._show_placeholder()
|
|
127
|
+
|
|
128
|
+
# ------------------------------------------------------------------
|
|
129
|
+
def _show_placeholder(self):
|
|
130
|
+
if self._placeholder is not None:
|
|
131
|
+
return
|
|
132
|
+
self._placeholder = QLabel("No variables watched. Click + to add.")
|
|
133
|
+
self._placeholder.setStyleSheet("color: #666; font-style: italic; padding: 4px 2px;")
|
|
134
|
+
self.layout.addWidget(self._placeholder)
|
|
135
|
+
|
|
136
|
+
def _hide_placeholder(self):
|
|
137
|
+
if self._placeholder is None:
|
|
138
|
+
return
|
|
139
|
+
self.layout.removeWidget(self._placeholder)
|
|
140
|
+
self._placeholder.deleteLater()
|
|
141
|
+
self._placeholder = None
|
|
142
|
+
|
|
143
|
+
# ------------------------------------------------------------------
|
|
144
|
+
def addVariable(self, name: str):
|
|
145
|
+
if not name or name in self._variable_set:
|
|
146
|
+
return
|
|
147
|
+
if not hasattr(self.debugger, 'watched'):
|
|
148
|
+
self.debugger.watched = [] # type: ignore[attr-defined]
|
|
149
|
+
if name not in self.debugger.watched: # type: ignore[attr-defined]
|
|
150
|
+
self.debugger.watched.append(name) # type: ignore[attr-defined]
|
|
151
|
+
|
|
152
|
+
# Ensure placeholder hidden
|
|
153
|
+
self._hide_placeholder()
|
|
154
|
+
|
|
155
|
+
# Build a simple row: [ QLabel("Name = value") | remove_btn ]
|
|
156
|
+
row_widget = QWidget(self)
|
|
157
|
+
row_layout = QHBoxLayout(row_widget)
|
|
158
|
+
row_layout.setContentsMargins(4, 0, 4, 0)
|
|
159
|
+
row_layout.setSpacing(6)
|
|
160
|
+
|
|
161
|
+
label = QLabel("")
|
|
162
|
+
label.setWordWrap(False)
|
|
163
|
+
row_layout.addWidget(label, 1)
|
|
164
|
+
|
|
165
|
+
remove_btn = QPushButton("–")
|
|
166
|
+
remove_btn.setFixedSize(22, 22)
|
|
167
|
+
|
|
168
|
+
def on_remove():
|
|
169
|
+
try:
|
|
170
|
+
if hasattr(self.debugger, 'watched') and name in self.debugger.watched: # type: ignore[attr-defined]
|
|
171
|
+
self.debugger.watched.remove(name) # type: ignore[attr-defined]
|
|
172
|
+
if name in self._variable_set:
|
|
173
|
+
self._variable_set.remove(name)
|
|
174
|
+
self.layout.removeWidget(row_widget)
|
|
175
|
+
row_widget.deleteLater()
|
|
176
|
+
self._rows.pop(name, None)
|
|
177
|
+
if not self._rows:
|
|
178
|
+
self._show_placeholder()
|
|
179
|
+
except Exception:
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
remove_btn.clicked.connect(on_remove)
|
|
183
|
+
row_layout.addWidget(remove_btn, 0)
|
|
184
|
+
|
|
185
|
+
# Track row
|
|
186
|
+
self.layout.addWidget(row_widget)
|
|
187
|
+
self._rows[name] = {
|
|
188
|
+
'widget': row_widget,
|
|
189
|
+
'label': label,
|
|
190
|
+
}
|
|
191
|
+
self._variable_set.add(name)
|
|
192
|
+
|
|
193
|
+
# Initial refresh for the new row
|
|
194
|
+
try:
|
|
195
|
+
self._refresh_one(name, self.debugger.program)
|
|
196
|
+
except Exception:
|
|
197
|
+
pass
|
|
198
|
+
|
|
199
|
+
# ------------------------------------------------------------------
|
|
200
|
+
def _refresh_one(self, name: str, program):
|
|
201
|
+
row = self._rows.get(name)
|
|
202
|
+
if not row:
|
|
203
|
+
return
|
|
204
|
+
try:
|
|
205
|
+
# ValueDisplay reduced to textify given symbol name
|
|
206
|
+
val_display = ValueDisplay()
|
|
207
|
+
val_display.setValue(program, name)
|
|
208
|
+
value_text = val_display.text()
|
|
209
|
+
except Exception as e:
|
|
210
|
+
value_text = f"<error: {e}>"
|
|
211
|
+
row['label'].setText(f"{name} = {value_text}")
|
|
212
|
+
|
|
213
|
+
# ------------------------------------------------------------------
|
|
214
|
+
def refreshVariables(self, program):
|
|
215
|
+
if not self._rows:
|
|
216
|
+
self._show_placeholder()
|
|
217
|
+
return
|
|
218
|
+
for name in list(self._rows.keys()):
|
|
219
|
+
self._refresh_one(name, program)
|