easycoder 251104.2__tar.gz → 251104.3__tar.gz

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.

Files changed (187) hide show
  1. {easycoder-251104.2 → easycoder-251104.3}/PKG-INFO +1 -1
  2. {easycoder-251104.2 → easycoder-251104.3}/easycoder/__init__.py +1 -1
  3. {easycoder-251104.2 → easycoder-251104.3}/easycoder/ec_border.py +15 -11
  4. {easycoder-251104.2 → easycoder-251104.3}/easycoder/ec_debug.py +350 -33
  5. {easycoder-251104.2 → easycoder-251104.3}/easycoder/ec_handler.py +1 -0
  6. {easycoder-251104.2 → easycoder-251104.3}/easycoder/ec_keyboard.py +50 -50
  7. {easycoder-251104.2 → easycoder-251104.3}/easycoder/ec_program.py +3 -2
  8. {easycoder-251104.2 → easycoder-251104.3}/easycoder/ec_pyside.py +80 -95
  9. {easycoder-251104.2 → easycoder-251104.3}/easycoder/ec_timestamp.py +2 -1
  10. easycoder-251104.3/easycoder/icons/exit.png +0 -0
  11. easycoder-251104.3/easycoder/icons/run.png +0 -0
  12. easycoder-251104.3/easycoder/icons/step.png +0 -0
  13. easycoder-251104.3/easycoder/icons/stop.png +0 -0
  14. {easycoder-251104.2 → easycoder-251104.3}/plugins/ec_keyboard.py +7 -7
  15. {easycoder-251104.2 → easycoder-251104.3}/scripts/ec_keyboard.py +10 -10
  16. {easycoder-251104.2 → easycoder-251104.3}/scripts/tests.ecs +29 -25
  17. {easycoder-251104.2 → easycoder-251104.3}/.github/copilot-instructions.md +0 -0
  18. {easycoder-251104.2 → easycoder-251104.3}/.gitignore +0 -0
  19. {easycoder-251104.2 → easycoder-251104.3}/.vscode/EXTENSION_GUIDE.md +0 -0
  20. {easycoder-251104.2 → easycoder-251104.3}/.vscode/PYTHON_SETUP.md +0 -0
  21. {easycoder-251104.2 → easycoder-251104.3}/.vscode/extensions/easycoder/README.md +0 -0
  22. {easycoder-251104.2 → easycoder-251104.3}/.vscode/extensions/easycoder/language-configuration.json +0 -0
  23. {easycoder-251104.2 → easycoder-251104.3}/.vscode/extensions/easycoder/package.json +0 -0
  24. {easycoder-251104.2 → easycoder-251104.3}/.vscode/extensions/easycoder/snippets/easycoder.json +0 -0
  25. {easycoder-251104.2 → easycoder-251104.3}/.vscode/extensions/easycoder/syntaxes/easycoder.tmLanguage.json +0 -0
  26. {easycoder-251104.2 → easycoder-251104.3}/.vscode/settings.json +0 -0
  27. {easycoder-251104.2 → easycoder-251104.3}/LICENSE +0 -0
  28. {easycoder-251104.2 → easycoder-251104.3}/README.md +0 -0
  29. {easycoder-251104.2 → easycoder-251104.3}/backdrop.jpg +0 -0
  30. {easycoder-251104.2 → easycoder-251104.3}/doc/README.md +0 -0
  31. {easycoder-251104.2 → easycoder-251104.3}/doc/core/README.md +0 -0
  32. {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/boolean.md +0 -0
  33. {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/empty.md +0 -0
  34. {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/ends.md +0 -0
  35. {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/even.md +0 -0
  36. {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/exists.md +0 -0
  37. {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/greater.md +0 -0
  38. {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/hasProperty.md +0 -0
  39. {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/includes.md +0 -0
  40. {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/is.md +0 -0
  41. {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/less.md +0 -0
  42. {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/list.md +0 -0
  43. {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/none.md +0 -0
  44. {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/not.md +0 -0
  45. {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/numeric.md +0 -0
  46. {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/object.md +0 -0
  47. {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/odd.md +0 -0
  48. {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/starts.md +0 -0
  49. {easycoder-251104.2 → easycoder-251104.3}/doc/core/conditions/string.md +0 -0
  50. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/add.md +0 -0
  51. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/append.md +0 -0
  52. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/assert.md +0 -0
  53. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/begin.md +0 -0
  54. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/clear.md +0 -0
  55. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/close.md +0 -0
  56. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/create.md +0 -0
  57. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/debug.md +0 -0
  58. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/decrement.md +0 -0
  59. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/delete.md +0 -0
  60. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/divide.md +0 -0
  61. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/exit.md +0 -0
  62. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/file.md +0 -0
  63. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/fork.md +0 -0
  64. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/get.md +0 -0
  65. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/go.md +0 -0
  66. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/gosub.md +0 -0
  67. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/if.md +0 -0
  68. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/import.md +0 -0
  69. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/increment.md +0 -0
  70. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/index.md +0 -0
  71. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/init.md +0 -0
  72. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/input.md +0 -0
  73. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/load.md +0 -0
  74. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/lock.md +0 -0
  75. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/log.md +0 -0
  76. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/module.md +0 -0
  77. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/multiply.md +0 -0
  78. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/negate.md +0 -0
  79. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/on.md +0 -0
  80. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/open.md +0 -0
  81. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/pop.md +0 -0
  82. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/post.md +0 -0
  83. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/print.md +0 -0
  84. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/push.md +0 -0
  85. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/put.md +0 -0
  86. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/read.md +0 -0
  87. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/release.md +0 -0
  88. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/replace.md +0 -0
  89. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/return.md +0 -0
  90. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/run.md +0 -0
  91. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/save.md +0 -0
  92. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/script.md +0 -0
  93. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/send.md +0 -0
  94. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/set.md +0 -0
  95. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/shuffle.md +0 -0
  96. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/split.md +0 -0
  97. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/stack.md +0 -0
  98. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/stop.md +0 -0
  99. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/system.md +0 -0
  100. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/take.md +0 -0
  101. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/toggle.md +0 -0
  102. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/truncate.md +0 -0
  103. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/unlock.md +0 -0
  104. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/use.md +0 -0
  105. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/variable.md +0 -0
  106. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/wait.md +0 -0
  107. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/while.md +0 -0
  108. {easycoder-251104.2 → easycoder-251104.3}/doc/core/keywords/write.md +0 -0
  109. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/arg.md +0 -0
  110. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/args.md +0 -0
  111. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/cos.md +0 -0
  112. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/datime.md +0 -0
  113. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/decode.md +0 -0
  114. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/element.md +0 -0
  115. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/elements.md +0 -0
  116. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/empty.md +0 -0
  117. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/encode.md +0 -0
  118. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/error.md +0 -0
  119. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/files.md +0 -0
  120. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/float.md +0 -0
  121. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/from.md +0 -0
  122. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/hash.md +0 -0
  123. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/index.md +0 -0
  124. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/integer.md +0 -0
  125. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/json.md +0 -0
  126. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/keys.md +0 -0
  127. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/left.md +0 -0
  128. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/length.md +0 -0
  129. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/lowercase.md +0 -0
  130. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/memory.md +0 -0
  131. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/modification.md +0 -0
  132. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/modulo.md +0 -0
  133. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/newline.md +0 -0
  134. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/now.md +0 -0
  135. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/position.md +0 -0
  136. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/property.md +0 -0
  137. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/random.md +0 -0
  138. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/right.md +0 -0
  139. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/sin.md +0 -0
  140. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/stringify.md +0 -0
  141. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/tab.md +0 -0
  142. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/tan.md +0 -0
  143. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/timestamp.md +0 -0
  144. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/today.md +0 -0
  145. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/trim.md +0 -0
  146. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/type.md +0 -0
  147. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/uppercase.md +0 -0
  148. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/value.md +0 -0
  149. {easycoder-251104.2 → easycoder-251104.3}/doc/core/values/weekday.md +0 -0
  150. {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/README.md +0 -0
  151. {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/keywords/attach.md +0 -0
  152. {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/keywords/close.md +0 -0
  153. {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/keywords/create.md +0 -0
  154. {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/keywords/ellipse.md +0 -0
  155. {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/keywords/image.md +0 -0
  156. {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/keywords/move.md +0 -0
  157. {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/keywords/on.md +0 -0
  158. {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/keywords/rectangle.md +0 -0
  159. {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/keywords/render.md +0 -0
  160. {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/keywords/run.md +0 -0
  161. {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/keywords/set.md +0 -0
  162. {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/keywords/text.md +0 -0
  163. {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/values/attribute.md +0 -0
  164. {easycoder-251104.2 → easycoder-251104.3}/doc/graphics/values/window.md +0 -0
  165. {easycoder-251104.2 → easycoder-251104.3}/easycoder/ec_classes.py +0 -0
  166. {easycoder-251104.2 → easycoder-251104.3}/easycoder/ec_compiler.py +0 -0
  167. {easycoder-251104.2 → easycoder-251104.3}/easycoder/ec_condition.py +0 -0
  168. {easycoder-251104.2 → easycoder-251104.3}/easycoder/ec_core.py +0 -0
  169. {easycoder-251104.2 → easycoder-251104.3}/easycoder/ec_value.py +0 -0
  170. {easycoder-251104.2/easycoder → easycoder-251104.3/easycoder/icons}/close.png +0 -0
  171. {easycoder-251104.2/easycoder → easycoder-251104.3/easycoder/icons}/tick.png +0 -0
  172. {easycoder-251104.2 → easycoder-251104.3}/images/Semoigo Dawn.jpg +0 -0
  173. {easycoder-251104.2 → easycoder-251104.3}/json/graphics-demo.json +0 -0
  174. {easycoder-251104.2 → easycoder-251104.3}/plugins/ec_p100.py +0 -0
  175. {easycoder-251104.2 → easycoder-251104.3}/plugins/ec_pyside6.py +0 -0
  176. {easycoder-251104.2 → easycoder-251104.3}/plugins/points.py +0 -0
  177. {easycoder-251104.2 → easycoder-251104.3}/plugins/sql.py +0 -0
  178. {easycoder-251104.2 → easycoder-251104.3}/pyproject.toml +0 -0
  179. {easycoder-251104.2 → easycoder-251104.3}/scripts/benchmark.ecs +0 -0
  180. {easycoder-251104.2 → easycoder-251104.3}/scripts/fizzbuzz.ecs +0 -0
  181. {easycoder-251104.2 → easycoder-251104.3}/scripts/hello.ecs +0 -0
  182. {easycoder-251104.2 → easycoder-251104.3}/scripts/points.ecs +0 -0
  183. {easycoder-251104.2 → easycoder-251104.3}/scripts/test.ecs +0 -0
  184. {easycoder-251104.2 → easycoder-251104.3}/test.py +0 -0
  185. {easycoder-251104.2 → easycoder-251104.3}/testrc.py +0 -0
  186. {easycoder-251104.2 → easycoder-251104.3}/testsql.py +0 -0
  187. {easycoder-251104.2 → easycoder-251104.3}/testui.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easycoder
3
- Version: 251104.2
3
+ Version: 251104.3
4
4
  Summary: Rapid scripting in English
5
5
  Keywords: compiler,scripting,prototyping,programming,coding,python,low code,hypertalk,computer language,learn to code
6
6
  Author-email: Graham Trott <gtanyware@gmail.com>
@@ -12,4 +12,4 @@ from .ec_pyside import *
12
12
  from .ec_timestamp import *
13
13
  from .ec_value import *
14
14
 
15
- __version__ = "251104.2"
15
+ __version__ = "251104.3"
@@ -9,34 +9,38 @@ class Border(QWidget):
9
9
 
10
10
  def __init__(self):
11
11
  super().__init__()
12
- self.size = 40
13
- self.setFixedHeight(self.size)
12
+ self._size = 40
13
+ self.setFixedHeight(self._size)
14
14
  self._drag_active = False
15
15
  self._drag_start_pos = None
16
+ self._tick: QPixmap = QPixmap()
17
+ self._close_icon: QPixmap = QPixmap()
16
18
 
17
19
  def paintEvent(self, event):
18
20
  painter = QPainter(self)
19
- painter.setRenderHint(QPainter.Antialiasing)
21
+ painter.setRenderHint(QPainter.RenderHint.Antialiasing)
20
22
  # Draw the tick icon
21
- self.tick = QPixmap(f'{os.path.dirname(os.path.abspath(__file__))}/tick.png').scaled(self.size, self.size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
23
+ self._tick = QPixmap(f'{os.path.dirname(os.path.abspath(__file__))}/icons/tick.png').scaled(
24
+ self._size, self._size, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)
22
25
  x = 0
23
26
  y = 0
24
- painter.drawPixmap(x, y, self.tick)
27
+ painter.drawPixmap(x, y, self._tick)
25
28
  # Draw the close icon
26
- self.close = QPixmap(f'{os.path.dirname(os.path.abspath(__file__))}/close.png').scaled(self.size, self.size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
27
- x = self.width() - self.close.width()
29
+ self._close_icon = QPixmap(f'{os.path.dirname(os.path.abspath(__file__))}/icons/close.png').scaled(
30
+ self._size, self._size, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)
31
+ x = self.width() - self._close_icon.width()
28
32
  y = 0
29
- painter.drawPixmap(x, y, self.close)
33
+ painter.drawPixmap(x, y, self._close_icon)
30
34
 
31
35
  def mousePressEvent(self, event):
32
36
  # Tick icon
33
37
  x = 0
34
38
  y = 0
35
- tickRect = self.tick.rect().translated(x, y)
39
+ tickRect = self._tick.rect().translated(x, y)
36
40
  # Close icon
37
- x = self.width() - self.close.width()
41
+ x = self.width() - self._close_icon.width()
38
42
  y = 0
39
- closeRect = self.close.rect().translated(x, y)
43
+ closeRect = self._close_icon.rect().translated(x, y)
40
44
  if tickRect.contains(event.pos()):
41
45
  self.tickClicked.emit()
42
46
  if closeRect.contains(event.pos()):
@@ -7,18 +7,19 @@ from PySide6.QtWidgets import (
7
7
  QVBoxLayout,
8
8
  QLabel,
9
9
  QSplitter,
10
- QFileDialog,
11
10
  QMessageBox,
12
11
  QScrollArea,
13
12
  QSizePolicy,
14
- QToolBar
13
+ QToolBar,
14
+ QPushButton,
15
+ QInputDialog
15
16
  )
16
- from PySide6.QtGui import QAction, QKeySequence, QTextCursor
17
- from PySide6.QtCore import Qt, QTimer, QProcess
17
+ from PySide6.QtGui import QTextCursor, QIcon
18
+ from PySide6.QtCore import Qt, QTimer
18
19
  from typing import Any
20
+ from typing import Any, Optional
19
21
 
20
22
  class Object():
21
- """Dynamic object that allows arbitrary attribute assignment"""
22
23
  def __setattr__(self, name: str, value: Any) -> None:
23
24
  self.__dict__[name] = value
24
25
 
@@ -26,15 +27,198 @@ class Object():
26
27
  return self.__dict__.get(name)
27
28
 
28
29
  class Debugger(QMainWindow):
30
+ # Help type-checkers know these attributes exist
31
+ _flush_timer: Optional[QTimer]
32
+
33
+ class ConsoleWriter:
34
+ def __init__(self, debugger: 'Debugger'):
35
+ self.debugger = debugger
36
+ self._buf: list[str] = []
37
+
38
+ def write(self, text: str):
39
+ if not text:
40
+ return
41
+ # Buffer text and request a flush on the GUI timer
42
+ self._buf.append(text)
43
+ if self.debugger._flush_timer and not self.debugger._flush_timer.isActive():
44
+ self.debugger._flush_timer.start()
45
+
46
+ def flush(self):
47
+ # Explicit flush request
48
+ self.debugger._flush_console_buffer()
29
49
 
30
50
  ###########################################################################
31
51
  # The left-hand column of the main window
32
52
  class MainLeftColumn(QWidget):
33
53
  def __init__(self, parent=None):
34
54
  super().__init__(parent)
55
+ self.debugger = parent
35
56
  layout = QVBoxLayout(self)
36
- layout.addWidget(QLabel("Left column"))
57
+
58
+ # Create toolbar with icon buttons
59
+ toolbar = QToolBar()
60
+ toolbar.setMovable(False)
61
+
62
+ # Get the icons directory path
63
+ icons_dir = os.path.join(os.path.dirname(__file__), 'icons')
64
+
65
+ # Run button
66
+ run_btn = QPushButton()
67
+ run_icon_path = os.path.join(icons_dir, 'run.png')
68
+ run_btn.setIcon(QIcon(run_icon_path))
69
+ run_btn.setToolTip("Run")
70
+ run_btn.clicked.connect(self.on_run_clicked)
71
+ toolbar.addWidget(run_btn)
72
+
73
+ # Step button
74
+ step_btn = QPushButton()
75
+ step_icon_path = os.path.join(icons_dir, 'step.png')
76
+ step_btn.setIcon(QIcon(step_icon_path))
77
+ step_btn.setToolTip("Step")
78
+ step_btn.clicked.connect(self.on_step_clicked)
79
+ toolbar.addWidget(step_btn)
80
+
81
+ # Stop button
82
+ stop_btn = QPushButton()
83
+ stop_icon_path = os.path.join(icons_dir, 'stop.png')
84
+ stop_btn.setIcon(QIcon(stop_icon_path))
85
+ stop_btn.setToolTip("Stop")
86
+ stop_btn.clicked.connect(self.on_stop_clicked)
87
+ toolbar.addWidget(stop_btn)
88
+
89
+ # Exit button
90
+ exit_btn = QPushButton()
91
+ exit_icon_path = os.path.join(icons_dir, 'exit.png')
92
+ exit_btn.setIcon(QIcon(exit_icon_path))
93
+ exit_btn.setToolTip("Exit")
94
+ exit_btn.clicked.connect(self.on_exit_clicked)
95
+ toolbar.addWidget(exit_btn)
96
+
97
+
98
+ layout.addWidget(toolbar)
99
+
100
+ # --- Watch panel (like VS Code) ---
101
+ watch_panel = QFrame()
102
+ watch_panel.setFrameShape(QFrame.Shape.StyledPanel)
103
+ # Ensure the VARIABLES bar stretches to full available width
104
+ watch_panel.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
105
+ watch_layout = QHBoxLayout(watch_panel)
106
+ watch_layout.setContentsMargins(4, 4, 4, 4)
107
+ watch_layout.setSpacing(4)
108
+
109
+
110
+ # Title label
111
+ title_label = QLabel("VARIABLES")
112
+ title_label.setStyleSheet("font-weight: bold; letter-spacing: 1px;")
113
+ watch_layout.addWidget(title_label)
114
+
115
+ # Stretch to push buttons right
116
+ watch_layout.addStretch()
117
+
118
+ # Placeholder add/remove icons (replace with real icons later)
119
+ add_btn = QPushButton()
120
+ add_btn.setToolTip("Add variable to watch")
121
+ # TODO: set add_btn.setIcon(QIcon(path)) when icon is available
122
+ add_btn.setText("+")
123
+ add_btn.setFixedSize(24, 24)
124
+ add_btn.clicked.connect(self.on_add_clicked)
125
+ watch_layout.addWidget(add_btn)
126
+
127
+ layout.addWidget(watch_panel)
128
+
129
+ # Watch list area (renders selected variables beneath the toolbar)
130
+ self.watch_list_widget = QWidget()
131
+ self.watch_list_layout = QVBoxLayout(self.watch_list_widget)
132
+ self.watch_list_layout.setContentsMargins(6, 2, 6, 2)
133
+ self.watch_list_layout.setSpacing(2)
134
+ layout.addWidget(self.watch_list_widget)
135
+
136
+ # Keep a simple set to prevent duplicate labels
137
+ self._watch_set = set()
138
+
37
139
  layout.addStretch()
140
+
141
+ def on_add_clicked(self):
142
+ # Build the variable list from the program. Prefer Program.symbols mapping.
143
+ try:
144
+ program = self.debugger.program # type: ignore[attr-defined]
145
+ # Fallback to scanning code if symbols is empty
146
+ items = []
147
+ if hasattr(program, 'symbols') and isinstance(program.symbols, dict) and program.symbols:
148
+ items = sorted([name for name in program.symbols.keys() if name and not name.endswith(':')])
149
+ else:
150
+ # Fallback heuristic: look for commands whose 'type' == 'symbol' (as per requirement)
151
+ for cmd in getattr(program, 'code', []):
152
+ try:
153
+ if cmd.get('type') == 'symbol' and 'name' in cmd:
154
+ items.append(cmd['name'])
155
+ except Exception:
156
+ pass
157
+ items = sorted(set(items))
158
+ if not items:
159
+ QMessageBox.information(self, "Add Watch", "No variables found in this program.")
160
+ return
161
+ choice, ok = QInputDialog.getItem(self, "Add Watch", "Select a variable:", items, 0, False)
162
+ if ok and choice:
163
+ # Record the choice for future use (UI for list will be added later)
164
+ if not hasattr(self.debugger, 'watched'):
165
+ self.debugger.watched = [] # type: ignore[attr-defined]
166
+ if choice not in self.debugger.watched: # type: ignore[attr-defined]
167
+ self.debugger.watched.append(choice) # type: ignore[attr-defined]
168
+ # Render as a plain label beneath the toolbar if not already present
169
+ if choice not in self._watch_set:
170
+ self._add_watch_row(choice)
171
+ self._watch_set.add(choice)
172
+ # Optionally echo to console for now
173
+ try:
174
+ self.debugger.console.append(f"Watching: {choice}") # type: ignore[attr-defined]
175
+ except Exception:
176
+ pass
177
+ except Exception as exc:
178
+ QMessageBox.warning(self, "Add Watch", f"Could not list variables: {exc}")
179
+
180
+ def _add_watch_row(self, name: str):
181
+ row = QWidget()
182
+ h = QHBoxLayout(row)
183
+ h.setContentsMargins(0, 0, 0, 0)
184
+ h.setSpacing(4)
185
+ lbl = QLabel(name)
186
+ lbl.setStyleSheet("font-family: mono; padding: 1px 2px;")
187
+ h.addWidget(lbl)
188
+ h.addStretch()
189
+ btn = QPushButton()
190
+ btn.setText("–") # placeholder until icon provided
191
+ btn.setToolTip(f"Remove '{name}' from watch")
192
+ btn.setFixedSize(20, 20)
193
+
194
+ def on_remove():
195
+ try:
196
+ # update internal structures
197
+ if hasattr(self.debugger, 'watched') and name in self.debugger.watched: # type: ignore[attr-defined]
198
+ self.debugger.watched.remove(name) # type: ignore[attr-defined]
199
+ if name in self._watch_set:
200
+ self._watch_set.remove(name)
201
+ # remove row from layout/UI
202
+ row.setParent(None)
203
+ row.deleteLater()
204
+ except Exception:
205
+ pass
206
+
207
+ btn.clicked.connect(on_remove)
208
+ h.addWidget(btn)
209
+ self.watch_list_layout.addWidget(row)
210
+
211
+ def on_run_clicked(self):
212
+ self.debugger.doRun() # type: ignore[attr-defined]
213
+
214
+ def on_step_clicked(self):
215
+ self.debugger.doStep() # type: ignore[attr-defined]
216
+
217
+ def on_stop_clicked(self):
218
+ self.debugger.doStop() # type: ignore[attr-defined]
219
+
220
+ def on_exit_clicked(self):
221
+ self.debugger.doClose() # type: ignore[attr-defined]
38
222
 
39
223
  ###########################################################################
40
224
  # The right-hand column of the main window
@@ -75,24 +259,25 @@ class Debugger(QMainWindow):
75
259
  #######################################################################
76
260
  # Add a line to the right-hand column
77
261
  def addLine(self, spec):
262
+
263
+ # Determine if this line is a command (not empty, not a comment), using the original script line
264
+ orig_line = getattr(spec, 'orig_line', spec.line) if hasattr(spec, 'orig_line') or 'orig_line' in spec.__dict__ else spec.line
265
+ line_lstripped = orig_line.lstrip()
266
+ is_command = bool(line_lstripped and not line_lstripped.startswith('!'))
267
+
78
268
  class Label(QLabel):
79
- def __init__(self, text, fixed_width=None, align=Qt.AlignmentFlag.AlignLeft, on_click=spec.onClick):
269
+ def __init__(self, text, fixed_width=None, align=Qt.AlignmentFlag.AlignLeft, on_click=None):
80
270
  super().__init__()
81
271
  self.setText(text)
82
- # remove QLabel's internal margins/padding to reduce top/bottom space
83
272
  self.setMargin(0)
84
273
  self.setContentsMargins(0, 0, 0, 0)
85
274
  self.setStyleSheet("padding:0px; margin:0px; font-family: mono")
86
275
  fm = self.fontMetrics()
87
- # set a compact fixed height based on font metrics
88
276
  self.setFixedHeight(fm.height())
89
- # optional fixed width (used for the lino column)
90
277
  if fixed_width is not None:
91
278
  self.setFixedWidth(fixed_width)
92
- # align horizontally (keep vertically centered)
93
279
  self.setAlignment(align | Qt.AlignmentFlag.AlignVCenter)
94
- # optional click callback
95
- self._on_click = on_click
280
+ self._on_click = on_click if is_command else None
96
281
 
97
282
  def mousePressEvent(self, event):
98
283
  if self._on_click:
@@ -120,11 +305,25 @@ class Debugger(QMainWindow):
120
305
  fm_main = self.fontMetrics()
121
306
  width_4 = fm_main.horizontalAdvance('0000') + 8
122
307
 
308
+
123
309
  # create the red blob (always present). We'll toggle its opacity
124
310
  # by changing the stylesheet (rgba alpha 255/0). Do NOT store it
125
311
  # on the MainRightColumn instance — keep it per-line.
126
- blob = QLabel()
312
+
313
+ class ClickableBlob(QLabel):
314
+ def __init__(self, on_click=None):
315
+ super().__init__()
316
+ self._on_click = on_click if is_command else None
317
+ def mousePressEvent(self, event):
318
+ if self._on_click:
319
+ try:
320
+ self._on_click()
321
+ except Exception:
322
+ pass
323
+ super().mousePressEvent(event)
324
+
127
325
  blob_size = 10
326
+ blob = ClickableBlob(on_click=(lambda: spec.onClick(spec.lino)) if is_command else None)
128
327
  blob.setFixedSize(blob_size, blob_size)
129
328
 
130
329
  def set_blob_visible(widget, visible):
@@ -149,7 +348,7 @@ class Debugger(QMainWindow):
149
348
 
150
349
  # create the line-number label; clicking it reports back to the caller
151
350
  lino_label = Label(str(spec.lino+1), fixed_width=width_4, align=Qt.AlignmentFlag.AlignRight,
152
- on_click=lambda: spec.onClick(spec.lino))
351
+ on_click=(lambda: spec.onClick(spec.lino)) if is_command else None)
153
352
  lino_label.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
154
353
  # create the text label for the line itself
155
354
  text_label = Label(spec.line, fixed_width=None, align=Qt.AlignmentFlag.AlignLeft)
@@ -178,6 +377,8 @@ class Debugger(QMainWindow):
178
377
  self.program = program
179
378
  self.setWindowTitle("EasyCoder Debugger")
180
379
  self.setMinimumSize(width, height)
380
+ # Disable the window close button
381
+ self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint)
181
382
  self.stopped = True
182
383
 
183
384
  # try to load saved geometry from ~/.ecdebug.conf
@@ -210,7 +411,10 @@ class Debugger(QMainWindow):
210
411
  self._writer = None
211
412
  self._orig_stdout = None
212
413
  self._orig_stderr = None
213
- self._flush_timer = None
414
+ self._flush_timer = QTimer(self)
415
+ self._flush_timer.setInterval(50)
416
+ self._flush_timer.timeout.connect(self._flush_console_buffer)
417
+ self._flush_timer.stop()
214
418
 
215
419
  # Keep a ratio so proportions are preserved when window is resized
216
420
  self.ratio = ratio
@@ -225,7 +429,7 @@ class Debugger(QMainWindow):
225
429
  left.setFrameShape(QFrame.Shape.StyledPanel)
226
430
  left_layout = QVBoxLayout(left)
227
431
  left_layout.setContentsMargins(8, 8, 8, 8)
228
- self.leftColumn = self.MainLeftColumn()
432
+ self.leftColumn = self.MainLeftColumn(self)
229
433
  left_layout.addWidget(self.leftColumn)
230
434
  left_layout.addStretch()
231
435
 
@@ -234,7 +438,7 @@ class Debugger(QMainWindow):
234
438
  right.setFrameShape(QFrame.Shape.StyledPanel)
235
439
  right_layout = QVBoxLayout(right)
236
440
  right_layout.setContentsMargins(8, 8, 8, 8)
237
- self.rightColumn = self.MainRightColumn()
441
+ self.rightColumn = self.MainRightColumn(self)
238
442
  # Give the rightColumn a stretch factor so its scroll area fills the vertical space
239
443
  right_layout.addWidget(self.rightColumn, 1)
240
444
 
@@ -264,6 +468,17 @@ class Debugger(QMainWindow):
264
468
  console_layout.addWidget(self.console)
265
469
  self.vsplitter.addWidget(console_frame)
266
470
 
471
+ # Redirect stdout/stderr so all program output is captured in the console
472
+ try:
473
+ self._orig_stdout = sys.stdout
474
+ self._orig_stderr = sys.stderr
475
+ self._writer = self.ConsoleWriter(self)
476
+ sys.stdout = self._writer # type: ignore[assignment]
477
+ sys.stderr = self._writer # type: ignore[assignment]
478
+ except Exception:
479
+ # Best effort; if redirection fails, continue without it
480
+ self._writer = None
481
+
267
482
  # Set initial vertical sizes: prefer saved console_height if available
268
483
  try:
269
484
  total_h = int(h) if 'h' in locals() else max(300, self.height())
@@ -277,6 +492,21 @@ class Debugger(QMainWindow):
277
492
  self.parse(program.script.lines)
278
493
  self.show()
279
494
 
495
+ def _flush_console_buffer(self):
496
+ try:
497
+ writer = self._writer
498
+ if not writer:
499
+ return
500
+ if getattr(writer, '_buf', None):
501
+ text = ''.join(writer._buf)
502
+ writer._buf.clear()
503
+ # Append to the console and scroll to bottom
504
+ self.console.moveCursor(QTextCursor.MoveOperation.End)
505
+ self.console.insertPlainText(text)
506
+ self.console.moveCursor(QTextCursor.MoveOperation.End)
507
+ except Exception:
508
+ pass
509
+
280
510
  def on_splitter_moved(self, pos, index):
281
511
  # Update stored ratio when user drags the splitter
282
512
  left_width = self.hsplitter.widget(0).width()
@@ -306,15 +536,17 @@ class Debugger(QMainWindow):
306
536
  # Parse and add new lines
307
537
  lino = 0
308
538
  for line in script:
539
+ orig_line = line
309
540
  if len(line) > 0:
310
541
  line = line.replace("\t", " ")
311
- line = self.coloriseLine(line, lino)
542
+ color_line = self.coloriseLine(line, lino)
312
543
  else:
313
544
  # still need to call coloriseLine to keep token list in sync
314
- self.coloriseLine(line, lino)
545
+ color_line = self.coloriseLine(line, lino)
315
546
  lineSpec = Object()
316
547
  lineSpec.lino = lino
317
- lineSpec.line = line
548
+ lineSpec.line = color_line
549
+ lineSpec.orig_line = orig_line
318
550
  lineSpec.bp = False
319
551
  lineSpec.onClick = self.onClickLino
320
552
  lino += 1
@@ -402,14 +634,16 @@ class Debugger(QMainWindow):
402
634
  ###########################################################################
403
635
  # Here when the user clicks a line number
404
636
  def onClickLino(self, lino):
637
+ # Show or hide the red blob next to this line
405
638
  lineSpec = self.scriptLines[lino]
406
639
  lineSpec.bp = not lineSpec.bp
407
640
  if lineSpec.bp: lineSpec.label.showBlob()
408
641
  else: lineSpec.label.hideBlob()
409
- # Set a breakpoint on this command
410
- command = self.program.code[self.program.pc]
411
- command['bp'] = True
412
- self.program.code[self.program.pc] = command
642
+ # Set or clear a breakpoint on this command
643
+ for command in self.program.code:
644
+ if 'lino' in command and command['lino'] == lino:
645
+ command['bp'] = lineSpec.bp
646
+ break
413
647
 
414
648
  ###########################################################################
415
649
  # Scroll to a given line number
@@ -450,15 +684,98 @@ class Debugger(QMainWindow):
450
684
  self.raise_()
451
685
  self.activateWindow()
452
686
 
687
+ ###########################################################################
688
+ # Set the background color of one line of the script
689
+ def setBackground(self, lino, color):
690
+ # Set the background color of the given line
691
+ if lino < 0 or lino >= len(self.scriptLines):
692
+ return
693
+ lineSpec = self.scriptLines[lino]
694
+ panel = lineSpec.panel
695
+ if not panel:
696
+ return
697
+ if color == 'none':
698
+ panel.setStyleSheet("")
699
+ else:
700
+ panel.setStyleSheet(f"background-color: {color};")
701
+
453
702
  ###########################################################################
454
703
  # Here when each instruction is about to run
455
- def step(self):
456
- if self.stopped:
457
- lino=self.program.code[self.program.pc]['lino']
458
- print(lino)
704
+ def continueExecution(self):
705
+ result = True
706
+ self.pc = self.program.pc
707
+ command = self.program.code[self.pc]
708
+ lino = command['lino'] + 1
709
+ if self.stopped: result = False
710
+ elif command['bp']:
711
+ print(f"Hit breakpoint at line {lino}")
712
+ self.stopped = True
713
+ result = False
714
+ if not result:
459
715
  self.scrollTo(lino)
460
- return False
461
- else:
462
- if self.program.code[self.program.pc]['bp']:
716
+ self.setBackground(command['lino'], 'LightYellow')
717
+ return result
718
+
719
+ def doRun(self):
720
+ self.stopped = False
721
+ print("Continuing execution at line", self.program.pc + 1)
722
+ self.program.run(self.pc)
723
+
724
+ def doStep(self):
725
+ command = self.program.code[self.pc]
726
+ # print("Stepping at line", command['lino'] + 1)
727
+ self.setBackground(command['lino'], 'none')
728
+ self.program.run(self.pc)
729
+
730
+ def doStop(self):
731
+ self.stopped = True
732
+
733
+ def doClose(self):
734
+ self.closeEvent(None)
735
+
736
+ ###########################################################################
737
+ # Override closeEvent to save window geometry
738
+ def closeEvent(self, event):
739
+ """Save window position and size to ~/.ecdebug.conf as JSON on exit."""
740
+ cfg = {
741
+ "x": self.x(),
742
+ "y": self.y(),
743
+ "width": self.width(),
744
+ "height": self.height(),
745
+ "ratio": self.ratio
746
+ }
747
+ # try to persist console height (bottom pane) if present
748
+ try:
749
+ ch = None
750
+ if hasattr(self, 'vsplitter'):
751
+ sizes = self.vsplitter.sizes()
752
+ if len(sizes) >= 2:
753
+ ch = int(sizes[1])
754
+ if ch is not None:
755
+ cfg['console_height'] = ch
756
+ except Exception:
757
+ pass
758
+ try:
759
+ cfg_path = os.path.join(os.path.expanduser("~"), ".ecdebug.conf")
760
+ with open(cfg_path, "w", encoding="utf-8") as f:
761
+ json.dump(cfg, f, indent=2)
762
+ except Exception as exc:
763
+ # best-effort only; avoid blocking shutdown
764
+ try:
765
+ self.statusBar().showMessage(f"Could not save config: {exc}", 3000)
766
+ except Exception:
463
767
  pass
464
- return True
768
+ # Restore stdout/stderr and stop timers
769
+ try:
770
+ if self._orig_stdout is not None:
771
+ sys.stdout = self._orig_stdout
772
+ if self._orig_stderr is not None:
773
+ sys.stderr = self._orig_stderr
774
+ if self._flush_timer is not None:
775
+ try:
776
+ self._flush_timer.stop()
777
+ except Exception:
778
+ pass
779
+ except Exception:
780
+ pass
781
+ super().close()
@@ -66,6 +66,7 @@ class Handler:
66
66
  def conditionHandler(self, name):
67
67
  return getattr(self, f'c_{name}')
68
68
 
69
+ @staticmethod
69
70
  def isJson(value):
70
71
  try:
71
72
  json.loads(value)