myokit 1.33.9__py3-none-any.whl → 1.35.0__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.
Files changed (229) hide show
  1. myokit/__init__.py +9 -36
  2. myokit/__main__.py +76 -142
  3. myokit/_aux.py +62 -16
  4. myokit/_bin/example.mmt +1 -2
  5. myokit/_bin/install-win/menu.json +7 -7
  6. myokit/_config.py +22 -31
  7. myokit/_datablock.py +30 -74
  8. myokit/_datalog.py +49 -72
  9. myokit/_err.py +25 -24
  10. myokit/_expressions.py +50 -68
  11. myokit/_io.py +15 -27
  12. myokit/_model_api.py +453 -249
  13. myokit/_myokit_version.py +1 -5
  14. myokit/_parsing.py +38 -44
  15. myokit/_progress.py +5 -8
  16. myokit/_protocol.py +99 -9
  17. myokit/_sim/__init__.py +7 -24
  18. myokit/_sim/cable.c +6 -8
  19. myokit/_sim/cable.py +6 -8
  20. myokit/_sim/cmodel.h +125 -70
  21. myokit/_sim/cmodel.py +12 -14
  22. myokit/_sim/compiler.py +1 -4
  23. myokit/_sim/cvodessim.c +196 -118
  24. myokit/_sim/cvodessim.py +130 -103
  25. myokit/_sim/differential.hpp +4 -4
  26. myokit/_sim/fiber_tissue.c +4 -8
  27. myokit/_sim/fiber_tissue.py +11 -13
  28. myokit/_sim/jacobian.cpp +2 -2
  29. myokit/_sim/jacobian.py +11 -8
  30. myokit/_sim/mcl.h +53 -55
  31. myokit/_sim/opencl.py +21 -27
  32. myokit/_sim/openclsim.c +3 -7
  33. myokit/_sim/openclsim.cl +3 -3
  34. myokit/_sim/openclsim.py +49 -40
  35. myokit/_sim/pacing.h +36 -16
  36. myokit/_sim/rhs.c +6 -13
  37. myokit/_sim/rhs.py +5 -14
  38. myokit/_sim/sundials.py +1 -4
  39. myokit/_system.py +10 -16
  40. myokit/_unit.py +4 -13
  41. myokit/float.py +0 -3
  42. myokit/formats/__init__.py +8 -10
  43. myokit/formats/ansic/__init__.py +0 -3
  44. myokit/formats/ansic/_ewriter.py +2 -4
  45. myokit/formats/ansic/_exporter.py +1 -4
  46. myokit/formats/ansic/template/cable.c +4 -4
  47. myokit/formats/ansic/template/euler.c +5 -5
  48. myokit/formats/ansic/template/sim.c +6 -6
  49. myokit/formats/axon/__init__.py +1 -3
  50. myokit/formats/axon/_abf.py +12 -17
  51. myokit/formats/axon/_atf.py +5 -6
  52. myokit/formats/axon/_importer.py +0 -3
  53. myokit/formats/cellml/__init__.py +0 -3
  54. myokit/formats/cellml/_ewriter.py +3 -6
  55. myokit/formats/cellml/_exporter.py +3 -6
  56. myokit/formats/cellml/_importer.py +1 -4
  57. myokit/formats/cellml/v1/__init__.py +0 -4
  58. myokit/formats/cellml/v1/_api.py +8 -11
  59. myokit/formats/cellml/v1/_parser.py +2 -5
  60. myokit/formats/cellml/v1/_writer.py +2 -11
  61. myokit/formats/cellml/v2/__init__.py +0 -3
  62. myokit/formats/cellml/v2/_api.py +8 -17
  63. myokit/formats/cellml/v2/_parser.py +2 -5
  64. myokit/formats/cellml/v2/_writer.py +1 -4
  65. myokit/formats/channelml/__init__.py +0 -3
  66. myokit/formats/channelml/_importer.py +11 -21
  67. myokit/formats/cpp/__init__.py +1 -3
  68. myokit/formats/cpp/_ewriter.py +0 -3
  69. myokit/formats/cuda/__init__.py +0 -3
  70. myokit/formats/cuda/_ewriter.py +2 -4
  71. myokit/formats/cuda/_exporter.py +0 -3
  72. myokit/formats/cuda/template/kernel.cu +8 -5
  73. myokit/formats/easyml/__init__.py +0 -3
  74. myokit/formats/easyml/_ewriter.py +9 -11
  75. myokit/formats/easyml/_exporter.py +2 -5
  76. myokit/formats/html/__init__.py +0 -3
  77. myokit/formats/html/_exporter.py +0 -3
  78. myokit/formats/html/_flatten.py +5 -21
  79. myokit/formats/latex/__init__.py +0 -3
  80. myokit/formats/latex/_ewriter.py +1 -4
  81. myokit/formats/latex/_exporter.py +4 -6
  82. myokit/formats/mathml/__init__.py +0 -3
  83. myokit/formats/mathml/_ewriter.py +2 -11
  84. myokit/formats/mathml/_parser.py +4 -6
  85. myokit/formats/matlab/__init__.py +0 -3
  86. myokit/formats/matlab/_ewriter.py +1 -4
  87. myokit/formats/matlab/_exporter.py +2 -5
  88. myokit/formats/matlab/template/main.m +3 -2
  89. myokit/formats/opencl/__init__.py +0 -3
  90. myokit/formats/opencl/_ewriter.py +2 -4
  91. myokit/formats/opencl/_exporter.py +2 -5
  92. myokit/formats/opencl/template/cable.c +10 -10
  93. myokit/formats/opencl/template/kernel.cl +1 -1
  94. myokit/formats/opencl/template/minilog.py +1 -1
  95. myokit/formats/python/__init__.py +0 -3
  96. myokit/formats/python/_ewriter.py +2 -5
  97. myokit/formats/python/_exporter.py +0 -3
  98. myokit/formats/python/template/sim.py +14 -14
  99. myokit/formats/sbml/__init__.py +0 -3
  100. myokit/formats/sbml/_api.py +50 -44
  101. myokit/formats/sbml/_importer.py +1 -4
  102. myokit/formats/sbml/_parser.py +2 -5
  103. myokit/formats/stan/__init__.py +0 -3
  104. myokit/formats/stan/_ewriter.py +2 -4
  105. myokit/formats/stan/_exporter.py +2 -5
  106. myokit/formats/stan/template/cell.stan +3 -3
  107. myokit/formats/sympy/__init__.py +0 -3
  108. myokit/formats/sympy/_ereader.py +1 -4
  109. myokit/formats/sympy/_ewriter.py +2 -5
  110. myokit/formats/wcp/__init__.py +0 -3
  111. myokit/formats/wcp/_wcp.py +2 -8
  112. myokit/formats/xml/__init__.py +0 -3
  113. myokit/formats/xml/_exporter.py +0 -3
  114. myokit/formats/xml/_split.py +0 -3
  115. myokit/gui/__init__.py +80 -246
  116. myokit/gui/datablock_viewer.py +103 -86
  117. myokit/gui/datalog_viewer.py +214 -66
  118. myokit/gui/explorer.py +15 -21
  119. myokit/gui/ide.py +171 -144
  120. myokit/gui/progress.py +9 -9
  121. myokit/gui/source.py +406 -375
  122. myokit/gui/vargrapher.py +2 -12
  123. myokit/lib/deps.py +12 -13
  124. myokit/lib/guess.py +3 -4
  125. myokit/lib/hh.py +20 -18
  126. myokit/lib/markov.py +21 -20
  127. myokit/lib/multi.py +1 -3
  128. myokit/lib/plots.py +20 -9
  129. myokit/pacing.py +0 -3
  130. myokit/pype.py +7 -18
  131. myokit/tests/__init__.py +3 -6
  132. myokit/tests/ansic_event_based_pacing.py +1 -4
  133. myokit/tests/ansic_fixed_form_pacing.py +3 -6
  134. myokit/tests/data/beeler-1977-model-compare-b.mmt +2 -2
  135. myokit/tests/data/clancy-1999-fitting.mmt +1 -0
  136. myokit/tests/test_aux.py +13 -28
  137. myokit/tests/test_cellml_v1_api.py +4 -19
  138. myokit/tests/test_cellml_v1_parser.py +0 -15
  139. myokit/tests/test_cellml_v1_writer.py +0 -9
  140. myokit/tests/test_cellml_v2_api.py +4 -19
  141. myokit/tests/test_cellml_v2_parser.py +0 -15
  142. myokit/tests/test_cellml_v2_writer.py +0 -9
  143. myokit/tests/test_cmodel.py +16 -22
  144. myokit/tests/test_compiler_detection.py +1 -11
  145. myokit/tests/test_component.py +108 -56
  146. myokit/tests/test_config.py +34 -67
  147. myokit/tests/test_datablock.py +1 -9
  148. myokit/tests/test_datalog.py +19 -24
  149. myokit/tests/test_dependency_checking.py +8 -23
  150. myokit/tests/test_expressions.py +0 -9
  151. myokit/tests/test_float.py +1 -5
  152. myokit/tests/test_formats.py +0 -9
  153. myokit/tests/test_formats_axon.py +1 -9
  154. myokit/tests/test_formats_cellml.py +0 -15
  155. myokit/tests/test_formats_channelml.py +0 -15
  156. myokit/tests/test_formats_easyml.py +0 -14
  157. myokit/tests/test_formats_exporters.py +1 -16
  158. myokit/tests/test_formats_expression_writers.py +1 -17
  159. myokit/tests/test_formats_html.py +0 -3
  160. myokit/tests/test_formats_importers.py +1 -16
  161. myokit/tests/test_formats_mathml_content.py +0 -9
  162. myokit/tests/test_formats_mathml_presentation.py +0 -9
  163. myokit/tests/test_formats_opencl.py +0 -10
  164. myokit/tests/test_formats_sbml.py +0 -15
  165. myokit/tests/test_formats_sympy.py +0 -9
  166. myokit/tests/test_formats_wcp.py +1 -3
  167. myokit/tests/test_io.py +27 -27
  168. myokit/tests/test_jacobian_calculator.py +6 -14
  169. myokit/tests/test_jacobian_tracer.py +0 -9
  170. myokit/tests/test_lib_deps.py +0 -9
  171. myokit/tests/test_lib_guess.py +0 -9
  172. myokit/tests/test_lib_hh.py +18 -12
  173. myokit/tests/test_lib_markov.py +21 -13
  174. myokit/tests/test_lib_multi.py +0 -9
  175. myokit/tests/test_lib_plots.py +13 -8
  176. myokit/tests/test_meta.py +0 -3
  177. myokit/tests/test_model.py +390 -96
  178. myokit/tests/test_model_building.py +44 -96
  179. myokit/tests/test_opencl_info.py +5 -14
  180. myokit/tests/test_pacing_factory.py +0 -3
  181. myokit/tests/test_pacing_system_c.py +1 -23
  182. myokit/tests/test_pacing_system_py.py +0 -9
  183. myokit/tests/test_parsing.py +139 -56
  184. myokit/tests/test_progress_reporters.py +0 -3
  185. myokit/tests/test_protocol.py +0 -9
  186. myokit/tests/test_protocol_floating_point.py +1 -10
  187. myokit/tests/test_protocol_time_series.py +82 -0
  188. myokit/tests/test_pype.py +0 -9
  189. myokit/tests/test_quantity.py +0 -9
  190. myokit/tests/test_rhs_benchmarker.py +1 -9
  191. myokit/tests/test_sbml_api.py +27 -42
  192. myokit/tests/test_sbml_parser.py +4 -19
  193. myokit/tests/test_simulation_1d.py +45 -25
  194. myokit/tests/test_simulation_cvodes.py +321 -55
  195. myokit/tests/test_simulation_cvodes_from_disk.py +0 -3
  196. myokit/tests/test_simulation_fiber_tissue.py +39 -12
  197. myokit/tests/test_simulation_log_interval.py +1 -431
  198. myokit/tests/test_simulation_opencl.py +69 -48
  199. myokit/tests/test_simulation_opencl_log_interval.py +1 -3
  200. myokit/tests/test_simulation_opencl_vs_cvode.py +1 -10
  201. myokit/tests/test_simulation_opencl_vs_sim1d.py +1 -10
  202. myokit/tests/test_system_info.py +1 -11
  203. myokit/tests/test_tools.py +0 -9
  204. myokit/tests/test_unit.py +1 -10
  205. myokit/tests/test_user_functions.py +0 -10
  206. myokit/tests/test_variable.py +231 -27
  207. myokit/tools.py +5 -21
  208. myokit/units.py +5 -3
  209. {myokit-1.33.9.dist-info → myokit-1.35.0.dist-info}/METADATA +12 -15
  210. myokit-1.35.0.dist-info/RECORD +391 -0
  211. {myokit-1.33.9.dist-info → myokit-1.35.0.dist-info}/WHEEL +1 -1
  212. {myokit-1.33.9.dist-info → myokit-1.35.0.dist-info}/entry_points.txt +0 -1
  213. myokit/_exec_new.py +0 -15
  214. myokit/_exec_old.py +0 -15
  215. myokit/_sim/cvodesim.c +0 -1551
  216. myokit/_sim/cvodesim.py +0 -674
  217. myokit/_sim/icsim.cpp +0 -563
  218. myokit/_sim/icsim.py +0 -363
  219. myokit/_sim/psim.cpp +0 -656
  220. myokit/_sim/psim.py +0 -493
  221. myokit/lib/common.py +0 -1094
  222. myokit/tests/test_lib_common.py +0 -130
  223. myokit/tests/test_simulation_cvode.py +0 -612
  224. myokit/tests/test_simulation_ic.py +0 -108
  225. myokit/tests/test_simulation_p.py +0 -223
  226. myokit-1.33.9.dist-info/RECORD +0 -403
  227. /myokit/formats/opencl/template/{test → test.sh} +0 -0
  228. {myokit-1.33.9.dist-info → myokit-1.35.0.dist-info}/LICENSE.txt +0 -0
  229. {myokit-1.33.9.dist-info → myokit-1.35.0.dist-info}/top_level.txt +0 -0
myokit/gui/source.py CHANGED
@@ -6,12 +6,11 @@
6
6
  # This file is part of Myokit.
7
7
  # See http://myokit.org for copyright, sharing, and licensing details.
8
8
  #
9
- from __future__ import absolute_import, division
10
- from __future__ import print_function, unicode_literals
11
-
12
9
  import myokit
10
+
13
11
  from myokit.gui import Qt, QtCore, QtGui, QtWidgets
14
12
 
13
+
15
14
  # GUI components
16
15
  # Constants
17
16
  SPACE = ' '
@@ -54,7 +53,7 @@ COLOR_SELECTED_LINE = QtGui.QColor(238, 238, 238)
54
53
 
55
54
  def _check_for_dark_mode(palette):
56
55
  """
57
- Checks the default editor background color, and adjusts the colour scheme
56
+ Checks the default editor background color, and adjusts the color scheme
58
57
  if it looks like dark-mode is enabled.
59
58
  """
60
59
  c = palette.base().color()
@@ -68,7 +67,7 @@ def _check_for_dark_mode(palette):
68
67
  STYLE_ANNOT_KEY.setForeground(QtGui.QColor(0, 31, 231))
69
68
  STYLE_ANNOT_VAL.setForeground(QtGui.QColor(57, 115, 214))
70
69
  STYLE_KEYWORD_1.setForeground(QtGui.QColor(0, 128, 0))
71
- STYLE_KEYWORD_1.setFontWeight(QtGui.QFont.Bold)
70
+ STYLE_KEYWORD_1.setFontWeight(QtGui.QFont.Weight.Bold)
72
71
  STYLE_KEYWORD_2.setForeground(QtGui.QColor(0, 128, 128))
73
72
  STYLE_LITERAL.setForeground(QtGui.QColor(255, 20, 215))
74
73
  STYLE_INLINE_UNIT.setForeground(QtGui.QColor(128, 0, 128))
@@ -78,7 +77,7 @@ def _check_for_dark_mode(palette):
78
77
  STYLE_ANNOT_KEY.setForeground(QtGui.QColor(179, 179, 179))
79
78
  STYLE_ANNOT_VAL.setForeground(QtGui.QColor(171, 177, 205))
80
79
  STYLE_KEYWORD_1.setForeground(QtGui.QColor(10, 195, 87))
81
- STYLE_KEYWORD_1.setFontWeight(QtGui.QFont.Bold)
80
+ STYLE_KEYWORD_1.setFontWeight(QtGui.QFont.Weight.Bold)
82
81
  STYLE_KEYWORD_2.setForeground(QtGui.QColor(10, 195, 87))
83
82
  STYLE_LITERAL.setForeground(QtGui.QColor(255, 223, 12))
84
83
  STYLE_INLINE_UNIT.setForeground(QtGui.QColor(168, 152, 33))
@@ -97,7 +96,7 @@ class Editor(QtWidgets.QPlainTextEdit):
97
96
  status bar.
98
97
  """
99
98
  def __init__(self, parent=None):
100
- super(Editor, self).__init__(parent)
99
+ super().__init__(parent)
101
100
 
102
101
  # Current style
103
102
  self._palette = QtGui.QGuiApplication.palette()
@@ -115,7 +114,12 @@ class Editor(QtWidgets.QPlainTextEdit):
115
114
  self.cursor_changed()
116
115
 
117
116
  # Line position
118
- self._line_offset = self.fontMetrics().width(' ' * 79)
117
+ try:
118
+ # https://doc.qt.io/qt-5/qfontmetrics.html#horizontalAdvance
119
+ # Qt 5.5.11 and onwards
120
+ self._line_offset = self.fontMetrics().horizontalAdvance(' ' * 79)
121
+ except AttributeError:
122
+ self._line_offset = self.fontMetrics().width(' ' * 79)
119
123
 
120
124
  # Number of blocks in page up/down
121
125
  self._blocks_per_page = 1
@@ -131,7 +135,7 @@ class Editor(QtWidgets.QPlainTextEdit):
131
135
  selection = QtWidgets.QTextEdit.ExtraSelection()
132
136
  selection.format.setBackground(COLOR_SELECTED_LINE)
133
137
  selection.format.setProperty(
134
- QtGui.QTextFormat.FullWidthSelection, True)
138
+ QtGui.QTextFormat.Property.FullWidthSelection, True)
135
139
  selection.cursor = self.textCursor()
136
140
  selection.cursor.clearSelection()
137
141
  extra_selections.append(selection)
@@ -143,13 +147,14 @@ class Editor(QtWidgets.QPlainTextEdit):
143
147
  pos = cursor.position()
144
148
  bracket = None
145
149
  if not cursor.atEnd():
146
- cursor.setPosition(pos + 1, QtGui.QTextCursor.KeepAnchor)
150
+ cursor.setPosition(
151
+ pos + 1, QtGui.QTextCursor.MoveMode.KeepAnchor)
147
152
  text = cursor.selectedText()
148
153
  if text in BRACKETS:
149
154
  bracket = cursor
150
155
  elif bracket is None and not cursor.atStart():
151
156
  cursor.setPosition(pos - 1)
152
- cursor.setPosition(pos, QtGui.QTextCursor.KeepAnchor)
157
+ cursor.setPosition(pos, QtGui.QTextCursor.MoveMode.KeepAnchor)
153
158
  text = cursor.selectedText()
154
159
  if text in BRACKETS:
155
160
  bracket = cursor
@@ -163,10 +168,10 @@ class Editor(QtWidgets.QPlainTextEdit):
163
168
  if text in BRACKETS_CLOSE:
164
169
  other = doc.find(
165
170
  text, start - 1,
166
- QtGui.QTextDocument.FindBackward)
171
+ QtGui.QTextDocument.FindFlag.FindBackward)
167
172
  match = doc.find(
168
173
  BRACKETS[text], start - 1,
169
- QtGui.QTextDocument.FindBackward)
174
+ QtGui.QTextDocument.FindFlag.FindBackward)
170
175
  else:
171
176
  other = doc.find(text, start)
172
177
  match = doc.find(BRACKETS[text], start)
@@ -222,11 +227,20 @@ class Editor(QtWidgets.QPlainTextEdit):
222
227
  # Set font
223
228
  self.setFont(FONT)
224
229
  # Set frame
225
- self.setFrameStyle(QtWidgets.QFrame.WinPanel | QtWidgets.QFrame.Sunken)
230
+ self.setFrameStyle(
231
+ QtWidgets.QFrame.Shape.WinPanel | QtWidgets.QFrame.Shadow.Sunken)
226
232
  # Disable wrapping
227
- self.setLineWrapMode(self.NoWrap)
233
+ self.setLineWrapMode(QtWidgets.QPlainTextEdit.LineWrapMode.NoWrap)
228
234
  # Set tab width (if ever seen) to 4 spaces
229
- self.setTabStopWidth(self.fontMetrics().width(' ' * 4))
235
+ try:
236
+ # https://doc.qt.io/qt-5/qtextedit-obsolete.html
237
+ # https://doc.qt.io/qt-5/qfontmetrics.html#horizontalAdvance
238
+ # Qt 5.10/5.11 and onwards
239
+ ts = self.fontMetrics().horizontalAdvance(' ' * 4)
240
+ self.setTabStopDistance(ts)
241
+ except AttributeError:
242
+ ts = self.fontMetrics().width(' ' * 4)
243
+ self.setTabStopWidth(ts)
230
244
 
231
245
  def get_text(self):
232
246
  """ Returns the text in this editor. """
@@ -242,6 +256,11 @@ class Editor(QtWidgets.QPlainTextEdit):
242
256
 
243
257
  def keyPressEvent(self, event):
244
258
  """ Qt event: A key was pressed. """
259
+ K = Qt.Key
260
+ KM = Qt.KeyboardModifier
261
+ MM = QtGui.QTextCursor.MoveMode
262
+ MO = QtGui.QTextCursor.MoveOperation
263
+
245
264
  # Get key and modifiers
246
265
  key = event.key()
247
266
  mod = event.modifiers()
@@ -251,11 +270,13 @@ class Editor(QtWidgets.QPlainTextEdit):
251
270
  # MetaModifier (i.e. super key)
252
271
  # KeyPadModifier (button is part of keypad)
253
272
  # GroupSwitchModifier (x11 thing)
273
+
254
274
  # Ignore the keypad modifier, we don't care!
255
- if mod & Qt.KeypadModifier:
256
- mod = mod ^ Qt.KeypadModifier # xor!
275
+ if mod & KM.KeypadModifier:
276
+ mod = mod ^ KM.KeypadModifier # xor!
277
+
257
278
  # Actions per key/modifier combination
258
- if key == Qt.Key_Tab and mod == Qt.NoModifier:
279
+ if key == K.Key_Tab and mod == KM.NoModifier:
259
280
  # Indent
260
281
  cursor = self.textCursor()
261
282
  start, end = cursor.selectionStart(), cursor.selectionEnd()
@@ -275,7 +296,7 @@ class Editor(QtWidgets.QPlainTextEdit):
275
296
  pos = cursor.positionInBlock()
276
297
  cursor.insertText((TABS - pos % TABS) * SPACE)
277
298
 
278
- elif key == Qt.Key_Backtab and mod == Qt.ShiftModifier:
299
+ elif key == K.Key_Backtab and mod == KM.ShiftModifier:
279
300
  # Dedent all lines in selection (or single line if no selection)
280
301
  '''
281
302
  cursor = self.textCursor()
@@ -295,11 +316,11 @@ class Editor(QtWidgets.QPlainTextEdit):
295
316
  p2 = p1 + min(4, len(t) - len(t.lstrip()))
296
317
  c = self.textCursor()
297
318
  c.setPosition(p1)
298
- c.setPosition(p2, QtGui.QTextCursor.KeepAnchor)
319
+ c.setPosition(p2, MM.KeepAnchor)
299
320
  c.removeSelectedText()
300
321
  cursor.endEditBlock()
301
322
  '''
302
- # This silly method is required because of a bug in qt4/qt5
323
+ # This silly method is required because of a bug in qt5 (and 6?)
303
324
  cursor = self.textCursor()
304
325
  start, end = cursor.selectionStart(), cursor.selectionEnd()
305
326
  first = self.document().findBlock(start)
@@ -324,22 +345,21 @@ class Editor(QtWidgets.QPlainTextEdit):
324
345
  cursor.beginEditBlock()
325
346
  cursor.setPosition(first.position())
326
347
  cursor.setPosition(
327
- last.position() + last.length() - 1,
328
- QtGui.QTextCursor.KeepAnchor)
348
+ last.position() + last.length() - 1, MM.KeepAnchor)
329
349
  cursor.removeSelectedText()
330
350
  cursor.insertText('\n'.join(new_text))
331
351
  cursor.endEditBlock()
332
352
  # Set new cursor
333
353
  cursor.setPosition(new_start)
334
- cursor.setPosition(new_end, QtGui.QTextCursor.KeepAnchor)
354
+ cursor.setPosition(new_end, MM.KeepAnchor)
335
355
  self.setTextCursor(cursor)
336
356
 
337
- elif key == Qt.Key_Enter or key == Qt.Key_Return:
357
+ elif key == K.Key_Enter or key == K.Key_Return:
338
358
  # Enter/Return with modifier is overruled here to mean nothing
339
359
  # This is very important as the default for shift-enter is to
340
360
  # start a new line within the same block (this can't happen with
341
361
  # copy-pasting, so it's safe to just catch it here).
342
- if mod == Qt.NoModifier:
362
+ if mod == KM.NoModifier:
343
363
  # "Smart" enter:
344
364
  # - If selection, selection is deleted
345
365
  # - Else, autoindenting is performed
@@ -361,8 +381,8 @@ class Editor(QtWidgets.QPlainTextEdit):
361
381
  # Scroll if necessary
362
382
  self.ensureCursorVisible()
363
383
 
364
- elif key == Qt.Key_Home and (
365
- mod == Qt.NoModifier or mod == Qt.ShiftModifier):
384
+ elif key == K.Key_Home and (
385
+ mod == KM.NoModifier or mod == KM.ShiftModifier):
366
386
  # Plain home button: move to start of line
367
387
  # If Control is used: Jump to start of document
368
388
  # Ordinary home button: Jump to first column or first
@@ -385,25 +405,22 @@ class Editor(QtWidgets.QPlainTextEdit):
385
405
  # Smart up/down:
386
406
  self._last_column = indent
387
407
  # If Shift is used: only move position (keep anchor, i.e. select)
388
- anchor = (
389
- QtGui.QTextCursor.KeepAnchor if mod == Qt.ShiftModifier
390
- else QtGui.QTextCursor.MoveAnchor)
391
- cursor.setPosition(newpos, anchor)
408
+ cursor.setPosition(
409
+ newpos,
410
+ MM.KeepAnchor if mod == KM.ShiftModifier else MM.MoveAnchor)
392
411
  self.setTextCursor(cursor)
393
412
 
394
- elif key == Qt.Key_Home and (
395
- mod == Qt.ControlModifier
396
- or mod == Qt.ControlModifier & Qt.ShiftModifier):
413
+ elif key == K.Key_Home and (
414
+ mod == KM.ControlModifier
415
+ or mod == KM.ControlModifier & KM.ShiftModifier):
397
416
  # Move to start of document
398
417
  # If Shift is used: only move position (keep anchor, i.e. select)
399
- anchor = (
400
- QtGui.QTextCursor.KeepAnchor if mod == Qt.ShiftModifier
401
- else QtGui.QTextCursor.MoveAnchor)
402
418
  cursor = self.textCursor()
403
- cursor.setPosition(0, anchor)
419
+ cursor.setPosition(
420
+ 0, MM.KeepAnchor if mod == KM.ShiftModifier else MM.MoveAnchor)
404
421
  self.setTextCursor(cursor)
405
422
 
406
- elif key in (Qt.Key_Up, Qt.Key_Down) and mod == Qt.AltModifier:
423
+ elif key in (K.Key_Up, K.Key_Down) and mod == KM.AltModifier:
407
424
  # Move selected lines up or down
408
425
  # Get current selection
409
426
  doc = self.document()
@@ -420,7 +437,7 @@ class Editor(QtWidgets.QPlainTextEdit):
420
437
  block2 = block2.previous() # always valid
421
438
  block2 = block1 if start == end else doc.findBlock(end)
422
439
  # Check if we can move
423
- if key == Qt.Key_Up:
440
+ if key == K.Key_Up:
424
441
  if not block1.previous().isValid():
425
442
  return
426
443
  elif not block2.next().isValid():
@@ -429,30 +446,28 @@ class Editor(QtWidgets.QPlainTextEdit):
429
446
  b1pos = block1.position()
430
447
  cursor.beginEditBlock()
431
448
  cursor.setPosition(b1pos)
432
- cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
433
- cursor.movePosition(
434
- QtGui.QTextCursor.EndOfLine, QtGui.QTextCursor.KeepAnchor)
449
+ cursor.setPosition(end, MM.KeepAnchor)
450
+ cursor.movePosition(MO.EndOfLine, MM.KeepAnchor)
435
451
  line = cursor.selectedText()
436
452
  size = cursor.selectionEnd() - cursor.selectionStart()
437
453
  cursor.removeSelectedText()
438
- if key == Qt.Key_Up:
454
+ if key == K.Key_Up:
439
455
  cursor.deletePreviousChar()
440
- cursor.movePosition(QtGui.QTextCursor.StartOfLine)
456
+ cursor.movePosition(MO.StartOfLine)
441
457
  cursor.insertText(line + '\n')
442
- cursor.movePosition(QtGui.QTextCursor.Left)
458
+ cursor.movePosition(MO.Left)
443
459
  else:
444
460
  cursor.deleteChar()
445
- cursor.movePosition(QtGui.QTextCursor.EndOfLine)
461
+ cursor.movePosition(MO.EndOfLine)
446
462
  cursor.insertText('\n' + line)
447
463
  cursor.endEditBlock()
448
464
  # Cursor is at the end of the moved lines.
449
465
  # Set moved lines as selection
450
- cursor.movePosition(
451
- QtGui.QTextCursor.Left, QtGui.QTextCursor.KeepAnchor, size)
466
+ cursor.movePosition(MO.Left, MM.KeepAnchor, size)
452
467
  self.setTextCursor(cursor)
453
468
 
454
- elif key in (Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp, Qt.Key_PageDown) \
455
- and (mod == Qt.NoModifier or mod == Qt.ShiftModifier):
469
+ elif key in (K.Key_Up, K.Key_Down, K.Key_PageUp, K.Key_PageDown) \
470
+ and (mod == KM.NoModifier or mod == KM.ShiftModifier):
456
471
  # Move cursor up/down
457
472
  # Maintain the column position, even when the current row doesn't
458
473
  # have as many characters. Reset this behavior as soon as a
@@ -460,13 +475,12 @@ class Editor(QtWidgets.QPlainTextEdit):
460
475
  # changed.
461
476
  # Set up operation
462
477
  anchor = (
463
- QtGui.QTextCursor.KeepAnchor if mod == Qt.ShiftModifier
464
- else QtGui.QTextCursor.MoveAnchor)
465
- operation = \
466
- QtGui.QTextCursor.PreviousBlock if key in (
467
- Qt.Key_Up, Qt.Key_PageUp) else QtGui.QTextCursor.NextBlock
468
- n = 1 if key in (Qt.Key_Up, Qt.Key_Down) else (
478
+ MM.KeepAnchor if mod == KM.ShiftModifier else MM.MoveAnchor)
479
+ operation = (MO.PreviousBlock if key in (K.Key_Up, K.Key_PageUp)
480
+ else MO.NextBlock)
481
+ n = 1 if key in (K.Key_Up, K.Key_Down) else (
469
482
  self._blocks_per_page - 3)
483
+
470
484
  # Move
471
485
  cursor = self.textCursor()
472
486
  if self._last_column is None:
@@ -478,22 +492,22 @@ class Editor(QtWidgets.QPlainTextEdit):
478
492
  else:
479
493
  # Up/Down beyond document start/end? Move cursor to document
480
494
  # start/end and update last column
481
- if operation == QtGui.QTextCursor.NextBlock:
482
- cursor.movePosition(QtGui.QTextCursor.EndOfBlock, anchor)
495
+ if operation == MO.NextBlock:
496
+ cursor.movePosition(MO.EndOfBlock, anchor)
483
497
  else:
484
- cursor.movePosition(QtGui.QTextCursor.StartOfBlock, anchor)
498
+ cursor.movePosition(MO.StartOfBlock, anchor)
485
499
  self._last_column = cursor.positionInBlock()
486
500
  self.setTextCursor(cursor)
487
501
 
488
- elif key in (Qt.Key_Left, Qt.Key_Right, Qt.Key_End) and not (
489
- mod & Qt.AltModifier):
502
+ elif key in (K.Key_Left, K.Key_Right, K.Key_End) and not (
503
+ mod & KM.AltModifier):
490
504
  # Allow all modifiers except alt
491
505
  # Reset smart up/down behavior
492
506
  self._last_column = None
493
507
  # Pass to parent class
494
- super(Editor, self).keyPressEvent(event)
508
+ super().keyPressEvent(event)
495
509
 
496
- elif key == Qt.Key_Insert and mod == Qt.NoModifier:
510
+ elif key == K.Key_Insert and mod == KM.NoModifier:
497
511
  # Insert/replace
498
512
  self.setOverwriteMode(not self.overwriteMode())
499
513
 
@@ -530,20 +544,26 @@ class Editor(QtWidgets.QPlainTextEdit):
530
544
  # Shift-Enter Starts new line within the same block!
531
545
  # Definitely removed
532
546
  # Ctrl-i Undocumented, but inserts tab...
533
- ctrl_ignore = (Qt.Key_K, Qt.Key_I)
534
- if mod == Qt.ControlModifier and key in ctrl_ignore:
547
+ ctrl_ignore = (K.Key_K, K.Key_I)
548
+ if mod == KM.ControlModifier and key in ctrl_ignore:
535
549
  # Control-K: ignore
536
550
  pass
537
- elif key == Qt.Key_Up or key == Qt.Key_Down:
551
+ elif key == K.Key_Up or key == K.Key_Down:
538
552
  # Up/down with modifiers: ignore
539
553
  pass
540
554
  else:
541
555
  # Let parent class handle it
542
- super(Editor, self).keyPressEvent(event)
556
+ super().keyPressEvent(event)
543
557
 
544
558
  def _line_number_area_width(self):
545
559
  """ Returns the required width for the number area. """
546
- return 8 + self.fontMetrics().width(str(max(1, self.blockCount())))
560
+ text = str(max(1, self.blockCount()))
561
+ try:
562
+ # https://doc.qt.io/qt-5/qfontmetrics.html#horizontalAdvance
563
+ # Qt 5.5.11 and onwards
564
+ return 8 + self.fontMetrics().horizontalAdvance(text)
565
+ except AttributeError:
566
+ return 8 + self.fontMetrics().width(text)
547
567
 
548
568
  def _line_number_area_paint(self, area, event):
549
569
  """ Repaints the line number area. """
@@ -573,8 +593,8 @@ class Editor(QtWidgets.QPlainTextEdit):
573
593
  while block.isValid() and btop <= ebot:
574
594
  count += 1
575
595
  if block.isVisible() and bbot >= etop:
576
- painter.drawText(
577
- 0, btop, width - 4, height, Qt.AlignRight, str(count))
596
+ painter.drawText(0, btop, width - 4, height,
597
+ Qt.AlignmentFlag.AlignRight, str(count))
578
598
  block = block.next()
579
599
  btop = bbot
580
600
  bbot += int(self.blockBoundingRect(block).height())
@@ -583,7 +603,7 @@ class Editor(QtWidgets.QPlainTextEdit):
583
603
  """ Paints this editor. """
584
604
 
585
605
  # Paint the editor
586
- super(Editor, self).paintEvent(e)
606
+ super().paintEvent(e)
587
607
 
588
608
  # Paint a line between the editor and the line number area
589
609
  x = int(
@@ -610,7 +630,7 @@ class Editor(QtWidgets.QPlainTextEdit):
610
630
 
611
631
  def resizeEvent(self, event):
612
632
  """ Qt event: Editor is resized. """
613
- super(Editor, self).resizeEvent(event)
633
+ super().resizeEvent(event)
614
634
  # Update line number area
615
635
  rect = self.contentsRect()
616
636
  self._line_number_area.setGeometry(
@@ -677,7 +697,8 @@ class Editor(QtWidgets.QPlainTextEdit):
677
697
  for block in blocks:
678
698
  p = block.position() + indent
679
699
  cursor.setPosition(p)
680
- cursor.setPosition(p + 1, QtGui.QTextCursor.KeepAnchor)
700
+ cursor.setPosition(
701
+ p + 1, QtGui.QTextCursor.MoveMode.KeepAnchor)
681
702
  cursor.removeSelectedText()
682
703
  else:
683
704
 
@@ -686,7 +707,8 @@ class Editor(QtWidgets.QPlainTextEdit):
686
707
  n = len(block.text())
687
708
  if len(block.text()) < indent:
688
709
  cursor.setPosition(p)
689
- cursor.setPosition(p + n, QtGui.QTextCursor.KeepAnchor)
710
+ cursor.setPosition(
711
+ p + n, QtGui.QTextCursor.MoveMode.KeepAnchor)
690
712
  cursor.removeSelectedText()
691
713
  cursor.insertText(' ' * indent + '#')
692
714
  else:
@@ -705,8 +727,8 @@ class Editor(QtWidgets.QPlainTextEdit):
705
727
  b = len(t.rstrip())
706
728
  if a > b:
707
729
  cursor.setPosition(block.position() + b)
708
- cursor.setPosition(
709
- block.position() + a, QtGui.QTextCursor.KeepAnchor)
730
+ cursor.setPosition(block.position() + a,
731
+ QtGui.QTextCursor.MoveMode.KeepAnchor)
710
732
  cursor.removeSelectedText()
711
733
  block = block.next()
712
734
  cursor.endEditBlock()
@@ -722,7 +744,7 @@ class LineNumberArea(QtWidgets.QWidget):
722
744
  """
723
745
 
724
746
  def __init__(self, editor):
725
- super(LineNumberArea, self).__init__(editor)
747
+ super().__init__(editor)
726
748
  self._editor = editor
727
749
  self._editor.blockCountChanged.connect(self.update_width)
728
750
  self._editor.updateRequest.connect(self.update_contents)
@@ -766,7 +788,7 @@ class FindReplaceWidget(QtWidgets.QWidget):
766
788
  find_action = QtCore.Signal(str)
767
789
 
768
790
  def __init__(self, parent, editor):
769
- super(FindReplaceWidget, self).__init__(parent)
791
+ super().__init__(parent)
770
792
  self._editor = editor
771
793
 
772
794
  # Create widgets
@@ -789,7 +811,8 @@ class FindReplaceWidget(QtWidgets.QWidget):
789
811
  text_layout.addWidget(self._search_field, 0, 1)
790
812
  text_layout.addWidget(self._replace_label, 1, 0)
791
813
  text_layout.addWidget(self._replace_field, 1, 1)
792
- check_layout = QtWidgets.QBoxLayout(QtWidgets.QBoxLayout.TopToBottom)
814
+ check_layout = QtWidgets.QBoxLayout(
815
+ QtWidgets.QBoxLayout.Direction.TopToBottom)
793
816
  check_layout.addWidget(self._case_check)
794
817
  check_layout.addWidget(self._whole_check)
795
818
  button_layout = QtWidgets.QGridLayout()
@@ -797,7 +820,8 @@ class FindReplaceWidget(QtWidgets.QWidget):
797
820
  button_layout.addWidget(self._replace_button, 0, 2)
798
821
  button_layout.addWidget(self._find_button, 0, 3)
799
822
 
800
- layout = QtWidgets.QBoxLayout(QtWidgets.QBoxLayout.TopToBottom)
823
+ layout = QtWidgets.QBoxLayout(
824
+ QtWidgets.QBoxLayout.Direction.TopToBottom)
801
825
  layout.addLayout(text_layout)
802
826
  layout.addLayout(check_layout)
803
827
  layout.addLayout(button_layout)
@@ -814,11 +838,11 @@ class FindReplaceWidget(QtWidgets.QWidget):
814
838
  if query == '':
815
839
  self.find_action.emit('No query set')
816
840
  return
817
- flags = 0x0
841
+ flags = QtGui.QTextDocument.FindFlag(0)
818
842
  if self._case_check.isChecked():
819
- flags |= QtGui.QTextDocument.FindCaseSensitively
843
+ flags |= QtGui.QTextDocument.FindFlag.FindCaseSensitively
820
844
  if self._whole_check.isChecked():
821
- flags |= QtGui.QTextDocument.FindWholeWords
845
+ flags |= QtGui.QTextDocument.FindFlag.FindWholeWords
822
846
  if flags:
823
847
  found = self._editor.find(query, flags)
824
848
  else:
@@ -867,11 +891,11 @@ class FindReplaceWidget(QtWidgets.QWidget):
867
891
  if query == '':
868
892
  self.find_action.emit('No query set')
869
893
  return
870
- flags = 0x0
894
+ flags = QtGui.QTextDocument.FindFlag(0)
871
895
  if self._case_check.isChecked():
872
- flags |= QtGui.QTextDocument.FindCaseSensitively
896
+ flags |= QtGui.QTextDocument.FindFlag.FindCaseSensitively
873
897
  if self._whole_check.isChecked():
874
- flags |= QtGui.QTextDocument.FindWholeWords
898
+ flags |= QtGui.QTextDocument.FindFlag.FindWholeWords
875
899
  n = 0
876
900
  found = True
877
901
  scrollpos = self._editor.verticalScrollBar().value()
@@ -917,10 +941,10 @@ class FindReplaceWidget(QtWidgets.QWidget):
917
941
  def keyPressEvent(self, event):
918
942
  """ Qt event: A key-press reaches the widget. """
919
943
  key = event.key()
920
- if key == Qt.Key_Enter or key == Qt.Key_Return:
944
+ if key == Qt.Key.Key_Enter or key == Qt.Key.Key_Return:
921
945
  self.action_find()
922
946
  else:
923
- super(FindReplaceWidget, self).keyPressEvent(event)
947
+ super().keyPressEvent(event)
924
948
 
925
949
  def load_config(self, config, section):
926
950
  """
@@ -956,144 +980,153 @@ class ModelHighlighter(QtGui.QSyntaxHighlighter):
956
980
  ANNOT_KEYS = ['in', 'bind', 'label']
957
981
 
958
982
  def __init__(self, document):
959
- super(ModelHighlighter, self).__init__(document)
983
+ super().__init__(document)
960
984
 
961
985
  # Expressions used to find strings & comments
962
- self._string_start = QtCore.QRegExp(r'"""')
963
- self._string_stop = QtCore.QRegExp(r'"""')
964
- self._comment_start = QtCore.QRegExp(r'#[^\n]*')
986
+ R = QtCore.QRegularExpression
987
+ self._string = R(r'"""')
965
988
 
966
989
  # Headers
967
990
  name = r'[a-zA-Z]+[a-zA-Z0-9_]*'
968
- self._rule_head = QtCore.QRegExp(r'^(\s*)\[{1,2}' + name + '\]{1,2}')
991
+ self._rule_head = R(r'^\s*(\[{1,2}' + name + '\]{1,2})')
969
992
 
970
993
  # Simple rules
971
994
  self._rules = []
972
995
 
973
996
  # Numbers
974
- pattern = QtCore.QRegExp(r'\b[+-]?[0-9]*\.?[0-9]+([eE][+-]?[0-9]+)?\b')
997
+ pattern = R(r'\b[+-]?[0-9]*\.?[0-9]+([eE][+-]?[0-9]+)?\b')
975
998
  self._rules.append((pattern, STYLE_LITERAL))
976
- unit = r'\[[a-zA-Z0-9/^]+\]'
977
- self._rules.append((QtCore.QRegExp(unit), STYLE_INLINE_UNIT))
999
+ unit = r'\[([a-zA-Z0-9/^-]|\*)+\]'
1000
+ self._rules.append((R(unit), STYLE_INLINE_UNIT))
978
1001
 
979
1002
  # Keywords
980
1003
  for keyword in self.KEYWORD_1:
981
- pattern = QtCore.QRegExp(r'\b' + keyword + r'\b')
982
- self._rules.append((pattern, STYLE_KEYWORD_1))
1004
+ self._rules.append((R(r'\b' + keyword + r'\b'), STYLE_KEYWORD_1))
983
1005
  for keyword in self.KEYWORD_2:
984
- pattern = QtCore.QRegExp(r'\b' + keyword + r'\b')
985
- self._rules.append((pattern, STYLE_KEYWORD_2))
1006
+ self._rules.append((R(r'\b' + keyword + r'\b'), STYLE_KEYWORD_2))
986
1007
 
987
1008
  # Meta-data coloring
988
1009
  self._rules_labels = [
989
- QtCore.QRegExp(r'(\s*)(bind)\s+(' + name + ')'),
990
- QtCore.QRegExp(r'(\s*)(label)\s+(' + name + ')'),
1010
+ R(r'(\s*)(bind)\s+(' + name + ')'),
1011
+ R(r'(\s*)(label)\s+(' + name + ')'),
991
1012
  ]
992
- self._rule_meta_key = QtCore.QRegExp(name + r':')
993
- self._rule_meta_val = QtCore.QRegExp(r':(\s*)(.+)')
994
- self._rule_var_unit = QtCore.QRegExp(r'^(\s*)(in)(\s*)(' + unit + ')')
1013
+ self._rule_meta = R(r'^\s*(' + name + r':)(\s*)(.+)')
1014
+ self._rule_var_unit = R(r'^(\s*)(in)(\s*)(' + unit + ')')
995
1015
 
996
- # Comments
997
- # Note: For some reason this can _not_ use self._comment_start
998
- self._comments = QtCore.QRegExp(r'#[^\n]*')
1016
+ # Comment
1017
+ self._comment = R(r'#')
1018
+
1019
+ def _highlight_ok(self, strings, start, length):
1020
+ """ Checks if the string ``start`` to ``length`` needs formatted. """
1021
+ for lo, hi in strings:
1022
+ if lo <= start < hi or lo <= start + length < hi:
1023
+ return False
1024
+ return True
999
1025
 
1000
1026
  def highlightBlock(self, text):
1001
1027
  """ Qt: Called whenever a block should be highlighted. """
1002
- # Simple rules
1003
- for (pattern, style) in self._rules:
1004
- i = pattern.indexIn(text)
1005
- while i >= 0:
1006
- # Note: Can't use matchedLength() here because it does quirky
1007
- # things with the subgroup for the numbers regex.
1008
- n = len(pattern.cap(0))
1009
- self.setFormat(i, n, style)
1010
- i = pattern.indexIn(text, i + n)
1011
-
1012
- # Model and component headers
1013
- i = self._rule_head.indexIn(text)
1014
- while i >= 0:
1015
- m = len(self._rule_head.cap(1))
1016
- n = len(self._rule_head.cap(0))
1017
- self.setFormat(i + m, n - m, STYLE_HEADER)
1018
- i = self._rule_head.indexIn(text, i + n)
1019
-
1020
- # Annotations
1021
- # Labels
1022
- for pattern in self._rules_labels:
1023
- i = pattern.indexIn(text)
1024
- while i >= 0:
1025
- n0 = len(pattern.cap(0))
1026
- n1 = len(pattern.cap(1))
1027
- n2 = len(pattern.cap(2))
1028
- n3 = len(pattern.cap(3))
1029
- self.setFormat(i + n1, n2, STYLE_ANNOT_KEY)
1030
- self.setFormat(i + n0 - n3, n3, STYLE_ANNOT_VAL)
1031
- i = pattern.indexIn(text, i + n0)
1032
-
1033
- # Units
1034
- i = self._rule_var_unit.indexIn(text)
1035
- if i == 0:
1036
- n1 = len(self._rule_var_unit.cap(1))
1037
- n2 = len(self._rule_var_unit.cap(2))
1038
- n3 = len(self._rule_var_unit.cap(3))
1039
- n4 = len(self._rule_var_unit.cap(4))
1040
- self.setFormat(n1, n2, STYLE_ANNOT_KEY)
1041
- self.setFormat(n1 + n2 + n3, n4, STYLE_ANNOT_VAL)
1042
-
1043
- # Meta properties
1044
- i = self._rule_meta_key.indexIn(text)
1045
- if i >= 0:
1046
- n0 = len(self._rule_meta_key.cap(0))
1047
- self.setFormat(i, n0, STYLE_ANNOT_KEY)
1048
- i = self._rule_meta_val.indexIn(text)
1049
- if i >= 0:
1050
- n1 = len(self._rule_meta_val.cap(1))
1051
- n2 = len(self._rule_meta_val.cap(2))
1052
- self.setFormat(i, 1, STYLE_ANNOT_KEY)
1053
- self.setFormat(1 + i + n1, n2, STYLE_ANNOT_VAL)
1054
-
1055
- # Comments (overrule all other formatting except multi-line strings)
1056
- i = self._comments.indexIn(text)
1057
- while i >= 0:
1058
- n = len(self._comments.cap(0))
1059
- self.setFormat(i, n, STYLE_COMMENT)
1060
- i = self._comments.indexIn(text, i + n)
1061
-
1062
- # Multi-line strings
1063
- # Block states:
1064
- # 0 Normal
1065
- # 1 Multi-line string (meta)
1028
+
1029
+ # To avoid formatting within strings each is stored as a (start, end).
1030
+ strings = []
1031
+ # If the start has been handled, set the offset.
1032
+ offset = 0
1033
+ # If the end has been handled, chop it off the string
1034
+
1035
+ # Multi-line strings are done first, because they overrule a lot of
1036
+ # things and we can skip formatting if we're inside one.
1037
+ # Block states: 0=No string, 1=A """ string
1066
1038
  self.setCurrentBlockState(0)
1067
1039
 
1068
- # Check state of previous block
1069
- if self.previousBlockState() != 1:
1070
- # Normal block
1071
- next = start = self._string_start.indexIn(text)
1072
- comment = self._comment_start.indexIn(text)
1073
- if next >= 0 and (comment < 0 or comment > next):
1074
- next += self._string_start.matchedLength()
1040
+ # Continuing a multi-line string?
1041
+ if self.previousBlockState() == 1:
1042
+ # Search for string stop
1043
+ ms = self._string.match(text)
1044
+ if ms.hasMatch():
1045
+ # Terminate the multi-line string
1046
+ offset = ms.capturedEnd(0)
1047
+ self.setFormat(0, offset, STYLE_ANNOT_VAL)
1075
1048
  else:
1076
- start = next = -1
1049
+ # Whole line in the string
1050
+ self.setCurrentBlockState(1)
1051
+ self.setFormat(0, len(text), STYLE_ANNOT_VAL)
1052
+ return
1077
1053
  else:
1078
- start = next = 0
1054
+ # Search for string start
1055
+ ms = self._string.match(text)
1056
+ if ms.hasMatch():
1057
+ # Potential start, but check that it's not commented out
1058
+ start = ms.capturedStart()
1059
+ mc = self._comment.match(text)
1060
+ if not (mc.hasMatch() and mc.capturedStart() < start):
1061
+ # Definitely a string start. See if it ends on this line
1062
+ me = self._string.match(text, offset=ms.capturedEnd())
1063
+ if me.hasMatch():
1064
+ # Terminate the single-line string
1065
+ end = me.capturedEnd()
1066
+ self.setFormat(start, end - start, STYLE_ANNOT_VAL)
1067
+ strings.append((start, end))
1068
+ else:
1069
+ # Multi-line string
1070
+ self.setCurrentBlockState(1)
1071
+ self.setFormat(start, len(text), STYLE_ANNOT_VAL)
1072
+
1073
+ # Comment
1074
+ i = self._comment.globalMatch(text, offset=offset)
1075
+ while i.hasNext():
1076
+ m = i.next()
1077
+ x = m.capturedStart()
1078
+ if self._highlight_ok(strings, x, 1):
1079
+ self.setFormat(x, len(text) - x, STYLE_COMMENT)
1080
+ text = text[:x]
1081
+ break
1079
1082
 
1080
- # Find any occurrences of string start / stop
1081
- while next >= 0:
1082
- stop = self._string_stop.indexIn(text, next)
1083
- if stop < 0:
1084
- # Continuing multi-line string
1085
- self.setCurrentBlockState(1)
1086
- self.setFormat(start, len(text) - start, STYLE_ANNOT_VAL)
1087
- next = -1
1088
- else:
1089
- # Ending single-line or multi-line string
1090
- # Format until stop token
1091
- next = stop = stop + self._string_stop.matchedLength()
1092
- self.setFormat(start, stop - start, STYLE_ANNOT_VAL)
1093
- # Find next string in block, if any
1094
- next = start = self._string_start.indexIn(text, next)
1095
- if next >= 0:
1096
- next += self._string_start.matchedLength()
1083
+ # Rule-based formatting
1084
+ for (pattern, style) in self._rules:
1085
+ i = pattern.globalMatch(text, offset=offset)
1086
+ while i.hasNext():
1087
+ m = i.next()
1088
+ x, w = m.capturedStart(), m.capturedLength()
1089
+ if self._highlight_ok(strings, x, w):
1090
+ self.setFormat(x, w, style)
1091
+
1092
+ # Model and component headers (must be at start of string)
1093
+ if offset == 0:
1094
+ m = self._rule_head.match(text)
1095
+ if m.hasMatch():
1096
+ x, w = m.capturedStart(1), m.capturedLength(1)
1097
+ self.setFormat(x, w, STYLE_HEADER)
1098
+
1099
+ # Variable units (must be at start of string)
1100
+ if offset == 0:
1101
+ m = self._rule_var_unit.match(text)
1102
+ if m.hasMatch():
1103
+ self.setFormat(
1104
+ m.capturedStart(2), m.capturedLength(2), STYLE_ANNOT_KEY)
1105
+ self.setFormat(
1106
+ m.capturedStart(4), m.capturedLength(4), STYLE_ANNOT_VAL)
1107
+
1108
+ # Binds and labels
1109
+ for pattern in self._rules_labels:
1110
+ i = pattern.globalMatch(text, offset=offset)
1111
+ while i.hasNext():
1112
+ m = i.next()
1113
+ x, w = m.capturedStart(), m.capturedLength()
1114
+ if self._highlight_ok(strings, x, w):
1115
+ self.setFormat(m.capturedStart(2), m.capturedLength(2),
1116
+ STYLE_ANNOT_KEY)
1117
+ self.setFormat(m.capturedStart(3), m.capturedLength(3),
1118
+ STYLE_ANNOT_VAL)
1119
+
1120
+ # Meta properties (must be at start of string)
1121
+ if offset == 0:
1122
+ m = self._rule_meta.match(text)
1123
+ if m.hasMatch():
1124
+ self.setFormat(
1125
+ m.capturedStart(1), m.capturedLength(1), STYLE_ANNOT_KEY)
1126
+ # Don't reformat strings (or bits after string end!)
1127
+ if m.captured(3)[:3] != '"""':
1128
+ self.setFormat(m.capturedStart(3), m.capturedLength(3),
1129
+ STYLE_ANNOT_VAL)
1097
1130
 
1098
1131
 
1099
1132
  class ProtocolHighlighter(QtGui.QSyntaxHighlighter):
@@ -1101,43 +1134,40 @@ class ProtocolHighlighter(QtGui.QSyntaxHighlighter):
1101
1134
  Syntax highlighter for ``mmt`` protocol definitions.
1102
1135
  """
1103
1136
  def __init__(self, document):
1104
- super(ProtocolHighlighter, self).__init__(document)
1137
+ super().__init__(document)
1105
1138
 
1106
1139
  # Headers and units
1107
- self._rule_head = QtCore.QRegExp(r'^(\s*)\[\[[a-zA-Z0-9_]+\]\]')
1140
+ R = QtCore.QRegularExpression
1141
+ self._rule_head = R(r'^\s*(\[\[[a-zA-Z0-9_]+\]\])')
1108
1142
 
1109
1143
  # Highlighting rules
1110
1144
  self._rules = []
1111
1145
 
1112
1146
  # Numbers
1113
- pattern = QtCore.QRegExp(r'\b[+-]?[0-9]*\.?[0-9]+([eE][+-]?[0-9]+)?\b')
1114
- self._rules.append((pattern, STYLE_LITERAL))
1147
+ self._rules.append(
1148
+ (R(r'\b[+-]?[0-9]*\.?[0-9]+([eE][+-]?[0-9]+)?\b'), STYLE_LITERAL))
1115
1149
 
1116
1150
  # Keyword "next"
1117
- pattern = QtCore.QRegExp(r'\bnext\b')
1118
- self._rules.append((pattern, STYLE_KEYWORD_1))
1151
+ self._rules.append((R(r'\bnext\b'), STYLE_KEYWORD_1))
1119
1152
 
1120
1153
  # Comments
1121
- pattern = QtCore.QRegExp(r'#[^\n]*')
1122
- self._rules.append((pattern, STYLE_COMMENT))
1154
+ self._rules.append((R(r'#[^\n]*'), STYLE_COMMENT))
1123
1155
 
1124
1156
  def highlightBlock(self, text):
1125
1157
  """ Qt: Called whenever a block should be highlighted. """
1158
+
1126
1159
  # Rule based formatting
1127
1160
  for (pattern, style) in self._rules:
1128
- i = pattern.indexIn(text)
1129
- while i >= 0:
1130
- n = len(pattern.cap(0))
1131
- self.setFormat(i, n, style)
1132
- i = pattern.indexIn(text, i + n)
1161
+ i = pattern.globalMatch(text)
1162
+ while i.hasNext():
1163
+ m = i.next()
1164
+ self.setFormat(m.capturedStart(), m.capturedLength(), style)
1133
1165
 
1134
- # Protocol header
1135
- i = self._rule_head.indexIn(text)
1136
- while i >= 0:
1137
- m = len(self._rule_head.cap(1))
1138
- n = len(self._rule_head.cap(0))
1139
- self.setFormat(i + m, n - m, STYLE_HEADER)
1140
- i = self._rule_head.indexIn(text, i + n)
1166
+ # Protocol header (must be at strart of string)
1167
+ m = self._rule_head.match(text)
1168
+ if m.hasMatch():
1169
+ self.setFormat(
1170
+ m.capturedStart(1), m.capturedLength(1), STYLE_HEADER)
1141
1171
 
1142
1172
 
1143
1173
  class ScriptHighlighter(QtGui.QSyntaxHighlighter):
@@ -1145,10 +1175,11 @@ class ScriptHighlighter(QtGui.QSyntaxHighlighter):
1145
1175
  Syntax highlighter for ``mmt`` script files.
1146
1176
  """
1147
1177
  def __init__(self, document):
1148
- super(ScriptHighlighter, self).__init__(document)
1178
+ super().__init__(document)
1149
1179
 
1150
- # Headers and units
1151
- self._rule_head = QtCore.QRegExp(r'^(\s*)\[\[[a-zA-Z0-9_]+\]\]')
1180
+ # Script header
1181
+ R = QtCore.QRegularExpression
1182
+ self._rule_head = R(r'^\s*(\[\[[a-zA-Z0-9_]+\]\])')
1152
1183
 
1153
1184
  # Highlighting rules
1154
1185
  self._rules = []
@@ -1156,197 +1187,197 @@ class ScriptHighlighter(QtGui.QSyntaxHighlighter):
1156
1187
  # Keywords
1157
1188
  import keyword
1158
1189
  for kw in keyword.kwlist:
1159
- pattern = QtCore.QRegExp(r'\b' + kw + r'\b')
1160
- self._rules.append((pattern, STYLE_KEYWORD_1))
1190
+ self._rules.append((R(r'\b' + kw + r'\b'), STYLE_KEYWORD_1))
1161
1191
 
1162
1192
  # Built-in essential functions
1163
1193
  for func in _PYFUNC:
1164
- pattern = QtCore.QRegExp(r'\b' + str(func) + r'\b')
1165
- self._rules.append((pattern, STYLE_KEYWORD_2))
1194
+ self._rules.append((R(r'\b' + str(func) + r'\b'), STYLE_KEYWORD_2))
1166
1195
 
1167
1196
  # Literals: numbers, True, False, None
1168
1197
  # Override some keywords
1169
- pattern = QtCore.QRegExp(r'\b[+-]?[0-9]*\.?[0-9]+([eE][+-]?[0-9]+)?\b')
1170
- self._rules.append((pattern, STYLE_LITERAL))
1171
- pattern = QtCore.QRegExp(r'\bTrue\b')
1172
- self._rules.append((pattern, STYLE_LITERAL))
1173
- pattern = QtCore.QRegExp(r'\bFalse\b')
1174
- self._rules.append((pattern, STYLE_LITERAL))
1175
- pattern = QtCore.QRegExp(r'\bNone\b')
1176
- self._rules.append((pattern, STYLE_LITERAL))
1198
+ self._rules.append((R(r'\b[+-]?[0-9]*\.?[0-9]+([eE][+-]?[0-9]+)?\b'),
1199
+ STYLE_LITERAL))
1200
+ self._rules.append((R(r'\bTrue\b'), STYLE_LITERAL))
1201
+ self._rules.append((R(r'\bFalse\b'), STYLE_LITERAL))
1202
+ self._rules.append((R(r'\bNone\b'), STYLE_LITERAL))
1177
1203
 
1178
1204
  # Strings
1179
- pattern = QtCore.QRegExp(r'"([^"\\]|\\")*"')
1180
- self._rules.append((pattern, STYLE_LITERAL))
1181
- pattern = QtCore.QRegExp(r"'([^'\\]|\\')*'")
1182
- self._rules.append((pattern, STYLE_LITERAL))
1183
-
1184
- # Multi-line strings
1185
- self._string1 = QtCore.QRegExp(r"'''")
1186
- self._string2 = QtCore.QRegExp(r'"""')
1205
+ self._s1 = R(r'"')
1206
+ self._s2 = R(r"'")
1207
+ self._ms1 = R(r'"""')
1208
+ self._ms2 = R(r"'''")
1209
+ self._s_start = R(r'"""|\'\'\'|"|\'')
1210
+ self._s_end = {
1211
+ '"': self._s1, "'": self._s2, '"""': self._ms1, "'''": self._ms2}
1187
1212
 
1188
1213
  # Comments
1189
- pattern = QtCore.QRegExp(r'#[^\n]*')
1190
- self._rules.append((pattern, STYLE_COMMENT))
1214
+ self._comment = R(r'#')
1215
+
1216
+ def _highlight_ok(self, strings, start, length):
1217
+ """ Checks if the string ``start`` to ``length`` needs formatted. """
1218
+ for lo, hi in strings:
1219
+ if lo <= start < hi or lo <= start + length < hi:
1220
+ return False
1221
+ return True
1191
1222
 
1192
1223
  def highlightBlock(self, text):
1193
1224
  """ Qt: Called whenever a block should be highlighted. """
1194
- # Rule based formatting
1195
- for (pattern, style) in self._rules:
1196
- i = pattern.indexIn(text)
1197
- while i >= 0:
1198
- # Note: Can't use matchedLength() here because it does quirky
1199
- # things with the subgroup for the numbers regex.
1200
- n = len(pattern.cap(0))
1201
- self.setFormat(i, n, style)
1202
- i = pattern.indexIn(text, i + n)
1203
1225
 
1204
- # Script header
1205
- i = self._rule_head.indexIn(text)
1206
- while i >= 0:
1207
- m = len(self._rule_head.cap(1))
1208
- n = len(self._rule_head.cap(0))
1209
- self.setFormat(i + m, n - m, STYLE_HEADER)
1210
- i = self._rule_head.indexIn(text, i + n)
1211
-
1212
- # Block states:
1213
- # 0 Normal
1214
- # 1 Multi-line string 1
1215
- # 2 Multi-line string 2
1226
+ # To avoid formatting within strings each is stored as a (start, end).
1227
+ strings = []
1228
+ # If the start has been handled, set the offset.
1229
+ offset = 0
1230
+ # If the end has been handled, chop it off the string
1231
+
1232
+ # Multi-line strings are done first, because they overrule a lot of
1233
+ # things and we can skip formatting if we're inside one.
1234
+ # Block states: 0=No string, 1=A " " " string, 2=A ' ' ' string
1216
1235
  self.setCurrentBlockState(0)
1217
1236
 
1218
- # Multi-line formats
1219
- def find_start(text, next):
1220
- s1 = self._string1.indexIn(text, next)
1221
- s2 = self._string2.indexIn(text, next)
1222
- if s1 < 0 and s2 < 0:
1223
- current = 0
1224
- start = -1
1225
- next = -1
1226
- else:
1227
- if s1 >= 0 and s2 >= 0:
1228
- current = 1 if s1 < s2 else 2
1229
- start = min(s1, s2)
1230
- elif s1 >= 0:
1231
- current = 1
1232
- start = s1
1233
- else: # (s2 >= 0)
1234
- current = 2
1235
- start = s2
1236
- next = start + 3
1237
-
1238
- # Check we're not in a comment
1239
- i = text.rfind('\n', start) + 1
1240
- if text[i:i + 1] == '#':
1241
- current = 0
1242
- start = next = -1
1243
-
1244
- return current, start, next
1245
-
1246
- # Check state of previous block
1237
+ # Continuing a multi-line string?
1247
1238
  previous = self.previousBlockState()
1248
1239
  if previous == 1 or previous == 2:
1249
- current, start, next = previous, 0, 0
1250
- else:
1251
- current, start, next = find_start(text, 0)
1252
-
1253
- # Find any occurrences of string start / stop
1254
- while next >= 0:
1255
- if current == 1:
1256
- stop = self._string1.indexIn(text, next)
1240
+ # Search for string stop
1241
+ r = self._ms1 if previous == 1 else self._ms2
1242
+ ms = r.match(text)
1243
+ if ms.hasMatch():
1244
+ # Terminate the multi-line string, and increase global offset
1245
+ offset = ms.capturedEnd(0)
1246
+ self.setFormat(0, offset, STYLE_LITERAL)
1257
1247
  else:
1258
- stop = self._string2.indexIn(text, next)
1259
- if stop < 0:
1260
- # Continuing multi-line string
1261
- self.setCurrentBlockState(current)
1262
- self.setFormat(start, len(text) - start, STYLE_LITERAL)
1263
- next = -1
1248
+ # Whole line in the string
1249
+ self.setCurrentBlockState(previous)
1250
+ self.setFormat(0, len(text), STYLE_LITERAL)
1251
+ return
1252
+
1253
+ # Search for string starts (single or multi-line)
1254
+ stroff = offset # Offset for string start/end searching
1255
+ m1 = self._s_start.match(text, offset=stroff)
1256
+ while m1.hasMatch():
1257
+ stroff = m1.capturedEnd()
1258
+ start = m1.capturedStart()
1259
+ # Are we in a comment?
1260
+ mc = self._comment.match(text)
1261
+ if (mc.hasMatch() and mc.capturedStart() < start):
1262
+ # No point searching for further string starts
1263
+ stroff = len(text)
1264
1264
  else:
1265
- # Ending single-line or multi-line string
1266
- # Format until stop token
1267
- stop += 3
1268
- self.setFormat(start, stop - start, STYLE_LITERAL)
1269
- # Find next string in block, if any
1270
- next = stop + 3
1271
- current, start, next = find_start(text, next)
1265
+ # Find string end
1266
+ m2 = self._s_end[m1.captured()].match(text, offset=stroff)
1267
+ if m2.hasMatch():
1268
+ stroff = m2.capturedEnd()
1269
+ # Ignore if escaped
1270
+ if text[m2.capturedStart() - 1] != '\\':
1271
+ # Terminate the single line string and move on
1272
+ self.setFormat(start, stroff - start, STYLE_LITERAL)
1273
+ strings.append((start, stroff))
1274
+ elif m1.capturedLength() > 1:
1275
+ # Multi-line string start. Block finished!
1276
+ self.setCurrentBlockState(
1277
+ 1 if m1.captured() == '"""' else 2)
1278
+ self.setFormat(start, len(text) - start, STYLE_LITERAL)
1279
+ return
1280
+ # No Match? Then not a string so ignore and continue
1281
+ m1 = self._s_start.match(text, offset=stroff)
1282
+
1283
+ # Comment
1284
+ i = self._comment.globalMatch(text, offset=offset)
1285
+ while i.hasNext():
1286
+ m = i.next()
1287
+ x = m.capturedStart()
1288
+ if self._highlight_ok(strings, x, 1):
1289
+ self.setFormat(x, len(text) - x, STYLE_COMMENT)
1290
+ text = text[:x]
1291
+ break
1292
+
1293
+ # Script header (must be at start of string)
1294
+ if offset == 0:
1295
+ m = self._rule_head.match(text)
1296
+ if m.hasMatch():
1297
+ self.setFormat(
1298
+ m.capturedStart(1), m.capturedLength(1), STYLE_HEADER)
1299
+
1300
+ # Rule based formatting
1301
+ for (pattern, style) in self._rules:
1302
+ i = pattern.globalMatch(text, offset=offset)
1303
+ while i.hasNext():
1304
+ m = i.next()
1305
+ x, w = m.capturedStart(), m.capturedLength()
1306
+ if self._highlight_ok(strings, x, w):
1307
+ self.setFormat(x, w, style)
1272
1308
 
1273
1309
 
1274
1310
  # List of essential built-in python functions
1275
1311
  _PYFUNC = [
1276
1312
  'abs()',
1277
- 'divmod()',
1278
- 'input()',
1279
- 'open()',
1280
- 'staticmethod()',
1313
+ 'aiter()',
1281
1314
  'all()',
1282
- 'enumerate()',
1283
- 'int()',
1284
- 'ord()',
1285
- 'str()',
1286
1315
  'any()',
1287
- 'eval()',
1288
- 'isinstance()',
1289
- 'pow()',
1290
- 'sum()',
1291
- 'basestring()',
1292
- 'execfile()',
1293
- 'issubclass()',
1294
- 'print()',
1295
- 'super()',
1316
+ 'anext()',
1317
+ 'ascii()',
1296
1318
  'bin()',
1297
- 'file()',
1298
- 'iter()',
1299
- 'property()',
1300
- 'tuple()',
1301
1319
  'bool()',
1302
- 'filter()',
1303
- 'len()',
1304
- 'range()',
1305
- 'type()',
1320
+ 'breakpoint()',
1306
1321
  'bytearray()',
1307
- 'float()',
1308
- 'list()',
1309
- 'raw_input()',
1310
- 'unichr()',
1322
+ 'bytes()',
1311
1323
  'callable()',
1312
- 'format()',
1313
- 'locals()',
1314
- 'reduce()',
1315
- 'unicode()',
1316
1324
  'chr()',
1317
- 'frozenset()',
1318
- 'long()',
1319
- 'reload()',
1320
- 'vars()',
1321
1325
  'classmethod()',
1326
+ 'compile()',
1327
+ 'complex()',
1328
+ 'delattr()',
1329
+ 'dict()',
1330
+ 'dir()',
1331
+ 'divmod()',
1332
+ 'enumerate()',
1333
+ 'eval()',
1334
+ 'exec()',
1335
+ 'filter()',
1336
+ 'float()',
1337
+ 'format()',
1338
+ 'frozenset()',
1322
1339
  'getattr()',
1323
- 'map()',
1324
- 'repr()',
1325
- 'range()',
1326
- 'cmp()',
1327
1340
  'globals()',
1328
- 'max()',
1329
- 'reversed()',
1330
- 'zip()',
1331
- 'compile()',
1332
1341
  'hasattr()',
1333
- 'memoryview()',
1334
- 'round()',
1335
- '__import__()',
1336
- 'complex()',
1337
1342
  'hash()',
1338
- 'min()',
1339
- 'set()',
1340
- 'delattr()',
1341
1343
  'help()',
1342
- 'next()',
1343
- 'setattr()',
1344
- 'dict()',
1345
1344
  'hex()',
1346
- 'object()',
1347
- 'slice()',
1348
- 'dir()',
1349
1345
  'id()',
1346
+ 'input()',
1347
+ 'int()',
1348
+ 'isinstance()',
1349
+ 'issubclass()',
1350
+ 'iter()',
1351
+ 'len()',
1352
+ 'list()',
1353
+ 'locals()',
1354
+ 'map()',
1355
+ 'max()',
1356
+ 'memoryview()',
1357
+ 'min()',
1358
+ 'next()',
1359
+ 'object()',
1350
1360
  'oct()',
1361
+ 'open()',
1362
+ 'ord()',
1363
+ 'pow()',
1364
+ 'print()',
1365
+ 'property()',
1366
+ 'range()',
1367
+ 'repr()',
1368
+ 'reversed()',
1369
+ 'round()',
1370
+ 'set()',
1371
+ 'setattr()',
1372
+ 'slice()',
1351
1373
  'sorted()',
1374
+ 'staticmethod()',
1375
+ 'str()',
1376
+ 'sum()',
1377
+ 'super()',
1378
+ 'tuple()',
1379
+ 'type()',
1380
+ 'vars()',
1381
+ 'zip()',
1382
+ '__import__()',
1352
1383
  ]