PrEditor 2.7.0__tar.gz → 2.9.0__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.
Files changed (205) hide show
  1. {preditor-2.7.0 → preditor-2.9.0}/.github/workflows/static-analysis-and-test.yml +5 -1
  2. {preditor-2.7.0 → preditor-2.9.0}/PKG-INFO +8 -1
  3. {preditor-2.7.0 → preditor-2.9.0}/PrEditor.egg-info/PKG-INFO +8 -1
  4. {preditor-2.7.0 → preditor-2.9.0}/PrEditor.egg-info/scm_file_list.json +1 -0
  5. preditor-2.9.0/PrEditor.egg-info/scm_version.json +8 -0
  6. {preditor-2.7.0 → preditor-2.9.0}/README.md +7 -0
  7. {preditor-2.7.0 → preditor-2.9.0}/preditor/debug.py +70 -5
  8. {preditor-2.7.0 → preditor-2.9.0}/preditor/excepthooks.py +11 -0
  9. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/__init__.py +1 -21
  10. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/codehighlighter.py +52 -66
  11. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/completer.py +44 -16
  12. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/console.py +14 -13
  13. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/console_base.py +177 -21
  14. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/drag_tab_bar.py +2 -3
  15. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/level_buttons.py +3 -3
  16. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/loggerwindow.py +41 -9
  17. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/ui/loggerwindow.ui +24 -18
  18. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/workboxwidget.py +1 -1
  19. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/documenteditor.py +3 -3
  20. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/finddialog.py +4 -4
  21. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/ui/finddialog.ui +5 -5
  22. {preditor-2.7.0 → preditor-2.9.0}/preditor/stream/director.py +48 -11
  23. {preditor-2.7.0 → preditor-2.9.0}/preditor/version.py +3 -3
  24. preditor-2.7.0/PrEditor.egg-info/scm_version.json +0 -8
  25. {preditor-2.7.0 → preditor-2.9.0}/.coveragerc +0 -0
  26. {preditor-2.7.0 → preditor-2.9.0}/.github/ISSUE_TEMPLATE/BUG_REPORT.md +0 -0
  27. {preditor-2.7.0 → preditor-2.9.0}/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md +0 -0
  28. {preditor-2.7.0 → preditor-2.9.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  29. {preditor-2.7.0 → preditor-2.9.0}/.github/workflows/release.yml +0 -0
  30. {preditor-2.7.0 → preditor-2.9.0}/.gitignore +0 -0
  31. {preditor-2.7.0 → preditor-2.9.0}/.pre-commit-config.yaml +0 -0
  32. {preditor-2.7.0 → preditor-2.9.0}/CODE_OF_CONDUCT.md +0 -0
  33. {preditor-2.7.0 → preditor-2.9.0}/CONTRIBUTING.md +0 -0
  34. {preditor-2.7.0 → preditor-2.9.0}/LICENSE +0 -0
  35. {preditor-2.7.0 → preditor-2.9.0}/MANIFEST.in +0 -0
  36. {preditor-2.7.0 → preditor-2.9.0}/PrEditor.egg-info/SOURCES.txt +0 -0
  37. {preditor-2.7.0 → preditor-2.9.0}/PrEditor.egg-info/dependency_links.txt +0 -0
  38. {preditor-2.7.0 → preditor-2.9.0}/PrEditor.egg-info/entry_points.txt +0 -0
  39. {preditor-2.7.0 → preditor-2.9.0}/PrEditor.egg-info/requires.txt +0 -0
  40. {preditor-2.7.0 → preditor-2.9.0}/PrEditor.egg-info/top_level.txt +0 -0
  41. {preditor-2.7.0 → preditor-2.9.0}/examples/add_to_app.py +0 -0
  42. {preditor-2.7.0 → preditor-2.9.0}/examples/output_capture_and_show.py +0 -0
  43. {preditor-2.7.0 → preditor-2.9.0}/examples/output_console.py +0 -0
  44. {preditor-2.7.0 → preditor-2.9.0}/examples/output_console.ui +0 -0
  45. {preditor-2.7.0 → preditor-2.9.0}/preditor/__init__.py +0 -0
  46. {preditor-2.7.0 → preditor-2.9.0}/preditor/__main__.py +0 -0
  47. {preditor-2.7.0 → preditor-2.9.0}/preditor/about_module.py +0 -0
  48. {preditor-2.7.0 → preditor-2.9.0}/preditor/cli.py +0 -0
  49. {preditor-2.7.0 → preditor-2.9.0}/preditor/config.py +0 -0
  50. {preditor-2.7.0 → preditor-2.9.0}/preditor/constants.py +0 -0
  51. {preditor-2.7.0 → preditor-2.9.0}/preditor/contexts.py +0 -0
  52. {preditor-2.7.0 → preditor-2.9.0}/preditor/cores/__init__.py +0 -0
  53. {preditor-2.7.0 → preditor-2.9.0}/preditor/cores/core.py +0 -0
  54. {preditor-2.7.0 → preditor-2.9.0}/preditor/dccs/.hab.json +0 -0
  55. {preditor-2.7.0 → preditor-2.9.0}/preditor/dccs/maya/PrEditor_maya.mod +0 -0
  56. {preditor-2.7.0 → preditor-2.9.0}/preditor/dccs/maya/README.md +0 -0
  57. {preditor-2.7.0 → preditor-2.9.0}/preditor/dccs/maya/plug-ins/PrEditor_maya.py +0 -0
  58. {preditor-2.7.0 → preditor-2.9.0}/preditor/dccs/studiomax/PackageContents.xml +0 -0
  59. {preditor-2.7.0 → preditor-2.9.0}/preditor/dccs/studiomax/PrEditor-PrEditor_Show.mcr +0 -0
  60. {preditor-2.7.0 → preditor-2.9.0}/preditor/dccs/studiomax/README.md +0 -0
  61. {preditor-2.7.0 → preditor-2.9.0}/preditor/dccs/studiomax/preditor.ms +0 -0
  62. {preditor-2.7.0 → preditor-2.9.0}/preditor/dccs/studiomax/preditor_menu.mnx +0 -0
  63. {preditor-2.7.0 → preditor-2.9.0}/preditor/delayable_engine/__init__.py +0 -0
  64. {preditor-2.7.0 → preditor-2.9.0}/preditor/delayable_engine/delayables.py +0 -0
  65. {preditor-2.7.0 → preditor-2.9.0}/preditor/enum.py +0 -0
  66. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/app.py +0 -0
  67. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/dialog.py +0 -0
  68. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/editor_chooser.py +0 -0
  69. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/errordialog.py +0 -0
  70. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/find_files.py +0 -0
  71. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/fuzzy_search/__init__.py +0 -0
  72. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/fuzzy_search/fuzzy_search.py +0 -0
  73. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/group_tab_widget/__init__.py +0 -0
  74. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/group_tab_widget/group_tab_widget.py +0 -0
  75. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/group_tab_widget/grouped_tab_menu.py +0 -0
  76. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/group_tab_widget/grouped_tab_models.py +0 -0
  77. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/group_tab_widget/grouped_tab_widget.py +0 -0
  78. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/group_tab_widget/one_tab_widget.py +0 -0
  79. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/logger_window_handler.py +0 -0
  80. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/logger_window_plugin.py +0 -0
  81. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/newtabwidget.py +0 -0
  82. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/output_console.py +0 -0
  83. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/qtdesigner/__init__.py +0 -0
  84. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/qtdesigner/_log_plugin.py +0 -0
  85. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/qtdesigner/console_base_plugin.py +0 -0
  86. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/qtdesigner/console_predit_plugin.py +0 -0
  87. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/set_text_editor_path_dialog.py +0 -0
  88. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/status_label.py +0 -0
  89. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/suggest_path_quotes_dialog.py +0 -0
  90. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/ui/editor_chooser.ui +0 -0
  91. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/ui/errordialog.ui +0 -0
  92. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/ui/find_files.ui +0 -0
  93. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/ui/set_text_editor_path_dialog.ui +0 -0
  94. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/ui/suggest_path_quotes_dialog.ui +0 -0
  95. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/window.py +0 -0
  96. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/workbox_mixin.py +0 -0
  97. {preditor-2.7.0 → preditor-2.9.0}/preditor/gui/workbox_text_edit.py +0 -0
  98. {preditor-2.7.0 → preditor-2.9.0}/preditor/logging_config.py +0 -0
  99. {preditor-2.7.0 → preditor-2.9.0}/preditor/osystem.py +0 -0
  100. {preditor-2.7.0 → preditor-2.9.0}/preditor/plugins.py +0 -0
  101. {preditor-2.7.0 → preditor-2.9.0}/preditor/prefs.py +0 -0
  102. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/error_mail.html +0 -0
  103. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/error_mail_inline.html +0 -0
  104. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/README.md +0 -0
  105. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/arrow_forward.png +0 -0
  106. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/check-bold.png +0 -0
  107. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/chevron-down.png +0 -0
  108. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/chevron-up.png +0 -0
  109. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/close-thick.png +0 -0
  110. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/comment-edit.png +0 -0
  111. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/content-copy.png +0 -0
  112. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/content-cut.png +0 -0
  113. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/content-duplicate.png +0 -0
  114. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/content-paste.png +0 -0
  115. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/content-save.png +0 -0
  116. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/debug_disabled.png +0 -0
  117. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/eye-check.png +0 -0
  118. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/file-plus.png +0 -0
  119. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/file-remove.png +0 -0
  120. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/format-align-left.png +0 -0
  121. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/format-letter-case-lower.png +0 -0
  122. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/format-letter-case-upper.png +0 -0
  123. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/format-letter-case.svg +0 -0
  124. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/information.png +0 -0
  125. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/logging_critical.png +0 -0
  126. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/logging_custom.png +0 -0
  127. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/logging_debug.png +0 -0
  128. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/logging_error.png +0 -0
  129. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/logging_info.png +0 -0
  130. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/logging_not_set.png +0 -0
  131. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/logging_warning.png +0 -0
  132. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/marker.png +0 -0
  133. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/play.png +0 -0
  134. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/playlist-play.png +0 -0
  135. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/plus-minus-variant.png +0 -0
  136. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/preditor.ico +0 -0
  137. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/preditor.png +0 -0
  138. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/preditor.psd +0 -0
  139. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/preditor.svg +0 -0
  140. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/regex.svg +0 -0
  141. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/restart.svg +0 -0
  142. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/skip-forward-outline.png +0 -0
  143. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/skip-next-outline.png +0 -0
  144. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/skip-next.png +0 -0
  145. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/skip-previous.png +0 -0
  146. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/subdirectory-arrow-right.png +0 -0
  147. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/text-search-variant.png +0 -0
  148. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/img/warning-big.png +0 -0
  149. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/lang/python.json +0 -0
  150. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/pref_updates/pref_updates.json +0 -0
  151. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/settings.ini +0 -0
  152. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/stylesheet/Bright.css +0 -0
  153. {preditor-2.7.0 → preditor-2.9.0}/preditor/resource/stylesheet/Dark.css +0 -0
  154. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/__init__.py +0 -0
  155. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/delayables/__init__.py +0 -0
  156. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/delayables/smart_highlight.py +0 -0
  157. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/delayables/spell_check.py +0 -0
  158. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lang/__init__.py +0 -0
  159. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lang/config/bash.ini +0 -0
  160. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lang/config/batch.ini +0 -0
  161. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lang/config/cpp.ini +0 -0
  162. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lang/config/css.ini +0 -0
  163. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lang/config/eyeonscript.ini +0 -0
  164. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lang/config/html.ini +0 -0
  165. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lang/config/javascript.ini +0 -0
  166. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lang/config/lua.ini +0 -0
  167. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lang/config/maxscript.ini +0 -0
  168. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lang/config/mel.ini +0 -0
  169. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lang/config/mu.ini +0 -0
  170. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lang/config/nsi.ini +0 -0
  171. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lang/config/perl.ini +0 -0
  172. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lang/config/puppet.ini +0 -0
  173. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lang/config/python.ini +0 -0
  174. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lang/config/ruby.ini +0 -0
  175. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lang/config/sql.ini +0 -0
  176. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lang/config/xml.ini +0 -0
  177. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lang/config/yaml.ini +0 -0
  178. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lang/language.py +0 -0
  179. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lexers/__init__.py +0 -0
  180. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lexers/cpplexer.py +0 -0
  181. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lexers/javascriptlexer.py +0 -0
  182. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lexers/maxscriptlexer.py +0 -0
  183. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lexers/mellexer.py +0 -0
  184. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lexers/mulexer.py +0 -0
  185. {preditor-2.7.0 → preditor-2.9.0}/preditor/scintilla/lexers/pythonlexer.py +0 -0
  186. {preditor-2.7.0 → preditor-2.9.0}/preditor/settings.py +0 -0
  187. {preditor-2.7.0 → preditor-2.9.0}/preditor/stream/__init__.py +0 -0
  188. {preditor-2.7.0 → preditor-2.9.0}/preditor/stream/console_handler.py +0 -0
  189. {preditor-2.7.0 → preditor-2.9.0}/preditor/stream/manager.py +0 -0
  190. {preditor-2.7.0 → preditor-2.9.0}/preditor/streamhandler_helper.py +0 -0
  191. {preditor-2.7.0 → preditor-2.9.0}/preditor/utils/__init__.py +0 -0
  192. {preditor-2.7.0 → preditor-2.9.0}/preditor/utils/call_stack.py +0 -0
  193. {preditor-2.7.0 → preditor-2.9.0}/preditor/utils/cute.py +0 -0
  194. {preditor-2.7.0 → preditor-2.9.0}/preditor/utils/stylesheets.py +0 -0
  195. {preditor-2.7.0 → preditor-2.9.0}/preditor/utils/text_search.py +0 -0
  196. {preditor-2.7.0 → preditor-2.9.0}/preditor/weakref.py +0 -0
  197. {preditor-2.7.0 → preditor-2.9.0}/pyproject.toml +0 -0
  198. {preditor-2.7.0 → preditor-2.9.0}/requirements-cli.txt +0 -0
  199. {preditor-2.7.0 → preditor-2.9.0}/requirements-dev.txt +0 -0
  200. {preditor-2.7.0 → preditor-2.9.0}/requirements-qsci5.txt +0 -0
  201. {preditor-2.7.0 → preditor-2.9.0}/requirements-qsci6.txt +0 -0
  202. {preditor-2.7.0 → preditor-2.9.0}/requirements-shortcut.txt +0 -0
  203. {preditor-2.7.0 → preditor-2.9.0}/requirements.txt +0 -0
  204. {preditor-2.7.0 → preditor-2.9.0}/setup.cfg +0 -0
  205. {preditor-2.7.0 → preditor-2.9.0}/tox.ini +0 -0
@@ -49,10 +49,14 @@ jobs:
49
49
  strategy:
50
50
  matrix:
51
51
  os: ['ubuntu-latest', 'windows-latest']
52
- python: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
52
+ python: ['3.10', '3.11', '3.12', '3.13']
53
53
  # Works around the depreciation of python 3.7 for ubuntu
54
54
  # https://github.com/actions/setup-python/issues/544
55
55
  include:
56
+ - os: 'ubuntu-24.04'
57
+ python: '3.9'
58
+ - os: 'ubuntu-24.04'
59
+ python: '3.8'
56
60
  - os: 'ubuntu-22.04'
57
61
  python: '3.7'
58
62
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PrEditor
3
- Version: 2.7.0
3
+ Version: 2.9.0
4
4
  Summary: A python REPL and Editor and console based on Qt.
5
5
  Author-email: Blur Studio <opensource@blur.com>
6
6
  License: LGPL-3.0
@@ -84,6 +84,13 @@ blocks([...](https://docs.python.org/3/glossary.html#term-...)), use the workbox
84
84
  loggers, and tracebacks(not using stderr). This can be used in various widgets to
85
85
  show selected output. See [examples/output_console.py](examples/output_console.py)
86
86
  for an example of the various modes.
87
+ * **Traceback markers:** Set the `PREDITOR_WRAP_TB` environment variable to
88
+ `prefix,suffix` to print those lines to stderr before and after every traceback
89
+ handled by PrEditor's excepthook. The traceback is still raised normally. This
90
+ makes tracebacks easy to find or parse in log files, for example:
91
+ ```
92
+ PREDITOR_WRAP_TB=--- TRACEBACK START ---,--- TRACEBACK END ---
93
+ ```
87
94
  * All code is run in `__main__`. In code you can add objects to it for inspection in PrEditor.
88
95
  * `Ctrl + Shift + PgUp/PgDown` changes focus between the console and workbox.
89
96
  * `Ctrl + Alt + Shift + PgUp/PgDown` changes focus and copies the current prompt
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PrEditor
3
- Version: 2.7.0
3
+ Version: 2.9.0
4
4
  Summary: A python REPL and Editor and console based on Qt.
5
5
  Author-email: Blur Studio <opensource@blur.com>
6
6
  License: LGPL-3.0
@@ -84,6 +84,13 @@ blocks([...](https://docs.python.org/3/glossary.html#term-...)), use the workbox
84
84
  loggers, and tracebacks(not using stderr). This can be used in various widgets to
85
85
  show selected output. See [examples/output_console.py](examples/output_console.py)
86
86
  for an example of the various modes.
87
+ * **Traceback markers:** Set the `PREDITOR_WRAP_TB` environment variable to
88
+ `prefix,suffix` to print those lines to stderr before and after every traceback
89
+ handled by PrEditor's excepthook. The traceback is still raised normally. This
90
+ makes tracebacks easy to find or parse in log files, for example:
91
+ ```
92
+ PREDITOR_WRAP_TB=--- TRACEBACK START ---,--- TRACEBACK END ---
93
+ ```
87
94
  * All code is run in `__main__`. In code you can add objects to it for inspection in PrEditor.
88
95
  * `Ctrl + Shift + PgUp/PgDown` changes focus between the console and workbox.
89
96
  * `Ctrl + Alt + Shift + PgUp/PgDown` changes focus and copies the current prompt
@@ -214,6 +214,7 @@
214
214
  "tests/ide/test_delayable_engine.py",
215
215
  "tests/test_config.py",
216
216
  "tests/test_cute.py",
217
+ "tests/test_debug.py",
217
218
  "tests/test_prefs.py",
218
219
  "tests/test_stream.py",
219
220
  "tox.ini"
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "2.9.0",
3
+ "distance": 0,
4
+ "node": "gb07591e17dc3fff29e25be4c2875475638a95942",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-11-02"
8
+ }
@@ -38,6 +38,13 @@ blocks([...](https://docs.python.org/3/glossary.html#term-...)), use the workbox
38
38
  loggers, and tracebacks(not using stderr). This can be used in various widgets to
39
39
  show selected output. See [examples/output_console.py](examples/output_console.py)
40
40
  for an example of the various modes.
41
+ * **Traceback markers:** Set the `PREDITOR_WRAP_TB` environment variable to
42
+ `prefix,suffix` to print those lines to stderr before and after every traceback
43
+ handled by PrEditor's excepthook. The traceback is still raised normally. This
44
+ makes tracebacks easy to find or parse in log files, for example:
45
+ ```
46
+ PREDITOR_WRAP_TB=--- TRACEBACK START ---,--- TRACEBACK END ---
47
+ ```
41
48
  * All code is run in `__main__`. In code you can add objects to it for inspection in PrEditor.
42
49
  * `Ctrl + Shift + PgUp/PgDown` changes focus between the console and workbox.
43
50
  * `Ctrl + Alt + Shift + PgUp/PgDown` changes focus and copies the current prompt
@@ -3,15 +3,20 @@ from __future__ import absolute_import, print_function
3
3
  import datetime
4
4
  import inspect
5
5
  import logging
6
+ import os
6
7
  import sys
7
8
 
8
9
  logger = logging.getLogger(__name__)
9
10
 
11
+ # Keeps the file `faulthandlerToFile` installed open for the life of the process
12
+ _faulthandlerFile = None
13
+
10
14
 
11
15
  class FileLogger:
12
16
  def __init__(self, stdhandle, logfile, _print=True, clearLog=True):
13
17
  self.old_stream = stdhandle
14
- self._logfile = logfile
18
+ # Accept pathlib objects but store the path as a string
19
+ self._logfile = os.fspath(logfile)
15
20
  self._print = _print
16
21
  if clearLog:
17
22
  # clear the log file
@@ -48,18 +53,21 @@ def logToFile(path, stdout=True, stderr=True, useOldStd=True, clearLog=True):
48
53
  sys.version info when stdout is True.
49
54
 
50
55
  Args:
51
- path (str): File path to log output to.
52
-
56
+ path (str or os.PathLike): File path to log output to. If None is explicitly
57
+ passed the path returned by `preditor.osystem.defaultLogFile()` is used.
53
58
  stdout (bool): If True(default) override sys.stdout.
54
-
55
59
  stderr (bool): If True(default) override sys.stderr.
56
-
57
60
  useOldStd (bool): If True, messages will be written to the FileLogger
58
61
  and the previous sys.stdout/sys.stderr.
59
62
 
60
63
  clearLog (bool): If True(default) clear the log file when this command is
61
64
  called.
62
65
  """
66
+ if path is None:
67
+ from . import osystem
68
+
69
+ path = osystem.defaultLogFile()
70
+
63
71
  if stderr:
64
72
  sys.stderr = FileLogger(sys.stderr, path, useOldStd, clearLog=clearLog)
65
73
  if stdout:
@@ -76,6 +84,63 @@ def logToFile(path, stdout=True, stderr=True, useOldStd=True, clearLog=True):
76
84
  StreamHandlerHelper.replace_stream(sys.stderr.old_stream, sys.stderr)
77
85
 
78
86
 
87
+ def faulthandlerToFile(path, allThreads=True, clearLog=True, force=False):
88
+ """Install faulthandler so hard crashes are written to a log file.
89
+
90
+ Python normally can't report fatal errors like segfaults, they simply kill
91
+ the process without any output. Enabling faulthandler makes python dump the
92
+ traceback of each thread to a file when one of those errors happens, which
93
+ is often the only record of what a host application was doing when it died.
94
+
95
+ Unlike `logToFile` this doesn't touch sys.stdout/sys.stderr. faulthandler
96
+ writes to the file descriptor directly, so it needs a real file on disk, not
97
+ a `FileLogger`. The file is left open for the life of the process, closing
98
+ it would leave faulthandler writing to an invalid file descriptor.
99
+
100
+ If faulthandler is already enabled it's left alone unless force is used.
101
+ This includes it being enabled outside of this function, for example by the
102
+ `PYTHONFAULTHANDLER` env var or `python -X faulthandler`.
103
+
104
+ Args:
105
+ path (str or os.PathLike): File path to write faulthandler output to. If
106
+ None is explicitly passed the path returned by
107
+ `preditor.osystem.defaultLogFile('preditorFaulthandler.log')` is used.
108
+ allThreads (bool): If True(default) dump the traceback of all threads,
109
+ otherwise only the thread that crashed.
110
+ clearLog (bool): If True(default) clear the log file when this command
111
+ is called, otherwise append to the existing contents.
112
+ force (bool): If True, install faulthandler even if it was already
113
+ enabled. Any file opened by a previous call is closed.
114
+
115
+ Returns:
116
+ bool: If faulthandler was installed by this call. False is returned if
117
+ it was already enabled and force was not used.
118
+ """
119
+ global _faulthandlerFile
120
+ import faulthandler
121
+
122
+ if faulthandler.is_enabled() and not force:
123
+ logger.debug('faulthandler is already enabled, not logging to: %s', path)
124
+ return False
125
+
126
+ if path is None:
127
+ from . import osystem
128
+
129
+ path = osystem.defaultLogFile('preditorFaulthandler.log')
130
+
131
+ # Newline forces windows to write unix style newlines
132
+ output = open(path, 'w' if clearLog else 'a', newline="\n", encoding="utf-8")
133
+ faulthandler.enable(file=output, all_threads=allThreads)
134
+
135
+ # Only close the file used by a previous call once the new one is installed
136
+ previous = _faulthandlerFile
137
+ _faulthandlerFile = output
138
+ if previous is not None:
139
+ previous.close()
140
+
141
+ return True
142
+
143
+
79
144
  def printCallingFunction(compact=False):
80
145
  """Prints and returns info about the calling function
81
146
 
@@ -1,5 +1,6 @@
1
1
  from __future__ import absolute_import, print_function
2
2
 
3
+ import os
3
4
  import sys
4
5
  import traceback
5
6
 
@@ -73,14 +74,24 @@ class PreditorExceptHook(object):
73
74
  A newline is printed pre-traceback to ensure the first line of output
74
75
  is not printed in-line with the prompt. This also provides visual
75
76
  separation between tracebacks, when received consecutively.
77
+
78
+ Checks the env var `PREDITOR_WRAP_TB`, if set it will split on commas and
79
+ print the first item to stderr before the traceback and the second item after
80
+ the traceback is handled on newlines.
76
81
  """
77
82
  # Print the newline to stderr so it will show up in the same stream as
78
83
  # the traceback will be printed to.
79
84
  print("", file=sys.stderr)
85
+ fixes = os.getenv("PREDITOR_WRAP_TB", "").split(",", 2)
86
+ if len(fixes):
87
+ print(fixes[0], file=sys.stderr)
80
88
  try:
81
89
  self.base_excepthook(*exc_info)
82
90
  except (TypeError, NameError):
83
91
  sys.__excepthook__(*exc_info)
92
+ finally:
93
+ if len(fixes) > 1:
94
+ print(fixes[1], file=sys.stderr)
84
95
 
85
96
  @classmethod
86
97
  def call_callbacks(cls, *exc_info):
@@ -1,29 +1,9 @@
1
- import re
2
-
3
- from Qt.QtGui import QCursor
4
- from Qt.QtWidgets import QStackedWidget, QToolTip
1
+ from Qt.QtWidgets import QStackedWidget
5
2
 
6
3
  from .dialog import Dialog # noqa: F401
7
4
  from .window import Window # noqa: F401
8
5
 
9
6
 
10
- def handleMenuHovered(action):
11
- """Actions in QMenus which are not descendants of a QToolBar will not show
12
- their toolTips, because... Reasons?
13
- """
14
- # Don't show if it's just the text of the action
15
- text = re.sub(r"(?<!&)&(?!&)", "", action.text())
16
- text = text.replace('...', '')
17
-
18
- if text == action.toolTip():
19
- text = ''
20
- else:
21
- text = action.toolTip()
22
-
23
- menu = action.parent()
24
- QToolTip.showText(QCursor.pos(), text, menu)
25
-
26
-
27
7
  def loadUi(filename, widget, uiname=''):
28
8
  """use's Qt's uic loader to load dynamic interafces onto the inputed widget
29
9
 
@@ -101,72 +101,28 @@ class CodeHighlighter(QSyntaxHighlighter):
101
101
  if not self.enabled():
102
102
  return
103
103
 
104
- # Reset the highlight spans for this text block
105
- self.spans = []
106
-
107
- if not self.isConsoleMode() or str(text).startswith('>>>'):
108
-
109
- # We only have a result pattern if the parent has an attr "outputPrompt", so
110
- # only proceed if we have been able to define self._resultPattern.
111
- if self._resultPattern:
112
- self.highlightText(
113
- text,
114
- self._resultPattern,
115
- self._resultFormat,
116
- )
117
-
118
- # Format the strings
119
- self.highlightText(
120
- text,
121
- self._stringPattern,
122
- self._stringFormat,
123
- )
124
-
125
- # format the comments
126
- self.highlightText(
127
- text,
128
- self._commentPattern,
129
- self._commentFormat,
130
- )
131
-
132
- # Format the keywords
133
- self.highlightText(
134
- text,
135
- self._keywordPattern,
136
- self._keywordFormat,
137
- )
138
-
139
- def highlightText(self, text, expr, format):
140
- """Highlights a text group with an expression and format
141
-
142
- Args:
143
- text (str): text to highlight
144
- expr (re.compile): search parameter
145
- format (QTextCharFormat): formatting rule
146
- """
147
- if expr is None or not text:
104
+ if self.isConsoleMode() and not text.startswith('>>>'):
148
105
  return
149
106
 
150
- # highlight all the given matches to the expression in the text
151
- for match in expr.finditer(text):
152
- match_span = match.span()
153
- start, end = match_span
154
- length = end - start
155
-
156
- # Determine if the current highlight is within an already determined
157
- # highlight, if so, let's block it.
158
- blocked = False
159
- for span in self.spans:
160
- if start > span[0] and start < span[-1]:
161
- blocked = True
162
- break
163
-
164
- if not blocked:
165
- self.setFormat(start, length, format)
166
-
167
- # Append the current span to self.spans, so we can later block
168
- # new highlights which should be blocked
169
- self.spans.append(match_span)
107
+ pattern = self._highlightPattern
108
+ if pattern is None:
109
+ pattern = self.defineHighlightPattern()
110
+
111
+ # Map the named groups of the combined pattern to their formats
112
+ formats = {
113
+ "result": self._resultFormat,
114
+ "string": self._stringFormat,
115
+ "comment": self._commentFormat,
116
+ "keyword": self._keywordFormat,
117
+ }
118
+
119
+ # The combined pattern finds the leftmost highlight first and continues
120
+ # searching after it. This prevents highlighting text inside of an
121
+ # existing highlight, for example a keyword inside a string or comment.
122
+ setFormat = self.setFormat
123
+ for match in pattern.finditer(text):
124
+ start, end = match.span()
125
+ setFormat(start, end - start, formats[match.lastgroup])
170
126
 
171
127
  def isConsoleMode(self):
172
128
  """checks to see if this highlighter is in console mode"""
@@ -267,12 +223,40 @@ class CodeHighlighter(QSyntaxHighlighter):
267
223
  """Define the regex pattern to use for comment"""
268
224
  pattern = "|".join(self._comments)
269
225
  self._commentPattern = re.compile(pattern)
226
+ self._highlightPattern = None
227
+
228
+ def defineHighlightPattern(self):
229
+ """Combine the result, string, comment and keyword patterns into a
230
+ single regex pattern with a named group for each. Searching once with
231
+ this pattern is much faster than searching for each pattern separately.
232
+
233
+ Returns:
234
+ re.Pattern: The combined pattern, also stored on this instance.
235
+ """
236
+ parts = []
237
+ for name, expr in (
238
+ ("result", self._resultPattern),
239
+ ("string", self._stringPattern),
240
+ ("comment", self._commentPattern),
241
+ ("keyword", self._keywordPattern),
242
+ ):
243
+ # Patterns that match empty text would prevent the other patterns
244
+ # from matching so skip them, they wouldn't highlight anything.
245
+ if expr is not None and expr.pattern:
246
+ parts.append(f"(?P<{name}>{expr.pattern})")
247
+
248
+ # If there is nothing to highlight, use a pattern that never matches.
249
+ self._highlightPattern = re.compile("|".join(parts) or "(?!)")
250
+ return self._highlightPattern
270
251
 
271
252
  def defineKeywordPattern(self):
272
253
  """Define the regex pattern to use for keyword"""
273
- keywords = [r"\b{}\b".format(word) for word in self._keywords]
274
- pattern = "|".join(keywords)
254
+ pattern = ""
255
+ if self._keywords:
256
+ keywords = "|".join(self._keywords)
257
+ pattern = rf"\b(?:{keywords})\b"
275
258
  self._keywordPattern = re.compile(pattern)
259
+ self._highlightPattern = None
276
260
 
277
261
  def defineResultPattern(self):
278
262
  """Define the regex pattern to use for results"""
@@ -281,9 +265,11 @@ class CodeHighlighter(QSyntaxHighlighter):
281
265
  prompt = parent.outputPrompt()
282
266
  pattern = '{}[^\n]*'.format(prompt)
283
267
  self._resultPattern = re.compile(pattern)
268
+ self._highlightPattern = None
284
269
 
285
270
  def defineStringPattern(self):
286
271
  """Define the regex pattern to use for strings."""
287
272
  lst = ["""{0}[^{0}\n]*{0}""".format(st) for st in self._strings]
288
273
  pattern = "|".join(lst)
289
274
  self._stringPattern = re.compile(pattern)
275
+ self._highlightPattern = None
@@ -1,14 +1,14 @@
1
1
  from __future__ import absolute_import
2
2
 
3
+ import heapq
3
4
  import inspect
4
5
  import re
5
6
  import sys
6
7
  from enum import Enum
7
8
 
8
- import Qt as Qt_py
9
- from Qt.QtCore import QRegExp, QSortFilterProxyModel, QStringListModel, Qt
10
- from Qt.QtGui import QCursor, QTextCursor
11
- from Qt.QtWidgets import QCompleter, QToolTip
9
+ from Qt.QtCore import QRegularExpression, QSortFilterProxyModel, QStringListModel, Qt
10
+ from Qt.QtGui import QCursor, QFontMetrics, QTextCursor
11
+ from Qt.QtWidgets import QCompleter, QStyle, QToolTip
12
12
 
13
13
 
14
14
  class CompleterMode(Enum):
@@ -53,6 +53,9 @@ class PythonCompleter(QCompleter):
53
53
 
54
54
  # update this completer
55
55
  self.setWidget(widget)
56
+ # All items use the same font, so let the popup skip measuring every row
57
+ # when laying out large lists.
58
+ self.popup().setUniformItemSizes(True)
56
59
 
57
60
  self.setCaseSensitive()
58
61
  self.setCompleterMode()
@@ -136,6 +139,27 @@ class PythonCompleter(QCompleter):
136
139
  def hideDocumentation(self):
137
140
  QToolTip.hideText()
138
141
 
142
+ def popupWidth(self, sampleCount=20):
143
+ """Returns the width needed to show the longest completions in the popup.
144
+
145
+ This is much faster than `popup().sizeHintForColumn(0)` for large lists
146
+ as that measures every row using the item delegate. Instead only the
147
+ `sampleCount` longest completions are measured.
148
+ """
149
+ popup = self.popup()
150
+ model = self.completionModel()
151
+ completions = (model.index(row, 0).data() for row in range(model.rowCount()))
152
+ longest = heapq.nlargest(sampleCount, completions, key=len)
153
+
154
+ metrics = QFontMetrics(popup.font())
155
+ textWidth = max((metrics.horizontalAdvance(t) for t in longest), default=0)
156
+ # Match the text margins QStyledItemDelegate adds on each side of the text.
157
+ margin = popup.style().pixelMetric(
158
+ QStyle.PixelMetric.PM_FocusFrameHMargin, None, popup
159
+ )
160
+ scrollBarWidth = popup.verticalScrollBar().sizeHint().width()
161
+ return textWidth + (margin + 1) * 2 + scrollBarWidth
162
+
139
163
  def refreshList(self, scope=None):
140
164
  """refreshes the string list based on the cursor word"""
141
165
  object, prefix = self.currentObject(scope)
@@ -150,24 +174,28 @@ class PythonCompleter(QCompleter):
150
174
  except AttributeError:
151
175
  keys = []
152
176
  keys.sort()
153
- self.model().sourceModel().setStringList(keys)
177
+ # Resetting the model can get expensive for large lists, this is called
178
+ # on every key press while the popup is visible, but the keys only change
179
+ # when the object being completed changes.
180
+ sourceModel = self.model().sourceModel()
181
+ if keys != sourceModel.stringList():
182
+ sourceModel.setStringList(keys)
154
183
 
155
184
  regExStr = ""
156
185
  if self._completerMode == CompleterMode.STARTS_WITH:
157
- regExStr = "^{}.*".format(prefix)
186
+ regExStr = f"^{QRegularExpression.escape(prefix)}"
158
187
  if self._completerMode == CompleterMode.OUTER_FUZZY:
159
- regExStr = ".*{}.*".format(prefix)
188
+ regExStr = QRegularExpression.escape(prefix)
160
189
  if self._completerMode == CompleterMode.FULL_FUZZY:
161
- regExStr = ".*".join(prefix)
162
-
163
- if Qt_py.IsPyQt6 or Qt_py.IsPySide6:
164
- regexp = QRegExp(regExStr)
165
- if self._sensitivity:
166
- regexp.setPatternOptions(QRegExp.PatternOption.CaseInsensitiveOption)
190
+ regExStr = ".*".join(QRegularExpression.escape(c) for c in prefix)
191
+
192
+ regexp = QRegularExpression(regExStr)
193
+ if self._sensitivity == Qt.CaseSensitivity.CaseInsensitive:
194
+ regexp.setPatternOptions(
195
+ QRegularExpression.PatternOption.CaseInsensitiveOption
196
+ )
197
+ if regexp != self.filterModel.filterRegularExpression():
167
198
  self.filterModel.setFilterRegularExpression(regexp)
168
- else:
169
- regexp = QRegExp(regExStr, self._sensitivity)
170
- self.filterModel.setFilterRegExp(regexp)
171
199
 
172
200
  def clear(self):
173
201
  self.popup().hide()
@@ -135,6 +135,7 @@ class ConsolePrEdit(ConsoleBase):
135
135
 
136
136
  def clear(self):
137
137
  """clears the text in the editor"""
138
+ self._pending_writes = []
138
139
  super(ConsoleBase, self).clear()
139
140
  self.startInputLine()
140
141
  # Note: Don't use the regular `super()` call here as it would result
@@ -483,15 +484,18 @@ class ConsolePrEdit(ConsoleBase):
483
484
  self.controller
484
485
  and self.controller.uiHighlightExactCompletionCHK.isChecked()
485
486
  ):
486
- for i in range(completer.completionCount()):
487
- completer.setCurrentRow(i)
488
- curCompletion = completer.currentCompletion()
489
- if prefix == curCompletion:
490
- index = model.index(i, 0)
491
- break
492
- elif prefix == curCompletion.lower():
493
- index = model.index(i, 0)
494
- break
487
+ # Find the first completion that equals prefix. Let Qt
488
+ # search the model, as checking each row from python is
489
+ # slow for large lists.
490
+ flags = Qt.MatchFlag.MatchFixedString
491
+ if prefix != prefix.lower():
492
+ # Handle lowercase options
493
+ flags |= Qt.MatchFlag.MatchCaseSensitive
494
+ matches = model.match(
495
+ index, Qt.ItemDataRole.DisplayRole, prefix, 1, flags
496
+ )
497
+ if matches:
498
+ index = matches[0]
495
499
 
496
500
  # Set completer current Row, so finishing the completer will use
497
501
  # correct text
@@ -504,10 +508,7 @@ class ConsolePrEdit(ConsoleBase):
504
508
 
505
509
  # show the completer for the rect
506
510
  rect = self.cursorRect()
507
- rect.setWidth(
508
- completer.popup().sizeHintForColumn(0)
509
- + completer.popup().verticalScrollBar().sizeHint().width()
510
- )
511
+ rect.setWidth(completer.popupWidth())
511
512
  completer.complete(rect)
512
513
 
513
514
  if completer.popup().isVisible():