PrEditor 1.3.0__tar.gz → 1.5.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.

Potentially problematic release.


This version of PrEditor might be problematic. Click here for more details.

Files changed (191) hide show
  1. {preditor-1.3.0 → preditor-1.5.0}/PKG-INFO +1 -1
  2. {preditor-1.3.0 → preditor-1.5.0}/PrEditor.egg-info/PKG-INFO +1 -1
  3. {preditor-1.3.0 → preditor-1.5.0}/preditor/debug.py +3 -3
  4. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/console.py +151 -40
  5. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/drag_tab_bar.py +4 -1
  6. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/group_tab_widget/__init__.py +14 -19
  7. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/group_tab_widget/grouped_tab_widget.py +26 -4
  8. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/group_tab_widget/one_tab_widget.py +42 -0
  9. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/logger_window_plugin.py +3 -0
  10. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/loggerwindow.py +28 -41
  11. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/workbox_mixin.py +101 -3
  12. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/workboxwidget.py +4 -1
  13. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/documenteditor.py +1 -9
  14. preditor-1.5.0/preditor/stream/director.py +144 -0
  15. {preditor-1.3.0 → preditor-1.5.0}/preditor/version.py +3 -3
  16. preditor-1.3.0/preditor/stream/director.py +0 -73
  17. {preditor-1.3.0 → preditor-1.5.0}/.coveragerc +0 -0
  18. {preditor-1.3.0 → preditor-1.5.0}/.github/ISSUE_TEMPLATE/BUG_REPORT.md +0 -0
  19. {preditor-1.3.0 → preditor-1.5.0}/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md +0 -0
  20. {preditor-1.3.0 → preditor-1.5.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  21. {preditor-1.3.0 → preditor-1.5.0}/.github/workflows/release.yml +0 -0
  22. {preditor-1.3.0 → preditor-1.5.0}/.github/workflows/static-analysis-and-test.yml +0 -0
  23. {preditor-1.3.0 → preditor-1.5.0}/.gitignore +0 -0
  24. {preditor-1.3.0 → preditor-1.5.0}/.pre-commit-config.yaml +0 -0
  25. {preditor-1.3.0 → preditor-1.5.0}/CODE_OF_CONDUCT.md +0 -0
  26. {preditor-1.3.0 → preditor-1.5.0}/CONTRIBUTING.md +0 -0
  27. {preditor-1.3.0 → preditor-1.5.0}/LICENSE +0 -0
  28. {preditor-1.3.0 → preditor-1.5.0}/MANIFEST.in +0 -0
  29. {preditor-1.3.0 → preditor-1.5.0}/PrEditor.egg-info/SOURCES.txt +0 -0
  30. {preditor-1.3.0 → preditor-1.5.0}/PrEditor.egg-info/dependency_links.txt +0 -0
  31. {preditor-1.3.0 → preditor-1.5.0}/PrEditor.egg-info/entry_points.txt +0 -0
  32. {preditor-1.3.0 → preditor-1.5.0}/PrEditor.egg-info/requires.txt +0 -0
  33. {preditor-1.3.0 → preditor-1.5.0}/PrEditor.egg-info/top_level.txt +0 -0
  34. {preditor-1.3.0 → preditor-1.5.0}/README.md +0 -0
  35. {preditor-1.3.0 → preditor-1.5.0}/examples/add_to_app.py +0 -0
  36. {preditor-1.3.0 → preditor-1.5.0}/examples/output_capture_and_show.py +0 -0
  37. {preditor-1.3.0 → preditor-1.5.0}/preditor/__init__.py +0 -0
  38. {preditor-1.3.0 → preditor-1.5.0}/preditor/__main__.py +0 -0
  39. {preditor-1.3.0 → preditor-1.5.0}/preditor/about_module.py +0 -0
  40. {preditor-1.3.0 → preditor-1.5.0}/preditor/cli.py +0 -0
  41. {preditor-1.3.0 → preditor-1.5.0}/preditor/config.py +0 -0
  42. {preditor-1.3.0 → preditor-1.5.0}/preditor/contexts.py +0 -0
  43. {preditor-1.3.0 → preditor-1.5.0}/preditor/cores/__init__.py +0 -0
  44. {preditor-1.3.0 → preditor-1.5.0}/preditor/cores/core.py +0 -0
  45. {preditor-1.3.0 → preditor-1.5.0}/preditor/dccs/.hab.json +0 -0
  46. {preditor-1.3.0 → preditor-1.5.0}/preditor/dccs/maya/PrEditor_maya.mod +0 -0
  47. {preditor-1.3.0 → preditor-1.5.0}/preditor/dccs/maya/README.md +0 -0
  48. {preditor-1.3.0 → preditor-1.5.0}/preditor/dccs/maya/plug-ins/PrEditor_maya.py +0 -0
  49. {preditor-1.3.0 → preditor-1.5.0}/preditor/dccs/studiomax/PackageContents.xml +0 -0
  50. {preditor-1.3.0 → preditor-1.5.0}/preditor/dccs/studiomax/PrEditor-PrEditor_Show.mcr +0 -0
  51. {preditor-1.3.0 → preditor-1.5.0}/preditor/dccs/studiomax/README.md +0 -0
  52. {preditor-1.3.0 → preditor-1.5.0}/preditor/dccs/studiomax/preditor.ms +0 -0
  53. {preditor-1.3.0 → preditor-1.5.0}/preditor/dccs/studiomax/preditor_menu.mnx +0 -0
  54. {preditor-1.3.0 → preditor-1.5.0}/preditor/delayable_engine/__init__.py +0 -0
  55. {preditor-1.3.0 → preditor-1.5.0}/preditor/delayable_engine/delayables.py +0 -0
  56. {preditor-1.3.0 → preditor-1.5.0}/preditor/enum.py +0 -0
  57. {preditor-1.3.0 → preditor-1.5.0}/preditor/excepthooks.py +0 -0
  58. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/__init__.py +0 -0
  59. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/app.py +0 -0
  60. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/codehighlighter.py +0 -0
  61. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/completer.py +0 -0
  62. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/dialog.py +0 -0
  63. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/editor_chooser.py +0 -0
  64. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/errordialog.py +0 -0
  65. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/find_files.py +0 -0
  66. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/fuzzy_search/__init__.py +0 -0
  67. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/fuzzy_search/fuzzy_search.py +0 -0
  68. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/group_tab_widget/grouped_tab_menu.py +0 -0
  69. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/group_tab_widget/grouped_tab_models.py +0 -0
  70. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/level_buttons.py +0 -0
  71. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/logger_window_handler.py +0 -0
  72. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/newtabwidget.py +0 -0
  73. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/set_text_editor_path_dialog.py +0 -0
  74. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/status_label.py +0 -0
  75. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/suggest_path_quotes_dialog.py +0 -0
  76. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/ui/editor_chooser.ui +0 -0
  77. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/ui/errordialog.ui +0 -0
  78. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/ui/find_files.ui +0 -0
  79. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/ui/loggerwindow.ui +0 -0
  80. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/ui/set_text_editor_path_dialog.ui +0 -0
  81. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/ui/suggest_path_quotes_dialog.ui +0 -0
  82. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/window.py +0 -0
  83. {preditor-1.3.0 → preditor-1.5.0}/preditor/gui/workbox_text_edit.py +0 -0
  84. {preditor-1.3.0 → preditor-1.5.0}/preditor/logging_config.py +0 -0
  85. {preditor-1.3.0 → preditor-1.5.0}/preditor/osystem.py +0 -0
  86. {preditor-1.3.0 → preditor-1.5.0}/preditor/plugins.py +0 -0
  87. {preditor-1.3.0 → preditor-1.5.0}/preditor/prefs.py +0 -0
  88. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/environment_variables.html +0 -0
  89. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/error_mail.html +0 -0
  90. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/error_mail_inline.html +0 -0
  91. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/README.md +0 -0
  92. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/arrow_forward.png +0 -0
  93. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/check-bold.png +0 -0
  94. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/chevron-down.png +0 -0
  95. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/chevron-up.png +0 -0
  96. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/close-thick.png +0 -0
  97. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/comment-edit.png +0 -0
  98. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/content-copy.png +0 -0
  99. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/content-cut.png +0 -0
  100. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/content-duplicate.png +0 -0
  101. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/content-paste.png +0 -0
  102. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/content-save.png +0 -0
  103. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/debug_disabled.png +0 -0
  104. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/eye-check.png +0 -0
  105. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/file-plus.png +0 -0
  106. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/file-remove.png +0 -0
  107. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/format-align-left.png +0 -0
  108. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/format-letter-case-lower.png +0 -0
  109. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/format-letter-case-upper.png +0 -0
  110. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/format-letter-case.svg +0 -0
  111. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/information.png +0 -0
  112. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/logging_critical.png +0 -0
  113. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/logging_custom.png +0 -0
  114. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/logging_debug.png +0 -0
  115. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/logging_error.png +0 -0
  116. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/logging_info.png +0 -0
  117. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/logging_not_set.png +0 -0
  118. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/logging_warning.png +0 -0
  119. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/marker.png +0 -0
  120. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/play.png +0 -0
  121. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/playlist-play.png +0 -0
  122. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/plus-minus-variant.png +0 -0
  123. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/preditor.ico +0 -0
  124. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/preditor.png +0 -0
  125. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/preditor.psd +0 -0
  126. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/preditor.svg +0 -0
  127. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/regex.svg +0 -0
  128. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/restart.svg +0 -0
  129. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/skip-forward-outline.png +0 -0
  130. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/skip-next-outline.png +0 -0
  131. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/skip-next.png +0 -0
  132. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/skip-previous.png +0 -0
  133. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/subdirectory-arrow-right.png +0 -0
  134. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/text-search-variant.png +0 -0
  135. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/img/warning-big.png +0 -0
  136. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/lang/python.json +0 -0
  137. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/settings.ini +0 -0
  138. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/stylesheet/Bright.css +0 -0
  139. {preditor-1.3.0 → preditor-1.5.0}/preditor/resource/stylesheet/Dark.css +0 -0
  140. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/__init__.py +0 -0
  141. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/delayables/__init__.py +0 -0
  142. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/delayables/smart_highlight.py +0 -0
  143. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/delayables/spell_check.py +0 -0
  144. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/finddialog.py +0 -0
  145. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lang/__init__.py +0 -0
  146. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lang/config/bash.ini +0 -0
  147. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lang/config/batch.ini +0 -0
  148. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lang/config/cpp.ini +0 -0
  149. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lang/config/css.ini +0 -0
  150. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lang/config/eyeonscript.ini +0 -0
  151. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lang/config/html.ini +0 -0
  152. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lang/config/javascript.ini +0 -0
  153. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lang/config/lua.ini +0 -0
  154. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lang/config/maxscript.ini +0 -0
  155. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lang/config/mel.ini +0 -0
  156. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lang/config/mu.ini +0 -0
  157. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lang/config/nsi.ini +0 -0
  158. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lang/config/perl.ini +0 -0
  159. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lang/config/puppet.ini +0 -0
  160. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lang/config/python.ini +0 -0
  161. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lang/config/ruby.ini +0 -0
  162. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lang/config/sql.ini +0 -0
  163. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lang/config/xml.ini +0 -0
  164. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lang/config/yaml.ini +0 -0
  165. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lang/language.py +0 -0
  166. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lexers/__init__.py +0 -0
  167. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lexers/cpplexer.py +0 -0
  168. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lexers/javascriptlexer.py +0 -0
  169. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lexers/maxscriptlexer.py +0 -0
  170. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lexers/mellexer.py +0 -0
  171. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lexers/mulexer.py +0 -0
  172. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/lexers/pythonlexer.py +0 -0
  173. {preditor-1.3.0 → preditor-1.5.0}/preditor/scintilla/ui/finddialog.ui +0 -0
  174. {preditor-1.3.0 → preditor-1.5.0}/preditor/settings.py +0 -0
  175. {preditor-1.3.0 → preditor-1.5.0}/preditor/stream/__init__.py +0 -0
  176. {preditor-1.3.0 → preditor-1.5.0}/preditor/stream/manager.py +0 -0
  177. {preditor-1.3.0 → preditor-1.5.0}/preditor/streamhandler_helper.py +0 -0
  178. {preditor-1.3.0 → preditor-1.5.0}/preditor/utils/__init__.py +0 -0
  179. {preditor-1.3.0 → preditor-1.5.0}/preditor/utils/cute.py +0 -0
  180. {preditor-1.3.0 → preditor-1.5.0}/preditor/utils/stylesheets.py +0 -0
  181. {preditor-1.3.0 → preditor-1.5.0}/preditor/utils/text_search.py +0 -0
  182. {preditor-1.3.0 → preditor-1.5.0}/preditor/weakref.py +0 -0
  183. {preditor-1.3.0 → preditor-1.5.0}/pyproject.toml +0 -0
  184. {preditor-1.3.0 → preditor-1.5.0}/requirements-cli.txt +0 -0
  185. {preditor-1.3.0 → preditor-1.5.0}/requirements-dev.txt +0 -0
  186. {preditor-1.3.0 → preditor-1.5.0}/requirements-qsci5.txt +0 -0
  187. {preditor-1.3.0 → preditor-1.5.0}/requirements-qsci6.txt +0 -0
  188. {preditor-1.3.0 → preditor-1.5.0}/requirements-shortcut.txt +0 -0
  189. {preditor-1.3.0 → preditor-1.5.0}/requirements.txt +0 -0
  190. {preditor-1.3.0 → preditor-1.5.0}/setup.cfg +0 -0
  191. {preditor-1.3.0 → preditor-1.5.0}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PrEditor
3
- Version: 1.3.0
3
+ Version: 1.5.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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PrEditor
3
- Version: 1.3.0
3
+ Version: 1.5.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
@@ -32,9 +32,9 @@ class FileLogger:
32
32
  return msg.format(today=datetime.datetime.today(), version=sys.version)
33
33
 
34
34
  def write(self, msg):
35
- f = open(self._logfile, 'a')
36
- f.write(msg)
37
- f.close()
35
+ with open(self._logfile, 'a', encoding="utf-8") as f:
36
+ f.write(msg)
37
+
38
38
  if self._print:
39
39
  self._stdhandle.write(msg)
40
40
 
@@ -53,6 +53,16 @@ class ConsolePrEdit(QTextEdit):
53
53
  # If populated, also write to this interface
54
54
  self.outputPipe = None
55
55
 
56
+ # For workboxes, use this regex pattern, so we can extract workboxName
57
+ # and lineNum
58
+ pattern = r'File "<Workbox(?:Selection)?>:(?P<workboxName>.*)", '
59
+ pattern += r'line (?P<lineNum>\d{1,6}), in'
60
+ self.workbox_pattern = re.compile(pattern)
61
+
62
+ # Define a pattern to capture info from tracebacks
63
+ pattern = r'File "(?P<filename>.*)", line (?P<lineNum>\d{1,10}), in'
64
+ self.traceback_pattern = re.compile(pattern)
65
+
56
66
  self._consolePrompt = '>>> '
57
67
  # Note: Changing _outputPrompt may require updating resource\lang\python.xml
58
68
  # If still using a #
@@ -90,7 +100,7 @@ class ConsolePrEdit(QTextEdit):
90
100
  # to the console and free up the memory consumed by previous writes as we
91
101
  # assume this is likely to be the only callback added to the manager.
92
102
  self.stream_manager.add_callback(
93
- self.write, replay=True, disable_writes=True, clear=True
103
+ self.pre_write, replay=True, disable_writes=True, clear=True
94
104
  )
95
105
  # Store the current outputs
96
106
  self.stdout = sys.stdout
@@ -122,6 +132,11 @@ class ConsolePrEdit(QTextEdit):
122
132
  if not self.cursorWidth():
123
133
  self.setCursorWidth(1)
124
134
 
135
+ # The act of changing from no scroll bar to a scroll bar can add up to 1
136
+ # second of time to the process of outputting text, so, just always have
137
+ # it on.
138
+ self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
139
+
125
140
  def doubleSingleShotSetScrollValue(self, origPercent):
126
141
  """This double QTimer.singleShot monkey business seems to be the only way
127
142
  to get scroll.maximum() to update properly so that we calc newValue
@@ -239,7 +254,7 @@ class ConsolePrEdit(QTextEdit):
239
254
  # info is a comma separated string, in the form: "filename, workboxIdx, lineNum"
240
255
  info = self.anchor.split(', ')
241
256
  modulePath = info[0]
242
- workboxIndex = info[1]
257
+ workboxName = info[1]
243
258
  lineNum = info[2]
244
259
 
245
260
  # fetch info from LoggerWindow
@@ -250,23 +265,27 @@ class ConsolePrEdit(QTextEdit):
250
265
  cmdTempl = window.textEditorCmdTempl
251
266
 
252
267
  # Bail if not setup properly
253
- msg = "Cannot use traceback hyperlink. "
254
- if not exePath:
255
- msg += "No text editor path defined."
256
- print(msg)
257
- return
258
- if not os.path.exists(exePath):
259
- msg += "Text editor executable does not exist: {}".format(exePath)
260
- print(msg)
261
- return
262
- if not cmdTempl:
263
- msg += "No text editor Command Prompt command template defined."
264
- print(msg)
265
- return
266
- if modulePath and not os.path.exists(modulePath):
267
- msg += "Specified module path does not exist: {}".format(modulePath)
268
- print(msg)
269
- return
268
+ if workboxName is None:
269
+ msg = (
270
+ "Cannot use traceback hyperlink (Correct the path with Options "
271
+ "> Set Preferred Text Editor Path).\n"
272
+ )
273
+ if not exePath:
274
+ msg += "No text editor path defined."
275
+ print(msg)
276
+ return
277
+ if not os.path.exists(exePath):
278
+ msg += "Text editor executable does not exist: {}".format(exePath)
279
+ print(msg)
280
+ return
281
+ if not cmdTempl:
282
+ msg += "No text editor Command Prompt command template defined."
283
+ print(msg)
284
+ return
285
+ if modulePath and not os.path.exists(modulePath):
286
+ msg += "Specified module path does not exist: {}".format(modulePath)
287
+ print(msg)
288
+ return
270
289
 
271
290
  if modulePath:
272
291
  # Check if cmdTempl filepaths aren't wrapped in double=quotes to handle
@@ -296,12 +315,9 @@ class ConsolePrEdit(QTextEdit):
296
315
  msg = "The provided text editor command template is not valid:\n {}"
297
316
  msg = msg.format(cmdTempl)
298
317
  print(msg)
299
- elif workboxIndex is not None:
300
- group, editor = workboxIndex.split(',')
318
+ elif workboxName is not None:
319
+ workbox = window.workbox_for_name(workboxName, visible=True)
301
320
  lineNum = int(lineNum)
302
- workbox = window.uiWorkboxTAB.set_current_groups_from_index(
303
- int(group), int(editor)
304
- )
305
321
  workbox.__goto_line__(lineNum)
306
322
  workbox.setFocus()
307
323
 
@@ -373,7 +389,34 @@ class ConsolePrEdit(QTextEdit):
373
389
  """returns the completer instance that is associated with this editor"""
374
390
  return self._completer
375
391
 
376
- def executeString(self, commandText, filename='<ConsolePrEdit>', extraPrint=True):
392
+ def getWorkboxLine(self, name, lineNum):
393
+ """Python 3 does not include in tracebacks the code line if it comes from
394
+ stdin, which is the case for PrEditor workboxes, so we fake it. This method
395
+ will return the line of code at lineNum, from the workbox with the provided
396
+ name.
397
+
398
+ Args:
399
+ name (str): The name of the workbox from which to get a line of code
400
+ lineNum (int): The number of the line to return
401
+
402
+ Returns:
403
+ txt (str): The line of text found
404
+ """
405
+ workbox = self.window().workbox_for_name(name)
406
+ if not workbox:
407
+ return None
408
+ if lineNum > workbox.lines():
409
+ return None
410
+ txt = workbox.text(lineNum).strip() + "\n"
411
+ return txt
412
+
413
+ def executeString(
414
+ self, commandText, consoleLine=None, filename='<ConsolePrEdit>', extraPrint=True
415
+ ):
416
+ # These vars helps with faking code lines in tracebacks for stdin input, which
417
+ # workboxes are, and py3 doesn't include in the traceback
418
+ self.consoleLine = consoleLine or ""
419
+
377
420
  if self.clearExecutionTime is not None:
378
421
  self.clearExecutionTime()
379
422
  cursor = self.textCursor()
@@ -428,6 +471,10 @@ class ConsolePrEdit(QTextEdit):
428
471
 
429
472
  def executeCommand(self):
430
473
  """executes the current line of code"""
474
+
475
+ # Not using workbox, so clear this
476
+ self.consoleLine = ""
477
+
431
478
  # grab the command from the line
432
479
  block = self.textCursor().block().text()
433
480
  p = '{prompt}(.*)'.format(prompt=re.escape(self.prompt()))
@@ -450,7 +497,9 @@ class ConsolePrEdit(QTextEdit):
450
497
  self._prevCommands = self._prevCommands[-1 * self._prevCommandsMax :]
451
498
 
452
499
  # evaluate the command
453
- cmdresult, wasEval = self.executeString(commandText)
500
+ cmdresult, wasEval = self.executeString(
501
+ commandText, consoleLine=commandText
502
+ )
454
503
 
455
504
  # print the resulting commands
456
505
  if cmdresult is not None:
@@ -787,28 +836,65 @@ class ConsolePrEdit(QTextEdit):
787
836
  """Determine if txt is a File-info line from a traceback, and if so, return info
788
837
  dict.
789
838
  """
790
- lineMarker = '", line '
839
+
791
840
  ret = None
841
+ if not txt.lstrip().startswith("File "):
842
+ return ret
843
+
844
+ match = self.traceback_pattern.search(txt)
845
+ if match:
846
+ filename = match.groupdict().get('filename')
847
+ lineNum = match.groupdict().get('lineNum')
848
+ fileStart = txt.find(filename)
849
+ fileEnd = fileStart + len(filename)
792
850
 
793
- filenameEnd = txt.find(lineMarker)
794
- if txt[:8] == ' File "' and filenameEnd >= 0:
795
- filename = txt[8:filenameEnd]
796
- lineNumStart = filenameEnd + len(lineMarker)
797
- lineNumEnd = txt.find(',', lineNumStart)
798
- if lineNumEnd == -1:
799
- lineNumEnd = len(txt)
800
- lineNum = txt[lineNumStart:lineNumEnd]
801
851
  ret = {
802
852
  'filename': filename,
803
- 'fileStart': 8,
804
- 'fileEnd': filenameEnd,
853
+ 'fileStart': fileStart,
854
+ 'fileEnd': fileEnd,
805
855
  'lineNum': lineNum,
806
856
  }
807
-
808
857
  return ret
809
858
 
859
+ @staticmethod
860
+ def getIndentForCodeTracebackLine(msg):
861
+ """Determine the indentation to recreate traceback lines
862
+
863
+ Args:
864
+ msg (str): The traceback line
865
+
866
+ Returns:
867
+ indent (str): A string of zero or more spaces used for indentation
868
+ """
869
+ indent = ""
870
+ match = re.match(r"^ *", msg)
871
+ if match:
872
+ indent = match.group() * 2
873
+ return indent
874
+
875
+ def pre_write(self, msg, error=False):
876
+ """In order to make a stack-trace provide clickable hyperlinks, it must be sent
877
+ to self.write line-by-line, like a actual exception traceback is. So, we check
878
+ if msg has the stack marker str, if so, send it line by line, otherwise, just
879
+ pass msg on to self.write.
880
+ """
881
+ stack_marker = "Stack (most recent call last)"
882
+ index = msg.find(stack_marker)
883
+ has_stack_marker = index > -1
884
+
885
+ if has_stack_marker:
886
+ lines = msg.split("\n")
887
+ for line in lines:
888
+ line = "{}\n".format(line)
889
+ self.write(line, error=error)
890
+ else:
891
+ self.write(msg, error=error)
892
+
810
893
  def write(self, msg, error=False):
811
894
  """write the message to the logger"""
895
+ if not msg:
896
+ return
897
+
812
898
  # Convert the stream_manager's stream to the boolean value this function expects
813
899
  error = error == stream.STDERR
814
900
  # Check that we haven't been garbage collected before trying to write.
@@ -859,25 +945,50 @@ class ConsolePrEdit(QTextEdit):
859
945
  # display normal output. Exclude ConsolePrEdits
860
946
  info = info if info else self.parseErrorHyperLinkInfo(msg)
861
947
  filename = info.get("filename", "") if info else ""
948
+
949
+ # Determine if this is a workbox line of code, or code run directly
950
+ # in the console
951
+ isWorkbox = '<WorkboxSelection>' in filename or '<Workbox>' in filename
862
952
  isConsolePrEdit = '<ConsolePrEdit>' in filename
863
953
 
954
+ # Starting in Python 3, tracebacks don't include the code executed
955
+ # for stdin, so workbox code won't appear. This attempts to include
956
+ # it.
957
+ if isWorkbox:
958
+ match = self.workbox_pattern.search(msg)
959
+ workboxName = match.groupdict().get("workboxName")
960
+ lineNum = int(match.groupdict().get("lineNum")) - 1
961
+
962
+ workboxLine = self.getWorkboxLine(workboxName, lineNum)
963
+ if workboxLine:
964
+ indent = self.getIndentForCodeTracebackLine(msg)
965
+ msg = "{}{}{}".format(msg, indent, workboxLine)
966
+
967
+ elif isConsolePrEdit:
968
+ consoleLine = self.consoleLine
969
+ indent = self.getIndentForCodeTracebackLine(msg)
970
+ msg = "{}{}{}\n".format(msg, indent, consoleLine)
971
+
864
972
  # To make it easier to see relevant lines of a traceback, optionally insert
865
973
  # a newline separating internal PrEditor code from the code run by user.
866
974
  if self.addSepNewline:
867
975
  if sepPreditorTrace:
868
- msg += "\n"
976
+ msg = "\n" + msg
869
977
  self.addSepNewline = False
870
978
 
871
979
  preditorCalls = ("cmdresult = e", "exec(compiled,")
872
980
  if msg.strip().startswith(preditorCalls):
873
981
  self.addSepNewline = True
874
982
 
983
+ # Error tracebacks and logging.stack_info supply msg's differently,
984
+ # so modify it here, so we get consistent results.
985
+ msg = msg.replace("\n\n", "\n")
986
+
875
987
  if info and doHyperlink and not isConsolePrEdit:
876
988
  fileStart = info.get("fileStart")
877
989
  fileEnd = info.get("fileEnd")
878
990
  lineNum = info.get("lineNum")
879
991
 
880
- isWorkbox = '<WorkboxSelection>' in filename or '<Workbox>' in filename
881
992
  if isWorkbox:
882
993
  split = filename.split(':')
883
994
  workboxIdx = split[-1]
@@ -139,8 +139,11 @@ class DragTabBar(QTabBar):
139
139
  """Used by the tab_menu to rename the tab at index `_context_menu_tab`."""
140
140
  if self._context_menu_tab != -1:
141
141
  current = self.tabText(self._context_menu_tab)
142
- msg = 'Rename the {} tab to:'.format(current)
142
+ msg = 'Rename the {} tab to (new name must be unique):'.format(current)
143
+
143
144
  name, success = QInputDialog.getText(self, 'Rename Tab', msg, text=current)
145
+ name = self.parent().get_next_available_tab_name(name)
146
+
144
147
  if success:
145
148
  self.setTabText(self._context_menu_tab, name)
146
149
 
@@ -1,7 +1,6 @@
1
1
  from __future__ import absolute_import
2
2
 
3
3
  import os
4
- import re
5
4
 
6
5
  from Qt.QtCore import Qt
7
6
  from Qt.QtGui import QIcon
@@ -46,6 +45,9 @@ class GroupTabWidget(OneTabWidget):
46
45
  self.editor_cls = WorkboxTextEdit
47
46
  self.core_name = core_name
48
47
  self.setStyleSheet(DEFAULT_STYLE_SHEET)
48
+
49
+ self.default_title = 'Group01'
50
+
49
51
  corner = QWidget(self)
50
52
  lyt = QHBoxLayout(corner)
51
53
  lyt.setSpacing(0)
@@ -69,7 +71,7 @@ class GroupTabWidget(OneTabWidget):
69
71
  self.uiCornerBTN = corner
70
72
  self.setCornerWidget(self.uiCornerBTN, Qt.Corner.TopRightCorner)
71
73
 
72
- def add_new_tab(self, group, title="Workbox", group_fmt=None):
74
+ def add_new_tab(self, group, title=None, prefs=None):
73
75
  """Adds a new tab to the requested group, creating the group if the group
74
76
  doesn't exist.
75
77
 
@@ -80,9 +82,6 @@ class GroupTabWidget(OneTabWidget):
80
82
  If True is passed, then the current group tab is used.
81
83
  title (str, optional): The name to give the newly created tab inside
82
84
  the group.
83
- group_fmt(str, optional): If None is passed to group, this string is
84
- used to search for existing tabs to calculate the last number
85
- and generate the new group tab name.
86
85
 
87
86
  Returns:
88
87
  GroupedTabWidget: The tab group for this group.
@@ -90,21 +89,14 @@ class GroupTabWidget(OneTabWidget):
90
89
  """
91
90
  parent = None
92
91
  if not group:
93
- if group_fmt is None:
94
- group_fmt = r'Group {}'
95
- last = 0
96
- for i in range(self.count()):
97
- match = re.match(group_fmt.format(r'(\d+)'), self.tabText(i))
98
- if match:
99
- last = max(last, int(match.group(1)))
100
- group = group_fmt.format(last + 1)
92
+ group = self.get_next_available_tab_name(self.default_title)
101
93
  elif group is True:
102
94
  group = self.currentIndex()
103
95
  if isinstance(group, int):
104
96
  group_title = self.tabText(group)
105
97
  parent = self.widget(group)
106
98
  elif isinstance(group, str):
107
- group_title = group
99
+ group_title = group.replace(" ", "_")
108
100
  index = self.index_for_text(group)
109
101
  if index != -1:
110
102
  parent = self.widget(index)
@@ -146,7 +138,7 @@ class GroupTabWidget(OneTabWidget):
146
138
  self,
147
139
  'Close all editors under this tab?',
148
140
  'Are you sure you want to close all tabs under the "{}" tab?'.format(
149
- self.tabText(self.currentIndex())
141
+ self.tabText(index)
150
142
  ),
151
143
  QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.Cancel,
152
144
  )
@@ -154,11 +146,11 @@ class GroupTabWidget(OneTabWidget):
154
146
  # Clean up all temp files created by this group's editors if they
155
147
  # are not using actual saved files.
156
148
  tab_widget = self.widget(self.currentIndex())
157
- for index in range(tab_widget.count()):
158
- editor = tab_widget.widget(index)
149
+ for editor_index in range(tab_widget.count()):
150
+ editor = tab_widget.widget(editor_index)
159
151
  editor.__remove_tempfile__()
160
152
 
161
- super(GroupTabWidget, self).close_tab(self.currentIndex())
153
+ super(GroupTabWidget, self).close_tab(index)
162
154
 
163
155
  def current_groups_widget(self):
164
156
  """Returns the current widget of the currently selected group or None."""
@@ -166,7 +158,8 @@ class GroupTabWidget(OneTabWidget):
166
158
  if editor_tab:
167
159
  return editor_tab.currentWidget()
168
160
 
169
- def default_tab(self, title='Group 1'):
161
+ def default_tab(self, title=None, prefs=None):
162
+ title = title or self.default_title
170
163
  widget = GroupedTabWidget(
171
164
  parent=self,
172
165
  editor_kwargs=self.editor_kwargs,
@@ -223,6 +216,8 @@ class GroupTabWidget(OneTabWidget):
223
216
  group_name = group['name']
224
217
  tab_widget = None
225
218
 
219
+ group_name = self.get_next_available_tab_name(group_name)
220
+
226
221
  for tab in group.get('tabs', []):
227
222
  # Only add this tab if, there is data on disk to load. The user can
228
223
  # open multiple instances of PrEditor using the same prefs. The
@@ -27,7 +27,12 @@ class GroupedTabWidget(OneTabWidget):
27
27
  self.uiCornerBTN.released.connect(lambda: self.add_new_editor())
28
28
  self.setCornerWidget(self.uiCornerBTN, Qt.Corner.TopRightCorner)
29
29
 
30
- def add_new_editor(self, title="Workbox"):
30
+ self.default_title = "Workbox01"
31
+
32
+ def add_new_editor(self, title=None, prefs=None):
33
+ title = title or self.default_title
34
+
35
+ title = self.get_next_available_tab_name(title)
31
36
  editor, title = self.default_tab(title)
32
37
  index = self.addTab(editor, title)
33
38
  self.setCurrentIndex(index)
@@ -45,11 +50,18 @@ class GroupedTabWidget(OneTabWidget):
45
50
  self, 'Tab can not be closed.', msg, QMessageBox.StandardButton.Ok
46
51
  )
47
52
  return
53
+
54
+ workbox = self.widget(index)
55
+ name = workbox.__workbox_name__()
56
+ msg = (
57
+ f"Would you like to donate the contents of tab\n{name}\nto the "
58
+ "/dev/null fund for wayward code?"
59
+ )
60
+
48
61
  ret = QMessageBox.question(
49
62
  self,
50
63
  'Donate to the cause?',
51
- "Would you like to donate this tabs contents to the /dev/null fund "
52
- "for wayward code?",
64
+ msg,
53
65
  QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.Cancel,
54
66
  )
55
67
  if ret == QMessageBox.StandardButton.Yes:
@@ -59,7 +71,8 @@ class GroupedTabWidget(OneTabWidget):
59
71
 
60
72
  super(GroupedTabWidget, self).close_tab(index)
61
73
 
62
- def default_tab(self, title='Workbox'):
74
+ def default_tab(self, title=None, prefs=None):
75
+ title = title or self.default_title
63
76
  kwargs = self.editor_kwargs if self.editor_kwargs else {}
64
77
  editor = self.editor_cls(parent=self, core_name=self.core_name, **kwargs)
65
78
  return editor, title
@@ -76,5 +89,14 @@ class GroupedTabWidget(OneTabWidget):
76
89
  if hasattr(self.window(), "setWorkboxFontBasedOnConsole"):
77
90
  self.window().setWorkboxFontBasedOnConsole()
78
91
 
92
+ def tab_widget(self):
93
+ """Return the tab widget which contains this group tab
94
+
95
+ Returns:
96
+ self._tab_widget (GroupTabWidget): The tab widget which contains
97
+ this workbox
98
+ """
99
+ return self.parent().parent()
100
+
79
101
  def update_closable_tabs(self):
80
102
  self.setTabsClosable(self.count() != 1)
@@ -1,7 +1,11 @@
1
1
  from __future__ import absolute_import
2
2
 
3
+ import re
4
+
3
5
  from Qt.QtWidgets import QTabWidget
4
6
 
7
+ TAB_ITERATION_PATTERN = re.compile(r"(\d+)(?!.*\d)")
8
+
5
9
 
6
10
  class OneTabWidget(QTabWidget):
7
11
  """A QTabWidget that shows the close button only if there is more than one
@@ -17,6 +21,44 @@ class OneTabWidget(QTabWidget):
17
21
  super(OneTabWidget, self).__init__(*args, **kwargs)
18
22
  self.tabCloseRequested.connect(self.close_tab)
19
23
 
24
+ def get_next_available_tab_name(self, name):
25
+ """Get the next available tab name, incrementing an iteration if needed.
26
+
27
+ Args:
28
+ name (str): The desired name
29
+
30
+ Returns:
31
+ name (str): The name, or updated name if needed
32
+ """
33
+ name = name.replace(" ", "_")
34
+
35
+ existing_names = [self.tabText(i) for i in range(self.count())]
36
+
37
+ # Use regex to find the last set of digits. If found, the base name is
38
+ # a slice of name minus the digits string. Otherwise, the base name is
39
+ # the full name and iteration is zero.
40
+ match = TAB_ITERATION_PATTERN.search(name)
41
+ if match:
42
+ # We found trailing digits, so slice to get base name, and convert
43
+ # iteration to int
44
+ iter_str = match.group()
45
+ base = name[: -len(iter_str)]
46
+ iteration = int(iter_str)
47
+ else:
48
+ # No trailing digits found, so base name is full name and iteration
49
+ # is zero.
50
+ base = name
51
+ iteration = 0
52
+
53
+ if name in existing_names:
54
+ for _ in range(99):
55
+ iteration += 1
56
+ new_iter_str = str(iteration).zfill(2)
57
+ name = base + new_iter_str
58
+ if name not in existing_names:
59
+ break
60
+ return name
61
+
20
62
  def addTab(self, *args, **kwargs): # noqa: N802
21
63
  ret = super(OneTabWidget, self).addTab(*args, **kwargs)
22
64
  self.update_closable_tabs()
@@ -13,6 +13,9 @@ class LoggerWindowPlugin:
13
13
  def __init__(self, parent):
14
14
  self.parent = parent
15
15
 
16
+ def updateWindowTitle(self, title):
17
+ return title
18
+
16
19
  def record_prefs(self, name):
17
20
  """Returns any prefs to save with the PrEditor's preferences.
18
21
 
@@ -48,6 +48,7 @@ from .completer import CompleterMode
48
48
  from .level_buttons import LoggingLevelButton
49
49
  from .set_text_editor_path_dialog import SetTextEditorPathDialog
50
50
  from .status_label import StatusLabel
51
+ from .workbox_mixin import WorkboxName
51
52
 
52
53
  logger = logging.getLogger(__name__)
53
54
 
@@ -59,31 +60,6 @@ class WorkboxPages:
59
60
  Workboxes = 1
60
61
 
61
62
 
62
- class WorkboxName(str):
63
- """The joined name of a workbox `group/workbox` with access to its parts.
64
-
65
- This subclass provides properties for the group and workbox values separately.
66
- """
67
-
68
- def __new__(cls, group, workbox):
69
- txt = "/".join((group, workbox))
70
- ret = super().__new__(cls, txt)
71
- # Preserve the imitable nature of str's by using properties without setters.
72
- ret._group = group
73
- ret._workbox = workbox
74
- return ret
75
-
76
- @property
77
- def group(self):
78
- """The tab name of the group tab that contains the workbox."""
79
- return self._group
80
-
81
- @property
82
- def workbox(self):
83
- """The workbox of the tab for this workbox inside of the group."""
84
- return self._workbox
85
-
86
-
87
63
  class LoggerWindow(Window):
88
64
  _instance = None
89
65
  styleSheetChanged = Signal(str)
@@ -290,10 +266,9 @@ class LoggerWindow(Window):
290
266
 
291
267
  self.dont_ask_again = []
292
268
 
293
- # Load any plugins that modify the LoggerWindow
294
- self.plugins = {}
295
- for name, plugin in plugins.loggerwindow():
296
- self.plugins[name] = plugin(self)
269
+ # Load any plugins, and set window title
270
+ self.loadPlugins()
271
+ self.setWindowTitle(self.defineWindowTitle())
297
272
 
298
273
  self.restorePrefs()
299
274
 
@@ -306,17 +281,6 @@ class LoggerWindow(Window):
306
281
  action.triggered.connect(partial(self.setStyleSheet, style_name))
307
282
 
308
283
  self.uiConsoleTOOLBAR.show()
309
- loggerName = QApplication.instance().translate(
310
- 'PrEditorWindow', DEFAULT_CORE_NAME
311
- )
312
- self.setWindowTitle(
313
- '{} - {} - {} {}-bit'.format(
314
- loggerName,
315
- self.name,
316
- '{}.{}.{}'.format(*sys.version_info[:3]),
317
- osystem.getPointerSize(),
318
- )
319
- )
320
284
 
321
285
  self.setWorkboxFontBasedOnConsole()
322
286
  self.setEditorChooserFontBasedOnConsole()
@@ -357,6 +321,29 @@ class LoggerWindow(Window):
357
321
 
358
322
  self.update_workbox_stack()
359
323
 
324
+ def loadPlugins(self):
325
+ """Load any plugins that modify the LoggerWindow."""
326
+ self.plugins = {}
327
+ for name, plugin in plugins.loggerwindow():
328
+ if name not in self.plugins:
329
+ self.plugins[name] = plugin(self)
330
+
331
+ def defineWindowTitle(self):
332
+ """Define the window title, including and info plugins may add."""
333
+
334
+ # Define the title
335
+ loggerName = QApplication.instance().translate(
336
+ 'PrEditorWindow', DEFAULT_CORE_NAME
337
+ )
338
+ pyVersion = '{}.{}.{}'.format(*sys.version_info[:3])
339
+ size = osystem.getPointerSize()
340
+ title = f"{loggerName} - {self.name} - {pyVersion} {size}-bit"
341
+
342
+ # Add any info plugins may add to title
343
+ for _name, plugin in self.plugins.items():
344
+ title = plugin.updateWindowTitle(title)
345
+ return title
346
+
360
347
  def comment_toggle(self):
361
348
  self.current_workbox().__comment_toggle__()
362
349
 
@@ -1046,7 +1033,7 @@ class LoggerWindow(Window):
1046
1033
  # Create timer to autohide status messages
1047
1034
  self.statusTimer = QTimer()
1048
1035
  self.statusTimer.setSingleShot(True)
1049
- self.statusTimer.setInterval(2000)
1036
+ self.statusTimer.setInterval(5000)
1050
1037
  self.statusTimer.timeout.connect(self.clearStatusText)
1051
1038
 
1052
1039
  def clearStatusText(self):